激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|JavaScript|易語(yǔ)言|

服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - Java+Windows+ffmpeg實(shí)現(xiàn)視頻轉(zhuǎn)換功能

Java+Windows+ffmpeg實(shí)現(xiàn)視頻轉(zhuǎn)換功能

2021-06-22 13:15鄭文亮 Java教程

這篇文章主要為大家詳細(xì)介紹了Java+Windows+ffmpeg實(shí)現(xiàn)視頻轉(zhuǎn)換功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

最近由于項(xiàng)目需要,研究了一下如何用java實(shí)現(xiàn)視頻轉(zhuǎn)換,“著實(shí)”廢了點(diǎn)心思,整理整理,寫出給自己備忘下。

思路

由于之前沒有沒法過相關(guān)功能的經(jīng)驗(yàn),一開始來真不知道從哪里入手。當(dāng)然,這個(gè)解決,google一下立馬就發(fā)現(xiàn)了ffmpeg,網(wǎng)上講解用java+ffmpeg來進(jìn)行視頻轉(zhuǎn)換的文章也不在少數(shù),我主要參考的這篇文章。

上文提到的這篇文章,基本已經(jīng)把開發(fā)流程什么的講的很清楚了,這里總結(jié)下:

1)核心是利用ffmpeg進(jìn)行視頻轉(zhuǎn)換,我們自己并不寫轉(zhuǎn)換視頻的代碼,只是調(diào)用ffmpeg,它會(huì)幫我們完成視頻的轉(zhuǎn)換。ffmpeg支持的類型有:asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等,這些類型,可以利用ffmpeg進(jìn)行直接轉(zhuǎn)換。ffmpeg不支持的類型有:wmv9,rm,rmvb等,這些類型需要先用別的工具(mencoder)轉(zhuǎn)換為avi(ffmpeg能解析的)格式。

2)了解java如何調(diào)用外部程序,這會(huì)是最困難的,也會(huì)是坑最多的地方。

3)根據(jù)我們的需求設(shè)置ffmpeg的參數(shù)。(這類文章網(wǎng)上已經(jīng)有很多了,我也不用復(fù)制黏貼了,見這里)

代碼

上文中提到的那篇文章中的代碼其實(shí)已經(jīng)寫的很友好了,基本拿來就能用,不過仍然存在許多問題,接下來會(huì)講到,下面是文中的代碼:

?
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
import java.io.file;
import java.util.arraylist;
import java.util.calendar;
import java.util.list;
 
public class convertvideo {
 
 private final static string path = "c:\\ffmpeg\\input\\c.mp4";
 
 public static void main(string[] args) {
  if (!checkfile(path)) {
   system.out.println(path + " is not file");
   return;
  }
  if (process()) {
   system.out.println("ok");
  }
 }
 
 private static boolean process() {
  int type = checkcontenttype();
  boolean status = false;
  if (type == 0) {
   system.out.println("直接將文件轉(zhuǎn)為flv文件");
   status = processflv(path);// 直接將文件轉(zhuǎn)為flv文件
  } else if (type == 1) {
   string avifilepath = processavi(type);
   if (avifilepath == null)
    return false;// avi文件沒有得到
   status = processflv(avifilepath);// 將avi轉(zhuǎn)為flv
  }
  return status;
 }
 
 private static int checkcontenttype() {
  string type = path.substring(path.lastindexof(".") + 1, path.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;
  }
  // 對(duì)ffmpeg無法解析的文件格式(wmv9,rm,rmvb等),
  // 可以先用別的工具(mencoder)轉(zhuǎn)換為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;
 }
 
 private static boolean checkfile(string path) {
  file file = new file(path);
  if (!file.isfile()) {
   return false;
  }
  return true;
 }
 
 // 對(duì)ffmpeg無法解析的文件格式(wmv9,rm,rmvb等), 可以先用別的工具(mencoder)轉(zhuǎn)換為avi(ffmpeg能解析的)格式.
 private static string processavi(int type) {
  list<string> commend = new arraylist<string>();
  commend.add("c:\\ffmpeg\\mencoder");
  commend.add(path);
  commend.add("-oac");
  commend.add("lavc");
  commend.add("-lavcopts");
  commend.add("acodec=mp3:abitrate=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("c:\\ffmpeg\\output\\a.avi");
  try {
   processbuilder builder = new processbuilder();
   builder.command(commend);
   builder.start();
   return "c:\\ffmpeg\\output\\a.avi";
  } catch (exception e) {
   e.printstacktrace();
   return null;
  }
 }
 
 // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
 private static boolean processflv(string oldfilepath) {
 
  if (!checkfile(path)) {
   system.out.println(oldfilepath + " is not file");
   return false;
  }
   
  // 文件命名
  calendar c = calendar.getinstance();
  string savename = string.valueof(c.gettimeinmillis())+ math.round(math.random() * 100000);
  list<string> commend = new arraylist<string>();
  commend.add("c:\\ffmpeg\\ffmpeg");
  commend.add("-i");
  commend.add(oldfilepath);
  commend.add("-ab");
  commend.add("56");
  commend.add("-ar");
  commend.add("22050");
  commend.add("-qscale");
  commend.add("8");
  commend.add("-r");
  commend.add("15");
  commend.add("-s");
  commend.add("600x500");
  commend.add("c:\\ffmpeg\\output\\a.flv");
 
  try {
   runtime runtime = runtime.getruntime();
   process proce = null;
   string cmd = "";
   string cut = "  c:\\ffmpeg\\ffmpeg.exe -i "
     + oldfilepath
     + " -y -f image2 -ss 8 -t 0.001 -s 600x500 c:\\ffmpeg\\output\\"
     + "a.jpg";
   string cutcmd = cmd + cut;
   proce = runtime.exec(cutcmd);
   processbuilder builder = new processbuilder(commend);
    builder.command(commend);
   builder.start();
 
   return true;
  } catch (exception e) {
   e.printstacktrace();
   return false;
  }
 }
}

接下來是我自己經(jīng)過修改后的代碼:

?
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
import java.io.file;
import java.io.ioexception;
import java.util.arraylist;
import java.util.calendar;
import java.util.list;
public class convertvideo {
 
 private static string inputpath = "";
 
 private static string outputpath = "";
 
 private static string ffmpegpath = "";
 
 public static void main(string args[]) throws ioexception {
  
  getpath();
  
  if (!checkfile(inputpath)) {
   system.out.println(inputpath + " is not file");
   return;
  }
  if (process()) {
   system.out.println("ok");
  }
 }
 
 private static void getpath() { // 先獲取當(dāng)前項(xiàng)目路徑,在獲得源文件、目標(biāo)文件、轉(zhuǎn)換器的路徑
  file diretory = new file("");
  try {
   string currpath = diretory.getabsolutepath();
   inputpath = currpath + "\\input\\test.wmv";
   outputpath = currpath + "\\output\\";
   ffmpegpath = currpath + "\\ffmpeg\\";
   system.out.println(currpath);
  }
  catch (exception e) {
   system.out.println("getpath出錯(cuò)");
  }
 }
 
 private static boolean process() {
  int type = checkcontenttype();
  boolean status = false;
  if (type == 0) {
   system.out.println("直接轉(zhuǎn)成flv格式");
   status = processflv(inputpath);// 直接轉(zhuǎn)成flv格式
  } else if (type == 1) {
   string avifilepath = processavi(type);
   if (avifilepath == null)
    return false;// 沒有得到avi格式
   status = processflv(avifilepath);// 將avi轉(zhuǎn)成flv格式
  }
  return status;
 }
 
 private static int checkcontenttype() {
  string type = inputpath.substring(inputpath.lastindexof(".") + 1, inputpath.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;
  }
  // 對(duì)ffmpeg無法解析的文件格式(wmv9,rm,rmvb等),
  // 可以先用別的工具(mencoder)轉(zhuǎn)換為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;
 }
 
 private static boolean checkfile(string path) {
  file file = new file(path);
  if (!file.isfile()) {
   return false;
  }
  return true;
 }
 
 // 對(duì)ffmpeg無法解析的文件格式(wmv9,rm,rmvb等), 可以先用別的工具(mencoder)轉(zhuǎn)換為avi(ffmpeg能解析的)格式.
 private static string processavi(int type) {
  list<string> commend = new arraylist<string>();
  commend.add(ffmpegpath + "mencoder");
  commend.add(inputpath);
  commend.add("-oac");
  commend.add("lavc");
  commend.add("-lavcopts");
  commend.add("acodec=mp3:abitrate=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(outputpath + "a.avi");
  try {
   processbuilder builder = new processbuilder();
   process process = builder.command(commend).redirecterrorstream(true).start();
   new printstream(process.getinputstream());
   new printstream(process.geterrorstream());
   process.waitfor();
   return outputpath + "a.avi";
  } catch (exception e) {
   e.printstacktrace();
   return null;
  }
 }
 
 // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
 private static boolean processflv(string oldfilepath) {
 
  if (!checkfile(inputpath)) {
   system.out.println(oldfilepath + " is not file");
   return false;
  }
  
  list<string> command = new arraylist<string>();
  command.add(ffmpegpath + "ffmpeg");
  command.add("-i");
  command.add(oldfilepath);
  command.add("-ab");
  command.add("56");
  command.add("-ar");
  command.add("22050");
  command.add("-qscale");
  command.add("8");
  command.add("-r");
  command.add("15");
  command.add("-s");
  command.add("600x500");
  command.add(outputpath + "a.flv");
 
  try {
   
   // 方案1
//   process videoprocess = runtime.getruntime().exec(ffmpegpath + "ffmpeg -i " + oldfilepath
//     + " -ab 56 -ar 22050 -qscale 8 -r 15 -s 600x500 "
//     + outputpath + "a.flv");
   
   // 方案2
   process videoprocess = new processbuilder(command).redirecterrorstream(true).start();
   
   new printstream(videoprocess.geterrorstream()).start();
   
   new printstream(videoprocess.getinputstream()).start();
   
   videoprocess.waitfor();
   
   return true;
  } catch (exception e) {
   e.printstacktrace();
   return false;
  }
 }
}
 
class printstream extends thread
{
 java.io.inputstream __is = null;
 public printstream(java.io.inputstream is)
 {
  __is = is;
 }
 
 public void run()
 {
  try
  {
   while(this != null)
   {
    int _ch = __is.read();
    if(_ch != -1)
     system.out.print((char)_ch);
    else break;
   }
  }
  catch (exception e)
  {
   e.printstacktrace();
  }
 }
}

問題

原文的代碼中有一個(gè)很大的問題,便是不知道視頻轉(zhuǎn)換到底什么時(shí)候結(jié)束。看原文中的這兩處代碼:

98行處

?
1
2
3
builder.command(commend);
 builder.start();
 return "c:\\ffmpeg\\output\\a.avi";

145行處

?
1
2
builder.start();
 return true;

在進(jìn)程開始之后,直接就返回結(jié)果了。要知道,這樣的寫法,是不會(huì)阻塞當(dāng)前進(jìn)程的,也就是說,當(dāng)然程序返回的時(shí)候,轉(zhuǎn)碼程序(ffmpeg和mencoder)還在執(zhí)行。如果需要mencoder進(jìn)行中間轉(zhuǎn)碼,那原文中的寫法會(huì)造成在avi文件還未轉(zhuǎn)換完成時(shí),程序就調(diào)用了ffmpeg進(jìn)行轉(zhuǎn)換。而對(duì)于最終的flv文件,我們也無法知道到底是什么時(shí)候轉(zhuǎn)換好的,這顯然是無法滿足我們的業(yè)務(wù)需求的 。

解決方案

最先想到的辦法自然就是阻塞當(dāng)前進(jìn)程(主進(jìn)程),實(shí)例代碼:

?
1
2
3
process process = new processbuilder(command).start();
 process.waitfor();
 return true;

采用這種的方案運(yùn)行程序,發(fā)現(xiàn)視頻轉(zhuǎn)到十幾秒的時(shí)候就不轉(zhuǎn)了,但是程序還沒返回,打開進(jìn)程管理器一開,ffmpeg進(jìn)程還在,內(nèi)存還占著,但是cpu為0,如圖:

Java+Windows+ffmpeg實(shí)現(xiàn)視頻轉(zhuǎn)換功能

當(dāng)時(shí)不知道什么原因,在網(wǎng)上查了半天,才明白這是死鎖了,但是不知道是什么原因造成的。當(dāng)時(shí)就一直覺得死鎖是waitfor()函數(shù)造成了,看來用它來判斷子進(jìn)程是否結(jié)果是不行了,所以又在網(wǎng)上查了半天其他判斷子進(jìn)程結(jié)束的辦法(這里其實(shí)就已經(jīng)走彎路了)。有人說可以用exitvalue(),于是就有了下面的代碼:

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

當(dāng)子進(jìn)程沒有結(jié)束的時(shí)候,如果執(zhí)行exitvalue()就會(huì)拋出異常,我采用的辦法是捕獲這個(gè)異常然后不去理他,直到程序結(jié)束exitvalue()返回0為止。但是,還是失敗了,出現(xiàn)的情況和用waitfor()方式時(shí)的一模一樣,我才覺得可能是另外的原因,在去google,發(fā)現(xiàn)可能是是由于jvm只提供有限緩存空間,當(dāng)外部程序(子進(jìn)程)的輸出流超出了這個(gè)有限空間而父進(jìn)程又不讀出這些數(shù)據(jù),子進(jìn)程會(huì)被阻塞waitfor()永遠(yuǎn)都不會(huì)返回,就會(huì)造成死鎖。

官方解釋:

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.

知道問題了就要對(duì)癥下藥(其實(shí)當(dāng)時(shí)我也不知道這是不是就是我遇到的問題,只能各種打散彈了,打中了算)。關(guān)于如何讀出子進(jìn)程的輸出流,如何解決這個(gè)死鎖,網(wǎng)上的辦法都大同小異,寫的比較好的可以看這個(gè)地址。

于是程序被改成這樣:

?
1
2
3
4
process process = new processbuilder(command).start();
             
 new printstream(process.getinputstream()).start();
 process.waitfor();

printstream類如下:

?
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
class printstream extends thread
{
  java.io.inputstream __is = null;
  public printstream(java.io.inputstream is)
  {
    __is = is;
  }
 
  public void run()
  {
    try
    {
      while(this != null)
      {
        int _ch = __is.read();
        if(_ch != -1)
          system.out.print((char)_ch);
        else break;
      }
    }
    catch (exception e)
    {
      e.printstacktrace();
    }
  }
}

運(yùn)行,發(fā)現(xiàn)還是不對(duì),癥狀和之前的一模一樣,我還以為是不是輸出流太多了,一個(gè)線程讀的不夠快(好吧,真的很傻很天真,人被逼急了真的什么想法都有),于是我就再開了幾個(gè)一模一樣的線程,結(jié)果還是一樣。

就在我快要放棄的時(shí)候,在百度知道上,看了個(gè)無關(guān)痛癢的例子,于是做了個(gè)小修改,在進(jìn)程啟動(dòng)之前,重定向了下錯(cuò)誤輸出流,如下:

?
1
2
3
4
5
6
process videoprocess = new processbuilder(command).redirecterrorstream(true).start();
             
 new printstream(videoprocess.getinputstream()).start();      
 videoprocess.waitfor();
       
 return true;

然后,然后,然后就可以了。

結(jié)論

其實(shí)有兩種寫法可以解決這個(gè)問題,這種事像我上面那樣寫,還有一種如下:

?
1
2
3
4
5
6
7
8
process videoprocess = new processbuilder(command).start();
      
 new printstream(videoprocess.geterrorstream()).start();     
 new printstream(videoprocess.getinputstream()).start();
      
 videoprocess.waitfor();
      
 return true;

其實(shí)道理還是一樣的,就是讀出ffmpeg的輸出流,避免ffmpeg的輸出流塞滿緩存造成死鎖。但是不知道為什么,ffmpeg的輸出信息是在錯(cuò)誤輸出流里面的,我看了下控制臺(tái)打印結(jié)果,發(fā)現(xiàn)只是一些當(dāng)前轉(zhuǎn)換狀態(tài)的信息,并沒有錯(cuò)誤,令人費(fèi)解。

在process類中,getinputstream用來獲取進(jìn)程的輸出流,getoutputstream用來獲取進(jìn)程的輸入流,geterrorstream用來獲取進(jìn)程的錯(cuò)誤信息流。為了保險(xiǎn)起見,在讀出的時(shí)候,最好把子進(jìn)程的輸出流和錯(cuò)誤流都讀出來,這樣可以保證清空緩存區(qū)。

其實(shí),我深刻地感覺到,這些解決的問題的經(jīng)歷是標(biāo)準(zhǔn)的散彈式編程,打到哪算哪,以后引以為戒。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。

原文鏈接:http://www.cnblogs.com/zhwl/p/4670478.html

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25
主站蜘蛛池模板: 日韩av有码在线 | 九九热在线视频观看这里只有精品 | 91精品国产九九九久久久亚洲 | 得得啪在线视频 | 羞羞草视频| 手机在线看片国产 | 中文亚洲视频 | 一级免费毛片 | 精品一区二区三区电影 | 久久99精品久久久久久园产越南 | 免费在线观看成年人视频 | 草草视频在线播放 | 午夜视频免费播放 | 久久亚洲精品久久国产一区二区 | 欧美性xxxx狂欢老少配 | 国产亚洲欧美一区久久久在 | 欧美视频一级 | 美女视频黄a视频免费全过程 | 一区二区美女视频 | 一级在线免费 | 久色亚洲| 久久久亚洲高清 | 日本在线看 | wankzhd| 艹男人的日日夜夜 | 国产一区二区在线观看视频 | 一级电影免费在线观看 | 国产九九热 | 亚洲国产精品二区 | 黄色免费小网站 | 成人三级电影在线 | 亚洲精品午夜在线 | 欧美性生活区 | 1024亚洲天堂 | 看免费黄色大片 | 欧美a在线观看 | 免费看黄色一级大片 | 成码无人av片在线观看网站 | 成人在线免费观看小视频 | 久久精品久久精品久久精品 | 国产亚洲精品久久久久婷婷瑜伽 |