Juhara.com
Language : English Indonesia

Merekam Suara dengan Wave API

Zamrony P Juhara
12 September 2006 11:25:00
 (12246 views)
Tutorial bagaimana melakukan sound recording menggunakan Wave API bawaan Windows

Pendahuluan

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)

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

  • 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 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:

  • WHDR_DONE. Driver soundcard telah selesai mengisi buffer dengan data.
  • WHDR_PREPARED. Buffer telah diprepared dengan pemanggilan waveInPrepareHeader().
  • WHDR_INQUEUE. Buffer sedang dalam antrian 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", 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.

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 Enkapsulasi Proses Recording.

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:

UML diagram sound recorder class

Gbr.1 Diagram UML sound recorder.

TSoundObject

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

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

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.

Konstruktor/Destruktor

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;

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

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

  • ['RIFF'][ukuran blok RIFF (4 byte)] [data blok WAVE]
    • Blok WAVE
      • [data blok WAVE] berisi data:
        • ['WAVE'][blok Format][blok Data]
          • Blok Format
            • ['fmt '][ukuran blok Format (4 bytes)][TWaveFormatEx]
          • Blok Data
            • ['data'][ukuran blok data (4 bytes)][sample data]

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.

Artikel Terkait

Anda suka artikel ini? Bantu website ini berkembang dengan menyumbang. Berapapun jumlahnya akan sangat dihargai.

Atau Anda dapat membantu dengan membuat bookmark. Delicious Bookmark this on Delicious