FFMPEG完美入门资料---003---FFmpeg 架构
写在前面
如果对FFmpeg有需要更多了解的请订阅我的专题:音视频专辑
3.1 FFmpeg 文件结构
- libavformat
主要存放 ffmpeg 支持的 各种编解码器 的实现及 ffmpeg 编解码 功能相关的数
文件 | 简要说明 |
---|---|
allcodecs.c | 简单的注册类函数 |
avcodec.h | 编解码相关结构体定义和函数原型声明 |
dsputil.c | 限幅数组初始化 |
dsputil.h | 限幅数组声明 |
imgconvert.c | 颜色空间转换相关函数实现 |
imgconvert_template.h | 颜色空间转换相关结构体定义和函数声明 |
utils_codec.c | 一些解码相关的工具类函数的实现 |
mpeg4audio.c | mpeg4 音频编解码器的函数实现 |
mpeg4audio.h | mpeg4 音频编解码器的函数声明 |
mpeg4data.h | mpeg4 音视频编解码器的公用的函数声明及数据结构定义 |
mpeg4video.c | mpeg4 视频编解码器的函数实现 |
mpeg4video.h | mpeg4 视频编解码器的函数的声明及先关数据结构的定义 |
mpeg4videodec.c | mpeg4 视频解码器的函数实现 |
mpeg4videoenc.c | mpeg4 视频编码器的函数实现 |
- libavformat
本目录主要存 放 FFMPEG 支持 的各种媒体格 式 MUXER/DEMUXER 和数据流协议 的定义和实现 文件以及 ffmpeg 解复用 相关的数据结 构及函数定义
文件 | 简要说明 |
---|---|
allformats.c | 简单注册类函数 |
avformat.h | 文件和媒体格式相关函数声明和数据结 构定义 |
avio.c | 无缓冲 IO 相关函数实现 |
avio.h | 无缓冲 IO 相关结构定义和函数声明 |
aviobuf.c | 有缓冲数据 IO 相关函数实现 |
cutils.c | 简单的字符串操作函数 |
utils_format.c | 文件和媒体格式相关的工具函数的实现 |
file.c | 文件 io 相关函数 |
…… | 其他相关媒体流 IO 的函数和数据结构实 现文件。 如:rtsp、http 等。 |
avi.c | AVI 格式的相关函数定西 |
avi.h | AVI 格式的相关函数声明及数据结构定义 |
avidec.c | AVI 格式 DEMUXER 相关函数定义 |
avienc.c | AVI 格式 MUXER 相关函数定义 |
…… | 其他媒体格式的 muxer/demuxer 相关函 数及数据结构定义和声明文件 |
*libavutil
主要存放 ffmpeg 工具类 函数的定义
avutil.h | 简单的像素格式宏定义 |
---|---|
bswap.h | 简单的大小端转换函数的实现 |
commom.h | 公共的宏定义和简单函数的实现 |
mathematics.c | 数学运算函数实现 |
rational.h | 分数相关表示的函数实现 |
3.2 I\O 模块分析
3.2.1 概述
ffmpeg 项目的数据 IO 部分主要是在 libavformat 库中实现, 某些对于内存的操作部分在 libavutil 库中。数据 IO 是基于文件格式(Format)以及文件传输协议(Protocol) 的, 与具体的编解码标准无关。 ffmpeg 工程转码时数据 IO 层次关系如图所示:
对于上面的数据 IO 流程, 具体可以用下面的例子来说明, 我们从一个 http 服务器 获取音视频数据, 格式是 flv 的, 需要通过转码后变成 avi 格式, 然后通过 udp 协议进 行发布。 其过程就如下所示:
- 1、读入 http 协议数据流, 根据 http 协议获取真正的文件数据(去除无关报文信 息);
- 2、根据 flv 格式对数据进行解封装;
- 3、读取帧进行转码操作;
- 4、按照目标格式 avi 进行封装;
- 5、通过 udp 协议发送出去。
3.2.2 相关数据结构介绍
在 libavformat 库中与数据 IO 相关的数据结构主要有 URLProtocol、URLContext、ByteIOContext、AVFormatContext 等, 各结构之间的关系如图所示。
1、URLProtocol 结构
表示广义的输入文件, 该结构体提供了很多的功能函数, 每一种广义的输入文件 (如:file、pipe、tcp、rtp 等等)对应着一个 URLProtocol 结构,在 av_register_all() 中将该结构体初始化为一个链表, 表头为 avio.c 里的 URLProtocol *first_protocol = NULL;保存所有支持的输入文件协议, 该结构体的定义如下:
typedef struct URLProtocol { const char *name; int (*url_open)(URLContext *h, const char *url, int flags); int (*url_read)(URLContext *h, unsigned char *buf, int size);int (*url_write)(URLContext *h, const unsigned char *buf, int size); int64_t (*url_seek)(URLContext *h, int64_t pos, int whence); int (*url_close)(URLContext *h); struct URLProtocol *next; int (*url_read_pause)(URLContext *h, int pause);int64_t (*url_read_seek)(URLContext *h, int stream_index,int64_t timestamp, int flags);int (*url_get_file_handle)(URLContext *h);int priv_data_size;const AVClass *priv_data_class; int flags;int (*url_check)(URLContext *h, int mask);} URLProtocol;
注意到, URLProtocol 是一个链表结构, 这是为了协议的统一管理, ffmpeg 项目中 将所有的用到的协议都存放在一个全局变量 first_protocol 中, 协议的注册是在 av_register_all 中完成的, 新添加单个协议可以调用 av_register_protocol2 函数实 现。 而协议的注册就是将具体的协议对象添加至 first_protocol 链表的末尾。
URLProtocol 在各个具体的文件协议中有一个具体的实例,如在 file 协议中定义为:
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,};
2、URLContext 结构
URLContext 提供了与当前打开的具体的文件协议(URL)相关数据的描述, 在该结 构中定义了指定当前 URL(即 filename 项)所要用到的具体的 URLProtocol, 即:提供 了一个在 URLprotocol 链表中找到具体项的依据, 此外还有一些其它的标志性的信息, 如 flags, is_streamed 等。 它可以看成某一种协议的载体。 其结构定义如下:
typedef struct URLContext {const AVClass *av_class; ///< information for av_log(). Set by url_open(). struct URLProtocol *prot; int flags; int is_streamed; /**< true if streamed (no seek possible), default = false *int max_packet_size; void *priv_data; char *filename; /**< specified URL */ int is_connected; } URLContext;
那么 ffmpeg 依据什么信息初始化 URLContext?然后又是如何初始化 URLContext 的呢?
在打开一个 URL 时, 全局函数 ffurl_open 会根据 filename 的前缀信息来确定 URL 所使用的具体协议, 并为该协议分配好资源, 再调用 ffurl_connect 函数打开具体协议, 即调用协议的 url_open, 调用关系如下:
int av_open_input_file(AVFormatContext **ic_ptr,const char *filename,AVInputFormat *fmt,int buf_size,AVFormatParameters *ap)int avformat_open_input(AVFormatContext **ps ,const char *filename ,AVInputFormat *fmt,AVDictionary **options)static int init_input(AVFormatContext *s, const char *filename)
浅蓝色部分的函数完成了 URLContext 函数的初始化,URLContext 使 ffmpeg 外所暴 露的接口是统一的,而不是对于不同的协议用不同的函数,这也是面向对象思维的体现。 在此结构中还有一个值得说的是 priv_data 项, 这是结构的一个可扩展项, 具体协议可 以根据需要添加相应的结构, 将指针保存在这就行。
3、AVIOContext 结构
AVIOContext(即:ByteIOContext)是由 URLProtocol 和 URLContext 结构扩展而 来,也是 ffmpeg 提供给用户的接口,它将以上两种不带缓冲的读取文件抽象为带缓冲的 读取和写入, 为用户提供带缓冲的读取和写入操作。 数据结构定义如下:
typedef struct { unsigned char *buffer; /**< Start of the buffer. */ int buffer_size; /**< Maximum buffer size */ unsigned char *buf_ptr; /**< Current position in the buffer */ unsigned char *buf_end; void *opaque; /关联 URLContext int (*read_packet)(void *opaque, uint8_t *buf, int buf_size); int (*write_packet)(void *opaque, uint8_t *buf, int buf_size); int64_t (*seek)(void *opaque, int64_t offset, int whence); int64_t pos; int must_flush; int eof_reached; /**< true if eof reached */ int write_flag; /**< true if open for writing */ int max_packet_size; unsigned long checksum; unsigned char *checksum_ptr; unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size); int error; int (*read_pause)(void *opaque, int pause) int64_t (*read_seek)(void *opaque, int stream_index,int64_t timestamp, int flags); int seekable; } AVIOContext;
结 构 简 单 的 为 用 户 提 供 读 写 容 易 实 现 的 四 个 操 作 , read_packet write_packet read_pause read_seek, 极大的方便了文件的读取, 四个函数在加了缓冲机制后被中转 到, URLContext 指向的实际的文件协议读写函数中。
下面给出 0.8 版本中是如何将 AVIOContext 的读写操作中转到实际文件中的。
在 avio_open()函数中调用了 ffio_fdopen()函数完成了对 AVIOContex 的初始 化, 其调用过程如下:
蓝色部分的函数调用完成了对 AVIOContext 的初始化, 在初始化的过程中, 将 AVIOContext 的 read_packet 、 write_packet 、 seek 分 别 初 始 化 为 : ffurl_read ffurl_write ffurl_seek , 而 这 三 个 函 数 又 将 具 体 的 读 写 操 作 中 转 为 : h->prot->url_read、h->prot->url_write、h->prot->url_seek, 另外两个变量初始化 时也被相应的中转, 如下:
(*s)->read_pause = (int (*)(void *, int))h->prot->url_read_pause;(*s)->read_seek = (int64_t (*)(void *, int, int64_t, int))h->prot->url_read_seek;
所以, 可以简要的描述为:AVIOContext 的接口口是加了缓冲后的 URLProtocol 的 函数接口。
在 aviobuf.c 中定义了一系列关于 ByteIOContext 这个结构体的函数, 如下
put_xxx 系列:
void put_byte(ByteIOContext *s, int b); void put_buffer(ByteIOContext *s, const unsigned char *buf, int size);void put_le64(ByteIOContext *s, uint64_t val); void put_be64(ByteIOContext *s, uint64_t val); void put_le32(ByteIOContext *s, unsigned int val); void put_be32(ByteIOContext *s, unsigned int val); void put_le24(ByteIOContext *s, unsigned int val); void put_be24(ByteIOContext *s, unsigned int val);void put_le16(ByteIOContext *s, unsigned int val); void put_be16(ByteIOContext *s, unsigned int val); void put_tag(ByteIOContext *s, const char *tag);
*get_xxx 系列:
int get_buffer(ByteIOContext *s, unsigned char *buf, int size);int get_partial_buffer(ByteIOContext *s, unsigned char *buf, int size); int get_byte(ByteIOContext *s);unsigned int get_le24(ByteIOContext *s);unsigned int get_le32(ByteIOContext *s); uint64_t get_le64(ByteIOContext *s);unsigned int get_le16(ByteIOContext *s);char *get_strz(ByteIOContext *s, char *buf, int maxlen); unsigned int get_be16(ByteIOContext *s); unsigned int get_be24(ByteIOContext *s); unsigned int get_be32(ByteIOContext *s);uint64_t get_be64(ByteIOContext *s);
这些 put_xxx 及 get_xxx 函数是用于从缓冲区 buffer 中写入或者读取若干个字节, 对于读写整型数据,分别实现了大端和小端字节序的版本。而缓冲区 buffer 中的数据又 是 从 何 而 来 呢 , 有 一 个 fill_buffer 的 函 数 , 在 fill_buffer 函 数 中 调 用 了 ByteIOContext 结构的 read_packet 接口。 在调用 put_xxx 函数时, 并没有直接进行真 正写入操作,而是先缓存起来,直到缓存达到最大限制或调用 flush_buffer 函数对缓冲 区进行刷新, 才使用 write_packet 函数进行写入操作。
3.3 Demuxer 和 muxer 模块分析
3.3.1 概述
ffmpeg 的 demuxer 和 muxer 接口分别在 AVInputFormat 和 AVOutputFormat 两个结
构体中实现, 在 av_register_all()函数中将两个结构分别静态初始化为两个链表, 保 存在全局变量:first_iformat 和 first_oformat 两个变量中。在 FFmpeg 的文件转换或 者打开过程中, 首先要做的就是根据传入文件和传出文件的后缀名匹配合适的 demuxer 和 muxer, 得到合适的信息后保存在 AVFormatContext 中。
3.3.2 相关数据结构介绍
1、AVInputFormat
该结构被称为 demuxer, 是音视频文件的一个解封装器, 它的定义如下:
typedef struct AVInputFormat { const char *name; const char *long_name; int priv_data_size; //具体文件容器格式对应的 Context 的大小, 如:avicontext int (*read_probe)(AVProbeData *); int (*read_header)(struct AVFormatContext *, AVFormatParameters *ap); int (*read_packet)(struct AVFormatContext *, AVPacket *pkt); int (*read_close)(struct AVFormatContext *); #if FF_API_READ_SEEK attribute_deprecated int (*read_seek)(struct AVFormatContext *, int stream_index, int64_t timestamp, int flags); #endif int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index, int64_t *pos, int64_t pos_limit); int flags; const char *extensions; int value; int (*read_play)(struct AVFormatContext *); int (*read_pause)(struct AVFormatContext *); const struct AVCodecTag * const *codec_tag; int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags); #if FF_API_OLD_METADATA2 const AVMetadataConv *metadata_conv; #endif const AVClass *priv_class; ///< AVClass for the private context struct AVInputFormat *next; } AVInputFormat;
对于不同的文件格式要实现相应的函数接口, 这样每一种格式都有一个对应的 demuxer, 所有的 demuxer 都保存在全局变量 first_iformat 中。 红色表示提供的接口。
2、AVOutputFormat
该结构与 AVInputFormat 类似也是在编译时静态初始化, 组织为一个链表结构, 提 供了多个 muxer 的函数接口。
int (*write_header)(struct AVFormatContext *); int (*write_packet)(struct AVFormatContext *, AVPacket *pkt); int (*write_trailer)(struct AVFormatContext *);
对于不同的文件格式要实现相应的函数接口, 这样每一种格式都有一个对应的 muxer, 所有的 muxer 都保存在全局变量 first_oformat 中。
3、AVFormatContext
该结构表示与程序当前运行的文件容器格式使用的上下文, 着重于所有文件容器共 有的属性,在运行时动态的确定其值,是 AVInputFormat 和 AVOutputFormat 的载体,但 同一个结构对象只能使 AVInputFormat 和 AVOutputFormat 中的某一个有效。每一个输入 和输出文件, 都在
static AVFormatContext *output_files[MAX_FILES]
和
static AVFormatContext *input_files[MAX_FILES];
定义的指针数组全局变量中有对应的实体。 对于输入和输出, 因为共用的是同一个结构 体, 所以需要分别对该结构中如下定义的 iformat 或 oformat 成员赋值。 在转码时读写 数据是通过 AVFormatContext 结构进行的。 定义如下:
typedef struct AVFormatContext { const AVClass *av_class; struct AVInputFormat *iformat; //指向具体的 demuxer struct AVOutputFormat *oformat; //指向具体的 muxer void *priv_data; //具体文件容器格式的 Context 如:avicontext AVIOContext *pb; //广义的输入输出; unsigned int nb_streams; //本次打开的文件容器中流的数量 AVStream **streams; //每个流的相关描述 char filename[1024]; // input or output filename */ int64_t timestamp; int ctx_flags; struct AVPacketList *packet_buffer; …… enum CodecID video_codec_id; enum CodecID audio_codec_id; enum CodecID subtitle_codec_id; unsigned int max_index_size; unsigned int max_picture_buffer; …… struct AVPacketList *raw_packet_buffer; struct AVPacketList *raw_packet_buffer_end; struct AVPacketList *packet_buffer_end; …… } AVFormatContext;
红色部分的成员是 AVFormatContext 中最为重要的成员变量, 这些变量的初始化是 ffmpeg 能正常工作的必要条件, 那么, AVFormatContext 是如何被初始化的呢?文件的 格式是如何被探测到的呢?
首先我们来探讨:
struct AVInputFormat *iformat; //指向具体的 demuxer struct AVOutputFormat *oformat; //指向具体的 muxer void *priv_data; //具体文件容器格式的 Context 如:avicontext
三个成员的初始化。
在 avformat_open_input() 函 数 中 调 用 了 init_input() 函 数 , 然 后 用 调 用 了 av_probe_input_format()函数实现了对 AVFormatContext 的初始化。其调用关系如下:
int av_open_input_file(AVFormatContext **ic_ptr, const char *filename, AVInputFormat *fmt, int buf_size, AVFormatParameters *ap); int avformat_open_input(ic_ptr, filename, fmt, &opts); static int init_input(s, filename); av_probe_input_format(&pd, 0);
av_probe_input_format (AVProbeData *pd, int is_opened, int *score_max) 函数用途是根据传入的 probe data 数据, 依次调用每个 demuxer 的 read_probe 接口, 来进行该 demuxer 是否和传入的文件内容匹配的判断。 与 demuxer 的匹配不同, muxer 的匹配是调用 guess_format 函数, 根据 main( ) 函数的 argv 里的输出文件后缀名来进 行的。 至此完成了前三个重要成员的初始化, 具体的做法就不在深入分析。
下面分别给出 av_read_frame 函数以及 av_write_frame 函数的基本流程。
int av_read_frame(AVFormatContext *s, AVPacket *pkt); ->av_read_frame_internel ->av_read_packet ->iformat->read_packet(在实现中会丢弃多余信息) ->av_get_packet ->get_xxxint av_write_frame(AVFormatContext *s, AVPacket *pkt); ->oformat->write_packet ->put_xxx
由上可见, 对 AVFormatContext 的读写操作最终是通过 ByteIOContext 来实现的, 这样, AVFormatContext 与 URLContext 就由 ByteIOContext 结构联系到一起了。 在 AVFormat 结构体中有一个 packet 的缓冲区 raw_packet_buffer, 是 AVPackList 的指针 类型, av_read_packet 函数将读到的包添加至 raw_packet_buffer 链表末尾。
3.4 Decoder/Encoder 模块
3.4.1 概述
编解码模块主要包含的数据结构为:AVCodec、AVCodecContext 每一个解码类型都 会有自己的 Codec 静态对像, Codec 的 int priv_data_size 记录该解码器上下文的结构 大 小 , 如 MsrleContext 。 这 些 都 是 编 译 时 确 定 的 , 程 序 运 行 时 通 过 avcodec_register_all()将所有的解码器注册成一个链表。在 av_open_input_stream() 函数中调用 AVInputFormat 的 read_header()中读文件头信息时, 会读出数据流的 CodecID, 即确定了他的解码器 Codec。
在 main()函数中除了解析传入参数并初始化 demuxer 与 muxer 的 parse_options( ) 函数以外, 其他的功能都是在 av_encode( )函数里完成的。 在 libavcodec\utils.c 中 有 如 下 二 个 函 数 :AVCodec *avcodec_find_encoder(enum CodecID id) 和 AVCodec *avcodec_find_decoder(enum CodecID id)他们的功能就是根据传入的 CodecID, 找到 匹配的 encoder 和 decoder。在 av_encode( )函数的开头,首先初始化各个 AVInputStream 和 AVOutputStream,然后分别调用上述二个函数,并将匹配上的 encoder 与 decoder 分 别保存在:
AVInputStream->AVStream *st->AVCodecContext *codec->struct AVCodec *codec
与
AVOutputStream->AVStream *st->AVCodecContext *codec->struct AVCodec *codec
变量。
3.4.2 相关数据结构的初始化
AVCodecContext 结构
AVCodecContext 保存 AVCodec 指针和与 codec 相关数据,如 video 的 width、height, audio 的 sample rate 等。
AVCodecContext 中的 codec_type, codec_id 二个变量对于 encoder/decoder 的匹 配来说, 最为重要。
enum CodecType codec_type; /* see CODEC_TYPE_xxx */enum CodecID codec_id; /* see CODEC_ID_xxx */
如上所示, codec_type 保存的是 CODEC_TYPE_VIDEO, CODEC_TYPE_AUDIO 等媒体类 型, codec_id 保存的是 CODEC_ID_FLV1, CODEC_ID_VP6F 等编码方式。
以支持 flv 格式为例, 在前述的 av_open_input_file(…… ) 函数中, 匹配到正确 的 AVInputFormat demuxer 后,通过 av_open_input_stream( )函数中调用 AVInputFormat 的 read_header 接口来执行 flvdec.c 中的 flv_read_header( )函数。flv_read_header( ) 函数内, 根据文件头中的数据, 创建相应的视频或音频 AVStream, 并设置 AVStream 中 AVCodecContext 的正确的 codec_type 值。codec_id 值是在解码过程。flv_read_packet( ) 函数执行时根据每一个 packet 头中的数据来设置的。
以 avidec 为例 有如下初始化,我们主要知道的就是 code_id 和 code_type 该字段关 联具体的解码器, 和解码类型(音视频或 subtitle)
if (st->codec->stream_codec_tag == AV_RL32(" Axan" )) { st->codec->codec_id = CODEC_ID_XAN_DPCM; st->codec->codec_tag = 0; } if (amv_file_format) { st->codec->codec_id = CODEC_ID_ADPCM_IMA_AMV; ast->dshow_block_align = 0; } break; case AVMEDIA_TYPE_SUBTITLE: st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; st->request_probe= 1; break; default: st->codec->codec_type = AVMEDIA_TYPE_DATA; st->codec->codec_id= CODEC_ID_NONE; st->codec->codec_tag= 0; avio_skip(pb, size);
3.5 其他重要数据结构的初始化
3.5.1 AVStream
AVStream 结构保存与数据流相关的编解码器,数据段等信息。比较重要的有如下二个成员:
AVCodecContext *codec; /**< codec context */ void *priv_data;
其中 codec 指针保存的就是上节所述的 encoder 或 decoder 结构。 priv_data 指针 保存的是和具体编解码流相关的数据,如下代码所示,在 ASF 的解码过程中,priv_data 保存的就是 ASFStream 结构的数据。
AVStream *st;ASFStream *asf_st; … … st->priv_data = asf_st;
3.5.2 AVInputStream/ AVOutputStream
根据输入和输出流的不同, 前述的 AVStream 结构都是封装在 AVInputStream 和 AVOutputStream 结构中, 在 av_encode( )函数中使用。 AVInputStream 中还保存的有与 时间有关的信息。 AVOutputStream 中还保存有与音视频同步等相关的信息。
3.5.3 AVPacket
AVPacket 结构定义如下, 其是用于保存读取的 packet 数据。
typedef struct AVPacket { int64_t pts; ///< presentation time stamp in time_base units int64_t dts; ///< decompression time stamp in time_base units uint8_t *data; int size; int stream_index; int flags; int duration; ///< presentation duration in time_base units void (*destruct)(struct AVPacket *); void *priv; int64_t pos; ///< byte position in stream, -1 if unknown } AVPacket;
在 av_encode()函数中, 调用 AVInputFormat 的
(*read_packet)(struct AVFormatContext *, AVPacket *pkt)
接口, 读取输入文 件的一帧数据保存在当前输入 AVFormatContext 的 AVPacket 成员中。
写在后面
如果对FFmpeg有需要更多了解的请订阅我的专题:音视频专辑