0%

记ffmpeg不同环境的问题

工作中的思考(20210603)

问题

遇到一个匪夷所思的问题,使用ffmpeg命令对音视频、图片等进行处理时,本地可以通过接口调用执行cmd返回一段视频,打包后发布到ECS和K8S多台机器后,在sit环境居然直接挂了。

思考

遂在日志中排查问题,将通过埋点获得的命令在k8s上执行,居然也成功了,从过程上看确实是由于这条命令该生成的文件没有生成,但服务器终端手动执行又执行成功,一模一样的命令通过终端执行和通过Java的exec执行会有区别吗?

按道理来说不会有区别,因为Java本身是一次编译到处执行,并不会受到跨平台的影响,本地也是直接路由到docker镜像上。

还有一种可能是线程池的问题,利用两个线程去跑两个异步任务,有没有可能这个线程被不明原因阻塞,而程序主线程继续执行,导致我们命令执行成功,但文件未写入到服务器中。

也有可能是waitFor方法失效,不过可能性不大。

相同的命令我们在几个环境测试,居然会有不同的结果。

1
2
3
4
5
6
7
8
9
ffmpeg -nostdin -r 25 -y -i 1.jpg -vf "zoompan='1.25':x='if(lte(on,-1),(iw-
iw/zoom)/2,x+20)':y='if(lte(on,1),(ih-ih/zoom)/2,y)':d=150" -c:v libx264 -t 2 -pix_fmt yuv420p 1.mp4
ffmpeg -nostdin -r 25 -y -i 2.jpg -vf "zoompan='1.25':x='if(lte(on,1),(iw/zoom)/2,x-20)':y='if(lte(on,1),(ih-ih/zoom)/2,y)':d=150" -c:v libx264 -t 2 -pix_fmt yuv420p 2.mp4
ffmpeg -nostdin -r 25 -y -i 3.jpg -vf "zoompan='1.5':x='if(lte(on,1),(iw-iw/zoom)/2,x)':y='if(lte(on,-1),(ih-ih/zoom)/2,y+15)':d=150" -c:v libx264 -t 2 -pix_fmt yuv420p 3.mp4
ffmpeg -nostdin -r 25 -y -i 4.jpg -vf "zoompan='1.5':x='if(lte(on,1),(iw-iw/zoom)/2,x)':y='if(lte(on,1),(ih/zoom)/2,y-15)':d=150" -c:v libx264 -t 2 -pix_fmt yuv420p 4.mp4

ffmpeg -r 25 -y -i 1.jpg -vf "scale=-2:10*ih,zoompan=z='min(zoom+0.035,2)':d=600:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)',scale=-2:720" -c:v libx264 -t 1 -pix_fmt yuv420p 5.mp4

ffmpeg -r 25 -y -i 3.jpg -vf scale=-2:10*ih,zoompan=z='if(eq(ld(1),0),zoom+1-0.035*st(1,1),max(zoom-0.035,1))':d=400:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)',scale=-2:720 -c:v libx264 -t 1 -pix_fmt yuv420p 6.mp4
环境 结果
本地ffmpeg执行 成功
本地Unbuntu20.04执行 成功
本地启动项目,通过调接口执行 成功
项目部署到k8s后,在服务器端centos的终端执行 成功
项目部署到k8s后,通过接口调用 失败

解决

今天通过增加日志的分析参数,发现在执行视频放缩时没有找到对应的过滤器,而ffmpeg中ld指令是获取我们在st中的值,查阅官方文档发现我们并没有缺少libavuitl这个库。

image-20210604204518926

而后我发现在执行**eq(ld(1),0)在中间多加一个空格eq(ld(1), 0)**,想到之前调试到Java的exec源码,顺便分析一下。

首先随便写个main函数,开始单步调试。

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
String cmd = "java -version";
Process cmdProcess = null;
try {
cmdProcess = runtime.exec(cmd);
cmdProcess.waitFor();
cmdProcess.destroy();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}

}

执行进来以后发现首先声明了一个StringTokenizer,然后通过空格拆分,但是考虑到本地Java执行成功,还要继续跟进源码,进入下一个exec函数。

image-20210604210400742

exec函数执行了start函数,可以得到如下图所示:

image-20210604210919304

这段源码写的很简练,也很精髓,利用Java的万类之王Object,直接克隆对象直接修改,效率很高,接下来就是判断命令是否为空,是否存在权限,是否有不合法字符。

image-20210604211219884

现在还没有发现问题所在,继续向下走。

image-20210604211419822

最后,我注意到使用了一个枚举,源码注释是”Represents a source of subprocess input or a destination of subprocess output”,明白我们的exec函数其实是创建了一个进程(Process),这个进程会处理我们的命令,通过返回两个数据流(正常流和ERROR流),将流进行合并返回结果,这里就不写了。

看样子应该是Java创建linux的进程(准确说应该是task_struct)和shell执行有一定出入,也有可能是ffmpeg的bug。

后来在ffmpeg社区中发现有人遇到这个问题,在linux环境下服务器端过滤器不能加引号,整条命令不能有空格,仅限打包到服务器后,其他没有任何问题。

结论

学习一些开源软件,可以去社区看看结果,撰写命令行不该像写程序一样加空格。