Java+Windows+ffmpeg实现视频转换
最近由于项目需要,研究了一下如何用Java实现视频转换,“着实”废了点心思,整理整理,写出给自己备忘下。
思路
由于之前没有没法过相关功能的经验,一开始来真不知道从哪里入手。当然,这个解决,google一下立马就发现了ffmpeg,网上讲解用Java+ffmpeg来进行视频转换的文章也不在少数,我主要参考的这篇文章。
上文提到的这篇文章,基本已经把开发流程什么的讲的很清楚了,这里总结下:
1)核心是利用ffmpeg进行视频转换,我们自己并不写转换视频的代码,只是调用ffmpeg,它会帮我们完成视频的转换。ffmpeg支持的类型有:asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等,这些类型,可以利用ffmpeg进行直接转换。ffmpeg不支持的类型有:wmv9,rm,rmvb等,这些类型需要先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式。
2)了解Java如何调用外部程序,这会是最困难的,也会是坑最多的地方。
3)根据我们的需求设置ffmpeg的参数。(这类文章网上已经有很多了,我也不用复制黏贴了,见这里)
代码
上文中提到的那篇文章中的代码其实已经写的很友好了,基本拿来就能用,不过仍然存在许多问题,接下来会讲到,下面是文中的代码:

1 import java.io.File;
2 import java.util.ArrayList;
3 import java.util.Calendar;
4 import java.util.List;
5
6 public class ConvertVideo {
7
8 private final static String PATH = "c:\\ffmpeg\\input\\c.mp4";
9
10 public static void main(String[] args) {
11 if (!checkfile(PATH)) {
12 System.out.println(PATH + " is not file");
13 return;
14 }
15 if (process()) {
16 System.out.println("ok");
17 }
18 }
19
20 private static boolean process() {
21 int type = checkContentType();
22 boolean status = false;
23 if (type == 0) {
24 System.out.println("直接将文件转为flv文件");
25 status = processFLV(PATH);// 直接将文件转为flv文件
26 } else if (type == 1) {
27 String avifilepath = processAVI(type);
28 if (avifilepath == null)
29 return false;// avi文件没有得到
30 status = processFLV(avifilepath);// 将avi转为flv
31 }
32 return status;
33 }
34
35 private static int checkContentType() {
36 String type = PATH.substring(PATH.lastIndexOf(".") + 1, PATH.length())
37 .toLowerCase();
38 // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
39 if (type.equals("avi")) {
40 return 0;
41 } else if (type.equals("mpg")) {
42 return 0;
43 } else if (type.equals("wmv")) {
44 return 0;
45 } else if (type.equals("3gp")) {
46 return 0;
47 } else if (type.equals("mov")) {
48 return 0;
49 } else if (type.equals("mp4")) {
50 return 0;
51 } else if (type.equals("asf")) {
52 return 0;
53 } else if (type.equals("asx")) {
54 return 0;
55 } else if (type.equals("flv")) {
56 return 0;
57 }
58 // 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),
59 // 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.
60 else if (type.equals("wmv9")) {
61 return 1;
62 } else if (type.equals("rm")) {
63 return 1;
64 } else if (type.equals("rmvb")) {
65 return 1;
66 }
67 return 9;
68 }
69
70 private static boolean checkfile(String path) {
71 File file = new File(path);
72 if (!file.isFile()) {
73 return false;
74 }
75 return true;
76 }
77
78 // 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等), 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.
79 private static String processAVI(int type) {
80 List<String> commend = new ArrayList<String>();
81 commend.add("c:\\ffmpeg\\mencoder");
82 commend.add(PATH);
83 commend.add("-oac");
84 commend.add("lavc");
85 commend.add("-lavcopts");
86 commend.add("acodec=mp3:abitrate=64");
87 commend.add("-ovc");
88 commend.add("xvid");
89 commend.add("-xvidencopts");
90 commend.add("bitrate=600");
91 commend.add("-of");
92 commend.add("avi");
93 commend.add("-o");
94 commend.add("c:\\ffmpeg\\output\\a.avi");
95 try {
96 ProcessBuilder builder = new ProcessBuilder();
97 builder.command(commend);
98 builder.start();
99 return "c:\\ffmpeg\\output\\a.avi";
100 } catch (Exception e) {
101 e.printStackTrace();
102 return null;
103 }
104 }
105
106 // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
107 private static boolean processFLV(String oldfilepath) {
108
109 if (!checkfile(PATH)) {
110 System.out.println(oldfilepath + " is not file");
111 return false;
112 }
113
114 // 文件命名
115 Calendar c = Calendar.getInstance();
116 String savename = String.valueOf(c.getTimeInMillis())+ Math.round(Math.random() * 100000);
117 List<String> commend = new ArrayList<String>();
118 commend.add("c:\\ffmpeg\\ffmpeg");
119 commend.add("-i");
120 commend.add(oldfilepath);
121 commend.add("-ab");
122 commend.add("56");
123 commend.add("-ar");
124 commend.add("22050");
125 commend.add("-qscale");
126 commend.add("8");
127 commend.add("-r");
128 commend.add("15");
129 commend.add("-s");
130 commend.add("600x500");
131 commend.add("c:\\ffmpeg\\output\\a.flv");
132
133 try {
134 Runtime runtime = Runtime.getRuntime();
135 Process proce = null;
136 String cmd = "";
137 String cut = " c:\\ffmpeg\\ffmpeg.exe -i "
138 + oldfilepath
139 + " -y -f image2 -ss 8 -t 0.001 -s 600x500 c:\\ffmpeg\\output\\"
140 + "a.jpg";
141 String cutCmd = cmd + cut;
142 proce = runtime.exec(cutCmd);
143 ProcessBuilder builder = new ProcessBuilder(commend);
144 builder.command(commend);
145 builder.start();
146
147 return true;
148 } catch (Exception e) {
149 e.printStackTrace();
150 return false;
151 }
152 }
153 }

接下来是我自己经过修改后的代码:

1 import java.io.File;
2 import java.io.IOException;
3 import java.util.ArrayList;
4 import java.util.Calendar;
5 import java.util.List;
6 public class ConvertVideo {
7
8 private static String inputPath = "";
9
10 private static String outputPath = "";
11
12 private static String ffmpegPath = "";
13
14 public static void main(String args[]) throws IOException {
15
16 getPath();
17
18 if (!checkfile(inputPath)) {
19 System.out.println(inputPath + " is not file");
20 return;
21 }
22 if (process()) {
23 System.out.println("ok");
24 }
25 }
26
27 private static void getPath() { // 先获取当前项目路径,在获得源文件、目标文件、转换器的路径
28 File diretory = new File("");
29 try {
30 String currPath = diretory.getAbsolutePath();
31 inputPath = currPath + "\\input\\test.wmv";
32 outputPath = currPath + "\\output\\";
33 ffmpegPath = currPath + "\\ffmpeg\\";
34 System.out.println(currPath);
35 }
36 catch (Exception e) {
37 System.out.println("getPath出错");
38 }
39 }
40
41 private static boolean process() {
42 int type = checkContentType();
43 boolean status = false;
44 if (type == 0) {
45 System.out.println("直接转成flv格式");
46 status = processFLV(inputPath);// 直接转成flv格式
47 } else if (type == 1) {
48 String avifilepath = processAVI(type);
49 if (avifilepath == null)
50 return false;// 没有得到avi格式
51 status = processFLV(avifilepath);// 将avi转成flv格式
52 }
53 return status;
54 }
55
56 private static int checkContentType() {
57 String type = inputPath.substring(inputPath.lastIndexOf(".") + 1, inputPath.length())
58 .toLowerCase();
59 // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
60 if (type.equals("avi")) {
61 return 0;
62 } else if (type.equals("mpg")) {
63 return 0;
64 } else if (type.equals("wmv")) {
65 return 0;
66 } else if (type.equals("3gp")) {
67 return 0;
68 } else if (type.equals("mov")) {
69 return 0;
70 } else if (type.equals("mp4")) {
71 return 0;
72 } else if (type.equals("asf")) {
73 return 0;
74 } else if (type.equals("asx")) {
75 return 0;
76 } else if (type.equals("flv")) {
77 return 0;
78 }
79 // 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),
80 // 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.
81 else if (type.equals("wmv9")) {
82 return 1;
83 } else if (type.equals("rm")) {
84 return 1;
85 } else if (type.equals("rmvb")) {
86 return 1;
87 }
88 return 9;
89 }
90
91 private static boolean checkfile(String path) {
92 File file = new File(path);
93 if (!file.isFile()) {
94 return false;
95 }
96 return true;
97 }
98
99 // 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等), 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.
100 private static String processAVI(int type) {
101 List<String> commend = new ArrayList<String>();
102 commend.add(ffmpegPath + "mencoder");
103 commend.add(inputPath);
104 commend.add("-oac");
105 commend.add("lavc");
106 commend.add("-lavcopts");
107 commend.add("acodec=mp3:abitrate=64");
108 commend.add("-ovc");
109 commend.add("xvid");
110 commend.add("-xvidencopts");
111 commend.add("bitrate=600");
112 commend.add("-of");
113 commend.add("avi");
114 commend.add("-o");
115 commend.add(outputPath + "a.avi");
116 try {
117 ProcessBuilder builder = new ProcessBuilder();
118 Process process = builder.command(commend).redirectErrorStream(true).start();
119 new PrintStream(process.getInputStream());
120 new PrintStream(process.getErrorStream());
121 process.waitFor();
122 return outputPath + "a.avi";
123 } catch (Exception e) {
124 e.printStackTrace();
125 return null;
126 }
127 }
128
129 // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
130 private static boolean processFLV(String oldfilepath) {
131
132 if (!checkfile(inputPath)) {
133 System.out.println(oldfilepath + " is not file");
134 return false;
135 }
136
137 List<String> command = new ArrayList<String>();
138 command.add(ffmpegPath + "ffmpeg");
139 command.add("-i");
140 command.add(oldfilepath);
141 command.add("-ab");
142 command.add("56");
143 command.add("-ar");
144 command.add("22050");
145 command.add("-qscale");
146 command.add("8");
147 command.add("-r");
148 command.add("15");
149 command.add("-s");
150 command.add("600x500");
151 command.add(outputPath + "a.flv");
152
153 try {
154
155 // 方案1
156 // Process videoProcess = Runtime.getRuntime().exec(ffmpegPath + "ffmpeg -i " + oldfilepath
157 // + " -ab 56 -ar 22050 -qscale 8 -r 15 -s 600x500 "
158 // + outputPath + "a.flv");
159
160 // 方案2
161 Process videoProcess = new ProcessBuilder(command).redirectErrorStream(true).start();
162
163 new PrintStream(videoProcess.getErrorStream()).start();
164
165 new PrintStream(videoProcess.getInputStream()).start();
166
167 videoProcess.waitFor();
168
169 return true;
170 } catch (Exception e) {
171 e.printStackTrace();
172 return false;
173 }
174 }
175 }
176
177 class PrintStream extends Thread
178 {
179 java.io.InputStream __is = null;
180 public PrintStream(java.io.InputStream is)
181 {
182 __is = is;
183 }
184
185 public void run()
186 {
187 try
188 {
189 while(this != null)
190 {
191 int _ch = __is.read();
192 if(_ch != -1)
193 System.out.print((char)_ch);
194 else break;
195 }
196 }
197 catch (Exception e)
198 {
199 e.printStackTrace();
200 }
201 }
202 }

问题
原文的代码中有一个很大的问题,便是不知道视频转换到底什么时候结束。看原文中的这两处代码:
98行处
1 builder.command(commend);
2 builder.start();
3 return "c:\\ffmpeg\\output\\a.avi";
145行处
1 builder.start();
2
3 return true;
在进程开始之后,直接就返回结果了。要知道,这样的写法,是不会阻塞当前进程的,也就是说,当然程序返回的时候,转码程序(ffmpeg和mencoder)还在执行。如果需要mencoder进行中间转码,那原文中的写法会造成在avi文件还未转换完成时,程序就调用了ffmpeg进行转换。而对于最终的flv文件,我们也无法知道到底是什么时候转换好的,这显然是无法满足我们的业务需求的 。
解决方案
最先想到的办法自然就是阻塞当前进程(主进程),实例代码:
1 Process process = new ProcessBuilder(command).start();
2 process.waitFor();
3 return true;
采用这种的方案运行程序,发现视频转到十几秒的时候就不转了,但是程序还没返回,打开进程管理器一开,ffmpeg进程还在,内存还占着,但是CPU为0,如图:
当时不知道什么原因,在网上查了半天,才明白这是死锁了,但是不知道是什么原因造成的。当时就一直觉得死锁是waitFor()函数造成了,看来用它来判断子进程是否结果是不行了,所以又在网上查了半天其他判断子进程结束的办法(这里其实就已经走弯路了)。有人说可以用exitValue(),于是就有了下面的代码:

1 Process process = new ProcessBuilder(command).start();
2 while (true) {
3 try {
4 if (process.exitValue() == 0)
5 break;
6 }
7 catch (IllegalThreadStateException e) {
8 continue;
9 }
10 }
11 return true;

当子进程没有结束的时候,如果执行exitValue()就会抛出异常,我采用的办法是捕获这个异常然后不去理他,直到程序结束exitValue()返回0为止。但是,还是失败了,出现的情况和用waitFor()方式时的一模一样,我才觉得可能是另外的原因,在去google,发现可能是是由于JVM只提供有限缓存空间,当外部程序(子进程)的输出流超出了这个有限空间而父进程又不读出这些数据,子进程会被阻塞waitFor()永远都不会返回,就会造成死锁。
官方解释:
Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.
知道问题了就要对症下药(其实当时我也不知道这是不是就是我遇到的问题,只能各种打散弹了,打中了算)。关于如何读出子进程的输出流,如何解决这个死锁,网上的办法都大同小异,写的比较好的可以看这个地址。
于是程序被改成这样:

1 Process process = new ProcessBuilder(command).start();
2
3 new PrintStream(process.getInputStream()).start();
4
5 process.waitFor();

PrintStream类如下:

1 class PrintStream extends Thread
2 {
3 java.io.InputStream __is = null;
4 public PrintStream(java.io.InputStream is)
5 {
6 __is = is;
7 }
8
9 public void run()
10 {
11 try
12 {
13 while(this != null)
14 {
15 int _ch = __is.read();
16 if(_ch != -1)
17 System.out.print((char)_ch);
18 else break;
19 }
20 }
21 catch (Exception e)
22 {
23 e.printStackTrace();
24 }
25 }
26 }

运行,发现还是不对,症状和之前的一模一样,我还以为是不是输出流太多了,一个线程读的不够快(好吧,真的很傻很天真,人被逼急了真的什么想法都有),于是我就再开了几个一模一样的线程,结果还是一样。
就在我快要放弃的时候,在百度知道上,看了个无关痛痒的例子,于是做了个小修改,在进程启动之前,重定向了下错误输出流,如下:

1 Process videoProcess = new ProcessBuilder(command).redirectErrorStream(true).start();
2
3 new PrintStream(videoProcess.getInputStream()).start();
4
5 videoProcess.waitFor();
6
7 return true;

然后,然后,然后就可以了,凌乱。。。
结论
其实有两种写法可以解决这个问题,这种事像我上面那样写,还有一种如下:

1 Process videoProcess = new ProcessBuilder(command).start();
2
3 new PrintStream(videoProcess.getErrorStream()).start();
4
5 new PrintStream(videoProcess.getInputStream()).start();
6
7 videoProcess.waitFor();
8
9 return true;

其实道理还是一样的,就是读出ffmpeg的输出流,避免ffmpeg的输出流塞满缓存造成死锁。但是不知道为什么,ffmpeg的输出信息是在错误输出流里面的,我看了下控制台打印结果,发现只是一些当前转换状态的信息,并没有错误,令人费解。
在Process类中,getInputStream用来获取进程的输出流,getOutputStream用来获取进程的输入流,getErrorStream用来获取进程的错误信息流。为了保险起见,在读出的时候,最好把子进程的输出流和错误流都读出来,这样可以保证清空缓存区。
其实,我深刻地感觉到,这些解决的问题的经历是标准的散弹式编程,打到哪算哪,以后引以为戒。
Java+Windows+ffmpeg实现视频转换的更多相关文章
- Java使用FFmpeg处理视频文件指南
Java使用FFmpeg处理视频文件指南 本文主要讲述如何使用Java + FFmpeg实现对视频文件的信息提取.码率压缩.分辨率转换等功能: 之前在网上浏览了一大圈Java使用FFmpeg处理音视频 ...
- Java使用FFmpeg处理视频文件的方法教程
这篇文章主要给大家介绍了关于Java使用FFmpeg处理视频文件的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧 前言 本文主要 ...
- Java也疯狂-分享利用ffmpeg做视频转换的工具
朋友需要经常将视频统一转换为mp4格式,市面上的工具很多,但是转换的体积.自动化程度等都不好,于是花了一个小时给朋友写了个给予ffmpeg的批量转换工具,功能简单但是很实用,也正好给学习Java的同学 ...
- C# 利用ffmpeg 对视频转换系类操作 (1) 基本分析
最近公司做一个项目,开发一个视频站点.项目需求中有很多视频转换的需求,如:格式转换(flv,Mp4),视频水印,视频截图,视频合成,获取视频的基本信息(时间戳,视频大小等).经过网络的收集资料以及自己 ...
- Java调用FFmpeg进行视频处理及Builder设计模式的应用
1.FFmpeg是什么 FFmpeg(https://www.ffmpeg.org)是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.它用来干吗呢?视频采集.视频格式转化.视频 ...
- windows下使用ffmpeg进行视频转换和截图。
author:fanfq(xiaoban) Email:fangqing.fan#gmail.comlink:http://fanfq.iteye.com/admin/blogs/655569chan ...
- java调用FFmpeg及mencoder转换视频为FLV并截图
Conver.java package com.ll19.flv; public class Conver { public void run() { try { // 转换并截图 String fi ...
- java调用ffmpeg获取视频文件信息的一些参数
一.下载ffmpeg http://www.ffmpeg.org/download.html 主要需要bin目录下的ffmpeg可执行文件 二.java代码实现 package com.aw.util ...
- ffmpeg进行视频转换
参考 Mencoder使用方法 Windows下MEncoder下载和Linux下MEncoder的编译 如何降低FFMpeg.exe使用过程中的Cpu使用率 实际上是通过参数控制服务器上用于ffmp ...
随机推荐
- PHP使用数据库的并发问题
在并行系统中并发问题永远不可忽视.尽管PHP语言原生没有提供多线程机制,那并不意味着所有的操作都是线程安全的.尤其是在操作诸如订单.支付等业务系统中,更需要注意操作数据库的并发问题. 接下来我通过一个 ...
- Flex 界面初始化 自定义 预加载 类!
说明: 自定义界面初始化过程提示:初始化...,初始化完毕,加载完毕! ZPreloader.as package com.command { import flash.display.Graphic ...
- java7,java8 中HashMap和ConcurrentHashMap简介
一:Java7 中的HashMap 结构: HashMap 里面是一个数组,然后数组中每个元素是一个单向链表.链表中每个元素称为一个Entry 实例,Entry 包含四个属性:key, value, ...
- django+mysql安装和设置
之前我们已经用sqlite建立了第一个web app.今天来学习如何在django中使用MySQL. 首先需要安装MySQL,到官网下载安装包:https://dev.mysql.com/downlo ...
- Route Between Two Nodes in Graph
Given a directed graph, design an algorithm to find out whether there is a route between two nodes. ...
- 关于 VS 2010 和 VS 2013 的警告 LNK4042
由于我最近调整了一下 Jimi 的文件结构,导致出现了一个 LNK4042 的 warning,我并没有很重视,这个 warning 导致出现了一些错误. 我调试了几个小时,一开始并没有想到是这个 w ...
- centos7执行 wget命令: command not found的两种解决方法
1.rpm 安装 下载wget的RPM包: http://mirrors.163.com/centos/6.8/os/x86_64/Packages/wget-1.12-8.el6.x86_64.rp ...
- Servlet3.0新特性WebFilter(Annotation Filter)详解
摘要: Servlet3.0作为J2EE 6规范一部分,并随J2EE6一起发布,WeFilter是过滤器注解,是Servlet3.0的新特性,不需要在web.xml进行配置,简化了配置. Name T ...
- Android 5.0 API
Android 5.0 (LOLLIPOP) 为用户和应用开发者提供了新功能.本文旨在介绍其中最值得关注的新 API. 如果您有已发布的应用,请务必看一看 Android 5.0 行为变更,了解您的应 ...
- <转> 解决异常:IllegalStateException: Fragment <ThisFragment> is not currently in the FragmentManager
上午敲代码时出现这个问题,简单记录一下解决办法,有时间详细描述一下深层原因. 问题出现在这: @Override public void onSaveInstanceState(Bundle outS ...