最近做個播放器,調用播出板卡播出一個視頻信號用於測試,硬件那邊希望可以循環播放以便能長時間跑測試,於是現改了一個測試版本。程序跑了一晚上,第二天去看,播放器死了。
首先懷疑板卡的播出調用未返回(憑以前做板卡驅動的直觀經驗),立馬調用VS附加到卡死狀態的程序上調試(程序有DEBUG編譯),暫停程序后一條線程一條線程的排查,果然是在板卡設置數據線程里,設置音頻數據未返回(block模式)。
查看板卡SDK文檔,發現可以使用nonblock模式,於是暫改用非阻塞來排查板卡播出流程哪里有問題。
然后發現是循環播了多次視頻文件以后,視頻數據沒有設置,因此板卡無法同步設置音頻數據。
非阻塞模式下設置音頻數據,一直返回設置0字節,阻塞模式下則一直等設置視頻數據,會阻塞住不返回。這里板卡驅動的SDK API設計其實有點不妥,應該返回一個異常或者錯誤,以便應用層處理。
再切換到設置視頻相關范圍上逐模塊排查,最終確認再反復播9次視頻文件后,第10次播放視頻文件,便不能啟用D3D11的H264硬件解碼了。
FFMPEG調用的硬解碼初始化一切正常,但是avcodec_receive_frame獲得的AVFrame指針里format視頻格式為默認值0(AV_PIX_FMT_YUV420P枚舉),並非AV_PIX_FMT_D3D11硬解碼枚舉(這里使用FFMPEG做硬件解碼的同學們,一定注意檢查拿到AVFrame的格式),硬解碼上下文雖然創建成功,但是實際出來的是軟解碼數據,因而沒有調用av_hwframe_transfer_data從紋理中download內存數據,自然沒有調用板卡視頻設置接口。
因為這份調用FFMPEG編解碼的代碼其實用得比較久了,以前從未遇到這類問題。鑒於工作用機的顯卡比較古董,一開始懷疑顯卡驅動古舊問題,內部有bug?程序員遇到bug,內心潛在的第一反應就是別人的: )。但更換顯卡得動手拆機子,還是先軟件上自我排查吧。也不排除FFMPEG的編解碼確實有問題。
重新把代碼的解碼播放部分整理成一個獨立的小模塊,啟動播放然后立即關閉10次,然后再模塊內部逐個功能關閉反復測試,最終發現把顯示繪制部分關閉,模塊反復開啟關閉均解碼正常。這里可以確認排除編解碼模塊的問題。轉而排查顯示部分。
因為使用的硬解碼,解碼出來的是ID3D11Texture2D的紋理指針,D3D11的顯示部分會使用這個紋理去獲取ID3D11Device,再重新鏈接指定繪制窗口的翻轉鏈表,再去顯示繪制紋理。
還是用逐句功能關閉測試排除問題,確定初始化顯示模塊,反復重啟即正常。懷疑是顯示部分有接口未釋放,但D3D11顯示部分使用的是ComPtr智能指針,內部不可能會有未釋放問題。同樣這模塊代碼也用了很久了,以前並未發現有此問題。並且做為C/C++老手,我一直遵循創建獲取對象同時,即寫好釋放的代碼的習慣。
不過淹死的都是會游泳的,我還是寫了個測試模塊。連續去獲取10個D3D11的Com接口,立即報"引發的異常: Microsoft C++ 異常: _com_error...",跟反復重啟顯示模塊所報異常信息一樣。就是Com接口指針釋放問題。
顯示模塊內部的CComPtr指針使用和釋放肯定不會有問題,只有看外面調用部分了,就只有兩句調用。一看,果然在獲得FFMPEG解碼AVFrame中的ID3D11Texture2D紋理指針后,先使用
CComPtr<ID3D11Device> spD3DDevice;
pTexture2D->GetDevice(&spD3DDevice);
獲取ID3D11Device,然后
ID3D11DeviceContext* pD3DDeviceContext;
spD3DDevice->GetImmediateContext(&pD3DDeviceContext);
獲取ID3D11DeviceContext設備上下文,這里搞忘使用CComPtr了,使用的普通指針。
普通指針在賦值給顯示部分用的ComPtr類型指針時,內部會AddRef一次,原普通指針需要手工釋放。賦值時應該用Attach,賦值同時釋放掉原有指針。或者改為同樣使用CComPtr即可。
CComPtr<ID3D11DeviceContext> spD3DDeviceContext;
spD3DDevice->GetImmediateContext(&spD3DDeviceContext);
老司機也有撞車的時候。 U·ェ·U