编译Android下可执行命令的FFmpeg
2017-06-07 22:36
1326 查看
请尊重原创,转载请注明出处:http://blog.csdn.net/mabeijianxi/article/details/72904694
本人环境与工具:
ndk:r14FFmpeg版本: 3.2.5
Android Studio: 2.3.2
一、说明:
本文是经过实战总结出的经验,本文将用两种方式编译可以在Android下执行命令的FFmpeg,一种是传统的ndk-build工具,一种是cmake工具,经过我的项目实战,非常推荐cmake,因为AS2.2以后对它支持的非常好,你可以非常方便的像debug Java代码一样去debug Native代码。本文以是在假设已经编译好了全架构的 FFmpeg 的基础上进行的,编译步骤: 编译Android下可用的全平台FFmpeg(包含libx264与libfdk-aac)。
二、传统ndk-build命令编译
所谓传统,必然就稍微没那么智能了,我们一步一步的搞。打开你养家糊口的Android Studio,娴熟的新建一个项目;
编写一个 native 函数,如果只是测试我们在MainActivity里面搞就行了:
public native int ffmpegRun(String[] cmd);
新建jni目录,在目录下新建文件: jx_ffmpeg_cmd_run.c;
编码对应的 JNI 接口:
#include <jni.h><br> JNIEXPORT jint JNICALL Java_com_mabeijianxi_jianxiffmpegcmd_MainActivity_ffmpegRun(JNIEnv *env, jobject instance, jobjectArray cmd) { // TODO }
找到我们FFmpeg编译后的根目录,然后 copy:
cmdutils.c cmdutils.h cmdutils_common_opts.h config.h ffmpeg.c ffmpeg.h ffmpeg_filter.c ffmpeg_opt.c(注意需要编译后才会有config.h)到 jni 目录下,再进入到我们的编译后的产物目录,把include文件夹与所有的 .so 动态库也
copy 到jni目录下。完成后你jni目录结构应该如下图:
文件修改
修改ffmpeg.c与ffmpeg.h
找到ffmpeg.c,把int main(int argc, char argv) 改名为 int jxRun(int argc, char argv)
找到ffmpeg.h, 在文件末尾添加函数申明: int jxRun(int argc, char **argv);
1)修改cmdutils.c 和 cmdutils.h
找到cmdutils.c中的exit_program函数
修改前:
修改后:
2)找到cmdutils.h中exit_program的申明,也把返回类型修改为int。
修改前:
修改后:
很多教程都只修改到这里,基本没什么问题,但是你实际运行的时候会发现如果连续多次执行命令会有问题的,通过源码我们可以知道,FFmpeg每次执行完命令后会调用 ffmpeg_cleanup 函数清理内存,并且会调用exit(0)结束当前进程,但是经过我们的修改,exit()的代码已经被删掉,我们在Android中自然不能结束当前进程了,所以有些变量的值还在内存中,这样就会导致下次执行的时候可能会出错。我也尝试过fork一个进程给ffmpeg执行,完事后通过 信号来进程间通信,这样管用但是很麻烦,我们其实只需要简单的重设一些变量即可。
打开ffmpeg.c找到刚修改的jxRun函数,然后在 return 前加上如下代码即可:
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;
编写调用函数
我们上面只在jx_ffmpeg_cmd_run.c新建了一个JNI接口函数,还没有实现逻辑,我们实现后的代码如下:
** * Created by jianxi on 2017/6/4. * https://github.com/mabeijianxi * mabeijianxi@gmail.com * #include "ffmpeg.h" #include <jni.h> JNIEXPORT jint JNICALL Java_com_mabeijianxi_jianxiffmpegcmd_MainActivity_ffmpegRun(JNIEnv *env, jobject type,jobjectArray commands){ int argc = *env)->GetArrayLength(env,commands); char *argv[argc]; int i; for (i = 0; i < argc; i++) { jstring js = (jstring) (*env)->GetObjectArrayElement(env,commands, i); argv[i] = (char *) (*env)->GetStringUTFChars(env,js, 0); } return jxRun(argc,argv); }
编写Application.mk与Android.mk
1)在jni目录下新建Application.mk与Android.mk
2)编写Application.mk,内容如下:
APP_ABI := armeabi-v7a APP_PLATFORM := android-14
3)编码Android.mk,其内容如下(不明含义的可看Android下玩JNI的新老三种姿势
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libavcodec LOCAL_SRC_FILES := libavcodec.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavfilter LOCAL_SRC_FILES := libavfilter.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavformat LOCAL_SRC_FILES := libavformat.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavutil LOCAL_SRC_FILES := libavutil.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libswresample LOCAL_SRC_FILES := libswresample.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libswscale LOCAL_SRC_FILES := libswscale.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := jxffmpegrun LOCAL_SRC_FILES := cmdutils.c ffmpeg.c ffmpeg_filter.c ffmpeg_opt.c jx_ffmpeg_cmd_run.c # 这里的地址改成自己的 FFmpeg 源码目录 LOCAL_C_INCLUDES :=/Users/jianxi/Downloads/code/ffmpeg-3.2.5 LOCAL_LDLIBS := -llog -lz -ldl LOCAL_SHARED_LIBRARIES :=libavcodec libavfilter libavformat libavutil libswresample libswscale include $(BUILD_SHARED_LIBRARY)
9. 开始编译:
我们打开终端,然后cd 到jni目录下,然后执行ndk-build,如果你没配置其环境变量,你选择写入ndk-build的全路径。
如果顺利你可以在命令结束后看到如下输出:
并且你的lib下会有如下产物:
这里就先不上测试的效果图了,等后面一起搞
三、cMake编译
有好多步骤都是很上面一样的,一会儿我就快乐的copy下来哈打开你养家糊口的Android Studio,版本最好大于2.2,很关键。娴熟的新建一个项目,但是与以往不同,你最好勾选上 C++ 支持与 C++ standard选项时选择 C++ 11,如下图:
新建完成后你会发现帮你生成好了接口,这时我们只需修改native函数即可:
修改前:
public native String stringFromJNI();
修改后
public native int ffmpegRun(String[] cmd);
修改native文件与函数:
你会发现这时多了一个cpp的文件夹,里面多了一个native-lib.cpp的文件,我们修改其名为jx_ffmpeg_cmd_run.c,然后修改里面的函数,修改后的函数应该如下:
#include <jni.h> JNIEXPORT jint JNICALL Java_com_mabeijianxi_jianxiffmpegcmd_MainActivity_ffmpegRun(JNIEnv *env, jobject instance, jobjectArray cmd) { // TODO }
找到我们FFmpeg编译后的根目录,然后 copy:
cmdutils.c cmdutils.h cmdutils_common_opts.h config.h ffmpeg.c ffmpeg.h ffmpeg_filter.c ffmpeg_opt.c(注意需要编译后才会有config.h)到 cpp 目录下,再进入到我们的编译后的产物目录,把include文件夹 copy 到cpp目录下。完成后你cpp目录结构应该如下图:
文件修改
修改ffmpeg.c与ffmpeg.h
找到ffmpeg.c,把int main(int argc, char argv) 改名为 int jxRun(int argc, char argv)
找到ffmpeg.h, 在文件末尾添加函数申明: int jxRun(int argc, char **argv);
1)修改cmdutils.c 和 cmdutils.h
找到cmdutils.c中的exit_program函数
修改前:
修改后:
2)找到cmdutils.h中exit_program的申明,也把返回类型修改为int。
修改前:
修改后:
很多教程都只修改到这里,基本没什么问题,但是你实际运行的时候会发现如果连续多次执行命令会有问题的,通过源码我们可以知道,FFmpeg每次执行完命令后会调用 ffmpeg_cleanup 函数清理内存,并且会调用exit(0)结束当前进程,但是经过我们的修改,exit()的代码已经被删掉,我们在Android中自然不能结束当前进程了,所以有些变量的值还在内存中,这样就会导致下次执行的时候可能会出错。我也尝试过fork一个进程给ffmpeg执行,完事后通过 信号来进程间通信,这样管用但是很麻烦,我们其实只需要简单的重设一些变量即可。
打开ffmpeg.c找到刚修改的jxRun函数,然后在 return 前加上如下代码即可:
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;
编写调用函数
我们上面只在jx_ffmpeg_cmd_run.c新建了一个JNI接口函数,还没有实现逻辑,我们实现后的代码如下:
\** \* Created by jianxi on 2017/6/4. \* https://github.com/mabeijianxi \* mabeijianxi@gmail.com */ #include "ffmpeg.h" #include <jni.h> JNIEXPORT jint JNICALL Java_com_mabeijianxi_jianxiffmpegcmd_MainActivity_ffmpegRun(JNIEnv *env, jobject type,jobjectArray commands){ int argc = (*env)->GetArrayLength(env,commands); char *argv[argc]; int i; for (i = 0; i < argc; i++) { jstring js = (jstring) (*env)->GetObjectArrayElement(env,commands, i); argv[i] = (char *) (*env)->GetStringUTFChars(env,js, 0); } return jxRun(argc,argv); }
编写cmake编译脚本:
在没有编写脚本的时候你的代码应该是一片红,没关系,马上我们就干掉它。
打开你当前Module下的CMakeLists.txt然后填写如下脚本(内容解释可看Android下玩JNI的新老三种姿势):
# 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. jxffmpegrun # 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/jx_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") include_directories( /Users/jianxi/Downloads/code/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. jxffmpegrun fdk-aac avcodec avfilter avformat avutil swresample swscale # Links the target library to the log library # included in the NDK. ${log-lib} )
当然你还需要修改脚本里面的一些文件路径。
8 . 修改当前Module下的build.gradle脚本:
我所谓的全架构其实是排除了 mips 的,觉得没什么卵用,所以我们还需要添加过滤:
修改前:
修改后:
做到这步我们就可以直接点击同步按钮了:
完成后就不会再爆红了,这时候是只能直接运行安装的,但是还没有输入命令,所以没什么意义,接下来将开始使用我们做好的工具。
四、测试与使用
我们要用动态库肯定得放入默认目录,或者在gradle.build中修改其路径,这里我就直接放入jniLibs 里面了,如图:
然后我就是在java里面调用了,搞了一个进度条,一个按钮。没有什么技术含量,直接贴代码了哈:
public class MainActivity extends AppCompatActivity { static { System.loadLibrary("jxffmpegrun"); System.loadLibrary("avcodec"); System.loadLibrary("avformat"); System.loadLibrary("avutil"); System.loadLibrary("swscale"); System.loadLibrary("fdk-aac"); } private ProgressBar pb; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); pb = (ProgressBar) findViewById(R.id.pb); } public void onClick(View v){ pb.setVisibility(View.VISIBLE); new Thread(new Runnable() { @Override public void run() { String basePath = Environment.getExternalStorageDirectory().getPath(); String cmd_transcoding = String.format("ffmpeg -i %s -c:v libx264 %s -c:a libfdk_aac %s", basePath+"/"+"girl.mp4", "-crf 40", basePath+"/"+"my_girl.mp4"); int i = jxFFmpegCMDRun(cmd_transcoding); new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { pb.setVisibility(View.GONE); Toast.makeText(MainActivity.this,"ok了",Toast.LENGTH_SHORT).show(); } }); } }).start(); } public int jxFFmpegCMDRun(String cmd){ String regulation="[ \\t]+"; final String[] split = cmd.split(regulation); return ffmpegRun(split); } public native int ffmpegRun(String[] cmd); }
再上面代码中我们指定编码器压缩了一个mp4的文件,压缩前叫girl.mp4,压缩后叫my_girl.mp4,如图所示,我们发现其缩小了近80%。
五、总结
这里输入路径不能每次是同一个,不然会报错,也就是说,运行了一次上面的命令后,输出的名字不能再叫my_girl.mp4了,你可以叫your_girl.mp4。然后还是那句话推荐用cMake,你看我们通过cMake编译可以直接debugnative
是不是很hi,然后我们在开发中可能会发现有些命令执行不了,这时候你首先需要检查命令,然后确保没问题后,你需要确定你在编译FFmpeg的时候是否开启了此功能,很关键,确定办法很简单,一是检查FFmpeg的编译脚本,二是通过FFmpeg的函数获取编译信息,我工程里面已经内含这个jni接口,有兴趣的基友可以下载来try一try。如果有兴趣进一步探索FFmpeg在Android上的实战运用,可以看我的开源项目和其讲解博客,分别是:https://github.com/mabeijianxi/small-video-record、利用FFmpeg玩转Android视频录制与压缩(一)、利用FFmpeg玩转Android视频录制与压缩(二)。
本文所有源码地址:https://github.com/mabeijianxi/FFmpeg4Android
相关文章推荐
- 编译Android下可执行命令的FFmpeg
- 编译Android下可执行命令的FFmpeg
- ffmpeg 编译好的库文件和可执行程序----for android
- Android编译系统详解(二)——命令执行流程
- 利用源码编译Android系统的APK和可执行命令的方法
- 在android中如何编译连接 .c 的可执行文件 Android点滴(1) -- 执行Linux命令并且获得输出的结果
- FFMPEG Android(2) 编译编译静态ffmpeg可执行文件
- 使用NDK交叉编译ffmpeg, android后台可执行。
- Android编译系统详解(二)——命令执行流程
- android 执行ffmpeg命令
- 利用源码编译Android系统可执行命令
- 编译完Android后,执行emulator提示“emulator:未找到命令”
- FFmpeg For Android (三) 移植main函数到安卓上执行ffmpeg命令
- 关于Cygwin中编译FFmpeg时执行sh文件出现arm-linux-androideabi-gcc is unable to create an executable file.
- Android编译系统详解(二)——命令执行流程
- linux中使用NDK编译ffmpeg的android平台的可执行文件
- android模块编译,mm命令, 模块编译
- Android 编译命令及选项
- android 编译命令
- 编译busybox1.16的时候,执行make命令,报错,错误信息:437 “mixed implicit and normal rules”,翻译为中文:混合的和隐含普通规则