FFmpeg 라이브러리 사용하기

by ADMINPLAY posted Feb 07, 2012
?

단축키

Prev이전 문서

Next다음 문서

ESC닫기

크게 작게 위로 아래로 댓글로 가기 인쇄

FFmpeg 는 다양한 디지털 음성 스트림과 영상 스트림을 녹화, 변환할 수 있는 유틸리티 프로그램이다.

원래 리눅스용으로 개발되었으나 현재는 윈도우에서도 많이 사용하고 있다.

 

명령어를 직접 날려서 (윈도우라면 cmd창) 동작하는 방식이며 여러가지 프리 소프트웨어와 오픈 소스

라이브러리로 구성되어 있다. 라이브러리 중에는 libavcodec 들어있는데, 이 라이브러리는 음성/영상

코덱 라이브러리로 여러 프로젝트에서 쓰이고 있다. 또, libavformat 이라는 음성/영상 다중화, 역다중화

라이브러리도 있다. 이 프로젝트의 이름은 MPEG 영상 표준화 그룹에서 유래했고, "mpeg" 앞에 붙은 "FF"는 "fast forward"를 의미한다.

 

 

다운로드

 

Windows용으로 컴파일된 FFMPEG 다운 → http://ffdshow.faireal.net/mirror/ffmpeg/
현재 최신 버전 :  ffmpeg.rev12665.7z

 

위 버전으로 집에서 인코딩 해본 결과

 

C:\tools\ffmpeg -i C:\videos\1.avi -ar 44100 -ab 32 -f flv -s 640x480 C:\videos\2.flv

avi → flv 성공


C:\tools\ffmpeg -i C:\videos\2.mpg -ar 44100 -ab 32 -f flv -s 320x240 C:\videos\3.flv

mpg → flv 성공


C:\tools\ffmpeg -i C:\videos\a.asf -ar 44100 -ab 32 -f flv -s 320x240 C:\videos\4.flv

asf → flv 성공


C:\tools\ffmpeg -i C:\videos\b.wmv -ar 44100 -ab 32 -f flv -s 320x240 C:\videos\b.flv

wmv → flv 성공 

 

 

기본적인 명령어 정리

 

ffmpeg -y -i "(인코딩할파일경로)" -title "(타이틀명)" -bitexact -vcodec h264 -coder 1 -bufsize 128 -g 250 -s (가로)x(세로) -r (프레임) -qscale (QScale값) -maxrate 1500 -muxvb 512 -acodec aac -ac 2 -ar 48000 -ab (오디오비트레이트) -vol (볼륨증폭) -f psp -croptop (픽셀) -cropbottom (픽셀) -cropleft (픽셀) -cropright (픽셀) "(인코딩결과물파일명).mp4"

 

(QScale값) : 1 ~ 51 사이값 중 하나를 입력한다. 값이 작을수록 인코딩결과물은 고화질 고용량,

                   값이 클수록 저화질 저용량이 된다. 보통 애니의 경우 25, 영화의 경우 30 정도면 적당함.

 

(오디오비트레이트) : 오디오비트레이트를 입력한다.
          주의) 오디오비트레이트를 64로 하고싶다면 32, 128로 하고싶다면 64를 입력해야 한다.

 

(볼륨증폭) : 볼륨을 얼마나 증폭시킬지 입력한다.
      보통 300~400 정도로 입력하는데... 영화의 경우 더 입력해야 소리가 잘 들릴 수도...

 

-croptop (픽셀) : (가로)x(세로) 크기에서 동영상 윗부분 몇 픽셀을 잘라낼 건지 입력한다.
-cropbottom (픽셀) : (가로)x(세로) 크기에서 동영상 아랫부분 몇 픽셀을 잘라낼 건지 입력한다.
-cropleft (픽셀) : (가로)x(세로) 크기에서 동영상 왼쪽부분 몇 픽셀을 잘라낼 건지 입력한다.
-cropright (픽셀) : (가로)x(세로) 크기에서 동영상 오른쪽부분 몇 픽셀을 잘라낼 건지 입력한다.

 

※ 참고로
 -croptop (픽셀) -cropbottom (픽셀) -cropleft (픽셀) -cropright (픽셀)
 ↑ 요놈들은 동영상 잘라내지 않을꺼면 입력하지 않는다.

 

예를들면... (1~5 모두 29.97 프레임, Qscale=25, 오디오비트레이트 64, 볼륨증폭 300 설정해서 인코딩)

 

  1. 320x240 픽셀 화면으로 인코딩할 경우
    ffmpeg -y -i "(인코딩할파일경로)" -title "(타이틀명)" -bitexact -vcodec h264 -coder 1 -bufsize 128 -g 250 -s 320x240 -r 29.97 -qscale 25 -maxrate 1500 -muxvb 512 -acodec aac -ac 2 -ar 48000 -ab 32 -vol 300 -f psp "(인코딩결과물파일명).mp4"
  2. 368x208 픽셀 화면으로 인코딩할 경우
    ffmpeg -y -i "(인코딩할파일경로)" -title "(타이틀명)" -bitexact -vcodec h264 -coder 1 -bufsize 128 -g 250 -s 368x208 -r 29.97 -qscale 25 -maxrate 1500 -muxvb 512 -acodec aac -ac 2 -ar 48000 -ab 32 -vol 300 -f psp "(인코딩결과물파일명).mp4"
  3. 480x272 픽셀 화면으로 인코딩할 경우
    ffmpeg -y -i "(인코딩할파일경로)" -title "(타이틀명)" -bitexact -vcodec h264 -coder 1 -bufsize 128 -g 250 -s 480x272 -r 29.97 -qscale 25 -maxrate 1500 -muxvb 512 -acodec aac -ac 2 -ar 48000 -ab 32 -vol 300 -f psp "(인코딩결과물파일명).mp4"
  4. 원본 동영상 가로세로 비율이 4:3 일때, 위아래를 잘라 16:9 화면으로 만든 후 368x208 픽셀 화면으로 인코딩할 경우
    ffmpeg -y -i "(인코딩할파일경로)" -title "(타이틀명)" -bitexact -vcodec h264 -coder 1 -bufsize 128 -g 250 -s 368x272 -r 29.97 -qscale 25 -maxrate 1500 -muxvb 512 -acodec aac -ac 2 -ar 48000 -ab 32 -vol 300 -f psp -croptop 32 -cropbottom 32 "(인코딩결과물파일명).mp4"
  5. 원본 동영상 가로세로 비율이 4:3 일때, 위아래를 잘라 16:9 화면으로 만든 후 480x272 픽셀 화면으로 인코딩할 경우
    ffmpeg -y -i "(인코딩할파일경로)" -title "(타이틀명)" -bitexact -vcodec h264 -coder 1 -bufsize 128 -g 250 -s 480x360 -r 29.97 -qscale 25 -maxrate 1500 -muxvb 512 -acodec aac -ac 2 -ar 48000 -ab 32 -vol 300 -f psp -croptop 44 -cropbottom 44 "(인코딩결과물파일명).mp4"
    ffmpeg -i hachikuro.avi -b 512000 hachikuro_512k.flv
    ffmpeg -i hachikuro -s 320x240 -r 200 -b 250 -ar 22050 -ab 48 hachikuro_512k.flv

 

요약 예제
  • AVI를 FLV로 변환 & FLV를 JPG로 변환 (AVI to FLV)
    C:\> ffmpeg -i test.avi -ar 44100 -ab 32 -f flv -s 640x480 test.flv

    ---------------------------------------------------------------------------------------------
    -i             : input file name
    -ar          : audio sampling rate in HZ
    -ab          : audio bit rate in kbit/s
    -f             : output format
    -s             : output dimension
    ---------------------------------------------------------------------------------------------

     

  •  FLV to JPG

    C:\> ffmpeg -i test.flv -an -r 1 -y -s 640x480 test%d.jpg
    ---------------------------------------------------------------------------------------------
    -i             : input file name
    -an         : disable audio
    -r            : fps
    -y           : overwrite file
    -s            : output dimension
    ---------------------------------------------------------------------------------------------  

  • Particular frame to JPG
    C:\> ffmpeg -i test.flv -an -ss 00:00:10 -t 00:00:01 -r 1 -y -s 640x480 test.jpg
    ---------------------------------------------------------------------------------------------
    -ss          : recored start time
    -t            : record end time last for

    ---------------------------------------------------------------------------------------------

 

 

자 그럼 이 방법을 응용하여 동영상이나 오디오를 재생, 녹화 및 변환을 해보자.

 

우선 자바에서 메모장을 실행해보면,

 

exec 메서드를 이용하여 메모장 실행
  1. Runtime.getRuntime().exec("notepad");

 

 이처럼 Runtime 클래스를 이용하여 프로그램을 실행한다. 같은 방법으로 ffmpeg을 실행해보자.

 

 exec 메서드를 이용하여 ffmepg 실행
  1. String cmd = "C:\\tools\\ffmpeg -i C:\\videos\\b.wmv -ar 44100 -ab 32 -f flv -s 320x240
    C:\\videos\\b.flv";
  2. Runtime.getRuntime().exec(cmd);

 

오디오 코덱 등의 문제만 발생하지 않는다면 파일이 정상적으로 변환되는 걸 볼 수 있다. 

자 이제 변환하는 방법도 알아봤으니 슬슬 자바 개발자에게 필요한 작업을 해보자.

일반적으로 동영상을 업로드하고, 재생하는 웹사이트에서는 다음과 같은 프로세스가 진행되게 마련이다.

 

  1. VIEW 단 (보통 플렉스나 HTML이 될것이다.) 에서 파일을 JAVA 쪽으로 업로드 한다.
  2. 업로드를 위한 로직을 처리하고 서버에 저장한다. // 보통 일반적인 업로드는 이곳에서 마무리된다.
  3. 서버에 저장한 파일을 ffmpeg 을 통해 변환한다.     // 부분에서 시간 소요 발생

3번 과정에서 약간의 시간이 소요된다는 게 문제다.
서버의 백그라운드의 프로세스는 계속 돌아가고 있지만 view단에서는 일단 업로드가 끝난 상태이기 때문에 
지금 현재 상태를 동영상 변환까지 완료된 상태라고 인식하기 때문이다.

 

그래서 서버쪽에서는 계속 동영상 변환을 계속하고 있음과 동시에 view단에서는 다른 작업을 할 수 있는

상태가 되버려서 동영상이 업로드 했는데도 일정시간동안 동영상을 볼 수 없는 상황이 되버린다.

 

이 문제를 극복하기 위해 3번 과정이 진행되는 동안 만큼은 부모 프로세스 (JAVA) 를 중지시켜서
view단에서 파일이 변환되는 시간 동안 대기하도록 하는 작업이 필요하다.

 

쉽게 얘기해서 동영상을 변환하는 동안 LOADING 중임을 사용자에게 보여주자는 것이다. ^^

 

동영상 파일을 FLV 파일로 변환하는 동안 자식 프로세스를 잠시 대기시킴

String cmd = "C:\\tools\\ffmpeg -i C:\\videos\\b.wmv -ar 44100 -ab 32 
               -f flv -s 320x240 C:\\videos\\b.flv";
Runtime r = Runtime.getRuntime();
Process p = null; 
try {
   p = r.exec(cmd); // 동영상 변환 명령어 실행시키고 부모 프로세스(자바) 를 얻는다.
   p.waitFor();  // 서브 프로세스 (ffmepg) 가 종료할때 까지 메인 프로세스를 잠시 대기시킨다.
} catch (InterruptedException e) { 
   p.destroy(); // 서브 프로세스를 강제로 종료시킴.

}

 

if (p.exitValue() != 0) { 
   System.out.println("변환 중 에러 발생"); 
   // 정상 종료가 되지 않았을 경우 로직처리
}

 

if (fResult.length() == 0) { 
   System.out.println("변환된 파일의 사이즈가 0임");
   // 변환을 하는 중 에러가 발생하여 파일의 크기가 0일 경우 로직 처리
}

 

// 변환 성공시 로직 처리
System.out.println("변환 성공 ^^");

 

API에도 나와 있다시피 부모 프로세스에서 waitFor() 메서드를 호출하게 되면

자식 프로세스가 종료/중단(Terminated)될 때까지 메인 프로세스와 그 안의 호출부의 쓰레드 까지 대기 (wait) 상태가 된다.

 

하지만 위 코드에서도 문제가 발생한다.  p.waitFor() 호출함과 동시에 블러킹 상태에 걸려서 모든 프로세스가 중단되버린다!

이 문제는 FFMPEG 에서 자체적으로 윈도우로 화면에 출력하는 메시지들이 있는데

 

ffmepg 이 출력하는 메시지

Window에서는 화면에 출력되는 버퍼가 가득 차게되면 자체적으로 블락 상태에 빠지게 되는 특성 때문에

발생한다. (프로세스는 기본적으로 in,out에 대한 버퍼를 가지고 있는데, 만약 이 버퍼가 다 차게 되면

더 이상 입/출력을 하지 못하고 deadlock에 걸린다.)

 

그러므로 이 문제를 해결하기 위해 버퍼에서 계속 읽어 주면서 비워줘야 한다.

 

쓰레드를 이용하여 버퍼를 계속 비워주는 클래스

 

class InputHandler extends Thread {
    InputStream is;

    public InputHandler (InputStream is) {
        this.is = is;
    }
 
    public void run() {
        try {
           BufferedReader br = new BufferedReader(new InputStreamReader(is));
           String cmd;
           while((cmd = br.readLine()) != -1) {
              System.out.println(cmd);
           }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

 

이 메서드를 사용하는 부분의 소스가 지저분해 보인다면 내부 클래스를 사용할 수 있다.

 

내부 클래스를 이용하여 버퍼를 비워주는 스레드 생성

private void exhaustInputStream(final InputStream is) {

 // InputStream.read() 에서 블럭상태에 빠지기 때문에 따로 쓰레드를 돌려서 스트림을 소비한다.
   new Thread() {

public void run() {
   try {
      BufferedReader br = new BufferedReader(new InputStreamReader(is));
      String cmd;
      while((cmd = br.readLine()) != nul) { // 읽을 라인이 없을때까지 계속 반복
         System.out.println(cmd);
      }
   } catch(IOException e) {
      e.printStackTrace();
   }
}

}.start();

}

 

자 그럼 데드락이 걸리는 부분도 해결했으니 작업하면서 만든 ProcessBuilder를 활용한 최종 버전을 소개해본다.

 

FLVConverter.java

package service;

 

import! java.io.BufferedReader;
import! java.io.File;
import! java.io.IOException;
import! java.io.InputStream;
import! java.io.InputStreamReader;

 

public class FLVConverter{
 private String fileName, basePath;
 
 public FLVConverter(String fileName, String basePath) {
  this.fileName = fileName;
  this.basePath = basePath;
 }
 
 public String convert() {
 
        File fOriginal = new File(basePath + System.getProperty("file.separator")

                                              + fileName);
        String outputName = fileName.substring(0, fileName.indexOf("."))+ ".flv";
        File fResult = new File(basePath + System.getProperty("file.separator")

                                             + outputName);
        String ffmpegPath = "C:\\tools\\ffmpeg\\ffmpeg";
       

         // cmd 창에 날릴 명령어 만들기~
        String[] cmdLine = new String[]{ffmpegPath,
                                        "-i",
                                        fOriginal.getPath(),
                                        "-ar",
                                        "11025",
                                        "-f",
                                        "flv",
                                        fResult.getPath()};

 

        // 만약 flv 파일이라면 변환과정 없이 스킵~
        if(fOriginal.getPath().endsWith(".flv")) {
         return outputName;
        }
              
        // 프로세스 속성을 관리하는 ProcessBuilder 생성.
        ProcessBuilder pb = new ProcessBuilder(cmdLine);
        pb.redirectErrorStream(true);
        Process p = null;
       
        try { 
            // 프로세스 작업을 실행함.
            p = pb.start();
           
        } catch (Exception e) {         
            e.printStackTrace();
            p.destroy();
            return null; 
        }
        

        // 자식 프로세스에서 발생되는 인풋스트림 소비시킴;;
        exhaustInputStream(p.getInputStream());   
          
        try {

            // p의 자식 프로세스의 작업이 완료될 동안 p를 대기시킴
            p.waitFor();
        } catch (InterruptedException e) {
            p.destroy();
        }

 

         // 정상 종료가 되지 않았을 경우
        if (p.exitValue() != 0) {
         System.out.println("변환 중 에러 발생");
            return null;
        }

 

        // 변환을 하는 중 에러가 발생하여 파일의 크기가 0일 경우
        if (fResult.length() == 0) {
            System.out.println("변환된 파일의 사이즈가 0임");
            return null;
        }

 

        System.out.println("변환 성공 ^^");
        fOriginal.delete(); // 원본 파일 삭제
        return outputName;
 }
 
 private void exhaustInputStream(final InputStream is) {
  // InputStream.read() 에서 블럭상태에 빠지기 때문에 따로 쓰레드를 구현하여 스트림을

  // 소비한다.
  new Thread() {
   public void run() {
      try {
         BufferedReader br = new BufferedReader(new InputStreamReader(is));
         String cmd;
         while((cmd = br.readLine()) != null) { // 읽어들일 라인이 없을때까지 계속 반복
            System.out.println(cmd);
         }
      } catch(IOException e) {
         e.printStackTrace();
      }
   }
  }.start();
 }
}

 

 

동영상 컷팅 예제

 

* 0에서 100 초 단위로 동영상을 잘라 재생하고 싶다면 아래의 명령어를 사용한다.

  1.  C:\>ffmpeg -ss 0 -i inputfile.flv -t 100 -sameq outfile.flv

 

  • 옵션 설명

    • ss : 시작 offset을 세팅 (입력 값은 초 단위다. )
    • t : 지속시간 (역시 초단위)
    • sameq : 입출력파일간의 같은 퀄리티를 제공

 

 

그리고 UCC 사이트처럼 동영상의 대표 이미지를 추출하고 싶으면 이때는 -ss, vframes 옵션을 사용한다.

 

 

동영상 썸네일 추출 예제

  1. c:\>ffmpeg -i "ok.flv" -an -ss 00:00:01 -r 1 -vframes 1 -y "%d.jpg"

 

  •  옵션 설명

    • ss (time_off)        : 시작 offset을 세팅 - 언제부터 썸네일을 추출할 것인가?
    • vframes number     : 기록할 비디오 프레임의 숫자 - 몇장 뽑을 것인가?
    • y : 오버라이트를 할 것인가?

마지막 아웃풋 파일의 %d는 C 언어에서 사용하는 변환 문자 (decimal)와 비슷한 의미로 보면 된다.

%d.jpg 는 1.jpg로 생성된다. 그러니 관리를 위해서 인풋파일명_%2d 로 하는게 좋을 듯 싶다.

 

그리고 만약 UCC 사이트를 만들 계획이 있다면 하나의 썸네일 이미지만 추출하는 게 아니라

하나의 동영상에 대해서 여러개의 대표 이미지를 추출할 필요성이 있을 것이다.

 

이때 필요한 이미지가 10장이라고 치자. 그리고 위에 나와있는 -vframes 옵션을 이용하여

-vframes 10 이라는 옵션만 추가하여 명령어를 날렸다고 하자.

그런데 아마 원하는 결과는 얻을 순 없을 것이다. ( 세상은 그리 만만하지 않다.)

 

별 다른 옵션없이 -vframes 옵션만 사용한다면 추출된 10장의 이미지들이

단지 1프레임 간격으로 연달아 추출되버려서 대표라고 부르기에는 민망한 사진들만 나열될테니 말이다.

 

그래서 이를 위해 -r 옵션을 활용 해야 한다.  이 옵션은 ffmpeg 의 help 메뉴얼에서도 나왔다시피

  •  -r rate : set frame rate (Hz value, fraction or abbreviation - default 25)

처럼 프레임 레이트를 설정하는 것이다.

 

즉, 프레임 레이트를 강제로 낮춰서 이미지를 뽑아 내야 한다.

 

 

5초 단위로 썸네일 이미지 추출하기
  1. c:\> ffmpeg -i "ok.flv" -an -ss 00:00:00 -qscale 1 -r 0.2 -vframes 10 -y "%3d.png"

 

그런데 이상하게 위 명령어를 이용하여 다수의 썸네일 이미지를 추출하면 시간 간격으로 잘 뽑히긴 하나,
처음 1번째 2번째 사진만 거의 프레임의 변화가 없이 추출된다.. 이 부분을 해결해볼려고 노력해봤으나..

방법을 찾기는 어려웠고, 그냥 가장 첫번째 이미지는 자바 쪽에서 강제로 지우는 게 가장 속이 편할 거 같다.

Who's ADMINPLAY

profile

일요일은 짜빠게뤼~ 먹는날~^^