Created
November 23, 2016 09:43
-
-
Save bashkirtsevich/089101f77e9eb3fd51062dc4819a62fd to your computer and use it in GitHub Desktop.
ffmpeg player
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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