FFmpeg的编译是一个大坑,尤其是编译安卓平台的动态库和静态库,应用于APP中。在Linux平台编译是相对简单的,但是我经过尝试在Linux编译静态库没有成功,所以又在windows平台尝试编译了ffempg的动态库,应用成功了,这里分享一下。
一、搭建编译环境
注意一定要注意NDK和FFmpeg的版本,如果你按照我的文章来的那就一定要下载NDK版本R14和FFmpeg版本3.3.9,并不一定要新版本,因为不同的FFmpeg需要不同版本的NDK支持,本文中的是验证过的。minGW的版本不用关心。
1.1 NDK配置
我使用的AndroidStudio,刚开始用自带的ndk-buidle进行编译,是通不过的,后来换到r14版本才能编译通过。
解压下载好的文件android-ndk-r14b-windows-x86_64.zip
到固定目录,尽量不要有中文,我的路径:D:\Android\android-ndk-r14b
。接下来配置到环境变量:
进入计算机->属性->高级系统设置->环境变量,在系统环境中新建NDK_PATH
,目录为刚才解压的NDK14的目录,如下:
![](https://img.haomeiwen.com/i4123353/44f737f67d25413c.png)
再打开PATH
,添加刚才新建的NDK_PATH
:
![](https://img.haomeiwen.com/i4123353/52ff81d913b68c64.png)
然后一路确定后,测试是否配置成功。
打开cmd命令行,输入
ndk-build
,如果成功有类似如下显示:![](https://img.haomeiwen.com/i4123353/2c9728455a3e7432.png)
1.2 minGW安装配置
打开.exe文件,选择安装位置,我的安装目录是D:\programs\minGW
。
点击需要安装的组件,我选择全部勾选:
![](https://img.haomeiwen.com/i4123353/e927786da59cd09f.png)
安装完成后,需要把两个路径添加到环境变量:
D:\programs\minGW
和D:\programs\minGW\msys\1.0\bin
![](https://img.haomeiwen.com/i4123353/bbeecc544a3c6f20.png)
测试一下minGW是否配置成功:
![](https://img.haomeiwen.com/i4123353/ddb79eb7dc7f986b.png)
如果就是配置成功了。
1.3 下载FFmpeg源码
切记一定下载3.3.9版本的,我试过编译4.0的是编译不过的。
![](https://img.haomeiwen.com/i4123353/62e3a5587b8fa09e.png)
下载后解压即可。
二、FFmpeg源码编译
那么问题来了,怎么执行编译命令呢?
这时候minGW上场了,还记得D:\programs\minGW\mysys\1.0\
吗,它下面有一个msys.bat
文件,双击打开就是类似于shell的环境。
2.1 修改生成的lib库文件名
编译之前首先要做件事情,就是修改源码目录下的configure
文件,因为我要的是Android平台的so库,所以要在配置文件中修改生成的库名称。使用记事本打开就行,修改方案如下:
# 原来的配置内容:
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)'
什么?你问我直接编译完了修改文件行不行,我认为是不行的,当然我也测试过。反正按照我说的做,保证行。
2.2开始编译
编译都是分三个步骤:
- configure
- make
- make install
因为ffmpeg编译时configure的参数很多,直接写在命令行会很麻烦,我们把这些写在脚本里面就很方便了:
#!/bin/bash
#NDK路径根据你自己设置的决定,注意不要直接拷贝window路径,因为linux不识别`\`,要使用`/`
NDK=D:/Android/android-ndk-r14b
SYSROOT=$NDK/platforms/android-18/arch-arm
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64
function build_one
{
./configure \
--prefix=$PREFIX \
--enable-shared \
--disable-static \
--disable-doc \
--enable-cross-compile \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=armv7-a \ //注意平台
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
}
CPU=armv7-a
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one
read -p "Press any key to continue."
保存为build_android.sh,其它名字也行。
注意里面的参数arch=armv7-a
,新版的AndroidStudio已经不支持armeabi
了,在使用so库的时候要与arch参数对应,后面会讲到。
打开mysys.bat,进入FFmpeg的源码目录:
![](https://img.haomeiwen.com/i4123353/79d76edd9a16391a.png)
添加可执行权限:
chmod 777 build_android.sh
![](https://img.haomeiwen.com/i4123353/ca9e27d5e88860d8.png)
可以看到,使用
ls -l
是看不到有可执行权限的,不过没关系。
步骤一、执行configure
./build_android.sh //这里要等待较长的时间,我等了七八分钟的样子才有反应
如果没有报错的话就会打印:
Press any key to continue. //按任意键退出脚本
步骤二、执行make
make //这一步会编译所有的.c文件,生成很多的.o文件,时间较长
步骤三、执行make install
make install //这一步会生成头文件和lib库到步骤一中configure指定的路径android/armv7-a目录
执行这几步过程中可能会出错,如果出错,请严格对比版本号和环境变量是否正确。
生成文件目录如下:
![](https://img.haomeiwen.com/i4123353/fe7edd66ecaf26e6.png)
![](https://img.haomeiwen.com/i4123353/b5fe598b4ec0d592.png)
接下来就是在AndroidStudio中使用了。
什么?为什么有带数字的,有不带数字的,该用哪个呢?用带数字的,至于为什么我也不知道,反正我试过只能用带数字的。
三、在AndroidStudio中使用编译好的FFmpeg库
我使用的较新的AndroidStudio版本,为什么要说这个呢?因为新版的AndroidStudio支持使用cmake,而不需要自己写Android.mk和Application.mk这些文件了。所以需要一定的cmake语法知识,大家可以自行搜索,需要几个简单的就行,我也会给出注释。
![](https://img.haomeiwen.com/i4123353/da87995067465271.png)
3.1 新建工程/Module
你需要新建支持c++的工程,再新建Module。如果你使用的工程不支持c++,新建Module时参考android studio: 为现有项目添加C++支持也可以。
我新建的支持C++的工程:
![](https://img.haomeiwen.com/i4123353/9a96af0cf2b358c5.png)
在默认Module里面会生成默认的两个文件:
![](https://img.haomeiwen.com/i4123353/c03075349f9f42e8.png)
build.gradle中也会生成JNI相关配置:
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
...
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt" //cmake配置文件路径
version "3.10.2"
}
}
}
3.2 本地方法并生成头文件
java代码如下:
public class NativeAPI {
public static native String stringFromJNI();
public static native String urlprotocolinfo();
public static native String avformatinfo();
public static native String avcodecinfo();
public static native String avfilterinfo();
}
生成头文件,如果不知道怎么快速生成,请参考AndroidStudio JNI 快速生成头文件。
3.3 编写本地方法的实现
首先要引入FFmpeg库文件,拷贝so库到libs文件夹:
![](https://img.haomeiwen.com/i4123353/a74b597a668219b9.png)
拷贝FFmpeg的头文件到指定目录,这个目录并不确定,只要在CMakeList.txt中引入即可:
![](https://img.haomeiwen.com/i4123353/5802451a9eb5a8fc.png)
修改build.gradle文件,在defaultconfig中添加指定平台,还记得编译FFmpeg时的arch=armv7-a
参数吧,一定要对应,否则编译可以通过,但是运行时会报错。
//指定平台,与编译FFmpeg时的arch参数对应
ndk{
abiFilters 'armeabi-v7a'
}
//指定SO库路径
3.4 CMakeList.txt
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
#lib库工程名,并非so生成的so库名称,可以不写
project(TestFFmpeg)
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
#包含指定目录的头文件
include_directories(include/)
#添加库文件 参数:生成的库名称 动态库(即so库) 源文件
add_library(native-lib SHARED native-lib.cpp)
#创建导入的库目标,FFmpeg7个SO库
add_library(avutil-55 SHARED IMPORTED)
add_library(swresample-2 SHARED IMPORTED)
add_library(avcodec-57 SHARED IMPORTED)
add_library(avfilter-6 SHARED IMPORTED)
add_library(swscale-4 SHARED IMPORTED)
add_library(avdevice-57 SHARED IMPORTED)
add_library(avformat-57 SHARED IMPORTED)
#设置变量,CMAKE_SOURCE_DIR是CMakeList.txt的路径
set(LIB_DIR ${CMAKE_SOURCE_DIR}/../../../libs/${ANDROID_ABI})
#message(SEND_ERROR "jni libs dir=${LIB_DIR}")
#设置目标属性,例如avutil-55的IMPORTED_LOCATION属性值是${LIB_DIR}/libavutil-55.so
set_target_properties(avutil-55 PROPERTIES IMPORTED_LOCATION ${LIB_DIR}/libavutil-55.so)
set_target_properties(swresample-2 PROPERTIES IMPORTED_LOCATION ${LIB_DIR}/libswresample-2.so)
set_target_properties(avcodec-57 PROPERTIES IMPORTED_LOCATION ${LIB_DIR}/libavcodec-57.so)
set_target_properties(avfilter-6 PROPERTIES IMPORTED_LOCATION ${LIB_DIR}/libavfilter-6.so)
set_target_properties(swscale-4 PROPERTIES IMPORTED_LOCATION ${LIB_DIR}/libswscale-4.so)
set_target_properties(avdevice-57 PROPERTIES IMPORTED_LOCATION ${LIB_DIR}/libavdevice-57.so )
set_target_properties(avformat-57 PROPERTIES IMPORTED_LOCATION ${LIB_DIR}/libavformat-57.so)
#在指定目录下搜索一个库, 保存在变量中
find_library(
# 变量名
log-lib
#搜索log库
log)
#目标文件与库文件进行链接
target_link_libraries(
# Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
avutil-55
swresample-2
avcodec-57
avfilter-6
swscale-4
avdevice-57
avformat-57
${log-lib})
native-lib.cpp
没有写完整,最后有Demo地址
extern "C" {
//因为FFmpeg是C库,所以要在extern "C"里面
#include "include/com_flyscale_testforffmpeg_NativeAPI.h"
#include "include/libavformat/avformat.h"
#include "include/libavutil/avutil.h"
#include "include/libavfilter/avfilter.h"
#include "include/libavcodec/avcodec.h"
JNIEXPORT jstring JNICALL Java_com_flyscale_testforffmpeg_NativeAPI_urlprotocolinfo
(JNIEnv *env, jclass clazz) {
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);
}
...
运行即可。
Demo下载地址
网友评论