Di artikel kali ini kita akan memanfaatkan DirectShow untuk melakukan konversi format file WAV menjadi MP3 dan juga sebaliknya. Di artikel ini anda juga akan belajar bagaimana menyusun filter graph secara manual meliputi cara menambahkan filter ke filter graph dan menghubungkan sebuah filter dengan filter lain.
Anda diharapkan telah memiliki pengetahuan mengenai apa itu DirectShow dan bagaimana menggunakan DirectShow dengan Delphi. Anda bisa membaca artikel saya Multimedia Player dengan DirectShow Part 1 dan Multimedia Player dengan DirectShow Part 2 sebagai referensi awal. Anda juga diharapkan telah memiliki pengetahuan mengenai COM programming, karena kita akan banyak bergelut dengan COM. Mengapa? Karena DirectX (termasuk komponen-komponen penyusunnya) dibangun menggunakan COM
Kita akan membutuhkan software-software berikut:
DirectShow adalah arsitektur untuk multimedia streaming pada Windows. Penyusun utama DirectShow adalah software yang disebut filter. Filter adalah COM server yang mengerjakan operasi pada multimedia stream data dan dienkaspulasi dalam interface IBaseFilter. Filter melakukan banyak hal, mulai dari membaca data media dari media penyimpan, melakukan dekompresi data, melakukan kompresi data, memanipulasi data, menulis data media ke media penyimpan dan lain-lain.
Filter mengerjakan operasi pada data, oleh karena itu filter dapat membutuhkan input maupun menghasilkan output. Sebuah filter paling tidak memiliki satu input atau output, dimana jumlah input maupun output tidak dibatasi. Satu filter dapat saja memerlukan dua input dan menghasilkan satu output. Input dan output pada filter diistilahkan dengan pin.
Sebuah filter umumnya hanya melakukan pekerjaan yang spesifik, misalnya meload data dari file. Untuk bisa mengerjakan sesuatu yang lebih kompleks, filter-filter dapat saling dihubungkan melalui pin-pin-nya. Agar pin dapat dihubungkan pin input harus dihubungkan dengan pin output dan kedua pin harus sepakat dengan tipe media yang ditransfer antara kedua pin. Bagaimana cara mengkoneksi pin-pin akan kita bahas segera. Contohnya adalah seperti pada diagram berikut ini:

Gbr.1 Filter graph WAV playback
Untuk memainkan file WAV ke speaker, pertama-tama filter File Source diset dengan nama file yang akan dibaca. Output pin nya yang berupa data audio stream dihubungkan dengan filter Wave Parser yang bertugas memisahkan data menjadi data audio yang siap dimainkan oleh WaveOut Renderer. Filter WaveOut Renderer bertugas memainkan data audio ke speaker. Perhatikan bahwa File Source hanya memiliki satu output tanpa input. Filter Wave Parser memiliki satu input dan satu output. Sedangkan WaveOut Renderer memiliki satu pin input.
Filter-filter umumnya harus dihubungkan secara downstream yakni dari source ke destination. Jadi pada tiga filter di atas. File Source harus dihubungkan dengan Wave Parser, baru kemudian Wave Parser dapat dihubungkan dengan WaveOut Renderer.
Untuk mengubah file WAV ke MP3 kita membutuhkan susunan filter seperti berikut:

Gbr.2 Filter graph untuk konversi WAV ke MP3.
Pada beberapa file WAV yang waveformatnya telah diketahui, Wave Parser dapat langsung dihubungkan dengan filter MPEG Layer-3. Untuk file thud.wav yang saya gunakan, waveformatnya tidak standar, Wave Parser harus dihubungkan dengan filter perantara yang mampu mengkonversi waveformat menjadi waveformat yang mampu dihandle MPEG Layer-3. WAV Dest bertugas mengubah data audio ke stream sehingga selanjutnya dapat disimpan ke file oleh File Writer. WAV Dest bukan filter standar DirectShow. Filter ini adalah filter sample yang disertakan dalam DirectX SDK. Untuk mudahnya filter (wavdest.ax) ini juga saya sertakan dalam download. Anda harus meregister COM servernya dengan mengeksekusi RegSvr32 wavdest.ax atau dengan menjalankan file batch regwavedest.bat.
Untuk konversi MP3 ke WAV kita memerlukan susunan filter graph seperti berikut ini:

Gbr.3 Filter graph untuk konversi MP3 ke WAV.
Untuk mendapatkan instance filter, kita bisa menggunakan CoCreateInstance()
CoCreateInstance(clsid,
nil,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
afilter);
di mana variabel clsid berisi GUID pengenal filter dan aFilter bertipe IBaseFilter.
Beberapa filter tidak dapat di inisialisasi langsung menggunakan CoCreateInstance(), contohnya filter-filter compressor. Dari pengalaman saya, dengan CoCreateInstance, filter-filter tersebut akan sukses diciptakan, namun tidak melakukan encoding sebagaimana yang diharapkan. Untuk filter-filter kompressor kita akan mendapatkan instancenya melalui enumerasi terlebih dahulu.
Untuk melakukan enumerasi filter-filter yang terinstall di sistem, kita harus menciptakan system device enumerator bertipe ICreateDevEnum menggunakan fungsi CoCreateInstance. System device enumerator menggunakan CLSID_SystemDeviceEnum sebagai pengenalnya.
Jika sukses, kita buat class enumerator menggunakan metode CreateClassEnumerator dengan GUID class kita isikan dari parameter classid. Untuk audio compressor, kita bisa menggunakan CLSID_AudioCompressorCategory. Parameter kedua adalah variabel yang akan menerima moniker hasil enumerasi. Parameter ketiga adalah tipe filter yang hendak kita enumerasi. Jika diisi nol, maka kita mengenumerasi semua filter.
function FindFilterByFriendlyName(const classid:TGUID;
const friendlyname: string): IBaseFilter;
var sysEnum:ICreateDevEnum;
enumMoniker:IEnumMoniker;
moniker:IMoniker;
propBag:IPropertyBag;
hr:HRESULT;
afriendlyName:OleVariant;
propName,aname:widestring;
begin
result:=nil;
CoCreateInstance(CLSID_SystemDeviceEnum,
nil,
CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum,
sysenum);
if sysEnum<>nil then
begin
aname:=FriendlyName;
propName:='FriendlyName';
hr:=sysEnum.CreateClassEnumerator(classid,
enumMoniker,0);
if hr=S_OK then
begin
while enumMoniker.Next(1,moniker,nil)=S_OK do
begin
hr:=moniker.BindToStorage(nil,nil,IPropertyBag,propBag);
if succeeded(hr) then
begin
propBag.Read(PWideChar(propName),
afriendlyName,nil);
if afriendlyName=aname then
begin
moniker.BindToObject(nil,nil,
IID_IBaseFilter,
result);
exit;
end;
end;
end;
end else
RaiseDirectshowException(hr,'Create Class Enumerator failed');
end;
end;
Selanjutnya dengan Next(), kita ambil moniker untuk tiap filter. Parameter pertama adalah jumlah moniker yang hendak diambil. Kita isi dengan 1 untuk mengambil moniker satu demi satu. Parameter kedua verisi variabel yang akan menampung moniker. Parameter ketiga adalah jumlah moniker yang diambil. Kita isi dengan nil karena tidak kita perlukan. Fungsi Next() menghasilkan nilai kembali S_OK bila moniker sukses diambil.
Jika sukses, kita ambil nama FriendlyName filter untuk kita bandingkan dengan nama yang dimasukkan lewat parameter. Untuk mengambil FriendlyName kita menggunakan BindToStorage(). Parameter pertama dan kedua masing-masing adalah context binding dan moniker sebelumnya. Kedua parameter ini tidak relevan dan bisa kita isi dengan nil. Parameter ketiga BindToStorage adalakah GUID interface yang ingin kita minta. Parameter keempat menampung pointer ke instance IPropertyBag.
Dari propBag, kita baca isinya untuk mendapatkan isi FriendlyName menggunakan Read(). Kita harus memastikan bahwa nama menggunakan tipe widestring, karena COM menggunakan widestring. Hasilnya kita bandingkan, jika namanya sama, maka moniker yang sedang kita proses menyimpan filter yang kita cari. Instance filter kita ambil menggunakan BindToObject() milik moniker. Parameter BindToObject() sama dengan BindToStorage().
Filter ditambahkan ke filter graph menggunakan fungsi AddFilter() milik IFilterGraph.
FFilterGraph.AddFilter(afilter,PWideChar(FilterName));
afilter adalah instance IBaseFilter yang hendak ditambahkan. Parameter kedua adalah teks nama filter, bertipe PWidechar.
Kita membutuhkan informasi pin untuk dapat menghubungkan filter. Untuk mendapatkan pin sebuah filter, kita lakukan enumerasi pin-pin yang ada pada sebuah filter. Interface IBaseFilter dilengkapi dengan fungsi EnumPins() untuk enumerasi pin. Parameter fungsi ini hanya satu yakni variabel yang akan menampung instance IEnumPins.
function 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 (ctr=indx) and (curPinDir=PinDir) then
begin
result:=aPin;
exit;
end;
end;
end;
end;
Jika EnumPins berhasil dilakukan, pPins akan berisi pointer ke intance IEnumPins. Dengan Next() kita ambil satu demi satu pin yang ada pada filter.Jika sebuah pin berhasil kita dapatkan instancenya, kita ambil pin direction, apakah pin tersebut input pin atau output pin. Selanjutnya kita bandingkan dengan pin direction yang kita lewatkan parameter juga apakah indeks pin sama dengan indeks yang hendak kita ambil.
Untuk menghubungkan dua filter kita perlu mengetahui pin-pin yang hendak kita hubungkan, menggunakan GetPin di atas. Ada dua cara menghubungkan dua buah filter, yakni menggunakan Connect milik IPin atau Connect milik IGraphBuilder. Fungsi Connect yang pertama adalah direct connection, dimana bila kedua pin sepakat dengan tipe media yang mampu dihandle, baru koneksi dapat dibentuk. Jika tidak maka, koneksi gagal. Connect milik IGraphBuilder, sedikit berbeda. Jika kedua pin tidak memiliki tipe media yang cocok, IGraphBuilder akan mencoba mengkoneksi filter lain yang mampu mengubah input pin menjadi output pin. Jika tidak ditemukan filter perantara yang cocok, koneksi akan gagal. Di aplikasi kita kita akan menggunakan Connect milik IGraphBuilder.
function ConnectFilter(OutFilter,
InFilter: IBaseFilter): HResult;
var outpin,inPin:IPin;
begin
outPin:=GetPin(OutFilter,PINDIR_OUTPUT,0);
inPin:=GetPin(InFilter,PINDIR_INPUT,0);
result:=FFilterGraph.Connect(outPin,inPin);
end;
Kita ambil pin input filter pertama dan menghubungkannya dengan pin output filter kedua. Lalu kita panggil Connect() untuk menghubungkan kedua filter.
Proses konversi akan kita pisahkan dalam dua kelas bernama TWAVToMP3Converter untuk konversi WAV ke MP3 dan TMP3ToWAVConverter untuk konversi MP3 ke WAV. Kita akan menurunkan kedua kelas ini dari kelas TBasicConverter yang merupakan turunan TBasicPlayer. Kelas TBasicPlayer adalah kelas dasar mutimdia player yang sudah kita kembangkan pada artikel Multimedia Player dengan DirectShow Part 1 dan Multimedia Player dengan DirectShow Part 2. Berikut ini adalah diagram UML kelas untuk konversi WAV - MP3.

Gbr.4 UML Diagram untuk konversi WAV - MP3
TBasicPlayer akan kita modifikasi dengan menambahkan metode-metode protected yang berguna untuk proses koneksi filter (ConnectFilter), mendapatkan pin sebuah filter (GetPin), dan mendapatkan filter melalui enumerasi (FindFilterByName) dan menambahkan filter ke dalam filter graph menggunakan CLSID filter (AddFilterByCLSID).
TBasicConverter akan dilengkapi dengan dua properti yakni SrcFilename dan DstFilename, masing-masing untuk menampung nama file sumber dan nama file target konversi. Kelas ini juga akan ditambahkan metode Convert yang isinya adalah proses konstruksi filter graph dan menjalankan proses konversi.
Pada kelas TWAVToMP3Converter, metode virtual abstract BuildFilterGraph kita override. Di sinilah kita susun filter-filter yang diperlukan untuk konversi WAV ke MP3.
Pada kelas TMP3ToWAVConverter, metode virtual BuildFilterGraph juga kita override untuk mengerjakan penyusunan filter graph konversi MP3 ke WAV.
TBasicConverter=class(TBasicPlayer)
private
FSrcFilename: string;
FDstFilename: string;
procedure SetDstFilename(const Value: string);
procedure SetSrcFilename(const Value: string);
protected
public
procedure Convert;
published
property SrcFilename:string read FSrcFilename write SetSrcFilename;
property DstFilename:string read FDstFilename write SetDstFilename;
end;
TWAVToMP3Converter=class(TBasicConverter)
public
procedure BuildFilterGraph;override;
end;
TMP3ToWAVConverter=class(TBasicConverter)
public
procedure BuildFilterGraph;override;
end;
TBasicPlayer menjadi seperti berikut:
TBasicPlayer=class(TObject)
private
...
protected
...
function GetPin(aFilter:IBaseFilter;
const PinDir:TPin_Direction;
const indx:integer):IPin;
function ConnectFilter(OutFilter,InFilter:IBaseFilter):HResult;
function AddFilterByCLSID(clsid:TGUID;const name:string):IBaseFilter;
function FindFilterByFriendlyName(const friendlyname:string):IBaseFilter;
...
end;
{ TBasicConverter }
procedure TBasicConverter.Convert;
begin
BuildFilterGraph;
Run;
end;
procedure TBasicConverter.SetDstFilename(const Value: string);
begin
FDstFilename := Value;
end;
procedure TBasicConverter.SetSrcFilename(const Value: string);
begin
FSrcFilename := Value;
end;
Convert pada dasarnya hanya pembungkus untuk dua metode yakni BuildFilterGraph dan Run yang bertugas menjalankan proses encoding.
Secara umum, menggunakan komentar yang ada, saya rasa alur code cukup jelas. Mungkin yang perlu saya jelaskan adalah cara mengatur filter File Source untuk membaca file yang hendak kita proses.Filter File Source yang alamatnya disimpan dalam aFileReader masih bertipe IBaseFilter. Agar kita bisa mengatur nama file sumber kita perlu mendapatkan interface IFileSourceFilter.Selanjutnya menggunakan metode Load(), kita load file. Parameter pertama adalah nama file bertipe PWideChar dan kedua playlist yang disini tidak kita pergunakan sehingga kita isi nil.
Khusus untuk filter MPEG Layer-3 kita ambil instancenya menggunakan FindFilterByName().
{ TWAVToMP3Converter }
procedure TWAVToMP3Converter.BuildFilterGraph;
var pFileSink:IFileSinkFilter;
pFileSource:IFileSourceFilter;
afileReader,awaveParser,aWaveDest,
aMPEGLayer3,
aFileWriter:IBaseFilter;
aSrcFilename,aDestFilename:widestring;
hr:HResult;
begin
if (FFilterGraph<>nil) then
begin
//add file reader filter
afileReader:=AddFilterByCLSID(CLSID_AsyncReader,'File Reader');
//add Wave parser filter
aWaveParser:=AddFilterByCLSID(CLSID_WaveParser,'Wave Parser');
//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 file writer filter
afileWriter:=AddFilterByCLSID(CLSID_FileWriter,'File Writer');
if (afileReader<>nil) and
(awaveParser<>nil) and
(aMPEGLayer3<>nil) and
(aWaveDest<>nil) and
(aFileWriter<>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 Wave Parser
hr:=ConnectFilter(aFileReader,aWaveParser);
if hr<>S_OK then
RaiseDirectShowException(hr,'Koneksi File Reader ke MPEG-1 Splitter gagal. ');
//connect output Wave Parser ke
//input pin MPEG Layer 3
hr:=ConnectFilter(aWaveParser,aMPEGLayer3);
if hr<>S_OK then
RaiseDirectShowException(hr,'Koneksi Wave Parser 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
afileWriter.QueryInterface(IID_IFileSinkFilter,pFileSink);
if pFileSink<>nil then
begin
aDestFilename:=FDstFilename;
pFileSink.SetFileName(PWideChar(aDestFilename),nil);
end;
//connect output wave Dest ke
//input pin FileWriter
hr:=ConnectFilter(aWaveDest,aFileWriter);
if hr<>S_OK then
RaiseDirectShowException(hr,'Koneksi Wave Dest ke File Writer gagal. ');
end;
end;
end;
Cara yang hampir sama kita lakukan untuk filter File Writer, kecuali bahwa yang kita ambil instancenya adalah interface IFileSinkFilter. Nama file target kita set dengan menggunakan fungsi SetFilename(). Parameter pertama adalah nama file dan kedua adalah playlist.
Cara yang hampir serupa kita lakukan untuk konversiMP3 ke WAV. Yang berbeda hanyalah susunan filter-filternya saja.
{ TMP3ToWAVConverter }
procedure TMP3ToWAVConverter.BuildFilterGraph;
var pFileSink:IFileSinkFilter;
pFileSource:IFileSourceFilter;
afileReader,aMPEG1Splitter,aWaveDest,
aMPEGLayer3,
aFileWriter:IBaseFilter;
aSrcFilename,aDestFilename:widestring;
hr:HResult;
begin
if (FFilterGraph<>nil) then
begin
//add file reader filter
afileReader:=AddFilterByCLSID(CLSID_AsyncReader,'File Reader');
//add MPEG-1 Stream Splitter filter
aMPEG1Splitter:=AddFilterByCLSID(CLSID_MPEG1Splitter,'MPEG-1 Stream Splitter');
//Add MPEG Layer 3 Decoder
aMPEGLayer3:=AddFilterByCLSID(CLSID_MPEGLayer3Decoder,'MPEG Layer-3 Decoder');
//add Wave Dest filter
awaveDest:=AddFilterByCLSID(CLSID_WaveDest,'Wave Dest');
//add file writer filter
afileWriter:=AddFilterByCLSID(CLSID_FileWriter,'File Writer');
if (afileReader<>nil) and
(aMPEG1Splitter<>nil) and
(aMPEGLayer3<>nil) and
(aWaveDest<>nil) and
(aFileWriter<>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 MPEG-1 Splitter
hr:=ConnectFilter(aFileReader,aMPEG1Splitter);
if hr<>S_OK then
RaiseDirectShowException(hr,'Koneksi File Reader ke MPEG-1 Splitter gagal. ');
//connect output MPEG-1 Splitter ke
//input pin MPEG Layer 3 Decoder
hr:=ConnectFilter(aMPEG1Splitter,aMPEGLayer3);
if hr<>S_OK then
RaiseDirectShowException(hr,'Koneksi MPEG-1 Splitter ke MPEG Layer 3 gagal. ');
//connect output MPEG Layer 3 Decoder 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
afileWriter.QueryInterface(IID_IFileSinkFilter,pFileSink);
if pFileSink<>nil then
begin
aDestFilename:=FDstFilename;
pFileSink.SetFileName(PWideChar(aDestFilename),nil);
end;
//connect output wave Dest ke
//input pin FileWriter
hr:=ConnectFilter(aWaveDest,aFileWriter);
if hr<>S_OK then
RaiseDirectShowException(hr,'Koneksi Wave Dest ke File Writer gagal. ');
end;
end;
end;
Ok, sekarang kita bahas implementasi aplikasi utamanya. Buat aplikasi barudan drag drop kontrol sehingga menjadi seperi berikut ini:

Gbr.5 Design Form Utama.
Kode lengkap aplikasi utama adalah sebagai berikut:
unit ufrmMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls,
directshow,uDirectShowPlayer, ComCtrls;
type
TfrmMain = class(TForm)
Panel1: TPanel;
GroupBox1: TGroupBox;
rdgrpConvertType: TRadioGroup;
edSrcFilename: TEdit;
edDestFilename: TEdit;
btnSrcBrowse: TButton;
btnDestBrowse: TButton;
Label1: TLabel;
Label2: TLabel;
btnConvert: TButton;
btnClose: TButton;
OpenDialog1: TOpenDialog;
SaveDialog1: TSaveDialog;
procedure btnSrcBrowseClick(Sender: TObject);
procedure btnDestBrowseClick(Sender: TObject);
procedure btnConvertClick(Sender: TObject);
procedure btnCloseClick(Sender: TObject);
private
mp3ToWavConverter:TMP3ToWAVConverter;
WavToMP3Converter:TWAVToMP3Converter;
{ Private declarations }
procedure WavToMP3(const srcfile,dstfile:string);
procedure MP3ToWav(const srcfile,dstfile:string);
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}
procedure TfrmMain.btnSrcBrowseClick(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
edSrcFilename.Text:=OpenDialog1.FileName;
end;
end;
procedure TfrmMain.btnDestBrowseClick(Sender: TObject);
begin
if SaveDialog1.Execute then
begin
edDestFilename.Text:=SaveDialog1.FileName;
end;
end;
procedure TfrmMain.btnConvertClick(Sender: TObject);
begin
case rdgrpConvertType.ItemIndex of
0:WavToMP3(edSrcFilename.Text,edDestFilename.Text);
1:MP3ToWav(edSrcFilename.Text,edDestFilename.Text);
end;
end;
procedure TfrmMain.btnCloseClick(Sender: TObject);
begin
Close;
end;
procedure TfrmMain.MP3ToWav(const srcfile, dstfile: string);
begin
mp3toWavconverter.SrcFilename:=srcfile;
mp3toWavconverter.DstFilename:=dstfile;
mp3toWavconverter.Convert;
end;
procedure TfrmMain.WavToMP3(const srcfile, dstfile: string);
begin
WavToMp3converter.SrcFilename:=srcfile;
WavToMp3converter.DstFilename:=dstfile;
WavToMp3converter.Convert;
end;
constructor TfrmMain.Create(AOwner: TComponent);
begin
inherited;
mp3ToWavconverter:=TMP3ToWavConverter.Create;
mp3ToWavConverter.Handle:=Handle;
WavToMp3converter:=TWavToMP3Converter.Create;
WavToMp3Converter.Handle:=Handle;
end;
destructor TfrmMain.destroy;
begin
mp3ToWAVConverter.Free;
WAVToMp3Converter.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;
end;
end;
aplayer.EventObj.FreeEventParams(evCode,param1,param2);
end;
end.
Saya hanya akan menjelaskan tentang metode WM_MMNotify. Metode ini akan dipanggil ketika DirectShow perlu memberitahukan sesuatu ke aplikasi kita. Kita perlu mengecek apakah proses konversi telah selesai untuk membuang semua filter yang sudah kita susun, karena tiap kali Convert dipanggil, BuildFilterGraph akan dijalankan dan menambahkan filter-filter ke filter graph. Jika tidak dibuang, filter akan terus terduplikasi dalam filter graph.
Untuk mendownload source code klik di sini.
Kita telah membahas bagaimana melakukan konversi WAV ke MP3 dan sebaliknya. Kita juga telah membahas bagaimana menyusun flter graph secara manual dengan menghubungkan filter-filter satu demi satu.
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