ffmpeg 之avcodec_receive_frame分析二

直接看代码,我们在一中分析到,在send的时候最后一步就是将gpu中数据拷贝出来(这个过程是异步的),然后在这里读取


int attribute_align_arg avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{
    AVCodecInternal *avci = avctx->internal;
    int ret, changed;

    av_frame_unref(frame);

    if (!avcodec_is_open(avctx) || !av_codec_is_decoder(avctx->codec))
        return AVERROR(EINVAL);

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

    if (avctx->flags & AV_CODEC_FLAG_DROPCHANGED) {

        if (avctx->frame_number == 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;
                avci->initial_channels       = frame->channels;
                avci->initial_channel_layout = frame->channel_layout;
                break;
            }
        }

        if (avctx->frame_number > 1) {
            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 ||
                           avci->initial_channels       != frame->channels ||
                           avci->initial_channel_layout != frame->channel_layout;
                break;
            }

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



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

    av_assert0(!frame->buf[0]);

    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;

    if (!ret) {
        /* 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;
}

### 使用 `avcodec_send_packet` 和 `avcodec_receive_frame` 在 FFmpeg 库中,`avcodec_send_packet` 函数用于向解码器发送编码后的数据包以便处理。而 `avcodec_receive_frame` 则是从解码器获取已解码的数据帧。 #### 初始化编解码上下文 为了能够正常使用这两个函数,首先需要初始化一个合适的 `AVCodecContext` 实例并打开相应的编解码器: ```c // 找到所需的解码器 const AVCodec* codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (!codec) { fprintf(stderr, "Failed to find decoder\n"); exit(1); } // 创建一个新的编解码上下文实例 AVCodecContext* codec_context = avcodec_alloc_context3(codec); if (!codec_context) { fprintf(stderr, "Could not allocate video codec context\n"); exit(1); } // 设置参数... int ret; if ((ret = avcodec_open2(codec_context, codec, NULL)) < 0) { fprintf(stderr, "Cannot open codec: %s\n", av_err2str(ret)); exit(1); } ``` #### 向解码器提交数据包 当准备好要解码的数据包之后,可以使用 `avcodec_send_packet` 将其传递给解码器进行处理: ```c AVPacket packet; // 假设已经填充好了packet... ret = avcodec_send_packet(codec_context, &packet); if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) { fprintf(stderr, "Error during decoding: %s\n", av_err2str(ret)); } else if (ret == AVERROR(EAGAIN)){ // 需要更多输入或等待输出可用 } ``` 这里需要注意的是,在某些情况下可能会返回 `EAGAIN` 错误码表示当前无法接受新的数据包直到有空间为止;或者是遇到其他类型的错误时应该适当地处理这些异常情况[^2]。 #### 获取解码完成的视频帧 一旦成功地把数据送入了解码队列里边,则可以通过调用 `avcodec_receive_frame` 来取出已经被完全解析出来的图像信息: ```c AVFrame* frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Out of memory\n"); exit(1); } do { ret = avcodec_receive_frame(codec_context, frame); if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF){ fprintf(stderr, "Error receiving a frame from the decoder.\n"); break; } if (ret >= 0) { // 处理解码得到的一帧画面 // 渲染或其他操作完成后记得释放资源 av_frame_unref(frame); } } while (ret == EAGAIN || ret == 0); av_frame_free(&frame); ``` 上述代码片段展示了如何持续尝试接收来自解码器的新帧直至不再可能获得更多的有效输出位置。如果此时仍然存在未被消耗掉的数据包则应当继续重复此过程来确保所有的媒体流都被正确地转换成可视化的形式[^3]。 #### 关闭编解码器 最后不要忘记关闭之前开启过的任何资源以防止内存泄漏等问题的发生: ```c avcodec_close(codec_context); av_freep(&codec_context->extradata); avcodec_free_context(&codec_context); ``` 通过以上步骤就可以实现基本的功能需求了。当然实际应用当中还涉及到很多细节上的优化以及对于不同场景下的特殊考虑,比如多线程支持、硬件加速等等特性都需要额外关注。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值