Skip to content

Instantly share code, notes, and snippets.

@bashkirtsevich
Created November 23, 2016 09:43
Show Gist options
  • Save bashkirtsevich/089101f77e9eb3fd51062dc4819a62fd to your computer and use it in GitHub Desktop.
Save bashkirtsevich/089101f77e9eb3fd51062dc4819a62fd to your computer and use it in GitHub Desktop.
ffmpeg player
/**
* FFmpeg sample player
* Related to the article at http://habrahabr.ru/blogs/video/137793/
*/
#include <stdio.h>
#include <SDL.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("Usage: %s filename\n", argv[0]);
return 0;
}
// Register all available file formats and codecs
av_register_all();
int err;
// Init SDL with video support
err = SDL_Init(SDL_INIT_VIDEO);
if (err < 0) {
fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
return -1;
}
// Open video file
const char* filename = argv[1];
AVFormatContext* format_context = NULL;
err = avformat_open_input(&format_context, filename, NULL, NULL);
if (err < 0) {
fprintf(stderr, "ffmpeg: Unable to open input file\n");
return -1;
}
// Retrieve stream information
err = avformat_find_stream_info(format_context, NULL);
if (err < 0) {
fprintf(stderr, "ffmpeg: Unable to find stream info\n");
return -1;
}
// Dump information about file onto standard error
av_dump_format(format_context, 0, argv[1], 0);
// Find the first video stream
int video_stream;
for (video_stream = 0; video_stream < format_context->nb_streams; ++video_stream) {
if (format_context->streams[video_stream]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
break;
}
}
if (video_stream == format_context->nb_streams) {
fprintf(stderr, "ffmpeg: Unable to find video stream\n");
return -1;
}
AVCodecContext* codec_context = format_context->streams[video_stream]->codec;
AVCodec* codec = avcodec_find_decoder(codec_context->codec_id);
err = avcodec_open2(codec_context, codec, NULL);
if (err < 0) {
fprintf(stderr, "ffmpeg: Unable to open codec\n");
return -1;
}
SDL_Surface* screen = SDL_SetVideoMode(codec_context->width, codec_context->height, 0, 0);
if (screen == NULL) {
fprintf(stderr, "Couldn't set video mode\n");
return -1;
}
SDL_Overlay* bmp = SDL_CreateYUVOverlay(codec_context->width, codec_context->height,
SDL_YV12_OVERLAY, screen);
struct SwsContext* img_convert_context;
img_convert_context = sws_getCachedContext(NULL,
codec_context->width, codec_context->height,
codec_context->pix_fmt,
codec_context->width, codec_context->height,
PIX_FMT_YUV420P, SWS_BICUBIC,
NULL, NULL, NULL);
if (img_convert_context == NULL) {
fprintf(stderr, "Cannot initialize the conversion context\n");
return -1;
}
AVFrame* frame = avcodec_alloc_frame();
AVPacket packet;
while (av_read_frame(format_context, &packet) >= 0) {
if (packet.stream_index == video_stream) {
// Video stream packet
int frame_finished;
avcodec_decode_video2(codec_context, frame, &frame_finished, &packet);
if (frame_finished) {
SDL_LockYUVOverlay(bmp);
// Convert frame to YV12 pixel format for display in SDL overlay
AVPicture pict;
pict.data[0] = bmp->pixels[0];
pict.data[1] = bmp->pixels[2]; // it's because YV12
pict.data[2] = bmp->pixels[1];
pict.linesize[0] = bmp->pitches[0];
pict.linesize[1] = bmp->pitches[2];
pict.linesize[2] = bmp->pitches[1];
sws_scale(img_convert_context,
frame->data, frame->linesize,
0, codec_context->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(bmp);
SDL_Rect rect;
rect.x = 0;
rect.y = 0;
rect.w = codec_context->width;
rect.h = codec_context->height;
SDL_DisplayYUVOverlay(bmp, &rect);
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
// Handling SDL events there
SDL_Event event;
if (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
break;
}
}
}
sws_freeContext(img_convert_context);
// Free the YUV frame
av_free(frame);
// Close the codec
avcodec_close(codec_context);
// Close the video file
avformat_close_input(&format_context);
// Quit SDL
SDL_Quit();
return 0;
}
/**
* FFmpeg sample player with audio support and syncing.
* Compile with:
* gcc player2.c -o player -lavutil -lavformat -lavcodec -lswscale -lz -lbz2 `/opt/SDL/bin/sdl-config --cflags --libs`
* Related to: http://habrahabr.ru/blogs/video/138426/
* Author: Alexey Zhuchkov (zerodivisi0n)
* License: GPL
*/
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL.h>
#define SDL_AUDIO_BUFFER_SIZE 1024
#define AUDIO_BUF_SIZE ((AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2)
#define MAX_AUDIO_BUF_SIZE (AVCODEC_MAX_AUDIO_FRAME_SIZE * 4)
/* no AV sync correction is done if below the AV sync threshold */
#define AV_SYNC_THRESHOLD 0.01
/* no AV correction is done if too big error */
#define AV_NOSYNC_THRESHOLD 10.0
#define FF_REFRESH_EVENT (SDL_USEREVENT + 1)
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;
int size;
int eof;
SDL_mutex* mutex;
SDL_cond* cond;
} PacketQueue;
typedef struct RingBuffer {
uint8_t* data;
int size;
int max_size;
int rindex; // Read position
int windex; // Write position
char eof; // EOF flag
char lastop; // last operation flag: 0 - read, 1 - write
SDL_mutex* mutex;
SDL_cond* rcond;
SDL_cond* wcond;
} RingBuffer;
typedef struct VideoPicture {
SDL_Surface* screen;
SDL_Overlay* bmp;
double pts;
int ready;
SDL_mutex* mutex;
SDL_cond* cond;
} VideoPicture;
typedef struct MainContext {
AVFormatContext *format_context;
int quit;
// Streams
AVStream* video_stream;
AVStream* audio_stream;
// Queues
PacketQueue videoq;
PacketQueue audioq;
struct SwsContext* sws_context;
VideoPicture pict;
RingBuffer audio_buf;
double video_clock;
double audio_clock;
double frame_timer;
double frame_last_pts;
double frame_last_delay;
// Threads
SDL_Thread* demux_tid;
SDL_Thread* video_decode_tid;
SDL_Thread* audio_decode_tid;
} MainContext;
static void usage(const char* progname) {
assert(progname != NULL);
fprintf(stderr, "Usage %s movie\n", progname);
}
static void packet_queue_init(PacketQueue* q) {
assert(q != NULL);
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
q->cond = SDL_CreateCond();
}
static void packet_queue_deinit(PacketQueue* q) {
assert(q != NULL);
assert(q->mutex != NULL);
assert(q->cond != NULL);
SDL_DestroyMutex(q->mutex);
SDL_DestroyCond(q->cond);
}
static int packet_queue_put(PacketQueue* q, AVPacket* pkt) {
assert(q != NULL);
assert(pkt != NULL);
// Duplicate current packet
if (av_dup_packet(pkt) < 0) {
return -1;
}
AVPacketList* pkt_list = av_malloc(sizeof(AVPacketList));
if (pkt_list == NULL) {
return -1;
}
pkt_list->pkt = *pkt;
pkt_list->next = NULL;
SDL_LockMutex(q->mutex);
if (!q->eof) {
if (q->last_pkt == NULL) {
// It's a first packet in queue
q->first_pkt = pkt_list;
} else {
// Append to the end of queue
q->last_pkt->next = pkt_list;
}
q->last_pkt = pkt_list;
q->nb_packets++;
q->size += pkt->size;
}
SDL_CondSignal(q->cond);
SDL_UnlockMutex(q->mutex);
return 0;
}
static int packet_queue_get(PacketQueue* q, AVPacket* pkt, int block) {
assert(q != NULL);
assert(pkt != NULL);
SDL_LockMutex(q->mutex);
AVPacketList* pkt_list = q->first_pkt;
if (!block && (pkt_list == NULL)) {
SDL_UnlockMutex(q->mutex);
return -1;
} else {
while ((pkt_list == NULL) && !q->eof) {
// Wait for packets
SDL_CondWait(q->cond, q->mutex);
pkt_list = q->first_pkt;
}
}
if ((pkt_list == NULL) && q->eof) {
SDL_UnlockMutex(q->mutex);
return -1;
}
q->first_pkt = pkt_list->next;
if (q->first_pkt == NULL) {
// No more packets
q->last_pkt = NULL;
}
*pkt = pkt_list->pkt;
q->nb_packets--;
q->size -= pkt->size;
av_free(pkt_list);
SDL_UnlockMutex(q->mutex);
return 0;
}
static void packet_queue_eof(PacketQueue* q) {
assert(q != NULL);
SDL_LockMutex(q->mutex);
q->eof = 1;
SDL_CondBroadcast(q->cond);
SDL_UnlockMutex(q->mutex);
}
int ring_buffer_init(RingBuffer* rb, int initial_size, int max_size) {
assert(rb != NULL);
assert((max_size <= 0) || (max_size >= initial_size));
memset(rb, 0, sizeof(RingBuffer));
rb->data = av_malloc(initial_size);
if (rb->data == NULL) {
return -1;
}
rb->size = initial_size;
rb->max_size = max_size;
rb->mutex = SDL_CreateMutex();
rb->rcond = SDL_CreateCond();
rb->wcond = SDL_CreateCond();
return 0;
}
void ring_buffer_deinit(RingBuffer* rb) {
assert(rb != NULL);
assert(rb->mutex != NULL);
assert(rb->rcond != NULL);
assert(rb->wcond != NULL);
SDL_DestroyMutex(rb->mutex);
SDL_DestroyCond(rb->rcond);
SDL_DestroyCond(rb->wcond);
}
// Returns number of bytes written
int ring_buffer_write(RingBuffer* rb, void* buffer, int len, int block) {
assert(rb != NULL);
assert(rb != NULL);
if (len == 0) {
return 0;
}
SDL_LockMutex(rb->mutex);
if (rb->eof) {
// Buffer ended
SDL_UnlockMutex(rb->mutex);
return -1;
}
uint8_t* buffer_ptr = buffer;
while (len > 0) {
int step;
if ((rb->rindex < rb->windex) || // write forward
((rb->rindex == rb->windex) && (rb->lastop == 0))) { // buffer must be free
step = rb->size - rb->windex;
} else if (rb->rindex > rb->windex) {
step = rb->rindex - rb->windex;
} else if (block) {
SDL_CondWait(rb->rcond, rb->mutex);
if (rb->eof) {
break;
}
continue;
} else {
break;
}
if (len < step) {
step = len;
}
memcpy(rb->data + rb->windex, buffer_ptr, step);
rb->windex += step;
assert(rb->windex <= rb->size);
if (rb->windex == rb->size) {
rb->windex = 0;
}
rb->lastop = 1;
SDL_CondSignal(rb->wcond);
buffer_ptr += step;
len -= step;
}
SDL_UnlockMutex(rb->mutex);
return buffer_ptr - (uint8_t*)buffer;
}
// Returns number of bytes read
int ring_buffer_read(RingBuffer* rb, void* buffer, int len, int block) {
assert(rb != NULL);
assert(buffer != NULL);
if (len == 0) {
return 0;
}
SDL_LockMutex(rb->mutex);
uint8_t* buffer_ptr = buffer;
while (len > 0) {
int step;
if (rb->rindex < rb->windex) { // write forward
step = rb->windex - rb->rindex;
} else if ((rb->rindex > rb->windex) || // read forward
((rb->rindex == rb->windex) && (rb->lastop == 1))) { // buffer must be full
step = rb->size - rb->rindex;
} else if (block && !rb->eof) {
SDL_CondWait(rb->wcond, rb->mutex);
continue;
} else {
break;
}
if (len < step) {
step = len;
}
memcpy(buffer_ptr, rb->data + rb->rindex, step);
rb->rindex += step;
assert(rb->rindex <= rb->size);
if (rb->rindex == rb->size) {
rb->rindex = 0;
}
rb->lastop = 0;
SDL_CondSignal(rb->rcond);
buffer_ptr += step;
len -= step;
}
int count = buffer_ptr - (uint8_t*)buffer;
if ((count == 0) && rb->eof) {
count = -1;
}
SDL_UnlockMutex(rb->mutex);
return count;
}
int ring_buffer_size(RingBuffer* rb) {
assert(rb != NULL);
int size;
SDL_LockMutex(rb->mutex);
if (rb->rindex < rb->windex) {
size = rb->windex - rb->rindex;
} else {
size = rb->size - rb->rindex + rb->windex;
}
SDL_UnlockMutex(rb->mutex);
return size;
}
void ring_buffer_eof(RingBuffer* rb) {
assert(rb != NULL);
SDL_LockMutex(rb->mutex);
rb->eof = 1;
SDL_CondBroadcast(rb->wcond);
SDL_UnlockMutex(rb->mutex);
}
static AVStream* open_stream(AVFormatContext* format_context, int type) {
assert(format_context != NULL);
int index;
AVStream* stream = NULL;
// Find stream index
for (index = 0; index < format_context->nb_streams; ++index) {
if (format_context->streams[index]->codec->codec_type == type) {
stream = format_context->streams[index];
break;
}
}
if (stream == NULL) {
// Stream index not found
return NULL;
}
AVCodecContext* codec_context = stream->codec;
// Find suitable codec
AVCodec* codec = avcodec_find_decoder(codec_context->codec_id);
if (codec == NULL) {
// Codec not found
return NULL;
}
if (avcodec_open2(codec_context, codec, NULL) < 0) {
// Failed to open codec
return NULL;
}
return stream;
}
static void close_stream(AVStream* stream) {
assert(stream != NULL);
if (stream->codec != NULL) {
avcodec_close(stream->codec);
}
}
static void audio_callback(void* userdata, uint8_t* stream, int len) {
assert(userdata != NULL);
MainContext* main_context = (MainContext*)userdata;
ring_buffer_read(&main_context->audio_buf, stream, len, 1);
}
static int configure_audio(MainContext* main_context) {
assert(main_context != NULL);
AVCodecContext* codec_context = main_context->audio_stream->codec;
SDL_AudioSpec wanted_spec, spec;
// Set audio settings from codec info
wanted_spec.freq = codec_context->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = codec_context->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = main_context;
if (SDL_OpenAudio(&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL: %s\n", SDL_GetError());
return -1;
}
SDL_PauseAudio(0);
return 0;
}
static int open_file(MainContext* main_context, const char* filename) {
assert(main_context != NULL);
assert(filename != NULL);
int err;
// Open video file
AVFormatContext* format_context = NULL;
err = avformat_open_input(&format_context, filename, NULL, NULL);
if (err < 0) {
fprintf(stderr, "ffmpeg: Unable to open input file\n");
return -1;
}
main_context->format_context = format_context;
// Retrieve stream information
err = avformat_find_stream_info(format_context, NULL);
if (err < 0) {
fprintf(stderr, "ffmpeg: Unable to find stream info\n");
return -1;
}
// Dump information about file onto standard error
av_dump_format(format_context, 0, filename, 0);
// Open video and audio streams
main_context->video_stream = open_stream(format_context, AVMEDIA_TYPE_VIDEO);
if (main_context->video_stream == NULL) {
fprintf(stderr, "ffmpeg: Could not open video stream\n");
return -1;
}
main_context->audio_stream = open_stream(format_context, AVMEDIA_TYPE_AUDIO);
if (main_context->audio_stream == NULL) {
fprintf(stderr, "ffmpeg: Could not open audio stream\n");
return -1;
}
// Create conversion context
AVCodecContext* video_codec_context = main_context->video_stream->codec;
main_context->sws_context = sws_getCachedContext(NULL,
video_codec_context->width, video_codec_context->height,
video_codec_context->pix_fmt,
video_codec_context->width, video_codec_context->height,
PIX_FMT_YUV420P, SWS_BICUBIC,
NULL, NULL, NULL);
// Prepare SDL video output
main_context->pict.screen = SDL_SetVideoMode(video_codec_context->width, video_codec_context->height, 0, 0);
if (main_context->pict.screen == NULL) {
fprintf(stderr, "Couldn't set video mode\n");
return -1;
}
main_context->pict.bmp = SDL_CreateYUVOverlay(video_codec_context->width, video_codec_context->height,
SDL_YV12_OVERLAY, main_context->pict.screen);
if (main_context->pict.bmp == NULL) {
fprintf(stderr, "Couldn't create YUV overlay\n");
return -1;
}
// Set initial timer value
main_context->frame_timer = (double)av_gettime() / 1000000.0;
return 0;
}
// Main demux loop
static int demux_thread(void* arg) {
assert(arg != NULL);
MainContext* main_context = (MainContext*)arg;
int video_stream_index = main_context->video_stream->index;
int audio_stream_index = main_context->audio_stream->index;
AVPacket packet;
while (av_read_frame(main_context->format_context, &packet) >= 0) {
if (main_context->quit) {
av_free_packet(&packet);
break;
}
if (packet.stream_index == video_stream_index) {
// Video packet
packet_queue_put(&main_context->videoq, &packet);
} else if (packet.stream_index == audio_stream_index) {
// Audio packet
packet_queue_put(&main_context->audioq, &packet);
} else {
av_free_packet(&packet);
}
}
packet_queue_eof(&main_context->videoq);
packet_queue_eof(&main_context->audioq);
return 0;
}
double synchronize_video(MainContext* main_context, AVFrame *src_frame, double pts) {
assert(main_context != NULL);
assert(src_frame != NULL);
AVCodecContext* video_codec_context = main_context->video_stream->codec;
if(pts != 0) {
/* if we have pts, set video clock to it */
main_context->video_clock = pts;
} else {
/* if we aren't given a pts, set it to the clock */
pts = main_context->video_clock;
}
/* update the video clock */
double frame_delay = av_q2d(video_codec_context->time_base);
/* if we are repeating a frame, adjust clock accordingly */
frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
main_context->video_clock += frame_delay;
return pts;
}
static int video_decode_thread(void* arg) {
assert(arg != NULL);
MainContext* main_context = (MainContext*)arg;
AVCodecContext* video_codec_context = main_context->video_stream->codec;
AVFrame frame;
while (!main_context->quit) {
avcodec_get_frame_defaults(&frame);
// Get packet from queue
AVPacket pkt;
if (packet_queue_get(&main_context->videoq, &pkt, 1) < 0) {
// eof queue
break;
}
int got_frame;
int len = avcodec_decode_video2(video_codec_context, &frame, &got_frame, &pkt);
if (len < 0) {
av_free_packet(&pkt);
fprintf(stderr, "Failed to decode video frame\n");
break;
}
if (got_frame) {
// Consider sync
double pts = frame.pkt_dts;
if (pts == AV_NOPTS_VALUE) {
pts = frame.pkt_pts;
}
if (pts == AV_NOPTS_VALUE) {
pts = 0;
}
pts *= av_q2d(main_context->video_stream->time_base);
pts = synchronize_video(main_context, &frame, pts);
// Wait until picture is released
SDL_LockMutex(main_context->pict.mutex);
while (main_context->pict.ready && !main_context->quit) {
SDL_CondWait(main_context->pict.cond, main_context->pict.mutex);
}
SDL_UnlockMutex(main_context->pict.mutex);
if (main_context->quit) {
break;
}
SDL_Overlay* bmp = main_context->pict.bmp;
main_context->pict.pts = pts;
// Convert frame to YV12 pixel format for display in SDL overlay
SDL_LockYUVOverlay(bmp);
AVPicture pict;
pict.data[0] = bmp->pixels[0];
pict.data[1] = bmp->pixels[2]; // it's because YV12
pict.data[2] = bmp->pixels[1];
pict.linesize[0] = bmp->pitches[0];
pict.linesize[1] = bmp->pitches[2];
pict.linesize[2] = bmp->pitches[1];
sws_scale(main_context->sws_context,
frame.data, frame.linesize,
0, video_codec_context->height,
pict.data, pict.linesize);
SDL_UnlockYUVOverlay(bmp);
SDL_LockMutex(main_context->pict.mutex);
main_context->pict.ready = 1;
SDL_CondSignal(main_context->pict.cond);
SDL_UnlockMutex(main_context->pict.mutex);
}
}
return 0;
}
static int audio_decode_thread(void *arg) {
assert(arg != NULL);
MainContext* main_context = (MainContext*)arg;
AVCodecContext* audio_codec_context = main_context->audio_stream->codec;
AVFrame frame;
while (!main_context->quit) {
avcodec_get_frame_defaults(&frame);
// Get packet from queue
AVPacket pkt;
if (packet_queue_get(&main_context->audioq, &pkt, 1) < 0) {
// eof queue
break;
}
// The audio packet can contain several frames
int got_frame;
int len = avcodec_decode_audio4(audio_codec_context, &frame, &got_frame, &pkt);
if (len < 0) {
av_free_packet(&pkt);
fprintf(stderr, "Failed to decode audio frame\n");
break;
}
if (got_frame) {
// Store frame
// Get decoded buffer size
int data_size = av_samples_get_buffer_size(NULL, audio_codec_context->channels,
frame.nb_samples,
audio_codec_context->sample_fmt, 1);
// Obtain audio clock
if (pkt.pts != AV_NOPTS_VALUE) {
main_context->audio_clock = av_q2d(main_context->audio_stream->time_base) * pkt.pts;
} else {
/* if no pts, then compute it */
main_context->audio_clock += (double)data_size /
(audio_codec_context->channels *
audio_codec_context->sample_rate *
av_get_bytes_per_sample(audio_codec_context->sample_fmt));
}
ring_buffer_write(&main_context->audio_buf, frame.data[0], data_size, 1);
}
av_free_packet(&pkt);
}
ring_buffer_eof(&main_context->audio_buf);
return 0;
}
static uint32_t sdl_refresh_timer(uint32_t interval, void *opaque) {
SDL_Event event;
event.type = FF_REFRESH_EVENT;
event.user.data1 = opaque;
SDL_PushEvent(&event);
return 0;
}
static void schedule_refresh(MainContext* main_context, int delay) {
assert(main_context != 0);
assert(delay > 0.0);
SDL_AddTimer(delay, sdl_refresh_timer, main_context);
}
static double get_audio_clock(MainContext* main_context) {
assert(main_context != NULL);
AVCodecContext* audio_codec_context = main_context->audio_stream->codec;
double pts = main_context->audio_clock;
int bytes_per_sec = audio_codec_context->sample_rate *
audio_codec_context->channels *
av_get_bytes_per_sample(audio_codec_context->sample_fmt);
int buffer_size = ring_buffer_size(&main_context->audio_buf);
if (bytes_per_sec != 0) {
pts -= (double)buffer_size / bytes_per_sec;
}
return pts;
}
static void video_display(MainContext* main_context) {
AVCodecContext* video_codec_context = main_context->video_stream->codec;
SDL_Rect rect;
rect.x = 0;
rect.y = 0;
rect.w = video_codec_context->width;
rect.h = video_codec_context->height;
SDL_DisplayYUVOverlay(main_context->pict.bmp, &rect);
}
static double compute_delay(MainContext* main_context) {
double delay = main_context->pict.pts - main_context->frame_last_pts;
if (delay <= 0.0 || delay >= 1.0) {
// Delay incorrect - use previous one
delay = main_context->frame_last_delay;
}
// Save for next time
main_context->frame_last_pts = main_context->pict.pts;
main_context->frame_last_delay = delay;
// Update delay to sync to audio
double ref_clock = get_audio_clock(main_context);
double diff = main_context->pict.pts - ref_clock;
double sync_threshold = FFMAX(AV_SYNC_THRESHOLD, delay);
if (fabs(diff) < AV_NOSYNC_THRESHOLD) {
if (diff <= -sync_threshold) {
delay = 0;
} else if (diff >= sync_threshold) {
delay = 2 * delay;
}
}
main_context->frame_timer += delay;
double actual_delay = main_context->frame_timer - (av_gettime() / 1000000.0);
if(actual_delay < 0.010) {
/* Really it should skip the picture instead */
actual_delay = 0.010;
}
return actual_delay;
}
static void video_refresh_timer(MainContext* main_context) {
SDL_LockMutex(main_context->pict.mutex);
while ((main_context->pict.ready == 0) && !main_context->quit) {
SDL_CondWait(main_context->pict.cond, main_context->pict.mutex);
}
SDL_UnlockMutex(main_context->pict.mutex);
if (main_context->quit) {
return;
}
// Sync video to audio
double delay = compute_delay(main_context);
schedule_refresh(main_context, (int)(delay * 1000 + 0.5));
// Show the picture
video_display(main_context);
SDL_LockMutex(main_context->pict.mutex);
main_context->pict.ready = 0;
SDL_CondSignal(main_context->pict.cond);
SDL_UnlockMutex(main_context->pict.mutex);
}
static MainContext* allocate_context() {
MainContext* main_context = av_mallocz(sizeof(MainContext));
if (main_context == NULL) {
// memory allocation failed
return NULL;
}
packet_queue_init(&main_context->videoq);
packet_queue_init(&main_context->audioq);
ring_buffer_init(&main_context->audio_buf, AUDIO_BUF_SIZE, MAX_AUDIO_BUF_SIZE);
main_context->pict.mutex = SDL_CreateMutex();
main_context->pict.cond = SDL_CreateCond();
return main_context;
}
static void free_context(MainContext* main_context) {
assert(main_context != NULL);
// Close streams
close_stream(main_context->video_stream);
close_stream(main_context->audio_stream);
packet_queue_deinit(&main_context->videoq);
packet_queue_deinit(&main_context->audioq);
ring_buffer_deinit(&main_context->audio_buf);
// Free SDL resources
SDL_FreeSurface(main_context->pict.screen);
SDL_FreeYUVOverlay(main_context->pict.bmp);
// Close file
if (main_context->format_context != NULL) {
avformat_close_input(&main_context->format_context);
}
}
static void quit(MainContext* main_context) {
main_context->quit = 1;
SDL_CondBroadcast(main_context->pict.cond);
}
static void event_loop(MainContext* main_context) {
assert(main_context != NULL);
SDL_Event event;
while (!main_context->quit) {
SDL_WaitEvent(&event);
switch (event.type) {
case SDL_QUIT:
quit(main_context);
break;
case FF_REFRESH_EVENT:
video_refresh_timer(main_context);
break;
}
}
}
typedef int (SDLCALL *ThreadProc)(void *);
static SDL_Thread* thread_start(ThreadProc fn, void* userdata, const char* name) {
assert(fn != NULL);
SDL_Thread* thread = SDL_CreateThread(fn, userdata);
if (thread == NULL) {
fprintf(stderr, "SDL: Failed to run '%s' thread - %s\n", name, SDL_GetError());
}
return thread;
}
static int thread_wait(SDL_Thread* tid, const char* name) {
int status;
SDL_WaitThread(tid, &status);
printf("Thread '%s' finished with status %d\n", name, status);
return status;
}
int main(int argc, char* argv[]) {
int err;
// Check arguments
if (argc < 2) {
usage(argv[0]);
return -1;
}
// Register all available file formats and codecs
av_register_all();
// Init SDL
err = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);
if (err < 0) {
fprintf(stderr, "SDL: %s\n", SDL_GetError());
return -1;
}
MainContext* main_context = allocate_context();
const char* filename = argv[1];
err = open_file(main_context, filename);
if (err < 0) {
return -1;
}
configure_audio(main_context);
// Run required threads
main_context->demux_tid = thread_start(demux_thread, main_context, "demux");
main_context->video_decode_tid = thread_start(video_decode_thread, main_context, "video_decode");
main_context->audio_decode_tid = thread_start(audio_decode_thread, main_context, "audio_decode");
// Start video refresh
schedule_refresh(main_context, 40);
// Start event loop
event_loop(main_context);
// Wait until all the threads will be finished
thread_wait(main_context->demux_tid, "demux");
thread_wait(main_context->video_decode_tid, "video_decode");
thread_wait(main_context->audio_decode_tid, "audio_decode");
// Free resources
free_context(main_context);
SDL_Quit();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment