Ir al contenido



Foto

Capturar y guardar sonido en formato wav.


  • Por favor identifícate para responder
No hay respuestas en este tema

#1 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.877 mensajes
  • LocationMadrid - España

Escrito 30 octubre 2016 - 08:57

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:


delphi
  1. PWAV_HEADER = ^WAV_HEADER;
  2. WAV_HEADER = record
  3.   riff: array [0..3] of CHAR;        // RIFF string
  4.   File_size: DWORD;                  // overall size of file in bytes - 8
  5.   wave: array [0..3] of CHAR;        // WAVE string
  6.   fmt_chunk: array [0..3] of CHAR;   // fmt string with trailing null char
  7.   length_of_fmt: DWORD;              // length of the format data
  8.   format_type: WORD;                 // format type. 1-PCM, 3- IEEE float, 6 - 8bit A law, 7 - 8bit mu law
  9.   channels: WORD;                    // nº of channels
  10.   sample_rate: DWORD;                // sampling rate (blocks per second)
  11.   byterate: DWORD;                   // SampleRate * NumChannels * BitsPerSample/8
  12.   block_align: WORD;                 // NumChannels * BitsPerSample/8
  13.   bits_per_sample: WORD;             // bits per sample, 8- 8bits, 16- 16 bits etc
  14.   data_chunk: array [0..3] of CHAR;  // DATA string or FLLR string
  15.   data_size: DWORD;                  // NumSamples * NumChannels * BitsPerSample/8 - size of the next chunk that will be read
  16. 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:


delphi
  1. procedure WaveInProc(WaveIn: HWAVEIN; uMsg: UINT; Data: PWAVEIN_DATA; WaveHdr: PWAVEHDR; dwParam2: DWORD); stdcall;
  2. begin
  3.   if uMsg = WIM_DATA then
  4.   begin
  5.     WriteFile(Data.hFile, WaveHdr.lpData^, WaveHdr.dwBytesRecorded, PDWORD(0)^, nil);
  6.     if not Data.Finish then
  7.       waveInAddBuffer(WaveIn, WaveHdr, sizeof(TWAVEHDR));
  8.   end;
  9. end;
  10.  
  11. function AudioCapture(FileName: PCHAR): BOOL;
  12. var
  13.   WaveFormatEx: TWAVEFORMATEX;
  14.   WaveIn: HWAVEIN;
  15.   WaveHdr: array [0..1] of TWAVEHDR;
  16.   Data: WAVEIN_DATA;
  17.   WavHeader: WAV_HEADER;
  18.   hFile: THANDLE;
  19.   msg: TMsg;
  20.   i: integer;
  21. begin
  22.   Result:= FALSE;
  23.   WaveFormatEx.wFormatTag:= WAVE_FORMAT_PCM; // simple, uncompressed format
  24.   WaveFormatEx.nChannels:= 2;                // 1 = mono, 2 = stereo
  25.   WaveFormatEx.nSamplesPerSec:= 44100;       // sampleRate
  26.   WaveFormatEx.wBitsPerSample:= 16;          // 16 for high quality, 8 for telephone-grade
  27.   WaveFormatEx.nBlockAlign:= WaveFormatEx.nChannels * (WaveFormatEx.wBitsPerSample div 8);   //nChannels * (wBitsPerSample/8)
  28.   WaveFormatEx.nAvgBytesPerSec:= WaveFormatEx.nSamplesPerSec * WaveFormatEx.nBlockAlign;
  29.   WaveFormatEx.cbSize:= 0;
  30.  
  31.   lstrcpy(WavHeader.riff, 'RIFF');
  32.   WavHeader.File_size:= 0;
  33.   lstrcpy(WavHeader.wave, 'WAVE');
  34.   lstrcpy(WavHeader.fmt_chunk, 'fmt ');
  35.   WavHeader.length_of_fmt:= 16;
  36.   WavHeader.format_type:= WAVE_FORMAT_PCM;
  37.   WavHeader.channels:= WaveFormatEx.nChannels;
  38.   WavHeader.sample_rate:= WaveFormatEx.nSamplesPerSec;
  39.   WavHeader.byterate:=   WaveFormatEx.nAvgBytesPerSec;
  40.   WavHeader.block_align:= WaveFormatEx.nBlockAlign;
  41.   WavHeader.bits_per_sample:= WaveFormatEx.wBitsPerSample;
  42.   lstrcpy(WavHeader.data_chunk, 'data');
  43.   WavHeader.data_size:= 0;
  44.  
  45.   hFile:= CreateFile(FileName, GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  46.   if hFile <> INVALID_HANDLE_VALUE then
  47.   begin
  48.     WriteFile(hFile, WavHeader, sizeof(WAV_HEADER), PDWORD(0)^, nil);
  49.     Data.hFile:= hFile;
  50.     Data.Finish:= FALSE;
  51.  
  52.     if waveInOpen(@WaveIn, WAVE_MAPPER, @WaveFormatEx, DWORD(@WaveInProc), DWORD(@Data), CALLBACK_FUNCTION) = MMSYSERR_NOERROR then
  53.     begin
  54.       for i:=0 to 1 do
  55.       begin
  56.         WaveHdr[i].dwBufferLength:= WaveFormatEx.nAvgBytesPerSec;
  57.         WaveHdr[i].lpData:= Ptr(LocalAlloc(0, WaveHdr[i].dwBufferLength));
  58.         WaveHdr[i].dwFlags:= 0;
  59.         WaveHdr[i].dwBytesRecorded:= 0;
  60.         waveInPrepareHeader(WaveIn, @WaveHdr[i], sizeof(WAVEHDR));
  61.         waveInAddBuffer(WaveIn, @WaveHdr[i], sizeof(WAVEHDR));
  62.       end;
  63.       waveInStart(WaveIn);
  64.       Result:= TRUE;
  65.  
  66.       // Bucle de captura.
  67.       repeat
  68.         PeekMessage(msg,0,0,0, PM_REMOVE);
  69.         Data.Finish:= KeyboardEnd();
  70.         Sleep(100);
  71.       until (msg.message = WM_QUIT) or Data.Finish;
  72.  
  73.       if WaveIn <> 0 then
  74.       begin
  75.         Data.Finish:= TRUE;
  76.         waveInStop(WaveIn);
  77.         waveInReset(WaveIn);
  78.         waveInClose(WaveIn);
  79.         for i:=0 to 1 do
  80.           LocalFree(DWORD(WaveHdr[i].lpData));
  81.       end;
  82.     end;
  83.     // Completar la cabecera
  84.     WavHeader.File_size:=  GetFileSize(hFile, nil) - 8;
  85.     WavHeader.data_size:= GetFileSize(hFile, nil) - sizeof(WAV_HEADER);
  86.     SetFilePointer(hFile, 0, nil, FILE_BEGIN);
  87.     WriteFile(hFile, WavHeader, sizeof(WAV_HEADER), PDWORD(0)^, nil);
  88.     CloseHandle(hFile);
  89.   end;
  90. end;

La conversión de el código anterior a un thread es como sigue:


delphi
  1. function thCapture(var P: TH_PARAM_WAVCAPTURE): BOOL; stdcall;
  2. begin
  3.   DeleteFile(P.FileName);
  4.  
  5.   PostMessage(P.Wnd, UM_FINISH, 1, 0);
  6.   Result:= AudioCapture(P.FileName);
  7.   PostMessage(P.Wnd, UM_FINISH, 0, DWORD(Result));
  8.   ThreadId:= 0;
  9. end;
  10.  
  11. function WAVCapture(Wnd: HWND; FileName: PCHAR): BOOL;
  12. begin
  13.   Result:= FALSE;
  14.   if ThreadId = 0 then
  15.   begin
  16.     Param.Wnd:= Wnd;
  17.     lstrcpy(Param.FileName, FileName);
  18.     CloseHandle(CreateThread(nil, 0, @thCapture, @Param, 0, ThreadId));
  19.     Result:= TRUE;
  20.   end;
  21. end;                     

Mencionar una función especial para la parada de la captura:


delphi
  1. function StopWAVCapture: DWORD;
  2. var
  3.   hThread: THANDLE;
  4. begin
  5.   Result:= 0;
  6.   if ThreadId <> 0 then
  7.   begin
  8.     hThread:= OpenThread(SYNCHRONIZE or THREAD_QUERY_INFORMATION, FALSE, ThreadId);
  9.     PostThreadMessage(ThreadId, WM_QUIT, 0, 0);
  10.     WaitForSingleObject(hThread, INFINITE);
  11.     GetExitCodeThread(hThread, Result);
  12.     CloseHandle(hThread);
  13.     ThreadId:= 0;
  14.   end;
  15. 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:


delphi
  1. procedure TForm1.OnWAVCapture(var Msg: TMessage);
  2. begin
  3.   // Fin de captura
  4.   if Msg.WParam = 0 then
  5.     Windows.Beep(1000, 200);
  6.  
  7.   Button1.Enabled:= Msg.WParam = 0;
  8.   Button2.Enabled:= Msg.WParam = 1;
  9. end;

Subo el código y una pequeña aplicación de ejemplo.


Saludos.

Archivos adjuntos


  • 2