FFmpeg在mac下編譯成android平臺(tái)包

首先感謝文中所鏈接到的文章地址的作者,向前輩們學(xué)習(xí)!
此文章基于前輩們的博客而摘抄與修改完成。讓入門(mén)的童鞋少走些彎路,作者亦是如此。


編譯腳本摘自:編譯Android平臺(tái)使用的FFmpeg(armeabi,armeabi-v7a,arm64-v8a,x86,x86_64)
移植到Android中摘自:編譯FFmpeg4.0.1并移植到Android app中使用(最詳細(xì)的FFmpeg-Android編譯教程)


編譯平臺(tái):macOS Mojave 10.14.2
ndk環(huán)境 :android-ndk-r14b
FFmpeg源碼版本:FFmpeg-n4.0.1


---------------------------------------- 操作開(kāi)始 ----------------------------------------

一、環(huán)境搭建

1.下載ndk
我用的是ndk r14b,附上下載地址:https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip
將ndk下載到 /home/ndk/目錄下,下載完成后執(zhí)行tar -zxvf android-ndk-r14b-linux-x86_64.zip解壓

2.下載FFmpeg4.0.1
下載地址:https://codeload.github.com/FFmpeg/FFmpeg/tar.gz/n4.0.1
下載完成后執(zhí)行tar -zxvf n4.0.1解壓!

二、編譯FFmpeg

  1. 修改configure腳本
# SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
# LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
# SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
# SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'
#修改為以下內(nèi)容

SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'

2.編寫(xiě)編譯腳本
取自與夏同炙博客
a.進(jìn)入到源碼目錄,創(chuàng)建編譯腳本

cd 源碼根目錄
vim build.sh

b.編寫(xiě)腳本(需要什么架構(gòu)的,自己選擇)

#!/bin/bash
MY_LIBS_NAME=FFmpeg-n4.0.1
MY_DIR=FFmpeg-n4.0.1

# cd ./${MY_DIR}

#編譯的過(guò)程中產(chǎn)生的中間件的存放目錄,為了區(qū)分編譯目錄,源碼目錄,install目錄
MY_BUILD_DIR=binary


NDK_PATH=/Users/KevenTao/Documents/work_soft/AndroidStudio/android-ndk-r14b
BUILD_PLATFORM=darwin-x86_64
TOOLCHAIN_VERSION=4.9
ANDROID_VERSION=24

ANDROID_ARMV5_CFLAGS="-march=armv5te"
ANDROID_ARMV7_CFLAGS="-march=armv7-a -mfloat-abi=softfp -mfpu=neon"  #-mfloat-abi=hard -mfpu=vfpv3-d16 #-mfloat-abi=hard -mfpu=vfp
ANDROID_ARMV8_CFLAGS="-march=armv8-a"
ANDROID_X86_CFLAGS="-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32"
ANDROID_X86_64_CFLAGS="-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel"


# params($1:arch,$2:arch_abi,$3:host,$4:cross_prefix,$5:cflags)
build_bin() {

    echo "-------------------star build $2-------------------------"

    ARCH=$1         # arm arm64 x86 x86_64
    ANDROID_ARCH_ABI=$2     # armeabi armeabi-v7a x86 mips

    PREFIX=$(pwd)/dist/${MY_LIBS_NAME}/${ANDROID_ARCH_ABI}/

    HOST=$3
    SYSROOT=${NDK_PATH}/platforms/android-${ANDROID_VERSION}/arch-${ARCH}

    CFALGS=$5


    TOOLCHAIN=${NDK_PATH}/toolchains/${HOST}-${TOOLCHAIN_VERSION}/prebuilt/${BUILD_PLATFORM}
    CROSS_PREFIX=${TOOLCHAIN}/bin/$4-

    # build 中間件
    BUILD_DIR=./${MY_BUILD_DIR}/${ANDROID_ARCH_ABI}

    echo "pwd==$(pwd)"
    echo "ARCH==${ARCH}"
    echo "PREFIX==${PREFIX}"
    echo "HOST==${HOST}"
    echo "SYSROOT=${SYSROOT}"
    echo "CFALGS=$5"
    echo "CFALGS=${CFALGS}"
    echo "TOOLCHAIN==${TOOLCHAIN}"
    echo "CROSS_PREFIX=${CROSS_PREFIX}"

    #echo "-------------------------按任意鍵繼續(xù)---------------------"
    #read -n 1
    #echo "-------------------------繼續(xù)執(zhí)行-------------------------"

    # mkdir -p ${BUILD_DIR}   #創(chuàng)建當(dāng)前arch_abi的編譯目錄,比如:binary/armeabi-v7a
    # cd ${BUILD_DIR}         #此處 進(jìn)了當(dāng)前arch_abi的2級(jí)編譯目錄


    bash ./configure \
        --prefix=${PREFIX} \
        --target-os=linux \
        --arch=${ARCH} \
        --sysroot=$SYSROOT \
        --enable-cross-compile \
        --cross-prefix=${CROSS_PREFIX} \
        --extra-cflags="$CFALGS -Os -fPIC -DANDROID -Wfatal-errors -Wno-deprecated" \
        --extra-cxxflags="-D__thumb__ -fexceptions -frtti" \
        --extra-ldflags="-L${SYSROOT}/usr/lib" \
        --enable-shared \
        --enable-asm \
        --enable-neon \
        --disable-encoders \
        --enable-encoder=aac \
        --enable-encoder=mjpeg \
        --enable-encoder=png \
        --disable-decoders \
        --enable-decoder=aac \
        --enable-decoder=aac_latm \
        --enable-decoder=h264 \
        --enable-decoder=mpeg4 \
        --enable-decoder=mjpeg \
        --enable-decoder=png \
        --disable-demuxers \
        --enable-demuxer=image2 \
        --enable-demuxer=h264 \
        --enable-demuxer=aac \
        --disable-parsers \
        --enable-parser=aac \
        --enable-parser=ac3 \
        --enable-parser=h264 \
        --enable-gpl \
        --disable-doc \
        --disable-ffmpeg \
        --disable-ffplay \
        --disable-ffprobe \
        --disable-symver \
        --disable-debug \
        --enable-small


    make clean
    make
    make install

    #從當(dāng)前arch_abi編譯目錄跳出,對(duì)應(yīng)上面的cd ${BUILD_DIR},以便function多次執(zhí)行
        cd ../../

    echo "-------------------$2 build end-------------------------"
}


# build armeabi
# build_bin arm armeabi arm-linux-androideabi arm-linux-androideabi "$ANDROID_ARMV5_CFLAGS"

#build armeabi-v7a
# build_bin arm armeabi-v7a arm-linux-androideabi arm-linux-androideabi "$ANDROID_ARMV7_CFLAGS"

#build arm64-v8a
build_bin arm64 arm64-v8a aarch64-linux-android aarch64-linux-android "$ANDROID_ARMV8_CFLAGS"

#build x86
# build_bin x86 x86 x86 i686-linux-android "$ANDROID_X86_CFLAGS"

#build x86_64
# build_bin x86_64 x86_64 x86_64 x86_64-linux-android "$ANDROID_X86_64_CFLAGS"

3.運(yùn)行編譯腳本

chmod +x build.sh
./build.sh

4.編譯完成
等到編譯完成后在源碼目錄下會(huì)是這樣


image.png

include中為頭文件 、lib中為編譯好的so文件

至此編譯就完成了

三、移植到Android App中去

摘自AngryNoob博客

第一步、 jni編譯
1. 文件拷貝
  • 新建一個(gè)jni文件夾(任意位置都行)
  • 將所有的.so文件放到 …\jni\prebuilt\目錄下;
  • 將 剛剛include目錄下的所有文件夾復(fù)制到 …\jni\目錄下;
  • 將源碼fftools目錄下的以下文件復(fù)制到 …\jni\目錄下
    cmdutils.h 、ffmpeg.h、ffmpeg.c、ffmpeg_opt.c、config.h、ffmpeg_filter.c、cmdutils.c、ffmpeg_hw.c
2. 修改cmdutils
  • 打開(kāi)cmdutils.h,將void show_help_children(const AVClass *class, int flags);
    改為void show_help_children(const AVClass *clazz, int flags);
    否則和C++一起編譯會(huì)出問(wèn)題
  • 打開(kāi)cmdutils.c將void exit_program(int ret)中的退出函數(shù)注釋掉,否則命令執(zhí)行完會(huì)導(dǎo)致APP退出:
void exit_program(int ret)
{
    //if (program_exit)
    //    program_exit(ret);
    //exit(ret);
}
3. 修改ffmpeg.c

找到入口函數(shù)int main(int argc, char **argv)
將其修改為int ffmpeg_exec(int argc, char **argv)
并將該函數(shù)末尾此行代碼注釋掉:

//exit_program(received_nb_signals ? 255 : main_return_code);

同時(shí)在ffmpeg.h中添加函數(shù)申明:
int ffmpeg_exec(int argc, char **argv);
在函數(shù)static void ffmpeg_cleanup(int ret)末尾加上以下代碼:

nb_filtergraphs = 0;
nb_output_files = 0;
nb_output_streams = 0;
nb_input_files = 0;
nb_input_streams = 0;
4. 輸出ADB日志

在ffmpeg.c中引入頭文件

#include "android/log.h"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG  , "ffmpeg.c", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , "ffmpeg.c", __VA_ARGS__)

實(shí)現(xiàn)log_callback_null函數(shù)

static void log_callback_null(void *ptr, int level, const char *fmt, va_list vl)
{
    static int print_prefix = 1;
    static int count;
    static char prev[1024];
    char line[1024];
    static int is_atty;

    av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix);

    strcpy(prev, line);
    //sanitize((uint8_t *)line);

    if (level <= AV_LOG_WARNING) {
        LOGE("%s", line);
    } else {
        LOGD("%s", line);
    }
}
5. 實(shí)現(xiàn)JNI接口

編寫(xiě)ffmpeg-invoke.cpp并放到 …\jni\目錄下:

#include <jni.h>
#include <string>
#include "android/log.h"

extern "C"{
#include "ffmpeg.h"
}

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG  , "ffmpeg-invoke", __VA_ARGS__)


extern "C"
JNIEXPORT jstring JNICALL
Java_com_mission_ffmpeg_jni_FFmpegInvoke_test(JNIEnv *env, jclass type) {

    std::string retValue = "FFmpeg invoke test";
    return env->NewStringUTF(retValue.c_str());
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_mission_ffmpeg_jni_FFmpegInvoke_run(JNIEnv *env, jclass type, jint cmdLen,
                                                       jobjectArray cmd) {

    char *argCmd[cmdLen] ;
    jstring buf[cmdLen];
    LOGD("length=%d",cmdLen);

    for (int i = 0; i < cmdLen; ++i) {
        buf[i] = static_cast<jstring>(env->GetObjectArrayElement(cmd, i));
        char *string = const_cast<char *>(env->GetStringUTFChars(buf[i], JNI_FALSE));
        argCmd[i] = string;
        LOGD("argCmd=%s",argCmd[i]);
    }

    ffmpeg_exec(cmdLen, argCmd);
  
    return 0;

}

注意:將com_mission_ffmpeg_jni_FFmpegInvoke改為自己工程中的JAVA文件對(duì)應(yīng)的包名和路徑(可以了解下Android jni使用)

6. 編寫(xiě).mk文件

編寫(xiě)Android.mk文件并放到 …\jni\目錄

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE :=  libavutil
LOCAL_SRC_FILES := prebuilt/libavutil-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE :=  libswresample
LOCAL_SRC_FILES := prebuilt/libswresample-3.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE :=  libswscale
LOCAL_SRC_FILES := prebuilt/libswscale-5.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libavcodec
LOCAL_SRC_FILES := prebuilt/libavcodec-58.so
include $(PREBUILT_SHARED_LIBRARY)


include $(CLEAR_VARS)
LOCAL_MODULE := libavformat
LOCAL_SRC_FILES := prebuilt/libavformat-58.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libavfilter
LOCAL_SRC_FILES := prebuilt/libavfilter-7.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := libavdevice
LOCAL_SRC_FILES := prebuilt/libavdevice-58.so
include $(PREBUILT_SHARED_LIBRARY)


include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg-invoke


LOCAL_SRC_FILES :=ffmpeg-invoke.cpp \
                    ffmpeg_hw.c\
                    cmdutils.c \
                    ffmpeg_filter.c \
                    ffmpeg_opt.c \
                    ffmpeg.c


LOCAL_C_INCLUDES := /Users/KevenTao/Downloads/FFmpeg-n4.0.1

LOCAL_LDLIBS := -llog -ljnigraphics -lz -landroid -lm -pthread -L$(SYSROOT)/usr/lib
LOCAL_SHARED_LIBRARIES := libavcodec libavfilter libavformat libavutil libswresample libswscale libavdevice
                              
include $(BUILD_SHARED_LIBRARY)

注意:將LOCAL_C_INCLUDES改為自己電腦中FFmpeg源碼所在目錄
編寫(xiě)Application.mk并放到 …\jni\目錄下

APP_ABI := arm64-v8a
APP_PLATFORM=android-21
APP_OPTIM := release
APP_STL := stlport_static
7. 編譯

確認(rèn)所需文件都準(zhǔn)備就緒:

image.png

cd到該目錄,執(zhí)行ndk-build(確保ndk路徑已經(jīng)配置到環(huán)境變量中)

編譯成功后長(zhǎng)這樣

image.png
第二步、新建Android Studio測(cè)試工程 FFmpegTest
1. 在FFmpegTest\app\src\main\目錄下新建jniLibs目錄,將上一步編譯生成的 \libs\armeabi-v7a文件夾復(fù)制到j(luò)niLibs目錄下;

在com.mission.ffmpeg.jni路徑下新建FFmpegInvoke.java

public class FFmpegInvoke {
    static {
        System.loadLibrary("avutil-56");
        System.loadLibrary("postproc-55");
        System.loadLibrary("avcodec-58");
        System.loadLibrary("swresample-3");
        System.loadLibrary("avformat-58");
        System.loadLibrary("swscale-5");
        System.loadLibrary("avfilter-7");
        System.loadLibrary("avdevice-58");
        System.loadLibrary("ffmpeg-invoke");
    }

    private static native int run(int cmdLen, String[] cmd);
    public static native String test();

    public static int run(String[] cmd){
        return run(cmd.length,cmd);
    }
}
image.png
2. 測(cè)試

測(cè)試是否能夠成功調(diào)用動(dòng)態(tài)鏈接庫(kù)函數(shù)

public class MainActivity extends AppCompatActivity {

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

        TextView tvMessage = findViewById(R.id.tv_message);
        tvMessage.setText(FFmpegInvoke.test());
    }
}
image.png

成功會(huì)調(diào)用ffmpeg-invoke.cpp中Java_com_mission_ffmpeg_jni_FFmpegInvoke_test(JNIEnv *env, jclass type)函數(shù)返回的字符串,說(shuō)明java調(diào)用C++函數(shù)成功。

四.在第三步編譯的時(shí)候,我遇到的問(wèn)題

libavcodec/aaccoder.c: In function 'search_for_ms': 
libavcodec/aaccoder.c:803:25: error: expected identifier or '(' before numeric constant 
libavcodec/aaccoder.c:865:28: error: lvalue required as left operand of assignment 
libavcodec/aaccoder.c:866:25: error: 'B1' undeclared (first use in this function) 
libavcodec/aaccoder.c:866:25: note: each undeclared identifier is reported only once for each function it appears in 
ffbuild/common.mak:60: recipe for target 'libavcodec/aaccoder.o' failed 
make: *** [libavcodec/aaccoder.o] Error 1

這種問(wèn)題是由于/usr/arm-linux-androideabi/include/asm/termbits.h文件中已經(jīng)宏定義了。

#define B0 0000000  

導(dǎo)致int B0 = 0, B1 = 0;實(shí)際上變成了int 0000000 = 0, B1 = 0;導(dǎo)致的。
因此可以更改有問(wèn)題的頭文件

ffmpeg/libavcodec/aaccoder.c(我只發(fā)現(xiàn)了這一個(gè))
別的版本可能還有,可以參考(歡雨天的我)的博客
這邊也是摘自他的文章

B0改成bo或者其他字符
還有一種方法:由于選擇的版本問(wèn)題。因此你可以checkout低版本的FFmpeg來(lái)繞開(kāi)這個(gè)問(wèn)題。

五、在android 7.0以上還遇到類(lèi)似這樣的問(wèn)題

java.lang.UnsatisfiedLinkError: dlopen failed: library "/system/lib64/avutil.so" needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace "classloader-namespace"
                 at java.lang.Runtime.loadLibrary0(Runtime.java:977)
                 at java.lang.System.loadLibrary(System.java:1530)
                 at com.sina.weibo.sdk.net.HttpManager.<clinit>(HttpManager.java:43)
                 at com.sina.weibo.sdk.net.HttpManager.openUrl(HttpManager.java:63)
                 at com.sina.weibo.sdk.utils.AidTask.loadAidFromNet(AidTask.java:400)
                 at com.sina.weibo.sdk.utils.AidTask.access$200(AidTask.java:49)
                 at com.sina.weibo.sdk.utils.AidTask$2.run(AidTask.java:232)
                 at java.lang.Thread.run(Thread.java:761)
看了網(wǎng)上好多這個(gè)那個(gè)的最終都沒(méi)有解決,大部分都是說(shuō)的7.0之后對(duì)so加載的問(wèn)題,我的這個(gè)錯(cuò)誤是因?yàn)槭謾C(jī)cpu架構(gòu)是64位的,我先前打的so文件是arm-v7a的。后來(lái)重新編譯了arm64-v8a的包就好了
好了,暫時(shí)就記錄這么多了,若有錯(cuò)誤,請(qǐng)批評(píng)指正。
其余文中大部分都是引用的文中幾位博主的文章內(nèi)容,若引用不當(dāng),盡請(qǐng)告知,謝謝?。?!
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容