Mac下为Android编译FFMPEG和x264(二)

May 26, 2016

前言

前一篇文章我们编译好了一个so文件(或者一个个独立的so,为了方便,我合并成一个so文件)。那么如何在Android Studio(后面简称AS)中导入ffmpeg以及使用呢?

1. AS的jni支持

AS从1.3开始支持jni,可以在src/main/jni目录下新建C或者C++的文件,AS会自动编译并打包至apk中,但是as目前对于更加复杂的C模块支持还不是很好,在gradle的实验版本里正在加入完善的jni支持,可以看这里http://tools.android.com/tech-docs/new-build-system/gradle-experimental。我暂时禁用了AS的jni功能,自行通过ndk-build生成native模块。然后在AS中调用。

2. jni目录配置

首先我们在项目的app/main目录下新建一个jni目录,并将编译好的libffmpeg.so 和include目录拷贝到jni目录下, 然后新建一个Android.mk文件。

.
├── Android.mk
├── include
│   ├── libavcodec
│   ├── libavdevice
│   ├── libavfilter
│   ├── libavformat
│   ├── libavutil
│   ├── libpostproc
│   ├── libswresample
│   └── libswscale
└── libffmpeg.so

Android.mk文件内容如下:

LOCAL_PATH := $(call my-dir)

# FFmpeg库
include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg
LOCAL_SRC_FILES := libffmpeg.so
include $(PREBUILT_SHARED_LIBRARY)

# Program
include $(CLEAR_VARS)
LOCAL_MODULE := hello-android-jni
LOCAL_SRC_FILES := hello-android-jni.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_LDLIBS := -llog -lz
LOCAL_SHARED_LIBRARIES := ffmpeg
include $(BUILD_SHARED_LIBRARY)

上面的代码里的LOCAL_MODULE := hello-android-jni是我们编译后的ndk模块名,LOCAL_SRC_FILES := hello-android-jni.c是模块源文件。我们在jni目录下新建hello-android-jni.c,填入以下内容:

#include <jni.h>
#include "include/libavcodec/avcodec.h"

JNIEXPORT jstring JNICALL
Java_me_zheteng_android_ffmpeg_MainActivity_getMsgFromJni(JNIEnv *env, jobject instance) {

char info[10000] = {0};
sprintf(info, "%s\n", avcodec_configuration());
return (*env)->NewStringUTF(env, info);
}

在jni目录下运行ndk-build来编译(ndk-build在/path/to/ndk/ndk-build下),我们会发现自动生成了和jni同级的目录libs和obj:

├── jni
├── libs
│   └── armeabi
│   ├── libffmpeg.so
│   └── libhello-android-jni.so
└── obj
   └── local
   └── armeabi

gradle配置

由于gradle默认的jniLibs并没有这个路径下的目录,并且我们要防止gradle默认的jni行为,所以我们修改gradle文件,加入以下内容:

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

完整的build.gradle看起来应该是这样的(可能略有差别

apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"

defaultConfig {
applicationId "me.zheteng.android.ffmpeg"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles.add(file('proguard-android.txt'))
}
}

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

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
}

在Java类里调用jni

要在Java程序里调用native的函数,我们必须加载so库,加载方式很简单,

static {
System.loadLibrary("hello-android-jni");
System.loadLibrary("ffmpeg");
}

回到我们刚才创建的C程序里的函数名Java_me_zheteng_android_ffmpeg_MainActivity_getMsgFromJni,jni命名规则是这样的Java_包名(点变下划线)_类名_函数名 ,所以我们的类名是me.zheteng.android.ffmepg.MainActivity,在MainActivity中声明这个函数:

public native String getMsgFromJni();

调用这个函数就能获取到我们编译ffmpeg时所用的参数。

完整的Activity代码如下:

public class MainActivity extends AppCompatActivity {

static {
System.loadLibrary("hello-android-jni");
System.loadLibrary("ffmpeg");
}

public native String getMsgFromJni();

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

((TextView) findViewById(R.id.text)).setText(getMsgFromJni());
}
}

运行结果

运行结果如下图所示:

截图