FFmpeg库的基本组成
- libavcodec: 提供各种音频,视频,字幕等编码和解码功能。
- libavformat: 用于音视频封装格式的生成和解析, 包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能。
- libavutil: 包括了hash器,解码器和各类工具函数。
- libavfilter: 音视频滤镜库,提供了音频特效和视频特效的处理。
- libavdevice: 提供了访问捕获设备和回放设备的接口。
- libswresample: 用于混音和音频重采样,对数字音频进行声道数,数据格式 采样率等多种基本信息的转换。
- libswscale: 用于视频场景比例缩放、色彩映射转换。比如YUV转换为RGB的数据
- libpostproc:用于后期效果处理,当使用avfilter的时候需要打开该模块的开关,因为它会使用该模块的一些移除函数。
ffmpeg中的结构体
结构体很多,最关键的可以分为以下几类
解协议(http,rtsp,rtmp,mms)
AVIOContext,URLProtocol,URLcontext主要用于存储音视频使用的协议类型以及状态。URLProtocol存储输入音视频使用的封装格式。每一种协议都对应一个URLProtocol结构。
解封装(flv、avi、rmvb、mp4)
AVFormatContext主要存储音视频封装格式中包含的信息。
AVInputFormat存储输入音视频使用的封装格式。每种音视频的封装格式都对应一个AVInputFormat结构。
解码(h264,mpeg2,aac,mp3)
每个AVStream存储一个视频流/音频流的相关数据,每个AVStream对应一个AVCodecContext,存储该视频流/音频流使用解码方式的相关数据;每个AVcodecContext对应一个AVCodec,包含该音频流/视频流对应的解码器,每种解码器都对应一个AVcodec结构。
存数据
视频的每个结构一般是存一帧,音频可能有好几帧。
解码前数据格式:AVPacket;
- 音频的话一个AVPacket中可能有好几个AVFrame
- 视频的话一个AVPacket中只有一个个AVFrame
解码后数据格式:AVFrame;
注册协议、格式与编解码器
使用FFmpeg的API首先要调用FFmpeg的注册协议,格式与编解码器的方法。确保所有的格式和编解码器都被注册到了FFmpeg框架中。
av_register_all();
这个方法里会还会调用 avfilter_register_all 和 avcodec_register_all avformat_register_all
如果需要用到网络的操作,也要注册网络协议
avformat_network_init();
ffmpeg API
利用FFmpeg解码的全部过程:
0.注册协议、格式与编解码器
1.打开媒体文件
2.寻找各个流,并且打开对应的解码器
3.初始化解码后数据的结构体
4.读取流内容并且解码
5.处理解码后的裸数据
6.关闭所有资源
通用API解析
1.av_register_all
编译静态库的时候有一些编译条件,通过enable,disable进行了了一些开关的控制,其中就包括协议或者一些编解码器,上边提到的注册协议,就是注册这里开开的也就是enable的部分。
2.av_find_codec
这里包含了两部分内容,一部分是寻找解码器,另一部分是寻找编码器,av_register_all中已经把编码器和解码器都存放到一个链表中了,泽库其实就是从这个构造的链表中进行遍历通过Codec的ID或者name进行条件匹配,最终返回对应的Codec。
3.avcodec_open2
该函数是打开编解码器的函数,无论编码,还是解码,都会用到。输入参数有三个
- AVCodecContext
- av_find_codec寻找出来的编解码器
- 第三个一般传NULL
该函数的具体实现文件是如何找到的?需要看第一部中如何注册的。可能会调用x264或者lame库中的API。
3.avcodec_close
就是avcodec_open的一个逆过程,找到对应的实现文件的close指针所指向的函数。然后调用对应的第三方库的API来关闭对应的编码库。
调用FFmpeg解码时用到的函数
1.avformat_open_input
通过文件路径判断文件格式,根据格式选择Demuxer(解轨器)。然后对应的关键生命周期的方法,read_header、read_packet、read_seek、read_close都会使用找到的解轨器中的函数指针指定的函数。
其中read_header会构建好AVStream的结构体,以便后续传参使用。
2.avformat_find_stream_info
它的作用就是把所有stream的metaData信息填充好。利用选择好的Demuxer中的read_packet函数读取一段数据进行解码。它
3.av_read_frame
该方法读取出来的数据是AVPacket(数据包)。这个函数的实现首先会委托到Demuxer的read_packet方法中去。
对于音频流一个AVPacket可能包含多个AVFrame,但是对于视频流一个AVPacket只包含一个AVFrame。该函数最终就返回一个AVPacket结构体
4.avcodec_decode
该方法包含两部分内容
- 解码音频
- 解码视频
解码其实是委托给对应的解码器来实施的,在打开解码器的时候就找到了对应的解码器的实现。解码器中会有对应的生命周期的函数的实现,最重要的就是 - init 打开解码器
- decoder 解码
- close 关闭解码器
5.avformat_close_input
该函数负责释放对应的资源,首先就会调用对应的Demuxer的read_close方法,然后释放掉AVFormatContext,最后关闭文件或者远程连接。
调用FFmpeg编码时用到的函数
1.avformat_alloc_output_context2
该函数内部会调用avformat_alloc_context来分配一个AVFormatContext结构体,然后找到注册的Muxer和Demuxer,也就是封装格式部分,去找到对应的封装格式。如果找不到返回对应的错误提示。
2.avio_open2
首先调用函数ffurl_open,构造出URLContext结构体,这个结构体中包含了URLProtocol(需要去第一步register_protocol中已经注册的协议链表中寻找);接着会调用avio_alloc_context方法,分配出AVIOContext结构体,并将上一步构造出来的URLProtocol传递进来;然后把上一步分配出来AVIOContext结构体赋值给AVFormatContext的属性,其实这就是图3-2表示的结构了。而该过程恰好是上面所分析的avformat_open_input函数的实现过程的一个逆过程。之前就提到过,编码过程和解码过程从逻辑上来讲本来就是一个逆过程,所以在FFmpeg的实现过程中它们也是一个逆过程。
后面的步骤也都是解码的一个逆过程,解码过程中的av_f ind_stream_info对应到这里就是avformat_new_stream和avformat_write_header。avformat_new_stream函数会将音频流或者视频流的信息填充好,分配出AVStream结构体,在音频流中分配声道、采样率、表示格式、编码器等信息,在视频流中分配宽、高、帧率、表示格式、编码器等信息;avformat_write_header函数与解码过程中的read_header恰好是一个逆过程,因此这里将不再介绍。接下来就是编码的阶段了,开发者需要将手动封装好的AVFrame结构体,作为avcodec_encode_video方法的输入,将其编码成为AVPacket,然后调用av_write_frame方法输出到媒体文件中。而av_write_frame方法会将编码后的AVPacket结构体作为Muxer中的write_packet生命周期方法的输入,write_packet函数会加上自己封装格式的头信息,然后调用协议层写到本地文件或者网络服务器上。最后一步就是av_write_trailer,该函数有一个非常大的坑,如果没有执行write_header操作,就直接执行write_trailer操作,程序会直接崩溃(即Crash掉),所以必须保证这两个函数成对出现。write_trailer函数的实现会把没有输出的AVPacket全部丢给协议层去做输出,然后会调用Muxer的write_trailer生命周期方法,对于不同的格式写出的尾部也不尽相同,这里不再逐一介绍。