MSDN.WhiteKnight - Stack Overflow answers
Ответ на "WASAPI Запись звука"
Answer 794133
С данным кодом есть две проблемы.
Флаг AUDCLNT_BUFFERFLAGS_SILENT надо не игнорировать, а писать в буфер соответствующее количество тишины (т.е. пустых байтов). Конечно, если пустоты слишком много, можно ее подрезать для экономии размера, но всю пустоту игнорировать неправильно.
Из функции возвращается массив сырых байтов, без всякого признака формата (видимо, подразумевается PCM). Так делать не имеет особого смысла - формат в устройстве может быть любым, чаще всего это IEEE Float, но полагаться на это не стоит. Нужно сохранять и формат и данные.
Примерный код захвата 5 секунд звука с устройства и записи в WAV-файл (протестирован в MSVC++ 2012):
#include <stdio.h> #include <tchar.h> #include "windows.h" #include "mmdeviceapi.h" #include "uuids.h" #include "audioclient.h" #pragma comment(lib, "ole32.lib") struct WAVHEADER { char chunkId[4]; //RIFF unsigned long chunkSize; char format[4]; //WAVE char subchunk1Id[4]; //fmt unsigned long subchunk1Size; //18 unsigned short audioFormat; //wFormatTag unsigned short numChannels; unsigned long sampleRate; //Hz unsigned long byteRate; unsigned short blockAlign; unsigned short bitsPerSample; unsigned short cbSize; }; const UINT WAVHEADER_SIZE = 38; struct WAV_FACT_CHUNK { char FactChunkID[4]; //fact unsigned long FactChunkSize; //4 unsigned long dwSampleLength; }; struct WAV_DATA_CHUNK { char subchunk2Id[4]; //data unsigned long subchunk2Size; // Далее следуют данные... }; // REFERENCE_TIME time units per second and per millisecond #define REFTIMES_PER_SEC 10000000 #define REFTIMES_PER_MILLISEC 10000 #define EXIT_ON_ERROR(hres) \ if (FAILED(hres)) { goto Exit; } #define SAFE_RELEASE(punk) \ if ((punk) != NULL) \ { (punk)->Release(); (punk) = NULL; } const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); const IID IID_IAudioClient = __uuidof(IAudioClient); const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); //Запись 5 секунд звука в WAV-файл HRESULT RecordAudioStream(TCHAR* file) { HRESULT hr; REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC; REFERENCE_TIME hnsActualDuration; UINT32 bufferFrameCount; UINT32 numFramesAvailable; IMMDeviceEnumerator *pEnumerator = NULL; IMMDevice *pDevice = NULL; IAudioClient *pAudioClient = NULL; IAudioCaptureClient *pCaptureClient = NULL; WAVEFORMATEX *pwfx = NULL; UINT32 packetLength = 0; BOOL bDone = FALSE; BYTE *pData; DWORD flags; hr = CoCreateInstance( CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator); if(FAILED(hr))printf("CoCreateInstance failed"); EXIT_ON_ERROR(hr) hr = pEnumerator->GetDefaultAudioEndpoint( eCapture, eConsole, &pDevice); if(FAILED(hr))printf("GetDefaultAudioEndpoint failed"); EXIT_ON_ERROR(hr) hr = pDevice->Activate( IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient); EXIT_ON_ERROR(hr) //Получение формата hr = pAudioClient->GetMixFormat(&pwfx); EXIT_ON_ERROR(hr) WAVEFORMATEXTENSIBLE wex={0}; WORD wFormatTag=0; if(pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ memcpy(&wex,pwfx,sizeof(WAVEFORMATEXTENSIBLE)); wFormatTag=wex.SubFormat.Data1; } else{ wFormatTag=pwfx->wFormatTag; } //инициализация... hr = pAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, 0, hnsRequestedDuration, 0, pwfx, NULL); if(FAILED(hr))printf("Initialize failed"); EXIT_ON_ERROR(hr) // Get the size of the allocated buffer. hr = pAudioClient->GetBufferSize(&bufferFrameCount); EXIT_ON_ERROR(hr) hr = pAudioClient->GetService( IID_IAudioCaptureClient, (void**)&pCaptureClient); EXIT_ON_ERROR(hr) int counter = 0; //кол-во записанных семплов int max_samples = 5 * pwfx->nSamplesPerSec; //максимальное кол-во семплов BYTE zero = 0; //формирование заголовков WAV-файла WAVHEADER header={ {'R','I','F','F'},0, {'W','A','V','E'},{'f','m','t',' '},18,0,0,0,0,0,0,0 }; WAV_FACT_CHUNK fact={{'f','a','c','t'},4,0}; WAV_DATA_CHUNK data={{'d','a','t','a'},0}; header.chunkSize = WAVHEADER_SIZE - 8 + sizeof(WAV_FACT_CHUNK) +sizeof(WAV_DATA_CHUNK) + max_samples * pwfx->nBlockAlign; header.audioFormat = wFormatTag; header.numChannels = pwfx->nChannels; header.sampleRate = pwfx->nSamplesPerSec; header.byteRate = pwfx->nAvgBytesPerSec; header.blockAlign = pwfx->nBlockAlign; header.bitsPerSample = pwfx->wBitsPerSample; fact.dwSampleLength = max_samples * pwfx->nChannels; data.subchunk2Size = max_samples * pwfx->nBlockAlign; //запись заголовков FILE* fp = _wfopen(file,L"wb"); size_t res = fwrite(&header,WAVHEADER_SIZE,1,fp); if(res<1)printf("header fwrite error!\n"); res = fwrite(&fact,sizeof(fact),1,fp); if(res<1)printf("fact fwrite error!\n"); res = fwrite(&data,sizeof(data),1,fp); if(res<1)printf("data fwrite error!\n"); // Calculate the actual duration of the allocated buffer. hnsActualDuration = (double)REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec; hr = pAudioClient->Start(); // Start recording. EXIT_ON_ERROR(hr) // Each loop fills about half of the shared buffer. while (true) { // Sleep for half the buffer duration. Sleep(hnsActualDuration/REFTIMES_PER_MILLISEC/2); hr = pCaptureClient->GetNextPacketSize(&packetLength); EXIT_ON_ERROR(hr) while (packetLength != 0) { // Get the available data in the shared buffer. hr = pCaptureClient->GetBuffer( &pData, &numFramesAvailable, &flags, NULL, NULL); EXIT_ON_ERROR(hr) if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { for(int i=0; i<numFramesAvailable * pwfx->nBlockAlign;i++) // Tell CopyData to write silence. { res=fwrite(&zero,1,1,fp); if(res<1)printf("Can't write silence!\n"); } } else { // Copy the available capture data to the audio sink. res=fwrite(pData,pwfx->nBlockAlign,numFramesAvailable,fp); if(res<numFramesAvailable)printf("Can't write data!\n"); } hr = pCaptureClient->ReleaseBuffer(numFramesAvailable); EXIT_ON_ERROR(hr) hr = pCaptureClient->GetNextPacketSize(&packetLength); EXIT_ON_ERROR(hr) counter += numFramesAvailable; } if(counter >= max_samples) break; } printf("Finished recording\n"); hr = pAudioClient->Stop(); // Stop recording. EXIT_ON_ERROR(hr) Exit: CoTaskMemFree(pwfx); SAFE_RELEASE(pEnumerator) SAFE_RELEASE(pDevice) SAFE_RELEASE(pAudioClient) SAFE_RELEASE(pCaptureClient) return hr; } int _tmain(int argc, _TCHAR* argv[]) { CoInitialize(NULL); RecordAudioStream(L"c:\\MP3\\test.wav"); system("PAUSE"); return 0; }
Примечания
- fact chunk и последнее поле в заголовке WAV нужны, поскольку некоторые проигрыватели требуют их обязательного наличия для non-PCM форматов.
- Значение WAVHEADER_SIZE отличается от sizeof(WAVHEADER) из-за выравнивания структур до 4-байтовой границы. sizeof его учитывает, а при записи в файл оно не нужно.
- В данном примере используется формат, возвращенный GetMixFormat, так как он гарантированно может быть использован для работы с устройством в не-эксклюзивном режиме. Если есть необходимость, можно выбрать другой формат, поддерживаемый устройством.
Content is retrieved from StackExchange API.
Auto-generated by ruso-archive tools.