Nvidia硬解碼總結

Nvidia硬解碼總結

1.前言

  本文的主要目的是對近期進行的nvidia硬件解碼工做的記錄和總結。至於爲何研究nvidia硬件解碼的具體內容,其實主要是爲了在項目中可以利用nvidia的硬件解碼和編碼能力,提升單機的編解碼並行能力。截止當前,nvidia的硬件編碼官方提供了nvenc的方法,且在ffmpeg中已經增長了對nvenc的編碼庫。對於硬件解碼,官方提供了基於cuda的解碼方法,可是ffmpeg中尚未相應的解碼庫。因此,個人目的就是調研一下這個硬解方案,並將其自定義增長到ffmpeg中。html

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

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

2.例程介紹

  官網提供的例程代碼解壓後以下圖所示,由於是調用解碼,因此主要參考了"NvDecodeD3D9"和"NvTranscoder"的代碼。ide

  總的來講,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對解碼後的數據處理的回調函數,能夠在該回調中對已解碼的數據進行獲取(從顯存到系統內存)並處理。this

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,主要參數是設置三個回調函數,實現對解析出來的數據的處理。code

//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的句柄。這個句柄會在以後的解碼接口中被使用。該接口的具體使用方法在例程中有詳細的參數設置,這裏就繁瑣地描述了。htm

  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到系統內存中。

相關文章
相關標籤/搜索