No es complicado realizar nuestras propias grabaciones de sonido capturadasdesde el propio PC, bien del micrófono o reproducidos en la tarjeta de sonido.
Windows dispone de un mecanismo para hacerlo que vamos a aprovechar. He elegido guardar en un archivo wav para simplificar el asunto. Dado que windows realiza la captura sin compresión, en formato RIFF, basta con añadir la cabecera apropiada para que el archivo consegido esté en formato wav y sea legible por cualquier reproductor de sonido.
La cabercera en cuestión es como sigue:
PWAV_HEADER = ^WAV_HEADER; WAV_HEADER = record riff: array [0..3] of CHAR; // RIFF string File_size: DWORD; // overall size of file in bytes - 8 wave: array [0..3] of CHAR; // WAVE string fmt_chunk: array [0..3] of CHAR; // fmt string with trailing null char length_of_fmt: DWORD; // length of the format data format_type: WORD; // format type. 1-PCM, 3- IEEE float, 6 - 8bit A law, 7 - 8bit mu law channels: WORD; // nº of channels sample_rate: DWORD; // sampling rate (blocks per second) byterate: DWORD; // SampleRate * NumChannels * BitsPerSample/8 block_align: WORD; // NumChannels * BitsPerSample/8 bits_per_sample: WORD; // bits per sample, 8- 8bits, 16- 16 bits etc data_chunk: array [0..3] of CHAR; // DATA string or FLLR string data_size: DWORD; // NumSamples * NumChannels * BitsPerSample/8 - size of the next chunk that will be read end;
Esta cabecera, como su nombre indica, será el comienzo del archivo, el resto será la captura que vamos a realizar y luego ajustaremos los datos correspondientes al tamaño del archivo final en la cabecera Wav, en concreto los campos File_size y data_size.
La captura la haré en un thread a bajo nivel que será exclusivo, es decir, solo puede existir uno al mismo tiempo, es lógico porque el sonido a capturar solo es uno, no hay más sonidos a la vez. La ventaja de un thread es impedir el bloqueo del hilo principal de la aplicación permitiéndonos el uso de un botón de inicio y otro de parada. La parada puede hacerse también con la tecla Escape. La comunicación entre la aplicación y el hilo de captura se hace mediante mensajes asegurando que el sistema sea thread-safe.
Sin más, os muestro el código de captura:
procedure WaveInProc(WaveIn: HWAVEIN; uMsg: UINT; Data: PWAVEIN_DATA; WaveHdr: PWAVEHDR; dwParam2: DWORD); stdcall; begin if uMsg = WIM_DATA then begin WriteFile(Data.hFile, WaveHdr.lpData^, WaveHdr.dwBytesRecorded, PDWORD(0)^, nil); if not Data.Finish then waveInAddBuffer(WaveIn, WaveHdr, sizeof(TWAVEHDR)); end; end; function AudioCapture(FileName: PCHAR): BOOL; var WaveFormatEx: TWAVEFORMATEX; WaveIn: HWAVEIN; WaveHdr: array [0..1] of TWAVEHDR; Data: WAVEIN_DATA; WavHeader: WAV_HEADER; hFile: THANDLE; msg: TMsg; i: integer; begin Result:= FALSE; WaveFormatEx.wFormatTag:= WAVE_FORMAT_PCM; // simple, uncompressed format WaveFormatEx.nChannels:= 2; // 1 = mono, 2 = stereo WaveFormatEx.nSamplesPerSec:= 44100; // sampleRate WaveFormatEx.wBitsPerSample:= 16; // 16 for high quality, 8 for telephone-grade WaveFormatEx.nBlockAlign:= WaveFormatEx.nChannels * (WaveFormatEx.wBitsPerSample div 8); //nChannels * (wBitsPerSample/8) WaveFormatEx.nAvgBytesPerSec:= WaveFormatEx.nSamplesPerSec * WaveFormatEx.nBlockAlign; WaveFormatEx.cbSize:= 0; lstrcpy(WavHeader.riff, 'RIFF'); WavHeader.File_size:= 0; lstrcpy(WavHeader.wave, 'WAVE'); lstrcpy(WavHeader.fmt_chunk, 'fmt '); WavHeader.length_of_fmt:= 16; WavHeader.format_type:= WAVE_FORMAT_PCM; WavHeader.channels:= WaveFormatEx.nChannels; WavHeader.sample_rate:= WaveFormatEx.nSamplesPerSec; WavHeader.byterate:= WaveFormatEx.nAvgBytesPerSec; WavHeader.block_align:= WaveFormatEx.nBlockAlign; WavHeader.bits_per_sample:= WaveFormatEx.wBitsPerSample; lstrcpy(WavHeader.data_chunk, 'data'); WavHeader.data_size:= 0; hFile:= CreateFile(FileName, GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if hFile <> INVALID_HANDLE_VALUE then begin WriteFile(hFile, WavHeader, sizeof(WAV_HEADER), PDWORD(0)^, nil); Data.hFile:= hFile; Data.Finish:= FALSE; if waveInOpen(@WaveIn, WAVE_MAPPER, @WaveFormatEx, DWORD(@WaveInProc), DWORD(@Data), CALLBACK_FUNCTION) = MMSYSERR_NOERROR then begin for i:=0 to 1 do begin WaveHdr[i].dwBufferLength:= WaveFormatEx.nAvgBytesPerSec; WaveHdr[i].lpData:= Ptr(LocalAlloc(0, WaveHdr[i].dwBufferLength)); WaveHdr[i].dwFlags:= 0; WaveHdr[i].dwBytesRecorded:= 0; waveInPrepareHeader(WaveIn, @WaveHdr[i], sizeof(WAVEHDR)); waveInAddBuffer(WaveIn, @WaveHdr[i], sizeof(WAVEHDR)); end; waveInStart(WaveIn); Result:= TRUE; // Bucle de captura. repeat PeekMessage(msg,0,0,0, PM_REMOVE); Data.Finish:= KeyboardEnd(); Sleep(100); until (msg.message = WM_QUIT) or Data.Finish; if WaveIn <> 0 then begin Data.Finish:= TRUE; waveInStop(WaveIn); waveInReset(WaveIn); waveInClose(WaveIn); for i:=0 to 1 do LocalFree(DWORD(WaveHdr[i].lpData)); end; end; // Completar la cabecera WavHeader.File_size:= GetFileSize(hFile, nil) - 8; WavHeader.data_size:= GetFileSize(hFile, nil) - sizeof(WAV_HEADER); SetFilePointer(hFile, 0, nil, FILE_BEGIN); WriteFile(hFile, WavHeader, sizeof(WAV_HEADER), PDWORD(0)^, nil); CloseHandle(hFile); end; end;
La conversión de el código anterior a un thread es como sigue:
function thCapture(var P: TH_PARAM_WAVCAPTURE): BOOL; stdcall; begin DeleteFile(P.FileName); PostMessage(P.Wnd, UM_FINISH, 1, 0); Result:= AudioCapture(P.FileName); PostMessage(P.Wnd, UM_FINISH, 0, DWORD(Result)); ThreadId:= 0; end; function WAVCapture(Wnd: HWND; FileName: PCHAR): BOOL; begin Result:= FALSE; if ThreadId = 0 then begin Param.Wnd:= Wnd; lstrcpy(Param.FileName, FileName); CloseHandle(CreateThread(nil, 0, @thCapture, @Param, 0, ThreadId)); Result:= TRUE; end; end;
Mencionar una función especial para la parada de la captura:
function StopWAVCapture: DWORD; var hThread: THANDLE; begin Result:= 0; if ThreadId <> 0 then begin hThread:= OpenThread(SYNCHRONIZE or THREAD_QUERY_INFORMATION, FALSE, ThreadId); PostThreadMessage(ThreadId, WM_QUIT, 0, 0); WaitForSingleObject(hThread, INFINITE); GetExitCodeThread(hThread, Result); CloseHandle(hThread); ThreadId:= 0; end; end;
Con esto tenemos una unit de captura de audio que podemos usar en cualquier aplicación. Esta aplicación deberá disponer de un evento en el que se le informa del inicio y del fin de la captura. Un ejemplo del mismo puede ser este:
procedure TForm1.OnWAVCapture(var Msg: TMessage); begin // Fin de captura if Msg.WParam = 0 then Windows.Beep(1000, 200); Button1.Enabled:= Msg.WParam = 0; Button2.Enabled:= Msg.WParam = 1; end;
Subo el código y una pequeña aplicación de ejemplo.
Saludos.