内容摘自《ffmpeg/ffplay源码剖析》
1.播放器一般原理
可以直观的看到播放这个媒体文件的基本模块(filter),七个模块按广度顺序:读文件模块(source filter),解复用模块(Demux filter),视/音频解码模块(Decode filter),颜色空间转换模块(Color Space converter filter),视频/音频播放模块(Render filter)。
Source filter 源过滤器的作用是为下级demux filter 以包的形式源源不断的提供数据流,在下一级的demux filter 看来,本地文件和网络数据是一样的
Demux filter 解复用过滤器的作用是识别文件类型,媒体类型,分离出各媒体原始数据流,打上时钟信息后 送给下级decoder filter。
Decoder filter 解码过滤器的作用就是解码数据包,并且把同步时钟信息传递下去。
Color space converter filte r 颜色空间转换过滤器的作用是把视频解码器解码出来的数据转换成当前显示系统 支持的颜色格式
Render filter 渲染过滤器的作用就是在适当的时间渲染相应的媒体,对视频媒体就是直接显示图像,对音频 就是播放声音
GraphEdit 应用程序可以看成是一个支撑平台,支撑框架。它容纳各种filter,在filte r 间的传递一些通讯消息, 控制filter 的运行(启动暂停停止),维护filter 运行状态
2.ffplay播放器原理
1)source filter读文件模块,分3层。最底层的是file ,pipe ,tcp,udp,http 等这些具体的本地文件或网络协议(注意ffplay 把file 也当协议看待);中间抽象层用URLContext 结构来统一表示底层具体的本地文件或网络协议,相关操作也只是简单的中转一下调用底层具体文件或协议的支撑函数;最上层用ByteIOContext 结构来扩展URLProtocol 结构成内部有缓冲机制的广泛意义上的文件,并且仅仅由ByteIOContext 对模块外提供服务。此模块主要有libavformat 目录下的file.c,avio.h, avio.c, aviobuf.c 等文件,实现读媒体文件功能。
ByteIOContext | 接口 |
URLContext | 抽象 |
file ,pipe ,tcp,udp,http | 文件 |
2)Demux filter 解复用模块,可以简单的分为两层,底层是AVIContext,TCPContext,UDPContext 等等这些具体媒体的解复用结构和相关的基础程序,上层是AVInputFormat 结构和相关的程序。上下层之间由AVInputFormat 相对应的AVFormatContext 结构的priv_data 字段关联AVIContext 或TCPContext 或UDPContext 等等具体的文件 格式。AVInputFormat 和具体的音视频编码算法格式由AVFormatContext 结构的streams 字段关联媒体格式,streams 相当于demux filter 的output pin,解复用模块分离音视频裸数据通过streams 传递给下级音视频解码器。此模块 主要有libavformat 目录下的avidec.c,utils_format.c 文件。
AVInputFormat | |
AVIContext,TCPContext,UDPContext |
3)Decoder filter 解码模块, 也可以简单的分为两层, 由AVCodec 统一表述。上层是AVCodec 对应的 AVCodecContext 结构和相关的基础程序,底层是TSContext,MsrleContext 等等这些具体的编解码器内部使用的 结构和相关的基础程序。AVCodecContext 结构的priv_data 字段关联TSContext,MsrleContext 等等具体解码器上 下文。此模块主要有libavcodec 目录下的msrle.c,truespeech.c,truespeech_data.h,utils_codec.c 等文件。
主要数据结构关系图:
有一些是动态与静态的关系,比如, URLProtocol 和URLContext,AVInputFormat 和AVFormatContext, AVCodec 和AVCodecContext。ffplay 把其他的每个大功能抽象成一个相当于C++中COM 接口的数据结构,着重于功能函数,同时这些功能函数指针在编译的时候就能静态确定。每一个大功能都要支持多种类型的广义数据,ffplay 把这多种类型的广义数据的共同的部分抽象成对应的Context 结构,这些对应的context 结构着重于动态性,其核心成员只能在程序运行时动态确定其值。并且COM接口类的数据结构在程序运行时有很多很多实例,而相应的Context 类只有一个实例,这里同时体现了数据结构的划分原则,如果有一对多的关系就要分开定义。
有一些是指针表述的排他性包含关系(因为程序运行时同一类型的多种数据只支持一种,所以就有排他性)。 比如,AVCodecContext 用priv_data 包含MsrleContext 或TSContext,AVFormatContext 用priv_data 包含AVIContext 或其他类Context,AVStream 用priv_data 包含AVIStream 或其他类Stream。由前面数据结构的动态与静态关系 可知,ffplay 把多种类型的广义数据的共同部分抽象成context 结构,那么广义数据的各个特征不同部分就抽象成 各种具体类型的context,然后用priv_data 字段表述的指针排他性的关联起来。由于瘦身后的ffplay 只支持有限 类型,所以AVFormatContext 只能关联包含AVIContext,AVStream 只能关联包含AVIStream。
3.视音频处理的简单流程:
音视频数据流简单流程, 由ByteIOContext(URLContext/URLProtocol) 表示的广义输入文件, 在 AVStream(AVIStreamt)提供的特定文件容器流信息的指引下,用AVInputFormat(AVFormatContext /AVInputFormat )接口的read_packet()函数读取完整的一帧数据,分别放到音频或视频PacketQueue(AVPacketList/AVPacket)队列 中,这部分功能由decode_thread 线程完成。对于视频数据,video_thread 线程不停的从视频PacketQueue 队列中 取出视频帧,调用AVCodec(AVCodecContext/MsrleContext)接口的decode()函数解码视频帧,在适当延时后做颜 色空间转化并调用SDL 库显示出来。对于音频数据, SDL 播放库播放完缓冲区的PCM 数据后, 调用 sdl_audio_callback()函数解码音频数据,并把解码后的PCM 数据填充到SDL 音频缓存播放。当下次播放完后, 再调用sdl_audio_callback()函数解码填充,如此循环不已。