Sebenarnya untuk merekam suara TMediaPlayer sudah bisa melakukannya, caranya pun sederhana. Contohnya:
FMediaPlayer.Filename:='test.wav';
FMediaPlayer.DeviceType:=dtWaveAudio;
FMediaPlayer.StartRecording;
tapi kita tidak memiliki akses ke data audio yang saat ini sedang direkam, sehingga jika anda berniat mengembangkan aplikasi sound recorder multitrack seperti cakewalk, TMediaPlayer jelas bukan opsi. Mengapa? karena TMediaPlayer dibangun menggunakan MCI (Media Control Interface). MCI adalah interface generic untuk proses memainkan device multimedia dan merekam file multimedia.
Untuk merekam audio di Windows, kita bisa menggunakan DirectX atau Multimedia API bawaan Windows. Artikel ini akan menjelaskan bagaimana melakukan recording menggunakan Multimedia API fungsi-fungsi wavein*** yang ada pada Multimedia API Windows.
Source kode bisa didownload di sini
Pada Delphi, fungsi-fungsi multimedia API dideklarasi di unit mmsystem.pas. (Sebenarnya cukup banyak artikel mengenai topik ini cuma kebanyakan dalam bahasa inggris)
Untuk bisa menggunakan sound card guna melakukan perekaman kita perlu membuka device untuk recorder. Fungsi yang kita perlukan adalah WaveInOpen().
function waveInOpen(lphWaveIn: PHWAVEIN; uDeviceID:UINT;
lpFormatEx: PWaveFormatEx;
dwCallback, dwInstance, dwFlags: DWORD): MMRESULT; stdcall;
Jika waveInOpen sukses, fungsi ini mengembalikan handle (bertipe HWAVEIN) ke lphwavein. uDeviceID adalah device yang akan dibuka. Jika kita tidak tahu apa device apa yang akan kita buka gunakan saja konstanta WAVE_MAPPER. lpFormatEx adalah format recording yang kita inginkan. Lihat "Format Recording".
dwCallback adalah mekanisme callback yang kita pergunakan. Callback akan dipanggil tiap kali terjadi event misalnya saat sound card selesai mengisi buffer dengan data, device dibuka dan lain-lainnya. Parameter ini erat kaitannya dengan parameter dwFlags.
Jika dwFlags=CALLBACK_NULL maka mekanisme callback tidak digunakan, jika dwFlags=CALLBACK_FUNCTION maka dwCallback haruslah berisi pointer ke fungsi callback. Untuk info mengenai callback lihat di "Callback" jika dwFlags=CALLBACK_WINDOW maka dwCallback haruslah berisi handle Window yang akan dikirimi pesan.
Messagenya adalah:
Ada beberapa callback lain, namun biasanya yang lebih banyak digunakan adalah CALLBACK_FUNCTION atau CALLBACK_WINDOW.
dwInstance adalah user-defined data yang akan dikirim ke callback.
Format recording kita set menggunakan record TWaveFormatEx.
tWAVEFORMATEX = packed record
wFormatTag: Word;
nChannels: Word;
nSamplesPerSec: DWORD;
nAvgBytesPerSec: DWORD;
nBlockAlign: Word;
wBitsPerSample: Word;
cbSize: Word;
end;
wFormatTag berisi tipe format. Untuk format PCM (Pulse Code Modulation), kita set dengan WAVE_FORMAT_PCM. nChannels jumlah channel yang kita pergunakan, 1=mono 2=stereo nSamplePerSec frekuensi sampling data. Untuk PCM, kita bisa menggunakan frekuensi 8000 Hz, 11025 Hz, 22050 Hz, 44100 Hz. Semakin tinggi sample rate, semakin tinggi kualitas suara, namun ukuran datanya semakin besar. nAvgBytesPerSec rata-rata laju data transfer yang dibutuhkan. Untuk menghitung berapa laju data transfer kita kalikan nSamplesPerSec dengan nBlockAlign. nBlockAlign adalah satuan terkecil data untuk suatu format wave. Untuk PCM, nBlockAlign adalah nChannels*wBitsPerSample dibagi 8 (1 byte=8 bit) wBitsPerSample Jumlah bit untuk tiap sample data. Untuk PCM nilainya harus 8 atau 16. cbSize size data tambahan dalam bytes. untuk PCM field ini diabaikan.
Fungsi callbacknya berformat seperti berikut:
procedure waveCallback(const handle:HWAVEIN;
const uMsg:integer;
dwInstance,dwParam1,dwParam2:cardinal);stdcall;
handle akan diisi dengan handle device recorder yang kita peroleh saat memanggil WaveInOpen(). uMsg akan diisi dengan message yang terjadi yaitu salah satu message berikut: WIM_CLOSE = device ditutup dengan memanggil waveInClose() WIM_OPEN = device dibuka dengan waveinOpen(). WIM_DATA = device telah selesai mengisi buffer dengan data. message yang paling penting adalah WIM_DATA.
Perhatikan bahwa kita menggunakan direktif stdcall, direktif ini sangat penting mengingat callback kita akan dipanggil oleh device driver. Dengan direktif ini, parameter passingnya sesuai parameter passing windows. dwInstance berisi data milik user (lihat parameter dwInstance pada WaveInOpen() dwParam1 dan dwParam2 parameter message).
Kita tidak boleh memanggil fungsi-fungsi waveInXXX di dalam callback karena akan menyebabkan deadlock. Untuk informasi lebih lanjut mengenai deadlock lihat "Deadlock".
Kita perlu menyiapkan buffer yang akan diisi dengan data sample suara. Untuk menyiapkan buffer ini kita harus mengalokasikan memori buffer kemudian memanggil waveInPrepareHeader().
function waveInPrepareHeader(hWaveIn: HWAVEIN;
lpWaveInHdr: PWaveHdr;
uSize: UINT): MMRESULT; stdcall;
Fungsi ini berguna untuk memberitahukan driver soundcard mengenai buffer yang akan kita pergunakan untuk menampung data hasil rekaman, seperti berapa besar ukuran buffer dan alamat buffer itu sendiri,serta menginisialisasi internal resource. Parameter fungsi ini adalah hWaveIn handle device recorder lpWaveInHdr berisi pointer ke data wave header bertipe TWaveHdr. Lihat "Format Wave Header" untuk penjelasan lengkap struktur data ini. uSize adalah ukuran struktur TWaveHdr
Wave header adalah struktur data yang akan kita pergunakan untuk menyimpan alamat buffer yang akan menampung sample suara hasil perekaman. Tipenya adalah TWaveHdr
type
PWaveHdr = ^TWaveHdr;
{$EXTERNALSYM wavehdr_tag}
wavehdr_tag = record
lpData: PChar; { pointer to locked data buffer }
dwBufferLength: DWORD; { length of data buffer }
dwBytesRecorded: DWORD; { used for input only }
dwUser: DWORD; { for client's use }
dwFlags: DWORD; { assorted flags (see defines) }
dwLoops: DWORD; { loop control counter }
lpNext: PWaveHdr; { reserved for driver }
reserved: DWORD; { reserved for driver }
end;
TWaveHdr = wavehdr_tag;
{$EXTERNALSYM WAVEHDR}
WAVEHDR = wavehdr_tag;
lpData berisi pointer ke buffer. Field inilah yang akan sering kita pergunakan. dwBufferLength berisi ukuran buffer. dwBytesRecorded berisi jumlah aktual data yang terekam. dwBytesRecorded nilainya adalah <= dwBufferLength. dwUser adalah user-defined data. Kita dapat menggunakannya untuk menyimpan data milik kita sendiri dwFlags berisi status wave header. Bisa berisi nilai-nilai berikut:
dwLoops berisi berapakali buffer akan dimainkan. Untuk proses rekaman field ini tidak diperlukan. lpNext berisi pointer ke wave header berikutnya. reserved dicadangkan untuk masa datang.
Setelah kita menyiapkan buffer, kita perlu mengirimkan buffer tersebut ke driver menggunakan waveInAddBuffer()
function waveInAddBuffer(hWaveIn: HWAVEIN;
lpWaveInHdr: PWaveHdr;
uSize: UINT): MMRESULT; stdcall;
Parameternya sama dengan fungsi waveInPrepareHeader().
Proses recording dimulai dengan memanggil waveInStart() dan untuk menghentikannya menggunakan waveInStop()
function waveInStart(hWaveIn: HWAVEIN): MMRESULT;stdcall;
function waveInStop(hWaveIn: HWAVEIN): MMRESULT;stdcall;
Selama recording, tiap kali buffer telah penuh dengan data, aplikasi kita akan diberi tahu melalui mekanisme callback.
Fungsi ini akan menghentikan proses recording sama seperti waveInStop(). Perbedaannya dengan waveInStop() adalah, semua buffer yang masih ada dalam antrian akan dibuang dari antrian dan ditandai sebagai WHDR_DONE, sedangkan waveInStop() hanya menandai buffer saat ini sebagai WHDR_DONE, buffer lain yang ada dalam antrian tidak akan diremove.
function waveInReset(hWaveIn: HWAVEIN): MMRESULT; stdcall;
Seperti yang sebelumnya disebutkan didalam "Callback", sangat tidak disarankan memanggil fungsi-fungsi wave saat berada dalam callback. Fungsi-fungsi wave seperti waveInStop() akan menggenerate notifikasi yang akan menyebabkan callback kita dipanggil. Jika callback ini dipanggil dalam callback, yang terjadi adalah rekursif yang tak terkendali. Jika anda menemukan gejala aplikasi anda menjadi hang saat menjalankan callback, maka code aplikasi anda mengalami deadlock ini.
Mungkin anda bertanya, bagaimana bila kita ingin merekam data suara yang sangat panjang? Apakah kita perlu menyediakan buffer yang sangat besar? Tidak perlu! Jawabnya adalah menggunakan buffer lebih dari satu.
Dengan buffer lebih dari satu (double buffer atau triple buffer atau lebih), sementara buffer pertama sedang diisi dengan data, kita dapat menyiapkan/memproses data yang ada pada buffer kedua. Jika buffer pertama selesai diisi dengan data, buffer kedua kita kirim ke driver untuk diproses, data yang ada pada buffer pertama kita proses sementara menunggu buffer kedua diisi dengan data sample. Langkah ini diulang terus menerus sampai proses recording dihentikan. Dengan cara ini kita dapat merekam data yang banyak menggunakan ukuran buffer yang kecil.
Kita sudah cukup memiliki informasi bagaimana melakukan perekaman suara menggunakan wave API. Ok, kita lanjutkan dengan membuat implementasinya.
Kelas yang akan kita buat akan berfungsi untuk menyembunyikan detail proses perekaman suara. Proses manajemen buffer, dan menghandle notifikasi perekaman suara akan dihandle oleh kelas ini. Aplikasi yang memanfaatkan kelas ini hanya bertugas menentukan format wave untuk recording, memulai dan menghentikan proses perekaman serta menghandle data hasil recording, jika diperlukan.
Kelas ini akan menggunakan teknik double buffer untuk proses recording, oleh karena itu kita akan menyiapkan dua buah buffer masing-masing sebesar 4 KB untuk menampung data recording. Tiap kali buffer telah berisi data, kelas ini akan mengenerate event untuk memberitahukan aplikasi bahwa buffer siap diproses. Bagaimana aplikasi memproses data tersebut adalah tanggung jawab aplikasi.
Mengingat teknik merekam suara dan memainkan suara menggunakan Wave API caranya hampir serupa, kita akan membuat susunan inheritance kelas enkapsulasi perekaman sedemikian rupa sehingga nantinya mudah dikembangkan menjadi kelas enkapsulasi proses memainkan suara. Kelas yang akan mengenkapsulasi proses recording kita namakan TSoundRecorder.
Berikut ini adalah hirarki kelasnya:

Gbr.1 Diagram UML sound recorder.
Kelas TSoundObject adalah kelas dasar untuk kelas-kelas turunannya. Kelas ini mendeklarasikan beberapa procedure penting yakni Open, Close, Start dan Stop. Semuanya adalah prosedur abstrak, karena implementasi Open, Close, Start, Stop nantinya dapat berbeda-beda. Contohnya untuk membuka device untuk playback akan berbeda dengan cara membuka device untuk recording. Pada TSoundObject akan kita tambahkan property Handle yang akan dipergunakan menyimpan handle device. Berikut ini adalah deklarasinya
TSoundObject=class(TObject)
private
protected
FHandle:THandle;
public
procedure Open;virtual;abstract;
procedure Close;virtual;abstract;
procedure Start;virtual;abstract;
procedure Stop;virtual;abstract;
published
property Handle:THandle read FHandle;
end;
TWaveObject adalah kelas dasar untuk proses playback atau recording wave. Kelas TWaveObject ini mendeklarasikan procedure bernama WaveProc yakni callback yang akan dipanggil ketika driver selesai dengan data sample. Procedure ini dibuat abstrak karena, proses yang ada dalam callback harus didefinisikan oleh kelas turunan mengingat callback proses recording tentunya akan berbeda dengan callback untuk playback.
Proses playback maupun recording akan membutuhkan buffer untuk menyimpan format wave. Oleh karena itu dikelas ini dideklarasikan variabel internal bertipe TWaveFormatEx yang nantinya dapat dipergunakan kelas turunannya untuk menyimpan format wave. TWaveObject juga dilengkapi dengan property yang dipergunakan untuk mengatur format wave ini. Perhatikan bahwa karena field nBlockAlign, nAvgSamplesPerSec dapat dihitung dari data yang ada pada nChannel, nSamplesPerSec dan nBitsPerSample, maka hanya ketiga field ini yang akan tersedia pada property TWaveObject
TWaveObject=class(TSoundObject)
private
procedure SetupWaveFormat;
procedure SetBitsPerSample(const Value: word);
procedure SetChannel(const Value: word);
procedure SetSamplePerSec(const Value: cardinal);
protected
FWaveFormat:TWaveFormatEx;
procedure WaveProc(const handle:HWAVEIN;
const msg:UINT;
const dwInstance:cardinal;
const dwParam1,dwParam2:cardinal);virtual;abstract;
public
constructor Create;virtual;
published
property Channel:word read FWaveFormat.nChannels write SetChannel;
property SamplePerSec:cardinal read FWaveFormat.nSamplesPerSec write SetSamplePerSec;
property BitsPerSample:word read FWaveFormat.wBitsPerSample write SetBitsPerSample;
end;
TWaveObject memiliki konstruktor Create. Konstruktor ini akan melakukan inisialisasi format wave default yakni frekuensi sampling=11025 Hz, mono 8 bit.
TSoundRecorder adalah kelas lengkap yang akan mengimplementasi semua metode-metode abstrak yang ada pada ancestornya. yakni Open,Close, Start, Stop dan WaveProc. Mengingat kelas ini menggunakan double buffer untuk proses recording, SwapBuffers digunakan untuk mempertukarkan buffer yang siap dikirim ke driver. Sesuai spesifikasi, TSoundRecorder hanya bertugas sebagai data delivery, sehingga aplikasi adalah pihak yang bertanggung jawab menghandle data yang dikirim. Oleh karena itu kita lengkapi dengan event OnDAtaAvail seperti dibawah ini.
TDataAvailEvent=procedure(sender:TObject;
const Buffer:pointer;
const BufferSize:cardinal;
const BytesRecorded:cardinal) of object;
OnDataAvail mengirimkan parameter sender berisi instance TSoundRecorder. Buffer
berisi data, ukuran buffer dan jumlah aktual data yang ada pada buffer. Aplikasi
tidak boleh membebaskan memori buffer, karena manajemennya ditangani internal
oleh TSoundRecorder.
TSoundRecorder=class(TWaveObject)
private
FBuffer1,FBuffer2,FCurrentBuffer:PWaveHdr;
FRecording: boolean;
FOnDataAvail: TDataAvailEvent;
procedure SetOnDataAvail(const Value: TDataAvailEvent);
procedure SwapBuffers;
protected
procedure DoDataAvail(const Buffer:pointer;
const BufferSize:cardinal;
const BytesRecorded:cardinal);virtual;
procedure WaveProc(const handle:HWAVEIN;
const msg:UINT;
const dwInstance:cardinal;
const dwParam1,dwParam2:cardinal);override;
public
constructor Create;override;
destructor Destroy;override;
procedure Open;override;
procedure Close;override;
procedure Start;override;
procedure Stop;override;
published
property Recording:boolean read FRecording;
property OnDataAvail:TDataAvailEvent read FOnDataAvail write SetOnDataAvail;
end;
Selain event OnDataAvail, property lain yang penting adalah status recording guna memberitahukan apakah proses recording sedang berjalan atau tidak.
Mengingat lifetime buffer dihandle internal, maka konstruktor dan destruktor kita lengkapi dengan kode alokasi dan dealokasi buffer. Berikut ini adalah kodenya
constructor TSoundRecorder.Create;
begin
inherited;
new(FBuffer1);
ZeroMemory(FBuffer1,sizeOf(TWaveHdr));
GetMem(FBuffer1.lpData,MAX_BUFFER_SIZE);
FBuffer1.dwBufferLength:=MAX_BUFFER_SIZE;
new(FBuffer2);
ZeroMemory(FBuffer2,sizeOf(TWaveHdr));
GetMem(FBuffer2.lpData,MAX_BUFFER_SIZE);
FBuffer2.dwBufferLength:=MAX_BUFFER_SIZE;
end;
destructor TSoundRecorder.Destroy;
begin
Close;
FreeMem(FBuffer1.lpData,MAX_BUFFER_SIZE);
FreeMem(FBuffer2.lpData,MAX_BUFFER_SIZE);
dispose(FBuffer1);
dispose(FBuffer2);
inherited;
end;
dimana MAX_BUFFER_SIZE dideklarasikan sebagai
const MAX_BUFFER_SIZE=4*1024;
Procedure ini dipanggil untuk membuka device recording.
procedure TSoundRecorder.Open;
var ahandle:HWAVEIN;
status:MMResult;
statusStr:string;
begin
if Handle=0 then
begin
aHandle:=0;
status:=waveInOpen(@aHandle,
WAVE_MAPPER,
@FWaveFormat,
cardinal(@_WaveInCallback),
cardinal(self),
CALLBACK_FUNCTION);
FHandle:=aHandle;
if Handle=0 then
begin
setlength(statusStr,MAXERRORLENGTH);
waveInGetErrorText(status,pChar(statusStr),
MAXERRORLENGTH);
raise ESndError.Create(statusStr);
end;
WaveInPrepareHeader(Handle,FBuffer1,sizeof(TWaveHdr));
WaveInPrepareHeader(Handle,FBuffer2,sizeof(TWaveHdr));
end;
end;
Langkah pertama adalah melakukan pengecekan apakah Handle sama dengan nol. Jika sama, kita lakukan langkah membuka device. Kita kirimkan alamat format wave yang kita inginkan, juga alamat callback yang akan memproses data. Di kelas ini, callbacknya adalah fungsi bernama _WaveInCallback. Sengaja diberi underscore di depan untuk menekankan bahwa prosedur ini adalah prosedur yang seharusnya tidak dipanggil oleh aplikasi.
Kita juga mengirimkan alamat pointer instance ke callback. Mengapa kita kirimkan alamat instance akan kita bahas di bagian "Implementasi Callback". Jika gagal (Handle=0) kita generate eksepsi ESndError yang merupakan turunan Exception. Selanjutnya kita prepare wave header. Perhatikan bahwa kita hanya perlu melakukan prepare header sekali saja selama buffer kita tidak berubah. Jika buffer berubah, misalnya karena ukurannya diresize, kita harus melakukan header preparation lagi. Untuk kelas TSoundBuffer, ukuran buffer tidak pernah berubah.
Seperti yang sudah disebutkan prosedur _WaveInCallback adalah callback proses recording. Berikut ini adalah implementasinya:
procedure _WaveInCallback(const handle:HWAVEIN;
const msg:UINT;
const dwInstance:cardinal;
const dwParam1,dwParam2:cardinal);stdcall;
begin
TSoundRecorder(dwInstance).WaveProc(handle,
msg,
dwInstance,
dwParam1,
dwParam2);
end;
Perhatikan bahwa dwInstance kita cast ke TSoundRecorder. Ini aman kita lakukan karena pada saat memanggil waveInOpen, parameter dwInstance waveInOpen kita isi dengan Self. Kemudian kita panggil WaveProc, callback sesungguhnya yang menangani pemrosesan lebih lanjut. Mungkin ada yang bertanya lalu mengapa harus mengirimkan alamat _WaveInCallback? Mengapa tidak alamat WaveProc-nya langsung? Problem ini terkait dengan perbedaan parameter passing prosedur dan metode.
Pada metode kelas, saat metode dipanggil, register eax akan diisi dengan self, selanjutnya parameter pertama di edx dan parameter kedua di ecx, sedangkan parameter passing Windows tidak mengirimkan alamat instance self. Dengan teknik ini, kita dapat menggunakan callback yang merupakan metode suatu kelas. Berikut ini adalah implementasi WaveProc
procedure TSoundRecorder.WaveProc(const handle:HWAVEIN;
const msg:UINT;
const dwInstance:cardinal;
const dwParam1,dwParam2:cardinal);
var wavehdr:PWaveHdr;
begin
case msg of
WIM_DATA:begin
if FRecording then
begin
waveHdr:=PWaveHdr(dwParam1);
SwapBuffers;
DoDataAvail(waveHdr.lpData,
waveHdr.dwBufferLength,
waveHdr.dwBytesRecorded);
end;
end;
end;
end;
kita cek apakah msg berisi WIM_DATA. kalau ya, maka kita cek, jika statusnya masih merekam, kita cast dwParam1 ke PWaveHdr. Buffer kita pertukarkan menggunakan SwapBuffers, selanjutnya data alamat buffer dan ukurannya kita kirimkan ke aplikasi dengan mengenerate event OnDataAvail.
procedure TSoundRecorder.DoDataAvail(const Buffer: pointer;
const BufferSize, BytesRecorded: cardinal);
begin
if Assigned(FOnDataAvail) then
FOnDataAvail(self,Buffer,BufferSize,BytesRecorded);
end;
Prosedur SwapBuffers sendiri implementasinya adalah sebagai berikut:
procedure TSoundRecorder.SwapBuffers;
begin
if FCurrentBuffer=FBuffer1 then
FCurrentBuffer:=FBuffer2
else
FCurrentBuffer:=FBuffer1;
WaveInAddBuffer(Handle,FCurrentBuffer,sizeof(TWaveHdr));
end;
untuk memastikan tidak ada recording, kita panggil Stop().
procedure TSoundRecorder.Start;
begin
if Handle<>0 then
begin
Stop;
FCurrentBuffer:=FBuffer1;
WaveInAddBuffer(Handle,FBuffer1,sizeof(TWaveHdr));
waveInStart(Handle);
FRecording:=true;
end;
end;
Kirim buffer pertama ke device driver dan kita mulai recording dengan memanggil WaveInStart().
procedure TSoundRecorder.Stop;
begin
if Handle<>0 then
begin
waveInStop(Handle);
FRecording:=false;
end;
end;
Apa yang kita buka tentunya harus kita tutup untuk membebaskan resource yang terpakai.
procedure TSoundRecorder.Close;
begin
if Handle<>0 then
begin
Stop;
waveInUnPrepareHeader(Handle,FBuffer1,Sizeof(TWaveHdr));
waveInUnPrepareHeader(Handle,FBuffer2,Sizeof(TWaveHdr));
waveInClose(Handle);
FHandle:=0;
end;
end;
Jika masih merekam kita stop() dulu. Selanjutnya buffer di unprepare dan kita tutup device. Perhatikan bahwa kita harus melakukan unprepare sebelum buffer dibebaskan. Jika kita membebaskan buffer sebelum melakukan unprepare, device driver mungkin masih mengakses buffer ini. Dengan melakukan unprepare, pada dasarnya, kita memberitahukan device driver bahwa buffer ini tidak boleh dipergunakan lagi karena kita akan membebaskannya.
Tidak lengkap bila kita tidak mendiskusikan bagaimana menggunakan kelas TSoundRecorder ini. Buat aplikasi baru. dan drop empat tombol nama dengan btnStart, btnStop, btnSave dan btnPlay. Buat handler untuk OnClick seperti berikut:
{=========================
(c) 2006 zamrony p juhara
=========================}
unit ufrmMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,mmsystem,uSound,XPMan;
type
TfrmMain = class(TForm)
btnStart: TButton;
btnStop: TButton;
btnPlay: TButton;
btnSave: TButton;
SaveDialog1: TSaveDialog;
procedure btnPlayClick(Sender: TObject);
procedure btnStartClick(Sender: TObject);
procedure btnStopClick(Sender: TObject);
procedure btnSaveClick(Sender: TObject);
private
FRecordedStream,FWaveStream:TMemoryStream;
FSoundRecorder:TSoundRecorder;
procedure DataAvail(sender:TObject; const Buffer:pointer;
const BufferSize,bytesRecorded:cardinal);
procedure SaveWaveToStream(recorded,stream:TStream);
public
constructor Create(Owner:TComponent);override;
destructor Destroy;override;
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TfrmMain.btnPlayClick(Sender: TObject);
begin
FWaveStream.Clear;
SaveWaveToStream(FRecordedStream,FWaveStream);
PlaySound(FWaveStream.Memory,0,SND_MEMORY OR SND_ASYNC);
btnSave.Enabled:=true;
end;
constructor TfrmMain.Create(Owner: TComponent);
begin
inherited;
FWaveStream:=TMemoryStream.Create;
FRecordedStream:=TMemoryStream.Create;
FSoundRecorder:=TSoundRecorder.Create;
FSoundRecorder.OnDataAvail:=DataAvail;
FSoundRecorder.Open;
end;
procedure TfrmMain.DataAvail(sender: TObject; const
Buffer:pointer;const BufferSize,
bytesRecorded: cardinal);
begin
FRecordedStream.WriteBuffer(Buffer^,BytesRecorded);
end;
destructor TfrmMain.Destroy;
begin
FWaveStream.Free;
FRecordedStream.Free;
FSoundRecorder.Free;
inherited;
end;
procedure TfrmMain.btnStartClick(Sender: TObject);
begin
FSoundRecorder.Start;
FRecordedStream.Clear;
btnStop.Enabled:=true;
btnStart.Enabled:=false;
btnSave.Enabled:=false;
end;
procedure TfrmMain.btnStopClick(Sender: TObject);
begin
FSoundRecorder.Stop;
btnStop.Enabled:=false;
btnStart.Enabled:=true;
btnPlay.Enabled:=true;
end;
procedure TfrmMain.SaveWaveToStream(recorded, stream: TStream);
var waveformatex:TWaveFormatEx;
datacount,riffcount,tempInt:integer;
const
RiffId: string = 'RIFF';
WaveId: string = 'WAVE';
FmtId: string = 'fmt ';
DataId: string = 'data';
begin
with WaveFormatEx do
begin
wFormatTag := WAVE_FORMAT_PCM;
nChannels := FSoundRecorder.Channel;
nSamplesPerSec := FSoundRecorder.SamplePerSec;
wBitsPerSample := FSoundRecorder.BitsPerSample;
nBlockAlign := (nChannels * wBitsPerSample) div 8;
nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
cbSize := 0;
end;
datacount:=recorded.Size;
{hitung panjang data sound dan panjang stream WAV yang harus dihasilkan}
RiffCount := Length(WaveId) + Length(FmtId) + SizeOf(DWORD) +
SizeOf(TWaveFormatEx) + Length(DataId) + SizeOf(DWORD) +
dataCount;
{tulis wave header}
stream.WriteBuffer(RiffId[1], 4); // 'RIFF'
Stream.WriteBuffer(RiffCount, SizeOf(DWORD)); // file data size
Stream.WriteBuffer(WaveId[1], Length(WaveId)); // 'WAVE'
Stream.WriteBuffer(FmtId[1], Length(FmtId)); // 'fmt '
TempInt := SizeOf(TWaveFormatEx);
Stream.WriteBuffer(TempInt, SizeOf(DWORD)); // TWaveFormat data size
Stream.WriteBuffer(WaveFormatEx, SizeOf(TWaveFormatEx)); //
WaveFormatEx record
Stream.WriteBuffer(DataId[1], Length(DataId)); // 'data'
Stream.WriteBuffer(datacount, SizeOf(DWORD)); // sound data size
recorded.Seek(0,soFromBeginning);
Stream.CopyFrom(recorded,dataCount);
end;
procedure TfrmMain.btnSaveClick(Sender: TObject);
var fstream:TFileStream;
begin
if SaveDialog1.Execute then
begin
fstream:=TFileStream.Create(SaveDialog1.Filename,fmCreate);
try
FWaveStream.Position:=0;
fstream.CopyFrom(FWaveStream,0);
finally
fstream.Free;
end;
end;
end;
end.
Di aplikasi ini kita siapkan dua buffer, satu untuk menampung buffer hasil recording dan kedua buffer untuk menyimpan data berformat WAV. Kita membutuhkan buffer berformat WAV karena kita akan memainkan file WAV menggunakan PlaySound untuk playbacknya. Tidak sulit, konstruktor Create akan melakukan alokasi instance-instance TMemoryStream dan TSoundRecorder dan membuka device recorder. Destroy melakukan dealokasinya. Yang perlu dijelaskan mungkin adalah proses menghasilkan format WAV. Data yang dikirimkan di prosedur DataAvail adalah raw data. Agar bisa dimainkan oleh PlaySound, datanya harus berupa WAV.
Format WAV terdiri atas blok-blok data yakni blok terluar adalah blok RIFF
Penjelasan mengenai format WAV mungkin membingungkan tapi, dengan mengamati kode SaveWaveToStream tidak terlalu sulit untuk memahaminya.
Source kode bisa didownload di sini OK, sampai disini saja.
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