Android cmake編譯FFmpeg

項目地址
通過閱讀本文,你將學到以下內容:

  1. 如何編譯多種CPU指令集的FFmpeg動態(tài)庫
  2. 如何配置CMakeLists.txt和build.gradle構建Android項目

編譯環(huán)境

  1. ndk-r16
  2. ffmpeg-4.0.2

一、交叉編譯FFmpeg生成動態(tài)庫

FFmpeg是一個強大的音視頻處理庫,我們有時候只需要用到這個庫的部分功能,因此我們需要通過configure的一些選項對它進行裁剪骤星。此外,我們還需要配置生成哪種CPU指令集的動態(tài)庫、生成的動態(tài)庫在什么操作系統(tǒng)上使用以及一些編譯選項等败明。
一個支持armeabi-v7a、arm64-v8a太防、x86和x86_64四種CPU指令集的FFmpeg編譯腳本妻顶,內容如下:

#!/bin/sh

#  build.sh
#  Builds all supported architectures of FFmpeg for Android.
#  Versions: NDK - r16b, FFmpeg - 4.0.2

NDK=/Users/chenzhichang/Downloads/android-ndk-r16b
# MacOS:darwin-86_64
HOST=darwin-x86_64

# Takes three arguments:
# First: ARCH, supported values: armeabi-v7a, arm64-v8a, x86, x86_64
# Second: platform level. Range: 14-19, 21-24, 26-28
# Third: additinal configuration flags. Already present flags: --enable-cross-compile --disable-static --disable-programs --disable-doc --enable-shared --enable-protocol=file --enable-pic --enable-small
build () {
    ARCH=$1
    LEVEL=$2
    if [ ! $ARCH ]; then
       ARCH=armeabi-v7a
    fi 
    if [ ! $LEVEL ]; then
       LEVEL=21
    fi
    ISYSROOT=$NDK/sysroot
    PLATFORM_ARCH=
    CFLAGS=
    TARGET=
    TOOLCHAIN_FOLDER=
    
    CONFIGURATION="--disable-asm \
    --enable-cross-compile \
    --disable-static \
    --disable-programs \
    --disable-doc \
    --enable-shared \
    --enable-protocol=file \
    --enable-pic \
    --enable-small \
    --disable-devices \
    $3"
    

    case $ARCH in
        "armeabi-v7a")
            TARGET="arm-linux-androideabi"
            CFLAGS="-march=armv7-a -mfloat-abi=softfp -mfpu=neon"
            PLATFORM_ARCH="arm"
            TOOLCHAIN_FOLDER="arm-linux-androideabi"
        ;;
        "arm64-v8a")
            TARGET="aarch64-linux-android"
            CFLAGS="-march=armv8-a"
            PLATFORM_ARCH="arm64"
            CONFIGURATION="$CONFIGURATION --disable-pthreads"
            TOOLCHAIN_FOLDER="aarch64-linux-android"
        ;;
        "x86")
            TARGET="i686-linux-android"
            CFLAGS="-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32"
            PLATFORM_ARCH="x86"
            TOOLCHAIN_FOLDER="x86"
        ;;
        "x86_64")
            TARGET="x86_64-linux-android"
            CFLAGS="-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel"
            PLATFORM_ARCH="x86_64"
            TOOLCHAIN_FOLDER="x86_64"
        ;;
    esac

    CROSS_PREFIX=$NDK/toolchains/$TOOLCHAIN_FOLDER-4.9/prebuilt/$HOST/bin/$TARGET-
    ASM=$ISYSROOT/usr/include/$TARGET
    SYSROOT=$NDK/platforms/android-$LEVEL/arch-$PLATFORM_ARCH/
    PREFIX="android/$ARCH"

    ./configure --prefix=$PREFIX  \
                $CONFIGURATION \
                --cross-prefix=$CROSS_PREFIX \
                --arch=$PLATFORM_ARCH \
                --target-os=android \
                --sysroot=$SYSROOT \
                --extra-cflags="$CFLAGS -I$ASM -isysroot $ISYSROOT -D__ANDROID_API__=$LEVEL -Wfatal-errors -U_FILE_OFFSET_BITS -Os -fPIC -DANDROID -D__thumb__ -Wno-deprecated" 

    make clean
    make -j4
    make install
}
#build "armeabi-v7a" "21"
#build "arm64-v8a" "21"
#build "x86_64" "21"
#build "x86" "21"

這個腳本的用法很簡單酸员,只需要將該腳本文件拷貝到ffmpeg源碼的根目錄下,將腳本中的NDK變量值改為你的電腦上NDK所在的路徑讳嘱。如果你的電腦不是Mac幔嗦,則還需要修改HOST變量值。最后執(zhí)行下面這條命令即可沥潭。其中armeabi-v7a表示CPU指令集邀泉,21表示Android版本。

./build.sh armeabi-v7a 21

在這里簡單介紹一下關于此腳本的一些配置項钝鸽,從而方便你能夠根據你的需求來修改這個腳本:

  1. --prefix:指定編譯輸出的文件路徑
  2. --target-os:指定目標操作系統(tǒng)
  3. --disable-static:禁止生成靜態(tài)庫
  4. --disable-programs:禁止生成ffplay汇恤、ffmpeg等可執(zhí)行文件
  5. --disable-doc:禁止生成文檔
  6. --enable-shared:生成動態(tài)動態(tài)鏈接庫
  7. --enable-cross-compile:開啟交叉編譯(跨平臺編譯)

執(zhí)行此腳本,最終生成文件如下:

20181006165801357.png

二拔恰、創(chuàng)建NDK項目并引入FFmpeg動態(tài)庫

創(chuàng)建一個NDK項目因谎,然后將生成的ffmpeg動態(tài)庫文件和頭文件拷貝到libs文件夾,最終項目結構如下圖颜懊。

20181006111010365.png

三财岔、配置build.gradle

需要配置的內容如下:

  1. 配置so庫的路徑
  2. 配置cmake編譯選項和支持的CPU指令集,其中-Wno-deprecated-declarations選項用于忽略使用廢棄API的編譯警告饭冬。
  3. 配置CMakeLists.txt文件的路徑

最終配置如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.chenzhichang.testffmpeg"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        //配置so庫的路徑
        sourceSets {
            main {
                jniLibs.srcDirs = ['libs']
            }
        }
        //配置cmake編譯選項和支持的CPU指令集
        externalNativeBuild {
            cmake {
                cppFlags "-frtti -fexceptions -Wno-deprecated-declarations"
            }
            ndk{
                abiFilters "armeabi-v7a", "x86_64", "x86"
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    //配置CMakeLists.txt文件的路徑
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
         

四使鹅、配置CMakeLists.txt

配置如下:

cmake_minimum_required(VERSION 3.4.1)

find_library( log-lib
              log )

# 定義變量
set(distribution_DIR ../../../../libs)

# 添加庫——自己編寫的庫
# 庫名稱:native-lib
# 庫類型:SHARED,表示動態(tài)庫昌抠,后綴為.so(如果是STATIC患朱,則表示靜態(tài)庫,后綴為.a)
# 庫源碼文件:src/main/cpp/native-lib.cpp
add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp )

# 添加庫——外部引入的庫
# 庫名稱:avcodec(不需要包含前綴lib)
# 庫類型:SHARED炊苫,表示動態(tài)庫裁厅,后綴為.so(如果是STATIC,則表示靜態(tài)庫侨艾,后綴為.a)
# IMPORTED表明是外部引入的庫
add_library( avcodec
             SHARED
             IMPORTED)
# 設置目標屬性
# 設置avcodec目標庫的IMPORTED_LOCATION屬性执虹,用于說明引入庫的位置
# 還可以設置其他屬性,格式:PROPERTIES key value
set_target_properties( avcodec
                       PROPERTIES IMPORTED_LOCATION
                       ${distribution_DIR}/${ANDROID_ABI}/libavcodec.so)

add_library( avfilter
             SHARED
             IMPORTED)
set_target_properties( avfilter
                       PROPERTIES IMPORTED_LOCATION
                       ${distribution_DIR}/${ANDROID_ABI}/libavfilter.so)

add_library( avformat
             SHARED
             IMPORTED)
set_target_properties( avformat
                       PROPERTIES IMPORTED_LOCATION
                       ${distribution_DIR}/${ANDROID_ABI}/libavformat.so)

add_library( avutil
             SHARED
             IMPORTED)
set_target_properties( avutil
                       PROPERTIES IMPORTED_LOCATION
                       ${distribution_DIR}/${ANDROID_ABI}/libavutil.so)

add_library( swresample
             SHARED
             IMPORTED)
set_target_properties( swresample
                       PROPERTIES IMPORTED_LOCATION
                       ${distribution_DIR}/${ANDROID_ABI}/libswresample.so)

add_library( swscale
             SHARED
             IMPORTED)
set_target_properties( swscale
                       PROPERTIES IMPORTED_LOCATION
                       ${distribution_DIR}/${ANDROID_ABI}/libswscale.so)

# 引入頭文件
include_directories(libs/include)

# 告訴編譯器生成native-lib庫需要鏈接的庫
# native-lib庫需要依賴avcodec唠梨、avfilter等庫
target_link_libraries( native-lib
                       avcodec
                       avfilter
                       avformat
                       avutil
                       swresample
                       swscale
                       ${log-lib} )

關于CMake的詳細內容可以閱讀官方文檔

五袋励、編寫代碼測試FFmpeg是否能夠正常使用

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_protocol"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="2dp"
            android:text="Protocol"
            android:textAllCaps="false" />

        <Button
            android:id="@+id/btn_format"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="2dp"
            android:text="Format"
            android:textAllCaps="false" />

        <Button
            android:id="@+id/btn_codec"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="2dp"
            android:text="Codec"
            android:textAllCaps="false" />

        <Button
            android:id="@+id/btn_filter"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="2dp"
            android:text="Filter"
            android:textAllCaps="false" />
    </LinearLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tv_info"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Hello World!" />
    </ScrollView>

</LinearLayout>
  • native-lib.cpp
#include <jni.h>
#include <string>

extern "C"
{


#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
JNIEXPORT jstring JNICALL
Java_com_chenzhichang_testffmpeg_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

JNIEXPORT jstring JNICALL
Java_com_chenzhichang_testffmpeg_MainActivity_urlprotocolinfo(JNIEnv *env, jobject instance) {
    char info[40000] = {0};
    av_register_all();
    struct URLProtocol *pup = NULL;
    struct URLProtocol **p_temp = &pup;
    avio_enum_protocols((void **) p_temp, 0);
    while ((*p_temp) != NULL) {
        sprintf(info, "%sInput: %s\n", info, avio_enum_protocols((void **) p_temp, 0));
    }
    pup = NULL;
    avio_enum_protocols((void **) p_temp, 1);
    while ((*p_temp) != NULL) {
        sprintf(info, "%sInput: %s\n", info, avio_enum_protocols((void **) p_temp, 1));
    }

    return env->NewStringUTF(info);
}

JNIEXPORT jstring JNICALL
Java_com_chenzhichang_testffmpeg_MainActivity_avformatinfo(JNIEnv *env, jobject instance) {

    char info[40000] = {0};

    av_register_all();

    AVInputFormat *if_temp = av_iformat_next(NULL);
    AVOutputFormat *of_temp = av_oformat_next(NULL);
    while (if_temp != NULL) {
        sprintf(info, "%sInput: %s\n", info, if_temp->name);
        if_temp = if_temp->next;
    }
    while (of_temp != NULL) {
        sprintf(info, "%sOutput: %s\n", info, of_temp->name);
        of_temp = of_temp->next;
    }
    return env->NewStringUTF(info);
}

JNIEXPORT jstring JNICALL
Java_com_chenzhichang_testffmpeg_MainActivity_avcodecinfo(JNIEnv *env, jobject instance) {
    char info[40000] = {0};

    av_register_all();

    AVCodec *c_temp = av_codec_next(NULL);

    while (c_temp != NULL) {
        if (c_temp->decode != NULL) {
            sprintf(info, "%sdecode:", info);
        } else {
            sprintf(info, "%sencode:", info);
        }
        switch (c_temp->type) {
            case AVMEDIA_TYPE_VIDEO:
                sprintf(info, "%s(video):", info);
                break;
            case AVMEDIA_TYPE_AUDIO:
                sprintf(info, "%s(audio):", info);
                break;
            default:
                sprintf(info, "%s(other):", info);
                break;
        }
        sprintf(info, "%s[%10s]\n", info, c_temp->name);
        c_temp = c_temp->next;
    }

    return env->NewStringUTF(info);
}

JNIEXPORT jstring JNICALL
Java_com_chenzhichang_testffmpeg_MainActivity_avfilterinfo(JNIEnv *env, jobject instance) {
    char info[40000] = {0};
    avfilter_register_all();

    AVFilter *f_temp = (AVFilter *) avfilter_next(NULL);
    while (f_temp != NULL) {
        sprintf(info, "%s%s\n", info, f_temp->name);
        f_temp = f_temp->next;
    }
    return env->NewStringUTF(info);
}
}
  • MainActivity.java
package com.chenzhichang.testffmpeg;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

/**
 * @author chenzhichang
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }


    TextView tvInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ((Button)findViewById(R.id.btn_protocol)).setOnClickListener(this);
        ((Button)findViewById(R.id.btn_codec)).setOnClickListener(this);
        findViewById(R.id.btn_filter).setOnClickListener(this);
        findViewById(R.id.btn_format).setOnClickListener(this);
        tvInfo = (TextView) findViewById(R.id.tv_info);
    }



    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_protocol:
                tvInfo.setText(urlprotocolinfo());
                break;
            case R.id.btn_format:
                tvInfo.setText(avformatinfo());
                break;
            case R.id.btn_codec:
                tvInfo.setText(avcodecinfo());
                break;
            case R.id.btn_filter:
                tvInfo.setText(avfilterinfo());
                break;
            default:
                break;
        }
    }

    public native String stringFromJNI();

    public native String urlprotocolinfo();

    public native String avformatinfo();

    public native String avcodecinfo();

    public native String avfilterinfo();
}

運行結果如下圖:

Jietu20181006-172358-HD.gif

參考資料

IljaKosynkin/FFmpeg-Development-Kit
ffmpeg使用NDK編譯時遇到的一些坑 - luo0xue的博客 - CSDN博客
ffmpeg ./configure參數說明 - azraelly - 博客園
Android開發(fā)學習之路--Android Studio cmake編譯ffmpeg - 東月之神 - CSDN博客

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市当叭,隨后出現的幾起案子茬故,更是在濱河造成了極大的恐慌,老刑警劉巖蚁鳖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件磺芭,死亡現場離奇詭異,居然都是意外死亡醉箕,警方通過查閱死者的電腦和手機钾腺,發(fā)現死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門徙垫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人放棒,你說我怎么就攤上這事姻报。” “怎么了哨查?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵逗抑,是天一觀的道長。 經常有香客問我寒亥,道長邮府,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任溉奕,我火速辦了婚禮褂傀,結果婚禮上,老公的妹妹穿的比我還像新娘加勤。我一直安慰自己仙辟,他們只是感情好,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布鳄梅。 她就那樣靜靜地躺著叠国,像睡著了一般。 火紅的嫁衣襯著肌膚如雪戴尸。 梳的紋絲不亂的頭發(fā)上粟焊,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機與錄音孙蒙,去河邊找鬼项棠。 笑死,一個胖子當著我的面吹牛挎峦,可吹牛的內容都是我干的香追。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼坦胶,長吁一口氣:“原來是場噩夢啊……” “哼透典!你這毒婦竟也來了?” 一聲冷哼從身側響起顿苇,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤峭咒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后岖圈,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡钙皮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年蜂科,在試婚紗的時候發(fā)現自己被綠了顽决。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡导匣,死狀恐怖才菠,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情贡定,我是刑警寧澤赋访,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站缓待,受9級特大地震影響蚓耽,放射性物質發(fā)生泄漏。R本人自食惡果不足惜旋炒,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一步悠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瘫镇,春花似錦鼎兽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至尚粘,卻和暖如春择卦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背背苦。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工互捌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人行剂。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓秕噪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親厚宰。 傳聞我的和親對象是個殘疾皇子腌巾,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內容