1.简介

* 在之前的文章中,我们已经在windows环境下编译FFMPEG源码得到了.so库文件和include头文件夹。
* android开发-Windows环境下编译FFMPEG源码
<https://blog.csdn.net/qq_38261174/article/details/83210458>
但是我们想要在Android开发时使用FFMPEG的功能,不仅仅把.so库文件和头文件夹移到工程下面就可以直接调用,

还需要我们进行一些配置才能在代码中使用FFMPEG的功能。

* 在进行配置之前,你可以先了解一下 JNI本地接口和NDK开发。
* android开发-NDK-JNI入门教程
<https://blog.csdn.net/qq_38261174/article/details/83239718>
网上搜了一大堆博客教程,讲如何在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入门教程
<https://blog.csdn.net/qq_38261174/article/details/83239718>   找对应的内容。

创建工程完成后你会发现多出来两个东西,稍后会用到。



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按钮,效果如下: