背景
很久以前对FFmpeg的源码脉络详细的梳理过,当时阅读的是雷神的FFmpeg 源码分析的博客文章,最近经常使用FFmpeg进行编解码、解封装等处理,但对FFmpeg内部的脉络只记得一点点它的结构设计及它的功能及API的使用方式,所以就准备了这篇博客。单纯分析FFmpeg的结构设计会非常枯燥,结合FFplay播放器的流程去梳理FFmpeg的脉络应该会起到事半功倍的效果。
本人才学疏浅,ffmpeg 工程非常复杂庞大,理解可能有误,还请热心指正。
播放器流程梳理
我这里把播放器的流程分为9个模块:文件的读取模块、文件格式解封装模块、视频解码模块、音频的解码模块、视频流格式转换模块、音频流重采样模块、音视频同步模块、视频渲染播放模块、音频播放模块。下面从播放器的角度简单介绍下这9个模块的功能。
播放器简单结构图:
文件的读取模块
播放器整体流程可以完全类比成工厂里面的流水线。文件的读取模块是流水线的第一个工序,它具备两大功能。
- 从本地/网络源源不断的读取数据
- 屏蔽数据的格式把数据流传送给下个模块
文件格式解封装模块
解封装模块的主要功能是:
- 解析媒体文件的格式信息
- 解析媒体流信息
- 媒体流信息打上时间戳
媒体文件的格式信息及媒体流信息的确定通常读取文件部分信息,确定媒体文件格式及媒体流格式。例如:FLV封装格式,开头的3个字符是“FLV”。也可以根据文件名后缀推测媒体文件的格式信息。媒体流信息的时间戳也要在这个模块计算,用于音视频之间的同步。
视频解码模块
解码模块主要功能就是把H264/H265格式的视频流解码成YUV格式。
音频的解码模块
解码模块主要功能就是把AAC格式的视频流解码成PCM格式。
视频流格式转换模块
视频解码模块输出的YUV格式可能不能直接在显示系统上面渲染,需要转化为显示系统支持的格式。
音频流重采样模块
重采样模块的主要功能与视频格式转换模块是相同的。由于音频渲染模块对音频的声道数、采样率、采样格式的限制,同样需要把音频解码之后的音频格式转化为音频渲染模块支持的格式。
音视频同步模块
设备、网络都没有任何限制的理想情况下,视频按照指定的帧率播放,音频按照采样率播放,它们默认就是同步的;但是这种情况在现实情况下是不存在的,特别是播放网络视频时,由于网络的不确定性必须在音频和视频之间添加规则让他们同步渲染。
常用的方式就是音频按照自己的采样率、声道数、采样格式正常的播放,同时保存当前音频帧的时间戳。视频流在渲染时通过对比当前帧的时间戳与音频的时间戳,决定是加快渲染还是延时渲染还是正常渲染。
视频渲染播放模块
FFplay 视频渲染模块采用的是SDL技术。是一套开放源代码的跨平台多媒体开发库,如果想了解跨平台渲染库的实现,可以学习SDL源码。
Android端视频渲染有多重方式,这里我介绍3种方式:
- OpenGL 渲染:通过 YUV -> RGB -> Texture的操作,然后把Texture通过OpenGL 操作渲染到屏幕上。
- NDK ANativeWindow技术:通过把Java层的Surface转化到NDK中的ANativeWindow,可以通过把YUV -> RGB, 再把RGB数据拷贝到ANativeWindow的Buffer中就可以实现渲染。
- MediaCodec: 通过向MediaCodec绑定渲染Surface,MediaCodec解码后自动完成渲染。
音频播放模块
FFplay 音频渲染模块采用的也是SDL技术。
Android端音频渲染方式:
- OpenSL
- AudioTrack
- AAudio
总结
播放器的流程大概就是上面的过程,下面的章节会把播放器的部分模块通过站在FFmpeg的视角再详细讲解下,下一章节的介绍才是整篇文章的重点。
从FFmpeg角度分析播放器流程
文件的读取模块
FFmpeg内部把文件的读取模块分为3层:
- AVIOContext、URLContext
- URLProtocol
- file(FileContext)、http(HttpContext)、udp(UdpContext)、tcp(TcpContext)
读取模块的最底层是file、http、udp、tcp的操作,底层模块的操作会被抽象成URLProtocol,每一个底层文件都对应着一个URLProtocol。URLContext对URLProtocol进行了一层封装,相关操作也只是简单的中转一下调用底层具体文件或协议的支撑函数。AVIOContext是对URLContext的功能的扩展,增加了缓冲机制。
当我们读写文件的时候,操作流程是:AVIOContext -> URLContext -> URLProtocol -> FileContext(file).
该模块主要通过ffmpeg libavformat目录中的file.c、avio.c、aviobuf.c等文件,实现文件的读写。
URLProtocol ff_file_protocol = {
.name = "file",
.url_open = file_open,
.url_read = file_read,
.url_write = file_write,
.url_seek = file_seek,
.url_close = file_close,
.url_get_file_handle = file_get_handle,
.url_check = file_check,
.url_delete = file_delete,
.url_move = file_move,
.priv_data_size = sizeof(FileContext),
.priv_data_class = &file_class,
.url_open_dir = file_open_dir,
.url_read_dir = file_read_dir,
.url_close_dir = file_close_dir,
};
URLProtocol ff_udp_protocol = {
.name = "udp",
.url_open = udp_open,