首先感谢文中所链接到的文章地址的作者,向前辈们学习!
此文章基于前辈们的博客而摘抄与修改完成。让入门的童鞋少走些弯路,作者亦是如此。
编译脚本摘自:编译Android平台使用的FFmpeg(armeabi,armeabi-v7a,arm64-v8a,x86,x86_64)
移植到Android中摘自:编译FFmpeg4.0.1并移植到Android app中使用(最详细的FFmpeg-Android编译教程)
编译平台:macOS Mojave 10.14.2
ndk环境 :android-ndk-r14b
FFmpeg源码版本:FFmpeg-n4.0.1
---------------------------------------- 操作开始 ----------------------------------------
一、环境搭建
1.下载ndk
我用的是ndk r14b,附上下载地址:https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip
将ndk下载到 /home/ndk/目录下,下载完成后执行tar -zxvf android-ndk-r14b-linux-x86_64.zip解压
2.下载FFmpeg4.0.1
下载地址:https://codeload.github.com/FFmpeg/FFmpeg/tar.gz/n4.0.1
下载完成后执行tar -zxvf n4.0.1解压!
二、编译FFmpeg
- 修改configure脚本
# 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.编写编译脚本
取自与夏同炙博客
a.进入到源码目录,创建编译脚本
cd 源码根目录
vim build.sh
b.编写脚本(需要什么架构的,自己选择)
#!/bin/bash
MY_LIBS_NAME=FFmpeg-n4.0.1
MY_DIR=FFmpeg-n4.0.1
# cd ./${MY_DIR}
#编译的过程中产生的中间件的存放目录,为了区分编译目录,源码目录,install目录
MY_BUILD_DIR=binary
NDK_PATH=/Users/KevenTao/Documents/work_soft/AndroidStudio/android-ndk-r14b
BUILD_PLATFORM=darwin-x86_64
TOOLCHAIN_VERSION=4.9
ANDROID_VERSION=24
ANDROID_ARMV5_CFLAGS="-march=armv5te"
ANDROID_ARMV7_CFLAGS="-march=armv7-a -mfloat-abi=softfp -mfpu=neon" #-mfloat-abi=hard -mfpu=vfpv3-d16 #-mfloat-abi=hard -mfpu=vfp
ANDROID_ARMV8_CFLAGS="-march=armv8-a"
ANDROID_X86_CFLAGS="-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32"
ANDROID_X86_64_CFLAGS="-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel"
# params($1:arch,$2:arch_abi,$3:host,$4:cross_prefix,$5:cflags)
build_bin() {
echo "-------------------star build $2-------------------------"
ARCH=$1 # arm arm64 x86 x86_64
ANDROID_ARCH_ABI=$2 # armeabi armeabi-v7a x86 mips
PREFIX=$(pwd)/dist/${MY_LIBS_NAME}/${ANDROID_ARCH_ABI}/
HOST=$3
SYSROOT=${NDK_PATH}/platforms/android-${ANDROID_VERSION}/arch-${ARCH}
CFALGS=$5
TOOLCHAIN=${NDK_PATH}/toolchains/${HOST}-${TOOLCHAIN_VERSION}/prebuilt/${BUILD_PLATFORM}
CROSS_PREFIX=${TOOLCHAIN}/bin/$4-
# build 中间件
BUILD_DIR=./${MY_BUILD_DIR}/${ANDROID_ARCH_ABI}
echo "pwd==$(pwd)"
echo "ARCH==${ARCH}"
echo "PREFIX==${PREFIX}"
echo "HOST==${HOST}"
echo "SYSROOT=${SYSROOT}"
echo "CFALGS=$5"
echo "CFALGS=${CFALGS}"
echo "TOOLCHAIN==${TOOLCHAIN}"
echo "CROSS_PREFIX=${CROSS_PREFIX}"
#echo "-------------------------按任意键继续---------------------"
#read -n 1
#echo "-------------------------继续执行-------------------------"
# mkdir -p ${BUILD_DIR} #创建当前arch_abi的编译目录,比如:binary/armeabi-v7a
# cd ${BUILD_DIR} #此处 进了当前arch_abi的2级编译目录
bash ./configure \
--prefix=${PREFIX} \
--target-os=linux \
--arch=${ARCH} \
--sysroot=$SYSROOT \
--enable-cross-compile \
--cross-prefix=${CROSS_PREFIX} \
--extra-cflags="$CFALGS -Os -fPIC -DANDROID -Wfatal-errors -Wno-deprecated" \
--extra-cxxflags="-D__thumb__ -fexceptions -frtti" \
--extra-ldflags="-L${SYSROOT}/usr/lib" \
--enable-shared \
--enable-asm \
--enable-neon \
--disable-encoders \
--enable-encoder=aac \
--enable-encoder=mjpeg \
--enable-encoder=png \
--disable-decoders \
--enable-decoder=aac \
--enable-decoder=aac_latm \
--enable-decoder=h264 \
--enable-decoder=mpeg4 \
--enable-decoder=mjpeg \
--enable-decoder=png \
--disable-demuxers \
--enable-demuxer=image2 \
--enable-demuxer=h264 \
--enable-demuxer=aac \
--disable-parsers \
--enable-parser=aac \
--enable-parser=ac3 \
--enable-parser=h264 \
--enable-gpl \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-symver \
--disable-debug \
--enable-small
make clean
make
make install
#从当前arch_abi编译目录跳出,对应上面的cd ${BUILD_DIR},以便function多次执行
cd ../../
echo "-------------------$2 build end-------------------------"
}
# build armeabi
# build_bin arm armeabi arm-linux-androideabi arm-linux-androideabi "$ANDROID_ARMV5_CFLAGS"
#build armeabi-v7a
# build_bin arm armeabi-v7a arm-linux-androideabi arm-linux-androideabi "$ANDROID_ARMV7_CFLAGS"
#build arm64-v8a
build_bin arm64 arm64-v8a aarch64-linux-android aarch64-linux-android "$ANDROID_ARMV8_CFLAGS"
#build x86
# build_bin x86 x86 x86 i686-linux-android "$ANDROID_X86_CFLAGS"
#build x86_64
# build_bin x86_64 x86_64 x86_64 x86_64-linux-android "$ANDROID_X86_64_CFLAGS"
3.运行编译脚本
chmod +x build.sh
./build.sh
4.编译完成
等到编译完成后在源码目录下会是这样
include中为头文件 、lib中为编译好的so文件
至此编译就完成了
三、移植到Android App中去
第一步、 jni编译
1. 文件拷贝
- 新建一个jni文件夹(任意位置都行)
- 将所有的.so文件放到 …\jni\prebuilt\目录下;
- 将 刚刚include目录下的所有文件夹复制到 …\jni\目录下;
- 将源码fftools目录下的以下文件复制到 …\jni\目录下
cmdutils.h 、ffmpeg.h、ffmpeg.c、ffmpeg_opt.c、config.h、ffmpeg_filter.c、cmdutils.c、ffmpeg_hw.c
2. 修改cmdutils
- 打开cmdutils.h,将
void show_help_children(const AVClass *class, int flags);
改为void show_help_children(const AVClass *clazz, int flags);
否则和C++一起编译会出问题 - 打开cmdutils.c将
void exit_program(int ret)
中的退出函数注释掉,否则命令执行完会导致APP退出:
void exit_program(int ret)
{
//if (program_exit)
// program_exit(ret);
//exit(ret);
}
3. 修改ffmpeg.c
找到入口函数int main(int argc, char **argv)
将其修改为int ffmpeg_exec(int argc, char **argv)
并将该函数末尾此行代码注释掉:
//exit_program(received_nb_signals ? 255 : main_return_code);
同时在ffmpeg.h中添加函数申明:
int ffmpeg_exec(int argc, char **argv);
在函数static void ffmpeg_cleanup(int ret)
末尾加上以下代码:
nb_filtergraphs = 0;
nb_output_files = 0;
nb_output_streams = 0;
nb_input_files = 0;
nb_input_streams = 0;
4. 输出ADB日志
在ffmpeg.c中引入头文件
#include "android/log.h"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , "ffmpeg.c", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , "ffmpeg.c", __VA_ARGS__)
实现log_callback_null
函数
static void log_callback_null(void *ptr, int level, const char *fmt, va_list vl)
{
static int print_prefix = 1;
static int count;
static char prev[1024];
char line[1024];
static int is_atty;
av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix);
strcpy(prev, line);
//sanitize((uint8_t *)line);
if (level <= AV_LOG_WARNING) {
LOGE("%s", line);
} else {
LOGD("%s", line);
}
}
5. 实现JNI接口
编写ffmpeg-invoke.cpp并放到 …\jni\目录下:
#include <jni.h>
#include <string>
#include "android/log.h"
extern "C"{
#include "ffmpeg.h"
}
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , "ffmpeg-invoke", __VA_ARGS__)
extern "C"
JNIEXPORT jstring JNICALL
Java_com_mission_ffmpeg_jni_FFmpegInvoke_test(JNIEnv *env, jclass type) {
std::string retValue = "FFmpeg invoke test";
return env->NewStringUTF(retValue.c_str());
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_mission_ffmpeg_jni_FFmpegInvoke_run(JNIEnv *env, jclass type, jint cmdLen,
jobjectArray cmd) {
char *argCmd[cmdLen] ;
jstring buf[cmdLen];
LOGD("length=%d",cmdLen);
for (int i = 0; i < cmdLen; ++i) {
buf[i] = static_cast<jstring>(env->GetObjectArrayElement(cmd, i));
char *string = const_cast<char *>(env->GetStringUTFChars(buf[i], JNI_FALSE));
argCmd[i] = string;
LOGD("argCmd=%s",argCmd[i]);
}
ffmpeg_exec(cmdLen, argCmd);
return 0;
}
注意:将
com_mission_ffmpeg_jni_FFmpegInvoke
改为自己工程中的JAVA文件对应的包名和路径(可以了解下Android jni使用)
6. 编写.mk文件
编写Android.mk文件并放到 …\jni\目录
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libavutil
LOCAL_SRC_FILES := prebuilt/libavutil-56.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libswresample
LOCAL_SRC_FILES := prebuilt/libswresample-3.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libswscale
LOCAL_SRC_FILES := prebuilt/libswscale-5.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libavcodec
LOCAL_SRC_FILES := prebuilt/libavcodec-58.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libavformat
LOCAL_SRC_FILES := prebuilt/libavformat-58.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libavfilter
LOCAL_SRC_FILES := prebuilt/libavfilter-7.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libavdevice
LOCAL_SRC_FILES := prebuilt/libavdevice-58.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg-invoke
LOCAL_SRC_FILES :=ffmpeg-invoke.cpp \
ffmpeg_hw.c\
cmdutils.c \
ffmpeg_filter.c \
ffmpeg_opt.c \
ffmpeg.c
LOCAL_C_INCLUDES := /Users/KevenTao/Downloads/FFmpeg-n4.0.1
LOCAL_LDLIBS := -llog -ljnigraphics -lz -landroid -lm -pthread -L$(SYSROOT)/usr/lib
LOCAL_SHARED_LIBRARIES := libavcodec libavfilter libavformat libavutil libswresample libswscale libavdevice
include $(BUILD_SHARED_LIBRARY)
注意:将
LOCAL_C_INCLUDES
改为自己电脑中FFmpeg源码所在目录
编写Application.mk并放到 …\jni\目录下
APP_ABI := arm64-v8a
APP_PLATFORM=android-21
APP_OPTIM := release
APP_STL := stlport_static
7. 编译
确认所需文件都准备就绪:
cd到该目录,执行ndk-build(确保ndk路径已经配置到环境变量中)
编译成功后长这样
第二步、新建Android Studio测试工程 FFmpegTest
1. 在FFmpegTest\app\src\main\目录下新建jniLibs目录,将上一步编译生成的 \libs\armeabi-v7a文件夹复制到jniLibs目录下;
在com.mission.ffmpeg.jni路径下新建FFmpegInvoke.java
public class FFmpegInvoke {
static {
System.loadLibrary("avutil-56");
System.loadLibrary("postproc-55");
System.loadLibrary("avcodec-58");
System.loadLibrary("swresample-3");
System.loadLibrary("avformat-58");
System.loadLibrary("swscale-5");
System.loadLibrary("avfilter-7");
System.loadLibrary("avdevice-58");
System.loadLibrary("ffmpeg-invoke");
}
private static native int run(int cmdLen, String[] cmd);
public static native String test();
public static int run(String[] cmd){
return run(cmd.length,cmd);
}
}
2. 测试
测试是否能够成功调用动态链接库函数
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tvMessage = findViewById(R.id.tv_message);
tvMessage.setText(FFmpegInvoke.test());
}
}
成功会调用ffmpeg-invoke.cpp中Java_com_mission_ffmpeg_jni_FFmpegInvoke_test(JNIEnv *env, jclass type)函数返回的字符串,说明java调用C++函数成功。
四.在第三步编译的时候,我遇到的问题
libavcodec/aaccoder.c: In function 'search_for_ms':
libavcodec/aaccoder.c:803:25: error: expected identifier or '(' before numeric constant
libavcodec/aaccoder.c:865:28: error: lvalue required as left operand of assignment
libavcodec/aaccoder.c:866:25: error: 'B1' undeclared (first use in this function)
libavcodec/aaccoder.c:866:25: note: each undeclared identifier is reported only once for each function it appears in
ffbuild/common.mak:60: recipe for target 'libavcodec/aaccoder.o' failed
make: *** [libavcodec/aaccoder.o] Error 1
这种问题是由于/usr/arm-linux-androideabi/include/asm/termbits.h
文件中已经宏定义了。
#define B0 0000000
导致int B0 = 0, B1 = 0;
实际上变成了int 0000000 = 0, B1 = 0;
导致的。
因此可以更改有问题的头文件
ffmpeg/libavcodec/aaccoder.c(我只发现了这一个)
别的版本可能还有,可以参考(欢雨天的我)的博客
这边也是摘自他的文章
将B0
改成bo
或者其他字符
还有一种方法:由于选择的版本问题。因此你可以checkout低版本的FFmpeg来绕开这个问题。
五、在android 7.0以上还遇到类似这样的问题
java.lang.UnsatisfiedLinkError: dlopen failed: library "/system/lib64/avutil.so" needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace "classloader-namespace"
at java.lang.Runtime.loadLibrary0(Runtime.java:977)
at java.lang.System.loadLibrary(System.java:1530)
at com.sina.weibo.sdk.net.HttpManager.<clinit>(HttpManager.java:43)
at com.sina.weibo.sdk.net.HttpManager.openUrl(HttpManager.java:63)
at com.sina.weibo.sdk.utils.AidTask.loadAidFromNet(AidTask.java:400)
at com.sina.weibo.sdk.utils.AidTask.access$200(AidTask.java:49)
at com.sina.weibo.sdk.utils.AidTask$2.run(AidTask.java:232)
at java.lang.Thread.run(Thread.java:761)