【視頻開發】Nvidia硬解碼總結


Nvidia硬解碼總結

1.前言

  本文的主要目的是對近期進行的nvidia硬件解碼工作的記錄和總結。至於為什么研究nvidia硬件解碼的具體內容,其實主要是為了在項目中能夠利用nvidia的硬件解碼和編碼能力,提高單機的編解碼並行能力。截止當前,nvidia的硬件編碼官方提供了nvenc的方法,且在ffmpeg中已經增加了對nvenc的編碼庫。對於硬件解碼,官方提供了基於cuda的解碼方法,但是ffmpeg中還沒有相應的解碼庫。所以,我的目的就是調研一下這個硬解方案,並將其自定義增加到ffmpeg中。

  官方提供的資料比較少,只包括一頁的視頻解碼器介紹示例代碼

  吐槽一下:官網那個一頁的介紹參考量真不大,主要還是參考例程代碼。

2.例程介紹

  官網提供的例程代碼解壓后如下圖所示,因為是調用解碼,所以主要參考了"NvDecodeD3D9"和"NvTranscoder"的代碼。

  總的來說,nvidia提供了source, parser, decoder三個基本模塊。其中source是用來解析視頻文件(例如:純h.264文件),parser是用來解析視頻並得到一幀幀的數據,decoder就是解碼了。

流程圖

  這三個模塊相輔相成,其主要操作流程如上圖所示。source模塊輸出h264數據,parser解析這些h264數據,並通過3個重要的回調函數(pfnSequenceCallback, pfnDecodePicture, pfnDisplayPicture)完成解碼及輸出功能。其中,pfnSequenceCallback是parser解析到序列及圖像參數信息時的回調函數,其傳入的參數是parser解析好的視頻參數,可以用於初始化解碼器或重置解碼器。pfnDecodePicture是parser解析到視頻編碼數據后的回調函數,其傳入的參數parser處理好待解碼的視頻編碼數據,需要在該函數中調用decoder的接口進行解碼操作。pfnDisplayPicture是parser對解碼后的數據處理的回調函數,可以在該回調中對已解碼的數據進行獲取(從顯存到系統內存)並處理。

3.主要接口說明

  cuvidCreateVideoSource : 該接口的作用是創建source,主要參數是設置視頻文件路徑和回調函數。source會去解析指定視頻文件,並通過回調函數實現對視頻數據的自定義處理。源碼中在視頻數據回調函數中,調用了cuvidParseVideoData,即向parser中傳遞數據。

    //init video source
    CUVIDSOURCEPARAMS oVideoSourceParameters;
    memset(&oVideoSourceParameters, 0, sizeof(CUVIDSOURCEPARAMS));
    oVideoSourceParameters.pUserData = this;
    oVideoSourceParameters.pfnVideoDataHandler = HandleVideoData;
    oVideoSourceParameters.pfnAudioDataHandler = NULL;

    oResult = cuvidCreateVideoSource(&m_videoSource, videoPath, &oVideoSourceParameters);
    if (oResult != CUDA_SUCCESS) {
        fprintf(stderr, "cuvidCreateVideoSource failed\n");
        fprintf(stderr, "Please check if the path exists, or the video is a valid H264 file\n");
        exit(-1);
    }

  cuvidCreateVideoParser : 該接口是用來創建video parser,主要參數是設置三個回調函數,實現對解析出來的數據的處理。

    //init video parser
    CUVIDPARSERPARAMS oVideoParserParameters;
    memset(&oVideoParserParameters, 0, sizeof(CUVIDPARSERPARAMS));
    oVideoParserParameters.CodecType = oVideoDecodeCreateInfo.CodecType;
    oVideoParserParameters.ulMaxNumDecodeSurfaces = oVideoDecodeCreateInfo.ulNumDecodeSurfaces;
    oVideoParserParameters.ulMaxDisplayDelay = 1;
    oVideoParserParameters.pUserData = this;
    oVideoParserParameters.pfnSequenceCallback = HandleVideoSequence;
    oVideoParserParameters.pfnDecodePicture = HandlePictureDecode;
    oVideoParserParameters.pfnDisplayPicture = HandlePictureDisplay;

    oResult = cuvidCreateVideoParser(&m_videoParser, &oVideoParserParameters);
    if (oResult != CUDA_SUCCESS) {
        fprintf(stderr, "cuvidCreateVideoParser failed, error code: %d\n", oResult);
        exit(-1);
    }

  cuvidParseVideoData : 該接口是用來向parser塞數據,通過不斷地塞h.264數據,parser會通過回調接口對解析出來的數據進行處理。在例程中,cuvidParseVideoData是在source的pfnVideoDataHandler回調中被使用的,即source獲取到視頻數據,就將其傳遞給parser。

    // the callback of source pfnVideoDataHandler
    static int CUDAAPI HandleVideoData(void* pUserData, CUVIDSOURCEDATAPACKET* pPacket) {
        assert(pUserData);
        CudaDecoder* pDecoder = (CudaDecoder*)pUserData;

        CUresult oResult = cuvidParseVideoData(pDecoder->m_videoParser, pPacket);
        if(oResult != CUDA_SUCCESS) {
            printf("error!\n");
        }

        return 1;
    }

  cuvidCreateDecoder : 該接口是用來創建decoder,通過設置一些解碼參數,會返回一個decoder的句柄。這個句柄會在之后的解碼接口中被使用。該接口的具體使用方法在例程中有詳細的參數設置,這里就繁瑣地描述了。

  cuvidDecodePicture : 該接口就是向解碼器傳遞待解碼的數據。需要說明一下,該接口是異步解碼,不能通過該接口得到解碼后的視頻數據,它只是向解碼器傳數據而已。解碼后的數據,是通過parser的pfnDisplayPicture回調得到。

4.技術點說明

庫的使用

  nvidia解碼需要使用cuda和nvcuvid兩個庫(在linux中是libcuda.so和libnvcuvid.so),使用的時候要加載它們,並使用其中一些接口。主要使用到的接口主要有:

    cuInit
    cuDeviceGetCount
    cuDeviceGet
    cuDeviceGetName
    cuDeviceComputeCapability
    cuCtxCreate
    cuCtxPushCurrent
    cuCtxPopCurrent
    cuCtxDestroy
    cuMemAllocHost
    cuMemFreeHost
    cuStreamCreate
    cuStreamDestroy
    cuMemcpyDtoHAsync
    cuvidCreateDecoder
    cuvidDestroyDecoder
    cuvidDecodePicture
    cuvidCtxLockCreate
    cuvidCtxLockDestroy
    cuvidCtxLock
    cuvidCtxUnlock
    cuvidMapVideoFrame
    cuvidUnmapVideoFrame
    cuvidCreateVideoParser
    cuvidParseVideoData
    cuvidDestroyVideoParser

注意:根據庫的版本不同,接口有的需要使用v2版本。例如:cuCtxCreate和cuCtxCreate_v2。

device內存和system內存

  使用nvidia進行硬件解碼需要了解一下device內存(可以叫顯存或設備內存)和系統內存的數據處理方法。在解碼完成后,視頻YUV數據是在device內存中的,所以需要使用nvidia提供的接口把數據弄出來。涉及的接口主要有:cuMemAllocHost, cuMemFreeHost, cuvidMapVideoFrame, cuvidUnmapVideoFrame, cuMemcpyDtoHAsync。其中,cuMemAllocHost是用來創建系統及顯卡都可訪問的系統內存。cuvidMapVideoFrame可以獲取到設備內存中指定的YUV數據地址。最后通過cuMemcpyDtoHAsync將設備內存中指定的數據copy到系統內存中。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM