编译Android上可用的FFmpeg并测试。
编译环境:
Ubuntu 16.04.1 64位 (虚拟机)
android-ndk-r9d
开发环境:
Window 10 64位
android-ndk-r9d
AndroidStudio 2.2.3
详细步骤
以下步骤在Ubuntu环境中执行
配置NDK环境变量
- 下载并解压ndk包(本次测试ndk版本是android-ndk-r9d)
- 在终端执行 gedit ~/.bashrc
- 在文件末尾写入如下内容并保存(注意替换自己的路径)
ANDROID_NDK = “/home/ygl/ndk/android-ndk-r9d”
export ANDROID_NDK - 可以通过在终端输入 ndk-build 验证是否配置正确
- 如需配置sdk环境,步骤与以上类似
编译最新FFmpeg源码
- 在[FFmpeg官网](“http://ffmpeg.org/download.html“)下载最新3.3.2压缩文件
- 将下载的文件拷贝至合适的文件夹
- 在该目录下执行tar -jxvf ffmpeg-3.3.2.tar.bz2(请注意自己的版本和压缩格式)
修改 ./configure 文件,该文件影响编译后so文件的命名,默认在Android上出现问题
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)'
新建可执行文件,在根目录下新建 build_android.sh,并写入一下内容
export TMPDIR=/Users/hubin/Desktop/ffmpeg-3.3.2/ffmpegtemp #这句很重要,不然会报错 unable to create temporary file in # NDK的路径,根据自己的安装位置进行设置 NDK=~/Applications/android-sdk/ndk-bundle # 编译针对的平台,可以根据自己的需求进行设置 # 这里选择最低支持android-14, arm架构,生成的so库是放在 # libs/armeabi文件夹下的,若针对x86架构,要选择arch-x86 PLATFORM=$NDK/platforms/android-14/arch-arm # 工具链的路径,根据编译的平台不同而不同 # arm-linux-androideabi-4.9与上面设置的PLATFORM对应,4.9为工具的版本号, # 根据自己安装的NDK版本来确定,一般使用最新的版本 TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 function build_one { ./configure \ --prefix=$PREFIX \ --target-os=linux \ --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \ --arch=arm \ --sysroot=$PLATFORM \ --extra-cflags="-I$PLATFORM/usr/include" \ --cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc \ --nm=$TOOLCHAIN/bin/arm-linux-androideabi-nm \ --enable-shared \ --enable-runtime-cpudetect \ --enable-gpl \ --enable-small \ --enable-cross-compile \ --disable-debug \ --disable-static \ --disable-doc \ --disable-asm \ --disable-ffmpeg \ --disable-ffplay \ --disable-ffprobe \ --disable-ffserver \ --disable-postproc \ --disable-avdevice \ --disable-symver \ --disable-stripping \ $ADDITIONAL_CONFIGURE_FLAG sed -i '' 's/HAVE_LRINT 0/HAVE_LRINT 1/g' config.h sed -i '' 's/HAVE_LRINTF 0/HAVE_LRINTF 1/g' config.h sed -i '' 's/HAVE_ROUND 0/HAVE_ROUND 1/g' config.h sed -i '' 's/HAVE_ROUNDF 0/HAVE_ROUNDF 1/g' config.h sed -i '' 's/HAVE_TRUNC 0/HAVE_TRUNC 1/g' config.h sed -i '' 's/HAVE_TRUNCF 0/HAVE_TRUNCF 1/g' config.h sed -i '' 's/HAVE_CBRT 0/HAVE_CBRT 1/g' config.h sed -i '' 's/HAVE_RINT 0/HAVE_RINT 1/g' config.h make clean make -j4 make install } # arm v7vfp CPU=armv7-a OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU " PREFIX=./android/$CPU-vfp ADDITIONAL_CONFIGURE_FLAG= build_one
- 执行 chmod 777 build_android.sh, 添加执行权限
- 执行* ./build_android.sh * 完成编译
- 编译成功后再 android 文件夹下会有 include文件夹和编译好的so文件
以下步骤在开发环境中进行
JNI实现FFmpeg方法的调用
- 在MainActivity中新建本地方法(位置随意)
//输出FFmpeg信息作为测试
public native String avformatinfo();
//执行FFmpeg 命令
public native int ffmpegcore(String[] argv);
- 在Terminal中执行 javah com.ygl.ffmpegtest.MainActivity (javah 完整包名)
- 将生成的 xxx.h文件移至 jni目录下
新建ffmpegdemo.c文件,并将ffmpeg部分源码文件和编译好的so文件也拷贝至该目录,项目最终目录如下:
需要拷贝的源文件有:cmdutils.c cmdutils.h cmdutils_common_opts.h ffmpeg.c ffmpeg.h ffmpeg_filter.c ffmpeg_opt.c修改 ffmpeg.c 和 ffmpeg.h
在ffmpeg.c中,把 void main(int argc,char argv) 改成 int main(int argc,char argv)
在ffmpeg.h 末尾添加函数申明 int run(int argc,char **argv)int run(int argc,char **argv);
- 修改 cmdutils.c 和 cmdutils.h
cmdutils.c中删除 exit_program 的函数体,直接返回参数,并修改函数的返回类型
cmdutils.h 中修改 exit_program 的申明将返回值类型修改为 int
int exit_program(int ret){
return ret;
}
jni目录下新建Android.mk文件
LOCAL_PATH := $(call my-dir) # FFmpeg library include $(CLEAR_VARS) LOCAL_MODULE := avformat LOCAL_SRC_FILES := libavformat-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avcodec LOCAL_SRC_FILES := libavcodec-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avfilter LOCAL_SRC_FILES := libavfilter-6.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avdevice LOCAL_SRC_FILES := libavdevice-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avutil LOCAL_SRC_FILES := libavutil-55.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := swresample LOCAL_SRC_FILES := libswresample-2.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := swscale LOCAL_SRC_FILES := libswscale-4.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := postproc LOCAL_SRC_FILES := libpostproc-54.so include $(PREBUILT_SHARED_LIBRARY) # Program include $(CLEAR_VARS) LOCAL_MODULE := ffmpegdemo LOCAL_SRC_FILES := ffmpegdemo.c ffmpeg.c ffmpeg_opt.c cmdutils.c ffmpeg_filter.c #源文件地址,替换成自己的地址 LOCAL_C_INCLUDES := F:/ffmpeg-3.3.2 LOCAL_LDLIBS := -llog -lz LOCAL_SHARED_LIBRARIES := avfilter avformat avdevice avcodec avutil swresample swscale postproc include $(BUILD_SHARED_LIBRARY)
- jni下新建Application.mk(可选)
APP_ABI := armeabi-v7a
APP_MODULES := libffmpegdemo
编辑 ffmpegdemo.c 文件
// // Created by Ygl on 2017/6/7. // #include <stdio.h> #include "com_ygl_ffmpegtest_MainActivity.h" #include "include/libavformat/avformat.h" #include "include/libavcodec/avcodec.h" #include "include/libavutil/avutil.h" #include "include/libavfilter/avfilter.h" #include <ffmpeg.h> //Log #ifdef ANDROID #include <jni.h> #include <android/log.h> #define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, "(>_<)", format, ##__VA_ARGS__) #else #define LOGE(format, ...) printf("(>_<) " format "\n", ##__VA_ARGS__) #endif /** * com.ihubin.ffmpegstudy.MainActivity.avformatinfo() * AVFormat Support Information */ int ffmpegmian(int argc, char **argv); JNIEXPORT jstring JNICALL Java_com_ygl_ffmpegtest_MainActivity_avformatinfo (JNIEnv *env, jobject obj){ char info[40000] = { 0 }; av_register_all(); AVInputFormat *if_temp = av_iformat_next(NULL); AVOutputFormat *of_temp = av_oformat_next(NULL); //Input while(if_temp!=NULL){ sprintf(info, "%s[In ][%10s]\n", info, if_temp->name); if_temp=if_temp->next; } //Output while (of_temp != NULL){ sprintf(info, "%s[Out][%10s]\n", info, of_temp->name); of_temp = of_temp->next; } //LOGE("%s", info); return (*env)->NewStringUTF(env, info); } JNIEXPORT jint JNICALL Java_com_ygl_ffmpegtest_MainActivity_ffmpegcore (JNIEnv *env, jobject obj, 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 run(argc, argv); }
- 在 jni 目录下,执行 ndk-build 命令,便可以生成需要so文件
编辑我们的 MainActivity
public class MainActivity extends AppCompatActivity { static { System.loadLibrary("avcodec-57"); System.loadLibrary("avdevice-57"); System.loadLibrary("avfilter-6"); System.loadLibrary("avformat-57"); System.loadLibrary("avutil-55"); System.loadLibrary("postproc-54"); System.loadLibrary("swresample-2"); System.loadLibrary("swscale-4"); System.loadLibrary("ffmpegdemo"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void onClick(View view){ String base = Environment.getExternalStorageDirectory().getPath(); StringBuilder cmdLine = new StringBuilder(); cmdLine.append("ffmpeg -i ").append(base).append("/input.mp4 ").append(base).append("/out.avi"); Log.v("ygl", cmdLine.toString()); final String[] commands = cmdLine.toString().split(" "); new Thread(){ @Override public void run() { super.run(); int result = ffmpegcore(commands); Log.v("ygl","result = "+result); } }.start(); } //输出FFmpeg信息作为测试 public native String avformatinfo(); //执行FFmpeg 命令 public native int ffmpegcore(String[] argv); }
- 可以调用 ffmpegcore 该本地方法来执行我们的FFmpeg命令了
实践中可能出现的问题
- 尽量保证编译和开发环境的ndk版本一致
在编译FFmpeg的时候提示错误”config.mak no such file or directory”
先执行 ./configure 再执行 build_android.sh
如果不熟悉JNI,一定要先照着网上写一些例子,熟悉一下过程。
- 运行的时候如果忘记加适当的权限或者命令有错都可能导致崩溃哦
写在最后
感谢雷霄骅前辈的专栏博客 http://blog.csdn.net/leixiaohua1020/article/details/47008825/
还有那些无私的开源贡献者,很赞!