这篇文章的重点在于编译FFmpeg库和Android studio 3.0中配置FFmpeg相关文件,所以如果是需要了解FFmpeg的实际应用的可以不用继续往下看了。因为我自己在整个过程中遇到很多的坑,所以我在整个过程中都用云笔记记录了下来,希望帮助到后来需要的同学,文章涉及的所有步骤我都是亲自尝试过的,如果有不正确的地方,烦请指正。

环境准备

1. ubuntu
阿里云服务器 Ubuntu 16.04.3 LTS
2. ndk
android-ndk-r15c

国内可能无法访问,可以通过如下命令:

wget https://dl.google.com/android/repository/android-ndk-r15c-linux-x86_64.zip

如果需要下载其他版本的可以查阅这篇博客

下载完成后,使用如下命令:

sudo unzip android-ndk-r15c-linux-x86_64.zip

然后把ndk的路径,使用如下命令:

vim /etc/profile

然后在文件末尾加入ndk的路径,内容如下:

export ANDROID_NDK=/home/download/android-ndk-r15c
export PATH=$ANDROID_NDK:$PATH

然后使用如下命令,是环境变量生效:

source /etc/profile

:上面这个命令只是临时生效,重新打开一个终端就失效了,优点是不用重启系统就能马上生效

3. ffmpeg

到官网下载ffmpeg,或者使用命令下载,我这里下载的是4.0.2的版本:

wget https://ffmpeg.org/releases/ffmpeg-4.0.2.tar.bz2

然后解压:

tar -jxvf ffmpeg-4.0.2.tar.bz2

开始编译

进入ffmpeg解压完成后的根目录,为了方便多次编译,我们可以将编译的命令写入一个shell脚本中,以后每次更改编译参数重新运行脚本就可以了。

编译脚本:

#!/bin/bash
export NDK=/home/download/android-ndk-r15c
export SYSROOT=$NDK/platforms/android-9/arch-arm/
export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
export PREFIX=/usr/local/ffmpeg/
export ADDI_CFLAGS="-marm"

./configure --target-os=android \
--prefix=$PREFIX --arch=arm \
--disable-doc \
--enable-shared \
--disable-static \
--disable-x86asm \
--disable-symver \
--enable-gpl \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make
make install
注意:
  1. shell脚本的格式问题,windows上创建的shell脚本在linux上可能因为格式问题识别不了;
  2. ./configure \和下面的参数之间不能有空格;
  3. 编译平台对应的android版本最好使用android-9,不然到低于这个版本的android平台上可以会报错;
  4. make到一半,卡住不动,可能是linux内存不够了,通过命令free -m查看内存,-m表示以M为单位显示内存。

android项目中使用FFmpeg

上面编译完成之后会在编译脚本指定的PREFIX的路径下生成include、lib、share三个文件夹,include中是FFmpeg的方法的头文件,lib是生成的so动态链接库,share里面有一些FFmpeg的示例程序。

这里我们使用Android Studio 3.0来创建android工程,从as 2.2之后,我们开始用cmake编译jni,所以我们用CMakeLists.txt来代替Android.mk。略过使用as创建android ndk项目,创建完成后,我们开始配置。

1. 导入ffmpeg相关文件

在src/main下新建ffmpeg文件夹,然后将编译生成的包含所有ffmpeg头文件的整个include文件夹拷贝到该目录下,因为我们是编译的armeabi-v7a平台下的so库,所以在ffmpeg目录下新建armeabi-v7a文件夹,然后将所有so库拷贝到该文件夹下,最终目录结构如下:

Android studio 3.0 集成 FFmpeg - 从编译到配置-LMLPHP

2. 配置CMakeList.txt
# CMake的最低版本
cmake_minimum_required(VERSION 3.4.1)

add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp )

find_library( log-lib
              log )

set(JNI_LIBS_DIR ${CMAKE_SOURCE_DIR}/src/main/ffmpeg)

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

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

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

add_library(avcodec
            SHARED
            IMPORTED )
set_target_properties(avcodec
                      PROPERTIES IMPORTED_LOCATION
                      ${JNI_LIBS_DIR}/${ANDROID_ABI}/libavcodec.so )

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

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

add_library(avdevice
            SHARED
            IMPORTED )
set_target_properties(avdevice
                      PROPERTIES IMPORTED_LOCATION
                      ${JNI_LIBS_DIR}/${ANDROID_ABI}/libavdevice.so )

include_directories(src/main/ffmpeg/include)

target_link_libraries(  native-lib
                        avutil
                        swresample
                        swscale
                        avcodec
                        avformat
                        avfilter
                        avdevice
                        android
                        ${log-lib})

说明:

  • cmake_minimum_required:指定cmake的最低版本
  • set():这里相当于定义一个变量,后面通过${var}来引用这个变量,我们这里是指定了ffmpeg库文件根目录
  • add_library和set_target_properties:添加库文件,ffmpeg的8个so库我们都需要通过这两个方法引入
  • include_directories:这里,如果这里不指定的话,我们在编写代码时include头文件需要写很长的路径,当然还有个更重要的原因是,ffmpeg本身的头文件之间互相引用就是默认这个路径作为根目录的,如果不指定这个目录,编译的时候会在ffmpeg的头文件里面报错,说找不到其他头文件,ffmpeg的自己的头文件引用是下面的格式:
#include "libavcodec/avcodec.h"
#include "libavutil/dict.h"
#include "libavutil/log.h"

看到这里我们就能明白为什么一定要指定这个头文件的根目录了

  • target_link_libraries:链接so库。这里有个要注意的地方就是:如果我们在编写代码时使用了ANativeWindow相关的方法的话,需要在链接库文件的时候加入android这个库文件,不然会报undefined reference to的错
3. 修改build.gradle文件
android {
    ......
    defaultConfig {
        externalNativeBuild {
            cmake {
                cppFlags "-fexceptions"
                abiFilters "armeabi-v7a"
            }
        }
    }
	// 加上这个,打包apk的时候才会将ffmpeg的so库打包进libs目录里面
	sourceSets {
		main {
			jni.srcDirs = []
			jniLibs.srcDirs = ['src/main/ffmpeg']
		}
	}
    ......
}
4. 编写cpp文件使用ffmpeg

上面配置完成之后,我们就可以编写代码,然后在代码中使用ffmpeg的方法了。

在src/main/java的包下创建我们的java类,然后在类中编写native方法,然后通过System.loadLibrary加载我们需要的so库,如下所示:

static {
        System.loadLibrary("avutil");
        System.loadLibrary("swresample");
        System.loadLibrary("swscale");
        System.loadLibrary("avcodec");
        System.loadLibrary("avformat");
        System.loadLibrary("avfilter");
        System.loadLibrary("avdevice");
    }

public native void decode(String input, String output);

然后在命令行下,进入项目根目录app/src/main/java/目录下,使用命令生成头文件:

javah 包名.类名

然后我们在创建ndk项目时自动生成的cpp文件里面加入一个方法,方法名和参数就是我们刚生成的头文件里面声明的方法,示例如下:

#include <jni.h>
#include <string>
#include <android/log.h>
#include <android/native_window_jni.h>
#include "libyuv.h"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
}


// __android_log_print()需要<android/log.h>头文件
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO, "sisyphus", FORMAT, ##__VA_ARGS__)
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR, "sisyphus", FORMAT, ##__VA_ARGS__)

extern "C"
JNIEXPORT jstring

JNICALL
Java_com_sisyphus_ffmpegdemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

extern "C"
JNIEXPORT void JNICALL
Java_com_sisyphus_ffmpegdemo_FFmpegPlayer_decode(JNIEnv *env, jobject instance, jstring input_,
                                                 jstring output_, jobject surface) {
    const char *input = env->GetStringUTFChars(input_, 0);
    const char *output = env->GetStringUTFChars(output_, 0);

    av_register_all();

    env->ReleaseStringUTFChars(input_, input);
    env->ReleaseStringUTFChars(output_, output);
}

这里还有最后一个需要注意的地方就是:因为ffmpeg是用c语言编写的,我们是在cpp文件里面使用的,所以我们在include他的头文件时需要将头文件包含在extern “C” {}里面,如下:

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
}

不然的话,虽然我们在编写代码的时候不会报错,在最后编译的时候就会报错说我们使用的方法都没有定义。

5. 编译

点击菜单Build->Make Project编译项目,最后会在项目根目录的\app\build\intermediates\cmake\debug\obj\armeabi-v7a下生成一个so库。

10-06 17:55