【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设为1if (!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) {// 函数还未实现,默认会返回0ret = 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 allif (!(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]) {// 分配一个新的bufferret = av_frame_get_buffer(dst, 0);if (ret < 0)goto fail;// 将帧数据从src复制到dstret = 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);} elsedst->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_WARNINGSdst->key_frame              = src->key_frame;
FF_ENABLE_DEPRECATION_WARNINGS
#endifdst->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_WARNINGSdst->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_WARNINGSdst->palette_has_changed    = src->palette_has_changed;
FF_ENABLE_DEPRECATION_WARNINGS
#endifdst->sample_rate            = src->sample_rate;dst->opaque                 = src->opaque;dst->pkt_dts                = src->pkt_dts;
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGSdst->pkt_pos                = src->pkt_pos;dst->pkt_size               = src->pkt_size;
FF_ENABLE_DEPRECATION_WARNINGS
#endifdst->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 + 1atomic_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);} elseret = encode_simple_receive_packet(avctx, avpkt); // 3.使用软件编码器进行编码if (ret >= 0)avpkt->flags |= encode_ctx(avci)->intra_only_flag; // 是否是intra flagif (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 frameframe = 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 {// 编码cbret = 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缓冲bufferret = encode_make_refcounted(avctx, avpkt);if (ret < 0)goto unref;// Date returned by encoders must always be ref-countedav_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 delayif (!(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: colorspaceavci->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_framefor (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 outputav_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的bufferret = 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.mzph.cn/news/865330.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Appium 使用隐式等待,无法执行代码怎么办?

简介 添加等待是为了确保自动化脚本在执行过程中与应用程序之间的同步和稳定性。 应用程序的响应时间是不确定的&#xff0c;可能存在网络延迟、加载时间、动画效果等因素。如果在执行自动化脚本时没有适当的等待机制&#xff0c;脚本可能会在应用程序还未完成相应操作或加载完…

转让5000万无区域能源公司要求和流程

国家局的公司&#xff0c;也就是无地域无区域性的公司名称。这样的公司是还可以继续注册的&#xff0c;但是想要拥有国家局无区域的名称就不是那么容易的了。总局的企业要求高&#xff0c;也是实力的体现。对字号有保护。所以有很多人都对无地域的名称一直情有独钟。现有一家名…

智能视频监控平台LntonCVS视频监控汇聚平台系统详细介绍

视频监控综合管理平台是专为大中型用户设计的安防管理软件&#xff0c;旨在实现跨区域网络化视频监控的集中管理。该平台集成了信息资源管理、设备管理、用户管理、网络管理和安全管理等多项功能&#xff0c;能够集中管理监控中心的所有视频图像&#xff0c;支持多品牌设备联网…

AI基准测评(下):视频生成、代码能力、逻辑推理,AI是否已经超越人类?

本文作者&#xff1a;王加龙&#xff0c;阿里云高级算法专家 文章推荐 AI实测&#xff5c;基于图像、语言与声音&#xff0c;人工智能是否已经超越了人类&#xff1f; 知乎AI产品“直答”正式上线&#xff01;文心一言4.0 Turbo来袭&#xff0c;可为农民提供专业指导&#x…

Fork/Join框架

什么是 Fork/Join Fork/Join 框架是 Java7 提供了的一个用于并行执行任务的框架&#xff0c; 是一个把大任务分割成若干个小任务&#xff0c;最终汇总每个小任务结果后得到大任务结果的框架。 分治 我们再通过 Fork 和 Join 这两个单词来理解下 Fork/Join 框架&#xff0c;F…

数据库设计规范(DOC文件)

1 编写目的 2 数据库策略 2.1 数据库对象长度策略 2.2 数据完整性策略 2.3 规范化设计与性能之间的权衡策略 2.4 字段类型的定义与使用策略 3 命名规范 3.1 数据库命名规则 3.2 数据库对象命名的一般原则 3.3 表空间(Tablespace)命名规则 3.4 表(Table)命名规则 3.5…

Java 虚拟机 一

运行时数据区 我们先看线程隔离的数据区 程序计数器 程序计数器&#xff08; Program Counter Register&#xff09; 是一块较小的内存空间&#xff0c; 它可以看作是当前线程所执行的字节码的行号指示器。 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执…

提升用户体验之requestAnimationFrame实现前端动画

1)requestAnimationFrame是什么? 1.MDN官方解释 2.解析这段话&#xff1a; 1、那么浏览器重绘是指什么呢&#xff1f; ——大多数电脑的显示器刷新频率是60Hz&#xff0c;1000ms/6016.66666667ms的时间刷新一次 2、重绘之前调用指定的回调函数更新动画&#xff1f; ——requ…

六西格玛绿带培训ROI:你的投资究竟值不值?

近年来&#xff0c;企业对于员工培训的投入日益增加&#xff0c;六西格玛绿带培训更是作为提升企业运营效率和质量管理的利器&#xff0c;更是备受关注。然而&#xff0c;面对高昂的培训成本&#xff0c;企业如何评估六西格玛绿带培训的投资回报率&#xff08;ROI&#xff09;呢…

安装Intel Realsense D435i驱动与ROS包报错

1.下载安装realsense SDK 1.1 安装依赖 sudo apt install libudev-dev pkg-config libgtk-3-dev sudo apt install libusb-1.0-0-dev pkg-config sudo apt install libglfw3-dev sudo apt install libssl-dev1.2 权限 cd librealsense/ sudo cp config/99-realsense-libusb.…

一万年太久,只争朝夕 | Foundation model的进展仍不够快

编者按&#xff1a;如今根基模型&#xff08;Foundation Models&#xff09;的应用和相关创新正在快速涌现&#xff0c;但仍有很大的提升空间&#xff0c;目前还无法充分发挥根基模型的潜能、将其高效快速地应用于企业级AI应用中。 根基模型的加速应用和落地&#xff0c;带动了…

64、基于去噪卷积神经网络的彩色图像去噪(matlab)

1、基于去噪卷积神经网络的彩色图像去噪的原理及流程 基于去噪卷积神经网络的彩色图像去噪是一种基于深度学习的图像处理技术&#xff0c;可以有效地去除图像中的噪声&#xff0c;提高图像的质量。下面是在Matlab中实现基于去噪卷积神经网络的彩色图像去噪的原理及流程&#x…

移动端响应式布局开发的四大方案

移动端响应式布局开发的四大方案 media媒体查询remflexvh/vw media媒体查询 媒体查询通常会结合百分比实现自适应&#xff0c;它经常应用于pc端与移动端是一套项目代码的情况&#xff0c;依据媒体查询写多套不同的样式 rem pc端和移动端是两套代码的&#xff0c;通常pc端不做…

ChatGPT如何应用在谷歌seo?

ChatGPT在提升博客和创作效率方面非常有用。它可以帮助你快速生成吸引人的标题&#xff0c;确保内容第一眼就能抓住读者的注意力。不仅如此&#xff0c;ChatGPT还能根据你的主题生成详细的文章提纲&#xff0c;让你在写作时思路更加清晰。关键词优化也是它的强项&#xff0c;可…

300KG载重履带式无人车技术详解

一、动力系统 300KG载重履带式无人车采用了高效且稳定的动力系统&#xff0c;通常由电池组或燃油发动机作为动力源。电池组提供了较长的续航时间和较低的运行噪音&#xff0c;适用于需要静音作业的场合。而燃油发动机则能提供更高的功率和续航能力&#xff0c;适用于需要长时间…

STM32远程烧录程序

目录 简介 不同的程序下载方式 ICP&#xff1a;In-Circuit Programming ISP&#xff1a;In-System Programing IAP&#xff1a;In-Application Programming BootLoader Bootloader 是什么&#xff1f; STM32的启动方式 存储器组织 存储器映像 嵌入式SRAM 嵌入式FL…

不同行业如何选择适合自己行业的项目管理工具?

在当今的信息化时代&#xff0c;项目管理软件已成为各行各业不可或缺的工具。然而&#xff0c;由于各行业具有不同的特点和需求&#xff0c;因此选择合适的项目管理软件成为了一个重要问题。本文将探讨不同行业在选择项目管理软件时需要考虑的因素&#xff0c;希望能帮助大家更…

vue实现一个简单的审批绘制功能

1、vue代码 <div class"approval"><div class"approval_ul" v-for"(item,key) in approvalList" :key"key"><div><el-radio-group v-model"item.jointlySign"><el-radio label"1">…

【超全详解】耳机怎么清理?手把手教你清洁各种耳机!

平时听歌、听书、打电话耳机往往有时候几个小时都戴着&#xff0c;时间久了难免会堆积污垢堵孔。这可能就会造成耳机左右音量不一致、声音小、降噪效果变差、嗡嗡声等问题&#xff0c; 而且耳机用久了如果不及时清理&#xff0c;可能导致耳朵感染细菌&#xff0c;严重的话会影响…

亚马逊测评策略全攻略:详析各方案优势与局限,你精通了吗?

亚马逊测评&#xff0c;一个绕不开的话题。不管是对于新手卖家还是资深卖家来说&#xff0c;它都是提升产品销量和排名的有效手段之一。接下来&#xff0c;我将为大家详细解析亚马逊测评的各种方式和注意事项。 一、精准筛选真人测评资源 在寻找真人测评资源时&#xff0c;许多…