一、技术介绍
在日常工作中,经常会涉及到的对音视频的操作,这就不得不用到ffmpeg。ffmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。其中ffmpeg强大的视频滤镜功能是必不可少的,使用视频滤镜可以实现一系列的视频处理和转换效果,如:画面旋转,翻转,裁剪,缩放,色彩调整等效果;视频滤镜VideoFilter,可以同时设置一个或多个滤镜 , 多个滤镜之间使用逗号隔开,因此又称为"视频滤镜链"。
对于单个视频滤镜,在通过JAVA调用时,可以直接通过Process调用实现。 例如:
String cmd = "ffmpeg -y -i " + inPath + " -vf scale=1920:1080 " + outPut;
Process process = Runtime.getRuntime().exec(cmd);
……
但视频滤镜链按上面的写法,程序是不能正确识别的
ffmpeg指令:
ffmpeg -i input.mov -vf "color=white:size=720x1280:d=1.0:r=30[bg];[bg][0:v]overlay=(W-w)/2:(H-h)/2:format=auto,scale=trunc(iw/2)*2:trunc(ih/2)*2,format=yuv420p" -c:a copy -r 30 -y output.mp4
程序执行期间或报错:
[AVFilterGraph @ 0xcac840] No such filter: 'color=white:size=720x1280:d=1.0:r=30[bg];[bg][0:v]overlay=(W-w)/2:(H-h)/2:format=auto,scale=trunc(iw/2)*2:trunc(ih/2)*2,format=yuv420p'
面对这种问题,两种思路:化整为零,把滤镜链拆分成一个个单视频滤镜,多次调用;化零为整,探索一种整合的新办法。
二、解决问题的思路和方法
化整为零,拆指令很显然不是最佳解决方案;我们采用“化零为整”,把ffmpeg的视频滤镜链写到一个.sh脚本里。
三、实践过程描述
编写ffmpeg.sh并存放在resource/conf目录下
#bin/bash
INPUT_VIDEO="$1"
INPUT_RESOLUTIONS="$2"
INPUT_FPS="$3"
OUTPUT_VIDEO="$4"
ffmpeg -i ${INPUT_VIDEO} -vf "color=white:size=${INPUT_RESOLUTIONS}:d=1.0:r=${INPUT_FPS}[bg];[bg][0:v]overlay=(W-w)/2:(H-h)/2:format=auto,scale=trunc(iw/2)*2:trunc(ih/2)*2,format=yuv420p" -vcodec libx264 -acodec aac -ac 1 -pix_fmt yuv420p -r ${INPUT_FPS} -b:v 5000k -y ${OUTPUT_VIDEO}
通过JAVA调用ffmpeg.sh脚本
List<URL> resources = ClassUtil.getResources("conf/ffmpeg.sh");
if (CollUtil.isEmpty(resources)) {
LOGGER.error("conf/ffmpeg.sh shell脚本不存在!");
throw new Exception("ffmpeg转码视频出现了错误,conf/ffmpeg.sh shell脚本不存在!");
}
String shellPath = resources.get(0).getPath();
// 构建ffmpeg命令
String commandSb = String.format("sh %s %s %s %s %s",shellPath,inPath,resolutionStr,frameRate,outPut);
LOGGER.info("【processStandardFormatVideo】 命令是:{}", commandSb);
// 执行命令
Process process = Runtime.getRuntime().exec(commandSb);
// 处理进程的输出流
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String errorLine;
while ((errorLine = errorReader.readLine()) != null) {
LOGGER.info(errorLine);
}
// 等待进程结束
int exitCode = process.waitFor();
if (exitCode == 0) {
return outPut;
}
四、对实践过程的思考和对效果的评价
充分理解问题的关键卡点,通过报错信息可以得出Process在解析滤镜链时无法识别,但滤镜链直接通过ffmpeg执行是没问题的,那问题就出现在了传参方式上,所以我们应该尝试不同的传参方式,来解决这类问题,而不是把复杂的滤镜链拆成一个个的单滤镜。