第一次完成FFmepg的移植,编译ffmpeg4Android

本文主要实现了FFmpeg的编译和移植,首先在linux下将官网下载的源码编译成.so文件,然后使用android-studio配合NDK工具,将.so文件移植到android项目当中,简单地介绍了如何一步步完成FFmpeg的编译流程

参考文章:手把手图文并茂教你用Android Studio编译FFmpeg库并移植

下面是我自己在ubuntu下编译

准备的编译工具

Git,NDK

安装git,检查本地git,git --version

直接用命令符安装: sudo apt install git

也可以去官网下载,git官网下载

如果已经有了git,想更新到最新版,可以输入 git clone https://github.com/git/git,再进行编译安装

然后下载NDK(现在已经有13的版本了),推荐使用android studio安装ndk,下载的ndk路径默认在AndroidSDK的ndk-boudle文件中

配置NDK的环境

使用terminal配置电脑的环境,(个人电脑,我直接以管理员权限配置的系统环境变量)

sudo gedit /etc/profile

打开之后,把我们的ndk路径配置进来,

1
2
3
#Android NDK
export ANDROID_NDK=/path/to/ndk(你的ndk解压路径)
export PATH=$PATH:$ANDROID_NDK

下载ffmpeg的最新的源码

新建一个ffempeg的工作文件夹,例如 mkdir workplace

git clone https://git.ffmpeg.org/ffmpeg.git

也可以直接去官网下载:ffmpeg官网下载

修改ffmpeg的configure文件

为了保证我们编译出的文件是以.so的后缀名
将文本中的3209-3212这4行:

1
2
3
4
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)'

修改改为:

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

编写编译脚本

写脚本的直观上的好处就是省去了一步步的执行编译命令,通常的在编译之前都需要进行配置,设置相应的环境变量,比如指定编译工具、编译平台等等,查看所有的配置选项可以在ffmpeg目录执行如下命令:

./configure –help

当然此脚本中,我们只需要设置几个我们比较关心的配置。

1. NDK的路径:
    NDK=/path/to/ndk

2. 编译的ndk plaform版本:
    SYSROOT=$NDK/platforms/android-23/arch-arm/

3. 交叉编译工具:
    TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64

4. CPU平台:
    CPU=arm

5. 编译完成后安装目录(当前目录下的android文件夹):
    PREFIX=./android/$CPU

确定我们需要的配置之后,在ffmpeg的目录下面新建一个脚本 build4android.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/sh
NDK=/path/to/ndk
SYSROOT=$NDK/platforms/android-23/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
CPU=arm
PREFIX=./android/$CPU
ADDI_CFLAGS="-marm"
config_para()
{
./configure \
--prefix=$PREFIX \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-avdevice \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
}
config_para

这里增加一个方便的脚本技巧,便于修改自己的配置,我们将这些configure配置参数写进一个脚本function,建议如下语法:

1
2
3
4
5
func_name()
{
./configure \
<---我们的参数配置---/>
}

另外一种一种语法(这种可能会报找不到function的错误,不推荐使用):

1
2
3
4
5
function func_name
{
./configure \
<---我们的参数配置---/>
}

还有就是脚本编辑推荐使用vim非常方便,变量参数一目了然


FFmpegBuildScript

开始编译

进入我们的FFmpeg的源码目录,执行刚才的脚本:
(先增加执行权限 sudo chmod +x build4android.sh,以防权限不够)

./build4anroid.sh

脚本执行中:脚本执行中图片

脚本执行完成:脚本执行完成图图片

生成的android/arm/目录中的文件,图中标出的为链接文件可以删除:

生成的.so文件图片

移植我们的.so文件

拷贝.so文件到android-studio工程文件中

在android-studio中切换成project工程目录模式,在app/src/main目录下面新建一个jni文件夹,将上面第6步中的include文件夹拷贝到此文件夹中,再在jni文件夹中新建一个lib文件夹,将.so文件拷贝进来,新建Android.mk,Application.mk和c文件目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
├── Android.mk
├── Application.mk
├── ffmpeg-jni.c
├── include
│ ├── libavcodec
│ ├── libavfilter
│ ├── libavformat
│ ├── libavutil
│ ├── libswresample
│ └── libswscale
└── lib
├── libavcodec-57.so
├── libavfilter-6.so
├── libavformat-57.so
├── libavutil-55.so
├── libswresample-2.so
└── libswscale-4.so

编写Android.mk文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavcodec
LOCAL_SRC_FILES:= lib/libavcodec-57.so
LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavformat
LOCAL_SRC_FILES:= lib/libavformat-57.so
LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libswscale
LOCAL_SRC_FILES:= lib/libswscale-4.so
LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavutil
LOCAL_SRC_FILES:= lib/libavutil-55.so
LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavfilter
LOCAL_SRC_FILES:= lib/libavfilter-6.so
LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libswresample
LOCAL_SRC_FILES:= lib/libswresample-2.so
LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= FFmpegCodec
LOCAL_SRC_FILES:= ffmpeg-jni.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_LDLIBS := -llog -lz
LOCAL_SHARED_LIBRARIES := avcodec avfilter avformat avutil swresample swscale
include $(BUILD_SHARED_LIBRARY)

编写Application.mk文件

1
2
3
4
APP_ABI :=armeabi //这里可选armeabi-v7a,x86,mips和all等
APP_PLATFORM := android-23
APP_OPTIM := release //默认是relaese,也可以设置成debug
APP_STL := gnustl_static //这里是可选,当你编写下面的C文件的时候这里就可以体现出作用,例如你要使用`#include <string>`这个库的时候,默认的system库是没有包含这个的

编写C文件

在上面编写的Android.mk中,我们制定了c文件的名字为ffmpeg-jni.c,所以c文件的与此保持一致,

函数申明语法:JNIEXPORT jstring Java_包名activity名函数名,包名中间的点号.全部变成下划线_

例如:我在包名com.righere.ffmpegndkbuild下面的MainActivity中要使用avcodecInfo这个函数,

JNIEXPORT jstring Java_com_righere_ffmpegndkbuild_MainActivity_avcodecInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include "libavformat/avformat.h"
#include <libavfilter/avfilter.h>
#include <jni.h>
JNIEXPORT jstring Java_com_righere_ffmpegndkbuild_MainActivity_avcodecInfo(JNIEnv* env, jobject obj)
{
char info[4000] = { 0 };
int count = 100; //输出前100个codec名字
av_register_all();//初始化所有decoder和encoder,注册所有容器类型和codec
AVCodec *c_temp = av_codec_next(NULL);
while (c_temp != NULL && count > 0){
//输出解码器和编码器
if(c_temp->decode != NULL){
sprintf(info,"%s[Dec]",info);
}
else{
sprintf(info,"%s[Enc]",info);
}
sprintf(info,"%s[%10s]\n",info,c_temp->name);
c_temp = c_temp->next;
count--;
}
return (*env)->NewStringUTF(env, info);
}

修改gradle(修改的是app的build.gradle)

  • 告诉gradle,.mk文件的路径(在android{–}里面添加):
1
2
3
4
5
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
  • 告诉gradle,完成编译之后,我们的生成的.so文件在哪个目录下面(在android{–}里面添加):

这里我编译的armeabi版本的生成的.so文件自动会放在了’src/main/libs/armeabi’这个目录下面,其他的版本只需要把armeabi名字变一下就行了

1
2
3
4
sourceSets.main {
jni.srcDirs = [] //disable automatic ndk-build
jniLibs.srcDirs = ['src/main/libs/armeabi']
}

  • 告诉gradle,ndk-build的时候还要执行哪些附加选项(在defaultConfig{}里面添加):

这一步其实可以看做是对.mk文件的补充

1
2
3
4
5
6
ndk {
abiFilter "armeabi" //在Application.mk里面设置保持一致,如果是编译了多个平台的,可以指定编译多平台的或者
moduleName "FFmpegCodec" //jni模块的名字,与android.mk文件保持一致
ldLibs "log", "z", "m", "jnigraphics", "android" //这里可以设置ldLibs选项,比如你想调试jni的时候必须添加"log"参数
}

执行ndk-build


ndk-build

编译完成main文件夹下会增加libsobj两个文件夹,其中libs/armeabi下就是我们需要的.so文件

1
2
3
4
5
6
7
8
9
10
11
├── jni
│ ├── Android.mk
│ ├── Application.mk
│ ├── ffmpeg-jni.c
│ ├── include
│ └── lib
├── libs
│ └── armeabi
├── obj
│ └── local
└── res

切换回Android结构的工程试图,系统会自动把生成的.so文件放入jniLibs中,看到这个FFmpeg的移植基本就成功了


Android—structure

测试FFmpeg移植

在MainActivity中添加测试代码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MainActivity extends AppCompatActivity {
//jni
public native String avcodecInfo();
static {
System.loadLibrary("FFmpegCodec");
System.loadLibrary("avcodec-57");
System.loadLibrary("avfilter-6");
System.loadLibrary("avformat-57");
System.loadLibrary("avutil-55");
System.loadLibrary("swresample-2");
System.loadLibrary("swscale-4");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView Codec_info = (TextView) findViewById(R.id.TextView_codec_info);
Codec_info.setMovementMethod(ScrollingMovementMethod.getInstance());
Codec_info.setText(avcodecInfo());
}
}