PRELOADER

当前文章 : 《使用ffmpeg和mencoder进行视频转码》

5/5/2019 —— 

概述

  FFmpeg的名称来自MPEG视频编码标准,前面的“FF”代表“Fast Forward”,FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。可以轻易地实现多种视频格式之间的相互转换。FFmpeg的用户有Google,Facebook,Youtube,优酷,爱奇艺,土豆等。

FFmpeg组成

  1. libavformat:用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能,包含demuxers和muxer库;
  2. libavcodec:用于各种类型声音/图像编解码;
  3. libavutil:包含一些公共的工具函数;
  4. libswscale:用于视频场景比例缩放、色彩映射转换;
  5. libpostproc:用于后期效果处理;
  6. ffmpeg:是一个命令行工具,用来对视频文件转换格式,也支持对电视卡实时编码;
  7. ffsever:是一个HTTP多媒体实时广播流服务器,支持时光平移;
  8. ffplay:是一个简单的播放器,使用ffmpeg 库解析和解码,通过SDL显示;

两者比较

  1. ffmpeg是mplayer和mencoder的库类之一,并且mencoder是mplayer的一部分。
  2. mplayer是一个播放器,mencoder是一个编码器。
  3. mencoder更易于操作,并且能转换更多音频/视频文件,但是它不能用于截图。
  4. 转换速度比较:总体上ffmpeg转换的速度快于Mencoder
  5. 转换格式要求:rm、rmvb、rt格式的文件只能用Mencoder转换。
  6. 纯音频格式只能用Mencoder进行转换。如何判断是否是纯音频格式可以通过使用命令 FFmpeg -i “文件的完整路径” 获得输出后就可以分析出来。
  7. .mov格式的用ffmpeg转换出来的效果比较差,建议用Mencoder进行转换,wmv8用ffmpeg经常会有花屏产生建议用Mencoder。
  8. 视频按比率输出的问题:必须先获取源视频文件的宽度和高度(也是通过 FFmpeg -i “文件的完整路径” 获得输出后就可以分析出来)根据这个高度和宽度的比率来设定输出文件的尺寸。

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
*@date: 2018年9月05日 上午10:43:22
*@说明:视频转码工具类
**/
public class VideoUtil {

private static String mencoder_home = Global.getMencoderHome();//mencoder.exe安装路径
private static String ffmpeg_home = Global.getFfmpegHome();//ffmpeg.exe安装路径

private static String tempFile_home;//存放rm,rmvb等无法使用ffmpeg直接转换为flv文件先转成的avi文件


/**
* @param inputFile 待处理视频,需带路径
* @param outputFile 输出文件地址
* @param isDel 是否删除转换前视频
* @return
*/
public static boolean convert(String inputFile, String outputFile){
tempFile_home = outputFile.substring(0,outputFile.lastIndexOf("."))+".avi";
System.out.println(outputFile);
System.out.println(tempFile_home);
if (!checkfile(inputFile)) {
System.out.println(inputFile + "文件不存在");
return false;
}
if (process(inputFile,outputFile)) {
System.out.println("转码成功");
return true;
}
return false;
}
//检查文件是否存在
private static boolean checkfile(String path) {
File file = new File(path);
if (!file.isFile()) {
return false;
}
return true;
}
/**
* 转换过程 :先检查文件类型,在决定调用 processMP4还是processAVI
* @param inputFile
* @param outputFile
* @return
*/
private static boolean process(String inputFile,String outputFile) {
int type = checkContentType( inputFile);
boolean status = false;
if (type == 0) {
status = processMP4(inputFile,outputFile);//ffmepg: 能解析的格式
} else if (type == 1) {
String avifilepath = processAVI(type,inputFile);// ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),用(mencoder)转换为avi
if (avifilepath == null)
return false;
status = processMP4(avifilepath,outputFile);
}
return status;
}
/**
* 检查视频类型
* @param inputFile
* @return ffmpeg 能解析返回0,不能解析返回1
*/
private static int checkContentType(String inputFile) {
String type = inputFile.substring(inputFile.lastIndexOf(".") + 1,inputFile.length()).toLowerCase();
// ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
if (type.equals("avi")) {
return 0;
} else if (type.equals("mpg")) {
return 0;
} else if (type.equals("wmv")) {
return 0;
} else if (type.equals("3gp")) {
return 0;
} else if (type.equals("mov")) {
return 0;
} else if (type.equals("mp4")) {
return 0;
} else if (type.equals("asf")) {
return 0;
} else if (type.equals("asx")) {
return 0;
} else if (type.equals("flv")) {
return 0;
}
// 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),
// 使用(mencoder)转换为avi(ffmpeg能解析的)格式.
else if (type.equals("wmv9")) {
return 1;
} else if (type.equals("rm")) {
return 1;
} else if (type.equals("rmvb")) {
return 1;
}
return 9;
}
/**
* ffmepg: 能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
* @param inputFile
* @param outputFile
* @return
*/
private static boolean processMP4(String inputFile,String outputFile) {
if (!checkfile(inputFile)) {
System.out.println(inputFile + "文件不存在");
return false;
}
File file = new File(outputFile);
if(file.exists()){
System.out.println("文件已经存在!");
return true;
} else {
System.out.println("正在转换");
List<String> commend = new ArrayList<String>();
//低精度
commend.add(ffmpeg_home);
commend.add("-i");
commend.add(inputFile);
commend.add("-ab");
commend.add("128");
commend.add("-acodec");
commend.add("libmp3lame");
commend.add("-ac");
commend.add("1");
commend.add("-ar");
commend.add("22050");
commend.add("-r");
commend.add("29.97");
// 清晰度 -qscale 4 为最好但文件大, -qscale 6基本能满足业务
commend.add("-qscale");
commend.add("4");
commend.add("-y");
commend.add(outputFile);
StringBuffer log=new StringBuffer();
for(int i=0;i<commend.size();i++)
log.append(commend.get(i)+" ");
System.out.println(log);
try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(commend);
builder.start();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}

}

}
/**
* Mencoder:
* 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),用(mencoder)转换为avi(ffmpeg能解析的)格式.
* @param type
* @param inputFile
* @return
*/
private static String processAVI(int type,String inputFile) {
File file =new File(tempFile_home);
if(file.exists()){
System.out.println("文件已经存在!");
return tempFile_home;
}
List<String> commend = new ArrayList<String>();
commend.add(mencoder_home);
commend.add(inputFile);
commend.add("-oac");
commend.add("mp3lame");
commend.add("-lameopts");
commend.add("preset=64");
commend.add("-ovc");
commend.add("xvid");
commend.add("-xvidencopts");
commend.add("bitrate=600");
commend.add("-of");
commend.add("avi");
commend.add("-o");
commend.add(tempFile_home);
StringBuffer log=new StringBuffer();
for(int i=0;i<commend.size();i++)
log.append(commend.get(i)+" ");
System.out.println(log);
try
{
ProcessBuilder builder = new ProcessBuilder();
builder.command(commend);
Process p=builder.start();
/**
* 清空Mencoder进程 的输出流和错误流
* 因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,
* 如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。
* 创建子进程Process之后,一定要及时取走子进程的输出信息和错误信息,
* 否则输出信息流和错误信息流很可能因为信息太多导致被填满,最终导致子进程阻塞住,然后执行不下去。
*/
final InputStream is1 = p.getInputStream();
final InputStream is2 = p.getErrorStream();
new Thread() {
public void run() {
BufferedReader br = new BufferedReader(new InputStreamReader(is1));
try {
String lineB = null;
while ((lineB = br.readLine()) != null ){
if(lineB != null)System.out.println(lineB);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
new Thread() {
public void run() {
BufferedReader br2 = new BufferedReader(new InputStreamReader(is2));
try {
String lineC = null;
while ( (lineC = br2.readLine()) != null){
if(lineC != null)System.out.println(lineC);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
//Mencoder进程转换结束,调用ffmpeg进程
p.waitFor();
return tempFile_home;
}catch (Exception e){
System.err.println(e);
return null;
}

}


/**
* 获取视频时长
* @param video_path
* @param ffmpeg_path
* @return
*/
public static String getVideoTime(String video_path) {
List<String> commands = new ArrayList<String>();
commands.add(ffmpeg_home);
commands.add("-i");
commands.add(video_path);
try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(commands);
final Process p = builder.start();
//从输入流中读取视频信息
BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
StringBuffer sb = new StringBuffer();
String line = "";
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
//从视频信息中解析时长
String regexDuration = "Duration: (.*?), start: (.*?), bitrate: (\\d*) kb\\/s";
Pattern pattern = Pattern.compile(regexDuration);
Matcher m = pattern.matcher(sb.toString());
if (m.find()) {
//int time = getTimelen(m.group(1));
//System.out.println(video_path+",视频时长:"+m.group(1));
return m.group(1);
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}


private static int getTimelen(String timelen){
int min=0;
String strs[] = timelen.split(":");
if (strs[0].compareTo("0") > 0) {
min+=Integer.valueOf(strs[0])*60*60;//秒
}
if(strs[1].compareTo("0")>0){
min+=Integer.valueOf(strs[1])*60;
}
if(strs[2].compareTo("0")>0){
min+=Math.round(Float.valueOf(strs[2]));
}
return min;
}

}