[ffmpeg] find 编码器

背景

整理 ffmpeg 中,如何通过名字或者 id 找到对应编码器的。

具体流程

搜索函数

avcodec_find_encoder  // 通过 ID 搜索编码器
avcodec_find_encoder_by_name // 通过名字搜索编码器

源码分析

ffmpeg 中所有支持的编码器都会注册到 codec_list.c 文件中,保存在 codec_list 结构体中,既有编码器也有解码器,且该结构体最后一个是 NULL,这样方便 ffmpeg 内部的迭代算法使用。

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 和传入相同。 (发现很多编码器的 id 是一样的(比如 n卡、a卡和 x264的ID都是 AV_CODEC_ID_H264),通过 id 去找可能是返回 codec_list最前面的编码器)(avcodec_find_encoder_by_name 类似,只是最后一步是判断 name是否相等)
av_codec_iterate 写的方式很像 c++ 中的迭代器,index 不断加1,然后通过 codec_list 结构体最后的 NULL 作为结尾的判断。

// 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 结构体

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?

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。

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;
  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C FFmpeg是一个开源的多媒体框架,它提供了一系列的库和工具,可以用于处理音频和视频数据。要实现一个编码器,你可以使用C FFmpeg提供的API来进行音视频编码。 首先,你需要初始化FFmpeg库,并注册所需的编码器。可以使用av_register_all()函数来完成这一步骤。 接下来,你需要创建一个AVFormatContext结构体,用于存储音视频流的相关信息。可以使用avformat_alloc_output_context2()函数来创建AVFormatContext对象。 然后,你需要打开输出文件,并设置相关的编码参数。可以使用avio_open()函数打开输出文件,使用avformat_new_stream()函数创建音视频流,并设置相应的编码参数。 接下来,你需要初始化编码器,并打开编码器。可以使用avcodec_find_encoder()函数找到所需的编码器,使用avcodec_alloc_context3()函数创建AVCodecContext对象,并设置相关的编码参数。然后,使用avcodec_open2()函数打开编码器。 接下来,你需要循环读取输入数据,并进行编码。可以使用av_read_frame()函数读取输入数据,使用avcodec_send_packet()函数将数据发送给编码器进行编码,使用avcodec_receive_packet()函数接收编码后的数据。 最后,你需要将编码后的数据写入输出文件。可以使用av_interleaved_write_frame()函数将编码后的数据写入输出文件。 这是一个简单的C FFmpeg实现编码器的步骤。当然,具体的实现还需要根据你的需求和具体的编码器进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值