【FFmpeg】avcodec_send_frame函数

目录

  • 1.avcodec_send_frame
    • 1.1 将输入的frame存入内部buffer(encode_send_frame_internal)
      • 1.1.1 frame的引用函数(av_frame_ref )
        • 1.1.1.1 帧属性的拷贝(frame_copy_props)
        • 1.1.1.2 buffer的引用函数(av_buffer_ref)
    • 1.2 将frame送入编码器(encode_receive_packet_internal)
      • 1.2.1 软编入口(encode_simple_receive_packet)
        • 1.2.1.1 实际编码(codec->cb.encode)
        • 1.2.1.2 创建pkt的refcount缓冲buffer(encode_make_refcounted)
  • 2.小结

FFmpeg相关记录:

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

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

结构体分析:
【FFmpeg】AVCodec结构体
【FFmpeg】AVCodecContext结构体
【FFmpeg】AVStream结构体
【FFmpeg】AVFormatContext结构体
【FFmpeg】AVIOContext结构体
【FFmpeg】AVPacket结构体

函数分析:
【通用】
【FFmpeg】avcodec_find_encoder和avcodec_find_decoder
【FFmpeg】关键结构体的初始化和释放(AVFormatContext、AVIOContext等)
【FFmpeg】avcodec_open2函数

【推流】
【FFmpeg】avformat_open_input函数
【FFmpeg】avformat_find_stream_info函数
【FFmpeg】avformat_alloc_output_context2函数
【FFmpeg】avio_open2函数
【FFmpeg】avformat_write_header函数
【FFmpeg】av_write_frame函数

avcodec_send_frame的函数调用关系为
在这里插入图片描述

1.avcodec_send_frame

avcodec_send_frame函数的主要功能是将外部输入的frame送入到编码器中实现编码,函数的实现位于libavcodec\encode.c中,从代码看,主要进行的流程为:
(1)输入信息avctx的检查
(2)如果输入的帧frame不存在,设置draining为1,随后将输入的frame存入avctx的buffer中,存入的过程使用encode_send_frame_internal实现
(3)将frame送入编码器进行编码,使用encode_receive_packet_internal实现

/**
 * Supply a raw video or audio frame to the encoder. Use avcodec_receive_packet()
 * to retrieve buffered output packets.
 *
 * @param avctx     codec context
 * @param[in] frame AVFrame containing the raw audio or video frame to be encoded.
 *                  Ownership of the frame remains with the caller, and the
 *                  encoder will not write to the frame. The encoder may create
 *                  a reference to the frame data (or copy it if the frame is
 *                  not reference-counted).
 *                  It can be NULL, in which case it is considered a flush
 *                  packet.  This signals the end of the stream. If the encoder
 *                  still has packets buffered, it will return them after this
 *                  call. Once flushing mode has been entered, additional flush
 *                  packets are ignored, and sending frames will return
 *                  AVERROR_EOF.
 *
 *                  For audio:
 *                  If AV_CODEC_CAP_VARIABLE_FRAME_SIZE is set, then each frame
 *                  can have any number of samples.
 *                  If it is not set, frame->nb_samples must be equal to
 *                  avctx->frame_size for all frames except the last.
 *                  The final frame may be smaller than avctx->frame_size.
 * @retval 0                 success
 * @retval AVERROR(EAGAIN)   input is not accepted in the current state - user must
 *                           read output with avcodec_receive_packet() (once all
 *                           output is read, the packet should be resent, and the
 *                           call will not fail with EAGAIN).
 * @retval AVERROR_EOF       the encoder has been flushed, and no new frames can
 *                           be sent to it
 * @retval AVERROR(EINVAL)   codec not opened, it is a decoder, or requires flush
 * @retval AVERROR(ENOMEM)   failed to add packet to internal queue, or similar
 * @retval "another negative error code" legitimate encoding errors
 */
// 向编码器提供原始视频或音频帧。使用avcodec_receive_packet()检索缓冲的输出数据包
int attribute_align_arg avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;
	// 1.输入的检查
    if (!avcodec_is_open(avctx) || !av_codec_is_encoder(avctx->codec))
        return AVERROR(EINVAL);

    if (avci->draining)
        return AVERROR_EOF;

    if (avci->buffer_frame->buf[0])
        return AVERROR(EAGAIN);
	// 如果输出不存在,draining设为1
    if (!frame) {
        avci->draining = 1;
    } else { // 2.将输入的frame存入avctx的buffer中
        ret = encode_send_frame_internal(avctx, frame);
        if (ret < 0)
            return ret;
    }
	// 3.将frame送入编码器进行编码
    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;
}

1.1 将输入的frame存入内部buffer(encode_send_frame_internal)

函数的功能是将输入的frame填充到avctx的buffer中,首先对音频进行检查,随后使用av_frame_ref将输入的frame填充到avctx的buffer中,如果是视频文件,最后还执行了encode_generate_icc_profile,但是这个函数没有被实现,默认会返回0

static int encode_send_frame_internal(AVCodecContext *avctx, const AVFrame *src)
{
    AVCodecInternal *avci = avctx->internal;
    EncodeContext     *ec = encode_ctx(avci);
    AVFrame *dst = avci->buffer_frame;
    int ret;
	// 音频
    if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {
        /* extract audio service type metadata */
        AVFrameSideData *sd = av_frame_get_side_data(src, AV_FRAME_DATA_AUDIO_SERVICE_TYPE);
        if (sd && sd->size >= sizeof(enum AVAudioServiceType))
            avctx->audio_service_type = *(enum AVAudioServiceType*)sd->data;

        /* check for valid frame size */
        if (!(avctx->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) {
            /* if we already got an undersized frame, that must have been the last */
            if (ec->last_audio_frame) {
                av_log(avctx, AV_LOG_ERROR, "frame_size (%d) was not respected for a non-last frame\n", avctx->frame_size);
                return AVERROR(EINVAL);
            }
            if (src->nb_samples > avctx->frame_size) {
                av_log(avctx, AV_LOG_ERROR, "nb_samples (%d) > frame_size (%d)\n", src->nb_samples, avctx->frame_size);
                return AVERROR(EINVAL);
            }
            if (src->nb_samples < avctx->frame_size) {
                ec->last_audio_frame = 1;
                if (!(avctx->codec->capabilities & AV_CODEC_CAP_SMALL_LAST_FRAME)) {
                    int pad_samples = avci->pad_samples ? avci->pad_samples : avctx->frame_size;
                    int out_samples = (src->nb_samples + pad_samples - 1) / pad_samples * pad_samples;

                    if (out_samples != src->nb_samples) {
                        ret = pad_last_frame(avctx, dst, src, out_samples);
                        if (ret < 0)
                            return ret;
                        goto finish;
                    }
                }
            }
        }
    }
	// 将src拷贝到dst中
    ret = av_frame_ref(dst, src);
    if (ret < 0)
        return ret;

finish:

    if (avctx->codec->type == AVMEDIA_TYPE_VIDEO) {
		// 函数还未实现,默认会返回0
        ret = encode_generate_icc_profile(avctx, dst);
        if (ret < 0)
            return ret;
    }

    // unset frame duration unless AV_CODEC_FLAG_FRAME_DURATION is set,
    // since otherwise we cannot be sure that whatever value it has is in the
    // right timebase, so we would produce an incorrect value, which is worse
    // than none at all
    if (!(avctx->flags & AV_CODEC_FLAG_FRAME_DURATION))
        dst->duration = 0;

    return 0;
}

下面看看av_frame_ref的实现过程

1.1.1 frame的引用函数(av_frame_ref )

int av_frame_ref(AVFrame *dst, const AVFrame *src)
{
    int ret = 0;

    av_assert1(dst->width == 0 && dst->height == 0);
    av_assert1(dst->ch_layout.nb_channels == 0 &&
               dst->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC);
	// 长宽和format的配置
    dst->format         = src->format;
    dst->width          = src->width;
    dst->height         = src->height;
    // 这个帧描述的音频采样数(每个通道)
    dst->nb_samples     = src->nb_samples;
	// 拷贝一些属性值
    ret = frame_copy_props(dst, src, 0);
    if (ret < 0)
        goto fail;
	// 拷贝音频声道数
    ret = av_channel_layout_copy(&dst->ch_layout, &src->ch_layout);
    if (ret < 0)
        goto fail;

    /* duplicate the frame data if it's not refcounted */
    // 如果src没有被引用,即只有1份。没有进行拷贝操作
    // refcounted可以看做是一种编程模式,用于管理动态分配的资源,如内存、句柄、文件描述等
    // 这种模式通过维护一个引用计数器来跟踪资源的使用者数量,以确保资源在不再需要时被正确释放
    // 例如,当这个资源首次被创建或分配时,它的引用计数器被初始化为1,当有一个新的使用者或引用使用这个资源时
    // 引用计数器就会加1,当使用者不再需要使用这个资源时,它会减少引用计数器,并检查计数器是否为0
    // 如果这个计数器为0,这说明没有其他的使用者了,可以进行安全的释放
    if (!src->buf[0]) {
    	// 分配一个新的buffer
        ret = av_frame_get_buffer(dst, 0);
        if (ret < 0)
            goto fail;
		// 将帧数据从src复制到dst
        ret = av_frame_copy(dst, src);
        if (ret < 0)
            goto fail;

        return 0;
    }

    /* ref the buffers */
    for (int i = 0; i < FF_ARRAY_ELEMS(src->buf); i++) {
        if (!src->buf[i])
            continue;
        // buffer的ref操作
        dst->buf[i] = av_buffer_ref(src->buf[i]);
        if (!dst->buf[i]) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }
	// extended_buf用于存储额外的数据缓冲区,这个字段通常用于存储与视频帧相关的额外信息,例如字幕、时间戳等
    if (src->extended_buf) {
        dst->extended_buf = av_calloc(src->nb_extended_buf,
                                      sizeof(*dst->extended_buf));
        if (!dst->extended_buf) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        dst->nb_extended_buf = src->nb_extended_buf;

        for (int i = 0; i < src->nb_extended_buf; i++) {
            dst->extended_buf[i] = av_buffer_ref(src->extended_buf[i]);
            if (!dst->extended_buf[i]) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
        }
    }
	// 硬件frame上下文信息
    if (src->hw_frames_ctx) {
        dst->hw_frames_ctx = av_buffer_ref(src->hw_frames_ctx);
        if (!dst->hw_frames_ctx) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }

    /* duplicate extended data */
    if (src->extended_data != src->data) {
        int ch = dst->ch_layout.nb_channels;

        if (!ch) {
            ret = AVERROR(EINVAL);
            goto fail;
        }

        dst->extended_data = av_malloc_array(sizeof(*dst->extended_data), ch);
        if (!dst->extended_data) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        memcpy(dst->extended_data, src->extended_data, sizeof(*src->extended_data) * ch);
    } else
        dst->extended_data = dst->data;

    memcpy(dst->data,     src->data,     sizeof(src->data));
    memcpy(dst->linesize, src->linesize, sizeof(src->linesize));

    return 0;

fail:
	// 如果失败,解引用
    av_frame_unref(dst);
    return ret;
}
1.1.1.1 帧属性的拷贝(frame_copy_props)
static int frame_copy_props(AVFrame *dst, const AVFrame *src, int force_copy)
{
    int ret;

#if FF_API_FRAME_KEY
FF_DISABLE_DEPRECATION_WARNINGS
    dst->key_frame              = src->key_frame;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    dst->pict_type              = src->pict_type;
    dst->sample_aspect_ratio    = src->sample_aspect_ratio;
    dst->crop_top               = src->crop_top;
    dst->crop_bottom            = src->crop_bottom;
    dst->crop_left              = src->crop_left;
    dst->crop_right             = src->crop_right;
    dst->pts                    = src->pts;
    dst->duration               = src->duration;
    dst->repeat_pict            = src->repeat_pict;
#if FF_API_INTERLACED_FRAME
FF_DISABLE_DEPRECATION_WARNINGS
    dst->interlaced_frame       = src->interlaced_frame;
    dst->top_field_first        = src->top_field_first;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
#if FF_API_PALETTE_HAS_CHANGED
FF_DISABLE_DEPRECATION_WARNINGS
    dst->palette_has_changed    = src->palette_has_changed;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    dst->sample_rate            = src->sample_rate;
    dst->opaque                 = src->opaque;
    dst->pkt_dts                = src->pkt_dts;
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGS
    dst->pkt_pos                = src->pkt_pos;
    dst->pkt_size               = src->pkt_size;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    dst->time_base              = src->time_base;
    dst->quality                = src->quality;
    dst->best_effort_timestamp  = src->best_effort_timestamp;
    dst->flags                  = src->flags;
    dst->decode_error_flags     = src->decode_error_flags;
    dst->color_primaries        = src->color_primaries;
    dst->color_trc              = src->color_trc;
    dst->colorspace             = src->colorspace;
    dst->color_range            = src->color_range;
    dst->chroma_location        = src->chroma_location;

    av_dict_copy(&dst->metadata, src->metadata, 0);

    for (int i = 0; i < src->nb_side_data; i++) {
        const AVFrameSideData *sd_src = src->side_data[i];
        AVFrameSideData *sd_dst;
        if (   sd_src->type == AV_FRAME_DATA_PANSCAN
            && (src->width != dst->width || src->height != dst->height))
            continue;
        if (force_copy) {
            sd_dst = av_frame_new_side_data(dst, sd_src->type,
                                            sd_src->size);
            if (!sd_dst) {
                frame_side_data_wipe(dst);
                return AVERROR(ENOMEM);
            }
            memcpy(sd_dst->data, sd_src->data, sd_src->size);
        } else {
            AVBufferRef *ref = av_buffer_ref(sd_src->buf);
            sd_dst = av_frame_new_side_data_from_buf(dst, sd_src->type, ref);
            if (!sd_dst) {
                av_buffer_unref(&ref);
                frame_side_data_wipe(dst);
                return AVERROR(ENOMEM);
            }
        }
        av_dict_copy(&sd_dst->metadata, sd_src->metadata, 0);
    }

    ret = av_buffer_replace(&dst->opaque_ref, src->opaque_ref);
    ret |= av_buffer_replace(&dst->private_ref, src->private_ref);
    return ret;
}
1.1.1.2 buffer的引用函数(av_buffer_ref)
AVBufferRef *av_buffer_ref(const AVBufferRef *buf)
{
    AVBufferRef *ret = av_mallocz(sizeof(*ret));

    if (!ret)
        return NULL;

    *ret = *buf;
	// 引用之后,refcount + 1
    atomic_fetch_add_explicit(&buf->buffer->refcount, 1, memory_order_relaxed);

    return ret;
}

1.2 将frame送入编码器(encode_receive_packet_internal)

函数的主要功能是将frame送入到编码器中,先检查图像的尺寸是否有效,随后看是否是硬编还是软编,如果是硬编则使用receive_packet获取硬编码器输出的信息,否则调用encode_simple_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.检查图像的给定维度是否有效,这意味着具有指定pix_fmt的图像平面的所有字节都可以用带符号的int寻址
        if (av_image_check_size2(avctx->width, avctx->height, avctx->max_pixels, AV_PIX_FMT_NONE, 0, avctx))
            return AVERROR(EINVAL);
    }
	// FF_CODEC_CB_TYPE_RECEIVE_PACKET表示编解码器是使用receive_packet回调的编码器;仅限音频和视频编解码器
	// 2.cb_type为这个类型的都是硬件编码器的回调,例如amf,nvidia,vaapi平台
    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); // 3.使用软件编码器进行编码
    if (ret >= 0)
        avpkt->flags |= encode_ctx(avci)->intra_only_flag; // 是否是intra flag

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

    return ret;
}

1.2.1 软编入口(encode_simple_receive_packet)

函数很简单,做一下检查之后,直接调用encode_simple_internal进行内部编码

static int encode_simple_receive_packet(AVCodecContext *avctx, AVPacket *avpkt)
{
    int ret;

    while (!avpkt->data && !avpkt->side_data) {
        ret = encode_simple_internal(avctx, avpkt);
        if (ret < 0)
            return ret;
    }

    return 0;
}

encode_simple_internal的定义如下

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;
	// 如果引用计数为0,需要先获取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);
	// 多线程编码
    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 {
    	// 编码cb
        ret = ff_encode_encode_cb(avctx, avpkt, frame, &got_packet);
    }

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

    return ret;
}

ff_encode_encode_cb的定义如下,调用encode进行实际编码,对于libx264格式,会调用X264_frame()函数进行编码,随后会调用encode_make_refcounted来创建refcount缓冲buffer

int ff_encode_encode_cb(AVCodecContext *avctx, AVPacket *avpkt,
                        AVFrame *frame, int *got_packet)
{
    const FFCodec *const codec = ffcodec(avctx->codec);
    int ret;
	// 执行编码,对于libx264格式,会调用X264_frame()函数进行编码
	// 编码后的压缩数据存储在avpkt的data中
    ret = codec->cb.encode(avctx, avpkt, frame, got_packet);
    emms_c();
    av_assert0(ret <= 0);

    if (!ret && *got_packet) {
        if (avpkt->data) {
        	// 创建avpkt的refcount缓冲buffer
            ret = encode_make_refcounted(avctx, avpkt);
            if (ret < 0)
                goto unref;
            // Date returned by encoders must always be ref-counted
            av_assert0(avpkt->buf);
        }

        // set the timestamps for the simple no-delay case
        // encoders with delay have to set the timestamps themselves
        // 为简单的无延迟情况设置时间戳,带有延迟的编码器必须自己设置时间戳
        if (!(avctx->codec->capabilities & AV_CODEC_CAP_DELAY) ||
            (frame && (codec->caps_internal & FF_CODEC_CAP_EOF_FLUSH))) {
            if (avpkt->pts == AV_NOPTS_VALUE)
                avpkt->pts = frame->pts;

            if (!avpkt->duration) {
                if (frame->duration)
                    avpkt->duration = frame->duration;
                else if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {
                    avpkt->duration = ff_samples_to_time_base(avctx,
                                                              frame->nb_samples);
                }
            }
			// 注释翻译:根据需要将用户不透明的值从帧传播到avctx/pkt
			// 这里应该是将AVFrame结构体中记录重新排序的opaque数据,主要用于编码过程中对视频帧的重排序
            ret = ff_encode_reordered_opaque(avctx, avpkt, frame);
            if (ret < 0)
                goto unref;
        }

        // dts equals pts unless there is reordering
        // there can be no reordering if there is no encoder delay
        if (!(avctx->codec_descriptor->props & AV_CODEC_PROP_REORDER) ||
            !(avctx->codec->capabilities & AV_CODEC_CAP_DELAY)        ||
            (codec->caps_internal & FF_CODEC_CAP_EOF_FLUSH))
            avpkt->dts = avpkt->pts;
    } else {
unref:
        av_packet_unref(avpkt);
    }

    if (frame)
        av_frame_unref(frame); // 这里不再使用,解引用

    return ret;
}
1.2.1.1 实际编码(codec->cb.encode)

本函数实际执行了编码过程,以libx264编码器为例,会调用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
                      ,
};

其中,X264_frame()的定义如下,主要工作流程为:
(1)根据外部输入的frame,初始化x264格式的pic_in,这个pic_in会被送入x264编码器进行编码(setup_frame)
(2)进行264编码(x264_encoder_encode)
(3)一些编码后信息的处理
(4)处理私有数据
(5)确定帧类型
(6)计算编码结束的psnr质量信息
主要函数的功能都用注释标出,不再具体对底层函数分析,另外x264_encoder_encode在【x264】系列记录中可以看到具体的实现

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;
	// 1.根据外部输入的frame,来初始化x264格式的pic_in,从而进行x264编码
    ret = setup_frame(ctx, frame, &pic_in);
    if (ret < 0)
        return ret;

    do {
    	// 2.x264的接口函数,具体执行264编码过程
        if (x264_encoder_encode(x4->enc, &nal, &nnal, pic_in, &pic_out) < 0)
            return AVERROR_EXTERNAL;
		// 3.下面是一些编码后信息的处理
        if (nnal && (ctx->flags & AV_CODEC_FLAG_RECON_FRAME)) {
            AVCodecInternal *avci = ctx->internal;

            av_frame_unref(avci->recon_frame);
			// csp: colorspace
            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;
            // 将编码输出的重建帧(img)信息给到recon_frame
            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];
            }
			// 确保帧数据是可写的,尽可能避免数据复制
			// 如果avframe当中的buf->flag不是READ_ONLY则可写
            ret = av_frame_make_writable(avci->recon_frame);
            if (ret < 0) {
                av_frame_unref(avci->recon_frame);
                return ret;
            }
        }
		// 将编码之后的nal信息,写入到pkt之中
		// pic_out.img当中没有存储nal信息,nal信息存储在了ctx之中
        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;
	// 4.处理私有数据,opaque是一种通用的未指定具体含义的数据
	// 通常与视频帧或编码器上下文相关联,可以存储一些自定义数据,统计数据等等
    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");
    }
	// 5.确定帧类型
    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;
    }

    pkt->flags |= AV_PKT_FLAG_KEY*pic_out.b_keyframe;
    // 6.计算编码结束的psnr质量信息
    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;

            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;
}
1.2.1.2 创建pkt的refcount缓冲buffer(encode_make_refcounted)

函数的主要功能是为avpkt分配refcounted的缓冲buffer

static int encode_make_refcounted(AVCodecContext *avctx, AVPacket *avpkt)
{
    uint8_t *data = avpkt->data;
    int ret;
	// 如果avpkt中存在buf,直接返回
    if (avpkt->buf)
        return 0;

    avpkt->data = NULL;
    // 获取encode的buffer
    ret = ff_get_encode_buffer(avctx, avpkt, avpkt->size, 0);
    if (ret < 0)
        return ret;
    memcpy(avpkt->data, data, avpkt->size);

    return 0;
}

ff_get_encode_buffer的定义如下,其核心就是调用了get_encode_buffer进行encode_buffer的创建,默认的创建函数为avcodec_default_get_encode_buffer

int ff_get_encode_buffer(AVCodecContext *avctx, AVPacket *avpkt, int64_t size, int flags)
{
    int ret;

    if (size < 0 || size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE)
        return AVERROR(EINVAL);

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

    avpkt->size = size;
    // 默认使用的get_encode_buffer函数由avcodec_default_get_encode_buffer指定
    ret = avctx->get_encode_buffer(avctx, avpkt, flags);
    if (ret < 0)
        goto fail;

    if (!avpkt->data || !avpkt->buf) {
        av_log(avctx, AV_LOG_ERROR, "No buffer returned by get_encode_buffer()\n");
        ret = AVERROR(EINVAL);
        goto fail;
    }
    memset(avpkt->data + avpkt->size, 0, AV_INPUT_BUFFER_PADDING_SIZE);

    ret = 0;
fail:
    if (ret < 0) {
        av_log(avctx, AV_LOG_ERROR, "get_encode_buffer() failed\n");
        av_packet_unref(avpkt);
    }

    return ret;
}

avcodec_default_get_encode_buffer的定义如下,核心的地方是使用av_buffer_realloc为avpkt分配buf的空间

int avcodec_default_get_encode_buffer(AVCodecContext *avctx, AVPacket *avpkt, int flags)
{
    int ret;

    if (avpkt->size < 0 || avpkt->size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE)
        return AVERROR(EINVAL);

    if (avpkt->data || avpkt->buf) {
        av_log(avctx, AV_LOG_ERROR, "avpkt->{data,buf} != NULL in avcodec_default_get_encode_buffer()\n");
        return AVERROR(EINVAL);
    }

    ret = av_buffer_realloc(&avpkt->buf, avpkt->size + AV_INPUT_BUFFER_PADDING_SIZE);
    if (ret < 0) {
        av_log(avctx, AV_LOG_ERROR, "Failed to allocate packet of size %d\n", avpkt->size);
        return ret;
    }
    avpkt->data = avpkt->buf->data;

    return 0;
}

2.小结

avcodec_send_frame函数主要功能是将外部输入的frame送入到编码器中进行编码,送入之前需要将输入信息转换成对应的格式(例如x264格式),编码完成之后获取重建信息进行计算和存储。在这个过程中,refcount这个概念很重要,这个技术应用的核心目的是良好的管理FFmpeg当中资源的生命周期(例如AVFrame,AVPacket等),每多一个访问者,该项资源的refcount数量加1,每减少一个访问者,refcount减1

粗略来说,使用refcount有几点好处:
(1)内存空间
在程序运行过程中,为了节省开销,许多地方没有必要重复分配空间,使用refcount可以通过浅拷贝的方式,访问同一资源

(2)内存管理
程序中,往往有很多地方会访问相同的地址,如果操作不当,可能会出现误操作如提前释放或双重释放,refcount提供了一个计数器,只有当计数器清零的时候才会释放这个变量

(3)代码清晰
将资源的生命周期管理封装起来,使得代码更好维护

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/775086.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学会python——用python制作一个绘图板(python实例十九)

目录 1.认识Python 2.环境与工具 2.1 python环境 2.2 Visual Studio Code编译 3.制作一个绘图板 3.1 代码构思 3.2 代码实例 3.3 运行结果 4.总结 1.认识Python Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可…

Node.js实现一个文章生成器

前言 本文将从零开始&#xff0c;讲解如何使用Node.js来实现一个文章生成器 node里面有很多优秀的模块&#xff0c;现在我们就借助node的fs模块来操控文本&#xff0c;来实现我们想要的效果 效果展示 体验 fs 首先我们先创建一个json文件 里面放一些内容 接下来我们书写代码…

【博士每天一篇文献-算法】Adult neurogenesis acts as a neural regularizer

阅读时间&#xff1a;2023-12-20 1 介绍 年份&#xff1a;2022 作者&#xff1a;Lina M. Tran&#xff0c;Adam Santoro&#xff0c;谷歌DeepMind 期刊&#xff1a; Proceedings of the National Academy of Sciences 引用量&#xff1a;13 代码&#xff1a;https://github.c…

Java后端每日面试题(day1)

目录 JavaWeb三大组件依赖注入的方式Autowire和Resurce有什么区别&#xff1f;Spring Boot的优点Spring IoC是什么&#xff1f;说说Spring Aop的优点Component和Bean的区别自定义注解时使用的RetentionPolicy枚举类有哪些值&#xff1f;如何理解Spring的SPI机制&#xff1f;Spr…

2024年【四川省安全员B证】考试及四川省安全员B证考试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【四川省安全员B证】考试及四川省安全员B证考试题&#xff0c;包含四川省安全员B证考试答案和解析及四川省安全员B证考试题练习。安全生产模拟考试一点通结合国家四川省安全员B证考试最新大纲及四川省安全员B证…

第一篇——导论:数学通识课的体系和学习攻略

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 数学的认知大厦&#xff1b;之前听的时候就觉得很重要&#xff0c;本次又…

Google Earth Engine(GEE)——控制台ui.Textbox复制你想要的textbox

结果 函数: ui.Textbox(placeholder, value, onChange, disabled, style) A textbox that enables the user to input text information. Arguments: placeholder (String, optional): The placeholder text to display when the textbox is empty. Defaults to none. …

鸿蒙开发设备管理:【@ohos.account.appAccount (应用帐号管理)】

应用帐号管理 说明&#xff1a; 本模块首批接口从API version 7开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 导入模…

Linux中配置sudo用户访问权限

一、如何在 Linux 中配置 sudo 的访问权限 1.1、给用户dhz普通用户增加 sudo 权限 [rootlocalhost ~]# id dhz uid1000(dhz) gid1000(dhz) 组1000(dhz),10(wheel)1.2、使用root用户编辑/etc/sudoers文件 [rootlocalhost ~]# vi /etc/sudoers-- 增加&#xff1a; dhz ALL(ALL…

2024/07/05

1、梳理笔记 2、课堂习题 1、循环输入一个5位数&#xff0c;判断它是不是回文数。当输入0时循环结束。 即12321是回文数&#xff0c;个位与万位相同&#xff0c;十位与千位相同。 #include<stdio.h> int main(int argc, char const *argv[]) {while(1){int num0;printf(…

贪吃蛇——C语言(VS2022含源代码,及源代码zip文件)

一.游戏背景 贪吃蛇是一款在世界上盛名已久的小游戏&#xff0c;贪食蛇游戏操作简单&#xff0c;可玩性比较高。这个游戏难度最大的不是蛇长得很长的时候&#xff0c;而是开始。那个时候蛇身很短&#xff0c;看上去难度不大&#xff0c;却最容易死掉&#xff0c;因为把玩一条小…

vscode连接SSH

1、安装Remote-SSH插件 2、点击左下角&#xff0c;选择SSH 3、点击连接到主机后&#xff0c;添加新的SSH主机&#xff0c;示例ssh 用户ip 4、点击服务器&#xff0c;输入密码登录服务器 5、可在远程资源管理器选项卡中查看 6、可以在ssh设置中打开ssh配置文件 config中的文件…

合合信息大模型“加速器”亮相2024世界人工智能大会,助力大模型学好“专业课”

7月4日至7日&#xff0c;2024世界人工智能大会在上海拉开帷幕。现阶段&#xff0c;“百模大战”现象背后的中国大模型发展前景与堵点仍然是各界关注的焦点。如何帮助大模型在信息的海洋中快速找准航向&#xff0c;在数据的荒漠中找到高质量的“水源”&#xff1f;合合信息在本次…

Vue报错:Module not found: Error: Can‘t resolve ‘less-loader‘ in ‘文件地址‘

原因&#xff1a;Webpack无法找到 less-loader 模块&#xff0c;但在<style langless></style>中进行使用。less-loader 是一个Webpack的加载器&#xff0c;它用于将less文件编译成CSS。如果Webpack无法解析这个加载器&#xff0c;它就无法处理less文件&#xff0c…

LeetCode刷题记录:(15)三角形最小路径和

知识点&#xff1a;倒叙的动态规划 题目传送 解法一&#xff1a;二维动态规划【容易理解】 class Solution {public int minimumTotal(List<List<Integer>> triangle) {int n triangle.size();if (n 1) {return triangle.get(0).get(0);}// dp[i][j]:走到第i层第…

论文导读 | 综述:大模型与推荐系统

最近&#xff0c;预训练语言模型&#xff08;PLM&#xff09;在自然语言处理领域取得了巨大成功&#xff0c;并逐渐引入推荐系统领域。本篇推文介绍了最近的两篇预训练语言模型和推荐系统结合的综述&#xff1a; [1] Pre-train, Prompt, and Recommendation: A Comprehensive …

深度调峰汽轮机相关技术资料 厂家培训用

网盘 https://pan.baidu.com/s/16KfuoVko5xCUk3bDOfTlvQ?pwdezjb 亚临界循环流化床机组深度调峰下的输出功率预测方法.pdf 基于时间序列分析的燃煤电厂深度调峰预测方法及装置】.pdf 基于汽轮机低压缸排汽压力调节的深度调峰方法.pdf 基于深度调峰工况下阀门阀杆的振动预测方…

c++之旅第十一弹——顺序表

大家好啊&#xff0c;这里是c之旅第十一弹&#xff0c;跟随我的步伐来开始这一篇的学习吧&#xff01; 如果有知识性错误&#xff0c;欢迎各位指正&#xff01;&#xff01;一起加油&#xff01;&#xff01; 创作不易&#xff0c;希望大家多多支持哦&#xff01; 一,数据结构…

代码随想录第43天|动态规划

121. 买卖股票的最佳时机 股票只能被买卖一次 dp[i][0] 持有股票所得到的最大现金, dp[i][1] 不持有股票所得的最大现金, 避免定义多个变量递推公式: dp[i][0] 可能是在之前买入, 也可能是在这次被买入 max(dp[i - 1][0],-prices[i])dp[i][1] 可能是在本次抛售, 也可能在之…

Day44:LeedCode 188.买卖股票的最佳时机IV 309.最佳买卖股票时机含冷冻期 714.买卖股票的最佳时机含手续费

188. 买卖股票的最佳时机 IV 给你一个整数数组 prices 和一个整数 k &#xff0c;其中 prices[i] 是某支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说&#xff0c;你最多可以买 k 次&#xff0c;卖 k 次。 注意&…