FFmpeg API(下)

接着上一篇《FFmpeg API(上)》中的示例代码,其中代码很多方法调用并没有详细注释,现在特意注释一下,顺便对着这张图熟悉熟悉FFmpeg处理音视频的基本流程。需要理解的几个重要的结构体 AVFormatContext 、AVCodecContext 、AVPacket 与 AVFrame,这同样也是上篇文章中的示例程序里用的特别多的几个结构体。

FFmpeg主要模块

上面的示例程序基本上演示了 FFmpeg 的一些常用 API,现在就对这些函数进行一个大致解读。

libavformat模块

AVFormatContext 是API层直接接触到的结构体,它会进行格式的封装与解封装,它的数据部分由底层提供,底层使用了 AVIOContext,这个 AVIOContext 实际上就是为普通的I/O增加了一层Buffer缓冲区,再往底层就是URLContext,也就是到达了协议层,协议层的具体实现有很多,包括rtmp、http、hls、file等,这就是libavformat的内部封装了。

libavcodec模块

对于开发者来说,这一层我们能接触到的最顶层的结构体就是 AVCodecContext,该结构体包含的就是与实际的编解码有关的部分。首先,AVCodecContext 是包含在一个 AVStream里面的,即描述了这路流的编码格式是什么,其中存放了具体的编码格式信息,根据Codec的信息可以打开编码器或者解码器,然后利用该编码器或者解码器进行 AVPacket 与 AVFrame 之间的转换(实际上就是解码或者编码的过程),这是 FFmpeg 中最重要的一部分。

函数功能

主要是介绍从文件/文件夹操作之后的一些函数。

av_register_all

在编译FFmpeg的时候,其中开启或者关闭了很多选项,生成 config.mk 与 config.h。config.mk实际上就是makefile文件需要包含进去的子模块,从而编译出正确的库;而config.h是作用在运行阶段,这一阶段将确定需要注册哪些容器以及编解码格式到FFmpeg框架中。所以该函数的内部实现会先调用 avcodec_register_all 来注册所有config.h 里面开放的编解码器,然后会注册所有的Muxer和Demuxer(也就是封装格式),最后注册所有的Protocol(即协议层的东西)。

avformat_open_input

函数 avformat_open_input 会根据所提供的文件路径判断文件的格式,其实就是通过这一步来决定使用的到底是哪一个 Demuxer。举例来说,如果是 flv,那么 Demuxer 就会使用对应的 ff_flv_demuxer,所以对应的关键生命周期的方法 read_header、read_packet、read_seek、read_close 都会使用该 flv的 Demuxer 中函数指针指定的函数。read_header 函数会将 AVStream 结构体构造好,以便后续的步骤继续使用 AVStream 作为输入参数。

avformat_find_stream_info

该函数可以读取一部分视音频数据并且获得一些相关的信息。avformat_find_stream_info() 的声明位于libavformat\avformat.h ,这个函数的作用就是把所有 Stream 的 MetaData 信息填充好。

方法内部会先查找对应的解码器,然后打开对应的解码器,紧接着会利用 Demuxer 中的 read_packet 函数读取一段数据进行解码,当然解码的数据越多,分析出的流信息就会越准确,如果是本地资源,那么很快就可以得到非常准确的信息了,但是对于网络资源来说,则会比较慢,因此该函数有几个参数可以控制读取数据的长度,一个是probe size,一个是max_analyze_duration,还有一个是 fps_probe_size,这三个参数共同控制解码数据的长度,当然,如果配置这几个参数的值越小,那么这个函数执行的时间就会越快,但是会导致AVStream结构体里面一些信息(视频的宽、高、fps、编码类型等)不准确。

在直播场景下的拉流客户端中 ”秒开首屏” 就与这个函数息息相关。

av_read_frame

使用 av_read_frame 读取出来的数据到 AVPacket,该函数的实现首先会委托到 Demuxer 的 read_packet 方法中去,当然 read_packet 通过解复用层和协议层的处理之后,会将数据返回到这里,在该函数中进行数据缓冲处理。对于音频流,一个 AVPacket 可能包含多个AVFrame,但是对于视频流,一个 AVPacket 只包含一个AVFrame。

av_init_packet

av_init_packet 直接为一个已经分配好内存的指针或对象参数置为默认值。

av_packet_unref

av_packet_free 就是首先调用 av_packet_unref 来减少引用计数,然后释放AVPacket。一般在处理完压缩数据之后,并且在进入下一次循环之前,记得使用 av_packet_unref 来释放已经分配的 AVPacket 。

avformat_close_input

该函数负责释放对应的资源,首先会调用对应的 Demuxer 中的生命周期 read_close 方法,然后释放掉AVFormatContext,最后关闭文件或者远程网络连接。

avformat_alloc_output_context2

该函数内部需要调用方法 avformat_alloc_context 来分配一个 AVFormatContext 结构体,当然最关键的还是根据上一步注册的 Muxer 和 Demuxer部分(也就是封装格式部分)去找到对应的格式。有可能是 flv 格式、MP4 格式、mov 格式,甚至是MP3格式等,如果找不到对应的格式(即在configure选项中没有打开这个格式的开关),那么这里会返回找不到对应的格式的错误提示。在调用API的时候,可以使用 av_err2str 把返回整数类型的错误代码转换为肉眼可读的字符串,这在调试的时候是一个比较有用的工具函数。该函数最终会将找出来的格式赋值给 AVFormatContext 类型的 oformat。

av_dump_format

av_dump_format 用于打印关于输入或输出格式的详细信息,例如持续时间,比特率,流,容器,程序,元数据,边数据,编解码器和时基。 最后一个参数 is_output 选择指定的上下文是输入(0)还是输出(1),也就说最后一个参数填0,打印输入流;最后一个参数填1,打印输出流。