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

发布于:2024-05-18 ⋅ 阅读:(75) ⋅ 点赞:(0)

示例工程:
【FFmpeg】调用ffmpeg库实现264软编
【FFmpeg】调用ffmpeg库实现264软解
【FFmpeg】调用ffmpeg库进行RTMP推流和拉流
【FFmpeg】调用ffmpeg库进行SDL2解码后渲染
流程分析:
【FFmpeg】编码链路上主要函数的简单分析

1. 主要工作流程

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

解码器:

  1. 查找解码器(avcodec_find_decoder)
  2. 创建解码器上下文(avcodec_alloc_context3)
  3. 创建输入信息的结构体(av_packet_alloc)
  4. 创建输出信息的结构体(av_frame_alloc)
  5. 创建解析器(av_parser_init)
  6. 打开解码器(avcodec_open2)
  7. 将输入帧填充到packet当中(…)
  8. 解析每一个帧的头信息(av_parser_parse2)
  9. 将输入帧送入解码器进行解码(avcodec_send_packet)
  10. 获取解码器输出的已解码信息(avcodec_receive_frame)
  11. 释放结构体信息(…)

2. 解码过程

2.1 查找解码器(avcodec_find_decoder)

该函数的主要功能是根据AVCodecID查找解码器,并返回一个AVCodec*。这个过程与avcodec_find_encoder一致,只不过查找的codec_list是解码器的codec_list

const AVCodec *avcodec_find_decoder(enum AVCodecID id)
{
    return find_codec(id, av_codec_is_decoder);
}

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

与编码器一致

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

与编码器一致

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

与编码器一致

2.5 创建解析器(av_parser_init)

该函数用于创建一个解析器,该解析器能够解析码流文件的头信息。函数位于libavcodec/parser.c。主要流程分如下:

  1. 获取parser_list中的parser,查看是否与输入的codec_id匹配
  2. 找到对应的parser,进行初始化
  3. 根据特定的解码器进行初始化(parser_init)
AVCodecParserContext *av_parser_init(int codec_id)
{
    AVCodecParserContext *s = NULL;
    const AVCodecParser *parser;
    void *i = 0;
    int ret;

    if (codec_id == AV_CODEC_ID_NONE)
        return NULL;
	// ----- 1.获取parser_list中的parser,查看是否与输入的codec_id匹配 ----- //
    while ((parser = av_parser_iterate(&i))) {
        if (parser->codec_ids[0] == codec_id ||
            parser->codec_ids[1] == codec_id ||
            parser->codec_ids[2] == codec_id ||
            parser->codec_ids[3] == codec_id ||
            parser->codec_ids[4] == codec_id ||
            parser->codec_ids[5] == codec_id ||
            parser->codec_ids[6] == codec_id)
            goto found;
    }
    return NULL;
	// ----- 2.找到对应的parser,进行初始化 ----- //
found:
    s = av_mallocz(sizeof(AVCodecParserContext));
    if (!s)
        goto err_out;
    s->parser = parser;
    s->priv_data = av_mallocz(parser->priv_data_size);
    if (!s->priv_data)
        goto err_out;
    s->fetch_timestamp=1;
    s->pict_type = AV_PICTURE_TYPE_I;
    if (parser->parser_init) {
    	// ----- 3.解析器的初始化 ----- //
        ret = parser->parser_init(s);
        if (ret != 0)
            goto err_out;
    }
    s->key_frame            = -1;
    s->dts_sync_point       = INT_MIN;
    s->dts_ref_dts_delta    = INT_MIN;
    s->pts_dts_delta        = INT_MIN;
    s->format               = -1;

    return s;

err_out:
    if (s)
        av_freep(&s->priv_data);
    av_free(s);
    return NULL;
}

2.6 打开解码器(avcodec_open2)

与编码器一致

2.7 解析每一个帧的头信息(av_parser_parse2)

该函数用于解析帧的头部信息,主要工作流程为:

  1. 检查codec_id和offset等信息
  2. 使用特定的解析器进行解析(parser_parse)
  3. 更新offset,用以更新下一帧的头信息
// 解析数据获得一个Packet, 从输入的数据流中分离出一帧一帧的压缩编码数据
// poutbuf: 解析后输出的压缩编码数据帧
// buf: 解析前的压缩编码数据帧
// 如果函数执行完后输出数据为空(poutbuf_size为0),则代表解析还没有完成,还需要再次调用av_parser_parse2()解析一部分数据才可以得到解析后的数据帧。
// 当函数执行完后输出数据不为空的时候,代表解析完成,可以将poutbuf中的这帧数据取出来做后续处理
int av_parser_parse2(AVCodecParserContext *s, AVCodecContext *avctx,
                     uint8_t **poutbuf, int *poutbuf_size,
                     const uint8_t *buf, int buf_size,
                     int64_t pts, int64_t dts, int64_t pos)
{
    int index, i;
    uint8_t dummy_buf[AV_INPUT_BUFFER_PADDING_SIZE];
    // 检查当前的codec id是否不为空
    av_assert1(avctx->codec_id != AV_CODEC_ID_NONE);

    /* Parsers only work for the specified codec ids. */
    // ----- 1.检查当前的parser的codec类型 ----- //
    av_assert1(avctx->codec_id == s->parser->codec_ids[0] ||
               avctx->codec_id == s->parser->codec_ids[1] ||
               avctx->codec_id == s->parser->codec_ids[2] ||
               avctx->codec_id == s->parser->codec_ids[3] ||
               avctx->codec_id == s->parser->codec_ids[4] ||
               avctx->codec_id == s->parser->codec_ids[5] ||
               avctx->codec_id == s->parser->codec_ids[6]);
    /* 第一次进入时,flags为0,会进入if将offset设置成当前pkt的pos */
    if (!(s->flags & PARSER_FLAG_FETCHED_OFFSET)) {
        s->next_frame_offset =
        s->cur_offset        = pos;
        s->flags            |= PARSER_FLAG_FETCHED_OFFSET;
    }

    if (buf_size == 0) {
        /* padding is always necessary even if EOF, so we add it here */
        memset(dummy_buf, 0, sizeof(dummy_buf));
        buf = dummy_buf;
    } else if (s->cur_offset + buf_size != s->cur_frame_end[s->cur_frame_start_index]) { /* skip remainder packets */
        /* add a new packet descriptor */
        // 保留一下cur_offset, frame_end 信息, 有4个槽位供使用, 方便知道数据偏移量
        i = (s->cur_frame_start_index + 1) & (AV_PARSER_PTS_NB - 1);
        s->cur_frame_start_index = i;
        s->cur_frame_offset[i]   = s->cur_offset;
        s->cur_frame_end[i]      = s->cur_offset + buf_size;
        s->cur_frame_pts[i]      = pts;
        s->cur_frame_dts[i]      = dts;
        s->cur_frame_pos[i]      = pos;
    }

    if (s->fetch_timestamp) {
        s->fetch_timestamp = 0;
        s->last_pts        = s->pts;
        s->last_dts        = s->dts;
        s->last_pos        = s->pos;
        ff_fetch_timestamp(s, 0, 0, 0);
    }
    // ----- 2.使用特定的解析器进行解析 ----- //
    /* WARNING: the returned index can be negative */
    index = s->parser->parser_parse(s, avctx, (const uint8_t **) poutbuf,
                                    poutbuf_size, buf, buf_size);
    av_assert0(index > -0x20000000); // The API does not allow returning AVERROR codes
#define FILL(name) if(s->name > 0 && avctx->name <= 0) avctx->name = s->name
    if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
        FILL(field_order);
        FILL(coded_width);
        FILL(coded_height);
        FILL(width);
        FILL(height);
    }
	// ----- 3.更新offset,用以更新下一帧的头信息 ----- //
    /* update the file pointer */
    if (*poutbuf_size) {
        /* fill the data for the current frame */
        s->frame_offset = s->next_frame_offset;

        /* offset of the next frame */
        s->next_frame_offset = s->cur_offset + index;
        s->fetch_timestamp   = 1;
    } else {
        /* Don't return a pointer to dummy_buf. */
        *poutbuf = NULL;
    }
    if (index < 0)
        index = 0;
    s->cur_offset += index;
    return index;
}

2.8 将输入帧送入解码器进行解码(avcodec_send_packet)

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

  1. 解码主函数(avcodec_send_packet)
  2. 分接收类型的解码获取帧(decode_receive_frame_internal)
  3. 内部解码获取帧函数(decode_simple_receive_frame)
  4. 分线程的解码函数(decode_simple_internal)
  5. 核心解码函数(h264_decode_frame)

2.8.1 解码主函数(avcodec_send_packet)

该函数的功能是将输入的帧(码流)送入解码器进行解码,其主要流程如下:

  1. 检查参数
  2. 解码获取帧(decode_receive_frame_internal)
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;

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

    return 0;
}

2.8.2 分接收类型的解码获取帧(decode_receive_frame_internal)

如果cb_type类型为FF_CODEC_CB_TYPE_RECEIVE_FRAME,则调用receive_frame;否则调用decode_simple_receive_frame

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]);

    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 */
    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);
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() */
        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;
}

2.8.3 内部解码获取帧函数(decode_simple_receive_frame)

如果frame的buf不存在,说明需要进行解码,调用decode_simple_internal

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;
}

2.8.4 分线程的解码函数(decode_simple_internal)

从注解看,该函数是用于实现简单API的解码器的receive_frame_wrapper的核心。某些解码器可能会消耗部分数据包而不返回任何输出,因此需要在循环中调用此函数,直到它返回EAGAIN
函数的主要流程为:

  1. 如果解码数据包不存在,则寻找下一个要解码的数据包
  2. 执行解码操作,decode(单线程)和ff_thread_decode_frame(多线程)
  3. 清空输出队列
  4. 更新时间戳
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;
	// ----- 1.获取下一个要解码的数据包 ----- //
    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.
    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;
	// ----- 2.执行解码操作 ----- //
    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;
	// ----- 3.清空输出队列 ----- //
    /* 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;
        }
    }
	// ----- 4.更新时间戳 ----- //
    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;
}

这里核心的解码函数为decode,这是一个函数指针,假如使用的是h264解码器,则会调用ff_h264_decoder结构体内的解码器函数,即h264_decode_frame

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),
    .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 []) {
    // 下面是一些硬件解码器的使用
#if CONFIG_H264_DXVA2_HWACCEL
                               HWACCEL_DXVA2(h264),
#endif
#if CONFIG_H264_D3D11VA_HWACCEL
                               HWACCEL_D3D11VA(h264),
#endif
#if CONFIG_H264_D3D11VA2_HWACCEL
                               HWACCEL_D3D11VA2(h264),
#endif
#if CONFIG_H264_D3D12VA_HWACCEL
                               HWACCEL_D3D12VA(h264),
#endif
#if CONFIG_H264_NVDEC_HWACCEL
                               HWACCEL_NVDEC(h264),
#endif
#if CONFIG_H264_VAAPI_HWACCEL
                               HWACCEL_VAAPI(h264),
#endif
#if CONFIG_H264_VDPAU_HWACCEL
                               HWACCEL_VDPAU(h264),
#endif
#if CONFIG_H264_VIDEOTOOLBOX_HWACCEL
                               HWACCEL_VIDEOTOOLBOX(h264),
#endif
#if CONFIG_H264_VULKAN_HWACCEL
                               HWACCEL_VULKAN(h264),
#endif
                               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,
};

2.8.5 核心解码函数(h264_decode_frame)

以h264解码为例,会调用h264_decode_frame函数执行具体的解码操作。
主要的工作流程为:

  1. 读取到流的末尾,输出buffer中剩余的信息
  2. 检查是否需要解码额外的信息
  3. 解码nal信息(decode_nal_unit)
  4. 催促去获得下一个延时的帧,因为此时的帧不存在并且达到了文件的末尾
  5. 计算消耗的比特数

这个过程中,解码nal信息是最关键函数,直接解码数据包当中的信息。这是链路上的最底层函数,再向下走就是解码器内部的函数,将在其他文章中记录。从代码上看,在FFmpeg中,H264的软解是有具体实现的,但是H264的软编需要额外编译x264的库

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);
	// ----- 1.读取到流的末尾,输出buffer中剩余的信息 ----- //
    /* end of stream, output what is still in the buffers */
    if (buf_size == 0)
        return send_next_delayed_frame(h, pict, got_frame, 0);
	// ----- 2.检查是否需要解码额外的信息 ----- //
    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);
    }
    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);
    }
	// ----- 3.解码nal信息 ----- //
    buf_index = decode_nal_units(h, buf, buf_size);
    if (buf_index < 0)
        return AVERROR_INVALIDDATA;
	// ----- 4.催促去获得下一个延时的帧,因为此时的帧不存在并且达到了文件的末尾 ----- //
    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);
	// ----- 5.计算消耗的比特数 ----- //
    return get_consumed_bytes(buf_index, buf_size);
}

2.9 获取解码器输出的已解码信息(avcodec_receive_frame)

从注解看,该函数实现从解码器或编码器返回解码的输出数据(当使用@ref AV_CODEC_FLAG_RECON_FRAME标志时)
函数首先判断是否是解码器,如果是则进入解码流程(ff_decode_receive_frame),否则进入编码流程(ff_encode_receive_frame)

int attribute_align_arg avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
    av_frame_unref(frame);

    if (av_codec_is_decoder(avctx->codec))
        return ff_decode_receive_frame(avctx, frame);
    return ff_encode_receive_frame(avctx, frame);
}

这里获取解码帧(ff_decode_receive_frame),主要工作流程为:

  1. 检查buf后,获取解码帧(decode_receive_frame_internal),这里与送入解码器解码使用的函数相同,但区别是这里只取帧,而不进行解码
  2. 检查帧是否有效(frame_validate)
  3. 查看输出分辨率是否变化
int ff_decode_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;

    if (!avcodec_is_open(avctx) || !av_codec_is_decoder(avctx->codec))
        return AVERROR(EINVAL);
	// ----- 1.检查buf后,获取解码帧 ----- //
    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;
    }
	// ----- 2.检查帧是否有效 ----- //
    ret = frame_validate(avctx, frame);
    if (ret < 0)
        goto fail;

    if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
        ret = apply_cropping(avctx, frame);
        if (ret < 0)
            goto fail;
    }

    avctx->frame_num++;
	// ----- 3.查看输出分辨率是否变化 ----- //
#if FF_API_DROPCHANGED
    if (avctx->flags & AV_CODEC_FLAG_DROPCHANGED) {

        if (avctx->frame_num == 1) {
            avci->initial_format = frame->format;
            switch(avctx->codec_type) {
            case AVMEDIA_TYPE_VIDEO:
                avci->initial_width  = frame->width;
                avci->initial_height = frame->height;
                break;
            case AVMEDIA_TYPE_AUDIO:
                avci->initial_sample_rate = frame->sample_rate ? frame->sample_rate :
                                                                 avctx->sample_rate;
                ret = av_channel_layout_copy(&avci->initial_ch_layout, &frame->ch_layout);
                if (ret < 0)
                    goto fail;
                break;
            }
        }

        if (avctx->frame_num > 1) {
            int changed = avci->initial_format != frame->format;

            switch(avctx->codec_type) {
            case AVMEDIA_TYPE_VIDEO:
                changed |= avci->initial_width  != frame->width ||
                           avci->initial_height != frame->height;
                break;
            case AVMEDIA_TYPE_AUDIO:
                changed |= avci->initial_sample_rate    != frame->sample_rate ||
                           avci->initial_sample_rate    != avctx->sample_rate ||
                           av_channel_layout_compare(&avci->initial_ch_layout, &frame->ch_layout);
                break;
            }

            if (changed) {
                avci->changed_frames_dropped++;
                av_log(avctx, AV_LOG_INFO, "dropped changed frame #%"PRId64" pts %"PRId64
                                            " drop count: %d \n",
                                            avctx->frame_num, frame->pts,
                                            avci->changed_frames_dropped);
                ret = AVERROR_INPUT_CHANGED;
                goto fail;
            }
        }
    }
#endif
    return 0;
fail:
    av_frame_unref(frame);
    return ret;
}

3.小结

从视频编解码的角度上看,解码流程比编码流程简单,速度更快,但是需要进行额外的头信息分析。此外,对于264解码而言,FFmpeg内部有封装,无需额外的编译库,解码之后的信息,可以直接使用获取的AVPacket,调用SDL2进行播放,实现播放器的功能

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


网站公告

今日签到

点亮在社区的每一天
去签到