자바+Windows+ffmpeg 비디오 변환 기능 구현

최근 프로젝트 가 필요 해 자바 로 영상 전환 을 어떻게 하 는 지 연구 해 보 니'정말'신경 을 쓰 고 정리 하 며 자신 에 게 메 모 를 남 겼 다.
사고의 방향
그동안 관련 기능 을 해 보지 못 한 경험 이 없 었 기 때문에 처음에는 어디서부터 시작 해 야 할 지 몰 랐 다.물론 이 해결 은 구 글 이 바로 ffmpeg 을 발 견 했 습 니 다.인터넷 에서 자바+ffmpeg 으로 영상 전환 을 하 는 글 도 적지 않 습 니 다.제 가 주로 참고 하 는 이 글 입 니 다.
앞에서 언급 한 이 글 은 기본적으로 개발 절차 와 같은 것 을 분명하게 말 했다.여기 서 요약 하면:
1)핵심 은 ffmpeg 를 이용 하여 영상 변환 을 하 는 것 이다.우 리 는 영상 변환 코드 를 쓰 지 않 고 ffmpeg 만 호출 하면 영상의 변환 을 완성 할 수 있다.ffmpeg 지원 유형 은 asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv 등 이 있 습 니 다.이러한 유형 은 ffmpeg 를 이용 하여 직접 전환 할 수 있 습 니 다.ffmpeg 가 지원 하지 않 는 유형 은 wmv 9,rm,rmvb 등 이 있 습 니 다.이 유형 들 은 다른 도구(mencoder)로 avi(ffmpeg 가 해석 할 수 있 는)형식 으로 변환 해 야 합 니 다.
2)자바 가 외부 프로그램 을 어떻게 호출 하 는 지 알 아 보 는 것 이 가장 어렵 고 구덩이 가 가장 많은 곳 일 것 이다.
3)우리 의 수요 에 따라 ffmpeg 의 인 자 를 설정 합 니 다.(이런 글 은 인터넷 에 이미 많이 있 으 니 나 도 복사 해서 붙 일 필요 가 없다.여기 참조)
코드
앞에서 언급 한 그 글 의 코드 는 사실 이미 매우 우호 적 으로 썼 기 때문에 기본적으로 가 져 오 면 사용 할 수 있 지만 아직도 많은 문제 가 존재 한다.다음은 글 의 코드 이다.

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("       flv  "); 
   status = processFLV(PATH);//        flv   
  } else if (type == 1) { 
   String avifilepath = processAVI(type); 
   if (avifilepath == null) 
    return false;// avi       
   status = processFLV(avifilepath);//  avi  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)   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)   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; 
  } 
 } 
}
다음은 제 가 수정 한 코드 입 니 다.

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() { //          ,      、    、      
  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("    flv  ");
   status = processFLV(inputPath);//     flv  
  } else if (type == 1) {
   String avifilepath = processAVI(type);
   if (avifilepath == null)
    return false;//     avi  
   status = processFLV(avifilepath);//  avi  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)   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)   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();
  } 
 }
}

문제.
원문의 코드 에는 동 영상 변환 이 언제 끝 날 지 모 르 는 큰 문제 가 있다.원문의 이 두 가지 코드 를 보십시오.
98 행

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

builder.start(); 
 return true;
프로 세 스 가 시 작 된 후에 바로 결 과 를 되 돌려 주 었 다.이러한 쓰기 가 현재 프로 세 스 를 막 지 않 는 다 는 것 을 알 아야 합 니 다.즉,프로그램 이 돌아 올 때 디 코딩 프로그램(ffmpeg 과 mencoder)이 실행 되 고 있 습 니 다.mencoder 가 중간 디 코딩 을 해 야 한다 면 원문 에 있 는 쓰기 가 avi 파일 이 아직 변환 되 지 않 았 을 때 프로그램 은 ffmpeg 를 호출 하여 변환 할 것 입 니 다.그리고 최종 flv 파일 에 대해 우 리 는 도대체 언제 바 뀌 었 는 지 알 수 없다.이것 은 우리 의 업무 수 요 를 만족 시 킬 수 없다.
해결 방안
가장 먼저 떠 오 르 는 방법 은 현재 프로 세 스(주 프로 세 스)를 막 는 것 입 니 다.인 스 턴 스 코드:

Process process = new ProcessBuilder(command).start();
 process.waitFor();
 return true;
이러한 프로젝트 를 사용 하여 프로그램 을 실행 합 니 다.동 영상 이 10 여 초 로 넘 어 갈 때 돌아 가지 않 는 것 을 발 견 했 습 니 다.그러나 프로그램 이 돌아 오지 않 았 습 니 다.프로 세 스 관리 자 를 열 면 ffmpeg 프로 세 스 가 아직 있 고 메모리 가 차지 하지만 CPU 는 0 입 니 다.그림 과 같 습 니 다.

당시 에는 무슨 원인 인지 인터넷 에서 한참 을 조사해 서 야 이것 이 자물쇠 라 는 것 을 알 았 지만 무슨 원인 으로 생 긴 것 인지 몰 랐 다.그 당시 에 자물쇠 가 wait For()함수 로 인해 생 긴 것 이 라 고 생각 했 습 니 다.이 를 통 해 하위 프로 세 스 의 결과 가 안 되 는 지 판단 하 는 것 같 았 습 니 다.그래서 인터넷 에서 한참 동안 다른 하위 프로 세 스 의 끝 을 판단 하 는 방법 을 찾 았 습 니 다.exitValue()를 사용 할 수 있다 고 해서 다음 코드 가 생 겼 습 니 다.

Process process = new ProcessBuilder(command).start();
while (true) {
  try {
    if (process.exitValue() == 0)
      break;
  }
  catch (IllegalThreadStateException e) {
    continue;
  }
}
return true;
하위 프로 세 스 가 끝나 지 않 았 을 때 exitValue()를 실행 하면 이상 을 던 집 니 다.프로그램 이 끝 날 때 까지 이 이상 을 포착 하고 상대 하지 않 는 방법 을 사용 합 니 다.exitValue()가 0 으로 돌아 갈 때 까지.그러나 실 패 했 습 니 다.waitFor()방식 을 사용 할 때 와 똑 같 습 니 다.저 는 다른 원인 이 라 고 생각 했 습 니 다.구 글 에 가 보 니 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.
문 제 를 알 면 증상 에 맞 게 약 을 써 야 한다.하위 프로 세 스 의 출력 흐름 을 어떻게 읽 는 지,이 잠 금 을 어떻게 해결 하 는 지,인터넷 의 방법 은 모두 대동소이 하 며,비교적 잘 쓴 것 은 이 주 소 를 볼 수 있 습 니 다.
그래서 프로그램 이 이렇게 바 뀌 었 습 니 다.

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

운행 하 다 보 니 아직도 틀 렸 습 니 다.증상 이 예전 과 똑 같 습 니 다.저 는 수출 흐름 이 너무 많은 줄 알 았 습 니 다.한 스 레 드 를 빨리 읽 지 못 했 습 니 다.(좋 습 니 다.정말 멍청 하고 순진 합 니 다.사람 이 급 해서 정말 모든 생각 이 있 습 니 다)그래서 저 는 똑 같은 스 레 드 를 몇 개 더 열 었 습 니 다.결 과 는 똑 같 았 습 니 다.
내 가 포기 하려 고 할 때 바 이 두 가 알 고 있 을 때 중요 하지 않 은 예 를 보고 작은 수정 을 했 습 니 다.프로 세 스 가 시작 되 기 전에 다음 과 같은 오류 출력 흐름 으로 방향 을 바 꾸 었 습 니 다.

Process videoProcess = new ProcessBuilder(command).redirectErrorStream(true).start();
             
 new PrintStream(videoProcess.getInputStream()).start();       
 videoProcess.waitFor();
       
 return true;
그리고,그리고,그리고.
결론.
사실은 이 문 제 를 해결 할 수 있 는 두 가지 방법 이 있 습 니 다.이런 일 은 제 가 위 에 쓴 것 처럼 다음 과 같 습 니 다.

Process videoProcess = new ProcessBuilder(command).start();
      
 new PrintStream(videoProcess.getErrorStream()).start();      
 new PrintStream(videoProcess.getInputStream()).start();
      
 videoProcess.waitFor();
      
 return true;
사실 이 치 는 똑 같 습 니 다.바로 ffmpeg 의 출력 흐름 을 읽 고 ffmpeg 의 출력 흐름 이 캐 시 에 가득 차 서 잠 겨 있 지 않도록 하 는 것 입 니 다.그런데 왜 그런 지 모 르 겠 습 니 다.ffmpeg 의 출력 정 보 는 잘못된 출력 흐름 에 있 습 니 다.콘 솔 인쇄 결 과 를 보 니 현재 상 태 를 바 꾸 는 정보 일 뿐 오류 가 없고 이해 하기 어렵 습 니 다.
Process 클래스 에서 getInputStream 은 프로 세 스 의 출력 흐름 을 가 져 오 는 데 사 용 됩 니 다.getOutputStream 은 프로 세 스 의 입력 흐름 을 가 져 오 는 데 사 용 됩 니 다.getErrorStream 은 프로 세 스 의 잘못된 정보 흐름 을 가 져 오 는 데 사 용 됩 니 다.안전 을 위해 읽 을 때 하위 프로 세 스 의 출력 흐름 과 오류 흐름 을 모두 읽 는 것 이 좋 습 니 다.그러면 캐 시 영역 을 비 울 수 있 습 니 다.
사실 나 는 이런 해결 문제 들 의 경험 이 표준적 인 산탄 식 프로 그래 밍 이 고 때 리 는 것 이 어디 에 있 는 지 나중에 경계 로 삼 는 다 는 것 을 깊이 느 꼈 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기