iOS開發中整合FFmpeg以及相關注意事項

TuGeLe發表於2018-06-11

FFmpeg是一套可以用來記錄、轉換數字音訊、視訊,並能將其轉化為流的開源計算機程式。它提供了錄製、轉換以及流化音視訊的完整解決方案。同時,FFmpeg是一套跨平臺的方案,所以我們可以在iOS開發中使用它來進行一些視訊與GIF的開發。

接下來,我們從編譯FFmpeg開始,到使用FFmpeg,再到使用中的一些注意事項進行總結。

一、編譯FFMpeg

在這個過程中,我們需要以下幾個資源:

1.gas-preprocessor

2.yasm

3.FFmpeg-iOS-build-script

1.gas-preprocessor

gas-preprocessor 其實就是我們要編譯FFmpeg所需的指令碼檔案。

1).下載並解壓

2).將 gas-preprocessor.pl 檔案複製到 /usr/sbin/ 目錄下,如果該目錄無法修改,那麼可將檔案複製到 /usr/local/bin/ 目錄下。

3).為 gas-preprocessor.pl 檔案開啟可執行許可權,在終端中進行如下命令:

chmod 777 /usr/sbin/gas-preprocessor.pl

chmod 777 /usr/local/bin/gas-preprocessor.pl

2.yasm

yasm 是一個完全重寫的 NASM 彙編。目前,它支援 x86 和 AMD64 指令集,接受 NASM 和氣體彙編語法,產出二進位制,ELF32,ELF64,COFF,Mach-O 的(32和64),RDOFF2的 Win32 和 Win64 物件的格式,並生成 STABS 除錯資訊的來源,DWARF 2 ,CodeView 8格式。

可以使用homebrew來安裝:

brew install yasm

3.FFMpeg-iOS-build-script

在這個檔案中,我們可以對要進行編譯的FFmpeg進行一系列的設定。

1).設定FFmpeg的版本

FF_VERSION="3.3.6"

2).設定所要支援的架構

ARCHS="arm64 armv7"

3).設定所需要的FFmpeg功能配置
該設定可在 CONFIGURE_FLAGS= 中進行,通過禁用一些不必要的功能,可以有效地減小最終庫檔案的大小,格式如下:

禁用交叉編譯:

--disable-cross-compile

支援交叉編譯:

--enable-cross-compile

4).確保該指令碼所在路徑中不包含有空格

5).需要為該指令碼所在資料夾賦予許可權

chmod 777 /Users/mdm/Desktop/ffmpeg

6).進入指令碼所在資料夾目錄,執行指令碼

./build-ffmpeg.sh

此過程可能會出現各種問題,大多數問題可以通過前往執行指令碼過程中生成的 scratch 資料夾下的 config.log 中檢視對應原因。

另外,如果遇到

xcrun -sdk iphoneos clang is unable to create an executable file.
C compiler test failed.

這是由於系統安裝了多個Xcode環境所致,可使用下面方法選定一個Xcode環境來解決問題:

sudo xcode-select -s /Application/Xcode.app

7).指令碼執行完畢,生成所需檔案

ffmpeg-3.3.6 FFmpeg原始檔

scratch 編譯過程中生成的檔案

thin 對應各個架構下的庫檔案

FFmpeg-iOS 合併各個架構之後的庫檔案

二.整合FFmpeg到專案中

1.將生成的FFmpeg-iOS資料夾拷貝至專案中。

2.新增所需依賴的依賴庫,如下:

AudioToolbox.framework

CoreMedia.framework

VideoToolbox.framework

libz.tbd

libbz2.tbd

libiconv.tbd

3.新增 Header Search Paths 設定

$(SRCROOT)/專案名/所在資料夾/FFmpeg-iOS/include

三.整合FFmpeg命令列功能

我們在使用ffmpeg時,可以直接使用該功能,通過設定命令引數,從而避免編寫大量c語言程式碼來呼叫ffmpeg庫。

1.找到如下檔案放入同一個資料夾下,並拷貝至工程目錄中:

1).從 ffmpeg-3.3.6 中找到以下檔案:

ffmpeg.h

ffmpeg.c

cmdutils.h

cmdutils.c

ffmpeg_filter.c

ffmpeg_opt.c

cmdutils_common_opts.h

2).從 scratch 資料夾下隨便一個架構資料夾中找到如下檔案:

config.h

2.修改檔案

1).前往 cmdutils.c 檔案中,註釋以下內容:

#include "compat/va_copy.h"
#include "libavdevice/avdevice.h"
#include "libavresample/avresample.h"
#include "libpostproc/postprocess.h"
#include "libavutil/libm.h"
PRINT_LIB_INFO(avdevice,   AVDEVICE,   flags, level);
PRINT_LIB_INFO(avresample, AVRESAMPLE, flags, level);
PRINT_LIB_INFO(postproc,   POSTPROC,   flags, level);

2).前往 ffmpeg_filter.c 檔案中,註釋以下內容:

#include "libavresample/avresample.h"

3).前往 ffmpeg.c 檔案中,註釋以下內容:

#include "libavdevice/avdevice.h"
#include "libavutil/internal.h"
#include "libavutil/libm.h"
#include "libavformat/os_support.h"
ff_dlog(NULL, "force_key_frame: n:%f n_forced:%f prev_forced_n:%f t:%f prev_forced_t:%f -> res:%f\n",
                    ost->forced_keyframes_expr_const_values[FKF_N],
                    ost->forced_keyframes_expr_const_values[FKF_N_FORCED],
                    ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_N],
                    ost->forced_keyframes_expr_const_values[FKF_T],
                    ost->forced_keyframes_expr_const_values[FKF_PREV_FORCED_T],
                    res);

同時,將 ffmpeg-3.3.6/libavcodec/mathops.hffmpeg-3.3.6/libavutil/reverse.h 兩個檔案複製至專案對應位置

4).前往 ffmpeg_opt.c 檔案中,註釋掉以下內容:

{ "videotoolbox_pixfmt", HAS_ARG | OPT_STRING | OPT_EXPERT, { &videotoolbox_pixfmt}, "" },
{ "vda",   videotoolbox_init,   HWACCEL_VDA,   AV_PIX_FMT_VDA },
{ "videotoolbox",   videotoolbox_init,   HWACCEL_VIDEOTOOLBOX,   AV_PIX_FMT_VIDEOTOOLBOX },

5).前往 ffmpeg.h 檔案下增加函式宣告:

int ffmpeg_main(int argc, char **argv);

6).修改 ffmpeg.c 檔案中

int main(int argc, char **argv)

為:

int ffmpeg_main(int argc, char **argv)

7).修改 ffmpeg.c 檔案中

#include "libavutil/time.h"

為:

#include "libavutil/ffmpegtime.h"

同時修改 FFmpeg-iOS/include/libavutil/time.hffmpegtime.h

8).修改執行一次 ffmpeg_main 方法後 App 退出問題
前往 cmdutils.h 中,將

void exit_program(int ret) av_noreturn;

方法宣告改為:

int exit_program(int ret);

並前往 cmdutils.c 中,將對應實現改為:

int exit_program(int ret)
{
    if (program_exit)
        program_exit(ret);

//    exit(ret);
    return ret;
}

9).修改多次呼叫 ffmpeg_main 時,訪問空指標的問題
前往 ffmpeg.c 中,在 ffmpeg_cleanup 方法中,增加處理。

term_exit();

前增加

nb_filtergraphs = 0;
nb_output_files = 0;
nb_output_streams = 0;
nb_input_files = 0;
nb_input_streams = 0;

四.使用ffmpeg

至此,我們已經整合了 ffmpeg 和 ffmpeg 的命令列工具,接下來我們就可以使用命令列來調起ffmpeg了。

使用ffmpeg命令列的大致格式如下:

ffmpeg [global_options] {[input_file_options] -i input_url} ... {[output_file_options] output_url}

對應於 ffmpeg 工具中,就是如下格式:
當然需要匯入 #import "ffmpeg.h"

int result = 1;
int argc = 19;
int i = 0;
char **arguments = calloc(argc, sizeof(char *));
if(arguments != NULL) {
    arguments[i++] = "ffmpeg";
    arguments[i++] = "-r";
    arguments[i++] = (char *)[fps UTF8String];
    arguments[i++] = "-i";
    arguments[i++] = (char *)[gifPath UTF8String];
    arguments[i++] = "-i";
    arguments[i++] = (char *)[globalPalettePath UTF8String];
    arguments[i++] = "-lavfi";
    arguments[i++] = "paletteuse";
    arguments[i++] = "-s";
    arguments[i++] = (char *)[[NSString stringWithFormat:@"%dx%d", (int)size.width, (int)size.height] UTF8String];
    arguments[i++] = "-t";
    arguments[i++] = (char *)[[self p_formatFloat:[gifInfo[kFFmpegToolGifDuration] floatValue]] UTF8String];
    arguments[i++] = "-r";
    arguments[i++] = "10";
    arguments[i++] = "-b:v";
    arguments[i++] = "1024k";
    arguments[i++] = "-y";
    arguments[i++] = (char *)[resizeGifPath UTF8String];

    result = ffmpeg_main(argc, arguments);
    free(arguments);
}

其中,一些常見的引數配置如下:

-f 強制指定編碼格式

-i 輸出源

-t 指定輸入輸出時長

-r 指定幀率,即1S內的幀數

-threads 指定執行緒數

-c:v 指定視訊的編碼格式

-ss 指定持續時長

-b:v 指定位元率

-s 指定解析度

-y 覆蓋輸出

-filter 指定過濾器

-vf 指定視訊過濾器

-an 指定去除對音訊的影響

五.常見的命令及注意事項

1.GIF轉為同等長度視訊

ffmpeg -t 3.0 -i /Users/mdm/Desktop/water.gif -t 3.0 -b:v 1024k -y /User/mdm/Desktop/water.mp4

1).如果需要新增水印,可以增加 -vf 過濾器

ffmpeg -t 3.0 -i /Users/mdm/Desktop/water.gif -vf movie=/Users/mdm/Desktop/mark.png[watermark];[in][watermark]overlay=0:0[out] -t 3.0 -b:v 1024k -y /User/mdm/Desktop/water.mp4

其中 overlay 指定水印圖片所處的位置

2).如果需要指定輸出的解析度,可為輸出指定 -s 引數,若不指定,則預設輸出為輸入源同等大小解析度

ffmpeg -t 3.0 -i /Users/mdm/Desktop/water.gif -s 480x480 -t 3.0 -b:v 1024k -y /User/mdm/Desktop/water.mp4

注意:-s之後的引數需指定為整數

2.將一組照片生成為視訊

ffmpeg -r 7 -threads 0 -c:v png -i /Users/mdm/Desktop/resources/image%d.png -t 3.0 -b:v 1024k -y /Users/mdm/Desktop/water.mp4

注意:

  • 該方法指定的是一個資料夾下的圖片路徑,ffmpeg是支援正則判斷的,所以該路徑下的圖片命名需要滿足 image%d.png 格式。
  • 該方法需指定輸入的編碼格式,即 -c:v png ,所以讀取目錄下的所有圖片必須為 png 格式。

1).如果輸入的圖片尺寸不一致,那麼所有的圖片都會從左上角開始繪製,如果需要居中展示,可以進行輸出的邊界設定,提前指定一個輸出尺寸:

ffmpeg -r 7 -threads 0 -c:v png -i /Users/mdm/Desktop/resources/image%d.png -filter scale=480:480:force_original_aspect_ratio=decrease, pad=480:480:(480-in_w)/2:(480-in_h)/2:white -t 3.0 -b:v 1024k -y /Users/mdm/Desktop/water.mp4

其中scale指定繪製畫布大小,pad格式為pad=w:h:x:y:color,其中可以使用in_w,in_h表示輸入寬高,color指定邊界顏色。

3.對視訊進行調速

ffmpeg -t 3.0 -i /Users/mdm/Desktop/water.mp4 -an -r 25 -filter:v setpts=0.5*PTS -t 6.0 -b:v 1024k -y /Users/mdm/Desktop/newWater.mp4

注意:

  • 該方法中,假設調為x倍速,則setpts=1.0 / x*PTS。
  • 該調速方法,最多支援在[0.25, 4]區間內調整。

4.根據視訊生成GIF

ffmpeg -i /Users/mdm/Desktop/water.mp4 -f gif -r 15 -t 3.0 -b:v 1024k -y /Users/mdm/Desktop/water.gif

該方法可以將視訊轉換為GIF輸出,但是在輸出之後,發現GIF的影象質量不是很高。這是由於ffmpeg預設使用一個通用的全域性調色盤來覆蓋所有的顏色區域,以此來支援含有大量內容的檔案,所以生成的GIF影象質量不是很高。我們可以為視訊提供一個特有的全域性調色盤,這樣該視訊轉換出的GIF影象就有了特定的圖片內容支援,從而可以提高影象質量。

ffmpeg -i /Users/mdm/Desktop/water.mp4 -i /Users/mdm/Desktop/waterGlobalPalette.png -lavfi paletteuse=dither=sierra2:diff_mode=rectangle -f gif -r 15 -t 3.0 -b:v 1024k -y /Users/mdm/Desktop/water.gif

通過制定一個輸入源為全域性調色盤,進而來提高輸出GIF影象質量。
全域性調色盤生成請看下一條。

5.輸出特定全域性調色盤

ffmpeg -i /Users/mdm/Desktop/water.mp4 -vf palettegen -vframes 1 -y /Users/mdm/Desktop/globalPalette.png

6.縮放GIF

ffmpeg -i /Users/mdm/Desktop/water.gif -i /Users/mdm/Desktop/globalPalette.png -lavfi paletteuse -s 480x480 -t 3.0 -y /Users/mdm/Desktop/newWater.gif

注意:

  • 在指定新的尺寸時,新尺寸不能大於輸入源舊尺寸。

7.裁剪GIF

ffmpeg -t 3.0 -i /Users/mdm/Desktop/water.png -vf crop=w=380:h=380:x=50:y=50 -t 3.0 -y /Users/mdm/Desktop/newWater.png

注意:

  • crop引數有以下幾個可選值:輸入寬 iw,輸入高 ih,輸出寬 ow,輸出高 oh
  • crop引數的x,y預設值為:x = (iw - ow) / 2.0;y = (ih - oh) / 2.0;
  • 在裁剪命令中,crop的引數需滿足 w + x 不大於輸入源寬度, h + y 不大於輸入源高度。
  • 該命令需要有 crop filter 的支援,所以在FFmpeg的功能配置中,不能有 –disable-filter=crop的出現。

六.其他注意事項

  • ffmpeg 是需要對實體檔案進行處理的,所以無論是輸入源還是輸出源,都必須對應實體檔案,同時在 ffmpeg 的命令中,需指定路徑。對於輸出源來說,如果不指定 -y,即覆蓋輸出,那麼如果輸出原始檔已經存在,ffmpeg 命令會執行失敗。
  • ffmpeg 命令需要阻塞執行緒來處理,所以為了避免主執行緒的阻塞,建議放入子執行緒進行處理。
  • ffmpeg 的輸入和輸出需要知道明確的編碼格式:輸入編碼可以通過解碼來獲取到,但是輸入編碼如果指定的話,就必須與實體檔案編碼一致,否則解碼會出錯;輸出源同樣需要指定編碼格式,如果沒有明確指定輸出的編碼格式,那麼需要在輸出路徑中指定字尾,否則會出現編碼出錯。
  • 如果需要對 GIF 進行調速的話,直接通過指定 GIF 的 -r 來生成新的 GIF 是不合適的,因為 GIF 的幀間隔可以不一致,而通過設定 -r,就將所有幀間隔設定為一致,這樣生成的效果與理想效果不一致。可通過將 GIF 轉為視訊,然後調整視訊的速度生成新的視訊,進而再生成新 GIF 來達到目的。
  • 由於 ffmpeg 的命令列工具中,有許多引數為全域性變數,所以為了保證使用的正確,我們需要保證在一個時間點,只有一次 ffmpeg_main() 方法的呼叫。
  • 在使用全域性調色盤的時候,需要注意與水印的搭配處理。如果全域性調色盤是在新增水印之前就已經生成,那麼新增水印之後,使用該全域性調色盤生成 GIF,水印會被全域性調色盤校正,從而在 GIF 上顯示不出來。

七.參考文獻

相關文章