1.简介
- 在之前的文章中,我们已经在windows环境下编译FFMPEG源码得到了.so库文件和include头文件夹。
- android开发-Windows环境下编译FFMPEG源码
但是我们想要在Android开发时使用FFMPEG的功能,不仅仅把.so库文件和头文件夹移到工程下面就可以直接调用,
还需要我们进行一些配置才能在代码中使用FFMPEG的功能。
- 在进行配置之前,你可以先了解一下 JNI本地接口和NDK开发。
- android开发-NDK-JNI入门教程
网上搜了一大堆博客教程,讲如何在AndroidStudio中配置FFMPEG的.so库和头文件,都是使用一个文件叫 Android.mk,奈何我没有成功过一次。
经过摸打滚爬,我终于汲取了一些经验。找到了一种我更好看懂及理解的方法,那就是不要Android.mk文件了,而是另外一个东西叫
CMakeLists.txt。自我感觉用 CMakeLists.txt简单多了。下面就讲讲我是如何在AndroidStudio中使用FFMPEG的。
2.具体步骤
1.在androidstudio中新建一个工程
我的工程名是 Useffmpeg
包名是 com.liuyan.myapplication
记得勾选 include c++ support ,然后一直next就行了
创建工程后,如果提示没有配置ndk,就自己配置好ndk路径。
ndk配置一定要慎重,不要从androidstudio直接下载,我说过很多遍了。
不知道ndk配置的去这篇文章 android开发-NDK-JNI入门教程 找对应的内容。
创建工程完成后你会发现多出来两个东西,稍后会用到。
2.在main目录下面新建一个包 jniLibs
由于这个包下面的东西是会被打包进APK的,但是我们简简单单创建了没有用,还需要在 build.gradle中配置这个包。
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//自动生成的,不用管
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
//配置我们的 jniLibs
sourceSets.main {
jniLibs.srcDirs = ['src/main/jniLibs']
}
buildTypes {
...
}
//自动生成的不用管
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
dependencies {
...
}
然后sync 一下 project。
把我们之前编译好的 include头文件夹 和.so文件准备好。
在 jniLibs 下创建一个 armeabi 文件夹。把 include 头文件夹移到 jniLibs下面,把.so文件移到armeabi文件内。如下:
3.编写我们的 CMakeLists.txt 文件
cmake_minimum_required(VERSION 3.4.1)
find_library( log-lib
log )
set(distribution_DIR ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
add_library( avutil-55
SHARED
IMPORTED )
set_target_properties( avutil-55
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libavutil-55.so)
add_library( swresample-2
SHARED
IMPORTED )
set_target_properties( swresample-2
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libswresample-2.so)
add_library( avcodec-57
SHARED
IMPORTED )
set_target_properties( avcodec-57
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libavcodec-57.so)
add_library( avfilter-6
SHARED
IMPORTED )
set_target_properties( avfilter-6
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libavfilter-6.so)
add_library( swscale-4
SHARED
IMPORTED )
set_target_properties( swscale-4
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libswscale-4.so)
add_library( avdevice-57
SHARED
IMPORTED)
set_target_properties( avdevice-57
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libavdevice-57.so )
add_library( avformat-57
SHARED
IMPORTED )
set_target_properties( avformat-57
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libavformat-57.so)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp)
include_directories(src/main/jniLibs/include)
target_link_libraries(native-lib swresample-2 avcodec-57 avfilter-6 swscale-4 avdevice-57 avformat-57 avutil-55
${log-lib})
至于为什么是上面那样配置,我也说不上来,自己理解就好,或者自己百度有专门解释 CMakeLists.txt 的内容。
编写完成之后, sync 一下 project。
4.编写 cpp 目录下面的 native-lib.cpp 文件
这个文件中已经有一个示例代码了,MainActivity中也调用了。我们不管这个,继续往下:
我们这一步主要就是用 c代码调用 ffmpeg的功能。
但是为什么我们要到这一步才开始编写 c代码调用 ffmpeg的功能呢?
- 答案当然是我们之前没有配置 头文件及库文件啊,只有配置好了,我们才能在 native-lib.cpp 文件先引入 ffmpeg相关的头文件,才能编写相关代码使用 ffmpeg的功能。
native-lib.cpp 文件编写如下,只是用来测试 ffmpeg能否使用,至少走的通。以后才能使用更强大的ffmpeg的功能。
#include <jni.h>
#include <string>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
jstring
Java_com_liuyan_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
jstring
Java_com_liuyan_myapplication_MainActivity_urlprotocolinfo(
JNIEnv *env, jobject) {
char info[40000] = {0};
av_register_all();
struct URLProtocol *pup = NULL;
struct URLProtocol **p_temp = &pup;
avio_enum_protocols((void **) p_temp, 0);
while ((*p_temp) != NULL) {
sprintf(info, "%sInput: %s\n", info, avio_enum_protocols((void **) p_temp, 0));
}
pup = NULL;
avio_enum_protocols((void **) p_temp, 1);
while ((*p_temp) != NULL) {
sprintf(info, "%sInput: %s\n", info, avio_enum_protocols((void **) p_temp, 1));
}
return env->NewStringUTF(info);
}
jstring
Java_com_liuyan_myapplication_MainActivity_avformatinfo(
JNIEnv *env, jobject) {
char info[40000] = {0};
av_register_all();
AVInputFormat *if_temp = av_iformat_next(NULL);
AVOutputFormat *of_temp = av_oformat_next(NULL);
while (if_temp != NULL) {
sprintf(info, "%sInput: %s\n", info, if_temp->name);
if_temp = if_temp->next;
}
while (of_temp != NULL) {
sprintf(info, "%sOutput: %s\n", info, of_temp->name);
of_temp = of_temp->next;
}
return env->NewStringUTF(info);
}
jstring
Java_com_liuyan_myapplication_MainActivity_avcodecinfo(
JNIEnv *env, jobject) {
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, "%sdecode:", info);
} else {
sprintf(info, "%sencode:", 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;
}
return env->NewStringUTF(info);
}
jstring
Java_com_liuyan_myapplication_MainActivity_avfilterinfo(
JNIEnv *env, jobject) {
char info[40000] = {0};
avfilter_register_all();
AVFilter *f_temp = (AVFilter *)avfilter_next(NULL);
while(f_temp != NULL) {
sprintf(info, "%s%s\n", info, f_temp->name);
f_temp = f_temp->next;
}
return env->NewStringUTF(info);
}
}
这里我编写了4个函数跟 ffmpeg有关,都是返回string,用来测试是否能够走通流程。
加上本来有的一个函数,一共5个函数。
你自己想要编写更多的 函数,可以自己编写函数名,函数命名的规则及参数,可以在上面找到规律,或者自己百度。
但是注意的是:
由于 native-lib.cpp 是 c++编写的,而我们的代码是 c语言编写的,所以,
所有的头文件引入 和 函数编写必须在 extern "C" { } 括号里面:
#include <jni.h>
#include <string>
//一定要在括号里面
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
jstring
Java_com_liuyan_myapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
jstring
Java_com_liuyan_myapplication_MainActivity_urlprotocolinfo(
JNIEnv *env, jobject) {
...
}
...
}
至此,我们的本地方法已经编写好了,所有的准备工作都已经做好了。终于歇了一口气!
5.在 MainActivity.java 中开始使用
首先当然是加载我们的库。
static {
System.loadLibrary("native-lib");
}
然后编写4个 native方法,名字要与之前在 native-lib.cpp文件中编写的函数名对应起来。加上本来有的一个native方法,一共5个。
而且编写完 native方法之后会有一个标记,点击可以跳到对应的 c函数。
使用如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
TextView tv;
Button protocol;
Button format;
Button codec;
Button filter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
protocol = (Button) findViewById(R.id.btn_protocol);
format = (Button) findViewById(R.id.btn_format);
codec = (Button) findViewById(R.id.btn_codec);
filter = (Button) findViewById(R.id.btn_filter);
protocol.setOnClickListener(this);
format.setOnClickListener(this);
codec.setOnClickListener(this);
filter.setOnClickListener(this);
tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
public native String stringFromJNI();
public native String urlprotocolinfo();
public native String avformatinfo();
public native String avcodecinfo();
public native String avfilterinfo();
static {
System.loadLibrary("native-lib");
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_protocol:
tv.setText(urlprotocolinfo());
break;
case R.id.btn_format:
tv.setText(avformatinfo());
break;
case R.id.btn_codec:
tv.setText(avcodecinfo());
break;
case R.id.btn_filter:
tv.setText(avfilterinfo());
break;
default:
break;
}
}
}
然后运行程序到手机上。
这里产生了一个错误:
Error:error: '../../../../src/main/jniLibs/mips64/libswresample-2.so', needed by '../obj/mips64/libnative-lib.so', missing and no known rule to make it
原因是打包过程中,找不到对应的系统架构mips64。因为我们之前只给出了一个系统架构armeabi , 我们可以在 build.gradle中配置一下解决这个问题。
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
cppFlags ""
}
}
ndk {
//我们只有armeabi就只配置这个就行了
abiFilters 'armeabi'
}
}
sourceSets.main {
jniLibs.srcDirs = ['src/main/jniLibs']
}
buildTypes {
...
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
dependencies {
...
}
重新运行程序。点击其中format按钮,效果如下: