背景
整理 ffmpeg 中,如何通过名字或者 id 找到对应编码器的。
具体流程
搜索函数
c
avcodec_find_encoder // 通过 ID 搜索编码器
avcodec_find_encoder_by_name // 通过名字搜索编码器
源码分析
ffmpeg 中所有支持的编码器都会注册到 codec_list.c 文件中,保存在 codec_list 结构体中,既有编码器也有解码器,且该结构体最后一个是 NULL,这样方便 ffmpeg 内部的迭代算法使用。
c
static const FFCodec *codec_list[] = {
&ff_a64multi_encoder,
&ff_a64multi5_encoder,
&ff_alias_pix_encoder,
&ff_amv_encoder,
...
&ff_av1_decoder,
NULL
};
搜索编码器用到的函数主要有这些,主要推测是一次遍历 codec_list 结构体,拿到结构体首先通过 av_codec_is_encoder 函数判断是不是编码器;然后在判断 id 和传入相同。 (avcodec_find_encoder_by_name 类似,只是最后一步是判断 name是否相等)
av_codec_iterate 写的方式很像 c++ 中的迭代器,index 不断加1,然后通过 codec_list 结构体最后的 NULL 作为结尾的判断。
cpp
// allcodecs.c 中
const AVCodec *avcodec_find_encoder(enum AVCodecID id)
{
return find_codec(id, av_codec_is_encoder);
}
static const AVCodec *find_codec(enum AVCodecID id, int (*x)(const AVCodec *))
{
const AVCodec *p, *experimental = NULL;
void *i = 0;
id = remap_deprecated_codec_id(id); //兼容代码,可先不管
while ((p = av_codec_iterate(&i))) {
if (!x(p))
continue;
if (p->id == id) {
//兼容代码,可先不管
if (p->capabilities & AV_CODEC_CAP_EXPERIMENTAL && !experimental) {
experimental = p;
} else
return p;
}
}
return experimental;
}
const AVCodec *av_codec_iterate(void **opaque)
{
uintptr_t i = (uintptr_t)*opaque;
const FFCodec *c = codec_list[i];
//av_codec_init_static 只运行一次,兼容代码,可先不管
ff_thread_once(&av_codec_static_init, av_codec_init_static);
if (c) {
*opaque = (void*)(i + 1);
return &c->p;
}
return NULL;
}
// 判断这个 avcodec 是不是编码器
int av_codec_is_encoder(const AVCodec *avcodec)
{
const FFCodec *const codec = ffcodec(avcodec);
return codec && (codec->cb_type == FF_CODEC_CB_TYPE_ENCODE ||
codec->cb_type == FF_CODEC_CB_TYPE_ENCODE_SUB ||
codec->cb_type == FF_CODEC_CB_TYPE_RECEIVE_PACKET);
}
具体例子
该结构体在 aacenc.c 文件中
主要是 FF_CODEC_ENCODE_CB,表示这个 codec 是编码器。
其他:.p.xx 这些是设置 AVCodec 结构体
c
const FFCodec ff_aac_encoder = {
.p.name = "aac",
CODEC_LONG_NAME("AAC (Advanced Audio Coding)"),
.p.type = AVMEDIA_TYPE_AUDIO,
.p.id = AV_CODEC_ID_AAC,
.p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |
AV_CODEC_CAP_SMALL_LAST_FRAME,
.priv_data_size = sizeof(AACEncContext),
.init = aac_encode_init,
FF_CODEC_ENCODE_CB(aac_encode_frame),
.close = aac_encode_end,
.defaults = aac_encode_defaults,
.p.supported_samplerates = ff_mpeg4audio_sample_rates,
.caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
.p.sample_fmts = (const enum AVSampleFormat[]){ AV_SAMPLE_FMT_FLTP,
AV_SAMPLE_FMT_NONE },
.p.priv_class = &aacenc_class,
};
#define CODEC_LONG_NAME(str) .p.long_name = str
#define FF_CODEC_ENCODE_CB(func) \
.cb_type = FF_CODEC_CB_TYPE_ENCODE, \
.cb.encode = (func)
细节推敲
为啥 AVCodec 可以强转为 FFCodec?
c
int av_codec_is_encoder(const AVCodec *avcodec)
{
**const FFCodec *const codec = ffcodec(avcodec);**
return codec && (codec->cb_type == FF_CODEC_CB_TYPE_ENCODE ||
codec->cb_type == FF_CODEC_CB_TYPE_ENCODE_SUB ||
codec->cb_type == FF_CODEC_CB_TYPE_RECEIVE_PACKET);
}
看了一下 FFCodec 中的结构定义,AVCodec p 是定义在FFCodec 最前面的,所以如果当前使用的 AVCodec 是用FFCodec 创建的,直接强转就能找到对应的 FFCodec 对象。如果 AVCodec 是独立创建的,强转肯定是有问题的。感觉这块写的有点 hardcode,不按 ffmpeg 约定俗成的一些规则写会有比较难查的bug。
c
typedef struct FFCodec {
/**
* The public AVCodec. See codec.h for it.
*/
AVCodec p;
/**
* Internal codec capabilities FF_CODEC_CAP_*.
*/
unsigned caps_internal:29;
/**
* This field determines the type of the codec (decoder/encoder)
* and also the exact callback cb implemented by the codec.
* cb_type uses enum FFCodecType values.
*/
unsigned cb_type:3;
// ...
/**
* List of supported codec_tags, terminated by FF_CODEC_TAGS_END.
*/
const uint32_t *codec_tags;
} FFCodec;