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

更新:
最近在做摄像头画面H.264编码,拿起MediaCodec咔咔一顿写,抱着一遍过的心态,然后果不其然的直接崩了,看了错误日志才发现,我的手机没有H.264的编码器,然后查了一下才发现,不同设备对MediaCodec硬编的支持参差不齐,于是索性换成软编,然后想起了FFmpeg,这次有了前车之鉴,先查了一下,发现FFmpeg默认没有集成这个编码器,需要引入libx264,好吧,革命事业曲折离奇,然后又是一顿踩坑之后,有了下面这段更新。

  1. 编译libx264
git clone git://git.videolan.org/x264.git

直接克隆下来,省时省力,然后进入文件目录下,创建待会儿要使用的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 CPU=arm
export PREFIX=/usr/local/x264
export ADDI_CFLAGS="-marm"

./configure \
--prefix=$PREFIX \
--enable-shared \
--enable-static \
--disable-gpac \
--disable-cli \
--disable-asm \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--host=arm-linux \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make j8
make install

这个脚本和下面编译FFmpeg的脚本非常类似,主要一定要加入跨平台编译,不然编译出来的so文件是64位的,在android armeabi-v7a上没法使用。下面是重头戏,也是在整个编译过程中一直困扰我的,就是在编译过程中经常出现一个log2f,起初没注意,每次编译到这儿就会停顿一下,后面混编的时候一直找不到libx264也是这个原因(这是libx264 not found的一个隐藏得比较深的原因,还可能是其他原因)。我们需要把libx264文件夹下的config.h里面的#define HAVE_LOG2F 1关闭,也就是修改成:

#define HAVE_LOG2F 0

这下再make也就不会出现log2f了。

  1. FFmpeg+libx264混编
    混编也就是在下面编译FFmpeg的脚本中开启libx264,以及告诉FFmpeg,libx264的so文件和头文件的位置。直接给出来,并标出不同的地方:
#!/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/
# 下面这两句就是告诉FFmpeg x264相关文件的位置
export ADDI_CFLAGS="-I/usr/local/x264/include"
export ADDI_LDFLAGS="-L/usr/local/x264/lib"

./configure --target-os=android \
--prefix=$PREFIX --arch=arm \
--disable-doc \
--enable-shared \
--disable-static \
--disable-x86asm \
--disable-symver \
--enable-gpl \
# 开启libx264
--enable-libx264 \
--enable-encoder=libx264 \
--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

最后贴一个混编之后的so文件,虽然痛恨CSDN的这个积分制,但是别人的资源也需要积分才能下啊,所以允许我小赚两个积分,终于我也活成了我曾经讨厌的样子…
资源
-------------------------------------------我是三八线---------------------------------------------
这篇文章的重点在于编译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

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

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 - 从编译到配置_第1张图片

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:这里指定ffmpeg的头文件的路径,如果这里不指定的话,我们在编写代码时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 
#include 
#include 
#include 
#include "libyuv.h"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/imgutils.h"
}


// __android_log_print()需要头文件
#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库。

打完收工

你可能感兴趣的:(音视频开发)