溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何在Java中使用Windows和ffmpeg實現(xiàn)一個視頻轉(zhuǎn)換功能

發(fā)布時間:2021-02-22 15:55:47 來源:億速云 閱讀:187 作者:Leah 欄目:編程語言

本篇文章為大家展示了如何在Java中使用Windows和ffmpeg實現(xiàn)一個視頻轉(zhuǎn)換功能,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。

Java的特點有哪些

Java的特點有哪些 1.Java語言作為靜態(tài)面向?qū)ο缶幊陶Z言的代表,實現(xiàn)了面向?qū)ο罄碚摚试S程序員以優(yōu)雅的思維方式進(jìn)行復(fù)雜的編程。 2.Java具有簡單性、面向?qū)ο?、分布式、安全性、平臺獨(dú)立與可移植性、動態(tài)性等特點。 3.使用Java可以編寫桌面應(yīng)用程序、Web應(yīng)用程序、分布式系統(tǒng)和嵌入式系統(tǒng)應(yīng)用程序等。

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; 
  } 
  // 對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; 
 } 
 
 // 對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)過修改后的代碼:

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)前項目路徑,在獲得源文件、目標(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出錯");
  }
 }
 
 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;
  }
  // 對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;
 }

 // 對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();
  } 
 }
}

問題

原文的代碼中有一個很大的問題,便是不知道視頻轉(zhuǎn)換到底什么時候結(jié)束??丛闹械倪@兩處代碼:

98行處

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

145行處

builder.start(); 
 return true;

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

解決方案

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

Process process = new ProcessBuilder(command).start();
 process.waitFor();
 return true;

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

如何在Java中使用Windows和ffmpeg實現(xiàn)一個視頻轉(zhuǎn)換功能

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

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

官方解釋:

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.

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

于是程序被改成這樣:

Process process = new ProcessBuilder(command).start();
             
 new PrintStream(process.getInputStream()).start();
 process.waitFor();

PrintStream類如下:

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)還是不對,癥狀和之前的一模一樣,我還以為是不是輸出流太多了,一個線程讀的不夠快(好吧,真的很傻很天真,人被逼急了真的什么想法都有),于是我就再開了幾個一模一樣的線程,結(jié)果還是一樣。

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

Process videoProcess = new ProcessBuilder(command).redirectErrorStream(true).start();
             
 new PrintStream(videoProcess.getInputStream()).start();       
 videoProcess.waitFor();
       
 return true;

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

結(jié)論

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

Process videoProcess = new ProcessBuilder(command).start();
      
 new PrintStream(videoProcess.getErrorStream()).start();      
 new PrintStream(videoProcess.getInputStream()).start();
      
 videoProcess.waitFor();
      
 return true;

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

在Process類中,getInputStream用來獲取進(jìn)程的輸出流,getOutputStream用來獲取進(jìn)程的輸入流,getErrorStream用來獲取進(jìn)程的錯誤信息流。為了保險起見,在讀出的時候,最好把子進(jìn)程的輸出流和錯誤流都讀出來,這樣可以保證清空緩存區(qū)。

上述內(nèi)容就是如何在Java中使用Windows和ffmpeg實現(xiàn)一個視頻轉(zhuǎn)換功能,你們學(xué)到知識或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識儲備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI