最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時間:2018年12月15日 14:30:41   作者:鄭文亮  
這篇文章主要為大家詳細介紹了Java+Windows+ffmpeg實現(xiàn)視頻轉(zhuǎn)換功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下

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

思路

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

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

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

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

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

代碼

上文中提到的那篇文章中的代碼其實已經(jī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;

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

解決方案

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

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

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

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

知道問題了就要對癥下藥(其實當(dāng)時我也不知道這是不是就是我遇到的問題,只能各種打散彈了,打中了算)。關(guā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();
    } 
  }
}

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

就在我快要放棄的時候,在百度知道上,看了個無關(guā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)的信息,并沒有錯誤,令人費解。

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

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

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 教您如何3分鐘快速搞定EasyExcel導(dǎo)入與導(dǎo)出功能

    教您如何3分鐘快速搞定EasyExcel導(dǎo)入與導(dǎo)出功能

    對于EasyExcel庫,我們可以使用它來實現(xiàn)數(shù)據(jù)的導(dǎo)入和導(dǎo)出,下面這篇文章主要給大家介紹了關(guān)于如何3分鐘快速搞定EasyExcel導(dǎo)入與導(dǎo)出功能的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-01-01
  • SpringBoot Bean花式注解方法示例下篇

    SpringBoot Bean花式注解方法示例下篇

    這篇文章主要介紹了SpringBoot Bean花式注解方法,很多時候我們需要根據(jù)不同的條件在容器中加載不同的Bean,或者根據(jù)不同的條件來選擇是否在容器中加載某個Bean
    2023-02-02
  • Dubbo新版本zk注冊中心連接問題及解決

    Dubbo新版本zk注冊中心連接問題及解決

    這篇文章主要介紹了Dubbo新版本zk注冊中心連接問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Hibernatede 一對多映射配置方法(分享)

    Hibernatede 一對多映射配置方法(分享)

    下面小編就為大家?guī)硪黄狧ibernatede 一對多映射配置方法(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • Java使用遞歸回溯完美解決八皇后的問題

    Java使用遞歸回溯完美解決八皇后的問題

    這篇文章主要介紹了Java基于循環(huán)遞歸回溯實現(xiàn)八皇后問題算法,結(jié)合具體實例形式分析了java的遍歷、遞歸、回溯等算法實現(xiàn)八皇后問題的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下
    2021-11-11
  • Java程序員必備的11大IntelliJ插件(附地址)

    Java程序員必備的11大IntelliJ插件(附地址)

    這篇文章主要介紹了Java程序員必備的11大IntelliJ插件(附地址),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • Knife4j的請求示例當(dāng)中有很多空白行的問題解決辦法

    Knife4j的請求示例當(dāng)中有很多空白行的問題解決辦法

    這篇文章主要介紹了Knife4j的請求示例當(dāng)中有很多空白行的問題解決辦法,按正常來說不應(yīng)該有上方的空白,當(dāng)然如果只是查看我也不至于非要解決他,主要是假如接口是json傳參,調(diào)試界面都沒辦法修改參數(shù),遇到同樣問題的同學(xué)可以參考閱讀本文
    2024-09-09
  • Java面試突擊為什么要用HTTPS及它的優(yōu)點

    Java面試突擊為什么要用HTTPS及它的優(yōu)點

    這篇文章主要介紹了Java面試突擊為什么要用HTTPS及它的優(yōu)點,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-07-07
  • 使用Post方法模擬登陸爬取網(wǎng)頁的實現(xiàn)方法

    使用Post方法模擬登陸爬取網(wǎng)頁的實現(xiàn)方法

    下面小編就為大家?guī)硪黄褂肞ost方法模擬登陸爬取網(wǎng)頁的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-03-03
  • Spring Boot 中使用 Mybatis Plus的操作方法

    Spring Boot 中使用 Mybatis Plus的操作方法

    本文介紹了如何在 Spring Boot 項目中集成 Mybatis Plus,Spring Boot 與 MyBatis Plus 的集成非常簡單,通過自動配置和簡潔的 API,可以大大減少開發(fā)中常見的數(shù)據(jù)庫操作代碼,需要的朋友參考下吧
    2024-12-12

最新評論

营山县| 扬州市| 高陵县| 浮山县| 博野县| 瑞昌市| 同德县| 鹤岗市| 扎鲁特旗| 米脂县| 甘肃省| 滁州市| 永清县| 门源| 德兴市| 沙雅县| 荥阳市| 定襄县| 溆浦县| 清涧县| 拉萨市| 蒲城县| 弥渡县| 遵义市| 铜鼓县| 平舆县| 山丹县| 新安县| 永济市| 红桥区| 新河县| 平顶山市| 嘉禾县| 伊金霍洛旗| 化州市| 郸城县| 柳江县| 莱阳市| 青州市| 丰宁| 天峨县|