Android NDK開發(fā)(四) 將FFmpeg移植到Android平臺

封面

FFmpeg是一套可以用來記錄笔宿、轉(zhuǎn)換數(shù)字音頻、視頻隶垮,并能將其轉(zhuǎn)化為流的開源計算機程序。它提供了錄制秘噪、轉(zhuǎn)換以及流化音視頻的完整解決方案狸吞。

1.寫在前面

在上一篇文章《Android NDK開發(fā)(三) 在Linux環(huán)境下編譯FFmpeg》中,我們學(xué)習(xí)了如何將FFmpeg源碼編譯成so文件指煎,但是這些so文件還不能直接引用到Android工程中蹋偏,還需要再次編譯加工才能使用,今天就讓我們來學(xué)習(xí)下如何將FFmpeg移植到Android平臺至壤,以及在Android項目中如何通過命令行的方式使用FFmpeg威始。

2.環(huán)境搭建

操作系統(tǒng):Windows 10 64bit

NDK版本:android-ndk-r14b-windows-x86_64

FFmpeg版本:3.4.2

編譯器:Android Studio 3.0

NDK構(gòu)建工具:CMake

創(chuàng)建FFmpeg項目

創(chuàng)建FFmpeg項目

在創(chuàng)建項目時,勾選【Include C++ support】選項像街,然后一路下一步黎棠,到達【Customize C++ Support】設(shè)置頁:

Customize C++ Support

可以看到三個選項:

  • C++ Standard:C++標準,選擇【Toolchain Default】會使用默認的CMake配置镰绎。

  • Exceptions Support:支持C++異常處理脓斩,標志為 -fexceptions。

  • Runtime Type Information Support:支持運行時類型識別畴栖,標志為 -frtti随静,程序能夠使用基類的指針或引用來檢查這些指針或引用所指的對象的實際派生類型。

在這里我們使用默認C++標準吗讶,不勾選下面的兩個選項燎猛,點擊【Finish】按鈕進入下一個環(huán)節(jié)恋捆,看下項目結(jié)構(gòu):

項目結(jié)構(gòu)

3.FFmpeg移植

準備工作

  • 將編譯生成的include文件夾拷貝至 src\main\cpp 目錄下。

  • 將FFmpeg源碼中fftools目錄下的 cmdutils.c扛门、cmdutils.h鸠信、ffmpeg.c、ffmpeg.h论寨、ffmpeg_filter.c星立、ffmpeg_opt.c 類拷貝至 src\main\cpp 目錄下。

  • 將編譯生成的so文件拷貝至 src\main\jniLibs\armeabi 目錄下葬凳,如果沒有此目錄绰垂,新建就可以了。

  • src\main\jniLibs\armeabi 目錄下的 native-lib.cpp 重命名為 ffmpeg_cmd.c火焰。

  • 設(shè)置ABI劲装,本篇文章只編譯armeabi架構(gòu)的so文件,所以需要在build.gradle中設(shè)置一下:

android {
    ...
    
    defaultConfig {
        ...
        
        ndk {
            abiFilters "armeabi"
        }
    }
}

看下此時的項目結(jié)構(gòu):

項目結(jié)構(gòu)

編譯腳本

準備工作到這里就基本完成了昌简,下面來寫一下CMake的構(gòu)建腳本CMakeLists.txt:

# 設(shè)置Cmake版本
cmake_minimum_required(VERSION 3.4.1)

# 設(shè)置cpp目錄路徑
set(CPP_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp)

# 設(shè)置jniLibs目錄路徑
set(LIBS_DIR ${CMAKE_SOURCE_DIR}/src/main/jniLibs)

# 添加庫
add_library( # 庫名稱
             ffmpeg

             # 動態(tài)庫占业,生成so文件
             SHARED

             # 源碼
             ${CPP_DIR}/cmdutils.c
             ${CPP_DIR}/ffmpeg.c
             ${CPP_DIR}/ffmpeg_filter.c
             ${CPP_DIR}/ffmpeg_opt.c
             ${CPP_DIR}/ffmpeg_cmd.c )

# 用于各種類型聲音、圖像編解碼
add_library( # 庫名稱
             avcodec

             # 動態(tài)庫纯赎,生成so文件
             SHARED

             # 表示該庫是引用的不是生成的
             IMPORTED )

# 引用庫文件
set_target_properties( # 庫名稱
                       avcodec

                       # 庫的路徑
                       PROPERTIES IMPORTED_LOCATION
                       ${LIBS_DIR}/armeabi/libavcodec.so )

# 用于各種音視頻封裝格式的生成和解析谦疾,讀取音視頻幀等功能
add_library( avformat
             SHARED
             IMPORTED )

set_target_properties( avformat
                       PROPERTIES IMPORTED_LOCATION
                       ${LIBS_DIR}/armeabi/libavformat.so )

# 包含一些公共的工具函數(shù)
add_library( avutil
             SHARED
             IMPORTED )

set_target_properties( avutil
                       PROPERTIES IMPORTED_LOCATION
                       ${LIBS_DIR}/armeabi/libavutil.so )

# 提供了各種音視頻過濾器
add_library( avfilter
             SHARED
             IMPORTED )

set_target_properties( avfilter
                       PROPERTIES IMPORTED_LOCATION
                       ${LIBS_DIR}/armeabi/libavfilter.so )

# 用于音頻重采樣,采樣格式轉(zhuǎn)換和混合
add_library( swresample
             SHARED
             IMPORTED )

set_target_properties( swresample
                       PROPERTIES IMPORTED_LOCATION
                       ${LIBS_DIR}/armeabi/libswresample.so )

# 用于視頻場景比例縮放犬金、色彩映射轉(zhuǎn)換
add_library( swscale
             SHARED
             IMPORTED )

set_target_properties( swscale
                       PROPERTIES IMPORTED_LOCATION
                       ${LIBS_DIR}/armeabi/libswscale.so )

# 引用源碼 ../代表上級目錄
include_directories( ../../ffmpeg-3.4.2/
                     ${CPP_DIR}/include/ )

# 關(guān)聯(lián)庫
target_link_libraries( ffmpeg
                       avcodec
                       avformat
                       avutil
                       avfilter
                       swresample
                       swscale )

腳本中已經(jīng)寫了很全的注釋念恍,不再多說,重點看下include_directories這個方法晚顷,可以看到里面引用了FFmpeg和編譯生成的include目錄源碼峰伙,其中有很多是重復(fù)的,看到網(wǎng)上很多文章都是只引用了FFmpeg源碼就可以了该默,但是我試了下會有幾個文件找不到瞳氓,所以就兩個都引用了。

修改FFmpeg源碼

  • src\main\cpp\ffmpeg.c

修改main方法:

// 修改前:
int main(int argc, char **argv)

// 修改后:
int run(int argc, char **argv)

執(zhí)行完指令之后重新初始化:

// return之前增加:

nb_filtergraphs = 0;
progress_avio = NULL;
input_streams = NULL;
nb_input_streams = 0;
input_files = NULL;
nb_input_files = 0;
output_streams = NULL;
nb_output_streams = 0;
output_files = NULL;
nb_output_files = 0;

注釋掉下面的代碼权均,否則執(zhí)行完指令后會crash:

exit_program(received_nb_signals ? 255 : main_return_code);
  • src\main\cpp\ffmpeg.h
// 增加:
int run(int argc, char **argv);
  • src\main\cpp\cmdutils.c

修改退出方法顿膨,原代碼中退出后,會直接把APP也退出叽赊,所以需要修改下:

// 修改前:
void exit_program(int ret)
{
    if (program_exit)
        program_exit(ret);

    exit(ret);
}

// 修改后:
int exit_program(int ret)
{
    return ret;
}
  • src\main\cpp\cmdutils.h
// 修改前:
void exit_program(int ret) av_noreturn;

// 修改后:
int exit_program(int ret);

修改ffmpeg_cmd.c

#include <jni.h>
#include "ffmpeg.h"

JNIEXPORT jint

JNICALL
Java_com_yl_ffmpeg4android_MainActivity_run(
        JNIEnv *env, jclass obj, jobjectArray commands) {
    int argc = (*env)->GetArrayLength(env, commands);
    char *argv[argc];

    int i;
    for (i = 0; i < argc; i++) {
        jstring js = (jstring) (*env)->GetObjectArrayElement(env, commands, i);
        argv[i] = (char *) (*env)->GetStringUTFChars(env, js, 0);
    }
    return run(argc, argv);
}

方法很簡單恋沃,傳入指令(String[] 類型),然后調(diào)用ffmpeg.h中的run方法執(zhí)行這些指令必指,點擊編譯按鈕囊咏,不出意外的話,肯定會報錯的,不要方梅割,繼續(xù)往下看霜第。

設(shè)置編譯模式

先看下報錯:

編譯報錯

大概意思是編譯模式不對,話說這個報錯真的是讓人頭疼户辞,查完百度查谷歌泌类,也沒有查到解決方法,后來一遍又一遍的看CMake文檔底燎,一個參數(shù)一個參數(shù)的試刃榨,終于解決了,心情瞬間舒暢了双仍,來看下如何解決:

android {
    ...
    
    defaultConfig {
        ...
        
        externalNativeBuild {
            cmake {
                arguments '-DANDROID_ARM_MODE=arm'
            }
        }
}

把ANDROID_ARM_MOD設(shè)置為arm就可以了枢希,默認是thumb,OK朱沃,繼續(xù)編譯苞轿,什么!又報錯:

編譯報錯

有一些方法沒有定義逗物,還好搬卒,在ffmpeg.c中加幾個空方法:

HWDevice *hw_device_get_by_name(const char *name) {
}

int hw_device_init_from_string(const char *arg, HWDevice **dev) {
}

void hw_device_free_all(void) {
}

int hw_device_setup_for_decode(InputStream *ist) {
}

int hw_device_setup_for_encode(OutputStream *ost) {
}

再次編譯:

編譯完成

久違的信息出現(xiàn)了,看下編譯出的so文件在哪里:

生成libffmpeg.so

4.測試

寫一個簡單的Demo來測試下編譯成功的FFmpeg翎卓,將一個視頻的前100幀截取成一個gif動圖秀睛,上代碼:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    static {
        System.loadLibrary("ffmpeg");
    }

    private ImageView ivGif;
    private Button btnConvert;
    private ProgressDialog progressDialog;
    // 設(shè)備根目錄路徑
    private String path = Environment.getExternalStorageDirectory().getAbsolutePath();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ivGif = findViewById(R.id.iv_gif);
        btnConvert = findViewById(R.id.btn_convert);
        btnConvert.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        // 截取視頻的前100幀
        final String cmd = "ffmpeg -i " + path + "/video.mp4 -vframes 100 -y -f gif -s 480×320 " + path + "/video_100.gif";
        // 顯示loading
        progressDialog = new ProgressDialog(this);
        progressDialog.setTitle("截取中...");
        progressDialog.show();

        new Thread() {
            @Override
            public void run() {
                super.run();
                // 執(zhí)行指令
                cmdRun(cmd);

                // 隱藏loading
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        progressDialog.dismiss();
                        progressDialog = null;

                        // 顯示gif
                        Glide.with(MainActivity.this)
                                .load(new File(path + "/video_500.gif"))
                                .into(ivGif);
                    }
                });
            }
        }.start();
    }

    /**
     * 以空格分割指令,生成String類型的數(shù)組
     *
     * @param cmd 指令
     * @return 執(zhí)行code
     */
    private int cmdRun(String cmd) {
        String regulation = "[ \\t]+";
        final String[] split = cmd.split(regulation);
        return run(split);
    }

    /**
     * ffmpeg_cmd中定義的run方法
     *
     * @param cmd 指令
     * @return 執(zhí)行code
     */
    public native int run(String[] cmd);
}

看下截取的gif:

截取的gif

5.寫在最后

文中如有錯誤的地方莲祸,可以給我留言指正,多謝椭迎!

文中用到的FFmpeg源碼锐帜、編譯腳本以及編譯生成的so文件已經(jīng)上傳至GitHub,后續(xù)還會更新畜号,歡迎Start缴阎、Fork!

GitHub傳送門

點我下載本文Demo的Apk

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末简软,一起剝皮案震驚了整個濱河市蛮拔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌痹升,老刑警劉巖建炫,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異疼蛾,居然都是意外死亡肛跌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來衍慎,“玉大人转唉,你說我怎么就攤上這事∥壤Γ” “怎么了赠法?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乔夯。 經(jīng)常有香客問我砖织,道長,這世上最難降的妖魔是什么驯嘱? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任镶苞,我火速辦了婚禮,結(jié)果婚禮上鞠评,老公的妹妹穿的比我還像新娘茂蚓。我一直安慰自己,他們只是感情好剃幌,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布聋涨。 她就那樣靜靜地躺著,像睡著了一般负乡。 火紅的嫁衣襯著肌膚如雪牍白。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天抖棘,我揣著相機與錄音茂腥,去河邊找鬼。 笑死切省,一個胖子當著我的面吹牛最岗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播朝捆,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼般渡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了芙盘?” 一聲冷哼從身側(cè)響起驯用,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎儒老,沒想到半個月后蝴乔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡驮樊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年淘这,在試婚紗的時候發(fā)現(xiàn)自己被綠了剥扣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡铝穷,死狀恐怖钠怯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情曙聂,我是刑警寧澤晦炊,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站宁脊,受9級特大地震影響断国,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜榆苞,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一稳衬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坐漏,春花似錦薄疚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至躏筏,卻和暖如春板丽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背趁尼。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工埃碱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酥泞。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓乃正,卻偏偏與公主長得像,于是被迫代替她去往敵國和親婶博。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容