Android 接入FFmpeg命令-低成本完成各類音視訊操作

語言: CN / TW / HK

零、寫在前面

“我正在參加「掘金·啟航計劃」” 利用FFmpeg的基礎庫方法我們能完成大多數的音視訊操作,但同時FFmpeg也封裝了常用的操作,只需要我們執行命令即可,極大的降低了開發成本。例如實現一個轉碼的業務,哪怕簡單點來說我們也要要解碼-重取樣-編碼。這其中我們要考慮很多條件,音訊資訊導致的不同處理,異常的捕捉等等,麻煩不說,相容性都有一段路走。所以如果不是因為要對音訊有特殊的邏輯處理,我們完全可以用命令來解決。但是看看FFmpeg的原始碼,它的命令是針對命令列環境的,和Android上執行還是有一定的差距,所以我們需要在原基礎上做一些Android端的適配,使我們Android端也能輕鬆使用命令,低成本完成各類音訊操作。後續操作都在已編譯好Android端so庫的情況下進行,如果還沒有走過這一步的朋友可以先看看我之前的文章 《Unbuntu環境編譯 Android平臺可用ffmpeg(帶三方庫fdk-aac和lame)》

一、匯入資源和編譯

1.1、匯入相關原始檔

FFmpge原始碼下有一個fftools目錄,裡面就是一些ffmpeg的命令列程式,我們將他們匯入進來,進行一定的修改後再自行編譯成我們的lib。

首先是對庫檔案進行相應的修改,因為ffmpeg.c在windows上的目的是編譯成一個可執行的應用,所以它的入口是main 通過命令列傳參,所以我們這裡需要把main函式改一下,改成一個我們具體功能的名字,例如我喜歡叫它exc,然後再再標頭檔案中宣告我們剛改的函式名,這樣它就變成了一個庫檔案。我們在jni的地方呼叫exc進行傳參,就達到了電腦端命令列的效果。

還有一個地方,ffmpeg如果指令執行出錯是執行的退出程式指令,我們在Android端肯定不行,那不然指令執行不成功就一個閃退這誰都頂不住,所以我們需要把exit_program函式的exit(ret);去掉,這裡我們可以做我們自己的邏輯,進行自己的錯誤碼回撥。 java 原始碼 void exit_program(int ret) { if (program_exit) program_exit(ret); exit(ret); } 修改後 void exit_program(int ret) { if (program_exit) program_exit(ret); }

編譯過程中發現會有些庫丟失,可以刪掉也可以把庫引進來.主要看這些庫是否是你所需要的功能,例如有些是一些列印函式缺失的我不需要我就直接刪掉也懶得導包了。

1.2、cmdkelist的編寫

首先是引入標頭檔案目錄 以我的檔案目錄為例我的為 include_directories(ffmpeg/fftools),後續的依賴完整如下: ``` add_library( # Sets the name of the library. ffmpeg-cmd # Sets the library as a shared library. SHARED

# Provides a relative path to your source file(s).
ffmpeg/fftools/cmdutils.c
ffmpeg/fftools/ffmpeg.c
ffmpeg/fftools/ffmpeg_opt.c
ffmpeg/fftools/ffmpeg_filter.c
ffmpeg/fftools/ffmpeg_hw.c
ffmpeg-cmd.c
)

target_link_libraries( # Specidefies the target library. ffmpeg-cmd # Links the target library to the log library # included in the NDK. fdk-aac mp3lame avdevice postproc avfilter avformat avcodec avutil swresample swscal ) ```

cmdutils.c、ffmpeg.c、ffmpeg_opt.c、ffmpeg_filter.c、ffmpeg_hw.c、ffmpeg-cmd.c都有一定的依賴關係,這幾個C檔案共同編譯成一個庫ffmpeg-cmd,ffmpeg-cmd是我的JNI層方法,負責雙邊溝通,fftools中依賴了其它例如解碼,濾鏡,重取樣等外部庫,也是我們之前編譯了的其它庫,我們需要把它們作為ffmpeg-cmd的庫連結進去。

二、加入執行回撥

對於命令的執行很多時候是一個耗時操作,我們UI不可能在那裡乾等,所以我們需要給命令執行加入回撥,告訴Java層指令的執行狀態,好做一些進度的變更,成功或失敗的處理。最基本的我們給指令執行加入進度,失敗,成功的回撥。

2.1、執行進度回撥

ffmpeg.c裡有一個print_report方法,裡面主要是一個它自身進度的列印,我們看其中一段主要程式碼

static void print_report(int is_last_report, int64_t timer_start, int64_t cur_time) { ...... secs = FFABS(pts) / AV_TIME_BASE; us = FFABS(pts) % AV_TIME_BASE; mins = secs / 60; secs %= 60; hours = mins / 60; mins %= 60; hours_sign = (pts < 0) ? "-" : ""; //program_progress(secs); //本身沒有,這是我們自己加的,這是簡單邏輯,可以自己再寫邏輯 bitrate = pts && total_size >= 0 ? total_size * 8 / (pts / 1000.0) : -1; speed = t != 0.0 ? (double)pts / AV_TIME_BASE / t : -1; ...... } 在這裡它計算出了已經執行完成的檔案時長,同樣的我們傳入函式指標,把這個時長傳到Java層,稍加計算就能得到我們需要的進度。

``` //ffmpeg.h / * Register a register_progress. / void register_progress(void (cb)(long progress));

//ffmpeg.c static void (program_progress)(long progress); void register_progress(void (cb)(long progress)) { program_progress = cb; } ```

2.1、執行成功/失敗回撥

不管成功還是失敗,ffmpeg都會走void exit_program(int ret) 方法,它自身會先走一個ffmpeg_cleanup方法,做一些他自己的收尾工作。我們可以在它自身方法的後面插入我們自己的回撥方法,就是我們自己傳入一個函式指標,其實就是我們重寫一個相同簽名的方法然後把這個方法的地址傳過去,程式自己進行地址切換,跳轉執行。回撥0表示成功,回撥其它表示失敗。Java層怎麼把介面傳給C層在這裡就不在贅述,也可以看看我之前的文章《JNI常用開發技巧》

``` //cmdutils.h

/ * Register a register_status. / void register_status(void (cb)(int ret));

//cmdutils.c

static void (*program_status)(int ret);

void register_status(void (*cb)(int ret)) { program_status = cb; }

void exit_program(int ret) { if (program_exit) program_exit(ret); if (program_status)//加入我們的狀態回撥 program_status(ret); } ``` 使用時我們把這兩個函式指標傳進去即可。執行一段命令就會有進度和狀態的回撥,我們可以把它展示到UI上。 

``` static void exit_call(int ret){ LOGE("exit_call %d", ret); }

static void cmd_progress(long progress){ LOGE("onProgress %d", progress); }

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM vm, void reserved){ JNIEnv jniEnv= NULL; int result=(vm)->GetEnv((vm),(void **)&jniEnv,JNI_VERSION_1_4); if (result!=JNI_OK){ return -1; }

av_log_set_callback(log_callback_test2);
register_status(exit_call);//傳入函式指標
register_progress(cmd_progress);//傳入函式指標
return JNI_VERSION_1_4;

} ​ ```

三、常見音訊操作命令

當然命令還是得看FFmpeg官方文件,但是官方有些好像並沒有中文文件,大家可以找找中文翻譯的。我這裡列出幾個我在用的一些命令,包括音訊操作和音訊濾鏡,其餘的大家可以自由發揮。

``` //在任意位置混音 //"ffmpeg -i %s -i %s -filter_complex " +"[1]adelay=delays=%s|%s[aud1];[0][aud1]amix=inputs=2 -y %s";

/* * 音訊操作 / //裁剪 private final String cropCmd="ffmpeg -i %s -ss %s -t %s -acodec copy %s"; //pcm轉MP3 private final String pcm2Mp3="ffmpeg -f s16le -ar 44100 -ac 2 -i %s -ar 44100 -ac 2 -y %s";

//靜音移除 大於間隔0.3s的靜音全部移除 private final String muteRemove="ffmpeg -i %s -af silenceremove=stop_periods=-1:stop_duration=0.3:stop_threshold=-30dB %s"; private final String highPassCmd="ffmpeg -i %s -af highpass=300 %s";//低切300 150 75 private final String gateCmd="ffmpeg -i %s -filter agate=knee=1:ratio=1.5:range=0.08 %s";//降噪 ```

``` / * chorus例子 * 一個延遲(二人合唱效果): * chorus=0.7:0.9:55:0.4:0.25:2 * 2個延遲(三人合唱效果): * chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3 * 3個延遲(四人及更多合唱效果): * chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3 */ private final String chorusCmd="ffmpeg -i %s -filter chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3 %s";//三人合唱效果 沒什麼效果 / * 濾鏡 */ private final String echoMountainCmd="ffmpeg -i %s -filter aecho=0.8:0.9:1000:0.3 %s";//山間迴音特效 private final String echoRobotCmd="ffmpeg -i %s -filter aecho=0.8:0.88:6:0.4 %s";//機器人特效 private final String afadeCmd="ffmpeg -i %s -filter afade=t=%s:ss=%:ns=%ld:st=%d:d=%d:curve=tri %s"; private final String volumeCmd="ffmpeg -i %s -filter volume=%s %s";//音量 private final String vibratoCmd="ffmpeg -i %s -filter vibrato=f=%f:d=%f %s";//顫音

private final String asetrateCmd="ffmpeg -i %s -filter asetrate=sample_rate=%s,atempo=%s %s";//男低音 30000 1.25變調 44100 private final String asetrate2Cmd="ffmpeg -i %s -filter asetrate=sample_rate=%s,atempo=%s %s";//娃娃音 73500 0.6 44100 private final String atempo2Cmd="ffmpeg -i %s -filter atempo=%s %s";//變速 private final String karaokeCmd="ffmpeg -i %s -filter stereotools=mlev=0.015625 %s";//卡拉OK private final String compandCmd=""ffmpeg -i %s -filter compand=.3|.3:1|1:-90/-60|-60/-40|-40/-30|-20/-20:6:0:-90:0.2 %s";//感覺像聲音加強了 private final String flangeCmd="ffmpeg -i %s -filter flanger=delay=0 %s";//環繞效果 ```

四、寫在最後

歡迎大家交流討論,批評指正,覺得有幫助的點個贊吧。