Tras publicar Capturar y guardar sonido en formato wav os presento una nueva aplicación, basada en la anterior, que captura audio en mp3. Esta basado en el encoder de Lame y su lame_enc.dll.
He preparado una unit de captura (uMP3Capture) que se puede usar con una app clásica VCL. uMP3Capture usa un thread de captura y se comunica por mensajes con la App que lo usa.
unit uMP3Capture; interface uses Windows, Messages, MMSystem; const THREAD_QUERY_INFORMATION = 64; UM_FINISH = WM_USER + 1; type PSHORT = ^SHORT; SHORT = Smallint; // Modos Lame VBR (lame_enc.dll) Tvbr_mode = ( vbr_off = 0, vbr_mt, // obsolete, same as vbr_mtrh vbr_rh, vbr_abr, vbr_mtrh, vbr_max_indicator, // Don't use this! It's used for sanity checks. vbr_default = vbr_mtrh // change this to change the default VBR mode of LAME ); // Estructura que se para a la rutina CallBack de captura WaveInProc PWAVEIN_DATA = ^WAVEIN_DATA; WAVEIN_DATA = record hFile: THANDLE; Lame: Pointer; BufferMp3: PBYTE; Finish: BOOL; end; // Parámetros pasados a el hilo (thread) de captura PTH_PARAM_WAVCAPTURE = ^TH_PARAM_WAVCAPTURE; TH_PARAM_WAVCAPTURE = record Wnd: HWND; FileName: array [0..MAX_PATH] of CHAR; end; function wsprintf(lpOut, lpFmt: PChar): Integer; cdecl; varargs; external 'User32.dll' name 'wsprintfA'; function OpenThread(DesiredAccess: DWORD; bInheritHandle: BOOL; dwThreadId: DWORD): THANDLE; stdcall; external 'Kernel32'; function GetMP3FileName(Buffer: PCHAR): PCHAR; function MP3Capture(Wnd: HWND; FileName: PCHAR): BOOL; function StopMP3Capture: DWORD; // Funciones usadas de lame_enc.dll function lame_init(): Pointer; cdecl; external 'lame_enc'; function lame_set_in_samplerate(lgf: Pointer; SampleRate: integer): integer; cdecl; external 'lame_enc'; function lame_set_VBR(lgf: Pointer; mode: Tvbr_mode): integer; cdecl; external 'lame_enc'; function lame_init_params(lgf: Pointer): integer; cdecl; external 'lame_enc'; function lame_encode_flush(lgf: Pointer; mp3buf: PBYTE; size: integer): integer; cdecl; external 'lame_enc'; function lame_encode_buffer_interleaved(lgf: Pointer; pcm: PSHORT; num_samples: integer; mp3buf: PBYTE; mp3buf_size: integer): integer; cdecl; external 'lame_enc'; function lame_close(lgf: Pointer): integer; cdecl; external 'lame_enc'; implementation var ThreadId: DWORD = 0; Param: TH_PARAM_WAVCAPTURE; function GetMP3FileName(Buffer: PCHAR): PCHAR; var Now: SYSTEMTIME; begin GetLocalTime(Now); wsprintf(Buffer, '%d%d%d%d%d.mp3', Now.wYear, Now.wMonth, Now.wDay, Now.wHour, Now.wSecond); Result:= Buffer; end; function KeyboardEnd: BOOL; begin Result:= BOOL(GetAsyncKeyState(VK_ESCAPE)); end; // Rutina CallBack de captura procedure WaveInProc(WaveIn: HWAVEIN; uMsg: UINT; Data: PWAVEIN_DATA; WaveHdr: PWAVEHDR; dwParam2: DWORD); stdcall; var MP3Writed: integer; begin if uMsg = WIM_DATA then begin // Compresión MP3 if WaveHdr.dwBytesRecorded = 0 then MP3Writed:= Lame_encode_flush(Data.Lame, Data.BufferMp3, WaveHdr.dwBufferLength div 4) else MP3Writed:= Lame_encode_buffer_interleaved(Data.Lame, PSHORT(WaveHdr.lpData), WaveHdr.dwBytesRecorded div 4, Data.BufferMp3, WaveHdr.dwBufferLength div 4); // Escritura del buffer WriteFile(Data.hFile, Data.BufferMp3^, MP3Writed, PDWORD(0)^, nil); // Siguiente Captura 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; hFile: THANDLE; msg: TMsg; i: integer; Lame: Pointer; 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; hFile:= CreateFile(FileName, GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if hFile <> INVALID_HANDLE_VALUE then begin Lame:= Lame_init; Lame_set_in_samplerate(Lame, WaveFormatEx.nSamplesPerSec); //44100 Lame_set_VBR(Lame, vbr_default); Lame_init_params(Lame); Data.BufferMp3:= Ptr(LocalAlloc(0, WaveFormatEx.nAvgBytesPerSec div 4)); Data.Lame:= Lame; 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; // Cerrando waveIn antes que Lame 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; // Cerrando Lame tras WaveIn para que pueda hacer flush Lame_close(Lame); LocalFree(DWORD(Data.BufferMp3)); end; CloseHandle(hFile); end; end; // Thread de captura 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 MP3Capture(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; function StopMP3Capture: 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; end.
Espero que sirva de ejemplo de como usar el excelente encoder de Lame, del que hago una pequeña muestra.
La app es completamente funcional, capturando el sonido de la tarjeta de sonido del PC siempre que esté configurada como dispositivo de grabación. Con el uso de las funciones presentadas de la librería de lame_enc.dll, podemos escribir sin grandes problemas un convertidor de archivos Wave -> MP3. Seguro que le encontráis utilidad.
Subo binario y fuente.
Saludos.
.