1
FFmpeg-ffplay 播放器分析(1).md
作者:Changing Lin
- 2022-11-01 广东
本文字数:3700 字
阅读完需:约 12 分钟
1.背景:
本文主要分析 ffplay 播放器的基础框架,从而理解代码结构和工作原理,同时,理解时间戳和音视频同步等知识点,掌握 ffmpeg 基础开发能力。
2.正文
1.main 方法
/* Called from the main */int main(int argc, char **argv){ avformat_network_init(); // 初始化ffmpeg框架 SDL_Init (flags); SDL_CreateWindow SDL_CreateRenderer SDL_GetRendererInfo is = stream_open(input_filename, file_iformat); event_loop(is);}
复制代码
2.stream_open 方法
static VideoState *stream_open(const char *filename, AVInputFormat *iformat){ frame_queue_init packet_queue_init init_clock SDL_CreateThread(read_thread, "read_thread", is); // 新建一个名字叫read_thread的子线程}
复制代码
3.event_loop 方法
static void event_loop(VideoState *cur_stream)for (;;) { refresh_loop_wait_event(cur_stream, &event); // 监听各种输入事件,移动进度,快进快退,退出,鼠标事件等 switch (event.type) { case SDL_KEYDOWN: switch (event.key.keysym.sym) { case SDLK_f: break; case SDLK_p: case SDLK_SPACE: break; case SDLK_m: break; case SDLK_KP_MULTIPLY: case SDLK_0: break; case SDLK_KP_DIVIDE: case SDLK_9: break; case SDLK_s: // S: Step to next frame break; case SDLK_a: break; case SDLK_v: break; case SDLK_c: break; case SDLK_t: break; case SDLK_w: break; case SDLK_PAGEUP: break; case SDLK_PAGEDOWN: break; case SDLK_LEFT: goto do_seek; case SDLK_RIGHT: goto do_seek; case SDLK_UP: goto do_seek; case SDLK_DOWN: do_seek: break; default: break; } break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEMOTION: break; case SDL_WINDOWEVENT: break; case SDL_QUIT: case FF_QUIT_EVENT: break; default: break; }}}
复制代码
4.read_thread 子线程
static int read_thread(void *arg){ AVFormatContext * ic = avformat_alloc_context(); avformat_open_input(&ic, is->filename, is->iformat, &format_opts); av_format_inject_global_side_data(ic); stream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]); stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]); stream_component_open(is, st_index[AVMEDIA_TYPE_SUBTITLE]); for (;;) { ret = av_read_frame(ic, pkt); if (ret < 0) { packet_queue_put_nullpacket(&is->videoq, is->video_stream); or packet_queue_put_nullpacket(&is->audioq, is->audio_stream); or packet_queue_put_nullpacket(&is->subtitleq, is->subtitle_stream); continue; } packet_queue_put(&is->audioq, pkt); or packet_queue_put(&is->videoq, pkt); or packet_queue_put(&is->subtitleq, pkt); }}
复制代码
5.stream_component_open 方法
static int stream_component_open(VideoState *is, int stream_index){ avctx = avcodec_alloc_context3(NULL); codec = avcodec_find_decoder(avctx->codec_id); ret = avcodec_open2(avctx, codec, &opts) switch (avctx->codec_type) { case AVMEDIA_TYPE_AUDIO: decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread); ret = decoder_start(&is->auddec, audio_thread, "audio_decoder", is) break; case AVMEDIA_TYPE_VIDEO: decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread); ret = decoder_start(&is->viddec, video_thread, "video_decoder", is) break; case AVMEDIA_TYPE_SUBTITLE: break; }}
复制代码
6.decoder_init/decoder_start 方法
d->decoder_tid = SDL_CreateThread(fn, thread_name, arg); // 启动子线程
复制代码
7.video_decoder 子线程
static int video_thread(void *arg){ for (;;) { ret = get_video_frame(is, frame); ret = queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial); }}
复制代码
8.queue_picture 方法
static int queue_picture(VideoState *is, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial){ av_frame_move_ref(vp->frame, src_frame); frame_queue_push(&is->pictq)}
复制代码
9.get_video_frame 方法
static int get_video_frame(VideoState *is, AVFrame *frame){ got_picture = decoder_decode_frame(&is->viddec, frame, NULL); if (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) { if (frame->pts != AV_NOPTS_VALUE) { double diff = dpts - get_master_clock(is); if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD && diff - is->frame_last_filter_delay < 0 && is->viddec.pkt_serial == is->vidclk.serial && is->videoq.nb_packets) { is->frame_drops_early++; // 计算是否丢帧,会不会是追帧策略? av_frame_unref(frame); got_picture = 0; } } }}
复制代码
10.decoder_decode_frame 方法
static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) { for (;;) { ret = avcodec_receive_frame(d->avctx, frame); packet_queue_get(d->queue, &pkt, 1, &d->pkt_serial) avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN) }}
复制代码
11.refresh_loop_wait_event 方法
static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) { double remaining_time = 0.0; SDL_PumpEvents(); while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) { if (!cursor_hidden && av_gettime_relative() - cursor_last_shown > CURSOR_HIDE_DELAY) { SDL_ShowCursor(0); cursor_hidden = 1; } if (remaining_time > 0.0) av_usleep((int64_t)(remaining_time * 1000000.0)); remaining_time = REFRESH_RATE; if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh)) video_refresh(is, &remaining_time); SDL_PumpEvents(); }}
复制代码
12.video_refresh->video_display->video_image_display 方法
static void video_refresh(void *opaque, double *remaining_time){ video_display(is);}static void video_display(VideoState *is){ if (!is->width) video_open(is); if (is->audio_st && is->show_mode != SHOW_MODE_VIDEO) video_audio_display(is); else if (is->video_st) video_image_display(is); SDL_RenderPresent(renderer);}static void video_image_display(VideoState *is){ vp = frame_queue_peek_last(&is->pictq); upload_texture SDL_RenderCopyEx SDL_RenderCopy}
复制代码
3.总结
3.1 多线程
通过梳理,ffplay 播放器总共开了 5 个线程
主线程:循环监听输入事件,从 FrameQueue 中获取每一帧 Frame,渲染画面或播放音频
read_thread:启动下面 3 个子线程,循环从 Stream 中读取每一个 AVPacket,并且放到 PacketQueue 里面
video_thread:循环从 PacketQueue 里面获取一个 AVPacket,并且解码成一个 Frame,计算是否丢帧策略,放到 FrameQueue 里面
audio_thread:如上,只不过 AVPacket 的类型是 audio,也就是音频包、音频帧
subtitle_thread:如上,只不过 AVPacket 的类型是 subtitle,也就是字幕包、字幕帧
3.2 时间戳:
音视频同步
快进、快退、从指定位置开始播放
划线
评论
复制
发布于: 刚刚阅读数: 4
版权声明: 本文为 InfoQ 作者【Changing Lin】的原创文章。
原文链接:【http://xie.infoq.cn/article/e7427a6488785db64ffcc2b5b】。文章转载请联系作者。
Changing Lin
关注
获得机遇的手段远超于固有常规之上~ 2020-04-29 加入
我能做的,就是调整好自己的精神状态,以最佳的面貌去面对那些未曾经历过得事情,对生活充满热情和希望。









评论