1 项目介绍

1.1 项目介绍

FFMpeg是做音视频开发的同学都会接触的一个开源项目,现将其移植到Android上,写一个简单的视频格式转码工具,作为自己Android jni开发的一个入门学习和Android 开发的练习。为了简化开发,项目中使用命令行的方式调用ffmpeg而不是直接用ffmpeg提供的函数进行本地开发。

除了视频转换格式外,项目还设计了视频GIF截取,视频压缩等等功能,这些都是使用ffmpeg很容易实现的功能。

1.2 开发环境说明

  • Ubuntu 18.04
  • Android Studio 3.2
  • jdk1.8.0_191
  • Android NDK, Revision 15c (July 2017)
  • FFmpeg 4.0.3 “Wu”

如果你按照本篇的步骤来进行FFMpeg的移植,请确保你的开发环境使用的版本和上述的一致。

1.3 特别感谢

特别感谢几位博主的分享,
1.最简单的基于FFmpeg的移动端例子:Android HelloWorld
2.编译FFmpeg4.0.1并移植到Android app中使用(最详细的FFmpeg-Android编译教程)
3.Android NDK开发(四) 将FFmpeg移植到Android平台
4.Cross Compiling FFMpeg 4-0 for Android

本篇中主要的配置修改均参考他们的博客,在此表示感谢。

2 JNI,NDK,ABI,.so,CMakeLists.txt

在开始之前有必要了解几个概念,
1.Android:JNI 与 NDK到底是什么?(含实例教学),
2.Android的.so文件、ABI和CPU的关系
3.关于Android的.so文件你所需要知道的
4.CMakeLists.txt 语法介绍与实例演练
几位博主都写得很清晰,需要的同学请移步去看看。

3 选择合适的NDK

从这里下载NDK
最新版本的是r19,不过在本项目中却不适用,本项目使用的是Android NDK, Revision 15c (July 2017),如果你使用的是其他版本,则在下一步编译FFMpeg和后面集成过程中,有大概率会遇到各种问题。

需要注意的是,Android Studio 3.2 中创建支持C++项目会默认下载并使用最新版本的NDK,而不是我们需要的版本。一个做法是替换(你可以在 第五步 创建项目,导入库文件 时再来替换)/home/your-user-name/Sdk/ndk-bundle(这里是默认的Sdk文件所在路径,如果你在初次安装Android Studio时有指定路径,那么ndk-bundle则在那个路径下)中的文件为我们下载的ndk中的文件。

比如/home/your-user-name/Downloads/android-ndk-r15c-linux-x86_64/是你下载的ndk的解压后的路径,你需要,把/home/your-user-name/Downloads/android-ndk-r15c-linux-x86_64/android-ndk-r15c 内的文件覆盖到/home/your-user-name/Sdk/ndk-bundle。

4 FFMpeg源码编译

4.1 下载和必要条件

在这里下载FFMpeg源码,注意版本的选择,本项目使用的 FFmpeg 4.0.3 “Wu” 这个版本,如果使用其他版本,则不保证你按照我分享的步骤来做而不出错。

关于下载编译源码所需的注意的点,参考我之前的分享
Linux 下ffmpeg的安装

下载好FFMpeg的源码和编译所需的工具后,进入下一步。

4.2 修改configure

解压你下载的ffmpeg源码,进入ffmpeg所在目录,找到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)'

4.3 准备编译脚本

假设/home/your-user-name/Downloads/ffmpeg-4.0 是你下载的ffmpeg源码解压后的路径,切换到这个目录下,创建一个名为build.sh的文件(文件名随意)

$ cd  /home/your-user-name/Downloads/ffmpeg-4.0
$ vim build.sh

build.sh的内容为

#!/bin/bash
NDK=/home/your-user-name/Android/Sdk/ndk-bundle
SYSROOT=$NDK/platforms/android-19/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
function build_one
{
./configure \
--prefix=$PREFIX \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffplay \
--disable-ffprobe \
--disable-doc \
--disable-symver \
--enable-protocol=concat \
--enable-protocol=file \
--enable-muxer=mp4 \
--enable-demuxer=mpegts \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
#make clean all
#make -j4
#make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one

注意替换 NDK=/home/your-user-name/Android/Sdk/ndk-bundle 为你自己的ndk所在路径,这里只编译了arm架构的,也可以编译x86等架构的cpu使用的库,需要修改function build_one中的架构参数。

#make clean all
#make -j4
#make install

build.sh中注释了上面的三句话,而是在执行了bulid.sh后再在命令行中一步步手动执行这三句,当然也可以去掉注释直接在build.sh中执行。

使用

$ sudo chmod +x build.sh
$ ./build.sh

来执行build.sh这个脚本(chmod +x 用来赋予可执行权限)

如果你没有在build.sh中执行下面的命令的话,再

$ make clean all
$ make -j4
$ make install

如果一切顺利的话,在/home/your-user-name/Downloads/ffmpeg-4.0/android/arm/ 目录下有

  • bin
  • include
  • lib
  • share

其中include中的头文件和lib中的库文件,是下一步所需的。

5 创建项目,导入文件

5.1 创建支持C++ 的项目,导入库文件,头文件

5.1.1 创建支持C++ 的项目


图1:新建Android 项目

如图1,新建一个Android项目,注意勾选“Include C++ support”,然后一路点next即可,最后完成即可。

5.1.2 导入库文件

然后在app/src/main 下创建jniLibs/armeabi-v7a,把4.3 中lib目录下的.so文件拷到 armeabi-v7a,鉴于主流手机CPU架构都是armeabi-v7a的,所以这里只提供了armeabi-v7a的库。如果要支持其他架构的,在4.3的编译脚本中修改CPU架构参数后,把生成的lib文件放到jniLibs下的对应的目录中。比如要支持x86的(如Android模拟器),则在jniLibs下创建x86目录,并把生成的.so文件放进去。

5.1.3 导入头文件

然后把4.3 中的到的include复制到app/src/main/cpp下

5.2 修改FFMpeg源码

实现命令行使用FFMpeg的大概的思路是,利用从FFMpeg源码中“搬运”的部分代码,编译成一个可供Android调用本地动态库,通过jni调用来实现我们的目的。

从你的FFMpeg源码的/fftools/目录下,复制如下的几个文件(比如/home/your-user-name/Downloads/ffmpeg-4.0/fftools/)

  • cmdutils.c
  • cmdutils.h
  • ffmpeg.c
  • ffmpeg.h
  • ffmpeg_filter.c
  • ffmpeg_opt.c

5.2.1 修改cmdutils

修改cmdutils.h中的

void show_help_children(const AVClass *class, int flags);

void show_help_children(const AVClass *clazz, int flags);

修改cmdutils.c中的

void exit_program(int ret)
{if (program_exit)program_exit(ret);exit(ret);
}

void exit_program(int ret)
{//if (program_exit)//    program_exit(ret);//exit(ret);
}

即注释掉退出函数的内容

5.2.2 修改ffmpeg.c

修改ffmpeg.c的入口函数int main(int argc, char **argv)
int run(int argc, char **argv)
(也可以随意,取一个其他名字,只要不是main就行)
并在ffmpeg.h添加这个函数的申明

int run(int argc, char **argv);

并注释掉ffmpeg.c末尾的

exit_program(received_nb_signals ? 255 : main_return_code);

找到函数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;

如果你需要输出ffmpeg执行的日志,在ffmpeg.c中添加下面的函数

static void my_av_log_callback(void *ptr, int level, const char *fmt, va_list vl) {FILE *fp = fopen("/storage/emulated/0/Android/data/com.your_package_name.demo/files/log/ffmpegDemolog.txt","a+");if (fp) {vfprintf(fp,fmt,vl);fflush(fp);fclose(fp);}
}

然后在你修改后的

int run(int argc, char **argv);

中调用

 av_log_set_callback(my_av_log_callback);

在ffmpeg命令执行后,ffmpeg的日志会输出到my_av_log_callback函数中指定的文件

/storage/emulated/0/Android/data/com.your_package_name.demo/files/log/ffmpegDemolog.txt

5.3 编写ffmpeg_cmd.c

直接复制cpp目录下,Android Studio生成的native-lib.cpp,重命名为ffmpeg_cmd.c(名称随意),native-lib.cpp的内容如下

#include <jni.h>
#include <string>extern "C" JNIEXPORT jstring JNICALL
Java_com_yourusername_ffmpeg_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}

观察其中的函数名的格式,类型名的格式,修改后得到

#include <jni.h>
#include "ffmpeg.h"
JNIEXPORT jint
JNICALL
Java_com_yourusername_ffmpeg_MainActivity_runFFMpegCMD(JNIEnv *env, jclass 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);
}

native方法的名称为runFFMpegCMD,java代码调用这个方法时,传入需要执行的命令(如ffmpeg -v)的string数组,在这个方法中,再由run(修改的ffmpeg.c的入口函数)函数执行这个命令。

5.4 导入C代码

把5.2,5.3中的C文件复制到cpp目录下,最后得到的项目目录结构为

图2 项目目录结构

6 修改CmakeLists.txt,build项目

# 设置Cmake版本
cmake_minimum_required(VERSION 3.4.1)# 设置cpp目录路径
set(CPP_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp)# 设置jniLibs目录路径
set(LIBS_DIR ${CMAKE_SOURCE_DIR}/src/main/jniLibs)# 设置CPU目录 armeabi
if(${ANDROID_ABI} STREQUAL "armeabi")
set(CPU_DIR armeabi)
endif(${ANDROID_ABI} STREQUAL "armeabi")# armeabi-v7a
if(${ANDROID_ABI} STREQUAL "armeabi-v7a")
set(CPU_DIR armeabi-v7a)
endif(${ANDROID_ABI} STREQUAL "armeabi-v7a")# arm64-v8a
if(${ANDROID_ABI} STREQUAL "arm64-v8a")
set(CPU_DIR arm64-v8a)
endif(${ANDROID_ABI} STREQUAL "arm64-v8a")# x86
if(${ANDROID_ABI} STREQUAL "x86")
set(CPU_DIR x86)
endif(${ANDROID_ABI} STREQUAL "x86")# x86_64
if(${ANDROID_ABI} STREQUAL "x86_64")
set(CPU_DIR x86_64)
endif(${ANDROID_ABI} STREQUAL "x86_64")# 添加库
add_library( # 库名称ffmpeg# 动态库,生成so文件SHARED# 源码${CPP_DIR}/cmdutils.c${CPP_DIR}/ffmpeg.c${CPP_DIR}/ffmpeg_filter.c${CPP_DIR}/ffmpeg_opt.c${CPP_DIR}/ffmpeg_cmd.c )# 用于各种类型声音、图像编解码
add_library( # 库名称avcodec# 动态库,生成so文件SHARED# 表示该库是引用的不是生成的IMPORTED )# 引用库文件
set_target_properties( # 库名称avcodec# 库的路径PROPERTIES IMPORTED_LOCATION${LIBS_DIR}/${CPU_DIR}/libavcodec.so )# 用于各种音视频封装格式的生成和解析,读取音视频帧等功能
add_library( avformatSHAREDIMPORTED )set_target_properties( avformatPROPERTIES IMPORTED_LOCATION${LIBS_DIR}/${CPU_DIR}/libavformat.so )# 包含一些公共的工具函数
add_library( avutilSHAREDIMPORTED )set_target_properties( avutilPROPERTIES IMPORTED_LOCATION${LIBS_DIR}/${CPU_DIR}/libavutil.so )# 提供了各种音视频过滤器
add_library( avfilterSHAREDIMPORTED )set_target_properties( avfilterPROPERTIES IMPORTED_LOCATION${LIBS_DIR}/${CPU_DIR}/libavfilter.so )# 用于音频重采样,采样格式转换和混合
add_library( swresampleSHAREDIMPORTED )set_target_properties( swresamplePROPERTIES IMPORTED_LOCATION${LIBS_DIR}/${CPU_DIR}/libswresample.so )# 用于视频场景比例缩放、色彩映射转换
add_library( swscaleSHAREDIMPORTED )set_target_properties( swscalePROPERTIES IMPORTED_LOCATION${LIBS_DIR}/${CPU_DIR}/libswscale.so )add_library( avdeviceSHAREDIMPORTED )set_target_properties( avdevicePROPERTIES IMPORTED_LOCATION${LIBS_DIR}/${CPU_DIR}/libavdevice.so )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 )# 引用源码 ../代表上级目录
include_directories( ../../ffmpeg-4.0/${CPP_DIR}/include/ )# 关联库
target_link_libraries( ffmpegavcodecavformatavutilavfilterswresampleswscaleavdevice${log-lib})

其中需要注意的点是

# 引用源码 ../代表上级目录
include_directories( ../../ffmpeg-4.0/${CPP_DIR}/include/ )

将FFMpeg的源码放在你的Android项目的同级目录下,否则build时可能会提示部分头文件缺失。比如你的项目为/home/your-user-name/AndroidStudioProjects/FFMpegAndroid,则FFMpeg源码应该在/home/your-user-name/AndroidStudioProjects/ffmpeg-4.0

修改app的build.gradle中的android闭包

android {compileSdkVersion 28defaultConfig {applicationId "com.example.renkangchen.ffmpegdemo"minSdkVersion 15targetSdkVersion 28versionCode 1versionName "1.0"testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"externalNativeBuild {cmake {cppFlags ""arguments '-DANDROID_ARM_MODE=arm'}}ndk {abiFilters 'armeabi-v7a'}}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}externalNativeBuild {cmake {path "CMakeLists.txt"}}sourceSets {main {jniLibs.srcDirs = ['src/main/jniLibs']}}
}

修改的内容有三处

cmake {cppFlags ""arguments '-DANDROID_ARM_MODE=arm'}
      ndk {abiFilters 'armeabi-v7a'}
     main {jniLibs.srcDirs = ['src/main/jniLibs']}}

对照你自己的build.gradle修改即可,修改好后,点击Build->Rebuild Project,如果没有提示有问题的话,恭喜,但是,大概率会遇到

图3:Build command failed.
错误提示应该都能看明白,“在执行make某某库的过程中出错了”,其中的[1/6]表示第几个出现问题,一个个排查吧,warning和note先不用管,看error。

7 测试

打开MainActivity.java可以观察一下Android Studio自动生成的代码是怎样调用native方法的,照猫画虎即可。

  static {System.loadLibrary("ffmpeg");}

public native int runFFMpegCMD(String[] cmd);

最后调用

final String cmd = "ffmpeg -i " + path + "/test.mp4 -vframes 100 -y -f gif -s 480×320 " + path + "/video_100.gif";
int a = runFFMpegCMD(CMDUtils.splitCmd(cmd));

其中CMDUtils.splitCmd为

   public static String[] splitCmd(String cmd) {String regulation = "[ \\t]+";final String[] split = cmd.split(regulation);return  split;}

具体,可以写个按钮事件,按下按钮就执行截取GIF的命令,

  @Overridepublic void onClick(View v) {final String cmd = "ffmpeg -i " + path + "/test.mp4 -vframes 100 -y -f gif -s 480×320 " + path + "/video_100.gif";new Thread() {@Overridepublic void run() {super.run();int a = runFFMpegCMD(CMDUtils.splitCmd(cmd));}}.start();}

其中的path可以是

 private String path = Environment.getExternalStorageDirectory().getAbsolutePath();

即,在测试的时候,复制一个mp4视频文件到你的手机外存的根目录下,命名为test.mp4,运行截图的命令,如果能得到GIF,说明移植没什么大问题。

8 总结

自己在移植时,各种报错,一大原因是版本问题,ndk的版本,android studio的版本,ffmpeg的版本,如果你参考本篇的步骤,请确保你使用的版本和我的是一致的;另外就是细心问题,移植过程涉及到许多的配置,修改,需要耐心细致;最后是善用搜索,但需要结合自己的实际问题。

如果是刚刚接触NDK,FFMpeg,JNI这方面的内容的话,很难“一次点亮”,多试几次。通过这个移植的过程,也可以基本对Android本地开发的流程,CMakeLists.txt的语法等有个了解。如果有问题,欢迎评论留言。

最后,能力有限,本篇提供的方法仅供参考,望指正海涵。

Reference

1.最简单的基于FFmpeg的移动端例子:Android HelloWorld
2.编译FFmpeg4.0.1并移植到Android app中使用(最详细的FFmpeg-Android编译教程)
3.Android NDK开发(四) 将FFmpeg移植到Android平台
4.Cross Compiling FFMpeg 4-0 for Android
5.Android:JNI 与 NDK到底是什么?(含实例教学),
6.Android的.so文件、ABI和CPU的关系
7.关于Android的.so文件你所需要知道的
8.CMakeLists.txt 语法介绍与实例演练
9.Linux 下ffmpeg的安装
10.下载NDK
11.下载FFMpeg源码

简单的Android视频转码器[1]:把FFMpeg移植到Android相关推荐

  1. 最简单的基于FFmpeg的移动端例子:Android 视频转码器

    ===================================================== 最简单的基于FFmpeg的移动端例子系列文章列表: 最简单的基于FFmpeg的移动端例子:A ...

  2. 最简单的基于FFmpeg的移动端样例:IOS 视频转码器

    ===================================================== 最简单的基于FFmpeg的移动端样例系列文章列表: 最简单的基于FFmpeg的移动端样例:A ...

  3. 最简单的基于FFmpeg的移动端例子:IOS 视频转码器

    ===================================================== 最简单的基于FFmpeg的移动端例子系列文章列表: 最简单的基于FFmpeg的移动端例子:A ...

  4. 音视频转码器产品规格

    音视频转码器产品规格 QQ:16614119 一.            产品简介: 音视频转码器是一套商业级的实时.非实时转码产品.提供常用编码格式之间的转换:支持远程控制转码任务:支持多种输入输出 ...

  5. 软件合码器-驾考-驾驶员考试-音视频合成-四合一-多路视频合成一路技术开发-音视频合码器

    本技术以实际开发实施案例为基础(驾驶员路考系统用的音视频监控合成) 软件合码器-驾考-驾驶员考试-音视频合成-四合一-多路视频合成一路技术开发-音视频合码器 软件效果: 设计流程: 简介 视频合成软件 ...

  6. 亲测好用的视频转码器:HandBrake Mac中文版

    HandBrake for Mac是一款运行在Mac平台上视频解码器,你可以使用handbrake mac中文版将各种类型的DVD快速转换为MPEG,而且支持任何类似的VIDEO_TS文件夹..VOB ...

  7. 超实用的视频转码器:HandBrake for Mac中文版

    HandBrake for Mac是一款运行在Mac平台上视频解码器,你可以使用handbrake mac版将各种类型的DVD快速转换为MPEG,而且支持任何类似的VIDEO_TS文件夹..VOB.. ...

  8. 专业好用的视频转码器:HandBrake for Mac中文版

    HandBrake for Mac是一款运行在Mac平台上视频解码器,你可以使用handbrake mac版将各种类型的DVD快速转换为MPEG,而且支持任何类似的VIDEO_TS文件夹..VOB.. ...

  9. HandBrake for Mac(专业的视频转码器)

    HandBrake for Mac是一款运行在Mac平台上视频解码器,你可以使用handbrake mac版将各种类型的DVD快速转换为MPEG,而且支持任何类似的VIDEO_TS文件夹..VOB.. ...

最新文章

  1. 再谈Spring Boot中的乱码和编码问题
  2. oracle最小精度,【整理+原创】Oracle的计算精度与误差
  3. ConcurrentLInkedQueue队列
  4. ssm(Spring+Spring mvc+mybatis)mybatis配置文件——mybatis-config.xml
  5. elementuiDemo1.1
  6. Hive的安装和使用以及Java操作hive
  7. 用Python统计瓦尔登湖的词频
  8. 20款免费响应式的 HTML5 网站模板下载
  9. PL/SQL基础入门,史上最全的教程
  10. 最新款电影程序源码 影院网站源码 在线采集多资源播放器去广告
  11. HC-05嵌入式蓝牙串口通讯
  12. 关于springboot微信点餐的错题集
  13. 多品种+小批量生产计划方法
  14. IE浏览器设置代理及例外批处理脚本
  15. 【解决方案】智慧水利:EasyNVR+EasyNVS视频监控解决方案
  16. python驱动:ddt用法
  17. Lasso Regression
  18. 解决Pycharm输入法无法切换中英文
  19. Python 实现相同的PPT合并,实现方便打印
  20. 《青春》--塞缪尔·厄尔曼

热门文章

  1. 计算机毕业设计Node.js+Vue基于html的网上购物系统(程序+源码+LW+部署)
  2. 通过js判断字符串是否包含某个字符串
  3. android客服功能介绍,Android 客服工作台 SDK
  4. C#生成单色bmp图片,转为单色bmp图片 任意语言完全用字节拼一张单色图,LCD取模 其它格式图片转为单色图
  5. 【微信公众号】2. 微信公众号申请注册流程
  6. html5数据超出显示省略号,h5文字超出,两行显示,超出显示省略号
  7. 【Win11尝鲜】Win 11 打开输入法自带GIF表情包、颜文字等
  8. 新能源汽车鸿蒙系统,华为鸿蒙车机系统提前曝光:奇瑞新能源 S61 将搭载
  9. 自编码器(Auto-Encoder)
  10. java 三大框架_java的三大框架是什么,功能各是什么