本文的主要目的是對近期進行的nvidia硬件解碼工做的記錄和總結。至於爲何研究nvidia硬件解碼的具體內容,其實主要是爲了在項目中可以利用nvidia的硬件解碼和編碼能力,提升單機的編解碼並行能力。截止當前,nvidia的硬件編碼官方提供了nvenc的方法,且在ffmpeg中已經增長了對nvenc的編碼庫。對於硬件解碼,官方提供了基於cuda的解碼方法,可是ffmpeg中尚未相應的解碼庫。因此,個人目的就是調研一下這個硬解方案,並將其自定義增長到ffmpeg中。html
官方提供的資料比較少,只包括一頁的視頻解碼器介紹和示例代碼。linux
吐槽一下:官網那個一頁的介紹參考量真不大,主要仍是參考例程代碼。異步
官網提供的例程代碼解壓後以下圖所示,由於是調用解碼,因此主要參考了"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
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回調獲得。
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。
使用nvidia進行硬件解碼須要瞭解一下device內存(能夠叫顯存或設備內存)和系統內存的數據處理方法。在解碼完成後,視頻YUV數據是在device內存中的,因此須要使用nvidia提供的接口把數據弄出來。涉及的接口主要有:cuMemAllocHost, cuMemFreeHost, cuvidMapVideoFrame, cuvidUnmapVideoFrame, cuMemcpyDtoHAsync。其中,cuMemAllocHost是用來建立系統及顯卡均可訪問的系統內存。cuvidMapVideoFrame能夠獲取到設備內存中指定的YUV數據地址。最後經過cuMemcpyDtoHAsync將設備內存中指定的數據copy到系統內存中。