音视频开发-FFplay视频播放流程

背景说明
FFmpeg是一个开源 , 免费 , 跨平台的视频和音频流方案 , 它提供了一套完整的录制、转换以及流化音视频的解决方案 。而ffplay是有ffmpeg官方提供的一个基于ffmpeg的简单播放器 。学习ffplay对于播放器流程、ffmpeg的调用等等是一个非常好的例子 。本文就是对ffplay的一个基本的流程剖析 , 很多细节内容还需要继续钻研 。
注:本文师基于ffmpeg-2.0版本进行分析 , 具体代码行还请对号入座 , 谢谢!
主框架流程
下图是一个使用“gcc+eygpt+graphviz+手工调整”生成的一个ffplay函数基本调用关系图 , 其中只保留了视频部分 , 去除了音频处理、字幕处理以及一些细节处理部分 。

音视频开发-FFplay视频播放流程

文章插图
 
注:图中的数字表示了播放中的一次基本调用流程 , X?序号表示退出流程 。
从上图中我们可以了解到以下几种信息:
  • 三个线程:主流程用于视频图像显示和刷新、read_thread用于读取数据、video_thread用于解码处理;
  • 视频数据处理:由read_thread读取原始数据解复用后 , 按照packet的方式放入到队列中;由video_thread从packet队列中读取packet解码后 , 按照picture的方式放入到队列中;由主流程从picture队列中依次取picture进行显示;
  • 启动流程:启动流程如上图中的数字部分
  • 退出流程:退出流程如上图中的X?序号部分
下面将对三个线程分别加以详细描述 。
read_thread线程
从read_thread开始说起而不是从main线程 , 主要原因是考虑按照视频数据转换的方式比较好理解 。
read_thread的创建是在main-->stream_open函数中:
is->read_tid = SDL_CreateThread(read_thread, is);
read_thread线程主要分为三部分:
  • 初始化部分:主要包括SDL_mutex信号量创建、AVFormatContext创建、打开输入文件、解析码流信息、查找音视频数据流并打开对应的数据流 。对应ffplay.c文件中的2693-2810行代码;
  • 循环读取数据部分:主要包括pause和resume操作处理、seek操作处理、packet队列写入失败处理、读数据结束处理、然后是读数据并写入到对应的音视频队列中 。对应ffplay.c文件中的2812-2946行代码;
  • 反初始化部分:主要包括退出前的等待、关闭音视频流、关闭avformat、给主线程发送FF_QUIT_EVENT消息以及销毁SDL_mutex信号量 。对应ffplay.c文件中的2947-2972行代码;
初始化部分
主要包括SDL_mutex信号量创建、创建avformat上下文、打开输入文件、解析码流信息、查找音视频数据流并打开对应的数据流 。
创建wait_mutex互斥量
SDL_mutex *wait_mutex = SDL_CreateMutex();该互斥量主要用于在对(VideoState *)is->continue_read_thread操作时加保护 , 如2887行和2925行:
//代码段一/* if the queue are full, no need to read more */if (infinite_buffer<1 &&……) {undefined/* wait 10 ms */SDL_LockMutex(wait_mutex);SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);<-- line 2887SDL_UnlockMutex(wait_mutex);continue;} //代码段二ret = av_read_frame(ic, pkt);if (ret < 0) {undefinedif (ret == AVERROR_EOF || url_feof(ic->pb))eof = 1;if (ic->pb && ic->pb->error)break;SDL_LockMutex(wait_mutex);SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);<-- line 2925SDL_UnlockMutex(wait_mutex);continue;}而continue_read_thread从其名字上来看 , 是一个控制read_thread线程是否继续阻塞的信号量 , 上面两次阻塞的地方分别是:packet队列已满 , 需要等待一会(即超时10ms)或者收到信号重新循环;读数据失败 , 但是并不是IO错误(ic->pb->error) , 如读取网络实时数据时取不到数据 , 此时也需要等待或者收到信号重新循环 。
注:seek操作时(L1216)和音频队列为空(L2327)时 , 会发送continue_read_thread信号 。
AVFormatContext创建
(AVFormatContext *)ic = avformat_alloc_context();此处创建的avformat上下文 , 类似于一个句柄 , 后续所有avformat相关的函数调用第一个参数都是该上下文指针 , 如avformat_open_input、avformat_find_stream_info以及一些和av相关的函数接口第一个参数也是该指针 , 如av_find_best_stream、av_read_frame等等 。


推荐阅读