【FFmpeg】编码链路上主要函数的简单分析

示例工程:
【FFmpeg】调用ffmpeg库实现264软编
【FFmpeg】调用ffmpeg库实现264软解
【FFmpeg】调用ffmpeg库进行RTMP推流和拉流
【FFmpeg】调用ffmpeg库进行SDL2解码后渲染

1. 主要工作流程

本文分析的FFmpeg版本是FFmpeg-7.0,只考虑视频编码部分的流程,不考虑音频部分
FFmpeg进行编码器的调用时,主要工作流程大约是

编码器:

  1. 查找编码器(avcodec_find_encoder)
  2. 创建编码器上下文(avcodec_alloc_context3)
  3. 创建输入信息的结构体(av_frame_alloc)
  4. 创建输出信息的结构体(av_packet_alloc)
  5. 打开编码器(avcodec_open2)
  6. 将输入帧填充到frame当中(…)
  7. 将输入帧送入编码器进行编码(avcodec_send_frame)
  8. 获取编码器输出的已编码信息(avcodec_receive_packet)
  9. 释放结构体信息(…)

2. 编码过程

2.1 查找编码器(avcodec_find_encoder)

该函数根据AVCodecID来查找编码器,返回一个AVCodec*。函数定义位于libavcodec/allcodecs.c,整体执行流程为:

  1. 进入查找编码器函数(avcodec_find_encoder)
  2. 进入内部查找函数(find_codec)
  3. 循环从codec_list当中读取codec(av_codec_iterate)
  4. 判断读取的codec是否与输入id匹配,同时检查这个编码器是否有编码能力,如果有能力则返回这个编码器的地址
  5. 如果没有查找到codec则返回NULL
const AVCodec *avcodec_find_encoder(enum AVCodecID id)
{
    return find_codec(id, av_codec_is_encoder);
}

find_codec的实现方式如下,调用了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); 	// 暂时没有实际操作,对id没有影响

    while ((p = av_codec_iterate(&i))) {	// 从codec_list当中循环读取一个codec
        if (!x(p))							// x就是avcodec_find_encoder函数,去寻找这个codec是否是编码器
            continue;
        if (p->id == id) {	// 如果找到了这个codec的id,则检查其能否正常使用
            if (p->capabilities & AV_CODEC_CAP_EXPERIMENTAL && !experimental) {
                experimental = p; // 能够正常使用,将这个codec进行输出
            } else
                return p;
        }
    }

    return experimental;
}

对于avcodec_find_decoder,工作的流程一致,只是查找的是解码器,调用的函数略有区别

2.2 创建编码器上下文(avcodec_alloc_context3)

该函数用于分配编解码器上下文信息这一结构体的内存,返回一个AVCodecContext*。函数定义位于libavcodec/options.c当中,整体的流程是:

  1. 调用av_malloc分配AVCodecContext的内存
  2. 调用init_context_defaults进行变量的初始化,使用的是默认值

初始化主要包括

名称 功能
av_class 记录了av_log的信息
codec_type 编解码器的类型(视频、音频或其他)
opt 记录解码的flag参数(根据codec_type确定)
ch_layout 音频的channel layout
time_base 时间基
framerate 帧率
pkt_timebase pkt的时间基
get_buffer2 获取默认AVFrame的buffer
get_format 获取默认的format(结合硬件的fomrat确定)
get_encode_buffer 获取编码的buffer,这里buffer的内容是AVPacket,即编码输出的buffer
execute 执行函数,输入的参数包含函数指针
execute2 执行函数,输入的参数包含函数指针
sample_aspect_ratio 样本纵横比(宽度除以高度)
ch_layout.order 在layout中使用的布局顺序
pix_fmt 像素格式
sw_pix_fmt 标称非加速像素格式
sample_fmt 音频样本格式
priv_data 私有数据,常用于强制转换,是重要的中间数据信息

实现的代码如下

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec)
{
    AVCodecContext *avctx= av_malloc(sizeof(AVCodecContext)); // 分配AVCodecContext的存储空间

    if (!avctx)
        return NULL;

    if (init_context_defaults(avctx, codec) < 0) {	// 进行初始值初始化
        av_free(avctx);
        return NULL;
    }

    return avctx;
}

使用默认值来初始化变量

static int init_context_defaults(AVCodecContext *s, const AVCodec *codec)
{
    const FFCodec *const codec2 = ffcodec(codec);
    int flags=0;
    memset(s, 0, sizeof(AVCodecContext));

    s->av_class = &av_codec_context_class;	// 默认的AVClass

    s->codec_type = codec ? codec->type : AVMEDIA_TYPE_UNKNOWN;
    if (codec) {
        s->codec = codec;
        s->codec_id = codec->id;
    }
	// 初始化flags
	// 这3个参数声明,是供用户设置用于demuxing和decoding
    if(s->codec_type == AVMEDIA_TYPE_AUDIO)
        flags= AV_OPT_FLAG_AUDIO_PARAM;
    else if(s->codec_type == AVMEDIA_TYPE_VIDEO)
        flags= AV_OPT_FLAG_VIDEO_PARAM;
    else if(s->codec_type == AVMEDIA_TYPE_SUBTITLE)
        flags= AV_OPT_FLAG_SUBTITLE_PARAM;
    // 根据flag进行s当中opt的赋值,这里的s是AVCodecContext
    av_opt_set_defaults2(s, flags, flags);
	// AVChannelLayout当中包含的是音频数据的layout的信息(不太理解音频数据的处理方式)
    av_channel_layout_uninit(&s->ch_layout);

    s->time_base           = (AVRational){0,1};
    s->framerate           = (AVRational){ 0, 1 };
    s->pkt_timebase        = (AVRational){ 0, 1 };


    // ----- 以下只是配置函数,在这里并没有执行 -----
    // 获取默认的buffer,执行s->get_buffer2时会默认调用avcodec_default_get_buffer2,下同
    // 1.如果设置了硬件编解码器,则会调用av_hwframe_get_buffer去获取硬件方面的信息
    // 2.否则调用video_get_buffer获取buffer,这个buffer分配的空间是AVFrame,并且会记录到internal->pool这个Framepool当中,即输入帧池
    s->get_buffer2         = avcodec_default_get_buffer2;		
    
    // 获取默认的格式
    // 1.如果设置了硬件处理方式,则去解析硬件的pix_fmt信息
    // 2.将软件支持的format加入到desc的list当中
    s->get_format          = avcodec_default_get_format;		
    
    // 获取默认的encode的buffer
    // 1.会分配AVPacket的内存空间(对于encode而言,输入frame是确定的,需要分配的buffer是存储编码后码流的空间)
    s->get_encode_buffer   = avcodec_default_get_encode_buffer;	

	// 获取默认的execute和execute2
	// execute指代的是函数的执行,其输入的参数包括函数指针
    s->execute             = avcodec_default_execute;	
    s->execute2            = avcodec_default_execute2;
    // ----- 配置函数end,在这里并没有执行 -----


    s->sample_aspect_ratio = (AVRational){0,1};
    // AV_CHANNEL_ORDER_UNSPEC,只定义了channel count,没有额外的信息
    s->ch_layout.order     = AV_CHANNEL_ORDER_UNSPEC;
    s->pix_fmt             = AV_PIX_FMT_NONE;
    s->sw_pix_fmt          = AV_PIX_FMT_NONE;
    s->sample_fmt          = AV_SAMPLE_FMT_NONE;
	// priv_data常会进行格式转换
    if(codec && codec2->priv_data_size){
        s->priv_data = av_mallocz(codec2->priv_data_size);
        if (!s->priv_data)
            return AVERROR(ENOMEM);
        if(codec->priv_class){
            *(const AVClass**)s->priv_data = codec->priv_class;
            av_opt_set_defaults(s->priv_data);
        }
    }
    if (codec && codec2->defaults) {
        int ret;
        const FFCodecDefault *d = codec2->defaults;
        while (d->key) {
            ret = av_opt_set(s, d->key, d->value, 0);
            av_assert0(ret >= 0);
            d++;
        }
    }
    return 0;
}

2.3 创建输入信息的结构体(av_frame_alloc)

该函数用于分配AVFrame*的内存空间。在编解码流程中,AVFrame常用于编码器的输入和解码器的输出。函数的定义位于libavutil/frame.c,主要流程如下:

  1. 调用av_malloc分配AVFrame的内存
  2. 调用get_frame_defaults进行初始化

初始化主要内容包括

名称 功能
pts 显示时间戳
pkt_dts packet的解码时间戳
best_effort_timestamp 最佳时间戳,以流的时间为基准
duration 持续时间
pkt_pos packet的位置
pkt_size packet的大小
time_base 时间基
sample_aspect_ratio 样本纵横比
format 帧的格式
extended_data 拓展数据,用frame->data初始化
color_primaries 颜色的基准(如BT709等)
color_trc 颜色格式转换特征
colorspace YUV颜色空间类型(如RGB、BT709等)
color_range 颜色范围(如MPEG、JPEG等)
chroma_location 色度分量的位置
flags 帧flag

实现代码如下

AVFrame *av_frame_alloc(void)
{
    AVFrame *frame = av_malloc(sizeof(*frame));

    if (!frame)
        return NULL;

    get_frame_defaults(frame);

    return frame;
}

调用了get_frame_defaults

static void get_frame_defaults(AVFrame *frame)
{
    memset(frame, 0, sizeof(*frame));

    frame->pts                   =
    frame->pkt_dts               = AV_NOPTS_VALUE;
    frame->best_effort_timestamp = AV_NOPTS_VALUE;
    frame->duration            = 0;
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGS
    frame->pkt_pos             = -1;
    frame->pkt_size            = -1;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    frame->time_base           = (AVRational){ 0, 1 };
    frame->sample_aspect_ratio = (AVRational){ 0, 1 };
    frame->format              = -1; /* unknown */
    frame->extended_data       = frame->data;
    frame->color_primaries     = AVCOL_PRI_UNSPECIFIED;
    frame->color_trc           = AVCOL_TRC_UNSPECIFIED;
    frame->colorspace          = AVCOL_SPC_UNSPECIFIED;
    frame->color_range         = AVCOL_RANGE_UNSPECIFIED;
    frame->chroma_location     = AVCHROMA_LOC_UNSPECIFIED;
    frame->flags               = 0;
}

2.4 创建输出信息的结构体(av_packet_alloc)

该函数用于分配AVPacket的空间,在编解码流程中,av_pkt可以作为编码器的输出,也可以作为解码器的输。函数定义位于libavcodec/avpacket.c,主要的流程如下

  1. 调用av_malloc进行AVPacket*内存的分配
  2. 调用get_packet_defaults为packet进行默认值配置

配置的内容包括

名称 功能
pts 显示时间戳
dts 解码时间戳
pos 位置
time_base 时间基

实现代码如下

AVPacket *av_packet_alloc(void)
{
    AVPacket *pkt = av_malloc(sizeof(AVPacket));
    if (!pkt)
        return pkt;

    get_packet_defaults(pkt);

    return pkt;
}

调用get_packet_defaults获取默认配置

static void get_packet_defaults(AVPacket *pkt)
{
    memset(pkt, 0, sizeof(*pkt));

    pkt->pts             = AV_NOPTS_VALUE;
    pkt->dts             = AV_NOPTS_VALUE;
    pkt->pos             = -1;
    pkt->time_base       = av_make_q(0, 1);
}

2.5 打开编码器(avcodec_open2)

该函数的功能是根据输入的AVCodec来初始化AVCodecContext。可以理解为配置了AVCodecContext,此时可以开始进行编解码器的工作了,即打开了编解码器。定义位于libavcodec/avcodec.c中,主要的流程为:

  1. 对输入变量进行一些检查
  2. 将codec部分信息赋值给codec_ctx
  3. 如果是解码器,则调用ff_decode_internal_alloc进行内存分配;如果是编码器,则调用ff_encode_internal_alloc进行内存分配,分配之后将地址存储到codec_ctx中。ff_XXX_internal_alloc直接调用av_malloc函数进行内存的分配。
  4. 分配av_frame和av_pakcet的空间,将地址存储到codec_ctx中
  5. 分配priv_data的空间,将地址存储到codec_ctx中
  6. 检查codec是否在codec的白名单中,不在则返回错误
  7. 根据码流当中的宽高设置维度
  8. 检查图像的尺寸
  9. 检查纵横比,如果不符合规定,则配置为默认值
  10. 检查采样率、每个数据包字节数(针对于音频)
  11. 检查layout(音频不是很理解,猜测这里应该是声道数)
  12. 初始化frame数量和编解码器的描述器
  13. 检查codec的能力
  14. 对于编码器,调用ff_encode_preinit进行早期初始化,否则调用ff_decode_preinit进行早期初始化
  15. 检查是否是帧级别的多线程处理
  16. 检查解码器情况下的channel layout是否有效

实现代码如下

int attribute_align_arg avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options)
{
    int ret = 0;
    AVCodecInternal *avci;
    const FFCodec *codec2;
	// ----- 1.检查输入变量是否存在 ----- //
    if (avcodec_is_open(avctx))
        return 0;

    if (!codec && !avctx->codec) {
        av_log(avctx, AV_LOG_ERROR, "No codec provided to avcodec_open2()\n");
        return AVERROR(EINVAL);
    }
    if (codec && avctx->codec && codec != avctx->codec) {
        av_log(avctx, AV_LOG_ERROR, "This AVCodecContext was allocated for %s, "
                                    "but %s passed to avcodec_open2()\n", avctx->codec->name, codec->name);
        return AVERROR(EINVAL);
    }
    if (!codec)
        codec = avctx->codec;
    codec2 = ffcodec(codec);

    if ((avctx->codec_type != AVMEDIA_TYPE_UNKNOWN && avctx->codec_type != codec->type) ||
        (avctx->codec_id   != AV_CODEC_ID_NONE     && avctx->codec_id   != codec->id)) {
        av_log(avctx, AV_LOG_ERROR, "Codec type or id mismatches\n");
        return AVERROR(EINVAL);
    }
	// ----- 2.赋值codec的信息 ----- // 
    avctx->codec_type = codec->type;
    avctx->codec_id   = codec->id;
    avctx->codec      = codec;

    if (avctx->extradata_size < 0 || avctx->extradata_size >= FF_MAX_EXTRADATA_SIZE)
        return AVERROR(EINVAL);
	// ----- 3.根据编解码器的类型进行内存分配 ----- //
	// ff_xxx_internal_alloc直接调用了av_malloc进行内存分配
    avci = av_codec_is_decoder(codec) ?
        ff_decode_internal_alloc()    :
        ff_encode_internal_alloc();
    if (!avci) {
        ret = AVERROR(ENOMEM);
        goto end;
    }
    avctx->internal = avci;
	// ----- 4.分配Frame和Packet的空间 ----- //
    avci->buffer_frame = av_frame_alloc();
    avci->buffer_pkt = av_packet_alloc();
    if (!avci->buffer_frame || !avci->buffer_pkt) {
        ret = AVERROR(ENOMEM);
        goto free_and_end;
    }
	// ----- 5.分配priv_data的空间 ----- //
    if (codec2->priv_data_size > 0) {
        if (!avctx->priv_data) {
            avctx->priv_data = av_mallocz(codec2->priv_data_size);
            if (!avctx->priv_data) {
                ret = AVERROR(ENOMEM);
                goto free_and_end;
            }
            if (codec->priv_class) {
                *(const AVClass **)avctx->priv_data = codec->priv_class;
                av_opt_set_defaults(avctx->priv_data);
            }
        }
        if (codec->priv_class && (ret = av_opt_set_dict(avctx->priv_data, options)) < 0)
            goto free_and_end;
    } else {
        avctx->priv_data = NULL;
    }
    if ((ret = av_opt_set_dict(avctx, options)) < 0)
        goto free_and_end;
	// ----- 6.检查codec是否在白名单当中 ----- //
    if (avctx->codec_whitelist && av_match_list(codec->name, avctx->codec_whitelist, ',') <= 0) {
        av_log(avctx, AV_LOG_ERROR, "Codec (%s) not on whitelist \'%s\'\n", codec->name, avctx->codec_whitelist);
        ret = AVERROR(EINVAL);
        goto free_and_end;
    }
	// ----- 7.根据码流当中的宽高设置维度 ----- // 
    // only call ff_set_dimensions() for non H.264/VP6F/DXV codecs so as not to overwrite previously setup dimensions
    if (!(avctx->coded_width && avctx->coded_height && avctx->width && avctx->height &&
          (avctx->codec_id == AV_CODEC_ID_H264 || avctx->codec_id == AV_CODEC_ID_VP6F || avctx->codec_id == AV_CODEC_ID_DXV))) {
        if (avctx->coded_width && avctx->coded_height)
            ret = ff_set_dimensions(avctx, avctx->coded_width, avctx->coded_height);
        else if (avctx->width && avctx->height)
            ret = ff_set_dimensions(avctx, avctx->width, avctx->height);
        if (ret < 0)
            goto free_and_end;
    }
	// ----- 8.检查图像的尺寸 ----- //
    if ((avctx->coded_width || avctx->coded_height || avctx->width || avctx->height)
        && (  av_image_check_size2(avctx->coded_width, avctx->coded_height, avctx->max_pixels, AV_PIX_FMT_NONE, 0, avctx) < 0
           || av_image_check_size2(avctx->width,       avctx->height,       avctx->max_pixels, AV_PIX_FMT_NONE, 0, avctx) < 0)) {
        av_log(avctx, AV_LOG_WARNING, "Ignoring invalid width/height values\n");
        ff_set_dimensions(avctx, 0, 0);
    }
	// ----- 9.检查纵横比 ----- //
    if (avctx->width > 0 && avctx->height > 0) {
        if (av_image_check_sar(avctx->width, avctx->height,
                               avctx->sample_aspect_ratio) < 0) {
            av_log(avctx, AV_LOG_WARNING, "ignoring invalid SAR: %u/%u\n",
                   avctx->sample_aspect_ratio.num,
                   avctx->sample_aspect_ratio.den);
            avctx->sample_aspect_ratio = (AVRational){ 0, 1 };
        }
    }
	// ----- 10.检查采样率和block_align(针对于音频) ----- //
    if (avctx->sample_rate < 0) {
        av_log(avctx, AV_LOG_ERROR, "Invalid sample rate: %d\n", avctx->sample_rate);
        ret = AVERROR(EINVAL);
        goto free_and_end;
    }
    if (avctx->block_align < 0) {
        av_log(avctx, AV_LOG_ERROR, "Invalid block align: %d\n", avctx->block_align);
        ret = AVERROR(EINVAL);
        goto free_and_end;
    }
	// ----- 11.检查音频的layout ----- //
    /* AV_CODEC_CAP_CHANNEL_CONF is a decoder-only flag; so the code below
     * in particular checks that nb_channels is set for all audio encoders. */
    if (avctx->codec_type == AVMEDIA_TYPE_AUDIO && !avctx->ch_layout.nb_channels
        && !(codec->capabilities & AV_CODEC_CAP_CHANNEL_CONF)) {
        av_log(avctx, AV_LOG_ERROR, "%s requires channel layout to be set\n",
               av_codec_is_decoder(codec) ? "Decoder" : "Encoder");
        ret = AVERROR(EINVAL);
        goto free_and_end;
    }
    if (avctx->ch_layout.nb_channels && !av_channel_layout_check(&avctx->ch_layout)) {
        av_log(avctx, AV_LOG_ERROR, "Invalid channel layout\n");
        ret = AVERROR(EINVAL);
        goto free_and_end;
    }
    if (avctx->ch_layout.nb_channels > FF_SANE_NB_CHANNELS) {
        av_log(avctx, AV_LOG_ERROR, "Too many channels: %d\n", avctx->ch_layout.nb_channels);
        ret = AVERROR(EINVAL);
        goto free_and_end;
    }
	// ----- 12.初始化帧数量和编解码描述器 ----- //
    avctx->frame_num = 0;
    avctx->codec_descriptor = avcodec_descriptor_get(avctx->codec_id);
	// ----- 13.检查codec的能力 ----- //
    if ((avctx->codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) &&
        avctx->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) {
        const char *codec_string = av_codec_is_encoder(codec) ? "encoder" : "decoder";
        const AVCodec *codec2;
        av_log(avctx, AV_LOG_ERROR,
               "The %s '%s' is experimental but experimental codecs are not enabled, "
               "add '-strict %d' if you want to use it.\n",
               codec_string, codec->name, FF_COMPLIANCE_EXPERIMENTAL);
        codec2 = av_codec_is_encoder(codec) ? avcodec_find_encoder(codec->id) : avcodec_find_decoder(codec->id);
        if (!(codec2->capabilities & AV_CODEC_CAP_EXPERIMENTAL))
            av_log(avctx, AV_LOG_ERROR, "Alternatively use the non experimental %s '%s'.\n",
                codec_string, codec2->name);
        ret = AVERROR_EXPERIMENTAL;
        goto free_and_end;
    }

    if (avctx->codec_type == AVMEDIA_TYPE_AUDIO &&
        (!avctx->time_base.num || !avctx->time_base.den)) {
        avctx->time_base.num = 1;
        avctx->time_base.den = avctx->sample_rate;
    }
	// ----- 14.早期初始化 -----  //
    if (av_codec_is_encoder(avctx->codec))
        ret = ff_encode_preinit(avctx);
    else
        ret = ff_decode_preinit(avctx);
    if (ret < 0)
        goto free_and_end;
	// ----- 15.检查是否是帧级别的多线程处理 ----- //
    if (HAVE_THREADS && !avci->frame_thread_encoder) {
        /* Frame-threaded decoders call FFCodec.init for their child contexts. */
        lock_avcodec(codec2);
        ret = ff_thread_init(avctx);
        unlock_avcodec(codec2);
        if (ret < 0) {
            goto free_and_end;
        }
    }
    if (!HAVE_THREADS && !(codec2->caps_internal & FF_CODEC_CAP_AUTO_THREADS))
        avctx->thread_count = 1;

    if (!(avctx->active_thread_type & FF_THREAD_FRAME) ||
        avci->frame_thread_encoder) {
        if (codec2->init) {
            lock_avcodec(codec2);
            ret = codec2->init(avctx);
            unlock_avcodec(codec2);
            if (ret < 0) {
                avci->needs_close = codec2->caps_internal & FF_CODEC_CAP_INIT_CLEANUP;
                goto free_and_end;
            }
        }
        avci->needs_close = 1;
    }

    ret=0;
	// ----- 16.检查解码器情况下channel layout是否有效 ----- //
    if (av_codec_is_decoder(avctx->codec)) {
        if (!avctx->bit_rate)
            avctx->bit_rate = get_bit_rate(avctx);

        /* validate channel layout from the decoder */
        if ((avctx->ch_layout.nb_channels && !av_channel_layout_check(&avctx->ch_layout)) ||
            avctx->ch_layout.nb_channels > FF_SANE_NB_CHANNELS) {
            ret = AVERROR(EINVAL);
            goto free_and_end;
        }
        if (avctx->bits_per_coded_sample < 0) {
            ret = AVERROR(EINVAL);
            goto free_and_end;
        }
    }
    if (codec->priv_class)
        av_assert0(*(const AVClass **)avctx->priv_data == codec->priv_class);

end:

    return ret;
free_and_end:
    ff_codec_close(avctx);
    goto end;
}

2.6 将输入帧送入编码器进行编码(avcodec_send_frame)

除去一些必要的检查之外,编码过程的主要函数调用流程为:

  1. 送帧主函数(avcodec_send_frame)
  2. 分接收类型的编码上层函数(encode_receive_packet_internal)
  3. 分线程的编码上层函数(encode_simple_receive_packet)
  4. 单线程编码主函数(ff_encode_encode_cb)
  5. 核心编码函数(X264_frame)

2.6.1 送帧主函数(avcodec_send_frame)

对于avcodec_send_frame函数而言,主要功能是将原始帧送入到编码器当中进行编码。函数位于libavcodec/encode.c,实现的主要流程为:

  1. 检查codec是否打开,检查是否是编码器
  2. 检查输入的frame buffer是否为空
  3. 针对音频数据进行参数检查并对数据进行填充(encode_send_frame_internal)
  4. 在pkt没有数据的情况下,辅助获取编码的pkt内容(encode_receive_packet_internal)

实现的代码为

int attribute_align_arg avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;
	// ----- 1.检查codec是否打开,检查是否是编码器 ----- //
    if (!avcodec_is_open(avctx) || !av_codec_is_encoder(avctx->codec))
        return AVERROR(EINVAL);

    if (avci->draining)
        return AVERROR_EOF;
	// ----- 2.检查输入的frame是否为空 ----- //
    if (avci->buffer_frame->buf[0])
        return AVERROR(EAGAIN);

    if (!frame) {
        avci->draining = 1;
    } else {
    	// ----- 3. 如果是音频,进行参数检查和数据填充 ----- //
        ret = encode_send_frame_internal(avctx, frame);
        if (ret < 0)
            return ret;
    }
	// ----- 4.在pkt没有数据的情况下,辅助获取编码的pkt内容 ----- //
    if (!avci->buffer_pkt->data && !avci->buffer_pkt->side_data) {
        ret = encode_receive_packet_internal(avctx, avci->buffer_pkt);
        if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
            return ret;
    }

    avctx->frame_num++;

    return 0;
}

2.6.2 分接收类型的编码上层函数(encode_receive_packet_internal)

进行编码器操作的代码如下(encode_receive_packet_internal),主要流程为:

  1. 检查图像尺寸
  2. 调用receive_packet进行编码
static int encode_receive_packet_internal(AVCodecContext *avctx, AVPacket *avpkt)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;

    if (avci->draining_done)
        return AVERROR_EOF;

    av_assert0(!avpkt->data && !avpkt->side_data);

    if (avctx->codec->type == AVMEDIA_TYPE_VIDEO) {
        if ((avctx->flags & AV_CODEC_FLAG_PASS1) && avctx->stats_out)
            avctx->stats_out[0] = '\0';
        // ----- 1.检查图像尺寸 ----- //
        if (av_image_check_size2(avctx->width, avctx->height, avctx->max_pixels, AV_PIX_FMT_NONE, 0, avctx))
            return AVERROR(EINVAL);
    }
	// ----- 2.获取packet ----- //
    if (ffcodec(avctx->codec)->cb_type == FF_CODEC_CB_TYPE_RECEIVE_PACKET) {
        ret = ffcodec(avctx->codec)->cb.receive_packet(avctx, avpkt);
        if (ret < 0)
            av_packet_unref(avpkt);
        else
            // Encoders must always return ref-counted buffers.
            // Side-data only packets have no data and can be not ref-counted.
            av_assert0(!avpkt->data || avpkt->buf);
    } else
        ret = encode_simple_receive_packet(avctx, avpkt);
    if (ret >= 0)
        avpkt->flags |= encode_ctx(avci)->intra_only_flag;

    if (ret == AVERROR_EOF)
        avci->draining_done = 1;

    return ret;
}

2.6.3 分线程的编码上层函数(encode_simple_receive_packet —> encode_simple_internal)

以简单的编码操作函数encode_simple_receive_packet为例,主要调用了encode_simple_internal,其主要工作流程如下:

  1. 获取下一个编码帧(ff_encode_get_frame)
  2. 编码分两种情况,多线程编码(ff_thread_video_encode_frame)和正常编码(ff_encode_encode_cb)
static int encode_simple_internal(AVCodecContext *avctx, AVPacket *avpkt)
{
    AVCodecInternal   *avci = avctx->internal;
    AVFrame          *frame = avci->in_frame;
    const FFCodec *const codec = ffcodec(avctx->codec);
    int got_packet;
    int ret;

    if (avci->draining_done)
        return AVERROR_EOF;
	// ----- 1.由编码器调用以获取下一帧进行编码 ----- //
    if (!frame->buf[0] && !avci->draining) {
        av_frame_unref(frame);
        ret = ff_encode_get_frame(avctx, frame);
        if (ret < 0 && ret != AVERROR_EOF)
            return ret;
    }

    if (!frame->buf[0]) {
        if (!(avctx->codec->capabilities & AV_CODEC_CAP_DELAY ||
              avci->frame_thread_encoder))
            return AVERROR_EOF;

        // Flushing is signaled with a NULL frame
        frame = NULL;
    }

    got_packet = 0;

    av_assert0(codec->cb_type == FF_CODEC_CB_TYPE_ENCODE);
	// ----- 2.编码 ----- //
    if (CONFIG_FRAME_THREAD_ENCODER && avci->frame_thread_encoder)
    	// 多线程编码
        /* This will unref frame. */
        ret = ff_thread_video_encode_frame(avctx, avpkt, frame, &got_packet);
    else {
        ret = ff_encode_encode_cb(avctx, avpkt, frame, &got_packet);
    }

    if (avci->draining && !got_packet)
        avci->draining_done = 1;

    return ret;
}

2.6.4 单线程编码主函数(ff_encode_encode_cb)

其中,ff_encode_encode_cb最主要的工作函数是调用了encode函数进行编码

int ff_encode_encode_cb(AVCodecContext *avctx, AVPacket *avpkt,
                        AVFrame *frame, int *got_packet)
{
    const FFCodec *const codec = ffcodec(avctx->codec);
    int ret;
	// ----- 1.执行编码过程 ----- //
    ret = codec->cb.encode(avctx, avpkt, frame, got_packet);
    emms_c();
    av_assert0(ret <= 0);

    // ...
    return ret;
}

这里调用的encode函数是一个函数指针,如果使用的是x264,其调用的函数应该是X264_frame。

FFCodec ff_libx264_encoder = {
    .p.name           = "libx264",
    CODEC_LONG_NAME("libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
    .p.type           = AVMEDIA_TYPE_VIDEO,
    .p.id             = AV_CODEC_ID_H264,
    .p.capabilities   = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |
                        AV_CODEC_CAP_OTHER_THREADS |
                        AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE |
                        AV_CODEC_CAP_ENCODER_FLUSH |
                        AV_CODEC_CAP_ENCODER_RECON_FRAME,
    .p.priv_class     = &x264_class,
    .p.wrapper_name   = "libx264",
    .priv_data_size   = sizeof(X264Context),
    .init             = X264_init,
    FF_CODEC_ENCODE_CB(X264_frame),
    .flush            = X264_flush,
    .close            = X264_close,
    .defaults         = x264_defaults,
#if X264_BUILD < 153
    .init_static_data = X264_init_static,
#else
    .p.pix_fmts       = pix_fmts_all,
#endif
    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP | FF_CODEC_CAP_AUTO_THREADS
#if X264_BUILD < 158
                      | FF_CODEC_CAP_NOT_INIT_THREADSAFE
#endif
                      ,
};

2.6.5 核心编码函数(X264_frame)

该函数完成实际的264编码,假设调用了x264编码器。该函数位于libavcodec/libx264.c中,大致的流程为:

  1. 进行编码(x264_encoder_encode)
  2. 解析编码帧类型
  3. 判断是否是关键帧
  4. 计算编码帧的质量

此时,再向下走就进入了编码器内部,不再属于链路。编码器内部在其他文章中分析。

static int X264_frame(AVCodecContext *ctx, AVPacket *pkt, const AVFrame *frame,
                      int *got_packet)
{
    X264Context *x4 = ctx->priv_data;
    x264_nal_t *nal;
    int nnal, ret;
    x264_picture_t pic_out = {0}, *pic_in;
    int pict_type;
    int64_t wallclock = 0;
    X264Opaque *out_opaque;

    ret = setup_frame(ctx, frame, &pic_in);
    if (ret < 0)
        return ret;

    do {
    	// ----- 1.进行编码 ----- //
        if (x264_encoder_encode(x4->enc, &nal, &nnal, pic_in, &pic_out) < 0)
            return AVERROR_EXTERNAL;

        if (nnal && (ctx->flags & AV_CODEC_FLAG_RECON_FRAME)) {
            AVCodecInternal *avci = ctx->internal;

            av_frame_unref(avci->recon_frame);

            avci->recon_frame->format = csp_to_pixfmt(pic_out.img.i_csp);
            if (avci->recon_frame->format == AV_PIX_FMT_NONE) {
                av_log(ctx, AV_LOG_ERROR,
                       "Unhandled reconstructed frame colorspace: %d\n",
                       pic_out.img.i_csp);
                return AVERROR(ENOSYS);
            }

            avci->recon_frame->width  = ctx->width;
            avci->recon_frame->height = ctx->height;
            for (int i = 0; i < pic_out.img.i_plane; i++) {
                avci->recon_frame->data[i]     = pic_out.img.plane[i];
                avci->recon_frame->linesize[i] = pic_out.img.i_stride[i];
            }

            ret = av_frame_make_writable(avci->recon_frame);
            if (ret < 0) {
                av_frame_unref(avci->recon_frame);
                return ret;
            }
        }

        ret = encode_nals(ctx, pkt, nal, nnal);
        if (ret < 0)
            return ret;
    } while (!ret && !frame && x264_encoder_delayed_frames(x4->enc));

    if (!ret)
        return 0;

    pkt->pts = pic_out.i_pts;
    pkt->dts = pic_out.i_dts;

    out_opaque = pic_out.opaque;
    if (out_opaque >= x4->reordered_opaque &&
        out_opaque < &x4->reordered_opaque[x4->nb_reordered_opaque]) {
        wallclock = out_opaque->wallclock;
        pkt->duration = out_opaque->duration;

        if (ctx->flags & AV_CODEC_FLAG_COPY_OPAQUE) {
            pkt->opaque                  = out_opaque->frame_opaque;
            pkt->opaque_ref              = out_opaque->frame_opaque_ref;
            out_opaque->frame_opaque_ref = NULL;
        }

        opaque_uninit(out_opaque);
    } else {
        // Unexpected opaque pointer on picture output
        av_log(ctx, AV_LOG_ERROR, "Unexpected opaque pointer; "
               "this is a bug, please report it.\n");
    }
	// ----- 2.解析X264类型 ----- //
    switch (pic_out.i_type) {
    case X264_TYPE_IDR:
    case X264_TYPE_I:
        pict_type = AV_PICTURE_TYPE_I;
        break;
    case X264_TYPE_P:
        pict_type = AV_PICTURE_TYPE_P;
        break;
    case X264_TYPE_B:
    case X264_TYPE_BREF:
        pict_type = AV_PICTURE_TYPE_B;
        break;
    default:
        av_log(ctx, AV_LOG_ERROR, "Unknown picture type encountered.\n");
        return AVERROR_EXTERNAL;
    }
	// ----- 3.解析是否是关键帧 ----- //
    pkt->flags |= AV_PKT_FLAG_KEY*pic_out.b_keyframe;
    if (ret) {
        int error_count = 0;
        int64_t *errors = NULL;
        int64_t sse[3] = {0};

        if (ctx->flags & AV_CODEC_FLAG_PSNR) {
            const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(ctx->pix_fmt);
            double scale[3] = { 1,
                (double)(1 << pix_desc->log2_chroma_h) * (1 << pix_desc->log2_chroma_w),
                (double)(1 << pix_desc->log2_chroma_h) * (1 << pix_desc->log2_chroma_w),
            };

            error_count = pix_desc->nb_components;
			// ----- 4.计算编码质量 ----- //
            for (int i = 0; i < pix_desc->nb_components; ++i) {
                double max_value = (double)(1 << pix_desc->comp[i].depth) - 1.0;
                double plane_size = ctx->width * (double)ctx->height / scale[i];

                /* psnr = 10 * log10(max_value * max_value / mse) */
                double mse = (max_value * max_value) / pow(10, pic_out.prop.f_psnr[i] / 10.0);

                /* SSE = MSE * width * height / scale -> because of possible chroma downsampling */
                sse[i] = (int64_t)floor(mse * plane_size + .5);
            };

            errors = sse;
        }

        ff_side_data_set_encoder_stats(pkt, (pic_out.i_qpplus1 - 1) * FF_QP2LAMBDA,
                                       errors, error_count, pict_type);

        if (wallclock)
            ff_side_data_set_prft(pkt, wallclock);
    }

    *got_packet = ret;
    return 0;
}

2.7 获取编码器输出的已编码信息(avcodec_receive_packet)

该函数的主要作用是获取已经编码的信息(存储在AVPacket中),函数的主要工作流程是:

  1. 检查输入变量是否存在
  2. 获取已经编码的信息(encode_receieve_packet_internal),这里调用的函数和2.6节中调用的函数相同,均调用了encode_receive_packet_internal。但是在这里进行的是获取操作,将获取的帧信息放到AVPacket当中。在实现上,如果已经调用了avcodec_send_frame,则pkt->data不为空,此时avcodec_receive_packet会返回这个pkt而不会执行编码
int attribute_align_arg avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;

    av_packet_unref(avpkt);
	// ----- 1.检查输入变量是否存在 ----- //
    if (!avcodec_is_open(avctx) || !av_codec_is_encoder(avctx->codec))
        return AVERROR(EINVAL);

    if (avci->buffer_pkt->data || avci->buffer_pkt->side_data) {
        av_packet_move_ref(avpkt, avci->buffer_pkt);
    } else {
    	// ----- 2.获取已经编码的信息 ----- //
        ret = encode_receive_packet_internal(avctx, avpkt);
        if (ret < 0)
            return ret;
    }

    return 0;
}

3.小结

从编码链路这一整体系统上看,大致的输入是AVFrame,处理系统是AVCodec,记录信息是AVCodecContext,推送输入的引擎是avcodec_send_frame,接收输出的引擎是avcodec_receive_packet,输出是AVPacket。在这之中需要考虑的细节问题是:

  1. 输入的AVFrame是什么格式?
    对于编码器而言,输入的AVFrame主要是以yuv420p的格式进行存储。通常的处理方式是读取yuv格式文件,随后将三通道分离存储到AVFrame当中。此外,还有一些其他的格式如NV12等等

  2. 处理系统AVCodec使用什么格式?
    AVCodec可以使用的编码器包括H264、H265、AV1等等主流的编解码器,其中商业上最常用的就是H264格式的编码器(如x264),在某些场景下会使用H265编码器(如硬件设备属于高性能设备),AV1编码器暂时使用很少。这是因为软件算法和硬件设备的发展前后不一致,目前很多硬件设备仅具备H264硬件编码能力,更高级别的编码器能力还在发展中

  3. 记录信息AVCodecContext记录了哪些信息?
    AVCodecContext记录了编码流程中的信息,包括码流帧的长宽,高度,AVCodec信息,Log信息,时间,色度格式等等一系列的信息,它纵览全局,是极其重要的结构体

  4. 推送输入的引擎是avcodec_send_frame如何处理输入信息?
    在考虑输入信息时,分了很多个层级逐步处理。首先,检查这个输入是否存在;其次,输入是否是CB_RECEIVE_TYPE;随后,是否以多线程处理;最后,使用哪个编码器进行实际编码。在做二次开发时,可以先简单考虑具体使用哪个编码器,随后再考虑线程问题

  5. 输出引擎avcodec_receive_packet如何接收输出信息?
    在考虑输出信息时,比较简单,只需要从已经编码的结构体当中去取出编码帧即可。但是这里调用了一个与输入引擎相同的函数(encode_receive_packet_internal),这个函数会判断是否是取出操作,感觉是将二者合并。早期的FFmpeg版本当中,是将推送引擎和接收引擎分开的

  6. 输出AVPacket何时有效?
    在输出引擎能够正常输出时既有效,并且数据存储在data中,可以正常存储为yuv格式,也可以直接使用sdl进行在线播放。通常来说,在线视频软件(如会议、直播、点播等)都是使用SDL直接播放的

CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen

相关推荐

  1. FFmpeg编码路上主要函数简单分析

    2024-05-16 07:32:06       16 阅读
  2. FFMPEG编译安装、简单使用

    2024-05-16 07:32:06       43 阅读
  3. ffmpeg使用不简单

    2024-05-16 07:32:06       23 阅读
  4. 编写一个简易 Axios 函数

    2024-05-16 07:32:06       38 阅读
  5. 5 scala函数编程简介

    2024-05-16 07:32:06       33 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-05-16 07:32:06       19 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-05-16 07:32:06       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-05-16 07:32:06       20 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-05-16 07:32:06       20 阅读

热门阅读

  1. Linux文件处理知识点

    2024-05-16 07:32:06       11 阅读
  2. 24.HashMap的扩容机制

    2024-05-16 07:32:06       14 阅读
  3. uni-app 实现下拉单选功能(六)

    2024-05-16 07:32:06       11 阅读
  4. 【kotlin其它】Gson的简单用法

    2024-05-16 07:32:06       13 阅读
  5. Mac M1安装 nacos并自定义 Mysql 数据

    2024-05-16 07:32:06       9 阅读