Merekam Suara dengan Wave API
April 17, 2006 at 9:18 am | In Code Samples, Components | 26 Commentsby: Zamrony P Juhara
Sebenarnya untuk merekam suara TMediaPlayer sudah bisa melakukannya, caranya pun simpel. 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)
Membuka Device
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 di buka 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:
MM_WIM_OPEN = device ditutup dengan waveinClose()
MM_WIM_CLOSE = device ditutup dengan waveinOpen()
MM_WIM_DATA = device selesai mengisi buffer dengan data
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
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.
Callback
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”
Menyiapkan Buffer
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
Format Wave Header
Wave header adalah sutuktur 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:
WHDR_DONE driver soundcard telah selesai mengisi buffer dengan data.
WHDR_PREPARED buffer telah diprepared dengan pemanggilan waveINPrepareHeader().
WHDR_INQUEUE buffer sedang dalam antiran menunggu untuk diisi dengan data.
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.
Mengirim Buffer ke Driver
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().
Memulai dan Menghentikan Proses Recording
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.
Mereset Proses Recording
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;
Deadlock
Seperti yang sebelumnya disebutkan didalam “Callback”, kita sangat disarankan tidak 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.
Merekam Data Yang Banyak.
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.
Desain
Spesifikasi:
Kelas yang akan kita buat akan berfungsi untuk menyembunyikan detail proses perekaman suara. Proses manajemen buffer, dan manghandle 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 proes recording kita namakan TSoundRecorder. Berikut ini adalah hirarki kelasnya
TObject
+
++ TSoundObject
+
++ TWaveObject
+
++ TSoundRecorder
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 dsb 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 recordin 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.
Selain itu 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
Terakhir 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 menbebaskan 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.
Konstruktor/Destruktor
Mengingat lifetime buffer dihandle internal, maka Konstruktor den 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;
Implementasi Metode Open
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 ESndRError. ESndError adalah 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.
Implementasi Callback
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;
Implementasi Start Recording
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().
Implementasi Penghentian Recording.
procedure TSoundRecorder.Stop;
begin
if Handle<>0 then
begin
waveInStop(Handle);
FRecording:=false;
end;
end;
Implementasi Penutupan device.
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.
Aplikasi Utama
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 mngkin adalah proses menghasilkan format WAV. Data yang
dikirimkan di prosedur DataAvail adalah raw data. Agar bisa dimainkan oleh
PlaySound datanya harus berupa WAV.
Deskripsi Singkat Format WAV.
Format WAV terdiri atas blok-blok data yakni blok terluar adalah blok RIFF
-Blok Riff
ID RIFF 4 byte berisi ‘RIFF’
Panjang Blok Wave 4 byte
Data Blok Wave
-Blok Wave
ID WAVE 4 byte berisi ‘WAVE’
blok Format
blok Data
-Blok Format
ID 4 byte berisi ‘fmt ‘
Panjang blok format 4 byte berisi sizeof(TWaveFormatEx)
waveFormat sizeof(TWaveFormatEx) byte
-Blok Data
ID 4 byte berisi ‘data’
panjang data 4 byte
data sample (panjang data) byte
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. hepi koding..
26 Comments »
RSS feed for comments on this post. TrackBack URI
Leave a comment
Blog at WordPress.com. | Theme: Pool by Borja Fernandez.
Entries and comments feeds.
Please My Source
Comment by Ridwan — May 5, 2006 #
Ihhhh mau dong bagus ahhhhhh mo nyoba nich…
ternyata ada blog delphindo nich dah lama aku gak buka milis ada yang baru asikkkk
Comment by delphigarut — June 17, 2006 #
klo misalkan saya ingin melakukan ‘autorecord’ bisa nggak ya ?
maksudku program akan jalan terus menunggu ada suara. begitu ada suara maka langsung merekam. setelah system mendeteksi tidak ada suara lagi maka otomatis berhenti dan simpan dalam sebuah file. Yang ta lihat kok aplikasi yang ada harus menunggu user untuk klik ‘record’ dan ’stop’.
regards,
Agus Widodo
Comment by Agus Widodo — August 16, 2006 #
Memulai merekam otomatis saat ada suara? untuk menjawabnya saya balik pertanyaannya. Bagaimana program anda bisa tahu ada suara (tanpa membaca data dari mic atau dgn kata lain tanpa recording)?
Jika anda ingin membuat sistem auto record seperti yang anda maksud, maka anda harus melakukan recording sejak pertama kali program dijalankan.
kemudian secara terus menerus program harus memantau sinyal yang terbaca dari mic. jika terjadi perubahan desibel secara drastis (dibandingkan data sebelumnya), data ini dan data-data berikutnya disimpan dan dianggap sebagai data suara. ketika terjadi penurunan desibel menjadi silence, recording distop, data yang ada
disimpan, dan recording distart lagi.proses ini dilooping sampai program distop
masalahnya sekarang, program anda wajib menentukan apakah suara-suara yang masuk ke mic tergolong noise,silence atau suara yang harus direkam
Comment by zamrony p juhara — August 16, 2006 #
bagaimana saya mengetahui adanya perubahan ‘desibel’ ? Apakah buffer terisi secara konstan berdasarkan satuan waktu ? maksudnya adalah kualitas suara dengan volume kencang dan lemah tidak akan diketahui dari buffer.Jadi buffer hanya akan info klo penuh tidak peduli apa isinya ?
maaf klo pertanyaan saya kurang pas. soalnya ini baru sekali bagi saya. posting Anda sangat bagus dan berguna. mohon pencerahan lebih lanjut.
terima kasih
Comment by Agus Widodo — August 18, 2006 #
Tiap sample data menentukan seberapa kencang volume/desibel suara. untuk bitspersample 8 (1 byte =1 sample suara) maka tingkat tekanan bunyi adalah dari 0-255 dimana 0 dan 255 adalah tekanan bunyi terkeras, 127 adalah tekanan bunyi paling lemah (silence). untuk bits persample 16 rangenya adalah 0-65535.
pada bit persample 8 bit, ini berarti jika anda menerima data-data dari mic yang mendekati nilai 127,127,126,128,…dst anda dapat mengasumsikan silence
buffer secara konstan akan diisi dengan data berdasarkan satuan waktu.Jumlah data yang disample tiap detik ditentukan oleh sample per sec
Untuk kualitas 44.1 KHz itu berarti tiap detik, buffer akan diisi data sejumlah kurang lebih 44100 data. Jika tiap sample data menggunakan 8 bit berarti untuk tiap detik recording, kita butuh buffer 44100 bytes.
Comment by zamrony p juhara — August 18, 2006 #
hi,
artikel yang bagus pertanyaan 1.Bisa gak dipake di VB.6??…soalnya aku udah pernah nyoba tpi dng MCI command (with VB.6)emang sih jalan tpi aku kesulitan ketika mau gabungin dengan file gambar utk dijadiin file video mis AVI…btw utk capture gambar (screen window)aku juga pake fungsi Windows API(with VB.6)…Punya masukan buat aku??? Thanks…
Comment by lia — August 30, 2006 #
Kalo WAVE API tentu saja bisa dipake di VB karena Wave API adalah API bawaan Windows.
saya rasa kesluitan terbesar adalah sinkonrisasi antara proses recording dengan proses capture gambar. Jika capture gambar terlalu lambat, maka animasi tidak sinkron dengan playback
Jika yang anda maksud mengcapture video jauh lebih meudah menggunakan DirectShow
Comment by zamrony p juhara — August 30, 2006 #
mas aku masih bingung tentang pengaturan output channel audionya, bisa ngga dijelasin lebih detail, atau bagaimana kalao aku pengen output channel audio untuk yang L atau R-nya dimute ?
kalo pake Tmediaplayer bisa ngga?
makasih atas blog bagus ini.(sangat membantu)
Comment by Bastian — September 12, 2006 #
TMediaPlayer tidak bisa gunakan untuk mengatur balance Left/right playback. Bagaimana cara melakukan pengubahan balance(pan) ini mungkin akan saya bahas dalam artikel lain karena terlalu panjang kalo dijelaskan dalam comment
Comment by zamronypj — September 12, 2006 #
Menarik juga artikel Merekam Suara dengan Wave API
April 17, 2006 at 9:18 am | In Components, Code Samples | by: Zamrony P Juhara.
Pertanyaan saya simpel aja, gimana caranya membandingkan suara rekaman 1 dengan suara rekaman 2, agar dapat diketahui persentasi nilai db-nya. misalnya: suara 1 rada pelo dan suara 2 merupakan suara rekaman asli yang sudah tersimpan difolder ato database.nah..ketika suara 1 (pelo) sudah terekam,kita dapat mengetahui persentase nilai db-nya.
Kamsya to alot of..
Comment by eksa — October 12, 2006 #
pertanyaan yang simple tapi sayangnya tidak mudah untuk dijawab.
desibel hanya memberikan informasi seberapa keras tingkat bunyi, tapi tidak memberi informasi apakah suatu suara mirip dengan suara lain.
Suara terdengar sama bila bentuk gelombangnya mirip. So karena berurusan dengan gelombang, untuk menentukan suaraa mirip atau tidak, anda bisa mengkonversi suara dalam domain waktu ke domain frekuensi dgn transformasi Fourier /FFT, kemudaian menganalisa frekuensi-frekuensi masing-masing suara. Jika frekuensi-frekuensi rendahnya banyak yang sama, bisa dipastikan suara pertama dan suara kedua mirip.
Comment by zam — October 13, 2006 #
ehm…
dikit banget hubungan pertanyaan saya dengan artikel merekam suara ini. Cuman yang pengen saya tanyain adalah gimana cara ndapetin chromagram / pitch class profile dari suatu file wav (skaligus cara ndapetin discrete fourier transform-nya juga). Karna critanya saya pengen bikin system “automation chord recognition” yang bakalan mengenali chord dari suatu file wav/mp3 (ex. : kayak software “chordpickout” gitu)… akses ke level frequency dari file wave tsb yang masih belum saya temukan…
Terima kasih banyak atas masukkannya.
Comment by endhik — December 21, 2006 #
mas boleh nggak minta source code untuk membuat recorder pake vb 6? soalnya baru belajar vb 6 neh….jadinya masih pemula…kalo bisa juga sekalian yang buat membaca sampel-sampel pada file .wav. thx…
Comment by prasetya — November 14, 2007 #
em… klo pingin record suara dari line in PC apa menggunakan cara yag sama?
Comment by ndari — January 31, 2008 #
to mas zamrony. gimana caranya buat recorder dengan vb.6 tapi system hanya merekam ketika ada sinyal suara yang masuk ke soundcard??
klo boleh skalian sourcecodenya. makasih mas.
maaf baru pemula.
bandi kalimantan timur
Comment by bandiyono — February 1, 2008 #
Merekam dari line-in atau langsung dari mik sama saja caranya. Untuk merekam hanya ketika ada suara jawabannya lihat di atas. Source code untuk VB sayang sekali belum ada. Silakan konversi sendiri ke VB.
Comment by zamrony p juhara — February 4, 2008 #
sekalian tanya juga nih mas Zamrony, dari jawaban di atas ada penjelaasan mengenai range 0 – 255, di situ disebutkan bahwa nilai 127 adalah nilai tekanan bunyi yg paling lemah, pertanyaannya adalah apa sifat perbedaan nilai antara 0-126 dan nilai antara 128-255, terus saya sendiri menggunakan format GSM di mana ada tambahan tag di wavehdr, apakah keterangan mengenai range nilai tekanan bunyi di format GSM sama dengan di format PCM.
Comment by pozlast — July 15, 2008 #
Angka 0-255 itu merupakan range amplitudo gelombang suara yang disimpan. Range 0-255 terkait dingan bit suara 8 bit, untuk kedalaman suara lebih tinggi range amplitudonya 0-65535.
Comment by zamrony p juhara — July 15, 2008 #
untuk problem mas Bastian tentang cara mengatur output channel L dan R, kebetulan saya pernah berhasil membuat hal yang sama yang mungkin bisa di jadikan alternatif solusi, tapi karena keterbatasan tempat untuk menulis penjelasan, silahkan email ke pozlast@yahoo.com
Comment by pozlast — July 18, 2008 #
Mas Zamrony, saya tertarik dengan penjelasan mengenai
mengkonversi suara dalam domain waktu ke domain frekuensi dgn transformasi Fourier /FFT, apakah ada keterangan lebih detail mengenai implementasi teknik program konversi tsb.
terima kasih sebelumnya.
Comment by pozlast — August 12, 2008 #
Yth mas zamrony,
Gmn cara mendapatkan array data dinamic pada suara yang telah direkam pake aplikasi ini mas?
Saya udah coba coding tapi hasilnya nihil,hiks…
Saya bermaksud setelah button stop di klik, trus munculin data array audio wav nya ke dalam memo,
Mohon bantuannya mas…thanks ya…
Comment by Terras — December 5, 2008 #
Yth. Mas Zamrony,
Bagaimana caranya menyimpan data hasil rekaman dalam satu field database (misal dengan Access), dimana data suara yang sudah terekam ini nantinya akan digunakan di lain waktu, misal digunakan sebagai password suara.
Terima kasih atas jawabannya
Musafa
Comment by Safa — January 6, 2009 #
Terras:
Gmn cara mendapatkan array data dinamic pada suara yang telah direkam pake aplikasi ini mas?
ZPJ:
Silakan pelajari contoh demo di atas. Data rekaman suara disimpan dalam stream bernama FRecordedStream
Safa:
Bagaimana caranya menyimpan data hasil rekaman dalam satu field database (misal dengan Access)?
ZPJ:
Gunakan TBlobStream untuk menyimpan data hasil rekaman ke database. Anda bisa ubah demo diatas dengan FRecordedStream diubah dari TMemoryStream ke TBlobStream.
Cara lain, stream hasil rekaman di simpan ke file menggunakan TFileStream, lalu nama filenya disimpan dalam field database
Comment by Zamrony P. Juhara — January 7, 2009 #
Mas bagus skali artikel nya..
maaf mas mau merepotkan…
aplikasi duiatas bisa g dipakai untuk plug and play, maksudnya dari suara yang kita tangkan langsung saja keluar lewat line out…
terimakasih sebelum nya atas bantuanya..
Comment by Afifzb — January 14, 2009 #
Bisa saja data yang sudah direkam dikirim ke speaker. Saya pribadi pernah mencobanya, hanya saja bila speakernya cukup dekat dengan mikropon perekam, terjadi gema pada suara yang dikeluarkan oleh speaker, karena suara keluar masuk lagi melalui mikropon.
Comment by Zamrony P. Juhara — January 14, 2009 #