FFmpeg編譯4.1.4並移植到Android

Android 編譯FFmpeg

咱們編譯FFmpeg通常在Linux的系統上進行編譯,固然windows也是能夠的,這裏講解一下在Linux系統上編譯。html

  1. 從官網下載FFmpeg(文檔編輯時版本 ffmpeg-4.1.4)
  2. 將下載下來的ffmpeg進行解壓
  3. 去Android官網下載ndk(文檔編輯時版本 ndk-14b)
  4. 進入已解壓的ffmpeg文件目錄,修改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)'
複製代碼

替換成java

SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='?(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'
複製代碼
  1. 編寫build_android.sh的可執行文件
#!/bin/bash
# 清空上次的編譯
make clean
#你本身的NDK路徑.
export NDK=/home/anjoiner/Documents/AnJoiner/ffmpeg/ndk
function build_android
{
echo "Compiling FFmpeg for $CPU"
./configure \
    --prefix=$PREFIX \
    --enable-neon \
    --enable-hwaccels \
    --enable-gpl \
    --enable-postproc \
    --enable-shared \
    --enable-jni \
    --enable-mediacodec \
    --enable-decoder=h264_mediacodec \
    --disable-static \
    --disable-doc \
    --enable-ffmpeg \
    --disable-ffplay \
    --disable-ffprobe \
    --enable-avdevice \
    --disable-doc \
    --disable-symver \
    --cross-prefix=$CROSS_PREFIX \
    --target-os=android \
    --arch=$ARCH \
    --cpu=$CPU \
    --enable-cross-compile \
    --sysroot=$SYSROOT \
    --extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
    --extra-ldflags="$ADDI_LDFLAGS" \
    $ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
echo "The Compilation of FFmpeg for $CPU is completed"
}

#armv8-a
ARCH=arm64
CPU=armv8-a
TOOLCHAIN=$NDK/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64
SYSROOT=$NDK/platforms/android-21/arch-$ARCH/
CROSS_PREFIX=$TOOLCHAIN/bin/aarch64-linux-android-
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-march=$CPU"
build_android

#armv7-a
ARCH=arm
CPU=armv7-a
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
SYSROOT=$NDK/platforms/android-21/arch-$ARCH/
CROSS_PREFIX=$TOOLCHAIN/bin/arm-linux-androideabi-
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
build_android

#x86
ARCH=x86
CPU=x86
TOOLCHAIN=$NDK/toolchains/x86-4.9/prebuilt/linux-x86_64
SYSROOT=$NDK/platforms/android-21/arch-$ARCH/
CROSS_PREFIX=$TOOLCHAIN/bin/i686-linux-android-
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32"
build_android

#x86_64
ARCH=x86_64
CPU=x86-64
TOOLCHAIN=$NDK/toolchains/x86_64-4.9/prebuilt/linux-x86_64
SYSROOT=$NDK/platforms/android-21/arch-$ARCH/
CROSS_PREFIX=$TOOLCHAIN/bin/x86_64-linux-android-
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-march=$CPU -msse4.2 -mpopcnt -m64 -mtune=intel"
build_android
複製代碼

注意:當執行./build_android.sh的時候,出現權限不足,那麼必定要給這個文件添加可執行權限linux

chmod 777 build_android.sh
複製代碼

這樣就能夠進行編譯生成Android下的ffmpegandroid

若是在編譯的時候出現以下問題:c++

ffbuild/common.mak:60: recipe for target 'libavformat/udp.o' failed
make: *** [libavformat/udp.o] Error 1
複製代碼

libavformat/udp.c 第290~295行進行註釋windows

//         mreqs.imr_multiaddr.s_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr;
//         if (local_addr)
//             mreqs.imr_interface= ((struct sockaddr_in *)local_addr)->sin_addr;
//         else
//             mreqs.imr_interface.s_addr= INADDR_ANY;
//         mreqs.imr_sourceaddr.s_addr = ((struct sockaddr_in *)&sources[i])->sin_addr.s_addr;
複製代碼

這樣以後,基本就是ok的,在咱們ffmpeg的文件夾下,會生成一個ffmpeg/android/armv7-a的文件目錄,裏面內容以下:bash

將編譯後的ffmpeg移植到Android

準備

  • 在任何地方新建一個jni的文件夾。
  • 將咱們剛剛編譯的ffmpeg/android/armv7-a/include下的全部文件拷貝進入jni文件夾。
  • jni下新建一個prebuilt的文件夾,將ffmpeg/android/armv7-a/lib下的so文件所有拷貝到prebuilt之下。
  • 而後將ffmpeg/fftools文件下的以下文件拷貝進入jni
    • cmdutils.c
    • cmdutils.h
    • config.h
    • ffmpeg_filter.c
    • ffmpeg_hw.c
    • ffmpeg_opt.c
    • ffmpeg.c
    • ffmpeg.h

修改ffmpeg文件

  • 修改剛剛拷貝的ffmpeg.c文件,找到int main(int argc, char **argv)函數,將其替換爲int run(int argc, char **argv)
  • 在修改後的run(int argc, char **argv) 末尾(retrun 以前)加上如上以下代碼:
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;
複製代碼
  • 並在ffmpeg.h文件末尾加上
int run(int argc, char **argv);
複製代碼
  • 再次打開ffmpeg.c文件,註釋掉run(int argc, char **argv)下的全部exit_program函數,大體代碼以下:
int run(int argc, char **argv)
{
    int i, ret;
    BenchmarkTimeStamps ti;

    init_dynload();

    register_exit(ffmpeg_cleanup);

    setvbuf(stderr,NULL,_IONBF,0); /* win32 runtime needs this */

    av_log_set_flags(AV_LOG_SKIP_REPEATED);
    parse_loglevel(argc, argv, options);

    if(argc>1 && !strcmp(argv[1], "-d")){
        run_as_daemon=1;
        av_log_set_callback(log_callback_null);
        argc--;
        argv++;
    }

#if CONFIG_AVDEVICE
    avdevice_register_all();
#endif
    avformat_network_init();

    show_banner(argc, argv, options);

    /* parse options and open all input/output files */
    ret = ffmpeg_parse_options(argc, argv);
    if (ret < 0);
        // exit_program(1);

    if (nb_output_files <= 0 && nb_input_files == 0) {
        show_usage();
        av_log(NULL, AV_LOG_WARNING, "Use -h to get full help or, even better, run 'man %s'\n", program_name);
        // exit_program(1);
    }

    /* file converter / grab */
    if (nb_output_files <= 0) {
        av_log(NULL, AV_LOG_FATAL, "At least one output file must be specified\n");
        // exit_program(1);
    }

//     if (nb_input_files == 0) {
//         av_log(NULL, AV_LOG_FATAL, "At least one input file must be specified\n");
//         exit_program(1);
//     }

    for (i = 0; i < nb_output_files; i++) {
        if (strcmp(output_files[i]->ctx->oformat->name, "rtp"))
            want_sdp = 0;
    }

    current_time = ti = get_benchmark_time_stamps();
    if (transcode() < 0);
        // exit_program(1);
    if (do_benchmark) {
        int64_t utime, stime, rtime;
        current_time = get_benchmark_time_stamps();
        utime = current_time.user_usec - ti.user_usec;
        stime = current_time.sys_usec  - ti.sys_usec;
        rtime = current_time.real_usec - ti.real_usec;
        av_log(NULL, AV_LOG_INFO,
               "bench: utime=%0.3fs stime=%0.3fs rtime=%0.3fs\n",
               utime / 1000000.0, stime / 1000000.0, rtime / 1000000.0);
    }
    av_log(NULL, AV_LOG_DEBUG, "%"PRIu64" frames successfully decoded, %"PRIu64" decoding errors\n",
           decode_error_stat[0], decode_error_stat[1]);
    if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1]);
        // exit_program(69);

    // exit_program(received_nb_signals ? 255 : main_return_code);

     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;
    

    return main_return_code;
}
複製代碼

新建ffmpeg-invoke

jni文件下新建一個ffmpeg-invoke.cpp的c++文件,將以下代碼寫入markdown

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

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

extern "C"{
#include "ffmpeg.h"
#include "libavcodec/jni.h"
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_coder_ffmpeg_jni_FFmpegCmd_run(JNIEnv *env, jclass type, jint cmdLen,
                                             jobjectArray cmd) {
    //set java vm
    JavaVM *jvm = NULL;
    env->GetJavaVM(&jvm);
    av_jni_set_java_vm(jvm, NULL);

    char *argCmd[cmdLen] ;
    jstring buf[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]);
    }

    int retCode = run(cmdLen, argCmd);
    LOGD("ffmpeg-invoke: retCode=%d",retCode);

    return retCode;

}
複製代碼

需注意的是 Java_com_coder_ffmpeg_jni_FFmpegCmd_runcom_coder_ffmpeg_jni表示FFmpegCmd所在你Android項目的位置。app

新建Android.mk

jni文件夾下,新建Android.mk的文件,將以下代碼拷貝,可是須要注意的是將 LOCAL_C_INCLUDES路徑替換成你源碼所在位置。jvm

// 將此處的路徑改成你ffmepg源碼所在位置
LOCAL_C_INCLUDES := /home/anjoiner/Documents/AnJoiner/ffmpeg
複製代碼
LOCAL_PATH := $(call my-dir)

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

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

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

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

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

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

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

include $(CLEAR_VARS)
LOCAL_MODULE := libpostproc
LOCAL_SRC_FILES := prebuilt/libpostproc.so
include $(PREBUILT_SHARED_LIBRARY)


include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg-invoke

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

// 將此處的路徑改成你ffmepg源碼所在位置
LOCAL_C_INCLUDES := /home/anjoiner/Documents/AnJoiner/ffmpeg

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

include $(BUILD_SHARED_LIBRARY)
複製代碼

新建Application.mk

jni文件夾下,新建Application.mk並將以下代碼拷貝進入

APP_ABI := armeabi-v7a
APP_PLATFORM := android-21
APP_OPTIM := release
APP_STL := stlport_static
複製代碼

到此基本上的配置已經完成,而後在jni文件下運行ndk-build

/home/anjoiner/Documents/AnJoiner/build/jni# /home/anjoiner/Documents/AnJoiner/ffmpeg/ndk/ndk-build
複製代碼

當出現以下代碼就表示成功了

[armeabi-v7a] SharedLibrary  : libffmpeg-invoke.so
[armeabi-v7a] Install        : libffmpeg-invoke.so => libs/armeabi-v7a/libffmpeg-invoke.so
[armeabi-v7a] Install        : libpostproc.so => libs/armeabi-v7a/libpostproc.so
[armeabi-v7a] Install        : libswresample.so => libs/armeabi-v7a/libswresample.so
[armeabi-v7a] Install        : libswscale.so => libs/armeabi-v7a/libswscale.so
複製代碼

測試

  • 新建一個支持C++項目,注意包名需是上述ava_com_coder_ffmpeg_jni_FFmpegCmd_run中的com.coder.ffmpeg
  • jni的同級目錄下,會生成一個libs的目錄,將這個目錄下的armeabi-v7a文件拷貝到你所在的Android項目中的app/src/main/jniLibs
  • 新建一個jni的文件目錄,在此目錄下新建一個FFmpegCmd
/**
 * @author: AnJoiner
 * @datetime: 19-7-30
 */
public class FFmpegCmd {
    static {
        System.loadLibrary("avdevice");
        System.loadLibrary("avutil");
        System.loadLibrary("avcodec");
        System.loadLibrary("swresample");
        System.loadLibrary("avformat");
        System.loadLibrary("swscale");
        System.loadLibrary("avfilter");
        System.loadLibrary("postproc");
        System.loadLibrary("ffmpeg-invoke");
    }

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

    public static int runCmd(String[] cmd){
        return run(cmd.length,cmd);
    }
}

複製代碼

拷貝進來run方法名會出現紅色,不用管他,

  • 在咱們的MainActivity中進行調用,此方法是將音頻進行剪切
public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    100);
        }

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());

        findViewById(R.id.btn_test).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ffmpegTest();
            }
        });
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();


    private void ffmpegTest() {
        new Thread() {
            @Override
            public void run() {
                long startTime = System.currentTimeMillis();
                String input =
                        Environment.getExternalStorageDirectory().getPath() + File.separator +
                                "DCIM" + File.separator + "test.mp3";
                String output =
                        Environment.getExternalStorageDirectory().getPath() + File.separator +
                                "DCIM" + File.separator + "output.mp3";

                String cmd = "ffmpeg -y -i %s -vn -acodec copy -ss %s -t %s %s";
                String result = String.format(cmd, input, "00:00:30", "00:00:40", output);
                FFmpegCmd.runCmd(result.split(" "));
                Log.d("FFmpegTest", "run: 耗時:" + (System.currentTimeMillis() - startTime));
            }
        }.start();


    }
複製代碼

若是上述步驟不出錯,基本沒什麼問題,在資料的參考下,我也弄了好幾天,但幸虧結果仍是好的~

使用 NDK-r20 編譯FFmpeg

前面使用過老版本NDK-r14b編譯FFmpeg,其實質是經過gcc去進行編譯,可是最新的NDK版本中已經不使用gcc去編譯,而是使用clang去進行編譯.這裏貼上最新配置文件代碼

#!/bin/bash
# 清空上次的編譯
make clean
#你本身的NDK路徑.
export NDK=/home/anjoiner/Android/Sdk/ndk-bundle
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64/
API=29

function build_android
{
echo "Compiling FFmpeg for $CPU"
./configure \
    --prefix=$PREFIX \
    --enable-neon \
    --enable-hwaccels \
    --enable-gpl \
    --enable-postproc \
    --enable-shared \
    --enable-jni \
    --enable-mediacodec \
    --enable-decoder=h264_mediacodec \
    --disable-static \
    --disable-doc \
    --enable-ffmpeg \
    --disable-ffplay \
    --disable-ffprobe \
    --enable-avdevice \
    --disable-doc \
    --disable-symver \
    --cross-prefix=$CROSS_PREFIX \
    --target-os=android \
    --arch=$ARCH \
    --cpu=$CPU \
    --cc=$CC
    --cxx=$CXX
    --enable-cross-compile \
    --sysroot=$SYSROOT \
    --extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
    --extra-ldflags="$ADDI_LDFLAGS" \
    $ADDITIONAL_CONFIGURE_FLAG
make clean
make 
make install
echo "The Compilation of FFmpeg for $CPU is completed"
}

#armv8-a
ARCH=arm64
CPU=armv8-a
CC=$TOOLCHAIN/bin/aarch64-linux-android$API-clang
CXX=$TOOLCHAIN/bin/aarch64-linux-android$API-clang++
SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
CROSS_PREFIX=$TOOLCHAIN/bin/aarch64-linux-android-
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-march=$CPU"
build_android

#armv7-a
ARCH=arm
CPU=armv7-a
CC=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang
CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang++
SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
CROSS_PREFIX=$TOOLCHAIN/bin/arm-linux-androideabi-
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
build_android

#x86
ARCH=x86
CPU=x86
CC=$TOOLCHAIN/bin/i686-linux-android$API-clang
CXX=$TOOLCHAIN/bin/i686-linux-android$API-clang++
SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
CROSS_PREFIX=$TOOLCHAIN/bin/i686-linux-android-
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32"
build_android

#x86_64
ARCH=x86_64
CPU=x86-64
CC=$TOOLCHAIN/bin/x86_64-linux-android$API-clang
CXX=$TOOLCHAIN/bin/x86_64-linux-android$API-clang++
SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
CROSS_PREFIX=$TOOLCHAIN/bin/x86_64-linux-android-
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-march=$CPU -msse4.2 -mpopcnt -m64 -mtune=intel"
build_android
複製代碼

參考資料

編譯FFmpeg4.1.3並移植到Android app中使用(最詳細的FFmpeg-Android編譯教程)

相關文章
相關標籤/搜索