9-3_使用MPP平台解码H264文件

使用MPP平台解码H264文件

在MPP平台下解码H264文件需要使用VDEC 模块,即视频解码模块。解码过程如下所示:

将H264码流文件传入解码模块后,会解码后生成的yuv文件。

1.主程序解析

1.1 初始化MPP平台

    memset(&stContext.mSysConf, 0, sizeof(MPP_SYS_CONF_S));
    stContext.mSysConf.nAlignWidth = 32;
    AW_MPI_SYS_SetConf(&stContext.mSysConf);
    AW_MPI_SYS_Init();

1.2 配置解码通道参数

    memset(&stContext.mVDecAttr, 0, sizeof(stContext.mVDecAttr));
    stContext.mVDecAttr.mType      = stContext.mConfigPara.mType;
    stContext.mVDecAttr.mOutputPixelFormat = MM_PIXEL_FORMAT_YVU_SEMIPLANAR_420;
    stContext.mVDecAttr.mBufSize   = 2048*1024;
    stContext.mVDecAttr.mPicWidth  = 1920;
    stContext.mVDecAttr.mPicHeight = 1088;

1.3 创建解码通道

    ERRORTYPE ret;
    BOOL bSuccessFlag = FALSE;
    stContext.mVDecChn = 0;
    while (stContext.mVDecChn < VDEC_MAX_CHN_NUM)
    {
        ret = AW_MPI_VDEC_CreateChn(stContext.mVDecChn, &stContext.mVDecAttr);
        if(SUCCESS == ret)
        {
            bSuccessFlag = TRUE;
            alogd("create VDec channel[%d] success!", stContext.mVDecChn);
            break;
        }
        else if (ERR_VDEC_EXIST == ret)
        {
            alogd("VDec channel[%d] exist, find next!", stContext.mVDecChn);
            stContext.mVDecChn++;
        }
        else
        {
            aloge("create VDec channel[%d] fail! ret[0x%x]!", stContext.mVDecChn, ret);
            break;
        }
    }
    if (FALSE == bSuccessFlag)
    {
        stContext.mVDecChn = MM_INVALID_CHN;
        aloge("fatal error! create VDec channel fail!");
    }

1.4 设置解码通道回调函数

	MPPCallbackInfo cbInfo;
    cbInfo.cookie = (void*)&stContext;
    cbInfo.callback = (MPPCallbackFuncType)&SampleVDecCallbackWrapper;
    AW_MPI_VDEC_RegisterCallback(stContext.mVDecChn, &cbInfo);

1.5 开启解码通道

	AW_MPI_VDEC_StartRecvStream(stContext.mVDecChn);

1.6 初始化数据buf

    char DstPath[128] = {0};
    VDEC_STREAM_S nStreamInfo;
    VIDEO_FRAME_INFO_S nFrameInfo;
    memset(&nStreamInfo, 0, sizeof(nStreamInfo));
    nStreamInfo.pAddr = malloc(4096*1024); // max size
    if (NULL == nStreamInfo.pAddr)
    {
        aloge("fatal error! malloc nStreamInfo.pAddr fail! size=4MB");
        goto _exit_1;
    }
    memset(nStreamInfo.pAddr, 0, 4096*1024);

1.7 打开h264和包长度

        FILE *fp_bs = fopen(stContext.mConfigPara.mH264VbsPath, "rb");//打开 H.264 比特流
        FILE *fp_sz = fopen(stContext.mConfigPara.mH264LenPath, "rb");//打开 包长度文件
        if ((fp_bs==NULL) || (fp_sz==NULL))
        {
            aloge("fopen fail! BitStreamFile: %p, %s, LenFile: %p, %s",
                fp_bs, stContext.mConfigPara.mH264VbsPath, fp_sz, stContext.mConfigPara.mH264LenPath);
            goto _exit_1;
        }

1.8 读取长度文件的内容到内存中

		fseek(fp_sz, 0, SEEK_END);
        int nLenFileSize = ftell(fp_sz);
        fseek(fp_sz, 0, SEEK_SET);
        char *pLenStr = malloc(nLenFileSize);

1.9 计算数据帧长度

        memset(pLenStr, 0, nLenFileSize);
        fread(pLenStr, 1, nLenFileSize, fp_sz);
        char *endptr0 =  pLenStr, *endptr1 = pLenStr + nLenFileSize;

1.10 打开输出文件

		sprintf(DstPath, "%sh264.yuv", stContext.mConfigPara.mYuvFilePath);
		stContext.mFpDstFile = fopen(DstPath, "wb");
		if (stContext.mFpDstFile == NULL)
		{
		    aloge("fatal error! Can't create file %s", DstPath);
		    exit(-1);
		}

1.11 计算每帧的长度

            int pkt_sz;
            pkt_sz = strtol(endptr0, &endptr1, 10);
            endptr0 += 8;//endptr1;
            if (pkt_sz && yuvId <= 20)
            {
                alogd("yuvId=%d, pkt_sz=%d", yuvId++, pkt_sz);
            }
            else
            {
                alogw("pkt_sz=0 or yuvId big enough, exit loop!");
                break;
            }

1.12 读取视频帧

            int rd_cnt = fread(nStreamInfo.pAddr, 1, pkt_sz, fp_bs);
            if (rd_cnt != pkt_sz)
            {
                aloge("error happen! pkt_sz=%d, rd_cnt=%d", pkt_sz, rd_cnt);
                break;
            }
            else
            {
                nStreamInfo.mLen = rd_cnt;
                nStreamInfo.mbEndOfFrame = TRUE;
                alogd("read vbs packet! rd_cnt=%d", rd_cnt);
            }

1.13 发送视频帧至解码器

            ret = AW_MPI_VDEC_SendStream(stContext.mVDecChn, &nStreamInfo, 100);//发送视频包到解码器
            if(ret != SUCCESS)
            {
                alogw("send stream with 100ms timeout fail?! Maybe Vbs is full!");
                goto __TryToSendStream;
            }

1.14 保存解码后的数据

获取解码器生成的YUV文件,并将Y和UV保存输出文件中,保存后将原来的解码通道获取的图像帧释放。

            if ((ret=AW_MPI_VDEC_GetImage(stContext.mVDecChn, &nFrameInfo, 1000)) == SUCCESS)//获取解码后的帧
            {
                //sprintf(DstPath, "%s%02d_%d_%d.yuv", stContext.mConfigPara.mYuvFilePath, yuvId++, nFrameInfo.VFrame.mWidth, nFrameInfo.VFrame.mHeight);
                //open yuv file
                //stContext.mFpDstFile = fopen(DstPath, "wb");
                fwrite(nFrameInfo.VFrame.mpVirAddr[0], 1, nFrameInfo.VFrame.mWidth*nFrameInfo.VFrame.mHeight, stContext.mFpDstFile);//Y
                fwrite(nFrameInfo.VFrame.mpVirAddr[1], 1, nFrameInfo.VFrame.mWidth*nFrameInfo.VFrame.mHeight/2, stContext.mFpDstFile);//UV
                //fclose(stContext.mFpDstFile);
                AW_MPI_VDEC_ReleaseImage(stContext.mVDecChn, &nFrameInfo);//释放解码图像帧
                alogd("--------------get one yuv frame(%s)-----------------", DstPath);
                waitFrmCnt = 0;
            }
            else if (ret == ERR_VDEC_NOBUF)
            {
                alogd("no valid buf! waitFrmCnt=%d", waitFrmCnt++);
                if (waitFrmCnt<5)
                    goto __TryToGetFrame;
                alogw("wait one frame for 5s, do not wait anymore! we send another stream!");
            }

1.15 释放和关闭输出文件

        free(pLenStr);
		fclose(stContext.mFpDstFile);

1.16 停止并销毁解码通道

    AW_MPI_VDEC_StopRecvStream(stContext.mVDecChn);
    AW_MPI_VDEC_DestroyChn(stContext.mVDecChn);

1.17 退出MPP平台

    AW_MPI_SYS_Exit();