1、ffmpeg rockchip处理帧并调用rkmpp进行硬解码流程

在这里插入图片描述
配置ffmpeg时添加符号:-g
在这里插入图片描述
烧到开发板上
在这里插入图片描述

整体大致流程如下:

avcodec_open2()rkmpp_init_decoder()avcodec_send_packet()rkmpp_decode()
   ↓
  ├── decode_put_packet()
  ├── decode_get_frame()avcodec_receive_frame()rkmpp_close_decoder()

2、源代码分析:libavcodec/rkmppdec.c

重要的函数:
在这里插入图片描述

2.1、rkmpp_decode_init

在这里插入图片描述

# 1、先根据根据上层想要的输出像素格式去匹配rkmpp硬件解码像素格式(switch case这里)

# 2、建立MPP解码器对象
    if ((ret = mpp_create(&r->mctx, &r->mapi)) != MPP_OK) {
        av_log(avctx, AV_LOG_ERROR, "Failed to create MPP context and api: %d\n", ret);
        ret = AVERROR_EXTERNAL;
        goto fail;
    }

    if ((ret = mpp_init(r->mctx, MPP_CTX_DEC, coding_type)) != MPP_OK) { // 初始化MPP上下文
        av_log(avctx, AV_LOG_ERROR, "Failed to init MPP context: %d\n", ret);
        ret = AVERROR_EXTERNAL;
        goto fail;
    }
# 3、这里有个特殊的处理MJPEG
if (avctx->codec_id == AV_CODEC_ID_MJPEG) {
    r->buf_mode = RKMPP_DEC_HALF_INTERNAL;
    /* Misc buffer group: for info change frame and bitstream only */
    ret = mpp_buffer_group_get_internal(&r->buf_group_misc, MPP_BUFFER_TYPE_DRM |
                                                            MPP_BUFFER_FLAGS_DMA32 |
                                                            MPP_BUFFER_FLAGS_CACHABLE);
    if (ret != MPP_OK) {
        av_log(avctx, AV_LOG_ERROR, "Failed to get MPP internal buffer group for Misc: %d\n", ret);
        ret = AVERROR_EXTERNAL;
        goto fail;
    }
}
/*
由于MJPEG的格式问题,与H264等不同,H264是帧间压缩的视频编码,分为IBP帧,帧间有强依赖性。
MJPEG本质上是由一连串的单独的JPEG压缩图片按照帧率压缩而成,每一帧都是单独无依赖的,单帧入,单帧出。

H264的码流本质上是按照NALU组成的大块连续数据,但是MJPEG是大量碎片化的小数据包,所以针对MJPEG
需要单独申请内存(会进行频繁的申请释放操作)

mpp_buffer_group_get_internal申请的是硬件显存,不是malloc申请的内存。cpu可以间接访问。
*/

# 4、创建/复用MPP的硬件设备
if (avctx->hw_device_ctx) { // 如果ffmpeg上下文有硬件设备上下文, 则尝试复用之, 否则创建新的
        AVBufferRef *device_ref = avctx->hw_device_ctx;
        AVHWDeviceContext *device_ctx = (AVHWDeviceContext *)device_ref->data;

        if (device_ctx && device_ctx->type == AV_HWDEVICE_TYPE_RKMPP) {
            r->hwdevice = av_buffer_ref(avctx->hw_device_ctx);
            if (r->hwdevice)
                av_log(avctx, AV_LOG_VERBOSE, "Picked up an existing RKMPP hardware device\n");
        }
    }
if (!r->hwdevice) {
	if ((ret = av_hwdevice_ctx_create(&r->hwdevice, AV_HWDEVICE_TYPE_RKMPP, NULL, NULL, 0)) < 0) 		{
		av_log(avctx, AV_LOG_ERROR, "Failed to create a RKMPP hardware device: %d\n", ret);
		goto fail;
	}
        av_log(avctx, AV_LOG_VERBOSE, "Created a RKMPP hardware device\n");
}
return 0;

2.2、rkmpp_decode_receive_frame

在这里插入图片描述
这是最关键的一个函数
在这里插入图片描述
本质来说就是一个死循环,不断地拿packet丢给MPP,rkmpp的其他API后面再深入分析。

2.3、rkmpp_send_packet

这个函数会将packet塞进MPP。注意,packet是压缩的数据,还没解码。
调用关系如图
在这里插入图片描述
跳帧处理策略:
在这里插入图片描述
很多码流(直播、切入)再开始阶段可能需要先喂一段数据才能“起解”,如果一上来就把非关键帧丢了,可能下一个关键帧还有很远,就会导致硬解一直不出画面,所以在跳帧的时候先确保已经稳定硬解出过帧再跳。

核心点:
对于MPP来说,MJPEG都是单独拿出来作为一种情况进行考虑的,上文提到过。

if (avctx->codec_id == AV_CODEC_ID_MJPEG) {
        MppBuffer mpp_buf = NULL;

        av_assert0(r->buf_group_misc);

        /* the input slot of the MJPEG decoder must be a DRM/DMA buffer,
         * so borrow some from the frame buffer to write the pkt data */
        if ((ret = mpp_buffer_get(r->buf_group_misc, &mpp_buf, pkt->size)) != MPP_OK) {
            av_log(avctx, AV_LOG_ERROR, "Failed to get buffer for packet: %d\n", ret);
            return AVERROR_EXTERNAL;
        }
        if (!mpp_buf)
            return AVERROR(ENOMEM);

        if ((ret = mpp_buffer_write(mpp_buf, 0, pkt->data, pkt->size)) != MPP_OK) {
            av_log(avctx, AV_LOG_ERROR, "Failed to write buffer: %d\n", ret);
            mpp_buffer_put(mpp_buf);
            return AVERROR_EXTERNAL;
        }
        if ((ret = mpp_buffer_sync_partial_end(mpp_buf, 0, pkt->size)) != MPP_OK)
            av_log(avctx, AV_LOG_DEBUG, "Failed to sync buffer write: %d\n", ret);

        if ((ret = mpp_packet_init_with_buffer(&mpp_pkt, mpp_buf)) != MPP_OK) {
            av_log(avctx, AV_LOG_ERROR, "Failed to init packet with buffer: %d\n", ret);
            mpp_buffer_put(mpp_buf);
            return AVERROR_EXTERNAL;
        }
        mpp_buffer_put(mpp_buf);
        mpp_packet_set_pts(mpp_pkt, mpp_pkt_pts);

        ret = rkmpp_mjpeg_put_packet(avctx, mpp_pkt);
        if (ret == MPP_ERR_UNKNOW) {
            if (mpp_pkt)
                mpp_packet_deinit(&mpp_pkt);
            return AVERROR_EXTERNAL;
        }
    } else {
        if ((ret = mpp_packet_init(&mpp_pkt, pkt->data, pkt->size)) != MPP_OK) {
            av_log(avctx, AV_LOG_ERROR, "Failed to init packet: %d\n", ret);
            return AVERROR_EXTERNAL;
        }
        mpp_packet_set_pts(mpp_pkt, mpp_pkt_pts);

        ret = r->mapi->decode_put_packet(r->mctx, mpp_pkt);
    }

可以看到AVPacket转换成MppPacket就是在函数mpp_packet_init()/mpp_packet_iniy_with_buffer()这里进行转换的。同样,后者是MJPEG使用的。

2.4、rkmpp_get_frame

函数内部获取frame其实也是分为MJPEG和非MJPEG
在这里插入图片描述
后面包括一些取帧超时、失败,没有拿到帧、处理EOS等杂项。
!!! 最核心的部分是info_change的处理(分辨率、格式的变化)

if (r->info_change = mpp_frame_get_info_change(mpp_frame) ||
        (avctx->codec_id == AV_CODEC_ID_MJPEG && !r->buf_group)) {
        char *opts = NULL;
        int fast_parse = r->fast_parse;
        int mpp_frame_mode = mpp_frame_get_mode(mpp_frame);
        const MppFrameFormat mpp_fmt = mpp_frame_get_fmt(mpp_frame);
        enum AVPixelFormat pix_fmts[3] = { AV_PIX_FMT_DRM_PRIME,
                                           AV_PIX_FMT_NONE,
                                           AV_PIX_FMT_NONE };

        av_log(avctx, AV_LOG_VERBOSE, "Noticed an info change\n");

        if (r->afbc && !(mpp_fmt & MPP_FRAME_FBC_MASK)) {
            av_log(avctx, AV_LOG_VERBOSE, "AFBC is requested but not supported\n");
            r->afbc = 0;
        }

        pix_fmts[1] = rkmpp_get_av_format(mpp_fmt & MPP_FRAME_FMT_MASK);

        if (avctx->pix_fmt == AV_PIX_FMT_DRM_PRIME)
            avctx->sw_pix_fmt = pix_fmts[1];
        else {
            if ((ret = ff_get_format(avctx, pix_fmts)) < 0)
                goto exit;
            avctx->pix_fmt = ret;
        }

        avctx->width        = mpp_frame_get_width(mpp_frame);
        avctx->height       = mpp_frame_get_height(mpp_frame);
        avctx->coded_width  = FFALIGN(avctx->width,  64);
        avctx->coded_height = FFALIGN(avctx->height, 64);
        rkmpp_export_avctx_color_props(avctx, mpp_frame);

        if (av_opt_serialize(r, 0, 0, &opts, '=', ' ') >= 0)
            av_log(avctx, AV_LOG_VERBOSE, "Decoder options: %s\n", opts);
        if (opts)
            av_freep(&opts);

        av_log(avctx, AV_LOG_VERBOSE, "Configured with size: %dx%d | pix_fmt: %s | sw_pix_fmt: %s\n",
               avctx->width, avctx->height,
               av_get_pix_fmt_name(avctx->pix_fmt),
               av_get_pix_fmt_name(avctx->sw_pix_fmt));

        if ((ret = rkmpp_set_buffer_group(avctx, pix_fmts[1], avctx->width, avctx->height)) < 0)
            goto exit;

        /* Disable fast parsing for the interlaced video */
        if (((mpp_frame_mode & MPP_FRAME_FLAG_FIELD_ORDER_MASK) == MPP_FRAME_FLAG_DEINTERLACED ||
             (mpp_frame_mode & MPP_FRAME_FLAG_FIELD_ORDER_MASK) == MPP_FRAME_FLAG_TOP_FIRST) && fast_parse) {
            av_log(avctx, AV_LOG_VERBOSE, "Fast parsing is disabled for the interlaced video\n");
            fast_parse = 0;
        }
        if ((ret = r->mapi->control(r->mctx, MPP_DEC_SET_PARSER_FAST_MODE, &fast_parse)) != MPP_OK) {
            av_log(avctx, AV_LOG_ERROR, "Failed to set parser fast mode: %d\n", ret);
            ret = AVERROR_EXTERNAL;
            goto exit;
        }

        if ((ret = r->mapi->control(r->mctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL)) != MPP_OK) {
            av_log(avctx, AV_LOG_ERROR, "Failed to set info change ready: %d\n", ret);
            ret = AVERROR_EXTERNAL;
            goto exit;
        }

        /* there's no real info change, export the frame directly */
        if (avctx->codec_id == AV_CODEC_ID_MJPEG)
            goto export;

        /* no more new pkts after EOS, retry to get frame */
        if (r->draining) {
            mpp_frame_deinit(&mpp_frame);
            return rkmpp_get_frame(avctx, frame, MPP_TIMEOUT_MAX);
        }
        ret = AVERROR(EAGAIN);
        goto exit;
    } else {
export:
        av_log(avctx, AV_LOG_DEBUG, "Received a frame\n");
        r->got_frame = 1;

        switch (avctx->pix_fmt) {
        case AV_PIX_FMT_DRM_PRIME:
            {
                if ((ret = rkmpp_export_frame(avctx, frame, mpp_frame)) < 0)
                    goto exit;
                return 0;
            }
            break;
        case AV_PIX_FMT_NV12:
        case AV_PIX_FMT_NV16:
        case AV_PIX_FMT_NV24:
        case AV_PIX_FMT_NV15:
        case AV_PIX_FMT_NV20:
            {
                AVFrame *tmp_frame = av_frame_alloc();
                if (!tmp_frame) {
                    ret = AVERROR(ENOMEM);
                    goto exit;
                }
                if ((ret = rkmpp_export_frame(avctx, tmp_frame, mpp_frame)) < 0)
                    goto exit;

                if ((ret = ff_get_buffer(avctx, frame, 0)) < 0) {
                    av_log(avctx, AV_LOG_ERROR, "ff_get_buffer failed: %d\n", ret);
                    av_frame_free(&tmp_frame);
                    goto exit;
                }
                if ((ret = av_hwframe_transfer_data(frame, tmp_frame, 0)) < 0) {
                    av_log(avctx, AV_LOG_ERROR, "av_hwframe_transfer_data failed: %d\n", ret);
                    av_frame_free(&tmp_frame);
                    goto exit;
                }
                if ((ret = av_frame_copy_props(frame, tmp_frame)) < 0) {
                    av_log(avctx, AV_LOG_ERROR, "av_frame_copy_props failed: %d\n", ret);
                    av_frame_free(&tmp_frame);
                    goto exit;
                }
                av_frame_free(&tmp_frame);
                return 0;
            }
            break;
        default:
            {
                ret = AVERROR_BUG;
                goto exit;
            }
            break;
        }
    }
  1. 检查AFBC是否实际可用,不可用就关掉
  2. 根据MPP的输出格式选择像素格式
  3. 更新尺寸和颜色属性
  4. 打印配置日志
  5. 重新配置buffer group,也就是rkmpp_set_buffer_group(),这是这个分支最重要的部分
  6. 如果正在draining,那就递归继续取帧

重点分析**export:**这个分支
分为三种情况:

  1. AV_PIX_FMT_DRM_PRIME
    这里直接调用rkmpp_export_frame()MppFramedma bufferfd + DRM描述符打包进AVFrame
  2. 软件格式NV12 / NV16 / NV24 / NV15 / NV20
    先导出一个临时硬件帧tmp_frame,再通过av_hwframe_transfer_data把硬件帧下载成普通内存
  3. 其他格式认为是出错
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐