Android编译FFmpeg之HelloWorld

编译FFmpeg3.3.1的so文件,并在Android工程中使用。

FFmpeg版本:3.3.1 / OS:Mac OSX

本文博客链接

GitHub,本文介绍内容请查看hello_world分支

ps: 开始的时候我只编译出了6个so文件,缺少libavdevice.solibpostproc.so,主要是因为build_andorid.sh的配置不同,现在可以编译出8个so文件,在文章中的图片出现的都是6个so文件,特此声明。

源代码 GitHub - FFmpegAndroidSupport(注意在 hello_world 分支)

配置 NDK 环境

打开~/.bash_profile文件,添加ndk的环境变量,最后别忘了source .bash_profile更新配置,完成之后运行 ndk-build -v查看版本,没有提示找不到命令就可以了。

1
2
# ndk
export PATH=${PATH}:/Users/march/AndroidRes/sdk/ndk-bundle

修改 configure

修改ffmpeg-3.3.1/configure文件,这个主要是生成的lib包的包名规范成以libxxx.so的形式。 否则生成的so文件在android下是无法加载的,替换过程一定要谨慎,需要全部替换掉。这里我提供一个替换好的configure文件供参考:thumbsup:

1
2
3
4
5
6
7
8
9
10
11
# 找到下面几行替换一下
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)'

编写 build_android.sh 脚本

编写ffmpeg-3.3.1/build_android.sh脚本注意,NDK后面的路径换成自己的路径,可以参考编写好的文件:smile:

关注下面的配置,不要直接拷贝
--disable-avdevice加上之后将不会生成avdevice.so文件
--enable-gpl加上之后将会生成postproc.so文件

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
#!/bin/sh
NDK=/Users/march/AndroidRes/sdk/ndk-bundle
SYSROOT=$NDK/platforms/android-23/arch-arm
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
function build_one
{
./configure \
--target-os=linux \
--prefix=$PREFIX \
--arch=arm \
--disable-doc \
--enable-shared \
--disable-static \
--disable-yasm \
--disable-symver \
--enable-gpl \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--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 clean
make
make install
}
make clean
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one

编译生成 ffmpeg so 库

执行build_android.sh脚本
如果没有权限可以使用chomd +x增加执行权限
然后等一段时间,😯不,是很长时间,所以前面的配置要谨慎,不然编译完了之后发现有问题,就会很💔,你会发现在FFmpeg中出现了一个名为android的文件夹。
目录如下

将编译生成的文件 copy 到 AS 中

目录如下,jniLibs里面是最后我们编译生成好之后拷贝进去的,现在应该是空的,编译生成的so文件会生成在src/main/libs目录里面,生成好之后,如果你使用jniLibs目录加载so,就拷贝到这里。图片中的描述略有歧义。

编写 C 语言的 JNI 接口

这里是拷贝了别人写好的代码,这篇文章主要还是把整个编译流程完成,因此直接用了别人已经写好的,后面的文章会对这一块详细介绍。

编写文件名为ffmpeg_support.c的c文件,声明java调用接口,函数命名需要按照Java_包名_类名_方法名的形式来编写,区分大小写。

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
#include <stdio.h>
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
#ifdef ANDROID
#include <jni.h>
#include <android/log.h>
#define LOGE(format,...) __android_log_print(ANDROID_LOG_ERROR,"myndk",format,##__VA_ARGS__)
#else
#define LOGE(format,...) printf("(>_<)"format "\n",##__VA_ARGS__)
#endif
JNIEXPORT jstring Java_com_march_fas_FFmpegSupport_ffmpegHello(JNIEnv *env,jobject obj)
{
char info[40000] ={0};
av_register_all();
AVCodec *c_temp = av_codec_next(NULL);
while(c_temp != NULL){
if(c_temp->decode!=NULL){
sprintf(info,"%s[Dec]",info);
}else{
sprintf(info,"%s[Enc]",info);
}
switch(c_temp->type){
case AVMEDIA_TYPE_VIDEO:
sprintf(info,"%s[Video]",info);
break;
case AVMEDIA_TYPE_AUDIO:
sprintf(info,"%s[AUDIO]",info);
break;
default:
sprintf(info,"%s[Other]",info);
break;
}
sprintf(info,"%s[%10s]\n",info,c_temp->name);
c_temp=c_temp->next;
LOGE("chendong");
}
return (*env)->NewStringUTF(env,info);
}

编写 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
LOCAL_PATH :=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := libavcodec-57.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := libavfilter-6.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := libavformat-57.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := libavutil-55.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := libswresample-2.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := libswscale-4.so
include $(PREBUILT_SHARED_LIBRARY)
#Program
include $(CLEAR_VARS)
LOCAL_MODULE :=sffhelloworld
LOCAL_SRC_FILES := simplest_ffmpeg_helloworld.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

关于Application.mk的相关配置可以查看官方文档
下面android-14指的是最小支持的AndroidApi在4.0以上,具体看可以查看官方文档APP_PLATFORM这块的内容。这个跟你在manifest文件里面配置的min-sdk也有些关联,不拼配会有警告,不过现在都会在gradle文件中配置minSdk,不用在意也可以,实在强迫症就在manifest里面再声明一次。

1
2
APP_ABI := armeabi armeabi-v7a
APP_PLATFORM := android-14

编译生成可用的 so 文件

local.properties 如下配置ndk目录,通常是默认配置好的。

1
2
ndk.dir=/Users/march/AndroidRes/sdk/ndk-bundle
sdk.dir=/Users/march/AndroidRes/sdk

然后进入到terminal,cd到jni目录,执行 ndk-build 命令
等待一段时间
编译完成的结果应该是这样的,如果你使用jniLibs目录作为加载so的目录,将so文件拷贝到jniLibs中。

app/build.gradle 配置以下代码,可以将 jniLibs 目录指向 libs,这样就不需要每次拷贝 so 文件到 jniLibs

1
2
3
4
5
6
7
8
sourceSets {
//定义编译时编译文件的路径
main {
res.srcDirs = ['src/main/res']
jniLibs.srcDirs = ['src/main/libs']
jni.srcDirs = []
}
}

调用 JNI

加载so文件以及java层的调用接口

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
public class FFmpegSupport {
static {
try {
System.loadLibrary("avutil-55");
System.loadLibrary("swresample-2");
System.loadLibrary("avcodec-57");
System.loadLibrary("avformat-57");
System.loadLibrary("swscale-4");
System.loadLibrary("avfilter-6");
System.loadLibrary("ffmpeg_support");
} catch (Exception e) {
e.printStackTrace();
}
}
public static native String ffmpegHello();
}
// 测试
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FFmpegSupport.ffmpegHello();
}
}

运行项目

编辑app/build.gradle配置好so加载路径,将so文件拷贝进jniLibs目录,当然你如果喜欢放在libs目录里面也是可以的,一定要记得armeabi/xxx.so,abi目录不要忘记,不然会提示找不到,不要问我为什么特别提醒 :smile:

1
2
3
4
5
6
7
sourceSets {
//定义编译时编译文件的路径
main {
res.srcDirs = ['src/main/res']
jniLibs.srcDirs = ['src/main/jniLibs']
}
}

点击运行,出现了以下错误:Your project contains C++ files but it is not using a supported native build system。下面的配置可以解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
// 在gradle.properties添加
Android.useDeprecatedNdk=true
// 在app/build.gradle 添加jni.srcDirs = []这一行
sourceSets {
//定义编译时编译文件的路径
main {
res.srcDirs = ['src/main/res']
jniLibs.srcDirs = ['src/main/jniLibs']
jni.srcDirs = []
}
}
------ 本文结束 🎉🎉 谢谢观看  ------