【FFmpeg】avcodec_send_packet函数

目录

  • 1.avcodec_send_packet
    • [1.1 解码入口函数(decode_receive_frame_internal)](#1.1 解码入口函数(decode_receive_frame_internal))
      • [1.1.1 软解入口(decode_simple_receive_frame)](#1.1.1 软解入口(decode_simple_receive_frame))

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函数

【编码】
【FFmpeg】avcodec_send_frame函数

1.avcodec_send_packet

函数的主要功能是将一个压缩包送入到解码器当中进行解码,定义位于libavcodec\decode.c中

c 复制代码
/**
 * Supply raw packet data as input to a decoder.
 *
 * Internally, this call will copy relevant AVCodecContext fields, which can
 * influence decoding per-packet, and apply them when the packet is actually
 * decoded. (For example AVCodecContext.skip_frame, which might direct the
 * decoder to drop the frame contained by the packet sent with this function.)
 *
 * @warning The input buffer, avpkt->data must be AV_INPUT_BUFFER_PADDING_SIZE
 *          larger than the actual read bytes because some optimized bitstream
 *          readers read 32 or 64 bits at once and could read over the end.
 *
 * @note The AVCodecContext MUST have been opened with @ref avcodec_open2()
 *       before packets may be fed to the decoder.
 *
 * @param avctx codec context
 * @param[in] avpkt The input AVPacket. Usually, this will be a single video
 *                  frame, or several complete audio frames.
 *                  Ownership of the packet remains with the caller, and the
 *                  decoder will not write to the packet. The decoder may create
 *                  a reference to the packet data (or copy it if the packet is
 *                  not reference-counted).
 *                  Unlike with older APIs, the packet is always fully consumed,
 *                  and if it contains multiple frames (e.g. some audio codecs),
 *                  will require you to call avcodec_receive_frame() multiple
 *                  times afterwards before you can send a new packet.
 *                  It can be NULL (or an AVPacket with data set to NULL and
 *                  size set to 0); in this case, it is considered a flush
 *                  packet, which signals the end of the stream. Sending the
 *                  first flush packet will return success. Subsequent ones are
 *                  unnecessary and will return AVERROR_EOF. If the decoder
 *                  still has frames buffered, it will return them after sending
 *                  a flush packet.
 *
 * @retval 0                 success
 * @retval AVERROR(EAGAIN)   input is not accepted in the current state - user
 *                           must read output with avcodec_receive_frame() (once
 *                           all output is read, the packet should be resent,
 *                           and the call will not fail with EAGAIN).
 * @retval AVERROR_EOF       the decoder has been flushed, and no new packets can be
 *                           sent to it (also returned if more than 1 flush
 *                           packet is sent)
 * @retval AVERROR(EINVAL)   codec not opened, it is an encoder, or requires flush
 * @retval AVERROR(ENOMEM)   failed to add packet to internal queue, or similar
 * @retval "another negative error code" legitimate decoding errors
 */
// 提供原始数据包数据作为解码器的输入
int attribute_align_arg avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
{
    AVCodecInternal *avci = avctx->internal;
    DecodeContext     *dc = decode_ctx(avci);
    int ret;
	// 1.输入信息的检查
    if (!avcodec_is_open(avctx) || !av_codec_is_decoder(avctx->codec))
        return AVERROR(EINVAL);

    if (dc->draining_started)
        return AVERROR_EOF;

    if (avpkt && !avpkt->size && avpkt->data)
        return AVERROR(EINVAL);

    if (avpkt && (avpkt->data || avpkt->side_data_elems)) {
        if (!AVPACKET_IS_EMPTY(avci->buffer_pkt))
            return AVERROR(EAGAIN);
        ret = av_packet_ref(avci->buffer_pkt, avpkt);
        if (ret < 0)
            return ret;
    } else
        dc->draining_started = 1;
	// 2.解码入口
    if (!avci->buffer_frame->buf[0] && !dc->draining_started) {
        ret = decode_receive_frame_internal(avctx, avci->buffer_frame);
        if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
            return ret;
    }

    return 0;
}

1.1 解码入口函数(decode_receive_frame_internal)

函数的定义位于libavcodec\decode.c中,如下所示。这里最重要的地方在于检查cb_type,如果使用硬件解码器,则使用receive_frame,否则使用decode_simple_receive_frame执行软解过程

c 复制代码
static int decode_receive_frame_internal(AVCodecContext *avctx, AVFrame *frame)
{
    AVCodecInternal *avci = avctx->internal;
    DecodeContext     *dc = decode_ctx(avci);
    const FFCodec *const codec = ffcodec(avctx->codec);
    int ret, ok;

    av_assert0(!frame->buf[0]);
	// 如果使用硬件解码器,cb_type才会是FF_CODEC_CB_TYPE_RECEIVE_FRAME
	// 这里使用函数指针,是调用硬件的解码器
    if (codec->cb_type == FF_CODEC_CB_TYPE_RECEIVE_FRAME) {
        ret = codec->cb.receive_frame(avctx, frame);
        emms_c();
        if (!ret) {
            if (avctx->codec->type == AVMEDIA_TYPE_VIDEO)
                ret = (frame->flags & AV_FRAME_FLAG_DISCARD) ? AVERROR(EAGAIN) : 0;
            else if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {
                int64_t discarded_samples = 0;
                ret = discard_samples(avctx, frame, &discarded_samples);
            }
        }
    } else // 使用软件解码器
        ret = decode_simple_receive_frame(avctx, frame);

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

    /* preserve ret */
    // 检测colorspace,这里函数还没实现,默认返回为0
    ok = detect_colorspace(avctx, frame);
    if (ok < 0) {
        av_frame_unref(frame);
        return ok;
    }

    if (!ret) {
        if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
            if (!frame->width)
                frame->width = avctx->width;
            if (!frame->height)
                frame->height = avctx->height;
        } else
            frame->flags |= AV_FRAME_FLAG_KEY;

        ret = fill_frame_props(avctx, frame);
        if (ret < 0) {
            av_frame_unref(frame);
            return ret;
        }

#if FF_API_FRAME_KEY
FF_DISABLE_DEPRECATION_WARNINGS
        frame->key_frame = !!(frame->flags & AV_FRAME_FLAG_KEY); // 配置key_frame
FF_ENABLE_DEPRECATION_WARNINGS
#endif
#if FF_API_INTERLACED_FRAME
FF_DISABLE_DEPRECATION_WARNINGS
        frame->interlaced_frame = !!(frame->flags & AV_FRAME_FLAG_INTERLACED);
        frame->top_field_first =  !!(frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
        frame->best_effort_timestamp = guess_correct_pts(dc,
                                                         frame->pts,
                                                         frame->pkt_dts);

        /* the only case where decode data is not set should be decoders
         * that do not call ff_get_buffer() */
        // 不设置decode数据的唯一情况应该是解码器不调用ff_get_buffer()
        av_assert0((frame->private_ref && frame->private_ref->size == sizeof(FrameDecodeData)) ||
                   !(avctx->codec->capabilities & AV_CODEC_CAP_DR1));

        if (frame->private_ref) {
            FrameDecodeData *fdd = (FrameDecodeData*)frame->private_ref->data;

            if (fdd->post_process) {
                ret = fdd->post_process(avctx, frame);
                if (ret < 0) {
                    av_frame_unref(frame);
                    return ret;
                }
            }
        }
    }

    /* free the per-frame decode data */
    av_buffer_unref(&frame->private_ref);

    return ret;
}

1.1.1 软解入口(decode_simple_receive_frame)

c 复制代码
static int decode_simple_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
    int ret;
    int64_t discarded_samples = 0;

    while (!frame->buf[0]) {
        if (discarded_samples > avctx->max_samples)
            return AVERROR(EAGAIN);
        ret = decode_simple_internal(avctx, frame, &discarded_samples);
        if (ret < 0)
            return ret;
    }

    return 0;
}

函数最重要的是调用了decode_simple_internal,函数的定义如下

c 复制代码
/*
 * The core of the receive_frame_wrapper for the decoders implementing
 * the simple API. Certain decoders might consume partial packets without
 * returning any output, so this function needs to be called in a loop until it
 * returns EAGAIN.
 **/
// 用于实现简单API的解码器的receive_frame_wrapper的核心。某些解码器可能会消耗部分数据包而不返回任何输出
// 因此需要在循环中调用此函数,直到它返回EAGAIN
static inline int decode_simple_internal(AVCodecContext *avctx, AVFrame *frame, int64_t *discarded_samples)
{
    AVCodecInternal   *avci = avctx->internal;
    AVPacket     *const pkt = avci->in_pkt;
    const FFCodec *const codec = ffcodec(avctx->codec);
    int got_frame, consumed;
    int ret;
	// 如果pkt没有data,获取下一个pkt
    if (!pkt->data && !avci->draining) {
        av_packet_unref(pkt);
        ret = ff_decode_get_packet(avctx, pkt);
        if (ret < 0 && ret != AVERROR_EOF)
            return ret;
    }

    // Some codecs (at least wma lossless) will crash when feeding drain packets
    // after EOF was signaled.
    // 一些编解码器(至少是wma无损的)在EOF信号发出后输入漏包时会崩溃
    if (avci->draining_done)
        return AVERROR_EOF;

    if (!pkt->data &&
        !(avctx->codec->capabilities & AV_CODEC_CAP_DELAY ||
          avctx->active_thread_type & FF_THREAD_FRAME))
        return AVERROR_EOF;

    got_frame = 0;
	// 执行解码过程
    if (HAVE_THREADS && avctx->active_thread_type & FF_THREAD_FRAME) {
    	// 线程解码
        consumed = ff_thread_decode_frame(avctx, frame, &got_frame, pkt);
    } else {
   		// 根据使用的解码器进行解码
        consumed = codec->cb.decode(avctx, frame, &got_frame, pkt);

        if (!(codec->caps_internal & FF_CODEC_CAP_SETS_PKT_DTS))
            frame->pkt_dts = pkt->dts;
        if (avctx->codec->type == AVMEDIA_TYPE_VIDEO) {
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGS
            if(!avctx->has_b_frames)
                frame->pkt_pos = pkt->pos;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
        }
    }
    emms_c();

    if (avctx->codec->type == AVMEDIA_TYPE_VIDEO) {
        ret = (!got_frame || frame->flags & AV_FRAME_FLAG_DISCARD)
                          ? AVERROR(EAGAIN)
                          : 0;
    } else if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {
        ret =  !got_frame ? AVERROR(EAGAIN)
                          : discard_samples(avctx, frame, discarded_samples);
    }

    if (ret == AVERROR(EAGAIN))
        av_frame_unref(frame);

    // FF_CODEC_CB_TYPE_DECODE decoders must not return AVERROR EAGAIN
    // code later will add AVERROR(EAGAIN) to a pointer
    av_assert0(consumed != AVERROR(EAGAIN));
    if (consumed < 0)
        ret = consumed;
    if (consumed >= 0 && avctx->codec->type == AVMEDIA_TYPE_VIDEO)
        consumed = pkt->size;

    if (!ret)
        av_assert0(frame->buf[0]);
    if (ret == AVERROR(EAGAIN))
        ret = 0;

    /* do not stop draining when got_frame != 0 or ret < 0 */
    if (avci->draining && !got_frame) {
        if (ret < 0) {
            /* prevent infinite loop if a decoder wrongly always return error on draining */
            /* reasonable nb_errors_max = maximum b frames + thread count */
            int nb_errors_max = 20 + (HAVE_THREADS && avctx->active_thread_type & FF_THREAD_FRAME ?
                                avctx->thread_count : 1);

            if (decode_ctx(avci)->nb_draining_errors++ >= nb_errors_max) {
                av_log(avctx, AV_LOG_ERROR, "Too many errors when draining, this is a bug. "
                       "Stop draining and force EOF.\n");
                avci->draining_done = 1;
                ret = AVERROR_BUG;
            }
        } else {
            avci->draining_done = 1;
        }
    }

    if (consumed >= pkt->size || ret < 0) {
        av_packet_unref(pkt);
    } else {
        pkt->data                += consumed;
        pkt->size                -= consumed;
        pkt->pts                  = AV_NOPTS_VALUE;
        pkt->dts                  = AV_NOPTS_VALUE;
        if (!(codec->caps_internal & FF_CODEC_CAP_SETS_FRAME_PROPS)) {
#if FF_API_FRAME_PKT
            // See extract_packet_props() comment.
            avci->last_pkt_props->stream_index = avci->last_pkt_props->stream_index - consumed;
#endif
            avci->last_pkt_props->pts = AV_NOPTS_VALUE;
            avci->last_pkt_props->dts = AV_NOPTS_VALUE;
        }
    }

    return ret;
}

代码最核心的地方在于调用codec->cb.decode执行解码过程,在FFmpeg中内嵌了H264的解码器,以这个为例进行记录,会使用h264_decode_frame进行解码

c 复制代码
const FFCodec ff_h264_decoder = {
    .p.name                = "h264",
    CODEC_LONG_NAME("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
    .p.type                = AVMEDIA_TYPE_VIDEO,
    .p.id                  = AV_CODEC_ID_H264,
    .priv_data_size        = sizeof(H264Context),
    .init                  = h264_decode_init,
    .close                 = h264_decode_end,
    FF_CODEC_DECODE_CB(h264_decode_frame), // 使用h264_decode_frame进行解码
    .p.capabilities        = AV_CODEC_CAP_DR1 |
                             AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |
                             AV_CODEC_CAP_FRAME_THREADS,
    .hw_configs            = (const AVCodecHWConfigInternal *const []) {
							// ...
                               NULL
                           },
    .caps_internal         = FF_CODEC_CAP_EXPORTS_CROPPING |
                             FF_CODEC_CAP_ALLOCATE_PROGRESS | FF_CODEC_CAP_INIT_CLEANUP,
    .flush                 = h264_decode_flush,
    UPDATE_THREAD_CONTEXT(ff_h264_update_thread_context),
    UPDATE_THREAD_CONTEXT_FOR_USER(ff_h264_update_thread_context_for_user),
    .p.profiles            = NULL_IF_CONFIG_SMALL(ff_h264_profiles),
    .p.priv_class          = &h264_class,
};

h264_decode_frame的定义位于libavcodec\h264dec.c中,定义如下

c 复制代码
static int h264_decode_frame(AVCodecContext *avctx, AVFrame *pict,
                             int *got_frame, AVPacket *avpkt)
{
    const uint8_t *buf = avpkt->data;
    int buf_size       = avpkt->size;
    H264Context *h     = avctx->priv_data;
    int buf_index;
    int ret;

    h->flags = avctx->flags;
    h->setup_finished = 0;
    h->nb_slice_ctx_queued = 0;
	
    ff_h264_unref_picture(&h->last_pic_for_ec);

    /* end of stream, output what is still in the buffers */
    // 到达流的末尾,输出buffer当中现有的数据
    if (buf_size == 0)
        return send_next_delayed_frame(h, pict, got_frame, 0);
	// 解码side data
    if (av_packet_get_side_data(avpkt, AV_PKT_DATA_NEW_EXTRADATA, NULL)) {
        size_t side_size;
        uint8_t *side = av_packet_get_side_data(avpkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size);
        ff_h264_decode_extradata(side, side_size,
                                 &h->ps, &h->is_avc, &h->nal_length_size,
                                 avctx->err_recognition, avctx);
    }
    // 解码extra data
    if (h->is_avc && buf_size >= 9 && buf[0]==1 && buf[2]==0 && (buf[4]&0xFC)==0xFC) {
        if (is_avcc_extradata(buf, buf_size))
            return ff_h264_decode_extradata(buf, buf_size,
                                            &h->ps, &h->is_avc, &h->nal_length_size,
                                            avctx->err_recognition, avctx);
    }
	// 解码入口
    buf_index = decode_nal_units(h, buf, buf_size);
    if (buf_index < 0)
        return AVERROR_INVALIDDATA;
	// 到达文件末尾
    if (!h->cur_pic_ptr && h->nal_unit_type == H264_NAL_END_SEQUENCE) {
        av_assert0(buf_index <= buf_size);
        return send_next_delayed_frame(h, pict, got_frame, buf_index);
    }

    if (!(avctx->flags2 & AV_CODEC_FLAG2_CHUNKS) && (!h->cur_pic_ptr || !h->has_slice)) {
        if (avctx->skip_frame >= AVDISCARD_NONREF ||
            buf_size >= 4 && !memcmp("Q264", buf, 4))
            return buf_size;
        av_log(avctx, AV_LOG_ERROR, "no frame!\n");
        return AVERROR_INVALIDDATA;
    }

    if (!(avctx->flags2 & AV_CODEC_FLAG2_CHUNKS) ||
        (h->mb_y >= h->mb_height && h->mb_height)) {
        if ((ret = ff_h264_field_end(h, &h->slice_ctx[0], 0)) < 0)
            return ret;

        /* Wait for second field. */
        if (h->next_output_pic) {
            ret = finalize_frame(h, pict, h->next_output_pic, got_frame);
            if (ret < 0)
                return ret;
        }
    }

    av_assert0(pict->buf[0] || !*got_frame);

    ff_h264_unref_picture(&h->last_pic_for_ec);
	// 获取使用的比特数
    return get_consumed_bytes(buf_index, buf_size);
}

在上面的代码中,先检查是否需要解码side data和extra data,随后使用decode_nal_units进行数据包的解码,随后会进入H264格式的解码过程,这个过程链路比较长,就是另一个系列记录的内容了,这里不做进一步记录

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

相关推荐
大胆飞猪1 小时前
C++9--前置++和后置++重载,const,日期类的实现(对前几篇知识点的应用)
c++
因我你好久不见1 小时前
springboot java ffmpeg 视频压缩、提取视频帧图片、获取视频分辨率
java·spring boot·ffmpeg
1 9 J1 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法
夕泠爱吃糖1 小时前
C++中如何实现序列化和反序列化?
服务器·数据库·c++
长潇若雪1 小时前
《类和对象:基础原理全解析(上篇)》
开发语言·c++·经验分享·类和对象
仍然探索未知中2 小时前
C语言经典100例
c语言
爱吃西瓜的小菜鸡3 小时前
【C语言】矩阵乘法
c语言·学习·算法
染指11103 小时前
50.第二阶段x86游戏实战2-lua获取本地寻路,跨地图寻路和获取当前地图id
c++·windows·lua·游戏安全·反游戏外挂·游戏逆向·luastudio
Code out the future4 小时前
【C++——临时对象,const T&】
开发语言·c++
Stark、4 小时前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端