MPP硬解码流程

使用MPP硬解码,同时适用解码H264和H265

在之前关于MPP的使用过程中,发现了一个非常让人费解的现象,同一套解码流程,即使在MPP初始化的时候配置了mpp_init的MppCodingType参数为对应类型,H265能正常解码,但是H264却只能解码关键帧,非关键帧解码出来以后画面是绿色的。通过多次尝试后问题得到了解决:

  1. 使用ffmpeg拉取rtsp流,讲读取到的AVPacket转换成MppPacket送入mpp解码器解码
  2. 初始化mpp解码器:
    MppCodingType type  = MPP_VIDEO_CodingAVC; //h264
    //MppCodingType type  = MPP_VIDEO_CodingHEVC; //h265
    
    MPP_RET ret = MPP_OK;
    ret = mpp_create(&ctx, &mpi);
    if (MPP_OK != ret) {
        qDebug() << "mpp_create failed!";
        deInit(ctx, mpi);
    }
    
    ret = mpp_init(ctx, MPP_CTX_DEC, type);
    if (MPP_OK != ret) {
        qDebug() << "mpp_init failed!";
        deInit(ctx, mpi);
    }
  1. mpp解码核心:
    RK_U32 pkt_done = 0;  //是否送包成功
    RK_U32 err_info = 0;  //错误信息
    RK_U32 discard_info = 0;  //标志此帧是否该被丢弃,非0为该丢弃
    MPP_RET ret = MPP_OK;
    MppPacket MppPacket = NULL;
    MppFrame frame = NULL;
    RK_U32 nPutErrNum = 0; //送包出现错误次数

	//将AVPacket转换成MppPacket
    ret = mpp_packet_init(&MppPacket, av_packet->data, av_packet->size);
    if(ret < 0){
        char errstrs[32] = {0};
        av_strerror(ret, errstrs, 32);
        qDebug() << "mpp_packet_init error: " << errstrs;
        av_packet_unref(av_packet);
        av_packet_free(&av_packet);
        return -1;
    }
    mpp_packet_set_pts(MppPacket, av_packet->pts);


    do{
        //送包入解码器
        if (!pkt_done) {
            ret = mpi->decode_put_packet(ctx, MppPacket);
            if (MPP_OK != ret){
                nPutErrNum ++;
                char errstr[32] = {0};
                av_strerror(ret, errstr, 32);
                qDebug() << "decode_put_packet error: " << errstr;
            }else{
                pkt_done = 1;
            }
        }

		RK_S32 times = 5;
        do{
            mpp_frame_init(&frame);
            try_again:
            //从解码器中获取MppFrame
            ret = data.mpi->decode_get_frame(data.ctx, &frame);
            if(MPP_OK != ret){
                char errstr[32] = {0};
                av_strerror(ret, errstr, 32);
                qDebug() << "decode_get_frame error: " << errstr;
                //获取帧超时则重试,超过5次则认为失败
                if(MPP_ERR_TIMEOUT == ret){
                    if(times > 0){
                        times--;
                        msleep(2);
                        goto try_again;
                    }
                   mpp_err("decode_get_frame failed too much time\n");
                   mpp_frame_deinit(&frame);
                   break;
                }
            }

            if (frame) //如果获取的帧有效
            {
                //只会在首次解码以及宽高变换时候产生一次
                if (mpp_frame_get_info_change(frame)) {
                    RK_U32 width = mpp_frame_get_width(frame);
                    RK_U32 height = mpp_frame_get_height(frame);
                    RK_U32 hor_stride = mpp_frame_get_hor_stride(frame);
                    RK_U32 ver_stride = mpp_frame_get_ver_stride(frame);
                    RK_U32 buf_size = mpp_frame_get_buf_size(frame);
                    qDebug() << "decode_get_frame get info changed found!";
                    qDebug() << QString("decoder require buffer w:h [%1:%2], stride [%3:%4], buf_size %5")
                                .arg(width).arg(height).arg(hor_stride).arg(ver_stride).arg(buf_size);
                    ret = mpp_buffer_group_get_internal(&data.frm_grp, MPP_BUFFER_TYPE_DRM);
                    if (ret) {
                        mpp_err("get mpp buffer group  failed ret %d\n", ret);
                        break;
                    }

                    //为mpp解码器设置外部缓冲组
                    mpi->control(ctx, MPP_DEC_SET_EXT_BUF_GROUP, data.frm_grp);
                    //通知解码器可以开始解码
                    mpi->control(ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL);
                }else{
                    //mpp_frame_get_discard判断当前帧是否应该被丢弃
                    //非零,则表示该帧应该被丢弃;如果返回值为零,则表示该帧应该被保留
                    err_info = mpp_frame_get_errinfo(frame);
                    discard_info = mpp_frame_get_discard(frame);
                    if(err_info) {
                        char errstr[32] = {0};
                        av_strerror(mpp_frame_get_errinfo(frame), errstr, 32);
                        qDebug() << "mpp_frame_get_errinfo: " << errstr;
                    }

                    if(discard_info) {
                        char errstr[32] = {0};
                        av_strerror(mpp_frame_get_discard(frame), errstr, 32);
                        qDebug() << "mpp_frame_get_discard: " << errstr;
                        mpp_frame_deinit(&frame);
                        break;
                    }

                    //-0.5到-4倍速,每个GOP的每一帧都解码,将一个GOP解码得到的MppFrame暂存到一个QList,再从后到先取出显示即是倒放
                    if(m_pPlaybackThread->getRate() < 0 and m_pPlaybackThread->getRate() > -8.0f){
                        appendTempFrame(frame);
                        break;
                    }else{
                        //0.5 to 16倍速本来就是正序
                        // -8 to -16只解关键帧,且ffmpeg读包的时候就是反向seek关键帧,所以送进解码器解码的时候已经是倒序
                        m_lstBackFrames.append(frame);
                        lastframePts = mpp_frame_get_pts(frame);
                        break;
                    }
                }
            } else {
                mpp_frame_deinit(&frame);
                usleep(3000);  
                break;
            }
        //这个while非常重要,如果记录此while的循环次数,
        //会发现如果没有上面的usleep(3000),会循环非常多次才能获取有效的MppFrame
        }while(1);  

        if (pkt_done)
            break;

        if(nPutErrNum > 5)  //最多送包5次,避免死循环
            break;

        usleep(3000); // 取到的帧无效或者送包失败,过3毫秒再进行下一次取帧

    }while(1);

    if(av_packet) {
        av_packet_unref(av_packet);
        av_packet_free(&av_packet);
    }

    if(MppPacket) {
        mpp_packet_deinit(&MppPacket);
    }

    return ret;

  1. 在我出现文章开始描述的那种情况的时候,是因为我解码的时候,没有使用两个while循环,不管是送包还是从解码器取帧都只尝试了一次,失败了就不进行下一步。然而这样操作,解码H265的时候行得通,解码H264的时候就只能解关键帧。谨以此文记录这个困扰我许久让我百思不得其解的问题的解决方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值