FFMPEG.EXE 프로그램은 동영상을 엔코딩/디코딩 및 변환 기능을 지원하는 프로그램입니다. (홈페이지 http://ffmpeg.mplayerhq.hu/ ) 프로그램에서 AVI 파일을 변환할때, x/h264 형식의 비디오 스트림을 DEMUX 할때, 그리고 오디오를 직접 AAC 형식으로 변환할때 사용하고 있습니다.

일반적인 프로그램에서는 STDOUT 으로 각종 화면 메시지를 출력하지만, FFMPEG 프로그램의 경우에는 STDERR 를 이용해서 메시지를 출력합니다. 사실, 필자도 이 부분 때문에 한참을 삽질을 해야 했습니다. T.T

이전 글에서 단순하게 Framework 에서 Process 를 생성해서 외부 프로그램을 실행하고 화면을 캡춰하였습니다. 이번에는 몇가지 다른 방법을 사용하고자 합니다.

   (1) 프로세스를 생성해서 외부 프로그램을 실행합니다. 
   (2) 외부 프로그램 동작 상태를 확인하고 제어하는 방법으로는 Thead를 생성하여 제어합니다.
   (3) 클래스 루틴이 실행될때, 메인 프로그램으로 메시지를 피드백하기 위해서, 특정 함수를 Callback 루틴으로 지정하여 메시지를 출력합니다.

화면 출력을 지원하고 메인 루틴이 HOLDING 되어 화면이 깨지는 것을 방지하기 위해서, 다음과 같이 두개의 Delegate 함수를 선언합니다. 그리고 함수를 연결하기 위한 함수를 추가합니다.

    Public Delegate Sub __STDOUT(ByVal Text As String)
    Private STDOUT As __STDOUT = Nothing

    Public Delegate Sub __DOEVENTS()
    Private DOEVENTS As __DOEVENTS = Nothing

    Public Sub __CallBack(ByVal Message As __STDOUT)
        STDOUT = Message
    End Sub
    Public Sub __CallBack(ByVal FormEventProcess As __DOEVENTS)
        DOEVENTS = FormEventProcess
    End Sub
    Public Sub __CallBack(ByVal Message As __STDOUT, ByVal FormEventProcess As __DOEVENTS)
        STDOUT = Message
        DOEVENTS = FormEventProcess
    End Sub

이렇게 선언만으로 프로그램이 실해되는 것은 아닙니다. 클래스를 호출할 프로그램에 다음의 예제와 같은 두가지의 루틴을 만들어서 이것을 연결해줘야합니다.

    Sub MyMessage(ByVal Text as Stinrg)
         '
         ' 여기에 문자열을 받아서 가공 처리합니다. 
         ' 
   End Sub
 
   Sub MyDoEvents()
         '
         ' 클래스를 호출할때, 메인 프로그램 제어(이벤트처리)를 계속 할 수 있도록 만든 함수 입니다.
         '
         Application.DoEvents()
   End Sub

클래스를 연결하고, 위의 두개의 함수를 클래스로 연결해 줍니다.  이때, 함수는 AddressOf 함수를 이용하여 함수의 포인터를 전달해주게 됩니다.

    Dim MyClass As New CML.FFMPEG
   
    MyClass.__CallBack(AddressOf MyMessage, AddressOf MyDoEvents)

이제, Thread 를 이용하기 위해서, Thread Server 함수를 만듭니다. 이 함수에는 프로세스를 생성하여 외부 프로그램을 실행하고, 프로세스가 종료할때 까지 대기하는 함수입니다. 
   
   Private varStop As Boolean = False                                 ' Thread 를 종료하기 위한 변수
   Private varDone As Boolean = False                               ' Thread 의 종료를 확인하기 위한 변수
   Private FFMPEG_THREAD As Threading.Thread

    Private Sub Server()

        If __Executable.Trim = String.Empty Then
            Exit Sub
        End If

        varStop = False
        varDone = False

        Dim MyStartInfo As New Diagnostics.ProcessStartInfo(__Executable, varArguments)
        MyStartInfo.CreateNoWindow = True
        MyStartInfo.UseShellExecute = False
        MyStartInfo.RedirectStandardError = True

        Dim MyProcess As New Diagnostics.Process
        MyProcess.StartInfo = MyStartInfo
        MyProcess.Start()

        Dim SR As StreamReader = MyProcess.StandardError   ' FFMPEG.EXE 의 경우에 StandardError 를 사용합니다.
 
        While ((Not MyProcess.HasExited) And (Not varStop))

            If Not STDOUT Is Nothing Then
                STDOUT(MyProcess.StandardError.ReadLine)  ' 메시지를 메인 프로그램에 전달하기 위한 CallBack 입니다.
            End If
           
        End While

        varDone = True

    End Sub


    Private Sub Server_Start()
        ' 
        '  아래 두 변수를 초기화 하는 것이 불필요할 것 같은데, 이렇게 하지 않으면 바로 연결되는 두번째 Thread 가
        '  정상적으로 동작하지 않습니다.
        '
        varDone = False
        varStop = False

        FFMPEG_THREAD = New Threading.Thread(New Threading.ThreadStart(AddressOf Server))
        FFMPEG_THREAD.Start()

    End Sub

이제, Server Start 함수를 마음대로 호출 할 수 있습니다.  아래 두개의 호출의 예가 있습니다.

   Public Sub __DEMUX_VIDEO_TO_RAW(ByVal SourceFilename As String, ByVal OutputFilename As String, Optional ByVal Wait As Boolean = False, Optional ByVal Delay As Double = Double.MaxValue)
        Me.__Arguments = " -y -i """ & SourceFilename & """ -vcodec copy -f rawvideo -an """ & OutputFilename & """"
        Me.Server_Start()
        If Wait Then
            __Wait(Delay)
        End If
    End Sub

    Public Sub __DEMUX_AUDIO_TO_AAC(ByVal SourceFilename As String, ByVal OutputFilename As String, Optional ByVal BitRate As Integer = 96000, Optional ByVal Wait As Boolean = False, Optional ByVal Delay As Double = Double.MaxValue)
        Me.__Arguments = " -y -i """ & SourceFilename & """  -f aac -acodec libfaac -ab " & BitRate & " """ & OutputFilename & """"
        Me.Server_Start()
        If Wait Then
            __Wait(Delay)
        End If
    End Sub


이처럼 Thread 로 호출함으로 해서 생기는 장점은 멀티 코어에서 CPU를 100% 완전히 사용할 수 있고, 클래스 프로그램이 동작 하는 동안에 메인 루틴을 잠시 대기해야하는 필요성이 없기 때문에, 멀티 프로세싱이 가능하겠죠.

예를 들어 클래스에서 음성을 인코딩 하는 동안에 메인 루틴에서는 클래스에서 전달받은 메시지를 해석해서 화면에 진행 상황을 뿌려주는 작업을 할 수 있습니다.

***** MP4TE 0.4.0 : AVI DEMUX 및 AUDIO CONVERT 에 사용하고 있습니다.