Juhara.com
Language : English Indonesia

DirectShow Tips: Memisahkan Data Audio - Video dari file Movie (AVI)

Zamrony P Juhara
22 November 2006 15:26:00
 (8136 views)
Tutorial cara memisahkan data audio - video dari file movie khususnya file AVI menggunakan DirectShow

Pendahuluan

Saya punya file video klip sebuah lagu. Berhubung MP3 Player saya tidak sanggup memainkan file video, saya ingin memisahkan data audio pada file tersebut dan menyimpannya sebagai file MP3. Kemudian saya berpikir bagaimana caranya memisah data audio - video dari file movie. Setelah menggali dokumentasi DirectShow, akhirnya saya menemukan cara melakukannya. Khusus untuk artikel kali ini, saya hanya fokus ke file AVI.

Prasyarat

Artikel ini adalah sambungan artikel DirectShow Tips: Memprogram WAV - MP3 Converter. Kita akan menggunakan frame work yang sudah dibangun di artikel tersebut. Kita akan memperbaiki dan menambahkan fitur pada unit uDirectShowPlayer.pas. Jika anda belum membacanya, saya sarankan membacanya terlebih dahulu karena saya hanya akan menjelaskan cara menyusun filter graph pemisahan audio - video.

Software yang diperlukan

Kita akan membutuhkan software-software berikut:

  • DirectX versi 8 ke atas.
  • Konversi header DirectX. Anda bisa download di sini (DirectX 9) atau di sini (DirectX 8.1). Saya sendiri menggunakan header konversi DirectX 8.1.
  • Kompiler Delphi.
  • MP3 codec dan Indeo® video 5.10 codec. Indeo® video 5.10 codec sudah disertakan dalam instalasi XP. Untuk mengetahui apakah Indeo® video 5.10 codec sudah terinstall, klik Control Panel » Sounds and Audio Devices » Hardware tab » Di Devices, pilih Video Codecs dan klik tombol Properties. Pada window Video Codecs Properties, temukan Indeo® video 5.10. Jika anda tidak menemukannya, anda harus mendownload dan menginstallnya. Anda bisa menemukan codec ini di banyak website yang menawarkan download video codec.
  • Filter wavdest.ax. Filter ini sudah disertakan dalam download.

Filter Graph Pemisahan Audio - Video dari File AVI.

Untuk memisahkan data audio - video dari file AVI kita membutuhkan beberapa yakni filter File Source untuk membaca file AVI, filter AVI Splitter untuk memisahkan stream AVI menjadi audio stream dan video stream, filter AVI Decompressor untuk dekode video stream. Filter MPEG Layer-3 kita perlukan untuk mengkompress data audio menjadi data MP3. Outputnya kita kirim WAV Dest untuk kemudian diubah menjadi stream dan kemudian disimpan ke file menggunakan filter File Writer.

Filter graph pemisahan data audio - video dari file AVI

Gambar 1. Filter graph pemisahan data audio - video dari file AVI.

Output AVI Decompressor adalah data frame-frame video yang tidak terkompress yang ukurannya sangat besar, oleh karena itu kita lewatkan filter video compressor Indeo® video 5.10 untuk dikompress sebelum akhirnya dimasukkan ke AVI Mux filter. Filter AVI Mux adalah filter multiplexer yang mengabung dan melakukan sinkronisasi video dan audio. Pin Input 01 menerima data video, sedangkan pin Input 02 menerima data audio. Pin Input 02 kita kosongkan, karena kita menginginkan AVI Out hanya berisi data video tanpa audio. Stream AVI kemudian dikirim ke File Writer untuk disimpan ke file.

Desain

Kita akan mengenkapsulasi proses pemisahan ini dalam kelas TBasicAudioVideoExtractor diturunkan dari kelas TBasicPlayer. Kelas ini adalah kelas dasar untuk proses pemisahan audio - video. Khusus untuk AVI, kita enkapsulasi dalam kelas TAVIAudioVideoExtractor yang kita turunkan dari TBasicAudioVideoExtractor.

AVI splitter UML diagram

Gambar 2. UML diagram enkapsulasi pemisahan audio - video.

TBasicAudioVideoExtractor dilengkapi tiga property yakni SrcFilename, AudioFilename dan VideoFilename. Masing-masing adalah nama file sumber, nama file audio output dan nama file video output. Kita juga akan menambahkan metode Extract() yang membungkus pemanggilan metode BuildFilterGraph() dan Run().

Implementasi

Memperbaiki Bug pada Metode GetPin().

Pada metode TBasicPlayer.GetPin() sebelumnya terdapat bug yang mengganggu yang mengakibatkan proses mendapatkan pin pada suatu index tidak menghasilkan interface IPin yang diinginkan. Bug ini tidak terdeteksi pada demo sebelumnya karena pada demo sebelumnya, pin yang kita ambil selalu pin pertama (yang selalu mengembalikan pin yang benar). Ok, di bawah ini adalah GetPin() yang telah diperbaiki.

function TBasicPlayer.GetPin(aFilter: IBaseFilter;
  const PinDir: TPin_Direction; const indx: integer): IPin;
var pPins:IEnumPins;
    ctr:integer;
    aPin:IPin;
    CurPinDir:TPin_Direction;
begin
  result:=nil;
  //start pin enumeration
  aFilter.EnumPins(pPins);
  if pPins<>nil then
  begin
    //pin enumeration ok
    ctr:=0;
    while pPins.Next(1,aPin,nil)=S_OK do
    begin
      aPin.QueryDirection(curPinDir);
      if (curPinDir=PinDir) then
      begin
        if (ctr=indx) then
        begin
          result:=aPin;
          exit;
        end;
        inc(ctr);
      end
    end;
  end;
end;

Menambahkan Fitur Koneksi ke Pin Tertentu pada Metode ConnectFilter().

Pada ConnectFilter() yang lama, OutFilter dan InFilter selalu dihubungkan pada pin output pertama dan pin input pertama. ConnectFilter yang baru, kita modifikasi agar mampu menghubungkan pin output dengan pin input sesuai keinginan. Agar kode aplikasi lama tetap dapat dikompile tanpa perlu mengubah source code, OutIndx dan InIndx kita jadikan parameter dengan nilai default 0.

function TBasicPlayer.ConnectFilter(OutFilter,
  InFilter: IBaseFilter;
  const OutIndx:integer=0;
  const InIndx:integer=0): HResult;
var outpin,inPin:IPin;
begin
  outPin:=GetPin(OutFilter,PINDIR_OUTPUT,OutIndx);
  inPin:=GetPin(InFilter,PINDIR_INPUT,InIndx);
  result:=FFilterGraph.Connect(outPin,inPin);
end;

Implementasi TBasicAudioVideoExtractor

Kelas TBasicAudioVideoExtractor dideklarasi sebagai berikut:

     TBasicAudioVideoExtractor=class(TBasicPlayer)
     private
       FAudioFilename: string;
       FVideoFilename: string;
       FSrcFilename: string;
       procedure SetAudioFilename(const Value: string);
       procedure SetSrcFilename(const Value: string);
       procedure SetVideoFilename(const Value: string);
     published
       property SrcFilename:string read FSrcFilename write SetSrcFilename;
       property AudioFilename:string read FAudioFilename write SetAudioFilename;
       property VideoFilename:string read FVideoFilename write SetVideoFilename;
     public
       function Extract:boolean;
     end;

Kode implementasi lengkapnya adalah sebagai berikut:

{ TBasicAudioVideoExtractor }

function TBasicAudioVideoExtractor.Extract:boolean;
begin
  try
    BuildFilterGraph;
    Run;
    result:=true;
  except
    RemoveAllFilters;
    result:=false;
  end;
end;

procedure TBasicAudioVideoExtractor.SetAudioFilename(const Value: string);
begin
  FAudioFilename := Value;
end;

procedure TBasicAudioVideoExtractor.SetSrcFilename(const Value: string);
begin
  FSrcFilename := Value;
end;

procedure TBasicAudioVideoExtractor.SetVideoFilename(const Value: string);
begin
  FVideoFilename := Value;
end;

Implementasi TAVIAudioVideoExtractor.

Kita hanya meng-override BuildFilterGraph. Di metode ini kita isi dengan kode untuk menyusun filter graph proses pemisahan data audio - video dari file AVI.

     TAVIAudioVideoExtractor=class(TBasicAudioVideoExtractor)
     public
       procedure BuildFilterGraph;override;
     end;

Kode lengkap BuildFilterGraph adalah sebagai berikut:

procedure TAVIAudioVideoExtractor.BuildFilterGraph;
var pMP3FileSink,pAVIFileSink:IFileSinkFilter;
    pFileSource:IFileSourceFilter;

    afileReader,aAVISplitter,aWaveDest,
    aMPEGLayer3,
    aMP3FileWriter,
    aAVIFileWriter,
    aAVIMux,
    aAVIDecompressor,
    aAVICompressor:IBaseFilter;

    aSrcFilename,aDestFilename:widestring;

    hr:HResult;
begin
  if (FFilterGraph<>nil) then
  begin
    //add file reader filter
    afileReader:=AddFilterByCLSID(CLSID_AsyncReader,'File Reader');
    //add AVI splitter filter
    aAVISplitter:=AddFilterByCLSID(CLSID_AVISplitter,'AVI Splitter');

    //Add MPEG Layer 3 Encoder
    aMPEGLayer3:=FindFilterByFriendlyName(CLSID_AudioCompressorCategory,
                                         'MPEG Layer-3');
    FFilterGraph.AddFilter(aMPEGLayer3,'MPEG Layer-3');

    //add Wave Dest filter
    awaveDest:=AddFilterByCLSID(CLSID_WaveDest,'Wave Dest');
    //add MP3 file writer filter
    aMP3FileWriter:=AddFilterByCLSID(CLSID_FileWriter,'MP3 File Writer');
    //add AVI file writer filter
    aAVIFileWriter:=AddFilterByCLSID(CLSID_FileWriter,'AVI File Writer');
    //add AVI Mux filter
    aAVIMux:=AddFilterByCLSID(CLSID_AVIDest,'AVI Mux');

    //add AVI Decompressor filter
    aAVIDecompressor:=AddFilterByCLSID(CLSID_AVIDec,'AVI Decompressor');
    //Add Indeo video 5.10 Encoder
    aAVICompressor:=FindFilterByFriendlyName(CLSID_VideoCompressorCategory,
                                         'Indeo® video 5.10');
    FFilterGraph.AddFilter(aAVICompressor,'Indeo video 5.10');

    if (afileReader<>nil) and
       (aAVISplitter<>nil) and
       (aMPEGLayer3<>nil) and
       (aWaveDest<>nil) and
       (aMP3FileWriter<>nil) and
       (aAVIFileWriter<>nil) and
       (aAVIMux<>nil)  and
       (aAVICompressor<>nil) then
    begin
      //ambil instance IFileSourceFilter
      afileReader.QueryInterface(IID_IFileSourceFilter,pFileSource);
      if pFileSource<>nil then
      begin
        aSrcFilename:=FSrcFilename;
        pFileSource.Load(PWideChar(aSrcFilename),nil);
      end;

      //connect output pin file reader ke
      //input pin AVI splitter
	    hr:=ConnectFilter(aFileReader,aAVISplitter);
      if hr<>S_OK then
        RaiseDirectShowException(hr,'Koneksi File Reader ke AVI Splitter gagal. ');

      //connect output AVI splitter ke
      //input pin AVI Decompressor
      hr:=ConnectFilter(aAVISplitter,aAVIDecompressor);
      if hr<>S_OK then
        RaiseDirectShowException(hr,'Koneksi AVI Splitter ke AVI Decompressor. ');

      {--------begin video extractor----------}

      //connect output AVI Decompressor ke
      //input pin AVI Compressor
      hr:=ConnectFilter(aAVIDecompressor,aAVICompressor);
      if hr<>S_OK then
        RaiseDirectShowException(hr,'Koneksi AVI Decompressor ke AVI compressor gagal. ');

      //connect output AVI Compressor ke
      //input pin AVI AVI Mux
      hr:=ConnectFilter(aAVICompressor,aAVIMux);
      if hr<>S_OK then
        RaiseDirectShowException(hr,'Koneksi AVI compressor ke AVI Mux gagal. ');

      //ambil instance IFileSinkFilter
      aAVIFileWriter.QueryInterface(IID_IFileSinkFilter,pAVIFileSink);
      if pAVIFileSink<>nil then
      begin
        aDestFilename:=FVideoFilename;
        pAVIFileSink.SetFileName(PWideChar(aDestFilename),nil);
      end;

      //connect output AVI Mux ke
      //input pin AVI File Writer
      hr:=ConnectFilter(aAVIMux,aAVIFileWriter);
      if hr<>S_OK then
        RaiseDirectShowException(hr,'Koneksi AVI Mux ke AVI FileWriter gagal. ');

      {--------end video extractor----------}
      {--------begin audio extractor----------}

      //connect output AVI splitter ke
      //input pin MPEG Layer 3
      hr:=ConnectFilter(aAVISplitter,aMPEGLayer3,1,0);
      if hr<>S_OK then
        RaiseDirectShowException(hr,'Koneksi AVI Splitter ke MPEG Layer 3. ');


      //connect output MPEG Layer 3 ke
      //input pin Wave Dest
      hr:=ConnectFilter(aMPEGLayer3,aWaveDest);
      if hr<>S_OK then
        RaiseDirectShowException(hr,'Koneksi MPEG Layer 3 ke Wave Dest gagal. ');

      //ambil instance IFileSinkFilter
      aMP3fileWriter.QueryInterface(IID_IFileSinkFilter,pMP3FileSink);
      if pMP3FileSink<>nil then
      begin
        aDestFilename:=FAudioFilename;
        pMP3FileSink.SetFileName(PWideChar(aDestFilename),nil);
      end;

      //connect output wave Dest ke
      //input pin FileWriter
      hr:=ConnectFilter(aWaveDest,aMP3FileWriter);
      if hr<>S_OK then
        RaiseDirectShowException(hr,'Koneksi Wave Dest ke File Writer gagal. ');

      {--------end audio extractor----------}
    end;
  end;
end;

Anda bisa mempelajari cara kerjanya meggunakan komentar yang ada.

Desain Aplikasi Utama

User interface aplikasi

Gambar 3. User interface aplikasi.

Buat aplikasi baru dan drag drop kontrol ke form sehingga menjadi seperti screenshot di atas. Atur nama kontrol-kontrolnya dan tambahkan variabel bertipe TAVIAudioVideoExtractor.

Implementasi Aplikasi Utama.

Lengkapi kode event handler untuk kontrol-kontrol sehingga kodenya menjadi seperti berikut ini:

unit ufrmMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,DirectShow,uDirectShowPlayer, ComCtrls, ExtCtrls;

type
  TfrmMain = class(TForm)
    grpbxSource: TGroupBox;
    lblFilename: TLabel;
    edSourceFilename: TEdit;
    grpbxDestination: TGroupBox;
    edAudioFile: TEdit;
    edVideoFile: TEdit;
    lblAudioFile: TLabel;
    lblVideoFile: TLabel;
    btnBrowse: TButton;
    btnBrowseAudio: TButton;
    btnBrowseVideo: TButton;
    btnExtract: TButton;
    btnClose: TButton;
    OpenDialog1: TOpenDialog;
    SaveVideoDialog: TSaveDialog;
    SaveAudioDialog: TSaveDialog;
    ProgressBar1: TProgressBar;
    progressTime: TTimer;
    procedure btnExtractClick(Sender: TObject);
    procedure btnBrowseClick(Sender: TObject);
    procedure btnBrowseAudioClick(Sender: TObject);
    procedure btnBrowseVideoClick(Sender: TObject);
    procedure btnCloseClick(Sender: TObject);
    procedure progressTimeTimer(Sender: TObject);
  private
    extractor:TAVIAudioVideoExtractor;
    { Private declarations }
    procedure WM_MMNotify(var msg: TMessage);message WM_MMNOTIFY;
  public
    constructor Create(AOwner:TComponent);override;
    destructor Destroy;override;
    { Public declarations }
  end;

var
  frmMain: TfrmMain;

implementation


{$R *.dfm}

{ TForm1 }

constructor TfrmMain.Create(AOwner: TComponent);
begin
  inherited;
  extractor:=TAVIAudioVideoExtractor.Create;
  extractor.Handle:=Handle;
end;

destructor TfrmMain.Destroy;
begin
  extractor.Free;
  inherited;
end;

procedure TfrmMain.WM_MMNotify(var msg: TMessage);
var aplayer:TBasicPlayer;
    evCode,param1,param2:integer;
begin
  aplayer:=TBasicPlayer(msg.LParam);
  aplayer.EventObj.GetEvent(evCode,param1,param2,0);
  case evCode of
    EC_COMPLETE:begin
                  aplayer.RemoveAllFilters;
                  btnExtract.Enabled:=true;
                  progressbar1.Position:=0;
                  progressTime.Enabled:=false;
                end;
  end;
  aplayer.EventObj.FreeEventParams(evCode,param1,param2);
end;

procedure TfrmMain.btnExtractClick(Sender: TObject);
begin
  btnExtract.Enabled:=false;

  extractor.SrcFilename:=edSourceFilename.Text;
  extractor.AudioFilename:=edAudioFile.Text;
  extractor.VideoFilename:=edVideoFile.Text;

  if (extractor.Extract) then
  begin
    progressbar1.Max:=extractor.Duration;
    progressbar1.Min:=0;
    progressbar1.Position:=0;
    progressTime.Enabled:=true;
  end else
    btnExtract.Enabled:=true;
end;

procedure TfrmMain.btnBrowseClick(Sender: TObject);
begin
  if OpenDialog1.Execute then
    edSourceFilename.Text:=OpenDialog1.FileName;
end;

procedure TfrmMain.btnBrowseAudioClick(Sender: TObject);
begin
  if SaveAudioDialog.Execute then
    edAudioFile.Text:=SaveAudioDialog.FileName;
end;

procedure TfrmMain.btnBrowseVideoClick(Sender: TObject);
begin
  if SaveVideoDialog.Execute then
    edVideoFile.Text:=SaveVideoDialog.FileName;
end;

procedure TfrmMain.btnCloseClick(Sender: TObject);
begin
  Close;
end;

procedure TfrmMain.progressTimeTimer(Sender: TObject);
begin
  progressbar1.Position:=extractor.Position.Current;
end;

end.

Untuk mendownload source code demo, silakan klik di sini.

Ringkasan

Di artikel ini kita telah membahas cara menyusun filter graph untuk proses pemisahan audio - video dari file AVI menggunakan DirectShow. Kita juga telah membuat implementasi kelas pembungkus proses pemisahan audio - video yang menjadikan proses ini menjadi mudah dilakukan.

Artikel Terkait

Anda suka artikel ini? Bantu website ini berkembang dengan menyumbang. Berapapun jumlahnya akan sangat dihargai.

Atau Anda dapat membantu dengan membuat bookmark. Delicious Bookmark this on Delicious