1.搭建编译环境
1.安装ubuntu14.04,安装完成后执行以下命令
apt-get update
apt-get install yasm
apt-get install pkg-config
2.下载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
解压
3.下载FFmpeg4.0.1
下载地址:https://codeload.github.com/FFmpeg/FFmpeg/tar.gz/n4.0.1
下载完成后执行tar -zxvf n4.0.1
解压
2.编译FFmpeg
1.修改configure
进入源码根目录,用vim打开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.配置编译脚本
在源码根目录新建build.sh,内容如下:
#!/bin/bash
NDK=/home/ndk/android-ndk-r14b
SYSROOT=$NDK/platforms/android-21/arch-arm/
CPU=armv7-a
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
function build_android
{
./configure \
--prefix=$PREFIX \
--enable-neon \
--enable-hwaccels \
--enable-shared \
--enable-jni \
--enable-mediacodec \
--enable-decoder=h264_mediacodec \
--disable-static \
--disable-doc \
--enable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--enable-avdevice \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=android \
--arch=arm \
--cpu=armv7-a \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
}
build_android
赋予脚本执行权限:chmod +x build.sh
执行脚本:./build.sh
编译:make -j4
(-j<改成你的CPU核心数量>)
生成动态链接库:make install
如果一切顺利就可以在源码更目录下的android/armv7-a下找到我们需要的.so文件了
3.移植到Android App中
1.JNI编译
(1)将编译后的FFmpeg源码目录打包并复制到Windows中,在任意目录下新建jni文件夹,在解压后的 …\FFmpeg-n4.0.1\android\armv7-a\lib目录下有7个.so文件,将所有的.so文件放到 …\jni\prebuilt\目录下;
将 …\FFmpeg-n4.0.1\android\armv7-a\include目录下的所有文件夹复制到 …\jni\目录下;
将 …\FFmpeg-n4.0.1\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_gamepp_ffmpegtest_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_gamepp_ffmpegtest_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_gamepp_ffmpegtest_jni_FFmpegInvoke改为自己工程中的JAVA文件对应的包名和路径
(6)编写.mk文件
编写Android.mk文件并放到 …\jni\目录
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libavutil
LOCAL_SRC_FILES := prebuilt/libavutil.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libswresample
LOCAL_SRC_FILES := prebuilt/libswresample.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libswscale
LOCAL_SRC_FILES := prebuilt/libswscale.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libavcodec
LOCAL_SRC_FILES := prebuilt/libavcodec.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libavformat
LOCAL_SRC_FILES := prebuilt/libavformat.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libavfilter
LOCAL_SRC_FILES := prebuilt/libavfilter.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libavdevice
LOCAL_SRC_FILES := prebuilt/libavdevice.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg-invoke
LOCAL_SRC_FILES :=ffmpeg-invoke.cpp \
cmdutils.c \
ffmpeg_filter.c \
ffmpeg_opt.c \
ffmpeg_hw.c \
ffmpeg.c
LOCAL_C_INCLUDES := D:\libs\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 := armeabi-v7a
APP_PLATFORM=android-21
APP_OPTIM := release
APP_STL := stlport_static
(7)编译
确认所需文件都准备就绪:
打开CMD,进入该目录,执行ndk-build
(确保ndk路径已经配置到环境变量中)
编译成功:
编译成功后可以再jni同级目录下找到libs目录,libs\armeabi-v7a目录下的.so文件就是我们最终需要的动态链接库:
2.新建Android Studio测试工程 FFmpegTest
(1)在FFmpegTest\app\src\main\目录下新建jniLibs目录,将上一步编译生成的 \libs\armeabi-v7a文件夹复制到jniLibs目录下;
在com.gamepp.ffmpegtest.jni路径下新建FFmpegInvoke.java
public class FFmpegInvoke {
static {
System.loadLibrary("avutil");
System.loadLibrary("avcodec");
System.loadLibrary("swresample");
System.loadLibrary("avformat");
System.loadLibrary("swscale");
System.loadLibrary("avfilter");
System.loadLibrary("avdevice");
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_gamepp_ffmpegtest_jni_FFmpegInvoke_test(JNIEnv *env, jclass type)
函数返回的字符串,说明java调用C++函数成功。
(3)测试FFmpeg命令
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
100);
}
TextView tvMessage = findViewById(R.id.tv_message);
tvMessage.setText(FFmpegInvoke.test());
ffmpegTest();
}
private void ffmpegTest() {
new Thread(){
@Override
public void run() {
long startTime = System.currentTimeMillis();
String input = "/sdcard/Movies/Replay_2018.05.08-13.46.mp4";
String output = "/sdcard/Movies/output.mp4";
//剪切视频从00:20-00:28的片段
String cmd = "ffmpeg -d -ss 00:00:20 -t 00:00:08 -i %s -vcodec copy -acodec copy %s";
cmd = String.format(cmd,input,output);
FFmpegInvoke.run(cmd.split(" "));
Log.d("FFmpegTest", "run: 耗时:"+(System.currentTimeMillis()-startTime));
}
}.start();
}
}
执行完毕后再/sdcard/Movies/目录下找到output.mp4,打开播放确实是我们想要的结果,说明FFmpeg的命令成功执行了。