天天看點

組裝FFmpeg,為我所用

小白:哇,終于要組裝FFmpeg了?但我直接開FFmpeg提供的整車,比如ffmpeg、ffplay、ffprobe等,也很帶勁啊。 花滿樓:整車雖好用,但畢竟體驗受限,你總不能讓使用者每次都運作你的腳本或指令行吧。而且,如果要在移動平台使用FFmpeg,就隻能組裝出來了,因為現在整車是不能在移動平台上跑的。是以,整車,也就在你自己的電腦、或者伺服器上跑跑就好,而組裝是勢在必行的,否則還有什麼挑戰好玩? 小白:我喜歡挑戰,來吧!

本文解決一個問題:我的代碼,調用FFmpeg的函數。

(一)編譯FFmpeg

調用FFmpeg的第一步是編譯FFmpeg。之前在講調試時介紹過如何編譯出macos上使用的版本,回顧一下,無非就是先configure再make。

現在介紹更流行的,在iOS跟Android平台使用的版本。主線還是先configure再make,但會有更多的細節要考慮。

(1)編譯環境準備

(a)pkg-config找第三方庫

FFmpeg在編譯時經常要使用到第三方庫(比如x264、rtmp等),編譯器在查找這些第三方庫的頭檔案與庫檔案時,使用的是程式pkg-config。

pkg-conifig給編譯器提供路徑與連結選項。第三方庫在make install時會生成pc檔案并拷貝到系統目錄,而pkg-config就是從這個pc檔案讀取出路徑資訊。

可以這樣得到第三方庫的路徑資訊:

pkg-config --cflags --libs librtmp

pkg-config安裝:

brew install pkg-config

可以設定PKG_CONFIG_PATH這個環境變量,讓pkg-config到這個目錄下面去找pc檔案,如果不設定,則預設在/usr/local/lib/pkgconfig目錄下面查找。

小白:說這麼多,其實隻要執行:brew install pkg-config 即可? 花滿樓:......差不多。

(b)clang編譯器

此項隻在編譯iOS版本時才需要

我在mac機上編譯FFmpeg,在編譯之前已經安裝過xcode,是以clang已經存在,而brew也早就存在。

小白:clang是什麼? 花滿樓:一個編譯器,iOS版本的FFmpeg就用它來編譯。 小白:這麼簡單? 花滿樓:你要複雜也可以很複雜的,clang參數的使用、靜态代碼走查、與gcc的對比,這一系列的東西,你要是感興趣可以找 西門吹雪。 小白:……為什麼你老提他? 花滿樓:因為不能把關聯的知識都在這裡展開啊。好吧,主要因為他昨晚請我喝酒了。 小白:……

(c)asm編譯器

x264或FFmpeg等,都有彙編代碼,編譯這些彙編代碼,要使用更先進的編譯腳本來處理,而mac系統沒有這樣的腳本,這個腳本是:gas-preprocessor.pl:

git clone git://github.com/mansr/gas-preprocessor.git sudo cp -f gas-preprocessor/gas-preprocessor.pl /usr/local/bin/ chmod +x /usr/local/bin/gas-preprocessor.pl

另一個需要的工具是yasm彙編編譯器:

brew install yasm 

或者:

wget http://www.tortall.net/projects/yasm/releases/yasm-1.2.0.tar.gz tar zxvf yasm-1.2.0.tar.gz cd yasm-1.2.0/ cat INSTALL ./configure --prefix=/usr/local/yasm make sudo make install export PATH="$PATH:/usr/local/yasm/bin"

(d)NDK工具包

此項隻在編譯Android版本時才需要

可以使用ndk-r9d版本,或者最新的版本,來編譯FFmpeg。

下載下傳位址:https://developer.android.google.cn/ndk/downloads/index.html

(2)FFmpeg源碼下載下傳

git clone git://source.ffmpeg.org/ffmpeg.git ffmpeg

或者: 

curl -0  http://ffmpeg.org/releases/ffmpeg-${VERSION}.tar.bz2 tar jxvf ffmpeg-${VERSION}.tar.bz2

(3)編譯腳本

不必自己重寫了,一些開源的項目就已經很好,比如: https://github.com/yixia/FFmpeg-Vitamio.git

在這個項目裡面,有編譯Android跟iOS平台的相應腳本,而且有相應的優化處理。

小白:直接configure再make很酷啊,還寫什麼腳本? 花滿樓:在移動平台使用的庫都很注重兩個東西,一個是性能,另一個是體積大小。一個好的腳本,既要根據不同的硬體類型作編譯上的優化,也要根據軟體需求裁剪FFmpeg的功能使得出來的庫盡可能小(畢竟FFmpeg的功能并非全部都用上)。

(4)腳本修改

小白:我差點就開始編譯了!你突然叫停改劇本? 花滿樓:改改更精彩嘛,給你加戲啦。

configure關鍵參數說明(并非兩平台通用)

    指定指令集:     --extra-cflags='-arch armv7s' --extra-ldflags='-arch armv7s'     指定cpu類型:     --arch=arm --cpu=cortex-a9       #應該根據不同的指令集使用不同的cpu優化;--arch=arm64,指定具體指令架構也可以     指定系統:     --target-os=darwin     指定sdk:     --sysroot=/Applications/Xcode.app/.../xxx.sdk     指定編譯器:     --cc=xxx/clang     指定庫生成目錄:     --prefix=build     指定使用的muxer/demuxer/encoder/decoder等:     --enable-muxer=mp4

基本上使用上面介紹的腳本就可以編譯了,但有時候也可以作一些修改,比如要加入第三方庫時,或者要對某個指令集作優化時,等等。

注意點

* --sysroot需要指定。iOS時為....sdk/,不包括usr/inclue;Android時是編譯鍊的目錄。 * extra-cflags跟extra-ldflags要指定-arch(iOS)或-march(Android)。 * 在xcode8.3.2(sdk為10.3)上,armv7/armv7s/arm64不能使用"-mfloat-    abi=hard"選項,并且arm64要指定-mcpu=cortex-a53。
小白:怎麼這麼多細節? 花滿樓:對于實際項目來說,FFmpeg的編譯是關鍵的一步,本來就應該多花時間去研究 一些關鍵的細節--功能、性能跟體積大小都很重要。

(5)開始編譯

運作腳本即可。最終會生成二進制庫,比如iOS一般為靜态庫(.a檔案),而Android一般為動态庫(.so檔案)。

(二)使用FFmpeg

拿到編譯後的二進制庫與頭檔案後,就可以調用FFmpeg了(設定路徑等,讓編譯器能找到庫檔案與頭檔案)。

小白:是要上代碼了嗎? 花滿樓:是的。代碼作了一定的封裝,不必在意細節,隻要大概知道是怎麼調用FFmpeg的函數即可。後續我會詳細講解一些細節的地方。也不用理會編譯運作的問題,我會在講更具體的FFmpge的應用時講清楚。
// 調用FFmpeg示例 extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" } void dump_file_format(const char* filepath) {     av_register_all();     av_log_set_level(AV_LOG_DEBUG);     AVFormatContext* formatContext = avformat_alloc_context();     AVCodecContext* codecContext = NULL;     int status = 0;     bool success = false;     int audioindex = -1;     status = avformat_open_input(&formatContext, filepath, NULL, NULL);     if (status == 0) {         status = avformat_find_stream_info(formatContext, NULL);         if (status >= 0) {             for (int i = 0; i < formatContext->nb_streams; i ++) {                 if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {                     audioindex = i;                     break;                 }                }             if (audioindex > -1) {                 codecContext = formatContext->streams[audioindex]->codec;                 AVCodec* codec = avcodec_find_decoder(codecContext->codec_id);                 if (codec) {                     status = avcodec_open2(codecContext, codec, NULL);                     if (status == 0) {                         success = true;                      }                 }         }     }     if (success) {         av_dump_format(formatContext, 0, filepath, false);         av_log(NULL, AV_LOG_DEBUG, "format and decoder sucessful, and now in decoding each frame\n");         printf("sample_rate=%d, channels=%d\n", codecContext->sample_rate, codecContext->channels);     avformat_free_context(formatContext); int main(int argc, const char *argv[]) {     const char filepath[] = "test2.mp3";         dump_file_format(filepath);     return 0;

https://mp.weixin.qq.com/s/ZDOOzIxhbia8KGXb4G0ZpA