AVCodec 结构体深度解析:编解码器的身份证
做 FFmpeg 开发,你天天跟 AVCodec 打交道。说白了,它就是编解码器的「身份证」。每个解码器、编码器在 FFmpeg 里都对应一个 AVCodec 实例。我刚开始看源码时,总觉得这结构体字段太多,后来踩过几次坑才明白——每个字段都有它的用处。
这一节,咱们就把 AVCodec 从头到脚拆一遍。重点看 name、long_name、type、id、capabilities 这些核心字段,顺便把编解码器 ID 枚举也捋清楚。
AVCodec 结构体概览
先看一眼它的定义(我简化了,去掉了一些不常用的字段):
typedef struct AVCodec {
const char *name; // 编解码器短名称
const char *long_name; // 编解码器长名称
enum AVMediaType type; // 媒体类型:音频/视频/字幕
enum AVCodecID id; // 编解码器 ID
int capabilities; // 能力标志位
const AVRational *supported_framerates; // 支持的帧率
const enum AVPixelFormat *pix_fmts; // 支持的像素格式
const int *supported_samplerates; // 支持的采样率
const enum AVSampleFormat *sample_fmts; // 支持的采样格式
const uint64_t *channel_layouts; // 支持的声道布局
int priv_data_size; // 私有数据大小
// ... 还有一堆函数指针
} AVCodec;
嗯,这里要注意:name 和 long_name 虽然都是名字,但用途完全不同。我见过不少新手把 long_name 当 key 去查解码器,结果死活查不到。
name 与 long_name:短名是钥匙,长名是标签
| 字段 | 含义 | 示例 | 用途 |
|---|---|---|---|
name |
短名称,全小写,无空格 | "h264" |
avcodec_find_decoder_by_name() 的查找 key |
long_name |
长名称,可读性强 | "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10" |
ffmpeg -codecs 命令显示用 |
name 是内部查找用的。avcodec_find_decoder_by_name("h264") 能直接定位到 H.264 解码器。而 long_name 纯粹给人看的,ffmpeg -codecs 命令输出里那一长串描述就是它。
type 与 id:编解码器的「性别」和「身份证号」
type 是 AVMediaType 枚举,告诉你是音频、视频还是字幕。常见的就三个:
AVMEDIA_TYPE_VIDEO— 视频AVMEDIA_TYPE_AUDIO— 音频AVMEDIA_TYPE_SUBTITLE— 字幕
id 是 AVCodecID 枚举,这才是真正的「身份证号」。每个编解码标准都有一个唯一的 ID。比如:
enum AVCodecID {
AV_CODEC_ID_NONE,
AV_CODEC_ID_MPEG1VIDEO,
AV_CODEC_ID_MPEG2VIDEO,
AV_CODEC_ID_H264, // = 27
AV_CODEC_ID_HEVC, // = 173
AV_CODEC_ID_AAC, // = 86018
AV_CODEC_ID_MP3, // = 86017
// ... 几百个
};
为什么要有 id?因为 name 可能重复或歧义。比如 "h264" 和 "libx264" 都是 H.264,但一个是解码器,一个是编码器。id 是唯一的,avcodec_find_decoder(AV_CODEC_ID_H264) 保证找到正确的解码器。
capabilities:能力标志位
capabilities 是一个 int 类型的位掩码。每个 bit 代表一种能力。常见的标志有:
| 宏定义 | 值 | 含义 |
|---|---|---|
AV_CODEC_CAP_DRAW_HORIZ_BAND |
0x0001 | 支持逐行解码输出 |
AV_CODEC_CAP_DR1 |
0x0002 | 支持用户自定义帧缓冲区 |
AV_CODEC_CAP_DELAY |
0x0020 | 解码器有内部延迟(B帧场景常见) |
AV_CODEC_CAP_SMALL_LAST_FRAME |
0x0040 | 最后一帧可以小于正常尺寸 |
AV_CODEC_CAP_HWACCEL |
0x0800 | 支持硬件加速 |
AV_CODEC_CAP_VARIABLE_FRAME_SIZE |
0x4000 | 输出帧大小可变(音频常见) |
判断能力的方法很简单:
if (codec->capabilities & AV_CODEC_CAP_DELAY) {
// 这个解码器有延迟,需要 flush 才能拿到所有帧
avcodec_flush_buffers(ctx);
}
我个人习惯在初始化解码器后,先检查 capabilities。特别是 AV_CODEC_CAP_DELAY,如果你没处理这个标志,B 帧场景下会丢帧。
编解码器 ID 枚举的体系
AVCodecID 枚举定义在 libavcodec/codec_id.h 里。它按媒体类型分段:
- 视频 ID:1 ~ 0xFFFF(约 200 个)
- 音频 ID:0x10000 ~ 0x1FFFF(约 100 个)
- 字幕 ID:0x20000 ~ 0x2FFFF(约 20 个)
- 数据/其他:0x30000+
为什么这么分?方便快速判断类型。比如:
static inline int av_codec_is_video(enum AVCodecID id) {
return id > AV_CODEC_ID_NONE && id < 0x10000;
}
嗯,这里要注意:AV_CODEC_ID_NONE 的值是 0,表示「未指定」。如果你从 demuxer 拿到的 id 是 0,说明这个流不认识,别硬解码。
SVG:AVCodec 核心字段关系图
下面这张图展示了 AVCodec 核心字段之间的关联,以及它们如何定位一个具体的编解码器:
实际项目中的使用模式
说了这么多理论,来点实际的。我在做播放器 SDK 时,初始化解码器的流程大概是这样的:
// 1. 从 AVStream 拿到 codecpar
AVCodecParameters *par = stream->codecpar;
// 2. 根据 id 找解码器
AVCodec *codec = avcodec_find_decoder(par->codec_id);
if (!codec) {
// 处理找不到解码器的情况
return AVERROR_DECODER_NOT_FOUND;
}
// 3. 检查能力
if (codec->capabilities & AV_CODEC_CAP_HWACCEL) {
// 尝试硬件加速
init_hwaccel(codec);
}
// 4. 创建上下文
AVCodecContext *ctx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(ctx, par);
// 5. 打开解码器
avcodec_open2(ctx, codec, NULL);
你看,整个流程里 id 和 capabilities 是核心。name 和 long_name 反而用得少。但调试时,long_name 很有用:
av_log(NULL, AV_LOG_INFO, "Using decoder: %s (%s)\n",
codec->name, codec->long_name);
name是内部查找 key,大小写敏感long_name给人看,别用它做逻辑判断type区分音频/视频/字幕id是唯一标识,推荐用它做匹配capabilities是位掩码,用 & 操作符检查
嗯,这一节的内容差不多了。AVCodec 结构体是 FFmpeg 编解码的基石,搞懂它,后面看 avcodec_open2 的源码会轻松很多。你想想看,一个结构体里十几个字段,真正核心的就这几个——name、id、type、capabilities。其他的比如 supported_framerates、pix_fmts 这些,等讲到编解码器参数协商时再细聊。