此文已由做者叶海啸受权网易云社区发布。
html
欢迎访问网易云社区,了解更多网易技术产品运营经验。java
FFmpeg是一个开源免费跨平台的视频和音频流方案,能够快速对音视频流进行多方面的处理,本文主要介绍FFmpeg经常使用的命令与参数讲解,如何在JAVA中使用FFmpeg以及遇到的一些问题。linux
项目需求中涉及到有关于视频、音频的一系列处理,包含视频中音频提取、视频首帧提取、音频重采样、字幕压缩的功能,一直在研究ffmpeg,仅仅几个功能,却深受ffmpeg的折磨。git
今天谈谈ffmpeg在java中的简单使用,首先下载ffmpeg包,官方地址:http://ffmpeg.org/download.html, 这里建议下载Linux Static Builds版本的,轻小并且解压后能够直接使用,本人使用的版本是ffmpeg-git-20170922-64bit-static.tar.xz。web
解压以后,文件夹中有一个可执行文件ffmpeg,在linux上能够直接运行./ffmpeg -version,能够查看ffmpeg的版本信息,以及configuration配置信息。安全
视频中音频提取:ffmpeg -i [videofile] -vn -acodec copy [targetaudiofile]网络
视频首帧提取:ffmpeg -i [videofile] -vframes 1 -q:v 2 -f image2 [imagefile]jvm
音频重采样:ffmpeg -i [audiofile] -ar [samplingrate] [targetaudiofile]ide
字幕压缩:ffmpeg -i [videofile] -vf subtitles=[subtitle.srt] [targetvideofile]工具
audiofile、videofile是音视频源文件,能够是本地文件,也能够是网络文件URL;
提取音频流时,-vn 忽略视频流 -acodec 设定声音编解码器,未设定时则使用与输入流相同的编解码器,若是须要提取视频流,则参数变为-an -vcodec;
-vframes 表示提取的第几帧,获取第一桢则后面的值为1,若是后面的值大于1,那么最后的[imagefile]不能指定一个文件,否则会报错,以下
指定了输出的文件名为“1.jpeg”,报错:不能从1.jpeg文件中获取第二帧的文件名,由于-vframes只要大于1,则会提取出每一帧的图片,建议使用如%03d.jpeg来做为文件名,那么它解析的结果即是001.jpeg,002.jpeg,...依次编号日后;
-q:v 2 q表明质量quality, v表明视频流,2是控制质量的参数;-f指定输出的格式是image2;
除了使用-vframes来获取视频帧,还有使用-ss参数来获取,-ss后的时间参数是自行设定,而且在视频的有效时间内(格式为00:00:00),使用-ss时,若是没有使用%03d.jpeg来做为文件名,则获取的是-ss参数指定的那个时间的帧;
-ar表示使用新的采样率,经常使用的有8,000Hz、16,000Hz、22,050Hz、32,000Hz、44,100Hz;
subtitle.srt是字幕文件(中文字幕即把英文变为中文,其它格式一致),这边就使用最简单的srt标准格式,srt文件写入的字符编码须要是UTF-8,不然压缩的时候会报没法读取srt文件;
若想压缩中文字幕,须要系统中有中文字体,使用fc-list查询系统支持的字体,fc-list :lang=zh查询支持的中文字体
通常须要调用系统命令时,大部分人第一反应确定是使用Runtime.getRuntime().exec(command)返回一个process对象,再调用process.waitFor()来等待命令执行结束,获取执行结果。
产品刚上线,运行很稳定,可是没过多久,产品同窗说从某个时间点开始添加的视频都不出来了!由于这个视频必需要通过一系列的处理,才会展现出来,因此中途某个环节出错了。
首先查看了日志,没有发现任何的报错,可是幸亏开发的时候加了debug日志,每一句命令exec先后都会打一句log,因而看下是否“开始执行”和“执行成功”两句log都打印了,结果发如今截取首帧的时候,只打印了“开始执行”,一直没有结束,那么猜想进程堵塞了。
可是,我把产品同窗的视频拿过来,直接执行提取视频第一帧的命令,提示图片未提取成功,后来发现该视频是产品同窗经过某个压缩工具压缩过的,点开视频能够看见黑屏,看不到任何东西,确定是压缩时把视频压缩出错了,可是截取首帧命令既然执行结束了,按道理不该该一直堵塞啊?
因而经过dump下了内存镜像文件,命令jmap -dump:live,format=b,file=heap.dmp PID,经过jvisualvm工具查看,发现有不少以下的堆栈:
所以能够判断,确实是在截取首帧的时候,进程阻塞了,可是为何会阻塞???
查看waitFor()源码能够发现,其实调用的是Object类中的,wait()方法,而且未指定等待时间,那么若是一直不返回,则会一直阻塞。
而且查看了JDK的帮助文档,以下
所以,能够得出结论:若是外部程序不断在向标准输出流(对于jvm来讲就是输入流)和标准错误流写数据,而JVM不读取的话,当缓冲区满以后将没法继续写入数据,最终形成阻塞在waitFor()这里。
解决方法:在waitFor()以前,利用单独两个线程,分别处理process的getInputStream()和getErrorSteam(),防止缓冲区被撑满,致使阻塞;
/** * 处理process输出流和错误流,防止进程阻塞 * 在process.waitFor();前调用 * @param process */ private static void dealStream(Process process) { if (process == null) { return; } // 处理InputStream的线程 new Thread() { @Override public void run() { BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = null; try { while ((line = in.readLine()) != null) { logger.info("output: " + line); } } catch (IOException e) { e.printStackTrace(); } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } }.start(); // 处理ErrorStream的线程 new Thread() { @Override public void run() { BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream())); String line = null; try { while ((line = err.readLine()) != null) { logger.info("err: " + line); } } catch (IOException e) { e.printStackTrace(); } finally { try { err.close(); } catch (IOException e) { e.printStackTrace(); } } } }.start(); }
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 Kubernetes 在网易云中的落地优化实践
【推荐】 寓教于乐——玩转角色互换游戏