windows环境下编译ffmpeg打包成单个so并使用Cmake集成到Android工程中

1、下载ffmpeg。我下载的是ffmpeg-3.3.3
下载地址:
https://ffmpeg.org/download.html
这里特别提一句,如果你使用本文编译的话,请不要用ffmpeg-3.4,因为我在使用FFmpeg-3.4的编译的时候一直报libavutil/timer.h: fatal error linux/perf_event.h : No
such file 的错误,一开始我以为是MinGW的库没下载全,后面把所有的库都下载下载,编译依旧出现同样的错误,我在网上找了一下,发现也有人跟我一样出现同样的错误,但没有解决方法:
FFmpeg 在CentOS7 下进行编译,一直报错
后来我在FFmpeg官网重新下载了ffmpeg-3.3.3版本,重新编译一次,直接就过了。似乎是MinGW工具链跟ffmpeg-3.4版本的依赖方式存在冲突(知道原因的话,请告诉我,在此先谢谢大家了)?

2、下载mingw
下载地址:
https://sourceforge.net/projects/mingw/files/
下载后运行,会自动下载安装器,安装器下载好之后会自动打开MinGW Installation Manager, 在安装管理器中选择Base Setup,选中需要安装的库,在安装时,最好选择网络比较稳定的时间,比如早上,因为如果msys下载失败,后面会无法编译通过的。

安装内容选择

3、下载x264FFmpeg。如果需要使用到H264解码功能,则需要集成x264的库。

4、编译x264。将以下内容保存为build_script.sh脚本文件,其中NDK表示你的路径,由于本人的NDK是从Android Studio里面下载的,因此路径默认在SDK下的ndk-bundle目录中,你也可以使用你自己下载的NDK,不过建议升级到最新的NDK 人4b版本,另外一点就是,TOOLCHAIN 路径需要你对一下,如果你用的版本不是NDK r14b,有可能使用的工具链版本不太一样,所以这里请确认toolchains下的arm-linux-androideabi版本是否对得上。在编译之前,先把x264的库解压到ffmpeg中,并改名为libx264:

#!/bin/bash
NDK=D:/Android/sdk/ndk-bundle
PLATFORM=$NDK/platforms/android-19/arch-arm
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64
PREFIX=./android/arm

EXTRA_CFLAGS="-march=armv7-a -mfloat-abi=softfp -mfpu=neon -D__ARM_ARCH_7__ -D__ARM_ARCH_7A__"

function build_one
{
./configure \
--prefix=$PREFIX \
--enable-static \
--enable-pic \
--enable-strip \
--host=arm-linux-androideabi \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--sysroot=$PLATFORM \
--extra-cflags="-Os -fpic $EXTRA_CFLAGS" \
--extra-ldflags="" \

$ADDITIONAL_CONFIGURE_FLAG
make clean
make -j4
make install

}
build_one

打开之前下载的MinGW路径中的C:\MinGW\msys\1.0目录下的msys.dat,cd到你下载的x264解压的目录,然后执行刚才保存的脚本文件: ./build_script.sh, 编译成功后会在libx264目录下生成了一个叫做android的文件夹,并且将libx264.a复制到了该文件夹的arm/lib文件夹中,下面是输出的log信息:


编译成功输出

5、编译包含x264的FFmpeg库。进入ffmpeg解压的目录,将以下内容保存为build_script.sh脚本文件,将NDK和TOOLCHAIN改成你自己的路径:

#!/bin/bash

NDK=D:/Android/sdk/ndk-bundle
PLATFORM=$NDK/platforms/android-19/arch-arm
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64

basepath=$(cd `dirname $0`; pwd)
X264_INCLUDE=$basepath/libx264/android/arm/include

X264_LIB=$basepath/libx264/android/arm/lib

function build_one
{
    ./configure \
--prefix=$PREFIX \
--arch=arm \
--cpu=armv7-a \
--target-os=android \
--enable-cross-compile \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--sysroot=$PLATFORM \
--extra-cflags="-I$X264_INCLUDE -I$PLATFORM/usr/include" \
--extra-ldflags="-L$X264_LIB" \
--cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc \
--nm=$TOOLCHAIN/bin/arm-linux-androideabi-nm \
--disable-shared \
--enable-static \
--enable-gpl \
--enable-version3 \
--enable-pthreads \
--enable-runtime-cpudetect \
--enable-small \
--disable-network \
--disable-vda \
--disable-iconv \
--enable-asm \
--enable-neon \
--enable-yasm \
--disable-encoders \
--enable-libx264 \
--enable-encoder=h263 \
--enable-encoder=libx264 \
--enable-encoder=aac \
--enable-encoder=mpeg4 \
--enable-encoder=mjpeg \
--enable-encoder=png \
--enable-encoder=gif \
--enable-encoder=bmp \
--disable-muxers \
--enable-muxer=h264 \
--enable-muxer=flv \
--enable-muxer=gif \
--enable-muxer=mp3 \
--enable-muxer=dts \
--enable-muxer=mp4 \
--enable-muxer=mov \
--enable-muxer=mpegts \
--disable-decoders \
--enable-decoder=aac \
--enable-decoder=aac_latm \
--enable-decoder=mp3 \
--enable-decoder=h263 \
--enable-decoder=h264 \
--enable-decoder=mpeg4 \
--enable-decoder=mjpeg \
--enable-decoder=gif \
--enable-decoder=png \
--enable-decoder=bmp \
--enable-decoder=yuv4 \
--disable-demuxers \
--enable-demuxer=image2 \
--enable-demuxer=h263 \
--enable-demuxer=h264 \
--enable-demuxer=flv \
--enable-demuxer=gif \
--enable-demuxer=aac \
--enable-demuxer=ogg \
--enable-demuxer=dts \
--enable-demuxer=mp3 \
--enable-demuxer=mov \
--enable-demuxer=m4v \
--enable-demuxer=concat \
--enable-demuxer=mpegts \
--enable-demuxer=mjpeg \
--enable-demuxer=mpegvideo \
--enable-demuxer=rawvideo \
--enable-demuxer=yuv4mpegpipe \
--disable-parsers \
--enable-parser=aac \
--enable-parser=ac3 \
--enable-parser=h264 \
--enable-parser=mjpeg \
--enable-parser=png \
--enable-parser=bmp\
--enable-parser=mpegvideo \
--enable-parser=mpegaudio \
--disable-protocols \
--enable-protocol=file \
--enable-protocol=hls \
--enable-protocol=concat \
--disable-filters \
--disable-filters \
--enable-filter=aresample \
--enable-filter=asetpts \
--enable-filter=setpts \
--enable-filter=ass \
--enable-filter=scale \
--enable-filter=concat \
--enable-filter=atempo \
--enable-filter=movie \
--enable-filter=overlay \
--enable-filter=rotate \
--enable-filter=transpose \
--enable-filter=hflip \
--enable-zlib \
--disable-outdevs \
--disable-doc \
--disable-ffplay \
--disable-ffmpeg \
--disable-ffserver \
--disable-debug \
--disable-ffprobe \
--disable-postproc \
--enable-avdevice \
--disable-symver \
--disable-stripping \
--extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG


    make clean
    make -j8
    make install


$TOOLCHAIN/bin/arm-linux-androideabi-ld \
-rpath-link=$PLATFORM/usr/lib \
-L$PLATFORM/usr/lib \
-L$PREFIX/lib \
-L$X264_LIB \
-soname libffmpeg.so -shared -nostdlib -Bsymbolic --whole-archive --no-undefined -o \
$PREFIX/libffmpeg.so \
libavcodec/libavcodec.a \
libavfilter/libavfilter.a \
libswresample/libswresample.a \
libavformat/libavformat.a \
libavutil/libavutil.a \
libswscale/libswscale.a \
libavdevice/libavdevice.a \
libx264/libx264.a \
-lc -lm -lz -ldl -llog --dynamic-linker=/system/bin/linker \
$TOOLCHAIN/lib/gcc/arm-linux-androideabi/4.9.x/libgcc.a
}
# arm v7vfp
CPU=arm-v7a
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=neon -marm -march=armv7-a "
ADDI_CFLAGS="-marm"
PREFIX=./android/$CPU
build_one

以上脚本包含了x264的路径,并且对ffmpeg做了相应的裁剪。在编译完成后,我把多个.a静态库文件合并到了一个so当中。这里编译包含x264的ffmpeg库,则需要指定x264的静态库路径,也就是libx264文件夹,最后合并的x264静态库的路径是libx264/libx264.a。

在FFmpeg源码目录下,打开conigure文件,找到以下几行:

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)'

将其替换成:

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

这是因为Android平台下,不能识别FFmpeg编译出来的so,比如 “libavcodec.so.5.100.1”,只能识别 “libavcodec.so”。如果你是将静态库打包进去的话,比如"libavcodec.a",则可以不考虑。

执行脚本,如无意外,编译成功后,将会在ffmpeg目录下面生成一个android目录,点击去可以看到生成了相应的libffmpeg.so,至此,我们把多个静态库打包成了单个so文件,如下图所示:


生成的目录情况

更多关于FFmpeg命令配置问题,可以参考这篇文章:
ffmpeg ./configure参数说明

6、Android Studio 里面集成x264 和 FFmpeg
新建一个包含C++ Project的工程,如下图所示:


新建工程

在main目录下新增一个cpp目录,然后在cpp目录下新增include目录,把ffmpeg的头文件和x264的头文件复制到该目录下,将生成的libffmpeg.so文件复制到工程的app/libs/armeabi-v7a/ 目录,如下图所示:


复制文件

7、使用Cmake 配置FFmpeg
由于Android Studio 2.2以后集成了Cmake,本人也更喜欢用Cmake,习惯使用ndk-build的同学请自行查找资料,使用ndk-build的资料非常多,这里就不介绍了。首先在build.gradle下配置前面复制到armeabi-v7a目录的libffmpeg.so文件:

defaultConfig {
        applicationId "com.cgfay.ffmpegsample"
        minSdkVersion 21
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11"
            }
            ndk {
                abiFilters "armeabi-v7a"
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    sourceSets.main {
        jniLibs.srcDirs = ['libs']
        jni.srcDirs = []
    }

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }

这样,so库文件就加载进来了,接下来我们需要我们需要配置CMakeLists.txt。
CMakeLists.txt的配置如下:

# 设置cmake最低版本
cmake_minimum_required(VERSION 3.4.1)

# 设置路径
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../libs)

# 加载头文件
include_directories(src/main/cpp/include)

# 加载ffmpeg库
add_library( ffmpeg
             SHARED
             IMPORTED )
set_target_properties( ffmpeg
                       PROPERTIES IMPORTED_LOCATION
                       ../../../../libs/armeabi-v7a/libffmpeg.so )

# 添加自身的jni库
add_library( native-lib

             SHARED

             src/main/cpp/native-lib.cpp )

# 查找Android存在的库
find_library( log-lib

              log )

# 链接库文件
target_link_libraries(
                       native-lib

                       # ffmpeg库
                       ffmpeg

                       ${log-lib} )

Cmake的详细配置过程可参考以下这篇文章:
android studio cmake 配置.a连接库

接下来用Gradle Sync同步一下,之后我们就可以开始编写JNI调用了。

我们在MainActivity里面添加一个stringFromFFmpeg的Native(),如下:

package com.cgfay.ffmpegsample;

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

public class MainActivity extends AppCompatActivity {

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

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

    public native String stringFromFFmpeg();

    static {
        System.loadLibrary("native-lib");
    }
}

这里加载了库 native-lib,也就是我们前面CMakeLists中target_link_libraries 链接输出的库,然后我们在native-lib.cpp文件中编写stringFromFFmpeg方法实体,如下:

#include <jni.h>
#include <string>


extern "C" {

#include <libavcodec/avcodec.h>

JNIEXPORT jstring
JNICALL
Java_com_cgfay_ffmpegsample_MainActivity_stringFromFFmpeg(
        JNIEnv *env,
        jobject /* this */) {
    char info[10000] = { 0 };
    sprintf(info, "%s\n", avcodec_configuration());
    return env->NewStringUTF(info);
}

}

这里要用extern "C" 将头文件和方法包裹起来,因为FFmpeg是一个C语言库,而我们使用的则是Cmake +CPP编写代码。至此,我们就把FFmpeg集成到工程里面了,编译运行就可以看到以下界面啦:


运行界面

我们将Demo编译成APK,然后解压可以看到,经过之前的步骤,打包后的libffmpeg.so包只有6.63MB,也不算太大,应该来说满足集成需求的,如下图所示:


libffmpeg.so的大小

Demo地址:FFmpegSample

个人建议,第一次编译使用FFmpeg的同学,最好自己手动操作一遍,纸上得到终觉浅,我在编译的时候也踩了很多坑。操作过一次之后,后面就方便了许多。