Merekam Suara dengan Wave API

April 17, 2006 at 9:18 am | Posted in Code Samples, Components | 37 Comments

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

37 Comments »

RSS feed for comments on this post. TrackBack URI

  1. Please My Source

  2. Ihhhh mau dong bagus ahhhhhh mo nyoba nich…
    ternyata ada blog delphindo nich dah lama aku gak buka milis ada yang baru asikkkk

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

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

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

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

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

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

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

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

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

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

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

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

  15. em… klo pingin record suara dari line in PC apa menggunakan cara yag sama?

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

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

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

    • halo mas pzlast, saya mau tanya apakah anda sudah menerima kiriman dari bang zamrony ttg fft? kalau uda, boleh dong dikirim dan di share ke aku juga renaldipp@yahoo.com

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

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

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

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

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

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

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

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

  27. Mas bisa kasih contoh coding yang di C#.net ga ya?

    soalnya saya buat di C# tapi buffernya ga keisi padahal dari callback funtionya udah ngirim message MIM_DATA..

    gimana ya mas??

  28. boleh minta link komponen media playernya ga,, buat delphi7

  29. mas link untuk download source kodenya kok gak aktif ya?,. gak bisa download,. mas unit USound bisa minta gak mas tlong kirim ke email sy,. thx bgt dlu mas y,.

  30. mas..ge bingung ne…tu semua u rekam suara pake delphi kn…?
    tp u connect k device nyag mau ya…pake windows 7

  31. Tidak mau connect itu maksudnya bagaimana? Apa kode errornya (exception dan lain sebagainya) atau contoh kode yang Anda pakai?

  32. mas g mampu lah aku bikin….angkat tngan aq…ne lagi aqnyusun..d kasi wktu sebulan krn mau cuti melahirkan…kalu proyek bisa g mas….

  33. mas untuk desain form nya itu seperti apa ya??
    bisa d buatkan g mas..?mohon bantuannya y mas…

  34. mas gimana cara membandingkan nilai amplitudo di memo???mohon bantuannya yaaaa

  35. mas zamrony tlg di share atau kirim email bagaimana biar tidak terjadi aliasing ketika kita mensampling suara , kenapa waktu saya mensampling suara dengan jenis suara yang sama/kata2 yang sama, suara yg tersampling berbeda? apa benar terjadi aliasing? tlg bantuannya

  36. mas boleh minta source codenya… saya mw download filenya tidak dapat di acces..
    thanks ya tutorialnya sangat bermanfaat sekali.
    marabahaya.show@gmail.com


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.
Entries and comments feeds.