/** * 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 #include #include #include #include #include #include #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; }