java项目中需要引入ffmpeg,主要用于处理视频转码和视频加水印。
ffmpeg是一个主流的音视频处理库,C语言编写。官网地址:http://ffmpeg.org/ 也有中文版地址。
网上关于ffmpeg java引入的帖子大多是先有ffmpeg可执行工具,然后再java模拟os命令执行。
而我们项目会在客户方的服务器上部署,当时看到这类帖子第一个想法是,客户方服务器是集群的,总不能让客户方每台服务器都装一个ffmpeg工具,那太麻烦客户了。然后就走进了另一个思维定式的坑。。。
原谅我是android刚转后端开发不久,当时就想着不如用android交叉编译出想要的jni中间层,然后放到java工程下用就好了,然后就各种尝试。有的帖子还是写得不错的,在此贴出来供需要android交叉编译的人参考。
以下将描述mac环境下android的ffmpeg动态库开发:
假设已具备android ndk基础,能看懂并改写简单c代码的基础。
step 1:mac环境编译ffmpeg的so库,推荐选择mac或linux操作系统做这一步,因为windows下做要解决的问题比较多。
ffmpeg这里有个牛人,英年早逝的雷霄骅csdn博客,但因为停更好几年了用的库都比较老,拿来做实战教程不太合适,但作为ffmpeg学习栈还是相当好的。从这位牛人的生平也能看出,年(cheng)轻(xu)人(yuan)还是不要太拼,还是要爱惜自己的生命。。。
ffmpeg编译趟过的坑大部分都在这篇里:http://blog.sina.com.cn/s/blog_61bc01360102w815.html。坑还是相当多的,谁编谁知道。
参考好多编译脚本,这个作者写的最好最全面:https://github.com/coopsrc/FFPlayerDemo/blob/master/build_ffmpeg.sh。支持
armeabi、armeabi-v7a、arm64-v8a、x86、x86_64等市面上能见着的所有android cpu,且连一个demo作者都在持续更新,看着感觉像是企业级在使用的,相当靠谱了。
当时就是想着我们服务器也是x86_64位的,那既然不会写makefile也不会用ide编动态库,不如借助android ndk工具打一个x86_64吧。cry~,事后咨询了一个linux行家,才知道,linux主要指令用的libc.so,而android定制了用的bionic.so,所以走了好大一个弯。
不如我把我自己的android_build.sh也分享一下吧,骗骗csdn积分。
其实就在build_ffmpeg.sh基础上改写了一下。
step 2:编写android下的jni中间层,坑了我好几天的一个步骤。
大体教程可以参考这个作者写的几篇:https://juejin.im/post/5caeb0a76fb9a0688a67fef9。2019年的,新鲜好用。
用的demo几乎是这个作者的demo基础上改的:https://blog.csdn.net/u014418171/article/details/53337759
这里面的坑也是巨多。
坑1:step1做完后会在ffmpeg编译目录下自动生成一个config.h,这个是编译过程产生的宏定义,每个人的编译需求都不同,需要把自己的文件替换这个demo的 config.h。不然会报各种error:user of undeclared identifer '***'变量找不到。
例如我用上述android_build.sh编译生成的config.h大概会长如下样子
#ifndef FFMPEG_CONFIG_H
#define FFMPEG_CONFIG_H
#define FFMPEG_CONFIGURATION "--prefix=android-build --libdir=android-build/libs/x86_64 --incdir=android-build/includes/x86_64 --pkgconfigdir=android-build/pkgconfig/x86_64 --arch=x86_64 --cpu=x86_64 --cross-prefix=/Users/zqyr/Downloads/android-ndk-r13b/toolchains/x86_64-4.9/prebuilt/darwin-x86_64/bin/x86_64-linux-android- --sysroot=/Users/zqyr/Downloads/android-ndk-r13b/platforms/android-21/arch-x86_64/ --extra-ldexeflags=-pie --target-os=linux --disable-static --enable-shared --enable-small --disable-programs --disable-ffmpeg --disable-ffplay --disable-ffprobe --disable-doc --disable-symver --disable-asm --enable-decoder=vorbis --enable-decoder=opus --enable-decoder=flac"
#define FFMPEG_LICENSE "LGPL version 2.1 or later"
#define CONFIG_THIS_YEAR 2019
#define FFMPEG_DATADIR "android-build/share/ffmpeg"
#define AVCONV_DATADIR "android-build/share/ffmpeg"
#define CC_IDENT "gcc 4.9.x (GCC) 20150123 (prerelease)"
#define av_restrict restrict
#define EXTERN_PREFIX ""
#define EXTERN_ASM
#define BUILDSUF ""
#define SLIBSUF ".so"
#define HAVE_MMX2 HAVE_MMXEXT
#define SWS_MAX_FILTER_SIZE 256
#define ARCH_AARCH64 0
#define ARCH_ALPHA 0...
坑2:如果按照这个demohttps://blog.csdn.net/u014418171/article/details/53337759,其实工程里的include头文件不全,没啥用,还不如用官网下的ffmpeg原文件include。android.mk如下
LOCAL_C_INCLUDES := /Users/***/***/ffmpeg-4.0.4
c语言的include和java的import概念差别挺大的,但是只要加对了系统及本地库引用路径,相对地址和绝对地址还是一样一样滴。
这里也是困扰我比较久的地方。
坑3:按照这个demohttps://blog.csdn.net/u014418171/article/details/53337759教程,需要把你当时用的官网ffmpeg版本下的几个tools工具.h.c都copy过来,然后ffmpeg-4.0.4新增了ffmpeg_hw.c,android.mk ffmpeg中间层ffmpeg_hw.c也要一起打包进来才行,不然会报error:undefined reference to 'hw_device_setup_for_decode'等各种hw相关的错误,主要ffmpeg.c有调用ffmpeg_hw.c的东西。
总之这一步的坑着实很多,当我成功编译出android中间层的时候有一种想哭的冲动。。。没有好的建议,总之就踩出问题然后根据error打地鼠一般逐步解决吧。
说了这么多,然而这篇帖子主要不是想表达android下的ffmpeg开发,而是想表达java项目下的ffmpeg引入。
所以以上写的东西对java项目ffmeg的开发没有任何帮助。。。
最终我采取了在x86_64 linux虚拟机上编译ffmpeg可执行包,编译选项为:
./configure --prefix=/usr/local/ffmpeg_text --disable-yasm --enable-small --enable-libfreetype --disable-ffplay --disable-ffprobe --disable-doc --disable-symver --disable-asm
找到bin下面的可执行文件ffmpeg,打出来也不大,十几M的样子。
然后把可执行包打包进java项目,server init时放在服务器某处,然后再java模拟os命令执行。
public static void main(String args[]) {
String command = "ffmpeg -i /home/parallels/Documents/vedio.mp4 -vf \"drawtext=fontfile=/home/parallels/Documents/FreeMono.ttf: text=‘2019-08-11’:x=100:y=10:fontsize=24:fontcolor=black:shadowy=2\" -b:v 3000k /home/parallels/Documents/vedio_text.mp4";
System.out.println("command = " + command);
try {
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(new String[] { "sh", "-c", command });
InputStream stderr = proc.getErrorStream(); // proc.getInputStream();
// //proc.getErrorStream();
InputStreamReader isr = new InputStreamReader(stderr);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null)
System.out.println(line);
int exitVal = proc.waitFor();
System.out.println("Process exitValue: " + exitVal);
} catch (Throwable t) {
t.printStackTrace();
}
}
然后就搞定了。。。
迂回了一大圈最后又绕回了原点,其实我是要被自己蠢哭的。