FFmpeg源码(三)解码前世今生——avcodec_decode_video2、avcodec_send_packet与avcodec_receive_frame

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/asd501823206/article/details/97013677

写在前面

本节主要讲AVPacket中的数据解码到AVFrame中的过程。


前置知识点

1.FFmpeg数据结构简介

AVFormatContext:封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息
AVInputFormat:每种封装格式对应一个该结构体
AVStream:视频文件每个视频(音频)流对应一个该结构体
AVCodecContext:编码器上下文结构体,保存了视频(音频)编解码相关信息
AVCodec:每种视频(音频)编解码器对应一个该结构体
AVPacket:存储一帧压缩编码数据
AVFrame:存储一帧解码后像素(采样)数据

2.AVCodecInternal

AVCodecContext中持有一个AVCodecInternal变量,保存了AVCodecContext的各种状态信息,详细如下:

/**
 * libavcodec->internal.h l:132
 *
 */
typedef struct AVCodecInternal {
    /**
     * parent AVCodecContext 是否为使用init()调用自己的
     *
     * 多线程使用 - 共享表和图片指针应仅从原始上下文中释放。
     */
    int is_copy;

    /**
     * 是否为帧线程分配进度。
     *
     * 如果编解码器使用ff_thread_await / report_progress(),则必须将其设置为1,
     * 然后将在ff_thread_get_buffer()中分配进度。 
     * 然后必须使用ff_thread_release_buffer()释放帧。
     *
     * 如果编解码器不需要调用进度函数(帧之间没有依赖关系),它应该将其保留为0.
     * 然后它可以直接解码到用户提供的帧(然后用户将使用av_frame_unref()释放它们 ),
     * 没有必要调用ff_thread_release_buffer()。
     */
    int allocate_progress;

    /**
     * 提交了少于所需样本的音频帧并用静音填充。 拒绝所有后续帧。
     */
    int last_audio_frame;

    AVFrame *to_free;

    FramePool *pool;

    void *thread_ctx;

    DecodeSimpleContext ds;
    DecodeFilterContext filter;

    /**
     * 从传递用于解码的最后一个数据包中提取的属性(时间戳+辅助数据)。
     */
    AVPacket *last_pkt_props;

    /**
     * 用于编码器存储其比特流的临时缓冲区
     */
    uint8_t *byte_buffer;
    unsigned int byte_buffer_size;

    void *frame_thread_encoder;

    /**
     * 在下一个解码帧开始时跳过的音频样本数
     */
    int skip_samples;

    /**
     * hwaccel-specific private data
     * 硬件加速专有的私有数据
     */
    void *hwaccel_priv_data;

    /**
     * 检查API使用情况:编解码器耗尽后,需要刷新才能恢复操作
     */
    int draining;

    /**
     * buffers for using new encode/decode API through legacy API
     * 缓冲区,用于通过旧API使用新的编码/解码API
     */
    AVPacket *buffer_pkt;
    int buffer_pkt_valid; // encoding: packet without data can be valid
    AVFrame *buffer_frame;
    int draining_done;
    
    /**
     * set to 1 when the caller is using the old decoding API 
     * 当调用者使用旧的解码API时,设置为1
     */
    int compat_decode;
    int compat_decode_warned;
    /**
     * this variable is set by the decoder internals to signal to the old
     * API compat wrappers the amount of data consumed from the last packet
     * 此变量由解码器内部设置,以向使用旧API的compat wrappers发送最后一个数据包中消耗的数据量
     */
    size_t compat_decode_consumed;
    /**
     * when a partial packet has been consumed, this stores the remaining size
     * of the packet (that should be submitted in the next decode call
     * 当消耗了部分数据包时,它会存储数据包的剩余大小(应该在下一个解码调用中提交)
     */
    size_t compat_decode_partial_size;
    AVFrame *compat_decode_frame;

    int showed_multi_packet_warning;

    int skip_samples_multiplier;

    /**
     * to prevent infinite loop on errors when draining
     * 清空缓存数据时(draining)防止出现无限循环错误
     */
    int nb_draining_errors;
} AVCodecInternal;
typedef struct DecodeFilterContext {
    AVBSFContext **bsfs;
    int         nb_bsfs;
} DecodeFilterContext;
3.AVCodec

编解码器的结构体,详细如下:

/**
 * AVCodec.
 */
typedef struct AVCodec {
    /**
     * Name of the codec implementation.
     * The name is globally unique among encoders and among decoders (but an
     * encoder and a decoder can share the same name).
     * This is the primary way to find a codec from the user perspective.
     * 编解码器实现的名称。
     * 该名称在编码器和解码器之间是全局唯一的(但编码器和解码器可以共享相同的名称)。
     * 这是从用户角度查找编解码器的主要方法。
     */
    const char *name;
    /**
     * Descriptive name for the codec, meant to be more human readable than name.
     * You should use the NULL_IF_CONFIG_SMALL() macro to define it.
     * 编解码器的描述性名称,意味着比名称更易于阅读。
     * 应该使用NULL_IF_CONFIG_SMALL()宏来定义它。
     */
    const char *long_name;
    enum AVMediaType type;
    enum AVCodecID id;
    /**
     * Codec capabilities.
     * 编解码器功能。
     *
     * see AV_CODEC_CAP_*
     */
    int capabilities;
    const AVRational *supported_framerates; ///< array of supported framerates, or NULL if any, array is terminated by {0,0}
    const enum AVPixelFormat *pix_fmts;     ///< array of supported pixel formats, or NULL if unknown, array is terminated by -1
    const int *supported_samplerates;       ///< array of supported audio samplerates, or NULL if unknown, array is terminated by 0
    const enum AVSampleFormat *sample_fmts; ///< array of supported sample formats, or NULL if unknown, array is terminated by -1
    const uint64_t *channel_layouts;         ///< array of support channel layouts, or NULL if unknown. array is terminated by 0
    uint8_t max_lowres;                     ///< maximum value for lowres supported by the decoder
    const AVClass *priv_class;              ///< AVClass for the private context
    const AVProfile *profiles;              ///< array of recognized profiles, or NULL if unknown, array is terminated by {FF_PROFILE_UNKNOWN}

    /*****************************************************************
     * No fields below this line are part of the public API. They
     * may not be used outside of libavcodec and can be changed and
     * removed at will.
     * New public fields should be added right above.
     *****************************************************************
     */
    int priv_data_size;
    struct AVCodec *next;
    /**
     * @name Frame-level threading support functions 帧级线程支持功能
     * @{
     */
    /**
     * If defined, called on thread contexts when they are created.
     * If the codec allocates writable tables in init(), re-allocate them here.
     * priv_data will be set to a copy of the original.
     * 如果已定义,则在创建时调用线程上下文。
     * 如果编解码器在init()中分配可写表,请在此处重新分配它们。
     * priv_data将设置为原始副本。
     */
    int (*init_thread_copy)(AVCodecContext *);
    /**
     * Copy necessary context variables from a previous thread context to the current one.
     * If not defined, the next thread will start automatically; otherwise, the codec
     * must call ff_thread_finish_setup().
     * 将必要的上下文变量从先前的线程上下文复制到当前的线程上下
     * 如果未定义,则下一个线程将自动启动; 否则,编解码器必须调用ff_thread_finish_setup()。
     *
     * dst and src will (rarely) point to the same context, in which case memcpy should be skipped.
     * dst和src将(很少)指向相同的上下文,在这种情况下应该跳过memcpy。
     */
    int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src);
    /** @} */

    /**
     * Private codec-specific defaults.
     */
    const AVCodecDefault *defaults;

    /**
     * Initialize codec static data, called from avcodec_register().
     */
    void (*init_static_data)(struct AVCodec *codec);

    int (*init)(AVCodecContext *);
    int (*encode_sub)(AVCodecContext *, uint8_t *buf, int buf_size,
                      const struct AVSubtitle *sub);
    /**
     * Encode data to an AVPacket.
     *
     * @param      avctx          codec context
     * @param      avpkt          output AVPacket (may contain a user-provided buffer)
     * @param[in]  frame          AVFrame containing the raw data to be encoded
     * @param[out] got_packet_ptr encoder sets to 0 or 1 to indicate that a
     *                            non-empty packet was returned in avpkt.
     * @return 0 on success, negative error code on failure
     */
    int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame,
                   int *got_packet_ptr);
    int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt);
    int (*close)(AVCodecContext *);
    /**
     * Encode API with decoupled packet/frame dataflow. The API is the
     * same as the avcodec_ prefixed APIs (avcodec_send_frame() etc.), except
     * that:
     * - never called if the codec is closed or the wrong type,
     * - if AV_CODEC_CAP_DELAY is not set, drain frames are never sent,
     * - only one drain frame is ever passed down,
     */
    int (*send_frame)(AVCodecContext *avctx, const AVFrame *frame);
    int (*receive_packet)(AVCodecContext *avctx, AVPacket *avpkt);

    /**
     * Decode API with decoupled packet/frame dataflow. This function is called
     * to get one output frame. It should call ff_decode_get_packet() to obtain
     * input data.
     */
    int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame);
    /**
     * Flush buffers.
     * Will be called when seeking
     */
    void (*flush)(AVCodecContext *);
    /**
     * Internal codec capabilities.
     * See FF_CODEC_CAP_* in internal.h
     */
    int caps_internal;

    /**
     * Decoding only, a comma-separated list of bitstream filters to apply to
     * packets before decoding.
     */
    const char *bsfs;
} AVCodec;

源码分析

由于作者也在学习阶段,本系列文章的规划路线也是参照了雷神总结的视频播放流程图。而本篇文章的起点也就是avcodec_decode_video2。所以,让我们从avcodec_decode_video2开始一步一步分析吧~

首先来看一下avcodec_decode_video2的注释和实现:

avcodec_decode_video2
/**
 * 将大小为avpkt-> size的视频帧从avpkt-> data解码为picture.
 * 一些解码器可以在单个AVPacket中支持多个帧,这样解码器就可以解码第一帧。
 *
 * @warning 输入缓冲区大小必须大于实际读取比特数(AV_INPUT_BUFFER_PADDING_SIZE),
 * 		     因为一些优化过的比特流读取器一次可以读取32bits或64bits,可能会超过边界
 *
 * @warning 输入缓冲区buf的结尾应设置为0,以确保对损坏的MPEG流不会发生过度读取。
 *
 * @note 具有AV_CODEC_CAP_DELAY功能集的编解码器在输入和输出之间具有延迟,
 * 	       读取一帧后需要将avpkt-> data = NULL,avpkt-> size = 0,来返回剩余的帧。
 *
 * @note 在将数据包馈送到解码器之前,必须使用@ref avcodec_open2()打开AVCodecContext。
 *
 * @param avctx the codec context
 * @param[out] picture 将存储解码视频帧的AVFrame。
 *             使用av_frame_alloc()获取AVFrame。
 *             编解码器将通过调用AVCodecContext.get_buffer2()回调为实际位图分配内存。
 *
 *             当AVCodecContext.refcounted_frames设置为1时,帧被引用计数,返回的引用属于调用者。
 *             当不再需要帧时,调用者必须使用av_frame_unref()释放帧。
 *             如果av_frame_is_writable()返回1,则调用者可以安全地写入帧。
 *
 *             当AVCodecContext.refcounted_frames设置为0时,返回的引用属于解码器,仅在下次调用此函数或关闭/刷新解码器之前有效。
 *             调用这可能不会写入数据。
 *
 * @param[in] avpkt 输入AVPacket包含了输入缓冲区。
 *            你可以使用av_init_packet()创建此类数据包,然后设置数据和大小,
 *            某些解码器可能还需要其他字段,如flags和AV_PKT_FLAG_KEY。
 *            所有解码器都设计为使用尽可能少的字段。
 *
 * @param[in,out] got_picture_ptr 如果没有帧可以解压缩则为零,否则为非零。
 *
 * @return 出错时,返回负值,否则使用的字节数,如果没有帧可以解压缩,则返回零。
 *
 * @deprecated Use avcodec_send_packet() and avcodec_receive_frame().
 */
attribute_deprecated
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
                         int *got_picture_ptr,
                         const AVPacket *avpkt);
                         
int attribute_align_arg avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
                                              int *got_picture_ptr,
                                              const AVPacket *avpkt)
{
    return compat_decode(avctx, picture, got_picture_ptr, avpkt);
}

compat_decode
static int compat_decode(AVCodecContext *avctx, AVFrame *frame,
                         int *got_frame, const AVPacket *pkt)
{
    // 取出AVCodecInternal结构体
    AVCodecInternal *avci = avctx->internal;
    int ret = 0;

    // compat_decode_consumed:此变量由解码器内部设置,以向使用旧API的compat wrappers发送最后一个数据包中消耗的数据量
    av_assert0(avci->compat_decode_consumed == 0);

    // draining_done:缓冲数据是否耗尽
    if (avci->draining_done && pkt && pkt->size != 0) {
        av_log(avctx, AV_LOG_WARNING, "Got unexpected packet after EOF\n");
        avcodec_flush_buffers(avctx);
    }

    // 标志:是否获取到frame
    *got_frame = 0;

    // 当调用者使用旧的解码API时,设置为1
    avci->compat_decode = 1;

    // compat_decode_partial_size:当消耗了部分数据包时,它会存储数据包的剩余大小(应该在下一个解码调用中提交)
    if (avci->compat_decode_partial_size > 0 &&
        avci->compat_decode_partial_size != pkt->size) {
        av_log(avctx, AV_LOG_ERROR,
               "Got unexpected packet size after a partial decode\n");
        ret = AVERROR(EINVAL);
        goto finish;
    }

    if (!avci->compat_decode_partial_size) {
        // 调用新API:avcodec_send_packet -> 提供原始packet->data数据作为解码器的输入。
        // 具体返回值的含义,详见文中avcodec_send_packet()函数的讲解
        ret = avcodec_send_packet(avctx, pkt);
        if (ret == AVERROR_EOF)
            ret = 0;
        else if (ret == AVERROR(EAGAIN)) {
            /* we fully drain all the output in each decode call, so this should not
             * ever happen */
            ret = AVERROR_BUG;
            goto finish;
        } else if (ret < 0)
            goto finish;
    }

    while (ret >= 0) {
        // 调用新API:avcodec_receive_frame -> 从解码器返回解码的输出数据。
        // 具体返回值的含义,详见文中avcodec_receive_frame()函数的讲解
        ret = avcodec_receive_frame(avctx, frame);
        if (ret < 0) {
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                ret = 0;
            goto finish;
        }

        if (frame != avci->compat_decode_frame) {
            // frame为引用计数的帧
            if (!avctx->refcounted_frames) {
                ret = unrefcount_frame(avci, frame);
                if (ret < 0)
                    goto finish;
            }

            *got_frame = 1;
            frame = avci->compat_decode_frame;
        } else {
            if (!avci->compat_decode_warned) {
                av_log(avctx, AV_LOG_WARNING, "The deprecated avcodec_decode_* "
                       "API cannot return all the frames for this decoder. "
                       "Some frames will be dropped. Update your code to the "
                       "new decoding API to fix this.\n");
                avci->compat_decode_warned = 1;
            }
        }

        // codec->bsfs:仅解码,在解码之前应用于数据包的,使用逗号分隔的,比特流过滤器列表。
        // 解码数据耗尽 或 (bsfs列表不为空 且 已消耗数据小于packet的数据)
        if (avci->draining || (!avctx->codec->bsfs && avci->compat_decode_consumed < pkt->size))
            break;
    }

finish:
    if (ret == 0) {
        /**
         * if there are any bsfs then assume full packet is always consumed 
         * 如果有任何bsfs,则假设始终消耗完整数据包
         */
        if (avctx->codec->bsfs)
            ret = pkt->size;
        else
            ret = FFMIN(avci->compat_decode_consumed, pkt->size);
    }
    avci->compat_decode_consumed = 0;
    avci->compat_decode_partial_size = (ret >= 0) ? pkt->size - ret : 0;

    return ret;
}

在这里我们发现,avcodec_decode_video2这个函数已经被废弃掉了,新的函数为avcodec_send_packet() 和 avcodec_receive_frame(),而新的avcodec_decode_video2也是通过调用这两个函数来实现的。那么接下来我们来看看这两个函数

avcodec_send_packet
/**
 * 提供原始packet->data数据作为解码器的输入。
 *
 * 在内部,此调用将复制相关的AVCodecContext字段,这些字段可影响每个数据包的解码,并在数据包实际解码时应用它们。
 * (例如AVCodecContext.skip_frame,它可能指示解码器丢弃使用此函数发送的数据包所包含的帧。)
 *
 * @warning 输入缓冲区大小必须大于实际读取比特数(AV_INPUT_BUFFER_PADDING_SIZE),
 *          因为一些优化过的比特流读取器一次可以读取32bits或64bits,可能会超过边界
 *
 * @warning 请勿在同一AVCodecContext上将此API与旧API(如avcodec_decode_video2())混合使用。
 *          它将在现在或未来的libavcodec版本中返回意外结果。
 *
 * @note 在将数据包馈送到解码器之前,必须使用@ref avcodec_open2()打开AVCodecContext。
 *
 * @param avctx codec context
 * @param[in] avpkt 输入AVPacket。 通常,这将是单个视频帧,或几个完整的音频帧。
 *                  数据包的所有权保留在调用者处,解码器不会写入数据包。
 *                  解码器可以创建对分组数据的引用(或者没有被分组技术,则复制它)。
 *
 *                  与较旧的API不同,数据包总是被完全消耗,如果它包含多个帧(例如一些音频编解码器),
 *                  则需要在发送新数据包之前多次调用avcodec_receive_frame()。
 *
 *                  它可以是NULL(或者数据设置为NULL且大小设置为0的AVPacket);
 *                  在这种情况下,它被认为是一个刷新数据包,它向流的结束发出信号。
 *                  发送第一个flush数据包将返回成功。 后续的是不必要的,将返回AVERROR_EOF。
 *                  如果解码器仍有缓冲帧,则在发送刷新数据包后将返回它们。
 *
 * @return 0 on success, otherwise negative error code:
 *      AVERROR(EAGAIN):   在当前状态下不接受输入 - 用户必须使用avcodec_receive_frame()读取输出
 *                         (读取所有输出后,应重新发送数据包,EAGAIN下调用不会失败)。
 *
 *      AVERROR_EOF:       已刷新解码器,并且不会向其发送新数据包(如果发送的数据包超过1个,也会返回)
 *
 *      AVERROR(EINVAL):   1.编解码器未打开。
 *                         2.它是编码器
 *                         3.需要刷新
 *
 *      AVERROR(ENOMEM):   无法将数据包添加到内部队列或类似的其他错误: legitimate decoding errors
 */
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
int attribute_align_arg avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
{
    // 取出AVCodecInternal结构体
    AVCodecInternal *avci = avctx->internal;
    int ret;

    // avcodec_is_open(avctx):如果avctx打开则返回正值(即,在没有相应的avcodec_close()的情况下调用avcodec_open2()),否则返回0
    // av_codec_is_decoder(avctx->codec):如果avctx->codec是解码器,则返回非零数字,否则返回零
    if (!avcodec_is_open(avctx) || !av_codec_is_decoder(avctx->codec))
        return AVERROR(EINVAL);

    // 检查API使用情况:编解码器耗尽后,需要刷新才能恢复操作
    if (avctx->internal->draining)
        return AVERROR_EOF;

    // packet中是否有数据
    if (avpkt && !avpkt->size && avpkt->data)
        return AVERROR(EINVAL);

    // 初始化avci的DecodeFilterContext
    ret = bsfs_init(avctx);
    if (ret < 0)
        return ret;

    av_packet_unref(avci->buffer_pkt);
    // 绑定avpkt中的数据
    if (avpkt && (avpkt->data || avpkt->side_data_elems)) {
        ret = av_packet_ref(avci->buffer_pkt, avpkt);
        if (ret < 0)
            return ret;
    }

    // core:将AVPacket中的数据发送给avci->filter.bsfs[0]
    ret = av_bsf_send_packet(avci->filter.bsfs[0], avci->buffer_pkt);
    if (ret < 0) {
        av_packet_unref(avci->buffer_pkt);
        return ret;
    }

    if (!avci->buffer_frame->buf[0]) {
        // 解码
        ret = decode_receive_frame_internal(avctx, avci->buffer_frame);
        if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
            return ret;
    }

    return 0;
}

代码中我们可以看到,通过av_bsf_send_packet发送数据,通过decode_receive_frame_internal解码数据,接下来看这两个函数

av_bsf_send_packet
/**
 * libavcodec->bsf.c
 *
 * 调用av_packet_move_ref
 */
int av_bsf_send_packet(AVBSFContext *ctx, AVPacket *pkt)
{
    if (!pkt || (!pkt->data && !pkt->side_data_elems)) {
        ctx->internal->eof = 1;
        return 0;
    }

    if (ctx->internal->eof) {
        av_log(ctx, AV_LOG_ERROR, "A non-NULL packet sent after an EOF.\n");
        return AVERROR(EINVAL);
    }

    if (ctx->internal->buffer_pkt->data ||
        ctx->internal->buffer_pkt->side_data_elems)
        return AVERROR(EAGAIN);

    // 将pkt中的data复制到ctx->internal->buffer_pkt中,并清空pkt
    av_packet_move_ref(ctx->internal->buffer_pkt, pkt);

    return 0;
}
av_packet_move_ref
/**
 * Move every field in src to dst and reset src.
 * 将src中的每个字段移动到dst并重置src。
 *
 * @see av_packet_unref
 *
 * @param src Source packet, will be reset
 * @param dst Destination packet
 */
void av_packet_move_ref(AVPacket *dst, AVPacket *src);

void av_packet_move_ref(AVPacket *dst, AVPacket *src)
{
    *dst = *src;
    av_init_packet(src);
    src->data = NULL;
    src->size = 0;
}

这里完成了将AVPacket中的数据放入了指定的AVCodecContext->AVCodecInternal->DecodeFilterContext->AVBSFContext->AVPacket中

decode_receive_frame_internal
static int decode_receive_frame_internal(AVCodecContext *avctx, AVFrame *frame)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;

    av_assert0(!frame->buf[0]);
    // 如果指定了解码方式,则使用codec->receive_frame;否则使用decode_simple_receive_frame
    if (avctx->codec->receive_frame)
        ret = avctx->codec->receive_frame(avctx, frame);
    else
        ret = decode_simple_receive_frame(avctx, frame);

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

    return ret;
}

解码数据时,根据是否指定了解码方式,分为调用receive_frame和调用decode_simple_receive_frame两种方式:

1)receive_frame
/**
 * Decode API with decoupled packet/frame dataflow. This function is called
 * to get one output frame. It should call ff_decode_get_packet() to obtain
 * input data.
 * 使用解耦的数据包/帧数据流解码API,调用此函数以获取一个输出帧。
 * 它应该调用ff_decode_get_packet()来获取输入数据。
 */
 int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame);
 
// 以crystalhd.c为例
static inline CopyRet receive_frame(AVCodecContext *avctx,
                                    AVFrame *frame, int *got_frame)
{
    BC_STATUS ret;
    BC_DTS_PROC_OUT output = {
        .PicInfo.width  = avctx->width,
        .PicInfo.height = avctx->height,
    };
    CHDContext *priv = avctx->priv_data;
    HANDLE dev       = priv->dev;

    *got_frame = 0;

    // Request decoded data from the driver
    ret = DtsProcOutputNoCopy(dev, OUTPUT_PROC_TIMEOUT, &output);
    if (ret == BC_STS_FMT_CHANGE) {
        av_log(avctx, AV_LOG_VERBOSE, "CrystalHD: Initial format change\n");
        avctx->width  = output.PicInfo.width;
        avctx->height = output.PicInfo.height;
        switch ( output.PicInfo.aspect_ratio ) {
        case vdecAspectRatioSquare:
            avctx->sample_aspect_ratio = (AVRational) {  1,  1};
            break;
        ...
        }
        return RET_COPY_AGAIN;
    } else if (ret == BC_STS_SUCCESS) {
        int copy_ret = -1;
        if (output.PoutFlags & BC_POUT_FLAGS_PIB_VALID) {
            print_frame_info(priv, &output);

            copy_ret = copy_frame(avctx, &output, frame, got_frame);
        } else {
            /*
             * An invalid frame has been consumed.
             */
            av_log(avctx, AV_LOG_ERROR, "CrystalHD: ProcOutput succeeded with "
                                        "invalid PIB\n");
            copy_ret = RET_COPY_AGAIN;
        }
        DtsReleaseOutputBuffs(dev, NULL, FALSE);

        return copy_ret;
    } else if (ret == BC_STS_BUSY) {
        return RET_COPY_AGAIN;
    } else {
        av_log(avctx, AV_LOG_ERROR, "CrystalHD: ProcOutput failed %d\n", ret);
        return RET_ERROR;
    }
}
2)decode_simple_receive_frame

decode_simple_receive_frame中直接调用了decode_simple_internal

static int decode_simple_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
    int ret;

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

    return 0;
}
/*
 * 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.
 * receive_frame_wrapper的核心,用于实现简单API的解码器。
 * 某些解码器可能会消耗部分数据包而不返回任何输出,因此需要在循环中调用此函数,直到它返回EAGAIN。
 **/
static int decode_simple_internal(AVCodecContext *avctx, AVFrame *frame)
{
    AVCodecInternal   *avci = avctx->internal;
    DecodeSimpleContext *ds = &avci->ds;
    AVPacket           *pkt = ds->in_pkt;
    // copy to ensure we do not change pkt
    AVPacket tmp;
    int got_frame, actual_got_frame, did_split;
    int ret;

    if (!pkt->data && !avci->draining) {
        av_packet_unref(pkt);
        // 获取av_bsf_send_packet时复制的AVPacket数据
        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.
    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;

    tmp = *pkt;

    got_frame = 0;

    if (HAVE_THREADS && avctx->active_thread_type & FF_THREAD_FRAME) {
        /**
         * Submit a new frame to a decoding thread.
         * Returns the next available frame in picture. *got_picture_ptr
         * will be 0 if none is available.
         * The return value on success is the size of the consumed packet for
         * compatibility with avcodec_decode_video2(). This means the decoder
         * has to consume the full packet.
         *
         * 将新帧提交到解码线程。返回图片中的下一个可用帧。 如果没有,*got_picture_ptr将为0。
         * 成功的返回值是与avcodec_decode_video2()兼容的消耗数据包的大小,这意味着解码器必须使用完整的数据包。
         *
         * Parameters are the same as avcodec_decode_video2().
         */ 
        ret = ff_thread_decode_frame(avctx, frame, &got_frame, &tmp);
    } else {
        // 调用AVCodec的decode
        ret = avctx->codec->decode(avctx, frame, &got_frame, &tmp);
        
        ...(internal、pkt_pos、sample_aspect_ratio、width、height、pix_fmt等)
        
    }
    emms_c();
    actual_got_frame = got_frame;

    if (avctx->codec->type == AVMEDIA_TYPE_VIDEO) {
        // Video
        if (frame->flags & AV_FRAME_FLAG_DISCARD)
            got_frame = 0;
        if (got_frame)
            frame->best_effort_timestamp = guess_correct_pts(avctx,
                                                             frame->pts,
                                                             frame->pkt_dts);
    } else if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {
        // Audio
        uint8_t *side;
        int side_size;
        uint32_t discard_padding = 0;
        uint8_t skip_reason = 0;
        uint8_t discard_reason = 0;

        ... (sample、sample_rate、channels、额外数据等)
    }
    
    ...

    if (!got_frame)
        av_frame_unref(frame);

    if (ret >= 0 && avctx->codec->type == AVMEDIA_TYPE_VIDEO && !(avctx->flags & AV_CODEC_FLAG_TRUNCATED))
        ret = pkt->size;

    /* do not stop draining when actual_got_frame != 0 or ret < 0 */
    /* got_frame == 0 but actual_got_frame != 0 when frame is discarded */
    if (avctx->internal->draining && !actual_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 (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;
        }
    }

    avci->compat_decode_consumed += ret;

    // 数据draining,取消引用;数据未用完,更新参数
    if (ret >= pkt->size || ret < 0) {
        av_packet_unref(pkt);
    } else {
        int consumed = ret;

        pkt->data                += consumed;
        pkt->size                -= consumed;
        avci->last_pkt_props->size -= consumed; // See extract_packet_props() comment.
        pkt->pts                  = AV_NOPTS_VALUE;
        pkt->dts                  = AV_NOPTS_VALUE;
        avci->last_pkt_props->pts = AV_NOPTS_VALUE;
        avci->last_pkt_props->dts = AV_NOPTS_VALUE;
    }

    if (got_frame)
        av_assert0(frame->buf[0]);

    return ret < 0 ? ret : 0;
}

解码时调用了AVCodec的decode函数,以AYUV为例

#if CONFIG_AYUV_DECODER
AVCodec ff_ayuv_decoder = {
    .name         = "ayuv",
    .long_name    = NULL_IF_CONFIG_SMALL("Uncompressed packed MS 4:4:4:4"),
    .type         = AVMEDIA_TYPE_VIDEO,
    .id           = AV_CODEC_ID_AYUV,
    .init         = v408_decode_init,
    .decode       = v408_decode_frame,
    .capabilities = AV_CODEC_CAP_DR1,
    .caps_internal  = FF_CODEC_CAP_INIT_THREADSAFE,
};

static int v408_decode_frame(AVCodecContext *avctx, void *data,
                             int *got_frame, AVPacket *avpkt)
{
    AVFrame *pic = data;
    const uint8_t *src = avpkt->data;
    uint8_t *y, *u, *v, *a;
    int i, j, ret;

    if (avpkt->size < 4 * avctx->height * avctx->width) {
        av_log(avctx, AV_LOG_ERROR, "Insufficient input data.\n");
        return AVERROR(EINVAL);
    }

    if ((ret = ff_get_buffer(avctx, pic, 0)) < 0)
        return ret;

    pic->key_frame = 1;
    pic->pict_type = AV_PICTURE_TYPE_I;

    y = pic->data[0];
    u = pic->data[1];
    v = pic->data[2];
    a = pic->data[3];

    for (i = 0; i < avctx->height; i++) {
        for (j = 0; j < avctx->width; j++) {
            if (avctx->codec_id==AV_CODEC_ID_AYUV) {
                v[j] = *src++;
                u[j] = *src++;
                y[j] = *src++;
                a[j] = *src++;
            } else {
                u[j] = *src++;
                y[j] = *src++;
                v[j] = *src++;
                a[j] = *src++;
            }
        }

        y += pic->linesize[0];
        u += pic->linesize[1];
        v += pic->linesize[2];
        a += pic->linesize[3];
    }

    *got_frame = 1;

    return avpkt->size;
}

send的过程就是以上这些,接下来我们看一下receive的过程:

avcodec_receive_frame
/**
 * 从解码器返回解码的输出数据。
 *
 * @param avctx codec context
 * @param frame frame将被设置为参考计数的视频或音频帧(取决于解码器类型),具体由解码器分配。
 *              请注意,在执行任何其他操作之前,该函数都需调用av_frame_unref(frame)。
 *
 * @return
 *      0:                 success, a frame was returned
 *
 *      AVERROR(EAGAIN):   输出在此状态下不可用 - 用户必须尝试发送新输入
 *
 *      AVERROR_EOF:       已刷新解码器,并且不会向其发送新数据包
 *
 *      AVERROR(EINVAL):   编解码器未打开,或者是编码器
 *
 *      其他错误: legitimate decoding errors
 */
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
int attribute_align_arg avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
    // 取出AVCodecInternal结构体
    AVCodecInternal *avci = avctx->internal;
    int ret;

    // 取消引用帧引用的所有缓冲区并重置帧字段。
    av_frame_unref(frame);

    // avcodec_is_open(avctx):如果avctx打开则返回正值(即,在没有相应的avcodec_close()的情况下调用avcodec_open2()),否则返回0
    // av_codec_is_decoder(avctx->codec):如果avctx->codec是解码器,则返回非零数字,否则返回零
    if (!avcodec_is_open(avctx) || !av_codec_is_decoder(avctx->codec))
        return AVERROR(EINVAL);

    // 初始化avci的DecodeFilterContext
    ret = bsfs_init(avctx);
    if (ret < 0)
        return ret;

    // 与avcodec_send_packet逻辑类似:如果存在可以已解码的数据,则直接取出使用;
    // 否则调用decode_receive_frame_internal自行解码
    if (avci->buffer_frame->buf[0]) {
        av_frame_move_ref(frame, avci->buffer_frame);
    } else {
        ret = decode_receive_frame_internal(avctx, frame);
        if (ret < 0)
            return ret;
    }

    // 裁剪
    if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
        ret = apply_cropping(avctx, frame);
        if (ret < 0) {
            av_frame_unref(frame);
            return ret;
        }
    }

    avctx->frame_number++;

    return 0;
}

总结

旧API:avcodec_decode_video2
新API:avcodec_send_packet与avcodec_receive_frame

avcodec_decode_video2:新版本中的实现也是使用新的API来完成

avcodec_send_packet:通过av_bsf_send_packet发送数据,通过decode_receive_frame_internal解码数据,将AVPacket中的数据放入了指定的AVCodecContext->AVCodecInternal->DecodeFilterContext->AVBSFContext->AVPacket中;解码数据时,根据是否指定了解码方式,分为调用receive_frame和调用decode_simple_receive_frame,解码时调用了AVCodec的decode函数

avcodec_receive_frame:如果存在可以已解码的数据,则直接取出使用,否则调用decode_receive_frame_internal自行解码。decode_receive_frame_internal的解码流程同上。

猜你喜欢

转载自blog.csdn.net/asd501823206/article/details/97013677