英伟达硬件解码器分析
這篇文章主要分析 NVCUVID 提供的解碼器,里面提到的所有的源文件都可以在英偉達(dá)的?nvenc_sdk?中找到。
解碼器的代碼分析
SDK 中的 sample 文件夾下的 NvTranscoder 中包含了編碼器和解碼器的用法,編碼器的內(nèi)容不在這里分析,因為 FFMPEG 中已經(jīng)包含了相關(guān)的代碼,不需要其他的處理。
解碼器在 SDK 中有一份封裝,主要是 NvTranscoder 下的 VideoDecoder 類。目前這個類的具體用法還不是特別的清楚。分析將會從 main 函數(shù)開始。
main
NvTranscoder 有一個單獨的文件,執(zhí)行邏輯從?main()?函數(shù)開始。
cuInit(0) 應(yīng)該是初始化 cuda 的相關(guān)代碼。目前沒有找到定義,估計類似于FFMPEG 中的?av_register_all。
105 行之前的代碼是編碼相關(guān)的代碼, 85 行以前設(shè)置各種編碼參數(shù),87 行分析從命令行讀入的參數(shù),100 行打開目標(biāo)輸出文件。
110 行開始代碼應(yīng)該是和解碼相關(guān)的核心代碼。cuDeviceGet,?cuCtxCreate?cuCtxPopCurrent,?cuvidCtxLockCreate?應(yīng)該是固定寫法。初始化一些內(nèi)部機(jī)制。
120 行的?InitVideoDecoder?是解碼器創(chuàng)建的地方。這個函數(shù)需要認(rèn)真的分析,整個解碼器的關(guān)鍵代碼應(yīng)該就在這里面。
137 行初始化 119 行中創(chuàng)建的 FrameQueue。這個隊列應(yīng)該相當(dāng)于解碼器和編碼器之間的一個緩沖區(qū),解碼器方內(nèi)容進(jìn)去,編碼器從中取內(nèi)容出來。
139 行到 180 都是關(guān)于編碼器的設(shè)置。這里不做詳細(xì)的分析。
187 行的注釋顯示?pthread_create?創(chuàng)建出來解碼線程,所以解碼工作是由這個線程完成的。線程執(zhí)行的函數(shù)是?DecodeProc?這個函數(shù),而這個函數(shù)只不過是調(diào)用的解碼器的?Start()?方法。
195 行的代碼注釋來看,這后續(xù)的代碼都是編碼相關(guān)的代碼,這里不做分析。最后在245 行和 246 行的未知?cuvidCtxLockDestroy?和?cuCtxDestroy?應(yīng)該是對應(yīng)于110 行的那些代碼。
VideoDecoder.cpp
這個文件實現(xiàn)了解碼器的封裝類 CudaDecoder, 這個類是整個硬件解碼器實現(xiàn)的關(guān)鍵,但是這個類其實比較簡單。它在?main?函數(shù)中涉及到的方法只有InitVideoDecoder,?GetCodecParam,?Start?和?GetDecoder?這四個。
InitVideoDecoder
這個方法是首先被調(diào)用的方法。它負(fù)責(zé)編碼器的初始化操作。
從 VideoDecoder.cpp 中的實現(xiàn)來看,初始化主要包括三個部分:
創(chuàng)建視頻源
視頻源的參數(shù)是 CUVIDSOURCEPARAMS,其中設(shè)置了一個?pfnVideoDateHandler,從字面上理解它是一個視頻數(shù)據(jù)的回調(diào)處理函數(shù)。
創(chuàng)建視頻源的方法是?cuvidCreateVideoSource()?函數(shù),目前來說這個函數(shù)的致命問題在于它的接收 videoPath 作為參數(shù),這似乎意味著它只能處理文件視頻源。這個函數(shù)的函數(shù)原型定義在?nvcuvid.h?這個頭文件中,這個頭文件只定義了下面這些和視頻源相關(guān)的接口:
cuvidCreateVideoSource(); cuvidCreateVideoSourceW(); cuvidDestroyVideoSource(); cuvidSetVideoSourceState(); cuvidGetVideoSourceState(); cuvidGetSourceVideoFormat(); cuvidGetSourceAudioFormat();從接口來看只有?cuvidCreateVideoSource(); cuvidCreateVideoSourceW();?這兩個函數(shù)可用,而它們唯一的區(qū)別在于接收不同的文件路徑字符串,前者是普通字符而后者是寬字符。
目前暫時沒有其他的資料表明可以創(chuàng)建非文件類型的視頻源,所以這個解碼器的用處估計不會太大,至少在傳屏應(yīng)用中的用處會相對較小。
獲取視頻源的參數(shù),并創(chuàng)建 cuvid 庫的解碼器
視頻源的參數(shù)信息在創(chuàng)建視頻源之后可以通過 cuvidGetSourceVideoFormat() 函數(shù)獲得,在?InitVideoDecoder()?函數(shù)中獲取參數(shù)最詭異的地方在于 111 行創(chuàng)建了一個 CUVIDOFORMATEX 類型的變量?oFormatEx,然后讓?oFormat?引用這個變量的 format 字段。在調(diào)用?cuvidGetSourceVideoFormat?之后竟然可以直接訪問oFormatEx?這個變量的?raw_seqhdr_data?字段,個人估計它的內(nèi)部實現(xiàn)使用了類似?container_of?這樣的技術(shù)訪問了?oFormatEx。但是為什么這樣設(shè)計不得而知。
創(chuàng)建解碼器的函數(shù)是?cuvidCreateDecoder(),這個函數(shù)的原型定義在 cuviddec.h文件中。
cuvidcreatedecoder(cuvideodecoder *, CUVIDDECODECREATEINFO *); cuvidDestroyDecoder(CUvideodecoder);解碼器的參數(shù)是通過?CUVIDDECODECREATEINFO?傳遞的,這個結(jié)構(gòu)體的大部分字段都是通過前面獲得的?oFormat?中的信息獲得。
創(chuàng)建視頻源的解析器
初始化的最后一步是創(chuàng)建一個視頻源的解析器,其中設(shè)置了三個回調(diào)函數(shù),HandleVideoSequence,?HandlePictureDecode,?HandlePictureDisplay, 在nvidia?的文檔中并沒有說這些回調(diào)函數(shù)會在什么時候調(diào)用,也沒有說明這些回調(diào)函數(shù)要完成的事情是什么,只能從名字中猜測?HandlePictureDecode這個函數(shù)是用來解碼的。
Start
在?main?函數(shù)的解碼線程函數(shù)中只調(diào)用了 CudaDecoder 類的 Start 函數(shù)。而Start?函數(shù)本身也非常的簡單,只不過調(diào)用了 cuvidSetVideoSourceState() 把狀態(tài)變成 cudaVideoState_Started 然后一直取狀態(tài)直到狀態(tài)不再是?started。
從這個函數(shù)的實現(xiàn)來看,它的內(nèi)部應(yīng)該在把視頻源設(shè)置為cudaVideoState_Started?狀態(tài)之后開始讀取視頻源(文件)中的數(shù)據(jù)。然后通過回調(diào)函數(shù)進(jìn)行處理。應(yīng)該是首先調(diào)用?HandleVideoData(), 個人猜測這個函數(shù)在數(shù)據(jù)從文件中讀取出來之后會被調(diào)用來解析原始數(shù)據(jù),HandleVideoSequence?這個函數(shù)沒有太大的用途,只是一些參數(shù)的檢測而已。HandlePictureDecode?應(yīng)該是在成功解析到數(shù)據(jù)幀的時候調(diào)用,這個函數(shù)調(diào)用了?cuvidDecodePicture?解碼數(shù)據(jù)。數(shù)據(jù)解碼出來之后會調(diào)用?HandlePictureDisplay?函數(shù),該函數(shù)把數(shù)據(jù)放入到數(shù)據(jù)緩沖區(qū)?FrameQueue?中以便編碼器能夠把數(shù)據(jù)取出來。
總結(jié)
使用 CudaDecoder 首先需要調(diào)用 cuvidCreateVideoSource 創(chuàng)建一個文件視頻源,然后調(diào)用?cuvidGetSourceVideoFormat?從文件中讀取解碼參數(shù)信息并使用參數(shù)信息創(chuàng)建一個?CUvideodecoder?解碼器,之后再創(chuàng)建一個視頻源解析器,設(shè)置回調(diào)函數(shù)處理視頻的解碼。
上面的初始化完成之后調(diào)用?cuvidSetVideoSourceState?把視頻源的狀態(tài)設(shè)置為cudaVideoState_Started,之后庫的內(nèi)部會開始讀文件,把讀取的數(shù)據(jù)交給HandleVideoData?解析,解析完成之后會把數(shù)據(jù)交給?HandlePictureDecode?調(diào)用?cuvidDecodePicture?進(jìn)行解碼。在解碼完成之后調(diào)用HandlePictureDisplay?把數(shù)據(jù)放入到 FrameQueue 緩沖區(qū)里面。
補(bǔ)充:video source 和 nvcuvid
在 SDK 給出的例子中,數(shù)據(jù)是通過?video source?接口來提供的。但是這并不意味著我們在編寫程序的時候只能使用它提供的?video source?接口。根據(jù)官方文檔?中第三小節(jié)最后給出的解釋
Note: The low level decode APIs are supported on both Linux and Windows platforms. The NVCUVID APIs for Parsing and Source Stream input are available only on Windows platforms.
NVCUVID 的?video source?只在?windows?平臺可用,不過從最新的nvenc_sdk?的代碼來看,videosource 和 sourcepraser 在 linux 平臺下也是可用的。只不過從接口來看,這兩個 API 只能用于文件的解析。
在文檔的第四小節(jié) 4.2 中有這么一段話:
For Linux platforms, you will need to write your own video source andparsing functions that connect to the Video Decoding functions.
這一點明確說明,其實我們可以不使用它本身的?video source?接口,使用自己的接口提供視頻源,然后使用?nvcuvid?最底層的解碼接口對數(shù)據(jù)進(jìn)行解碼和后續(xù)處理。
MAP
在 nvcuvid 的官方文檔中給出的接口中,最詭異的兩個接口是
cuvidMapVideoFrame() cuvidUnmapVideoFrame()這兩個函數(shù)好像是用于處理解碼之后的數(shù)據(jù)的,但是這其中的原理是什么并不清楚,有待后續(xù)研究。
總結(jié)
以上是生活随笔為你收集整理的英伟达硬件解码器分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: wpsmac和pc版的区别_WPS Ma
- 下一篇: c语言实现通讯录管理