Working video player!

This commit is contained in:
Scott Duensing 2019-11-26 19:29:54 -06:00
parent 06632fbcd7
commit 913d0599af
22 changed files with 1934 additions and 3732 deletions

34
singe/common.h Normal file
View file

@ -0,0 +1,34 @@
/*
*
* Singe 2
* Copyright (C) 2019 Scott Duensing <scott@kangaroopunch.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifndef COMMON_H
#define COMMON_H
#define byte unsigned char
#define bool unsigned char
#define true 1
#define false 0
#endif // COMMON_H

View file

@ -20,319 +20,69 @@
*/
/*
* NOTES:
*
* - What if the video source audio isn't in stereo?
*
*/
#include <stdio.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_mixer.h>
#include <SDL2/SDL_ttf.h>
#include <ffms.h>
#include <manymouse.h>
#include "thirdparty/utarray.h"
#include "thirdparty/c-ringbuf/ringbuf.h"
#include "stddclmr.h"
#include "common.h"
#include "util.h"
#include "videoPlayer.h"
// /home/scott/code/singe2/videotest/Stargate.m4v
// /home/scott/code/singe2/videotest/TestClip_720x480_29.97fps_2000_frames.avi
#define byte unsigned char
#define bool unsigned char
#define true 1
#define false 0
#define AUDIO_RING_SIZE (64 * 1024)
#define AUDIO_SAMPLES (AUDIO_RING_SIZE / (16 /* average bytes per sample */ * 2 /* channels */))
#define AUDIO_SILENCE_SECONDS 2
#define AUDIO_CHUNK_MULTIPLIER 2
typedef struct AudioRingBufferS {
ringbuf_t ring;
size_t sizeTemp;
SDL_mutex *mutex;
} AudioRingBufferT;
void dequeueVideoAudio(int channel, void *stream, int len, void *udata);
void die(char *fmt, ...);
int FFMS_CC indexCallBack(int64_t Current, int64_t Total, void *ICPrivate);
void say(char *fmt, ...);
void dequeueVideoAudio(int channel, void *stream, int len, void *udata) {
int toCopy = len;
AudioRingBufferT *ring = (AudioRingBufferT *)udata;
(void)channel;
ring->sizeTemp = ringbuf_bytes_used(ring->ring); // We do this in case more space is used by the mixer while we're working on this
if (len > (int)ring->sizeTemp) {
toCopy = (int)ring->sizeTemp;
}
if (SDL_LockMutex(ring->mutex) < 0) die("Unable to lock mutex for ringbuf_memcpy_from");
ringbuf_memcpy_from(stream, ring->ring, (size_t)toCopy);
if (SDL_UnlockMutex(ring->mutex) < 0) die("Unable to unlock mutex for ringbuf_memcpy_from");
//say("Dequeued %d bytes of audio data. Buffer is now %d.", toCopy, ringbuf_bytes_used(ring->ring));
}
__attribute__((__format__(__printf__, 1, 0)))
void die(char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
printf("\n");
fflush(stderr);
exit(1);
}
int FFMS_CC indexCallBack(int64_t current, int64_t total, void *ICPrivate) {
static int lastPercent = 0;
int thisPercent = 0;
(void)ICPrivate;
if ((current == 0) && (total == 0)) {
lastPercent = 0; // Reset
} else {
thisPercent = (int)((double)current / (double)total * 100.0);
if (thisPercent != lastPercent) {
lastPercent = thisPercent;
say("Indexing: %d%%", thisPercent);
}
}
return 0;
}
__attribute__((__format__(__printf__, 1, 0)))
void say(char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);
printf("\n");
fflush(stdout);
}
int main(int argc, char *argv[]) {
int err = 0;
int flags = 0;
int audioTrack = -1;
int videoTrack = -1;
int pixelFormats[2];
int frame = 0;
int audioSampleSize = 0;
int audioSilenceChannel = -1;
bool running = true;
bool playing = true;
bool resetTime = false;
int64_t frameDeltaTime = 0;
int64_t lastFrameTime = 0;
int64_t timestamp = 0;
int64_t audioPosition = 0;
int64_t audioCount = 0;
Uint16 audioFormat = 0;
Uint32 lastTickTime = 0;
Uint32 audioSilenceSize = 0;
int thisFrame = -1;
int lastFrame = -1;
int videoHandle = -1;
char *filename = argv[1];
char errMsg[1024];
char indexName[1024];
byte *audioBuffer = NULL;
byte audioSampleBytes = 0;
byte *audioSilenceRaw = NULL;
AudioRingBufferT audioRingBuffer;
bool running = true;
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
Mix_Chunk *silenceChunk = NULL;
FFMS_Index *index = NULL;
FFMS_Indexer *indexer = NULL;
SDL_Texture *videoTexture = NULL;
FFMS_ErrorInfo errInfo;
FFMS_AudioSource *audioSource = NULL;
FFMS_VideoSource *videoSource = NULL;
const FFMS_AudioProperties *audioProps = NULL;
const FFMS_VideoProperties *videoProps = NULL;
const FFMS_Frame *propFrame = NULL;
const FFMS_TrackTimeBase *videoTimeBase = NULL;
const FFMS_Frame *frameData = NULL;
const FFMS_FrameInfo *frameInfo = NULL;
SDL_Texture *texture = NULL;
// Did we get a filename to open?
if (argc != 2) die("Usage: %s <filename>\n", argv[0]);
if (argc != 2) utilDie("Usage: %s <filename>\n", argv[0]);
// Init SDL
err = SDL_Init(SDL_INIT_EVERYTHING);
if (err != 0) die("%s", SDL_GetError());
if (err != 0) utilDie("%s", SDL_GetError());
// Init SDL_mixer
flags = MIX_INIT_FLAC | MIX_INIT_MOD | MIX_INIT_MP3 | MIX_INIT_OGG | MIX_INIT_MID | MIX_INIT_OPUS;
err = Mix_Init(flags);
if (err != flags) die("%s", Mix_GetError());
if (err != flags) utilDie("%s", Mix_GetError());
// Create Resizable Window
window = SDL_CreateWindow(filename, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_RESIZABLE);
if (window == NULL) die("%s", SDL_GetError());
if (window == NULL) utilDie("%s", SDL_GetError());
// Create an accelerated renderer.
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == NULL) die("%s", SDL_GetError());
if (renderer == NULL) utilDie("%s", SDL_GetError());
// Clear screen with black
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
// Start FFMS
FFMS_Init(0, 0);
errInfo.Buffer = errMsg;
errInfo.BufferSize = sizeof(errMsg);
errInfo.ErrorType = FFMS_ERROR_SUCCESS;
errInfo.SubType = FFMS_ERROR_SUCCESS;
// Index file
snprintf(indexName, 1024, "%s.index", filename);
index = FFMS_ReadIndex(indexName, &errInfo);
if (index) {
if (FFMS_IndexBelongsToFile(index, filename, &errInfo)) {
FFMS_DestroyIndex(index);
index = NULL;
say("Cached index is invalid.");
} else {
say("Loaded cached index.");
}
}
if (!index) {
say("Creating new index.");
indexer = FFMS_CreateIndexer(filename, &errInfo);
if (indexer == NULL) die("%s", errInfo.Buffer);
FFMS_TrackTypeIndexSettings(indexer, FFMS_TYPE_AUDIO, 1, 0);
FFMS_TrackTypeIndexSettings(indexer, FFMS_TYPE_VIDEO, 1, 0);
indexCallBack(0, 0, NULL);
FFMS_SetProgressCallback(indexer, indexCallBack, NULL);
index = FFMS_DoIndexing2(indexer, FFMS_IEH_ABORT, &errInfo);
if (index == NULL) die("%s", errInfo.Buffer);
if (FFMS_WriteIndex(indexName, index, &errInfo)) die("%s", errInfo.Buffer);
}
// Find video track
videoTrack = FFMS_GetFirstTrackOfType(index, FFMS_TYPE_VIDEO, &errInfo);
if (videoTrack < 0) die("%s", errInfo.Buffer);
videoSource = FFMS_CreateVideoSource(filename, videoTrack, index, 1, FFMS_SEEK_NORMAL, &errInfo);
if (videoSource == NULL) die("%s", errInfo.Buffer);
// Get video properties
videoProps = FFMS_GetVideoProperties(videoSource);
propFrame = FFMS_GetFrame(videoSource, 0, &errInfo);
if (propFrame == NULL) die("%s", errInfo.Buffer);
videoTimeBase = FFMS_GetTimeBase(FFMS_GetTrackFromVideo(videoSource));
// Set up output format
pixelFormats[0] = FFMS_GetPixFmt("bgra");
pixelFormats[1] = -1;
if (FFMS_SetOutputFormatV2(videoSource, pixelFormats, propFrame->EncodedWidth, propFrame->EncodedHeight, FFMS_RESIZER_BICUBIC, &errInfo)) die("%s", errInfo.Buffer);
// Find audio track
audioTrack = FFMS_GetFirstTrackOfType(index, FFMS_TYPE_AUDIO, &errInfo);
if (audioTrack < 0) die("%s", errInfo.Buffer);
audioSource = FFMS_CreateAudioSource(filename, audioTrack, index, FFMS_DELAY_FIRST_VIDEO_TRACK, &errInfo);
if (audioSource == NULL) die("%s", errInfo.Buffer);
// Get audio properties
audioProps = FFMS_GetAudioProperties(audioSource);
// Index is now part of audioSource & videoSource, so release this one
FFMS_DestroyIndex(index);
// Create video texture
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
videoTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_STATIC, propFrame->EncodedWidth, propFrame->EncodedHeight);
if (videoTexture == NULL) die("%s", SDL_GetError());
SDL_RenderSetLogicalSize(renderer, propFrame->EncodedWidth, propFrame->EncodedHeight);
// Determine audio format
switch (audioProps->SampleFormat) {
case FFMS_FMT_U8:
audioFormat = AUDIO_U8;
audioSampleBytes = 1;
break;
case FFMS_FMT_S16:
audioFormat = AUDIO_S16SYS;
audioSampleBytes = 2;
break;
case FFMS_FMT_S32:
audioFormat = AUDIO_S32SYS;
audioSampleBytes = 4;
break;
case FFMS_FMT_FLT:
audioFormat = AUDIO_F32SYS;
audioSampleBytes = 4;
break;
default:
die("Unknown audio sample format.");
break;
}
if (audioProps->Channels > 2) die("Only mono and stereo audio are supported.");
// Create audio mixer device
err = Mix_OpenAudio(audioProps->SampleRate, audioFormat, 2, audioSampleSize * audioProps->Channels * AUDIO_CHUNK_MULTIPLIER);
if (err != 0) die("%s", Mix_GetError());
err = Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 44100 /* freq */ * 16 /* bits */ * 2 /* channels */ * 2 /* seconds */);
if (err != 0) utilDie("%s", Mix_GetError());
// Create a buffer for reading audio from the video stream
audioSampleSize = audioSampleBytes * audioProps->Channels;
audioBuffer = (byte *)malloc((size_t)(audioSampleSize * AUDIO_SAMPLES) * sizeof(byte));
if (!audioBuffer) die("Unable to allocate %ld byte audio buffer.", ((size_t)(audioSampleSize * AUDIO_SAMPLES) * sizeof(byte)));
if (videoInit()) utilDie("Unable to initialize video player.");
// Create a ring buffer to pass audio data from the video to the mixer
audioRingBuffer.ring = ringbuf_new(AUDIO_RING_SIZE);
if (!audioRingBuffer.ring) die("Unable to allocate %ld audio ring buffer.", AUDIO_RING_SIZE);
audioRingBuffer.mutex = SDL_CreateMutex();
if (!audioRingBuffer.mutex) die("Unable to create audio ring buffer mutex.");
videoHandle = videoLoad(filename, renderer);
if (videoHandle < 0) utilDie("Unable to load video file: %s", filename);
// Create a block of silent audio to overlay with video stream audio
audioSilenceSize = (Uint32)audioSampleSize * (Uint32)audioProps->SampleRate * AUDIO_SILENCE_SECONDS;
audioSilenceRaw = (byte *)calloc(1, (size_t)audioSilenceSize * sizeof(byte));
if (!audioSilenceRaw) die("Unable to allocate %ld silence buffer.", audioSilenceSize);
// Load silent audio
silenceChunk = Mix_QuickLoad_RAW(audioSilenceRaw, audioSilenceSize);
if (!silenceChunk) die("%s", Mix_GetError());
// Start silent audio playback & immediately pause it
audioSilenceChannel = Mix_PlayChannel(-1, silenceChunk, -1);
if (audioSilenceChannel < 0) die("%s", Mix_GetError());
// Register effect to provide video stream audio on this channel
Mix_RegisterEffect(audioSilenceChannel, dequeueVideoAudio, NULL, &audioRingBuffer);
// Play It
say("Frames: %d (%dx%d) Audio Samples: %ld (%d Hz) %d Channel%s",
videoProps->NumFrames,
propFrame->EncodedWidth,
propFrame->EncodedHeight,
audioProps->NumSamples,
audioProps->SampleRate,
audioProps->Channels,
audioProps->Channels == 1 ? "" : "s"
);
videoPlay(videoHandle);
while (running) {
@ -345,35 +95,25 @@ int main(int argc, char *argv[]) {
running = false;
break;
case SDLK_SPACE:
playing = !playing;
resetTime = playing;
if (videoIsPlaying(videoHandle)) {
videoPause(videoHandle);
} else {
videoPlay(videoHandle);
}
break;
case SDLK_RIGHT:
frame++;
resetTime = true;
videoSeek(videoHandle, lastFrame + 1);
break;
case SDLK_LEFT:
frame--;
resetTime = true;
videoSeek(videoHandle, lastFrame - 1);
break;
case SDLK_UP:
frame += 100;
resetTime = true;
videoSeek(videoHandle, lastFrame + 100);
break;
case SDLK_DOWN:
frame -= 100;
resetTime = true;
videoSeek(videoHandle, lastFrame - 100);
break;
}
if (resetTime) {
if (frame >= videoProps->NumFrames) {
frame = 0;
}
if (frame < 0) {
frame = videoProps->NumFrames - 1;
}
say("Seeking to frame %d", frame);
}
break;
case SDL_QUIT:
@ -382,83 +122,22 @@ int main(int argc, char *argv[]) {
}
}
// Handle video frames (and time)
if ((SDL_GetTicks() - lastTickTime >= frameDeltaTime) || resetTime) {
lastTickTime = SDL_GetTicks();
if (frameData) {
SDL_UpdateTexture(videoTexture, NULL, frameData->Data[0], frameData->Linesize[0]);
SDL_RenderCopy(renderer, videoTexture, NULL, NULL);
SDL_RenderPresent(renderer);
}
frameData = FFMS_GetFrame(videoSource, frame, &errInfo);
if (frameData == NULL) die("%s", errInfo.Buffer);
frameInfo = FFMS_GetFrameInfo(FFMS_GetTrackFromVideo(videoSource), frame);
timestamp = (int64_t)((frameInfo->PTS * videoTimeBase->Num) / (double)videoTimeBase->Den); // Convert to milliseconds
frameDeltaTime = timestamp - lastFrameTime;
lastFrameTime = timestamp;
if (playing) {
if (++frame >= videoProps->NumFrames) {
frame = 0;
timestamp = 0;
resetTime = true;
}
}
if (resetTime) {
if (SDL_LockMutex(audioRingBuffer.mutex) < 0) die("Unable to lock mutex for ringbuf_reset");
ringbuf_reset(audioRingBuffer.ring);
if (SDL_UnlockMutex(audioRingBuffer.mutex) < 0) die("Unable to unlock mutex for ringbuf_reset");
lastTickTime = 0;
frameDeltaTime = 0;
audioPosition = (int64_t)((double)(timestamp * 0.001) * (double)audioProps->SampleRate);
resetTime = false;
}
}
// Handle audio samples
if ((playing) && (!ringbuf_is_full(audioRingBuffer.ring)) && (audioPosition < audioProps->NumSamples)) {
// Maximum samples we can read at a time
audioCount = AUDIO_SAMPLES;
// Don't read past end of audio data
if (audioPosition + audioCount >= audioProps->NumSamples) {
audioCount = audioProps->NumSamples - audioPosition - 1;
}
// Will this fit in our ring buffer?
audioRingBuffer.sizeTemp = ringbuf_bytes_free(audioRingBuffer.ring); // We do this in case more space is freed by the mixer while we're working on this
if ((audioRingBuffer.sizeTemp) < (size_t)(audioCount * audioSampleSize)) {
audioCount = (int64_t)(audioRingBuffer.sizeTemp / (size_t)audioSampleSize);
}
// Are we reading anything?
if (audioCount > 0) {
if (FFMS_GetAudio(audioSource, audioBuffer, audioPosition, audioCount, &errInfo)) die("%s", errInfo.Buffer);
if (SDL_LockMutex(audioRingBuffer.mutex) < 0) die("Unable to lock mutex for ringbuf_memcpy_into");
ringbuf_memcpy_into(audioRingBuffer.ring, audioBuffer, (size_t)(audioCount * audioSampleSize));
if (SDL_UnlockMutex(audioRingBuffer.mutex) < 0) die("Unable to unlock mutex for ringbuf_memcpy_into");
//say("Added %d samples (%d bytes) to ring buffer. Buffer is now %d bytes", audioCount, audioCount * audioSampleSize, ringbuf_bytes_used(audioRingBuffer.ring));
audioPosition += audioCount;
}
thisFrame = videoUpdate(videoHandle, &texture);
if ((thisFrame != lastFrame) && (thisFrame >= 0)) {
utilSay("Presenting frame %d", thisFrame);
lastFrame = thisFrame;
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
}
}
videoUnload(videoHandle);
videoQuit();
// Shutdown
Mix_HaltChannel(audioSilenceChannel);
Mix_UnregisterEffect(audioSilenceChannel, dequeueVideoAudio);
Mix_FreeChunk(silenceChunk);
free(audioBuffer);
free(audioSilenceRaw);
ringbuf_free(&audioRingBuffer.ring);
SDL_DestroyMutex(audioRingBuffer.mutex);
Mix_CloseAudio();
FFMS_DestroyAudioSource(audioSource);
FFMS_DestroyVideoSource(videoSource);
FFMS_Deinit();
//SDL_CloseAudioDevice(audioDevice);
SDL_DestroyTexture(videoTexture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
Mix_Quit();

View file

@ -90,20 +90,23 @@ MANYMOUSE_SOURCES = \
# === SINGE ===
INCLUDEPATH += \
$$MANYMOUSE_INCLUDES \
$$PWD/../thirdparty-build/$$BITNESS/installed/include
QMAKE_CFLAGS += \
-isystem $$MANYMOUSE_INCLUDES \
-isystem $$PWD/../thirdparty-build/$$BITNESS/installed/include
HEADERS += \
$$MANYMOUSE_HEADERS \
stddclmr.h \
thirdparty/utarray.h \
thirdparty/c-ringbuf/ringbuf.h
thirdparty/uthash.h \
common.h \
util.h \
videoPlayer.h
SOURCES += \
$$MANYMOUSE_SOURCES \
main.c \
thirdparty/c-ringbuf/ringbuf.c
util.c \
videoPlayer.c \
main.c
LIBS += \
-L$$PWD/../thirdparty-build/$$BITNESS/installed/lib \
@ -139,7 +142,8 @@ LIBS += \
OTHER_FILES += \
preBuild.sh \
postLink.sh
postLink.sh \
old.c
# Strip & UPX the final result
#linux:QMAKE_POST_LINK += bash $$PWD/postLink.sh "$$PWD" "$$DESTDIR"

View file

@ -1,69 +0,0 @@
# CONTRIBUTING
For the sake of saving both your time and mine, please read the following guidelines before making a contribution to this project.
## OPENING ISSUES
When opening an issue, please first check for an existing issue in the project (in **both** the closed and open issues) that may cover the issue you're experiencing. If an issue is closed and you think it should not be, please feel free to add a new comment in a closed issue thread; I will be notified, and I'll reconsider whether the issue should be re-opened based on your comment.
If you determine that you need to open a new issue, please include the following details:
* Your operating system (e.g., Ubuntu 18.04).
* Your system's CPU architecture (e.g., x86-64).
* Your C compiler's version, as shown by, e.g., `clang -v`.
* What steps are necessary to reproduce the issue.
## PULL REQUESTS
If you would like to make a pull request (PR) or other contribution to this project, please first refer to the following guidelines.
### One PR per feature
Please make one PR/patch per feature. Do not lump multiple features into a single PR. One PR per feature is a hassle for contributors, and for that I am sorry; but it makes reviewing contributions easier, and it also means that, if your contribution creates a bug or other issue, it's easier to track down the root cause. This policy also increases the likelihood that your PRs will be accepted in the case where there is an issue with one part of your PR, but the rest is fine.
If you are in doubt about what constitutes "a feature," please contact me before making a pull request so that we can sort it out. Feel free to do this by opening an issue that describes what you're proposing to do.
### Testing
Make sure you have run all tests successfully before submitting your PR. If you are adding functionality, please add new tests to the test suite to exercise that functionality. If your PR fixes a bug, please write a test(s) that demonstrates:
1. how to trigger the bug on the existing code base, and
2. that once your code has been applied, the bug is fixed.
### Bug fixes
If your PR is a bug fix, please first submit an issue that describes the bug in detail and how it occurs (see [OPENING ISSUES](#opening-issues)), and refer to this issue when submitting the PR.
### Public domain
By submitting a PR to this project, you are agreeing that your contribution is dedicated to the public domain per the
[COPYING](../COPYING) file included in this distribution, and that you are waiving any copyright claims with respect to your contribution. You also assert that this contribution is your own creation, and not taken from another work.
### Commit messages
A commit message should consist of at least a single line describing the changes made by the commit. This line should not be too many characters; I'm not a stickler about line length, but please try to stay below 80 characters or so.
If one line cannot capture what you want to say about the commit, please feel free to add more detail in subsequent paragraphs. In general, I prefer commit messages that are more detailed than not. Commit messages with details make it possible to understand the motivation for the change you've made.
([Here](https://github.com/dhess/c-ringbuf/commit/21669475d7f4e13801f94f5031dbd9aa00e95796) is an example of one of my own commits where the commit message adds some important detail motivating why the change was made, and what impacts it might have on the code.)
There's no need to go overboard, however. If your commit is simple and straightforward, a simple and straightforward single line description will suffice.
If your commit is a bug fix, please add the following text somewhere in your commit message, obviously replacing "#13" with the issue number that your commit addresses:
`Fixes #13`
I reserve the right to reject PRs purely based on whether the commit message is adequate/accurate.
### Code formatting
Please respect the code formatting rules I've used for this project. There are a few hard and fast rules:
* Spaces, not tabs.
* Indent by 4 spaces.
* Macros in `UPPER_CASE`, using underscores (`_`) as separators.
* Everything else in `lower_case`, using underscores (`_`) as separators.
For other formatting, I'm less picky, but generally speaking, just look for an example in the existing code base and follow it. (It's quite possible that I myself have been inconsistent in a few places. Feel free to point these cases out to me by opening an issue, if you like.)
I reserve the right to reject PRs purely based on their formatting. Please do not take it personally it if happens to you; everyone has their own quirky way of formatting code, and mine is no better than anyone else's, but I think it's important to be consistent within a project.

View file

@ -1,14 +0,0 @@
# BEFORE OPENING AN ISSUE
Please read the [issue guidelines](https://github.com/dhess/c-ringbuf/blob/master/.github/CONTRIBUTING.md#opening-issues)
**Before submitting this issue, please delete everything from this line up, and fill in the template below.**
--------------------------------------------------
## Issue description
### Steps to reproduce
### Technical details

View file

@ -1,11 +0,0 @@
# BEFORE MAKING A PULL REQUEST
Please read the [pull request guidelines](https://github.com/dhess/c-ringbuf/blob/master/.github/CONTRIBUTING.md#pull-requests)
**Before submitting this PR, please delete everything from this line and above, and check the boxes in the template below.**
--------------------------------------------------
By submitting this PR, you agree to the following:
- [ ] This contribution is dedicated to the public domain per the [COPYING](https://github.com/dhess/c-ringbuf/blob/master/COPYING) file included in this distribution. I am waiving any copyright claims with respect to this contribution, and I assert that this contribution is my own creation, and not taken from another work.

View file

@ -1,4 +0,0 @@
.DS_Store
*.o
*~
ringbuf-test

View file

@ -1,122 +0,0 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View file

@ -1,51 +0,0 @@
CC=clang
CFLAGS=-O0 -g -Wall -Wpointer-arith -ftrapv -fsanitize=undefined-trap -fsanitize-undefined-trap-on-error
# or, for gcc...
#CC=gcc
#CFLAGS=-O0 -g -Wall
LD=$(CC)
LDFLAGS=-g
test: ringbuf-test
./ringbuf-test
coverage: ringbuf-test-gcov
./ringbuf-test-gcov
gcov -o ringbuf-gcov.o ringbuf.c
valgrind: ringbuf-test
valgrind ./ringbuf-test
help:
@echo "Targets:"
@echo
@echo "test - build and run ringbuf unit tests."
@echo "coverage - use gcov to check test coverage of ringbuf.c."
@echo "valgrind - use valgrind to check for memory leaks."
@echo "clean - remove all targets."
@echo "help - this message."
ringbuf-test-gcov: ringbuf-test-gcov.o ringbuf-gcov.o
gcc -o ringbuf-test-gcov --coverage $^
ringbuf-test-gcov.o: ringbuf-test.c ringbuf.h
gcc -c $< -o $@
ringbuf-gcov.o: ringbuf.c ringbuf.h
gcc --coverage -c $< -o $@
ringbuf-test: ringbuf-test.o ringbuf.o
$(LD) -o ringbuf-test $(LDFLAGS) $^
ringbuf-test.o: ringbuf-test.c ringbuf.h
$(CC) $(CFLAGS) -c $< -o $@
ringbuf.o: ringbuf.c ringbuf.h
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f ringbuf-test ringbuf-test-gcov *.o *.gcov *.gcda *.gcno
.PHONY: clean

View file

@ -1,37 +0,0 @@
# WHAT
`c-ringbuf` is a simple ring buffer implementation in C.
It includes support for `read(2)` and `write(2)` operations on ring buffers, `memcpy`'s into and out of ring buffers, setting the buffer contents to a constant value, and copies between ring buffers. It also supports searching for single characters, for use with line-oriented or character-delimited network protocols.
It should be fairly straightforward to extend `c-ringbuf` to support other C library operations that operate on buffers, e.g., `recv(2)`.
# WHY
I implemented `c-ringbuf` because I needed a simple, dependency-free ring buffer type for use with network services written in C.
# INSTALLING
`c-ringbuf` is not a library as such, so it doesn't need to be installed. Just copy the `ringbuf.[ch]` source files into your project. (Also see [LICENSE](#license) below.)
`c-ringbuf` has no dependencies beyond an ISO C90 standard library.
Note that `ringbuf.c` contains several `assert()` statements. These are intended for use with the test harness (see below), and should probably be removed from production code, once you're confident that `c-ringbuf` works as intended.
This distribution includes source for a test program executable (`ringbuf-test.c`), which runs extensive unit tests on the `c-ringbuf` implementation. On most platforms (other than Windows, which is not supported), you should be able to type `make` to run the unit tests. Note that the [Makefile](Makefile) uses the `clang` C compiler by default, but also has support for `gcc` -- just edit the [Makefile](Makefile) so that it uses `gcc` instead of `clang`.
The [Makefile](Makefile) also includes targets for `gcov` coverage testing and `valgrind` memory testing, assuming you have those tools installed on your system.
# LICENSE
`c-ringbuf` has no license; it is dedicated to the public domain. See the file [COPYING](COPYING), included in this distribution, for the specifics.
# CONTRIBUTING
If you would like to make a pull request (PR) or other contribution to this project, please see [CONTRIBUTING.md](.github/CONTRIBUTING.md) for details on how to do that.
# CONTACT
Drew Hess <src@drewhess.com>
https://drewhess.com/

File diff suppressed because it is too large Load diff

View file

@ -1,347 +0,0 @@
/*
* ringbuf.c - C ring buffer (FIFO) implementation.
*
* Written in 2011 by Drew Hess <dhess-src@bothan.net>.
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to
* the public domain worldwide. This software is distributed without
* any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "ringbuf.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/param.h>
#include <assert.h>
/*
* The code is written for clarity, not cleverness or performance, and
* contains many assert()s to enforce invariant assumptions and catch
* bugs. Feel free to optimize the code and to remove asserts for use
* in your own projects, once you're comfortable that it functions as
* intended.
*/
struct ringbuf_t
{
uint8_t *buf;
uint8_t *head, *tail;
size_t size;
};
ringbuf_t
ringbuf_new(size_t capacity)
{
ringbuf_t rb = malloc(sizeof(struct ringbuf_t));
if (rb) {
/* One byte is used for detecting the full condition. */
rb->size = capacity + 1;
rb->buf = malloc(rb->size);
if (rb->buf)
ringbuf_reset(rb);
else {
free(rb);
return 0;
}
}
return rb;
}
size_t
ringbuf_buffer_size(const struct ringbuf_t *rb)
{
return rb->size;
}
void
ringbuf_reset(ringbuf_t rb)
{
rb->head = rb->tail = rb->buf;
}
void
ringbuf_free(ringbuf_t *rb)
{
assert(rb && *rb);
free((*rb)->buf);
free(*rb);
*rb = 0;
}
size_t
ringbuf_capacity(const struct ringbuf_t *rb)
{
return ringbuf_buffer_size(rb) - 1;
}
/*
* Return a pointer to one-past-the-end of the ring buffer's
* contiguous buffer. You shouldn't normally need to use this function
* unless you're writing a new ringbuf_* function.
*/
static const uint8_t *
ringbuf_end(const struct ringbuf_t *rb)
{
return rb->buf + ringbuf_buffer_size(rb);
}
size_t
ringbuf_bytes_free(const struct ringbuf_t *rb)
{
if (rb->head >= rb->tail)
return ringbuf_capacity(rb) - (rb->head - rb->tail);
else
return rb->tail - rb->head - 1;
}
size_t
ringbuf_bytes_used(const struct ringbuf_t *rb)
{
return ringbuf_capacity(rb) - ringbuf_bytes_free(rb);
}
int
ringbuf_is_full(const struct ringbuf_t *rb)
{
return ringbuf_bytes_free(rb) == 0;
}
int
ringbuf_is_empty(const struct ringbuf_t *rb)
{
return ringbuf_bytes_free(rb) == ringbuf_capacity(rb);
}
const void *
ringbuf_tail(const struct ringbuf_t *rb)
{
return rb->tail;
}
const void *
ringbuf_head(const struct ringbuf_t *rb)
{
return rb->head;
}
/*
* Given a ring buffer rb and a pointer to a location within its
* contiguous buffer, return the a pointer to the next logical
* location in the ring buffer.
*/
static uint8_t *
ringbuf_nextp(ringbuf_t rb, const uint8_t *p)
{
/*
* The assert guarantees the expression (++p - rb->buf) is
* non-negative; therefore, the modulus operation is safe and
* portable.
*/
assert((p >= rb->buf) && (p < ringbuf_end(rb)));
return rb->buf + ((++p - rb->buf) % ringbuf_buffer_size(rb));
}
size_t
ringbuf_findchr(const struct ringbuf_t *rb, int c, size_t offset)
{
const uint8_t *bufend = ringbuf_end(rb);
size_t bytes_used = ringbuf_bytes_used(rb);
if (offset >= bytes_used)
return bytes_used;
const uint8_t *start = rb->buf +
(((rb->tail - rb->buf) + offset) % ringbuf_buffer_size(rb));
assert(bufend > start);
size_t n = MIN(bufend - start, bytes_used - offset);
const uint8_t *found = memchr(start, c, n);
if (found)
return offset + (found - start);
else
return ringbuf_findchr(rb, c, offset + n);
}
size_t
ringbuf_memset(ringbuf_t dst, int c, size_t len)
{
const uint8_t *bufend = ringbuf_end(dst);
size_t nwritten = 0;
size_t count = MIN(len, ringbuf_buffer_size(dst));
int overflow = count > ringbuf_bytes_free(dst);
while (nwritten != count) {
/* don't copy beyond the end of the buffer */
assert(bufend > dst->head);
size_t n = MIN(bufend - dst->head, count - nwritten);
memset(dst->head, c, n);
dst->head += n;
nwritten += n;
/* wrap? */
if (dst->head == bufend)
dst->head = dst->buf;
}
if (overflow) {
dst->tail = ringbuf_nextp(dst, dst->head);
assert(ringbuf_is_full(dst));
}
return nwritten;
}
void *
ringbuf_memcpy_into(ringbuf_t dst, const void *src, size_t count)
{
const uint8_t *u8src = src;
const uint8_t *bufend = ringbuf_end(dst);
int overflow = count > ringbuf_bytes_free(dst);
size_t nread = 0;
while (nread != count) {
/* don't copy beyond the end of the buffer */
assert(bufend > dst->head);
size_t n = MIN(bufend - dst->head, count - nread);
memcpy(dst->head, u8src + nread, n);
dst->head += n;
nread += n;
/* wrap? */
if (dst->head == bufend)
dst->head = dst->buf;
}
if (overflow) {
dst->tail = ringbuf_nextp(dst, dst->head);
assert(ringbuf_is_full(dst));
}
return dst->head;
}
ssize_t
ringbuf_read(int fd, ringbuf_t rb, size_t count)
{
const uint8_t *bufend = ringbuf_end(rb);
size_t nfree = ringbuf_bytes_free(rb);
/* don't write beyond the end of the buffer */
assert(bufend > rb->head);
count = MIN(bufend - rb->head, count);
ssize_t n = read(fd, rb->head, count);
if (n > 0) {
assert(rb->head + n <= bufend);
rb->head += n;
/* wrap? */
if (rb->head == bufend)
rb->head = rb->buf;
/* fix up the tail pointer if an overflow occurred */
if (n > nfree) {
rb->tail = ringbuf_nextp(rb, rb->head);
assert(ringbuf_is_full(rb));
}
}
return n;
}
void *
ringbuf_memcpy_from(void *dst, ringbuf_t src, size_t count)
{
size_t bytes_used = ringbuf_bytes_used(src);
if (count > bytes_used)
return 0;
uint8_t *u8dst = dst;
const uint8_t *bufend = ringbuf_end(src);
size_t nwritten = 0;
while (nwritten != count) {
assert(bufend > src->tail);
size_t n = MIN(bufend - src->tail, count - nwritten);
memcpy(u8dst + nwritten, src->tail, n);
src->tail += n;
nwritten += n;
/* wrap ? */
if (src->tail == bufend)
src->tail = src->buf;
}
assert(count + ringbuf_bytes_used(src) == bytes_used);
return src->tail;
}
ssize_t
ringbuf_write(int fd, ringbuf_t rb, size_t count)
{
size_t bytes_used = ringbuf_bytes_used(rb);
if (count > bytes_used)
return 0;
const uint8_t *bufend = ringbuf_end(rb);
assert(bufend > rb->head);
count = MIN(bufend - rb->tail, count);
ssize_t n = write(fd, rb->tail, count);
if (n > 0) {
assert(rb->tail + n <= bufend);
rb->tail += n;
/* wrap? */
if (rb->tail == bufend)
rb->tail = rb->buf;
assert(n + ringbuf_bytes_used(rb) == bytes_used);
}
return n;
}
void *
ringbuf_copy(ringbuf_t dst, ringbuf_t src, size_t count)
{
size_t src_bytes_used = ringbuf_bytes_used(src);
if (count > src_bytes_used)
return 0;
int overflow = count > ringbuf_bytes_free(dst);
const uint8_t *src_bufend = ringbuf_end(src);
const uint8_t *dst_bufend = ringbuf_end(dst);
size_t ncopied = 0;
while (ncopied != count) {
assert(src_bufend > src->tail);
size_t nsrc = MIN(src_bufend - src->tail, count - ncopied);
assert(dst_bufend > dst->head);
size_t n = MIN(dst_bufend - dst->head, nsrc);
memcpy(dst->head, src->tail, n);
src->tail += n;
dst->head += n;
ncopied += n;
/* wrap ? */
if (src->tail == src_bufend)
src->tail = src->buf;
if (dst->head == dst_bufend)
dst->head = dst->buf;
}
assert(count + ringbuf_bytes_used(src) == src_bytes_used);
if (overflow) {
dst->tail = ringbuf_nextp(dst, dst->head);
assert(ringbuf_is_full(dst));
}
return dst->head;
}

View file

@ -1,243 +0,0 @@
#ifndef INCLUDED_RINGBUF_H
#define INCLUDED_RINGBUF_H
/*
* ringbuf.h - C ring buffer (FIFO) interface.
*
* Written in 2011 by Drew Hess <dhess-src@bothan.net>.
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to
* the public domain worldwide. This software is distributed without
* any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
/*
* A byte-addressable ring buffer FIFO implementation.
*
* The ring buffer's head pointer points to the starting location
* where data should be written when copying data *into* the buffer
* (e.g., with ringbuf_read). The ring buffer's tail pointer points to
* the starting location where data should be read when copying data
* *from* the buffer (e.g., with ringbuf_write).
*/
#include <stddef.h>
#include <sys/types.h>
typedef struct ringbuf_t *ringbuf_t;
/*
* Create a new ring buffer with the given capacity (usable
* bytes). Note that the actual internal buffer size may be one or
* more bytes larger than the usable capacity, for bookkeeping.
*
* Returns the new ring buffer object, or 0 if there's not enough
* memory to fulfill the request for the given capacity.
*/
ringbuf_t
ringbuf_new(size_t capacity);
/*
* The size of the internal buffer, in bytes. One or more bytes may be
* unusable in order to distinguish the "buffer full" state from the
* "buffer empty" state.
*
* For the usable capacity of the ring buffer, use the
* ringbuf_capacity function.
*/
size_t
ringbuf_buffer_size(const struct ringbuf_t *rb);
/*
* Deallocate a ring buffer, and, as a side effect, set the pointer to
* 0.
*/
void
ringbuf_free(ringbuf_t *rb);
/*
* Reset a ring buffer to its initial state (empty).
*/
void
ringbuf_reset(ringbuf_t rb);
/*
* The usable capacity of the ring buffer, in bytes. Note that this
* value may be less than the ring buffer's internal buffer size, as
* returned by ringbuf_buffer_size.
*/
size_t
ringbuf_capacity(const struct ringbuf_t *rb);
/*
* The number of free/available bytes in the ring buffer. This value
* is never larger than the ring buffer's usable capacity.
*/
size_t
ringbuf_bytes_free(const struct ringbuf_t *rb);
/*
* The number of bytes currently being used in the ring buffer. This
* value is never larger than the ring buffer's usable capacity.
*/
size_t
ringbuf_bytes_used(const struct ringbuf_t *rb);
int
ringbuf_is_full(const struct ringbuf_t *rb);
int
ringbuf_is_empty(const struct ringbuf_t *rb);
/*
* Const access to the head and tail pointers of the ring buffer.
*/
const void *
ringbuf_tail(const struct ringbuf_t *rb);
const void *
ringbuf_head(const struct ringbuf_t *rb);
/*
* Locate the first occurrence of character c (converted to an
* unsigned char) in ring buffer rb, beginning the search at offset
* bytes from the ring buffer's tail pointer. The function returns the
* offset of the character from the ring buffer's tail pointer, if
* found. If c does not occur in the ring buffer, the function returns
* the number of bytes used in the ring buffer.
*
* Note that the offset parameter and the returned offset are logical
* offsets from the tail pointer, not necessarily linear offsets.
*/
size_t
ringbuf_findchr(const struct ringbuf_t *rb, int c, size_t offset);
/*
* Beginning at ring buffer dst's head pointer, fill the ring buffer
* with a repeating sequence of len bytes, each of value c (converted
* to an unsigned char). len can be as large as you like, but the
* function will never write more than ringbuf_buffer_size(dst) bytes
* in a single invocation, since that size will cause all bytes in the
* ring buffer to be written exactly once each.
*
* Note that if len is greater than the number of free bytes in dst,
* the ring buffer will overflow. When an overflow occurs, the state
* of the ring buffer is guaranteed to be consistent, including the
* head and tail pointers; old data will simply be overwritten in FIFO
* fashion, as needed. However, note that, if calling the function
* results in an overflow, the value of the ring buffer's tail pointer
* may be different than it was before the function was called.
*
* Returns the actual number of bytes written to dst: len, if
* len < ringbuf_buffer_size(dst), else ringbuf_buffer_size(dst).
*/
size_t
ringbuf_memset(ringbuf_t dst, int c, size_t len);
/*
* Copy n bytes from a contiguous memory area src into the ring buffer
* dst. Returns the ring buffer's new head pointer.
*
* It is possible to copy more data from src than is available in the
* buffer; i.e., it's possible to overflow the ring buffer using this
* function. When an overflow occurs, the state of the ring buffer is
* guaranteed to be consistent, including the head and tail pointers;
* old data will simply be overwritten in FIFO fashion, as
* needed. However, note that, if calling the function results in an
* overflow, the value of the ring buffer's tail pointer may be
* different than it was before the function was called.
*/
void *
ringbuf_memcpy_into(ringbuf_t dst, const void *src, size_t count);
/*
* This convenience function calls read(2) on the file descriptor fd,
* using the ring buffer rb as the destination buffer for the read,
* and returns the value returned by read(2). It will only call
* read(2) once, and may return a short count.
*
* It is possible to read more data from the file descriptor than is
* available in the buffer; i.e., it's possible to overflow the ring
* buffer using this function. When an overflow occurs, the state of
* the ring buffer is guaranteed to be consistent, including the head
* and tail pointers: old data will simply be overwritten in FIFO
* fashion, as needed. However, note that, if calling the function
* results in an overflow, the value of the ring buffer's tail pointer
* may be different than it was before the function was called.
*/
ssize_t
ringbuf_read(int fd, ringbuf_t rb, size_t count);
/*
* Copy n bytes from the ring buffer src, starting from its tail
* pointer, into a contiguous memory area dst. Returns the value of
* src's tail pointer after the copy is finished.
*
* Note that this copy is destructive with respect to the ring buffer:
* the n bytes copied from the ring buffer are no longer available in
* the ring buffer after the copy is complete, and the ring buffer
* will have n more free bytes than it did before the function was
* called.
*
* This function will *not* allow the ring buffer to underflow. If
* count is greater than the number of bytes used in the ring buffer,
* no bytes are copied, and the function will return 0.
*/
void *
ringbuf_memcpy_from(void *dst, ringbuf_t src, size_t count);
/*
* This convenience function calls write(2) on the file descriptor fd,
* using the ring buffer rb as the source buffer for writing (starting
* at the ring buffer's tail pointer), and returns the value returned
* by write(2). It will only call write(2) once, and may return a
* short count.
*
* Note that this copy is destructive with respect to the ring buffer:
* any bytes written from the ring buffer to the file descriptor are
* no longer available in the ring buffer after the copy is complete,
* and the ring buffer will have N more free bytes than it did before
* the function was called, where N is the value returned by the
* function (unless N is < 0, in which case an error occurred and no
* bytes were written).
*
* This function will *not* allow the ring buffer to underflow. If
* count is greater than the number of bytes used in the ring buffer,
* no bytes are written to the file descriptor, and the function will
* return 0.
*/
ssize_t
ringbuf_write(int fd, ringbuf_t rb, size_t count);
/*
* Copy count bytes from ring buffer src, starting from its tail
* pointer, into ring buffer dst. Returns dst's new head pointer after
* the copy is finished.
*
* Note that this copy is destructive with respect to the ring buffer
* src: any bytes copied from src into dst are no longer available in
* src after the copy is complete, and src will have 'count' more free
* bytes than it did before the function was called.
*
* It is possible to copy more data from src than is available in dst;
* i.e., it's possible to overflow dst using this function. When an
* overflow occurs, the state of dst is guaranteed to be consistent,
* including the head and tail pointers; old data will simply be
* overwritten in FIFO fashion, as needed. However, note that, if
* calling the function results in an overflow, the value dst's tail
* pointer may be different than it was before the function was
* called.
*
* It is *not* possible to underflow src; if count is greater than the
* number of bytes used in src, no bytes are copied, and the function
* returns 0.
*/
void *
ringbuf_copy(ringbuf_t dst, ringbuf_t src, size_t count);
#endif /* INCLUDED_RINGBUF_H */

View file

@ -47,7 +47,7 @@ static int poll_mouse(MouseStruct *mouse, ManyMouseEvent *outevent)
while (unhandled) /* read until failure or valid event. */
{
struct input_event event;
int br = read(mouse->fd, &event, sizeof (event));
int br = (int)read(mouse->fd, &event, sizeof (event));
if (br == -1)
{
if (errno == EAGAIN)
@ -154,6 +154,8 @@ static int init_mouse(const char *fname, int fd)
unsigned char abscaps[(ABS_MAX / 8) + 1];
unsigned char keycaps[(KEY_MAX / 8) + 1];
(void)fname;
memset(relcaps, '\0', sizeof (relcaps));
memset(abscaps, '\0', sizeof (abscaps));
memset(keycaps, '\0', sizeof (keycaps));
@ -260,7 +262,7 @@ static int linux_evdev_init(void)
while ((dent = readdir(dirp)) != NULL)
{
char fname[128];
char fname[384];
snprintf(fname, sizeof (fname), "/dev/input/%s", dent->d_name);
if (open_if_mouse(fname))
available_mice++;
@ -268,7 +270,7 @@ static int linux_evdev_init(void)
closedir(dirp);
return available_mice;
return (int)available_mice;
} /* linux_evdev_init */

View file

@ -6,8 +6,11 @@
* This file written by Ryan C. Gordon.
*/
#ifndef _INCLUDE_MANYMOUSE_H_
#define _INCLUDE_MANYMOUSE_H_
#ifndef INCLUDE_MANYMOUSE_H_
#define INCLUDE_MANYMOUSE_H_
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wempty-translation-unit"
#ifdef __cplusplus
extern "C" {
@ -46,6 +49,7 @@ typedef struct
int (*poll)(ManyMouseEvent *event);
} ManyMouseDriver;
extern const ManyMouseDriver *ManyMouseDriver_xinput2;
int ManyMouse_Init(void);
const char *ManyMouse_DriverName(void);
@ -57,7 +61,9 @@ int ManyMouse_PollEvent(ManyMouseEvent *event);
}
#endif
#endif /* !defined _INCLUDE_MANYMOUSE_H_ */
#pragma GCC diagnostic pop
#endif /* !defined INCLUDE_MANYMOUSE_H_ */
/* end of manymouse.h ... */

View file

@ -219,8 +219,11 @@ static int init_mouse(MouseStruct *mouse, const XIDeviceInfo *devinfo)
{
if ((classes[i]->type == XIValuatorClass) && (axis < MAX_AXIS))
{
const XIValuatorClassInfo *v = (XIValuatorClassInfo*) classes[i];
mouse->relative[axis] = (v->mode == XIModeRelative);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-align"
const XIValuatorClassInfo *v = (XIValuatorClassInfo*) classes[i];
#pragma GCC diagnostic pop
mouse->relative[axis] = (v->mode == XIModeRelative);
mouse->minval[axis] = (int) v->min;
mouse->maxval[axis] = (int) v->max;
axis++;
@ -311,7 +314,7 @@ static int x11_xinput2_init_internal(void)
} /* for */
pXIFreeDeviceInfo(device_list);
return available_mice;
return (int)available_mice;
} /* x11_xinput2_init_internal */
@ -341,7 +344,7 @@ static int find_mouse_by_devid(const int devid)
int i;
const MouseStruct *mouse = mice;
for (i = 0; i < available_mice; i++, mouse++)
for (i = 0; i < (int)available_mice; i++, mouse++)
{
if (mouse->device_id == devid)
return (mouse->connected) ? i : -1;
@ -435,8 +438,8 @@ static void pump_events(void)
event.type = MANYMOUSE_EVENT_RELMOTION;
else
event.type = MANYMOUSE_EVENT_ABSMOTION;
event.device = mouse;
event.item = i;
event.device = (unsigned int)mouse;
event.item = (unsigned int)i;
event.value = value;
event.minval = mice[mouse].minval[i];
event.maxval = mice[mouse].maxval[i];
@ -463,7 +466,7 @@ static void pump_events(void)
if (pressed) /* ignore "up" for these "buttons" */
{
event.type = MANYMOUSE_EVENT_SCROLL;
event.device = mouse;
event.device = (unsigned int)mouse;
if ((button == 4) || (button == 5))
event.item = 0;
@ -481,8 +484,8 @@ static void pump_events(void)
else
{
event.type = MANYMOUSE_EVENT_BUTTON;
event.device = mouse;
event.item = button-1;
event.device = (unsigned int)mouse;
event.item = (unsigned int)button-1;
event.value = pressed;
queue_event(&event);
} /* else */
@ -500,7 +503,7 @@ static void pump_events(void)
{
mice[mouse].connected = 0;
event.type = MANYMOUSE_EVENT_DISCONNECT;
event.device = mouse;
event.device = (unsigned int)mouse;
queue_event(&event);
} /* if */
} /* if */

View file

@ -1,247 +0,0 @@
/*
Copyright (c) 2008-2018, Troy D. Hanson http://troydhanson.github.com/uthash/
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* a dynamic array implementation using macros
*/
#ifndef UTARRAY_H
#define UTARRAY_H
#define UTARRAY_VERSION 2.1.0
#include <stddef.h> /* size_t */
#include <string.h> /* memset, etc */
#include <stdlib.h> /* exit */
#ifdef __GNUC__
#define UTARRAY_UNUSED __attribute__((__unused__))
#else
#define UTARRAY_UNUSED
#endif
#ifdef oom
#error "The name of macro 'oom' has been changed to 'utarray_oom'. Please update your code."
#define utarray_oom() oom()
#endif
#ifndef utarray_oom
#define utarray_oom() exit(-1)
#endif
typedef void (ctor_f)(void *dst, const void *src);
typedef void (dtor_f)(void *elt);
typedef void (init_f)(void *elt);
typedef struct {
size_t sz;
init_f *init;
ctor_f *copy;
dtor_f *dtor;
} UT_icd;
typedef struct {
unsigned i,n;/* i: index of next available slot, n: num slots */
UT_icd icd; /* initializer, copy and destructor functions */
char *d; /* n slots of size icd->sz*/
} UT_array;
#define utarray_init(a,_icd) do { \
memset(a,0,sizeof(UT_array)); \
(a)->icd = *(_icd); \
} while(0)
#define utarray_done(a) do { \
if ((a)->n) { \
if ((a)->icd.dtor) { \
unsigned _ut_i; \
for(_ut_i=0; _ut_i < (a)->i; _ut_i++) { \
(a)->icd.dtor(utarray_eltptr(a,_ut_i)); \
} \
} \
free((a)->d); \
} \
(a)->n=0; \
} while(0)
#define utarray_new(a,_icd) do { \
(a) = (UT_array*)malloc(sizeof(UT_array)); \
if ((a) == NULL) { \
utarray_oom(); \
} \
utarray_init(a,_icd); \
} while(0)
#define utarray_free(a) do { \
utarray_done(a); \
free(a); \
} while(0)
#define utarray_reserve(a,by) do { \
if (((a)->i+(by)) > (a)->n) { \
char *utarray_tmp; \
while (((a)->i+(by)) > (a)->n) { (a)->n = ((a)->n ? (2*(a)->n) : 8); } \
utarray_tmp=(char*)realloc((a)->d, (a)->n*(a)->icd.sz); \
if (utarray_tmp == NULL) { \
utarray_oom(); \
} \
(a)->d=utarray_tmp; \
} \
} while(0)
#define utarray_push_back(a,p) do { \
utarray_reserve(a,1); \
if ((a)->icd.copy) { (a)->icd.copy( _utarray_eltptr(a,(a)->i++), p); } \
else { memcpy(_utarray_eltptr(a,(a)->i++), p, (a)->icd.sz); }; \
} while(0)
#define utarray_pop_back(a) do { \
if ((a)->icd.dtor) { (a)->icd.dtor( _utarray_eltptr(a,--((a)->i))); } \
else { (a)->i--; } \
} while(0)
#define utarray_extend_back(a) do { \
utarray_reserve(a,1); \
if ((a)->icd.init) { (a)->icd.init(_utarray_eltptr(a,(a)->i)); } \
else { memset(_utarray_eltptr(a,(a)->i),0,(a)->icd.sz); } \
(a)->i++; \
} while(0)
#define utarray_len(a) ((a)->i)
#define utarray_eltptr(a,j) (((j) < (a)->i) ? _utarray_eltptr(a,j) : NULL)
#define _utarray_eltptr(a,j) ((a)->d + ((a)->icd.sz * (j)))
#define utarray_insert(a,p,j) do { \
if ((j) > (a)->i) utarray_resize(a,j); \
utarray_reserve(a,1); \
if ((j) < (a)->i) { \
memmove( _utarray_eltptr(a,(j)+1), _utarray_eltptr(a,j), \
((a)->i - (j))*((a)->icd.sz)); \
} \
if ((a)->icd.copy) { (a)->icd.copy( _utarray_eltptr(a,j), p); } \
else { memcpy(_utarray_eltptr(a,j), p, (a)->icd.sz); }; \
(a)->i++; \
} while(0)
#define utarray_inserta(a,w,j) do { \
if (utarray_len(w) == 0) break; \
if ((j) > (a)->i) utarray_resize(a,j); \
utarray_reserve(a,utarray_len(w)); \
if ((j) < (a)->i) { \
memmove(_utarray_eltptr(a,(j)+utarray_len(w)), \
_utarray_eltptr(a,j), \
((a)->i - (j))*((a)->icd.sz)); \
} \
if ((a)->icd.copy) { \
unsigned _ut_i; \
for(_ut_i=0;_ut_i<(w)->i;_ut_i++) { \
(a)->icd.copy(_utarray_eltptr(a, (j) + _ut_i), _utarray_eltptr(w, _ut_i)); \
} \
} else { \
memcpy(_utarray_eltptr(a,j), _utarray_eltptr(w,0), \
utarray_len(w)*((a)->icd.sz)); \
} \
(a)->i += utarray_len(w); \
} while(0)
#define utarray_resize(dst,num) do { \
unsigned _ut_i; \
if ((dst)->i > (unsigned)(num)) { \
if ((dst)->icd.dtor) { \
for (_ut_i = (num); _ut_i < (dst)->i; ++_ut_i) { \
(dst)->icd.dtor(_utarray_eltptr(dst, _ut_i)); \
} \
} \
} else if ((dst)->i < (unsigned)(num)) { \
utarray_reserve(dst, (num) - (dst)->i); \
if ((dst)->icd.init) { \
for (_ut_i = (dst)->i; _ut_i < (unsigned)(num); ++_ut_i) { \
(dst)->icd.init(_utarray_eltptr(dst, _ut_i)); \
} \
} else { \
memset(_utarray_eltptr(dst, (dst)->i), 0, (dst)->icd.sz*((num) - (dst)->i)); \
} \
} \
(dst)->i = (num); \
} while(0)
#define utarray_concat(dst,src) do { \
utarray_inserta(dst, src, utarray_len(dst)); \
} while(0)
#define utarray_erase(a,pos,len) do { \
if ((a)->icd.dtor) { \
unsigned _ut_i; \
for (_ut_i = 0; _ut_i < (len); _ut_i++) { \
(a)->icd.dtor(utarray_eltptr(a, (pos) + _ut_i)); \
} \
} \
if ((a)->i > ((pos) + (len))) { \
memmove(_utarray_eltptr(a, pos), _utarray_eltptr(a, (pos) + (len)), \
((a)->i - ((pos) + (len))) * (a)->icd.sz); \
} \
(a)->i -= (len); \
} while(0)
#define utarray_renew(a,u) do { \
if (a) utarray_clear(a); \
else utarray_new(a, u); \
} while(0)
#define utarray_clear(a) do { \
if ((a)->i > 0) { \
if ((a)->icd.dtor) { \
unsigned _ut_i; \
for(_ut_i=0; _ut_i < (a)->i; _ut_i++) { \
(a)->icd.dtor(_utarray_eltptr(a, _ut_i)); \
} \
} \
(a)->i = 0; \
} \
} while(0)
#define utarray_sort(a,cmp) do { \
qsort((a)->d, (a)->i, (a)->icd.sz, cmp); \
} while(0)
#define utarray_find(a,v,cmp) bsearch((v),(a)->d,(a)->i,(a)->icd.sz,cmp)
#define utarray_front(a) (((a)->i) ? (_utarray_eltptr(a,0)) : NULL)
#define utarray_next(a,e) (((e)==NULL) ? utarray_front(a) : (((a)->i != utarray_eltidx(a,e)+1) ? _utarray_eltptr(a,utarray_eltidx(a,e)+1) : NULL))
#define utarray_prev(a,e) (((e)==NULL) ? utarray_back(a) : ((utarray_eltidx(a,e) != 0) ? _utarray_eltptr(a,utarray_eltidx(a,e)-1) : NULL))
#define utarray_back(a) (((a)->i) ? (_utarray_eltptr(a,(a)->i-1)) : NULL)
#define utarray_eltidx(a,e) (((char*)(e) - (a)->d) / (a)->icd.sz)
/* last we pre-define a few icd for common utarrays of ints and strings */
static void utarray_str_cpy(void *dst, const void *src) {
char **_src = (char**)src, **_dst = (char**)dst;
*_dst = (*_src == NULL) ? NULL : strdup(*_src);
}
static void utarray_str_dtor(void *elt) {
char **eltc = (char**)elt;
if (*eltc != NULL) free(*eltc);
}
static const UT_icd ut_str_icd UTARRAY_UNUSED = {sizeof(char*),NULL,utarray_str_cpy,utarray_str_dtor};
static const UT_icd ut_int_icd UTARRAY_UNUSED = {sizeof(int),NULL,NULL,NULL};
static const UT_icd ut_ptr_icd UTARRAY_UNUSED = {sizeof(void*),NULL,NULL,NULL};
#endif /* UTARRAY_H */

1230
singe/thirdparty/uthash.h vendored Normal file

File diff suppressed because it is too large Load diff

54
singe/util.c Normal file
View file

@ -0,0 +1,54 @@
/*
*
* Singe 2
* Copyright (C) 2019 Scott Duensing <scott@kangaroopunch.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "common.h"
#include "util.h"
__attribute__((__format__(__printf__, 1, 0)))
__attribute__((noreturn))
void utilDie(char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
printf("\n");
fflush(stderr);
exit(1);
}
__attribute__((__format__(__printf__, 1, 0)))
void utilSay(char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);
printf("\n");
fflush(stdout);
}

29
singe/util.h Normal file
View file

@ -0,0 +1,29 @@
/*
*
* Singe 2
* Copyright (C) 2019 Scott Duensing <scott@kangaroopunch.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifndef UTIL_H
#define UTIL_H
void utilDie(char *fmt, ...);
void utilSay(char *fmt, ...);
#endif // UTIL_H

469
singe/videoPlayer.c Normal file
View file

@ -0,0 +1,469 @@
/*
*
* Singe 2
* Copyright (C) 2019 Scott Duensing <scott@kangaroopunch.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include <SDL2/SDL_mixer.h>
#include <ffms.h>
#include "thirdparty/uthash.h"
#include "common.h"
#include "util.h"
#include "videoPlayer.h"
#define AUDIO_STREAM_LOW_WATERMARK (32 * 1024)
#define AUDIO_SAMPLE_PREREAD 1024
#define AUDIO_SILENCE_SECONDS 2
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpadded"
typedef struct VideoPlayerS {
int id;
bool playing;
bool resetTime;
byte *audioBuffer;
byte audioSampleBytes;
byte *audioSilenceRaw;
char errMsg[1024];
int audioTrack;
int videoTrack;
int frame;
int audioSampleSize;
int mixSampleSize;
int audioSilenceChannel;
int64_t audioBufferSize;
int64_t frameDeltaTime;
int64_t lastFrameTime;
int64_t timestamp;
int64_t audioPosition;
Uint16 audioFormat;
Uint32 lastTickTime;
Uint32 audioSilenceSize;
Mix_Chunk *silenceChunk;
SDL_AudioStream *audioStream;
SDL_Texture *videoTexture;
FFMS_ErrorInfo errInfo;
FFMS_AudioSource *audioSource;
FFMS_VideoSource *videoSource;
const FFMS_AudioProperties *audioProps;
const FFMS_VideoProperties *videoProps;
const FFMS_Frame *propFrame;
const FFMS_TrackTimeBase *videoTimeBase;
const FFMS_Frame *frameData;
const FFMS_FrameInfo *frameInfo;
UT_hash_handle hh;
} VideoPlayerT;
#pragma GCC diagnostic pop
void _dequeueVideoAudio(int channel, void *stream, int len, void *udata);
int FFMS_CC _indexCallBack(int64_t Current, int64_t Total, void *ICPrivate);
static VideoPlayerT *_videoPlayerHash = NULL;
static int _nextId = 0;
static int _mixRate = -1;
static Uint8 _mixChannels = 0;
static SDL_AudioFormat _mixFormat = 0;
void _dequeueVideoAudio(int channel, void *stream, int bytes, void *udata) {
VideoPlayerT *v = (VideoPlayerT *)udata;
int bytesToCopy = bytes;
int available = SDL_AudioStreamAvailable(v->audioStream);
int remainder = 0;
(void)channel;
// Don't copy more than we have room for
if (bytesToCopy > available) {
bytesToCopy = available;
}
// Ensure we only copy complete samples (Is this needed?)
remainder = bytesToCopy % v->audioSampleSize;
bytesToCopy -= remainder;
if (SDL_AudioStreamGet(v->audioStream, stream, bytesToCopy) < 0) utilDie("%s", SDL_GetError());
}
int FFMS_CC _indexCallBack(int64_t current, int64_t total, void *ICPrivate) {
static int lastPercent = 0;
int thisPercent = 0;
(void)ICPrivate; // Contains current VideoPlayerT
if ((current == 0) && (total == 0)) {
lastPercent = 0; // Reset
} else {
thisPercent = (int)((double)current / (double)total * 100.0);
if (thisPercent != lastPercent) {
lastPercent = thisPercent;
utilSay("Indexing: %d%%", thisPercent);
}
}
return 0;
}
int videoInit(void) {
int channels = _mixChannels;
// Start FFMS
FFMS_Init(0, 0);
// Fetch mixer settings
if (!Mix_QuerySpec(&_mixRate, &_mixFormat, &channels)) utilDie("%s", Mix_GetError());
_mixChannels = (Uint8)channels;
return 0;
}
int videoIsPlaying(int playerIndex) {
VideoPlayerT *v = NULL;
// Get our player structure
HASH_FIND_INT(_videoPlayerHash, &playerIndex, v);
if (!v) utilDie("No video player at index %d in videoIsPlaying.", playerIndex);
return v->playing;
}
int videoLoad(char *filename, SDL_Renderer *renderer) {
char indexName[1024];
int pixelFormats[2];
int result = -1;
FFMS_Index *index = NULL;
FFMS_Indexer *indexer = NULL;
VideoPlayerT *v = NULL;
// Create new videoPlayer
v = calloc(1, sizeof(VideoPlayerT));
if (!v) utilDie("Unable to allocate new video player.");
// Set some starting values
v->audioTrack = -1;
v->videoTrack = -1;
v->audioSilenceChannel = -1;
v->playing = false; // Start paused
v->errInfo.Buffer = v->errMsg;
v->errInfo.BufferSize = sizeof(v->errMsg);
v->errInfo.ErrorType = FFMS_ERROR_SUCCESS;
v->errInfo.SubType = FFMS_ERROR_SUCCESS;
// Index file
snprintf(indexName, 1024, "%s.index", filename);
index = FFMS_ReadIndex(indexName, &v->errInfo);
if (index) {
if (FFMS_IndexBelongsToFile(index, filename, &v->errInfo)) {
FFMS_DestroyIndex(index);
index = NULL;
utilSay("Cached index is invalid.");
} else {
utilSay("Loaded cached index.");
}
}
if (!index) {
utilSay("Creating new index.");
indexer = FFMS_CreateIndexer(filename, &v->errInfo);
if (indexer == NULL) utilDie("%s", v->errInfo.Buffer);
FFMS_TrackTypeIndexSettings(indexer, FFMS_TYPE_AUDIO, 1, 0);
FFMS_TrackTypeIndexSettings(indexer, FFMS_TYPE_VIDEO, 1, 0);
_indexCallBack(0, 0, v);
FFMS_SetProgressCallback(indexer, _indexCallBack, v);
index = FFMS_DoIndexing2(indexer, FFMS_IEH_ABORT, &v->errInfo);
if (index == NULL) utilDie("%s", v->errInfo.Buffer);
if (FFMS_WriteIndex(indexName, index, &v->errInfo)) utilDie("%s", v->errInfo.Buffer);
}
// Find video track
v->videoTrack = FFMS_GetFirstTrackOfType(index, FFMS_TYPE_VIDEO, &v->errInfo);
if (v->videoTrack < 0) utilDie("%s", v->errInfo.Buffer);
v->videoSource = FFMS_CreateVideoSource(filename, v->videoTrack, index, 1, FFMS_SEEK_NORMAL, &v->errInfo);
if (v->videoSource == NULL) utilDie("%s", v->errInfo.Buffer);
// Get video properties
v->videoProps = FFMS_GetVideoProperties(v->videoSource);
v->propFrame = FFMS_GetFrame(v->videoSource, 0, &v->errInfo);
if (v->propFrame == NULL) utilDie("%s", v->errInfo.Buffer);
v->videoTimeBase = FFMS_GetTimeBase(FFMS_GetTrackFromVideo(v->videoSource));
// Set up output video format
pixelFormats[0] = FFMS_GetPixFmt("bgra");
pixelFormats[1] = -1;
if (FFMS_SetOutputFormatV2(v->videoSource, pixelFormats, v->propFrame->EncodedWidth, v->propFrame->EncodedHeight, FFMS_RESIZER_BICUBIC, &v->errInfo)) utilDie("%s", v->errInfo.Buffer);
// Find audio track
v->audioTrack = FFMS_GetFirstTrackOfType(index, FFMS_TYPE_AUDIO, &v->errInfo);
if (v->audioTrack < 0) utilDie("%s", v->errInfo.Buffer);
v->audioSource = FFMS_CreateAudioSource(filename, v->audioTrack, index, FFMS_DELAY_FIRST_VIDEO_TRACK, &v->errInfo);
if (v->audioSource == NULL) utilDie("%s", v->errInfo.Buffer);
// Get audio properties
v->audioProps = FFMS_GetAudioProperties(v->audioSource);
// Index is now part of audioSource & videoSource, so release this one
FFMS_DestroyIndex(index);
// Create video texture
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
v->videoTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_STATIC, v->propFrame->EncodedWidth, v->propFrame->EncodedHeight);
if (v->videoTexture == NULL) utilDie("%s", SDL_GetError());
SDL_RenderSetLogicalSize(renderer, v->propFrame->EncodedWidth, v->propFrame->EncodedHeight);
// Determine audio format
switch (v->audioProps->SampleFormat) {
case FFMS_FMT_U8:
v->audioFormat = AUDIO_U8;
v->audioSampleBytes = 1;
break;
case FFMS_FMT_S16:
v->audioFormat = AUDIO_S16SYS;
v->audioSampleBytes = 2;
break;
case FFMS_FMT_S32:
v->audioFormat = AUDIO_S32SYS;
v->audioSampleBytes = 4;
break;
case FFMS_FMT_FLT:
v->audioFormat = AUDIO_F32SYS;
v->audioSampleBytes = 4;
break;
default:
utilDie("Unknown audio sample format.");
break;
}
if (v->audioProps->Channels > 2) utilDie("Only mono and stereo audio are supported.");
// Create audio stream to convert audio to our desired format
v->audioStream = SDL_NewAudioStream(v->audioFormat, (Uint8)v->audioProps->Channels, v->audioProps->SampleRate, _mixFormat, _mixChannels, _mixRate);
if (!v->audioStream) utilDie("%s", SDL_GetError());
// Create a buffer to read audio into before conversion
v->mixSampleSize = SDL_AUDIO_BITSIZE(_mixFormat) / 8 * _mixChannels;
v->audioSampleSize = v->audioSampleBytes * v->audioProps->Channels;
v->audioBufferSize = v->audioSampleSize * AUDIO_SAMPLE_PREREAD;
v->audioBuffer = (byte *)malloc((size_t)v->audioBufferSize * sizeof(byte));
if (!v->audioBuffer) utilDie("Unable to allocate %ld byte audio buffer.", (size_t)v->audioBufferSize * sizeof(byte));
// Create a block of silent audio to overlay with video stream audio
v->audioSilenceSize = (Uint32)(_mixRate * SDL_AUDIO_BITSIZE(_mixFormat) / 8 * AUDIO_SILENCE_SECONDS);
v->audioSilenceRaw = (byte *)calloc(1, (size_t)v->audioSilenceSize * sizeof(byte));
if (!v->audioSilenceRaw) utilDie("Unable to allocate %ld silence buffer.", v->audioSilenceSize);
// Load silent audio
v->silenceChunk = Mix_QuickLoad_RAW(v->audioSilenceRaw, v->audioSilenceSize);
if (!v->silenceChunk) utilDie("%s", Mix_GetError());
// Start silent audio playback & immediately pause it
v->audioSilenceChannel = Mix_PlayChannel(-1, v->silenceChunk, -1);
if (v->audioSilenceChannel < 0) utilDie("%s", Mix_GetError());
// Register effect to provide video stream audio on this channel
Mix_RegisterEffect(v->audioSilenceChannel, _dequeueVideoAudio, NULL, v);
/*
utilSay("Frames: %d (%dx%d) Audio Samples: %ld (%d Hz) %d Channel%s",
v->videoProps->NumFrames,
v->propFrame->EncodedWidth,
v->propFrame->EncodedHeight,
v->audioProps->NumSamples,
v->audioProps->SampleRate,
v->audioProps->Channels,
v->audioProps->Channels == 1 ? "" : "s"
);
*/
// Add to player hash
v->id = _nextId;
HASH_ADD_INT(_videoPlayerHash, id, v);
result = _nextId++;
return result;
}
int videoPause(int playerIndex) {
VideoPlayerT *v = NULL;
// Get our player structure
HASH_FIND_INT(_videoPlayerHash, &playerIndex, v);
if (!v) utilDie("No video player at index %d in videoPause.", playerIndex);
v->playing = false;
v->resetTime = true;
return 0;
}
int videoPlay(int playerIndex) {
VideoPlayerT *v = NULL;
// Get our player structure
HASH_FIND_INT(_videoPlayerHash, &playerIndex, v);
if (!v) utilDie("No video player at index %d in videoPlay.", playerIndex);
v->playing = true;
v->resetTime = true;
return 0;
}
int videoQuit(void) {
VideoPlayerT *v = NULL;
VideoPlayerT *t = NULL;
// Unload any remaining videos
HASH_ITER(hh, _videoPlayerHash, v, t) {
videoUnload(v->id);
}
FFMS_Deinit();
return 0;
}
int videoSeek(int playerIndex, int seekFrame) {
VideoPlayerT *v = NULL;
// Get our player structure
HASH_FIND_INT(_videoPlayerHash, &playerIndex, v);
if (!v) utilDie("No video player at index %d in videoSeek.", playerIndex);
while (seekFrame >= v->videoProps->NumFrames) {
seekFrame -= v->videoProps->NumFrames;
}
while (seekFrame < 0) {
seekFrame += v->videoProps->NumFrames;
}
v->frame = seekFrame;
v->resetTime = true;
return 0;
}
int videoUnload(int playerIndex) {
VideoPlayerT *v = NULL;
// Get our player structure
HASH_FIND_INT(_videoPlayerHash, &playerIndex, v);
if (!v) utilDie("No video player at index %d in videoStop.", playerIndex);
Mix_HaltChannel(v->audioSilenceChannel);
Mix_UnregisterEffect(v->audioSilenceChannel, _dequeueVideoAudio);
Mix_FreeChunk(v->silenceChunk);
free(v->audioSilenceRaw);
SDL_FreeAudioStream(v->audioStream);
free(v->audioBuffer);
FFMS_DestroyAudioSource(v->audioSource);
FFMS_DestroyVideoSource(v->videoSource);
SDL_DestroyTexture(v->videoTexture);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-align"
HASH_DEL(_videoPlayerHash, v);
#pragma GCC diagnostic pop
free(v);
return 0;
}
int videoUpdate(int playerIndex, SDL_Texture **texture) {
int result = -1;
int64_t count = 0;
VideoPlayerT *v = NULL;
// Get our player structure
HASH_FIND_INT(_videoPlayerHash, &playerIndex, v);
if (!v) utilDie("No video player at index %d in videoUpdate.", playerIndex);
// Handle video frames (and time)
if ((SDL_GetTicks() - v->lastTickTime >= v->frameDeltaTime) || v->resetTime) {
v->lastTickTime = SDL_GetTicks();
if (v->frameData) {
SDL_UpdateTexture(v->videoTexture, NULL, v->frameData->Data[0], v->frameData->Linesize[0]);
}
*texture = v->videoTexture;
result = v->frame;
v->frameData = FFMS_GetFrame(v->videoSource, v->frame, &v->errInfo);
if (v->frameData == NULL) utilDie("%s", v->errInfo.Buffer);
v->frameInfo = FFMS_GetFrameInfo(FFMS_GetTrackFromVideo(v->videoSource), v->frame);
v->timestamp = (int64_t)((v->frameInfo->PTS * v->videoTimeBase->Num) / (double)v->videoTimeBase->Den); // Convert to milliseconds
v->frameDeltaTime = v->timestamp - v->lastFrameTime;
v->lastFrameTime = v->timestamp;
if (v->playing) {
if (++v->frame >= v->videoProps->NumFrames) {
v->frame = 0;
v->timestamp = 0;
v->resetTime = true;
}
}
if (v->resetTime) {
SDL_AudioStreamClear(v->audioStream);
v->lastTickTime = 0;
v->frameDeltaTime = 0;
v->audioPosition = (int64_t)((double)(v->timestamp * 0.001) * (double)v->audioProps->SampleRate);
v->resetTime = false;
}
}
// Handle audio samples
if ((v->playing) && (SDL_AudioStreamAvailable(v->audioStream) < AUDIO_STREAM_LOW_WATERMARK) && (v->audioPosition < v->audioProps->NumSamples)) {
// Maximum samples we can read at a time
count = AUDIO_SAMPLE_PREREAD;
// Don't read past end of audio data
if (v->audioPosition + count >= v->audioProps->NumSamples) {
count = v->audioProps->NumSamples - v->audioPosition - 1;
}
// Are we reading anything?
if (count > 0) {
// Get audio from video stream
if (FFMS_GetAudio(v->audioSource, v->audioBuffer, v->audioPosition, count, &v->errInfo)) utilDie("%s", v->errInfo.Buffer);
// Feed it to the mixer stream
if (SDL_AudioStreamPut(v->audioStream, v->audioBuffer, (int)(count * v->audioSampleSize)) < 0) utilDie("%s", SDL_GetError());
v->audioPosition += count;
}
}
return result;
}

41
singe/videoPlayer.h Normal file
View file

@ -0,0 +1,41 @@
/*
*
* Singe 2
* Copyright (C) 2019 Scott Duensing <scott@kangaroopunch.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifndef VIDEOPLAYER_H
#define VIDEOPLAYER_H
#include <SDL2/SDL.h>
int videoInit(void);
int videoIsPlaying(int playerIndex);
int videoLoad(char *filename, SDL_Renderer *renderer);
int videoPause(int playerIndex);
int videoPlay(int playerIndex);
int videoQuit(void);
int videoSeek(int playerIndex, int seekFrame);
int videoUnload(int playerIndex);
int videoUpdate(int playerIndex, SDL_Texture **texture);
#endif // VIDEOPLAYER_H