通過C++/CLI使用FFMPEG庫進行視頻解碼[初步]


  所有源代碼均為共有領域,您可以對他做任何事情。

         源代碼:https://github.com/slayercat/FFMPEG_H264_VIDEO_PLAYER

 

其實我現在還不知道這么寫是不是對的,因為有種想法告訴我FFmpeg是在特指FFmpeg這個應用程序,而我們使用的是他提供的編程接口。

 

使用的是C++/CLI,做出這個選擇之前我看了一下例程,有大量的struct,如果使用C#的話,會有大量的轉換工作要做,而且我對這個並不熟悉。使用C++/CLI的話,可以同時調用C++的原生本機代碼和CLI的托管代碼。

 

很多東西其實我也不知道,因為我只用C++/CLI做過一個很小的應用程序。不過沒關系,有些東西寫在這里或許會對某些同學有所幫助。

 

關於理解程序需要的C++/CLI的一些語法

 

對於托管庫(如.net Freamwork)來說,所有的靜態方法/命名空間里頭引用類等等等等,只要不涉及到具體的成員,全部使用“::”運算符指定。例如:

 

System::Void

 

對於具體托管對象而言,全部需要使用“->”運算符,以指定對該變量的成員進行操作。例如:

 

textBox1->Text

 

所有在托管堆上進行的新建操作,全部需要使用gcnew關鍵字。該關鍵字相當於csharp里頭的new,返回的是一個叫做“句柄”的東西——這是我從網上看到的,但是實際上這個和我們熟知的,windows內核的“句柄”不是一個東西。無論如何,就叫他某個對象的句柄吧,相當於某個對象的指針,但是指向的對象沒有其他對象引用的時候會在適當的時候被垃圾回收——所以叫做托管堆。

 

句柄用“^”運算符聲明,例如:

 

array<Byte>^ out_buf = gcnew array<Byte>(bmpheader.bfSize);

 

nullptr關鍵字用於表示托管堆上的空,任何托管堆句柄在沒有gcnew之前都是nullptr,相當於csharp里頭的null關鍵字

 

知道了這些,大致就可以用csharp的方法寫了。

 

不過還有幾個東西……對於我們的混合編程非常重要……那就是托管堆和非托管堆之間的復制。要不然兩者之間是不能進行信息交換的。

 

一個類叫做IntPtr用於完成這個操作,這個類用於封裝托管堆里頭對非托管部分指針的調用。

 

IntPtr(&bmpinfo.bmiHeader)

 

單純知道這個沒啥用,但是一個拷貝函數就用到了這個:

 

Marshal::Copy函數~~用於將數據從非托管8位數組拷貝到托管8位數組。

 

大致是這樣的:

 

Copy(源的指針的intptr封裝,目標Array的句柄,要寫到目標Array的開始點,8bit數組的個數。

 

一個栗子:

 

Marshal::Copy(IntPtr(&bmpinfo.bmiHeader), out_buf, currectPoint, sizeof(BITMAPINFOHEADER));

 

有了這些C++/CLI的東西,大致就夠了,要是還有不明確的,可能就需要自己google一下了。

 

接下來繼續扯沒有扯完的題目問題

 

關於FFMPEG和libavcodec

 

FFMPEG官網上的描述表明,FFMPEG是一個開源的解決方案,用於記錄,轉換和流處理音頻與視頻。包括一個叫做libavcodec的庫~~這個庫里頭是音頻和視頻的編解碼器。

 

下載windows版本的庫

 

可以從官網down到源代碼,可惜這個東西對我們用處不大,我們需要一些別的東西……

 

注意到download里頭的這句話:

 

FFmpeg Windows Builds are available at Zeranoe FFmpeg Builds.

 

OK,這里頭有下載,我們需要的是Builds (Shared) 和Builds (Dev)。

 

前者里頭有一堆的dll,后者有一堆的lib和頭文件。

 

下32bit還是64bit就仁者見仁智者見智了,但是我剛開始本能的下載了個x64的build,結果MS的編譯器給我報告了一堆的鏈接錯誤,沒有明白是怎么回事,以為是名稱改編的問題,改了大概一個小時的設置,然后扔到一邊干別的去了,兩天后才發現自己的工程是32bit的。改為32bit的庫直接ok。

 

使用64bit的童鞋需要注意這一點。要么用32bit的庫,要么將項目先調整為64bit的。

 

用慣了csharp里頭沒有32bit和64bit的區別,這個是需要特別注意的。

 

下載好了之后,放到項目根目錄下頭,然后修改include的路徑。

 

修改路徑及lib配置

 

 

放個圖

 

位置在:配置屬性——>C/C++——>常規——>附加包含目錄。

 

這個地方建議改一下,否則就需要使用對項目根目錄的絕對路徑進行include,感覺上是比較麻煩的。

 

然后是鏈接的問題,解壓出的那堆lib放在項目的根目錄下的某個文件夾里頭。

 

然后,我們看鏈接器

 

位置在:配置屬性——>鏈接器——>附加庫目錄

 

這里配置找lib文件的目錄。然后看輸入。

 

這個里頭有個附加依賴項,將你下載到的所有lib的文件名(全名),寫在這里頭。每個一行。

 

 

確認配置是否成功

 

試着在stdafx.h里頭加如下幾行並編譯:

 

    #include "libavcodec/avcodec.h"

    #include "libavformat/avformat.h"

#include "libswscale/swscale.h"

 

如果沒有報告錯誤,說明附加包含目錄改對了。

 

但是有一點問題……編譯出來的lib文件,可以用相關工具看到,全是用的c調用格式。因此,在include語句前后,應當用extern “C”括起來,即:

 

extern "C" {

    #include "libavcodec/avcodec.h"

    #include "libavformat/avformat.h"

    #include "libswscale/swscale.h"

}

 

現在,在你項目的main里頭,寫上這一句並編譯:

 

av_register_all();

 

如果沒有問題,那么恭喜你,編譯連接成功了。

 

這個時候如果你運行,會提示缺少文件,將你的shared庫里頭的所有dll都拷貝到生成的可執行文件目錄下就可以了。

 

如果不成功怎么辦?

 

如果include提示找不到,用相對於項目根目錄的絕對路徑試一試。

 

例如:我的項目建立在D:\temp_ffmpeg\FFMPEG_H264_PLAYER\FFMPEG_H264_PLAYER下,FFMPEG頭文件在FFMPEG_INCLUDE里頭。如果配置成功了,就不用寫FFMPEG_INCLUDE,但是如果配置錯誤,那么或許你可以試試將#include "libavformat/avformat.h"改為#include "FFMPEG_INCLUDE/libavformat/avformat.h"。

 

另外,使用引號而不是尖括號,因為尖括號代表相對於系統庫路徑,這個行為可能是根據編譯器而定的,但使用引號是一定沒有錯的。

 

關於鏈接問題,第一是必須使用c的調用約定,必須用extern "C" {}將所有的include語句包括進去。如果這個不行,將你所有引用的頭文件的最前頭和最后頭都用extern "C" {}包括起來試試。

 

要是還不行,而且你又下載的是x64的庫,那么你應該慎重的考慮是不是x32和x64的鏈接問題,在C++/CLI下頭,他給我報告的錯誤莫名其妙,加了extern ”C”,但報告的錯誤似乎還是在尋找C++的命名約定,里頭有“@”符號的那種。

 

其他的就只能自行google了。

 

關於編譯問題,有個問題是沒有頭文件,有個頭文件缺失了。這或許是linux下的標准頭文件,但似乎不是win下的標准頭文件~~解決方法,google這個頭文件,並且下載下來。

 

另一個編譯問題和vc2010/vc2012的版本有關,有個叫做stdint.h的文件,定義了一堆類似於typedef   int16_t         int_fast16_t;的東西,但這條語句產生了一個問題,我從網上google到的結果是,從VS2010開始,int_fast16_t就內置了一個數據類型,導致編譯器報告一個重定義錯誤……

 

解決方法:將int_fast16_t和uint_fast16_t的typedef用#if _MSC_VER < 1600{}圍起來。

 

我只遇到這兩個和FFMPEG有關的編譯問題。

 

另外,和FFMPEG無關的編譯問題,倒是和C++/CLI有關的問題,有幾個可能大家會遇到:

 

在頭文件里頭定義函數,函數沒有錯,IntelliSense也沒有發現任何錯誤,但是編譯提示找不到xxxx之類的東西……而這些東西明明應該有的。

 

解決方法是,.h里頭只寫定義,函數的實現在.cpp里頭寫。

 

另一個是關於類找不到的問題,包括新建的窗體和自己的托管類,實際上很簡單,就是沒有include頭文件。但是C++/CLI繼承了一個東西,就是這些頭文件得寫在stdafx.h里頭。

 

FFMPEG是一個更新速度非常快的東西,官方提供了一個教程,實際上非常經典,很多網上的資源都是從這個里頭出來的。但是有一點——對於我下載的版本來說,它已經過時了。

 

教程:An ffmpeg and SDL Tutorial,位置http://dranger.com/ffmpeg/

 

過時的函數會報告編譯錯誤,明顯的,我下載的這個版本,已經去掉了一些舊的,而且是舊的里頭非常重要的函數。但大部分函數都只是換了個名字,你可以google錯誤信息,或者在函數名之后加deprecated。

 

唯一一個會給我們帶來麻煩的東西是轉換機制的改變,img_convert這個函數已經徹底的沒有了。取而代之的是sws轉換環境。

 

首先得取得一個轉換環境,使用sws_getContext函數。並使用sws_scale和取得的轉換環境進行轉換,轉換的結果會保存在一個frame里頭。

 

我這里這兩句代碼:

 

SwsContext* img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);

 

sws_scale(img_convert_ctx,pFrame->data, pFrame->linesize, 0, pCodecCtx->height,pFrameRGB->data, pFrameRGB->linesize);

 

需要注意的是,轉換環境是一個很重要的東西,因為他定義了轉換后的樣子……對於官方文檔,他保存了五個幀,使用.ppm格式進行保存,使用的是RGB格式,對應的,使用的是PIX_FMT_RGB24,而我們需要的BMP格式,對應的是PIX_FMT_BGR24。如果不處理這點,結果的色彩會不正確,有些像紅綠色盲的感覺——如果我沒有記錯的話。

 

在構造BMP文件的時候,使用了windows的兩個結構:BITMAPFILEHEADER和BITMAPINFO。一個在前一個在后。BITMAPFILEHEADER里頭沒什么東西,就說了整個頭的大小,而BITMAPINFO則有一定的重要性了,包括了寬度、高度等一系列格式信息。

 

除了構造這兩個之外,就依次將BGR格式的數據寫進去就可以了。使用提到過的Marshal::Copy函數。

碰到的一個死鎖問題

程序用一個線程處理界面,另一個線程處理播放的解碼等東西。解碼用while(b_IsThreadPlayRunning)控制,若b_IsThreadPlayRunning被控制線程設置為false,則播放結束。而FFMPEG用av_read_frame(pFormatCtx, &packet)>=0來控制是否結束,若返回值大於等於0,則繼續讀取,小於0則結束。

 

由於b_IsThreadPlayRunning可能被多個進程訪問,為了避免同步問題,必須用volatile關鍵字進行修飾。

 

而且,不能使用System::Boolean,該類型相當於bool的包裝類,但是是托管的成員,使用這個給程序造成了同步問題——對它的操作不是原子的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM