In this tutorial we will use theoraplay to decode theora video (.ogv).
There is an example of this player for SDL 1.2 in the library theoraplay. I adapted the code for SDL2 since some things like SDL_Overlay has been deleted.
We want to create a function to play a video file.
The main function is simply a call to that function:
int main(int argc, char **argv) {
playfile("video.ogv");
return 0;
}
THEORAPLAY_Decoder *decoder = THEORAPLAY_startDecodeFile(fname, 30, THEORAPLAY_VIDFMT_IYUV);
const THEORAPLAY_VideoFrame *video = NULL;
const THEORAPLAY_AudioPacket *audio = NULL;
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
while (!audio || !video) {
if (!audio) audio = THEORAPLAY_getAudio(decoder);
if (!video) video = THEORAPLAY_getVideo(decoder);
SDL_Delay(10);
}
int width = video->width;
int height = video->height;
framems = (video->fps == 0.0) ? 0 : ((Uint32)(1000.0 / video->fps));
screen = SDL_CreateWindow("Video Player SDL2", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_OPENGL);
SDL_Renderer* renderer = SDL_CreateRenderer(screen, -1, 0);
SDL_texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, video->width, video->height);
int quit = 0;
int initfailed = quit = (!screen || !texture);
SDL_AudioSpec spec;
memset(&spec, '\0', sizeof(SDL_AudioSpec));
spec.freq = audio->freq;
spec.format = AUDIO_S16SYS;
spec.channels = audio->channels;
spec.samples = 2048;
spec.callback = audio_callback;
void SDLCALL audio_callback(void *userdata, Uint8 *stream, int len)
typedef struct AudioQueue {
const THEORAPLAY_AudioPacket *audio;
int offset;
struct AudioQueue *next;
} AudioQueue;
static Uint32 baseticks = 0;
static volatile AudioQueue *audio_queue = NULL;
static volatile AudioQueue *audio_queue_tail = NULL;
static void SDLCALL audio_callback(void *userdata, Uint8 *stream, int len) {
Sint16 *dst = (Sint16 *)stream;
while (audio_queue && (len > 0)) {
volatile AudioQueue *item = audio_queue;
AudioQueue *next = item->next;
const int channels = item->audio->channels;
const float *src = item->audio->samples + (item->offset * channels);
int cpy = (item->audio->frames - item->offset) * channels;
int i;
if (cpy > (len / sizeof(Sint16)))
cpy = len / sizeof(Sint16);
for (i = 0; i < cpy; i++) {
const float val = *(src++);
if (val < -1.0f)
*(dst++) = -32768;
else if (val > 1.0f)
*(dst++) = 32767;
else
*(dst++) = (Sint16)(val * 32767.0f);
}
item->offset += (cpy / channels);
len -= cpy * sizeof(Sint16);
if (item->offset >= item->audio->frames) {
THEORAPLAY_freeAudio(item->audio);
SDL_free((void *)item);
audio_queue = next;
}
}
if (!audio_queue)
audio_queue_tail = NULL;
if (len > 0)
memset(dst, '\0', len);
}
static void queue_audio(const THEORAPLAY_AudioPacket *audio) {
AudioQueue *item = (AudioQueue *)SDL_malloc(sizeof(AudioQueue));
if (!item) {
THEORAPLAY_freeAudio(audio);
return;
}
item->audio = audio;
item->offset = 0;
item->next = NULL;
SDL_LockAudio();
if (audio_queue_tail)
audio_queue_tail->next = item;
else
audio_queue = item;
audio_queue_tail = item;
SDL_UnlockAudio();
}
while (audio) {
queue_audio(audio);
audio = THEORAPLAY_getAudio(decoder);
}
void *pixels = NULL;
int pitch = 0;
baseticks = SDL_GetTicks();
while (!quit && THEORAPLAY_isDecoding(decoder)) {
const Uint32 now = SDL_GetTicks() - baseticks;
if (!video)
video = THEORAPLAY_getVideo(decoder);
if (video && (video->playms <= now)) {
if (framems && ((now - video->playms) >= framems)) {
const THEORAPLAY_VideoFrame *last = video;
while ((video = THEORAPLAY_getVideo(decoder)) != NULL) {
THEORAPLAY_freeVideo(last);
last = video;
if ((now - video->playms) < framems)
break;
}
if (!video)
video = last;
}
SDL_LockTexture(texture, NULL, &pixels, &pitch);
const int w = video->width;
const int h = video->height;
const Uint8 *y = (const Uint8 *)video->pixels;
const Uint8 *u = y + (w * h);
const Uint8 *v = u + ((w / 2) * (h / 2));
Uint8 *dst = (Uint8*)pixels;
int i;
//memcpy(pixels, video->pixels, video->height * pitch); // For RGBA texture
for (i = 0; i < h; i++, y += w, dst += pitch) {
memcpy(dst, y, w);
}
for (i = 0; i < h / 2; i++, u += w / 2, dst += pitch / 2) {
memcpy(dst, u, w / 2);
}
for (i = 0; i < h / 2; i++, v += w / 2, dst += pitch / 2) {
memcpy(dst, v, w / 2);
}
SDL_UnlockTexture(texture);
After the texture is updated we can free the video frame, if there is nothing to render we add a small delay.
THEORAPLAY_freeVideo(video);
video = NULL;
}
else {
SDL_Delay(10);
}
For each frame the audio need to be processed again:
while ((audio = THEORAPLAY_getAudio(decoder)) != NULL)
queue_audio(audio);
Most of the work is done, a small event loop and we can render the texture.
while (screen && SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
quit = 1;
break;
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_ESCAPE)
quit = 1;
break;
}
}
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
}
The last thing to do is display the error and free everything:
while (!quit) {
SDL_LockAudio();
quit = (audio_queue == NULL);
SDL_UnlockAudio();
if (!quit)
SDL_Delay(100);
}
if (initfailed)
printf("Initialization failed!\n");
else if (THEORAPLAY_decodingError(decoder))
printf("There was an error decoding this file!\n");
else
printf("done with this file!\n");
if (texture) SDL_DestroyTexture(texture);
if (video) THEORAPLAY_freeVideo(video);
if (audio) THEORAPLAY_freeAudio(audio);
if (decoder) THEORAPLAY_stopDecode(decoder);
SDL_CloseAudio();
SDL_Quit();
You can download the project: TheoPlay.7z