ffmpeg开发之旅(5):详解ffmpeg编译与在Android平台上的移植

ffmpeg开发之旅(5):详解ffmpeg编译与在Android平台上的移植

(原文链接:http://blog.csdn.net/andrexpert/article/details/73823740)

一、ffmpeg在linux环境下的编译

1. 编译环境

(1) VirtualBox:VirtualBox_5.1.22.15126.exe
(2) Ubuntu:ubuntu-14.04.5-desktop-amd64.iso
(3) NDK:android-ndk-r14b-linux-x86_64.zip
(4) ffmpeg:ffmpeg-3.3.2.tar.bz2
      为了提高ffmpeg编译速度,这里选择在Linux环境下对其进行编译。VirtualBox安装Ubuntu比较简单,可自行查找相关资料,只是在为虚拟系统分配磁盘空间时建议大于20GB,因为NDK体积还是比较大的,默认的8GB根本不够用。其次,NDK的版本一定要与Ubuntu版本一致,我这里选择的是64位的,为什么这里要强调下,因为就是这个版本不一致问题,让我在configure ffmpeg时整整花了两天时间去找bug,只怪太相信自己的记忆力了。最后,解压NDK和ffmpeg到同一目录下即可,我的解压路径是/home/jiangdongguo/ffmpeg。
2. 配置ffmpeg
(1) 设置NDK路径
      为了方便配置configure命令的相关参数,这里我们使用export命令将NDK存储路径设置为全局的,另外,我们还需要设置一个临时目录,以便存储ffmpeg编译时产生的临时数据,当然,这里需要保证该目录已经存在且可读写。注:也可以将这两行命令放到脚本文件中。
[javascript] view plain copy
  1. [email protected]:~$ export NDK=/home/jiangdongguo/ffmpeg/android-ndk-r14b  
  2. [email protected]:~$ export TMPDIR=/home/jiangdongguo/ffmpeg/ffmpegTmpDir  
(2) 配置ffmpeg
a) 创建执行configure命令脚本文件
[html] view plain copy
  1. [email protected]:/home/jiangdongguo/ffmpeg/ffmpeg-3.3.2# vim configure_arm.sh  
     在创建配置ffmpeg的脚本文件时,有三个地方需要根据自身情况更改:SYSTEMROOT,用于指定ndk platform的路径,一定要选择比你的目标机器使用的版本低,比如你的手机是Android 6.0,那么需要选择android-23以下;TOOLCHAIN_PREFIX,指定编译ffmpeg编译工具链所在路径;PREFIX,用于指定编译完成后so文件输出目录,会自动在改路径目录下生成Android使用所需的include和lib目录。
ffmpeg开发之旅(5):详解ffmpeg编译与在Android平台上的移植
     其中,--target-os选项指定目标系统类型、--arch选项指定目标系统架构、--enbale-shared、-enable-static指定只生成so共享库,--enable-cross-compile开启使用指定交叉编译工具等等。至于其他配置选项,可自行在ffmpeg源码目录下执行"configure --help"命令查看。
[html] view plain copy
  1. // 修改ffmpeg源码下configure文件  
  2. [email protected]:/home/jiangdongguo/ffmpeg/ffmpeg-3.3.2# vim configure  
    对ffmpeg源码下的configure文件进行编辑,找到如下代码进行修改。修改如下几行代码的目的是为了得到Android平台能够识别的so库,比如libavcodec-57.so、libavdevice-57.so等,而不是libavcodec.so.57、libavcodec.so.57.89.100等。具体修改情况如下:
[html] view plain copy
  1. SLIBNAME_WITH_VERSION='$(SLIBNAME).$(LIBVERSION)'     
  2. SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'         
  3. LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'  
  4. SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'  
  5. SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'  
  6. 将上面的内容修改如下:  
  7. SLIBNAME_WITH_VERSION='$(SLIBNAME).$(LIBVERSION)'  
  8. SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'  
  9. LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'  
  10. SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'  
  11. SLIB_INSTALL_LINKS='$(SLIBNAME)'  

[html] view plain copy
  1. // 赋予configure_arm.sh执行权限  
  2. [email protected]:/home/jiangdongguo/ffmpeg/ffmpeg-3.3.2# chmod u+x configure_arm.sh  
  3. // 执行脚本文件  
  4. [email protected]:/home/jiangdongguo/ffmpeg/ffmpeg-3.3.2# ./configure_arm.sh  
注:如果提示.../arm-linux-androideabi-pkg-config not found, library detection may fail.警告,忽视即可,编译时目前没有发现有什么影响。
3. 编译ffmpeg
[html] view plain copy
  1. [email protected]:/home/jiangdongguo/ffmpeg/ffmpeg-3.3.2#make clean  
  2. [email protected]:/home/jiangdongguo/ffmpeg/ffmpeg-3.3.2#make   
  3. [email protected]:/home/jiangdongguo/ffmpeg/ffmpeg-3.3.2#make install  
    编译前需要"make clean"清理下,然后"make"大概需要5-10分钟,待编译完毕后执行后再执行"make install",就会在之前创建的android/arm目录下自动生成include和lib目录,其分别存放了Android使用ffmpeg所需的头文件和so共享库。
ffmpeg开发之旅(5):详解ffmpeg编译与在Android平台上的移植

二、ffmpeg移植与在Android平台上的使用

1. 创建Android NDK工程
ffmpeg开发之旅(5):详解ffmpeg编译与在Android平台上的移植
讲解一下:
    有关eclipse中开发NDK/JNI,我这篇文章已经讲解得比较清楚了,可自行前往按步骤搭建即可。这里提一下与ffmpeg有关的相关文件,通过上面讲解可以知道,linux环境下编译好ffmpeg后,会自动在../android/arm目录下生成include和lib目录,我们将分别整个include目录、lib目录so库文件(链接文件和pkgconfig除外)拷贝到Android工程的jni目录下,另外,还需要将ffmpeg源码根目录下的ffmpeg.h、config.h和cmdutils.h拷贝到jni目录,否则调用ffmpeg相关函数会报错。
2. VideoFixUtils.class:Java层创建native方法
[cpp] view plain copy
  1. /** 处理视频native方法工具类 
  2.  *  
  3.  * @author Created by jianddongguo on 2017年6月26日下午11:14:27 
  4.  * @blogs http://blog.csdn.net/andrexpert 
  5.  */  
  6. public class VideoFixUtils {  
  7.       
  8.     /** 获得指定视频的角度 
  9.      * @param videoPath 视频路径 
  10.      * @return 拍摄角度值 
  11.      */  
  12.     public native static int getVideoAngle(String videoPath);  
  13.       
  14.     static {  
  15.         // 加载自定义动态库  
  16.         System.loadLibrary("FFMPEG4Android");  
  17.         // 加载ffmpeg相关动态库  
  18.         System.loadLibrary("avcodec-57");  
  19.         System.loadLibrary("avdevice-57");  
  20.         System.loadLibrary("avfilter-6");  
  21.         System.loadLibrary("avformat-57");  
  22.         System.loadLibrary("avutil-55");  
  23.         System.loadLibrary("swscale-4");  
  24.         System.loadLibrary("swresample-2");  
  25.     }     
  26. }  
讲解一下:
    通过上面代码可知,处理在static静态代码块中加载自定义的FFMPEG4Android动态库,还需加载与ffmpeg相关的所有动态库,至于需要加载哪些,可以到Android工程中的libs/armeabi目录下查看,动态库的名称通过去掉lib前缀可得。
3. FFMPEG4Android.c:C/C++层实现native函数原型
[cpp] view plain copy
  1. /** 处理视频c实现 
  2.  *  
  3.  * @author Created by jianddongguo on 2017年6月26日下午11:14:27 
  4.  * @blogs http://blog.csdn.net/andrexpert 
  5.  */  
  6. #include <jni.h>  
  7. #include <stdlib.h>  
  8. #include "com_jiangdg_ffmepg4android_VideoFixUtils.h"  
  9. #include "ffmpeg.h"  
  10.   
  11.   
  12. JNIEXPORT jint JNICALL Java_com_jiangdg_ffmepg4androidk_VideoFixUtils_getVideoAngle  
  13.   (JNIEnv *env, jclass jcls, jstring j_videoPath){  
  14.     const char *c_videoPath = (*env)->GetStringUTFChars(env,j_videoPath,NULL);  
  15.     //1. 注册所有组件  
  16.     av_register_all();  
  17.     //2. 打开视频、获取视频信息,  
  18.     //  其中,fmtCtx为封装格式上下文  
  19.     AVFormatContext *fmtCtx = avformat_alloc_context();  
  20.     avformat_open_input(&fmtCtx,c_videoPath,NULL,NULL);  
  21.     //3. 获取视频流的索引位置  
  22.     int i;  
  23.     int v_stream_idx = -1;  
  24.     for(i=0 ; i<fmtCtx->nb_streams ; i++){  
  25.         if(fmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){  
  26.             v_stream_idx = i;  
  27.             break;  
  28.         }  
  29.     }  
  30.     // 4. 获取旋转角度,元数据  
  31.     AVDictionaryEntry *tag = NULL;  
  32.     tag = av_dict_get(fmtCtx->streams[v_stream_idx]->metadata,"rotate",tag,NULL);  
  33.     int angle = -1;  
  34.     if(tag != NULL){  
  35.         // 将char *强制转换为into类型  
  36.         angle = atoi(tag->value);  
  37.     }  
  38.     // 5.释放封装格式上下文  
  39.     avformat_free_context(fmtCtx);  
  40.     (*env)->ReleaseStringUTFChars(env,j_videoPath,c_videoPath);  
  41.     return angle;  
  42. }  
讲解一下:
    由于本篇文章重点在于讲解如何在linux系统环境下编译so共享库,并将其移植到Android平台上使用,这里就不详细讲解ffmpeg实现代码,稍微讲下这里面的相关原理:我们知道mp4是一种视频封装格式,它可能包含三个轨,即音频、视频、字幕,每个轨对应一个AVStream,如果要想知道mp4文件的拍摄角度,就需要对mp4格式进行解封装,然后抽离出视频轨,进而得到所需角度值。
4. 配置Android.mk
[cpp] view plain copy
  1. LOCAL_PATH := $(call my-dir)  
  2. #ffmpeg prebuilt lib  
  3. include $(CLEAR_VARS)  
  4. LOCAL_MODULE    := avcodec_prebuilt  
  5. LOCAL_SRC_FILES := libavcodec-57.so  
  6. include $(PREBUILT_SHARED_LIBRARY)  
  7.   
  8.   
  9. include $(CLEAR_VARS)  
  10. LOCAL_MODULE    := avdevice_prebuilt  
  11. LOCAL_SRC_FILES := libavdevice-57.so  
  12. include $(PREBUILT_SHARED_LIBRARY)  
  13.   
  14.   
  15. include $(CLEAR_VARS)  
  16. LOCAL_MODULE    := avfilter_prebuilt  
  17. LOCAL_SRC_FILES := libavfilter-6.so  
  18. include $(PREBUILT_SHARED_LIBRARY)  
  19.   
  20.   
  21. include $(CLEAR_VARS)  
  22. LOCAL_MODULE    := avformat_prebuilt  
  23. LOCAL_SRC_FILES := libavformat-57.so  
  24. include $(PREBUILT_SHARED_LIBRARY)  
  25.   
  26.   
  27. include $(CLEAR_VARS)  
  28. LOCAL_MODULE    := avutil_prebuilt  
  29. LOCAL_SRC_FILES := libavutil-55.so  
  30. include $(PREBUILT_SHARED_LIBRARY)  
  31.   
  32.   
  33. include $(CLEAR_VARS)  
  34. LOCAL_MODULE    := swresample_prebuilt  
  35. LOCAL_SRC_FILES := libswresample-2.so  
  36. include $(PREBUILT_SHARED_LIBRARY)  
  37.   
  38.   
  39. include $(CLEAR_VARS)  
  40. LOCAL_MODULE    := swscale_prebuilt  
  41. LOCAL_SRC_FILES := libswscale-4.so  
  42. include $(PREBUILT_SHARED_LIBRARY)  
  43.   
  44.   
  45. #myapp lib  
  46. include $(CLEAR_VARS)  
  47. LOCAL_MODULE    := FFMPEG4Android  
  48. LOCAL_SRC_FILES := FFMPEG4Android.c  
  49. LOCAL_C_INCLUDES +=$(LOCAL_PATH)/include  
  50. LOCAL_LDLIBS := -llog  
  51. LOCAL_SHARED_LIBRARIES := avcodec_prebuilt avdevice_prebuilt avfilter_prebuilt avformat_prebuilt avutil_prebuilt swresample_prebuilt swscale_prebuilt  
  52. include $(BUILD_SHARED_LIBRARY)  
讲解一下:
     Android.mk是用来描述要编译某个具体的模块,所需要的一些资源,包括要编译的源码、要链接的库等等。对于ffmpeg相关库来说,LOCAL_MODULE变量用于指定该预编译库名称,可以任意,但是下面的LOCAL_SHARED_LIBRARIES要与指定的一致;LOCAL_SRC_FILES指定ffmpeg相关预编译so库所在路径,我这里存放在jni目录下。另外,还需要使用LOCAL_C_INCLUDES 变量指定ffmpeg相关头文件所在目录,$(LOCAL_PATH)为当前jni目录路径。
5. 配置Application.mk
[cpp] view plain copy
  1. #指定so支持的平台  
  2. APP_ABI := armeabi  
讲解一下:
    相对于Android.mk,Application.mk是用来描述你的应用程序需要哪些模块,以及这些模块所要具有的一些特性。由于我们在编译ffmpeg时,只编译了arm架构的so,因此,在Android工程的Application.mk文件中需要使用APP_ABI变量指定只生成armeiabi架构机器码,如果这里不处理,在ndk-build时会报错。
6.  执行ndk-build命令,生成so共享库
ffmpeg开发之旅(5):详解ffmpeg编译与在Android平台上的移植
7. 调用native方法,查看运行结果
[java] view plain copy
  1. public class MainActivity extends Activity {  
  2.     private TextView mTvDegreeInfo;  
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.         mTvDegreeInfo = (TextView) findViewById(R.id.tv_video_degree);  
  8.     }  
  9.   
  10.   
  11.     public void onRotateClick(View v) {  
  12.         String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath();  
  13.         File file = new File(rootPath + File.separator + "20170627_145524.mp4");  
  14.         if (!file.exists()) {  
  15.             return;  
  16.         }  
  17.         mTvDegreeInfo.setText(  
  18.                 "读取到20170627_145524.mp4的旋转角度:\n rotate = "   
  19.         + VideoFixUtils.getVideoAngle(file.getAbsolutePath()) + "度");  
  20.     }  
使用mediaInfo软件查看视频信息,对比旋转角度:

ffmpeg开发之旅(5):详解ffmpeg编译与在Android平台上的移植          ffmpeg开发之旅(5):详解ffmpeg编译与在Android平台上的移植


Demo下载:详解ffmpeg编译与在Android平台上的移植