









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.
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.
Kita akan membutuhkan software-software berikut:
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.

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.
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.

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

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.
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.
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.
Anda suka artikel ini? Bantu website ini berkembang dengan menyumbang. Berapapun jumlahnya akan sangat dihargai.
Atau Anda dapat membantu dengan membuat bookmark.
Bookmark this on Delicious