RK3568基于mpp实现硬解码(二):FFmpeg + mpp实现ipc摄像头图像解码

发布于:2025-08-01 ⋅ 阅读:(19) ⋅ 点赞:(0)

在上一篇文章中已经成功编译mpp并导入到项目中,接下来基于mpi接口实现解码器

说明一下视频图像是通过ipc摄像头采集的。在项目中通过FFmpeg的RTSP拉流获取ipc摄像头的码流数据,FFmpeg拉流这部分不作叙述,只需要知道FFmpeg拉流后数据保存在AVPacket这个结构体当中。

一、MPI接口使用说明

官方文档中给出的图示已经介绍了MPI接口的使用过程 

关于解码过程可以参照test目录下的mpi_dec_test.c文件

二、AVPacket转为MppPacket

mpp的使用流程可以参考官方的mpi_dec_test.c。因为官方demo传入的是一个容器文件(比如MP4文件),我的项目要处理的是FFmpeg拉取的码流数据,数据保存在AVPacket结构体中。mpp要处理的是MppPacket结构体,所以要将AVPacket中的码流数据转到MppPacket中保存

三、分帧处理的问题

这个问题在官方文档中已有解释

四、相关代码实现

#mppvideodecode.h

#ifndef MPPVIDEODECODE_H
#define MPPVIDEODECODE_H

#define myDebug()       qDebug()<<"["<<this->metaObject()->className()<<"]"<<__FUNCTION__<<__LINE__

#include <QObject>
#include <QDebug>
#include <unistd.h>

#include "libavcodec/packet.h"
#include "rk_mpi.h"
#include "mpp_log.h"
#include "mpp_mem.h"
#include "mpi_dec_utils.h"
#include "utils.h"
#include "mpp_time.h"
#include "libavcodec/avcodec.h"




typedef struct
{
    MppCtx ctx;
    MppApi *mpi;
    MppBufferGroup frm_grp;
    size_t max_usage;
    RK_S32 frame_count;  //解出帧的计数
} MpiDecLoopData;


class MppVideoDecode : public QObject
{
    Q_OBJECT
public:
    MppVideoDecode();
    void initMpiDecLoopData();               //初始化mpp相关参数
    QVector<MppFrame> mppDecode(AVPacket* avpacket);  //解码
    QString getFrameFormat(MppFrame frame);           //获取图像像素格式   
    void deInit(); //释放相关资源
private:
    MppFrame m_frame = NULL;
    MppPacket m_packet = NULL;
    MpiDecLoopData m_data;
    QVector<MppFrame> m_vecMppFrame;  //存放解出的帧


};

#endif // MPPVIDEODECODE_H
#mppvideodecode.cpp

#include "mppvideodecode.h"

MppVideoDecode::MppVideoDecode()
{
    initMpiDecLoopData();
}

void MppVideoDecode::deInit()
{
    if(m_packet){
        mpp_packet_deinit(&m_packet);
        m_packet = NULL;
    }



    if (m_data.frm_grp) {
        mpp_buffer_group_put(m_data.frm_grp);
        m_data.frm_grp = NULL;
    }


    for(MppFrame frame : m_vecMppFrame){
        if(frame){
            mpp_frame_deinit(&frame);
            frame = NULL;
        }
    }
    m_vecMppFrame.clear();


//    if (m_data.ctx) {
//        mpp_destroy(m_data.ctx);
//        m_data.ctx = NULL;
//    }



    //    if(!m_vecMppFrame.isEmpty()){
    //        m_vecMppFrame.clear();
    //    }
}

void MppVideoDecode::initMpiDecLoopData()
{
    MPP_RET ret         = MPP_OK;    //操作结果
    MppCtx ctx          = NULL;
    MppApi *mpi         = NULL;
    MpiCmd mpi_cmd      = MPP_CMD_BASE;  //控制模式
    MppParam param      = NULL;          //控制参数
    RK_U32 need_split   = 1;             //分帧标志位
    MppCodingType type  = MPP_VIDEO_CodingAVC;   //解码格式h.264

    memset(&m_data, 0, sizeof(m_data));

    ret = mpp_create(&ctx, &mpi);
    if (MPP_OK != ret) {
        mpp_err("mpp_create failed\n");
        if (m_data.ctx) {
            mpp_destroy(m_data.ctx);
            m_data.ctx = NULL;
        }
    }

    //内部分帧处理
    mpi_cmd = MPP_DEC_SET_PARSER_SPLIT_MODE;
    param = &need_split;
    ret = mpi->control(ctx, mpi_cmd, param);
    if (MPP_OK != ret) {
        mpp_err("mpi->control failed\n");
        if (m_data.ctx) {
            mpp_destroy(m_data.ctx);
            m_data.ctx = NULL;
        }
    }


    mpi_cmd = MPP_SET_INPUT_BLOCK;   //处理输入是否为阻塞模式(param为1表示输入缓冲区满时会阻塞)
    param = &need_split;
    ret = mpi->control(ctx, mpi_cmd, param);
    if (MPP_OK != ret) {
        mpp_err("mpi->control failed\n");
        if (m_data.ctx) {
            mpp_destroy(m_data.ctx);
            m_data.ctx = NULL;
        }
    }

    ret = mpp_init(ctx, MPP_CTX_DEC, type);
    if (MPP_OK != ret) {
        mpp_err("mpp_init failed\n");
        if (m_data.ctx) {
            mpp_destroy(m_data.ctx);
            m_data.ctx = NULL;
        }
    }

    m_data.ctx = ctx;
    m_data.mpi = mpi;
    m_data.frame_count = 0;
}

QVector<MppFrame> MppVideoDecode::mppDecode(AVPacket* avpacket)
{
    RK_U32 pkt_done = 0;
    RK_U32 err_info = 0;
    MPP_RET ret = MPP_OK;
    MppCtx ctx  = m_data.ctx;
    MppApi *mpi = m_data.mpi;

    //RK_S64 t_s, t_e;

    mpp_packet_init(&m_packet, avpacket->data, avpacket->size);
    mpp_packet_set_pts(m_packet, avpacket->pts);                      //显示时间戳


    //    t_s = mpp_time();
    //第一层循环用于确认必须把packet送进解码器
    do {
        RK_S32 times = 5;
        ret = mpi->decode_put_packet(ctx, m_packet);
        if (MPP_OK == ret)
            pkt_done = 1;

        // then get all available frame and release
        do {
            RK_S32 get_frm = 0;   //用于判断一包packet中是否还有frame
            RK_U32 frm_eos = 0;   //表示图像结束的标志

try_again:
            ret = mpi->decode_get_frame(ctx, &m_frame);
            if (MPP_ERR_TIMEOUT == ret) {
                if (times > 0) {
                    times--;
                    msleep(2);
                    goto try_again;
                }
                mpp_err("decode_get_frame failed too much time\n");
            }
            if (MPP_OK != ret) {
                mpp_err("decode_get_frame failed ret %d\n", ret);
                break;
            }

            if (m_frame) {
                if (mpp_frame_get_info_change(m_frame)) {
                    RK_U32 width = mpp_frame_get_width(m_frame);
                    RK_U32 height = mpp_frame_get_height(m_frame);
                    RK_U32 hor_stride = mpp_frame_get_hor_stride(m_frame);
                    RK_U32 ver_stride = mpp_frame_get_ver_stride(m_frame);
                    RK_U32 buf_size = mpp_frame_get_buf_size(m_frame);

                    mpp_log("decode_get_frame get info changed found\n");
                    mpp_log("decoder require buffer w:h [%d:%d] stride [%d:%d] buf_size %d",
                            width, height, hor_stride, ver_stride, buf_size);

                    ret = mpp_buffer_group_get_internal(&m_data.frm_grp, MPP_BUFFER_TYPE_ION);
                    if (ret) {
                        mpp_err("get mpp buffer group  failed ret %d\n", ret);
                        break;
                    }
                    mpi->control(ctx, MPP_DEC_SET_EXT_BUF_GROUP, m_data.frm_grp);

                    mpi->control(ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL);
                } else {
                    err_info = mpp_frame_get_errinfo(m_frame) | mpp_frame_get_discard(m_frame);
                    if (err_info) {
                        mpp_log("decoder_get_frame get err info:%d discard:%d.\n",
                                mpp_frame_get_errinfo(m_frame), mpp_frame_get_discard(m_frame));
                    }

                    //                MppBuffer buffer = mpp_frame_get_buffer(m_frame);
                    //                myDebug() << "buffer addr:" << buffer;
                }


                //                myDebug() << "the frame format is:" << getFrameFormat(frame); //获取图像格式(yuv or rgb)

                m_data.frame_count++;
                mpp_log("decode_get_frame get frame %d\n", m_data.frame_count);

                frm_eos = mpp_frame_get_eos(m_frame);  //图像结束标志
                get_frm = 1;
                m_vecMppFrame.append(m_frame);
            }

            // try get runtime frame memory usage
            if (m_data.frm_grp) {
                size_t usage = mpp_buffer_group_usage(m_data.frm_grp);
                if (usage > m_data.max_usage)
                    m_data.max_usage = usage;
            }

            if (frm_eos) {
                mpp_log("found last frame\n");
                break;
            }

            if (get_frm)
                continue;

            break;
        } while (1);



        if(pkt_done)
            break;

        /*
        * why sleep here:
        * mpi->decode_put_packet will failed when packet in internal queue is
        * full,waiting the package is consumed .Usually hardware decode one
        * frame which resolution is 1080p needs 2 ms,so here we sleep 3ms
        * * is enough.
        */
        msleep(3);
    }while (1);

    //    t_e = mpp_time();

    //    myDebug() << "解一个packet所用时间为" << (t_e - t_s) / 1000 << "ms";

    for(const MppFrame frame : m_vecMppFrame){
            myDebug() << "addr111111:" << frame;
    }
    return m_vecMppFrame;
}



QString MppVideoDecode::getFrameFormat(MppFrame frame)
{
    MppFrameFormat fmt = mpp_frame_get_fmt(frame);
    switch(fmt){
    case MPP_FMT_YUV420SP:
        return "MPP_FMT_YUV420SP";
    case MPP_FMT_YUV420SP_10BIT:
        return "MPP_FMT_YUV420SP_10BIT";
    case MPP_FMT_YUV422SP:
        return "MPP_FMT_YUV422SP";
    case MPP_FMT_YUV422SP_10BIT:
        return "MPP_FMT_YUV422SP_10BIT";
    case MPP_FMT_YUV420P:
        return "MPP_FMT_YUV420P";
    case MPP_FMT_YUV420SP_VU:
        return "MPP_FMT_YUV420SP_VU";
    case MPP_FMT_YUV422P:
        return "MPP_FMT_YUV422P";
    case MPP_FMT_YUV422SP_VU:
        return "MPP_FMT_YUV422SP_VU";
    case MPP_FMT_YUV422_YUYV:
        return "MPP_FMT_YUV422_YUYV";
    case MPP_FMT_YUV422_YVYU:
        return "MPP_FMT_YUV422_YVYU";
    case MPP_FMT_YUV422_UYVY:
        return "MPP_FMT_YUV422_UYVY";
    case MPP_FMT_YUV422_VYUY:
        return "MPP_FMT_YUV422_VYUY";
    case MPP_FMT_YUV400:
        return "MPP_FMT_YUV400";
    case MPP_FMT_YUV440SP:
        return "MPP_FMT_YUV440SP";
    case MPP_FMT_YUV411SP:
        return "MPP_FMT_YUV411SP";
    case MPP_FMT_YUV444SP:
        return "MPP_FMT_YUV444SP";
    case MPP_FMT_YUV444P:
        return "MPP_FMT_YUV444P";
    case MPP_FMT_YUV444SP_10BIT:
        return "MPP_FMT_YUV444SP_10BIT";
    case MPP_FMT_AYUV2BPP:
        return "MPP_FMT_AYUV2BPP";
    case MPP_FMT_AYUV1BPP:
        return "MPP_FMT_AYUV1BPP";
    case MPP_FMT_YUV_BUTT:
        return "MPP_FMT_YUV_BUTT";


    case MPP_FMT_RGB565:
        return "MPP_FMT_RGB565";
    case MPP_FMT_BGR565:
        return "MPP_FMT_BGR565";
    case MPP_FMT_RGB555:
        return "MPP_FMT_RGB555";
    case MPP_FMT_BGR555:
        return "MPP_FMT_BGR555";
    case MPP_FMT_RGB444:
        return "MPP_FMT_RGB444";
    case MPP_FMT_BGR444:
        return "MPP_FMT_BGR444";
    case MPP_FMT_RGB888:
        return "MPP_FMT_RGB888";
    case MPP_FMT_BGR888:
        return "MPP_FMT_BGR888";
    case MPP_FMT_RGB101010:
        return "MPP_FMT_RGB101010";
    case MPP_FMT_BGR101010:
        return "MPP_FMT_BGR101010";
    case MPP_FMT_ARGB8888:
        return "MPP_FMT_ARGB8888";
    case MPP_FMT_ABGR8888:
        return "MPP_FMT_ABGR8888";
    case MPP_FMT_BGRA8888:
        return "MPP_FMT_BGRA8888";
    case MPP_FMT_RGBA8888:
        return "MPP_FMT_RGBA8888";
    case MPP_FMT_ARGB4444:
        return "MPP_FMT_ARGB4444";
    case MPP_FMT_ARGB1555:
        return "MPP_FMT_ARGB1555";
    case MPP_FMT_RGB_BUTT:
        return "MPP_FMT_RGB_BUTT";
    case MPP_FMT_BUTT:
        return "MPP_FMT_BUTT";
    }
}



可以参考

https://github.com/MUZLATAN/ffmpeg_rtsp_mpp

五、decode_get_frame返回值为NULL

我在调用mpp库的mpp_create、mpp_init、mpp_packet_init、decode_put_packet甚至decode_get_frame返回的值都是0(正常),但是decode_get_frame解出来的frame为NULL

网上搜到的原因一般有两个
(1)在调用mpp_init时传入的解码类型与实际编码类型不相符(比如摄像头码流用的是H265编码,mpp_init传入的却是H264方法)
(2)FFmpeg拉流后的码流保存在AVPacket中,但是AVPacket中的码流不足以构成一帧数据所以解不出来

但是我的原因都不是以上这些。在FFmpeg拉流时,因为要不断从摄像头中拉数据,所以要用循环

while(1){
	...
	avformat_open_input();
	...
}

我在调用mpp时,把mpp_create()和mpp_init()也放在循环中了

while(1){
	...
	avformat_open_input();
    mpp(){
		mpp_create();
		mpp_init();
	    decode_put_packet();
        decode_get_frame();
	}
	...
}

这就导致每拉一次流,就重新创建一个mpp对象,所以读取的frame为NULL。最后修改代码为以下,成功读取的frame

mpp(){
	mpp_create();
	mpp_init();
}
while(1){
	...
	avformat_open_input();
    ...
	decode_put_packet();
    decode_get_frame();
	...
}


网站公告

今日签到

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