[C#] FFmpeg 音視訊開發總結

2023-06-08 12:01:08

為什麼選擇FFmpeg?

  1. 延遲低,引數可控,相關函數方便查詢,是選擇FFmpeg作為編解碼器最主要原因,如果是處理實時流,要求低延遲,最好選擇是FFmpeg。
  2. 如果需要用Opencv或者C#的Emgucv這種庫來處理視訊流,也多是用FFmpeg做編解碼然後再轉換影象資料給Opencv去處理。用Opencv編解碼延遲很高。
  3. 其他的庫多是基於FFmpeg封裝,如果做一個視訊播放器,像vlc這種庫是非常方便的,缺點是臃腫,需要手動剔除一些檔案,當然也有一些是基於FFmpeg封裝好的視訊播放器庫,也能快速實現一個播放器。
  4. 如果是載入單Usb介面中的多Usb攝像頭,FFmpeg這時就無能為力了,經過測試使用DirectShow能夠實現。AForge一個很好的學習樣例,它將DirectShow封裝的很好,能輕鬆實現載入單Usb介面中的多Usb攝像頭(不過它很久沒更新了,目前無法設定攝像頭引數,也沒有Usb攝像頭直接錄製,所以我把它重寫了),當然使用其他DirectShow的庫也是可以的。
  5. 寫此文章時才發現CaptureManager這個2023年4月釋出的非常簡便好用的基於D3D封裝的音視訊庫,它的官方樣例非常豐富,能實現很多功能。我嘗試了執行了他的官方樣例,開啟相同規格的Usb攝像頭,發覺cpu佔用是FFmpeg的兩倍。

如何學習FFmpeg?

記錄一下我是如何學習FFmpeg。首先是C#使用FFmpeg基本上用的是FFmpeg.autogen這個庫。也可以使用FFmpeg.exe,先不談論FFmpeg.exe的大小,我嘗試過從exe中取資料到C#前端顯示,相同引數情況下,延遲比使用FFmpeg.autogen高,主要是不能邊播放邊錄製(可以用其它的庫來錄製,但是效率比不上只使用一個庫)。

當然如果只需要部分功能也可以自己封裝FFmpeg(太花時間了,我放棄了。如果是專門從事這一行的可以試試)。

學習FFmpeg.autogen可以先去Github上下載它的樣例(其實樣例有個小問題,後面說),學習基礎的編解碼。

後面有人把官網的C++的樣例用FFmpeg.autogen寫了一遍,我把樣例壓縮好放夸克網路硬碟了:https://pan.quark.cn/s/c579aad1d8e0

然後是檢視一些部落格和Github上一些專案,瞭解編解碼整體架構,因為FFmpeg很多參考程式碼都是c++的所以我基本是參考C++寫C#,寫出整體的編解碼程式碼。

無論是編解碼還是開發Fliter都會涉及到很多引數設定。要查詢這些引數,我先是去翻部落格,最後還是去FFmpeg官網(官網檔案,編解碼引數很全),當然製作視訊濾鏡和一些其他功能,也是參考官網的引數。

對於部分基礎函數(有些函數會把幀用掉就釋放,要注意)檢視FFmpeg的原始碼,理解原理。

對於一些概念性的東西,我是翻閱碩博論文(一般都有總結這些)。

 C#使用FFmpeg需要注意什麼?

  1. FFmpeg.autogen是有一個缺點的,它是全靜態的,不支援多執行緒(這個我問作者了),所以用多程序,而用多程序渲染到同一畫面,可以參考我上一篇MAF的文章。
  2. 尤其要注意幀釋放,編解碼的幀如果沒有釋放是一定會產生記憶體漏失的,而且速度很快。
  3. 其次是c# 要將影象資料渲染到介面顯示,最最好使用WriteableBitmap,將WriteableBitmap和繫結到一個Image然後更新WriteableBitmap。我記得在一篇部落格中提到高效能渲染,使用MoveMemory來填充WriteableBitmap的BackBuffer,核心程式碼如下。
    [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
        private static extern void MoveMemory(IntPtr dest, IntPtr src, uint count);
     writeableBitmap.Lock();
            unsafe
            {
                 fixed (byte* ptr = intPtr)
                  {
                    MoveMemory(writeableBitmap.BackBuffer, new IntPtr(ptr), (uint)intPtr.Length);
                   }
           }
           writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
    
    writeableBitmap.Unlock();
    這樣處理有個致命的缺點。WriteableBitamp的寬高必須為2的整數倍,即使是修正過大小,當傳入資料為特殊尺寸使用此方法時還是會出現顯示異常的情況。所以還是老實使用WriteableBitmap的WritePixels。
  4. 對於FFmpeg很多函數都是會返回錯誤資訊,一定要將錯誤資訊記錄到紀錄檔,方便查詢和檢視(基本每個函數要加錯誤資訊判斷)。
  5. 軟編碼會佔用大量的CPU資源,所以最好採用寫死。FFmpeg有一個查詢編解碼器的函數,它並不能檢視硬體編碼器。如果要使用硬體加速查詢編解碼器最好是用其他方式獲取系統裝置或者直接一個一個開啟NVDIA和QSV等加速,都失敗了再啟用軟編解碼。
  6. QSV寫死要求輸入的畫素格式必須為AVPixelFormat.AV_PIX_FMT_NV12,如果是硬解碼出的資料,可以直接編碼,否則需要新增格式轉換。FFmepg.autogen的官方樣例中有格式轉換函數,但由於它沒有指定轉換後的格式會出問題(踩坑)。
  7. 儘量少的格式轉換,或者幀複製。這兩種方式會提高cpu和記憶體使用率同時也會有更高的延遲。
  8. 在製作FFmpeg的帶有文字的Filter時,將需要使用的字型複製到專案目錄然後指定字型位置而不是呼叫系統的字型(不知道是版本原因還是什麼問題,一用系統字型就會產生記憶體漏失)。
  9.  注意編解碼資料的格式。一些老的格式,雖然解碼沒有什麼問題(ffmpeg 會有提示)但是編碼是不支援的,出現這種問題,程式會直接死掉(踩坑)。
  10. 解碼時可以通過解碼資料自動搜尋硬體解碼器,而硬體編碼需要手動指定編碼器(可以通過,查詢並自動選擇GPU來實現自動選擇)。
  11. 多執行緒實現播放同時錄製時,最好採用幀複製ffmpeg.av_frame_clone(hwframe)不用對同一個幀進行操作。當然也可以不用多執行緒,同一個幀在播放完成後進行,錄製。

  暫時只想到這些,有其他的想法再更新     

如果有任何錯誤歡迎批評指正。