目录
AVFormat 模块
AVFormat 前处理部分
AVFormat 读写处理部分
小结
思考
FFmpeg 目录中包含了 FFmpeg 库代码目录、构建工程目录、自测子系统目录等,具体内容如下:
现在你知道 FFmpeg 的源代码目录中都包含了哪些内容,在之后使用 FFmpeg 的 API 做开发遇到问题时,就可以通过查看源代码来了解更多、更详细的内部实现了。
AVFormat 模块
从 FFmpeg 的目录结构中可以看出,libavformat 主要是用来做封装格式处理的模块,如果不做转码,只做切片或者封装格式转换的话,基本上用 AVFormat 模块就可以,下面我们来看一下 AVFormat 模块都有哪些常用接口提供给我们使用。
avformat_version、avformat_configuration、avformat_license 这三个接口都是用来调试的,确定使用的 FFmpeg 版本、编译配置信息以及 License。因为 FFmpeg 本身是 LGPL 的,但是FFmpeg 可以引入其他第三方库,比如 libfdkaac 是 nonfree 的,就有可能存在专利收费的法律风险。
如果引入了 libx264 这样的编码器,FFmpeg 会自动切换成 GPL 的 License,这个时候如果你想要基于 FFmpeg 做定制或者开发,就需要注意 GPL 的 License 法律风险,相关情况最好还是咨询一下开源 License 法律援助律师,尽量避免给自己的项目和公司带来不必要的麻烦。
GPL 是 GNU 公共许可证的缩写。它通常会具有 “传染性”,当某一项目使用了 GPL 下的软件部分的话,那么该项目将被 “感染”变成了 GPL 协议下产品,也就是你需要将其开源和免费。LGPL 是 GNU 宽松公共许可证的缩写,它是 GPL 的一个为主要为类库使用设计的开源协议。和 GPL 不同,LGPL 允许商业软件通过类库引用方式使用 LGPL 类库而不需要开源商业软件的代码。——关于开源许可 GPL 与 LGPL
AVFormat 前处理部分
当我们做音视频内容处理的时候,首先接触到的应该是 AVFormatContext 模块相关的操作,也就是我们这里说的 AVFormat 部分,但是操作 AVFormat 的时候,会有一个前处理部分,主要包含网络初始化、模块遍历、申请上下文空间、打开文件,还有分析音视频流等操作。下面我们逐个了解一下 AVFormat 前处理部分的接口与作用。
- avformat_network_init 和 avformat_network_deinit 两个接口,是网络相关模块的初始化和撤销网络相关模块初始化。
- av_muxer_iterate 和 av_demuxer_iterate 两个接口,是 muxer 和 demuxer 的遍历接口,如果你想查找自己需要的 muxer 或者 demuxer 是否在当前使用的 FFmpeg 库中,用这两个接口可以全面地查找。
- avformat_alloc_context 和 avformat_free_context 两个接口可以用来申请与释放 AVFormatContext 上下文结构。
- avformat_new_stream 接口用来创建新的 AVStream。
- av_stream_add_side_data 接口用来向 AVStream 中添加新的 side data 信息,例如视频旋转信息,通常是可以存储在 side data 里面的。
- av_stream_new_side_data 接口用来申请新的 side data。
- av_stream_get_side_data 接口用来获取 side data。
- avformat_alloc_output_context2 接口用来申请将要输出的文件的 AVFormatContext,可以通过 avformat_free_context 释放申请的 AVFormatContext。
- av_find_input_format 接口可以根据传入的 short_name 来获得对应的 AVFormat 模块,例如 MP4。
- avformat_open_input 接口主要用处是打开一个 AVInputFormat,并挂在 AVFormatContext 模块上,这个接口里面会调用 avformat_alloc_context,可以通过接口 avformat_close_input 来关闭和释放 avformat_open_input 里对应的 alloc 操作。
- av_find_best_stream 接口用来找到多个视频流或多个音频流中最优的那个流。
- avformat_find_stream_info 接口主要用来建立 AVStream 的信息,获得的信息大多数情况下是比较准确的。使用 avformat_find_stream_info 接口来获得 AVStream 信息的话,会比较消耗时间。因为里面需要通过 try_decode 进行解码操作,来获得更精准的 AVStream 信息,所以有些固定场景不使用 avformat_find_stream_info,是为了节省时间方面的开销。
我们可以通过 probesize、analyzeduration 来设置读取的音视频数据的阈值,avformat_find_stream_info 里面也会遍历这个阈值,所以通过设置 probesize 和 analyzeduration 也可以节省一些时间。
如果有多个类似 AAC 或者 H264 这样的 codec 的话,avformat_find_stream_info 内部会使用最先遍历到的 codec,其实我们可以在使用 avformat_find_stream_info 之前指定解码器,预期的结果会更准确一些。
AVFormat 读写处理部分
看完 AVFormat 前处理部分的操作,接下来我们进入 AVFormat 读写处理的部分。
- av_read_frame 接口用来从 AVFormatContext 中读取 AVPacket,AVPacket 里面存储的内容在之前有讲过,这里就不重复讲解了。
- 当拖动进度条的时候,我们可以调用 avformat_seek_file(旧版是 av_seek_frame)接口,seek 到自己想要指定的位置,但前提是对应的封装格式得支持精确 seek,seek 支持以下四种模式。
AVSEEK_FLAG_BACKWARD //往回seek
AVSEEK_FLAG_BYTE //以字节数的方式seek
AVSEEK_FLAG_ANY //可seek到任意帧
AVSEEK_FLAG_FRAME //以帧数量的方式seek
- avformat_flush 接口主要是用来清空当前 AVFormatContext 中的 buffer。
- avformat_write_header 接口主要用在“写”操作的开头部分,通常指传输协议的开始,写封装格式头部。avformat_write_header 里会调用到 avformat_init_output,通常 avformat_write_header 函数的最后一个参数可以传入 Option,Option 可以控制容器模块中的 Option,关于如何查看封装容器格式的 Option 参数,我们之前的时候讲过,你可以回顾一下。
写 MP4 文件有很多 Option,可以通过 ffmpeg -h muxer=mp4 看到生成 MP4 的一些列参数,也就是 Option。
- avformat_init_output 接口主要用来做容器格式初始化部分的操作,例如打开文件,或者有一些容器格式内部的信息需要初始化的时候。
- av_interleaved_write_frame 接口支持在写入 AVPacket 的时候,根据 dts 时间戳交错写入数据。使用这个接口有一个需要注意的地方,就是数据会先写入到 buffer 里用来交错存储数据,这个 buffer 会不断变大,如果有必要的话,可以考虑自己调用 avio_flush 或者写 NULL 把 buffer 写到磁盘。
我们在存储音视频数据的时候,如果是顺序读取音视频数据的话,音视频数据交错存储比较好,因为这样可以给内存、硬盘或者网络节省很多开销。
- av_write_frame 接口是不按照交错的形式存储 AVPacket,不过在写入文件的时候是直接写入到磁盘,不会有 buffer,所以可以考虑自己先做交错再用这个接口,不过我一般选择使用 av_interleaved_write_frame,因为比较方便,不需要自己做数据交错排列的操作。
- av_write_trailer 接口是写数据到封装容器的收尾部分。可以关闭和释放在此之前申请的内存,另外,MP4 文件如果需要把 moov 移动到 MP4 文件头部,也是在这个接口里面完成的。
小结
FFmpeg 中有很多重要的模块,比如 AVFormat 模块、AVcodec 模块、AVfilter 模块等。其中 AVFormat 是用来做封装格式处理的模块。这个模块的内部提供了很多常用的接口,比如前处理部分的 avformat_find_stream_info 等接口,读写处理部分的 avformat_write_header、av_interleaved_write_frame 等接口,了解这些接口的用途和可能出现的问题及解决办法,可以让我们在实践中更好地使用它们去做容器封装和解封装方面的操作。
关于 AVFormat 模块中 API 接口更多的使用方式,比如说参数相关的内容,你还需要多看一看 avformat.h 头文件中的注释和参数说明。如果你还是掌握不住这些接口的使用方式的话,也可以根据我的建议,先把源代码下来,去看一下 API 里实现的过程来加深理解。
思考
我们介绍最后一个接口 av_write_trailer 的时候,提到它支持把 MP4 的 moov 移动到文件的头部,在 FFmpeg 的命令行参数里面使用的是 -movflags faststart,那么如果我用 API 的话,需要在哪个接口里面传递这个参数呢?