您的位置:首页 > 移动开发 > Android开发

Android平台下编译FFmpeg用于视频压缩处理

2017-11-17 18:24 746 查看
最近因为项目需求需要用到视频压缩处理,其实这个功能要实现出来很简单,到Github上拉几个知名的库分分钟搞定,但是那些比较知名的库功能很齐全所以包也比较大,arm、arm-v7a、arm64-v8a、x86、x86_64等基本全部做到了支持。为了解决这个问题顺便学习学习NDK入门方面的知识,本文仅仅分析arm-v7a架构的编译,其他的几种处理方式原理是一样的,有兴趣自己可以编译下。

编译环境

Windows下编译(需要配置MinGW

Android studio 2.3.3 (需要配置ndk环境)

如果你是linux或者Mac环境那是最方便了,本人偷懒懒得装个Ubuntu所以还是在Windows上编译了,Android环境下的jni开发有两种方式,一种是传统的配置Android.mk,另一种就是AS2.2及以后主推的配置cmakelist方法,这里使用新方法进行编译因为更加简单。

下载相关源码

我在桌面创建一个目录FFmpegInAndroid,里面创建如下几个目录:



x264文件放libx264的源码,fad-aac-0.1.5文件夹放libfdk-aac源码,ffmpeg-3.2.5文件夹放FFmpeg-3.2.5源码,buildoutput里面放我们编译上述源码的so文件及头文件。我们所做的工作就是把号称最好的视频编码器libx264和音频编码器libfdk-aac替换ffmpeg自带的音视频编码器,然后把ffmpeg编译出来丢到buildoutput文件里面去。

编译 x264 视频编码器

在buildoutput目录创建写一个编译x264的脚本x264_arm_build.sh

#!/bin/bash

# ndk 环境(改成自己的相应的环境)
NDK=E:/Android/sdk/ndk-bundle
SYSROOT=$NDK/platforms/android-14/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64
# cpu 架构平台,若要编译 x86 则指定 x86
CPU=armv7-a

# 输出文件的前缀,也就是指定最后静态库输出到那里
PREFIX=$(pwd)/lib/x264/$CPU
# 优化参数
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
ADDI_CFLAGS=""
ADDI_LDFLAGS=""

# 因为当前目录在 build 目录,需要切换到 x264找到源码去执行 config
cd ../x264
function build_x264
{
./configure \
--prefix=$PREFIX \
--disable-shared \
--disable-asm \
--enable-static \
--enable-pic \
--enable-strip \
--host=arm-linux-androideabi \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--sysroot=$SYSROOT
--extra-cflags="-Os -fpic $ADDI_CFLAGS $OPTIMIZE_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make -j4
make install
}

# 执行编译指令
build_x264


编译请特别注意如果直接按照未修改的配置进行编译,结果编译出来的so文件类似libavcodec.so.55.39.101,版本号位于so之后,Android上似乎无法加载。因此需要按如下修改:

将x264/configure文件的

echo "SOSUFFIX=so" >> config.mak
echo "SONAME=libx264.so.$API" >> config.mak
echo "SOFLAGS=-shared -Wl,-soname,\$(SONAME) $SOFLAGS" >> config.mak


修改成

echo "SOSUFFIX=so" >> config.mak
echo "SONAME=libx264-$API.so" >> config.mak
echo "SOFLAGS=-shared -Wl,-soname,\$(SONAME) $SOFLAGS" >> config.mak


然后我们运行MinGW\msys\1.0目录下的msys.bat,cd到该目录执行x264_arm_build.sh脚本



chmod +x 命令是设置执行权限,出现上面的图说明编译成功了,现在去buildoutput/lib目录可以看到编译后生成的文件了。

编译 fad-aac 音频编码器

先在buildoutput目录创建一个配置脚本文件fdkacc_arm_build.sh

# 这个路径要根据自己环境修改
NDK=E:/Android/sdk/ndk-bundle
ANDROID_API=android-14

SYSROOT=$NDK/platforms/android-14/arch-arm/

ANDROID_BIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin

CROSS_COMPILE=${ANDROID_BIN}/arm-linux-androideabi-

CPU=armv7-a

# 输出文件的前缀,也就是指定最后静态库输出到那里
PREFIX=$(pwd)/lib/fdk_aac/$CPU

CFLAGS=""

FLAGS="--enable-static  --host=arm-linux-androideabi --target=android --disable-asm "

export CXX="${CROSS_COMPILE}g++ --sysroot=${SYSROOT}"

export LDFLAGS=" -L$SYSROOT/usr/lib  $CFLAGS "

export CXXFLAGS=$CFLAGS

export CFLAGS=$CFLAGS

export CC="${CROSS_COMPILE}gcc --sysroot=${SYSROOT}"

export AR="${CROSS_COMPILE}ar"

export LD="${CROSS_COMPILE}ld"

export AS="${CROSS_COMPILE}gcc"

# 因为当前目录在 build 目录,需要切换到fdk-aac-0.1.3找到源码去执行
cd ../fdk-aac-0.1.5
function build_fdkacc
{
./configure $FLAGS \
--enable-pic \
--enable-strip \
--prefix=$PREFIX\
$ADDITIONAL_CONFIGURE_FLAG
make clean
make -j12
make install
}

# 执行编译指令
build_fdkacc


同样,我们cd到这个目录执行脚本命令./fdkacc_arm_build.sh,这个等的时间有点长。。。



编译完成后去buildoutput/lib目录就能看到fdk_aac目录了。

交叉编译FFmpeg

所以的准备工作做好了后,我们开始编译ffmpeg,同样的我们还是在buildoutput目录创建一个配置脚本文件ffmpeg_arm_build.sh,由于完全编译的话可能so文件会很大,所以我们编译一个精简版的ffmpeg,我们从如何几个方面入手:

disable-encoders 禁用全部的编码器,然后视频使用libx264,音频使用libfdk_aac,以及图片mjpeg png等

disable-decoders 禁用全部的解码器,然后使用几个常用的

disable-demuxers 禁用全部的视音频分离器,然后使用几个常用的

disable-parsers 禁止全部的解析器,然后使用几个常用的

主要是这四个方面,其他一些应该也是通用作用的,具体没研究过。执行脚本命令./ffmpeg_arm_build.sh



编译完成后去buildoutput/lib目录就能看到ffmpeg目录了。

编译Android环境的FFmpeg

上面我们已经把我们定制的ffmpeg编译出来了,接下来我们需要修改ffmpeg源码的某些c类来满足Android环境下的使用,然后通过JNI给Java调用执行ffmpeg的命令,我们用cMake方式来写jni。

1.创建默认支持jni的项目,找到编译后ffmpeg-3.2.5文件目录找到如下8个文件,然后复制到项目的cpp目录(最新的ffmpeg源码目录没有cmdutils_common_opts.h编译方式未知)

ffmpeg_filter.c
ffmpeg_opt.c
ffmpeg.c
ffmpeg.h
cmdutils_common_opts.h
cmdutils.c
cmdutils.h
config.h (编译后才会有生成config.h)


2.把编译后产生的目录buildoutput/lib/ffmpeg/armv7-a中的产生头文件的include目录拷贝到cpp目录下



3.ffmpeg.c文件中有个main方法,这个就是ffmpeg命令执行的入口,我们把它改成我们自定义的函数名称run_command,并且在ffmpeg.h里面加上函数声明int run_command(int argc,char **argv);。

4.找到cmdutils.c中的exit_program函数,把exit_program函数修改成:

int exit_program(int ret)
{
av_log(NULL, AV_LOG_FATAL,"Quit at %d",ret);

if (program_exit)
program_exit(ret);

//    exit(ret);
return ret;
}


cmdutils.h中exit_program的申明,也把返回类型修改为int类型

int exit_program(int ret);


5.修改ffmpeg.c之前我们自定义的run_command方法,在末尾加上如下代码:

nb_filtergraphs = 0;
progress_avio = NULL;

input_streams = NULL;
nb_input_streams = 0;
input_files = NULL;
nb_input_files = 0;

output_streams = NULL;
nb_output_streams = 0;
output_files = NULL;
nb_output_files = 0;


6.实现我们定义的jni函数,在之前我们创建的ffmpeg_cmd_run.c中:

#include "ffmpeg.h"
#include <jni.h>
JNIEXPORT jint JNICALL
com_example_ffmpeginandroid_MainActivity_ffmpegCommand(JNIEnv *env, jobject instance, jobjectArray cmd) {
int argc = (*env)->GetArrayLength(env,cmd);
char *argv[argc];
int i;
for (i = 0; i < argc; i++) {
jstring js = (jstring) (*env)->GetObjectArrayElement(env,cmd, i);
argv[i] = (char *) (*env)->GetStringUTFChars(env,js, 0);
}
return run_command(argc,argv);
}


7.配置cmakelist用来编译jni,然后把之前编译ffmpeg生成的目录/buildoutput/lib/ffmpeg/armv7-a/lib

里面的七个so文件放到我们项目的/app/src/main/jniLibs/armeabi-v7a里面,还需要把ffmpeg源码放在项目根目录,目的是为了给我们上面引入的几个c文件设置头文件。

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html 
# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
ffmpeg-lib

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
src/main/cpp/cmdutils.c
src/main/cpp/ffmpeg.c
src/main/cpp/ffmpeg_filter.c
src/main/cpp/ffmpeg_opt.c
src/main/cpp/ffmpeg_cmd_run.c
)
add_library(
avcodec
SHARED
IMPORTED
)

add_library(
avfilter
SHARED
IMPORTED
)

add_library(
avformat
SHARED
IMPORTED
)

add_library(
avutil
SHARED
IMPORTED
)

add_library(
swresample
SHARED
IMPORTED
)

add_library(
swscale
SHARED
IMPORTED
)

add_library(
fdk-aac
SHARED
IMPORTED
)

if(${ANDROID_ABI} STREQUAL "armeabi")
set_target_properties(
avcodec
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavcodec.so
)

set_target_properties(
avfilter
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavfilter.so
)

set_target_properties(
avformat
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavformat.so
)

set_target_properties(
avutil
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavutil.so
)

set_target_properties(
swresample
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libswresample.so
)

set_target_properties(
swscale
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libswscale.so
)

set_target_properties(
fdk-aac
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libfdk-aac.so
)
endif(${ANDROID_ABI} STREQUAL "armeabi")

if(${ANDROID_ABI} STREQUAL "armeabi-v7a")

set_target_properties(
avcodec
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libavcodec.so
)

set_target_properties(
avfilter
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libavfilter.so
)

set_target_properties(
avformat
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libavformat.so
)

set_target_properties(
avutil
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libavutil.so
)

set_target_properties(
swresample
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libswresample.so
)

set_target_properties(
swscale
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libswscale.so
)

set_target_properties(
fdk-aac
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libfdk-aac.so
)

endif(${ANDROID_ABI} STREQUAL "armeabi-v7a")

if(${ANDROID_ABI} STREQUAL "arm64-v8a")
set_target_properties(
avcodec
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/arm64-v8a/libavcodec.so
)

set_target_properties(
avfilter
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/arm64-v8a/libavfilter.so
)

set_target_properties(
avformat
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/arm64-v8a/libavformat.so
)

set_target_properties(
avutil
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/arm64-v8a/libavutil.so
)

set_target_properties(
swresample
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/arm64-v8a/libswresample.so
)

set_target_properties(
swscale
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/arm64-v8a/libswscale.so
)

set_target_properties(
fdk-aac
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/arm64-v8a/libfdk-aac.so
)
endif(${ANDROID_ABI} STREQUAL "arm64-v8a")

if(${ANDROID_ABI} STREQUAL "x86")
set_target_properties(
avcodec
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86/libavcodec.so
)

set_target_properties(
avfilter
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86/libavfilter.so
)

set_target_properties(
avformat
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86/libavformat.so
)

set_target_properties(
avutil
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86/libavutil.so
)

set_target_properties(
swresample
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86/libswresample.so
)

set_target_properties(
swscale
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86/libswscale.so
)

set_target_properties(
fdk-aac
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86/libfdk-aac.so
)
endif(${ANDROID_ABI} STREQUAL "x86")

if(${ANDROID_ABI} STREQUAL "x86_64")
set_target_properties(
avcodec
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86_64/libavcodec.so
)

set_target_properties(
avfilter
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86_64/libavfilter.so
)

set_target_properties(
avformat
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86_64/libavformat.so
)

set_target_properties(
avutil
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86_64/libavutil.so
)

set_target_properties(
swresample
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86_64/libswresample.so
)

set_target_properties(
swscale
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86_64/libswscale.so
)

set_target_properties(
fdk-aac
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86_64/libfdk-aac.so
)
endif(${ANDROID_ABI} STREQUAL "x86_64")

# 我们拷贝进来的几个c文件中需要头文件 为了方便使用这里我们直接指向ffmpeg源代码
include_directories(
../ffmpeg-3.2.5

)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
ffmpeg-lib

fdk-aac
avcodec
avfilter
avformat
avutil
swresample
swscale
# Links the target library to the log library
# included in the NDK.
${log-lib} )


8.两个注意点:

1.本文只编译了armeabi-v7a架构的so文件,所以需要在build.gradle中过滤下:

externalNativeBuild {
cmake {
cppFlags ""
}
ndk{
abiFilters "armeabi-v7a"
}
}


2.千万不要忘了加上文件读写权限,不然会报Fatal signal 11 (SIGSEGV), code 1, fault addr 0x18 in tid 9051 (Thread-26083)错误,因为c代码在控制台上只报这一行错误,所以很难找到问题所在,本人也被这个问题坑了很久,最近有时间加上了日志系统才找到问题所在。

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>


使用FFmpeg命令

public class MainActivity extends AppCompatActivity {
private String mFileBeforeName = "out.mp4";
private String mFileAfterName = "my_girl.mp4";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FfmpegNativeBridge.setDebug(true);//开启日志系统

Button bt = (Button) findViewById(R.id.bt_text);
final ProgressBar pb = (ProgressBar) findViewById(R.id.pb);
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pb.setVisibility(View.VISIBLE);
new Thread(new Runnable() {
@Override
public void run() {
String basePath = Environment.getExternalStorageDirectory().getPath();
if (new File(basePath + "/" + mFileBeforeName).exists()
&& !new File(basePath + "/" + mFileAfterName).exists()) {
String cmd_transcoding = String.format(
"ffmpeg -i %s -c:v libx264 %s  -c:a libfdk_aac %s",
basePath + "/" + mFileBeforeName,
"-crf 40",
basePath + "/" + mFileAfterName);
Log.e("命令", cmd_transcoding);
int i = FFmpegCMDRun(cmd_transcoding);
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
pb.setVisibility(View.GONE);
Toast.makeText(MainActivity.this, "压缩成功了", Toast.LENGTH_SHORT).show();
}
});
} else if (new File(basePath + "/" + mFileBeforeName).exists()
&& new File(basePath + "/" + mFileAfterName).exists()) {
pb.post(new Runnable() {
@Override
public void run() {
pb.setVisibility(View.GONE);
Toast.makeText(MainActivity.this, "已经压缩成功了", Toast.LENGTH_SHORT).show();
}
});
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
pb.setVisibility(View.GONE);
Toast.makeText(MainActivity.this, "没找过该视频文件", Toast.LENGTH_SHORT).show();
}
});
}
}
}).start();
}
});

}

/**
* 以空格分割字符串
*/
public int FFmpegCMDRun(String cmd) {
String regulation = "[ \\t]+";
String[] split = cmd.split(regulation);
//执行命令
return FfmpegNativeBridge.RunCommand(split);
}

}


最后附上源码: Github
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: