DWORD CWASAPICapture::DoCaptureThread() { bool stillPlaying = true; HANDLE waitArray[2] = {_ShutdownEvent, _StreamSwitchEvent}; HANDLE mmcssHandle = NULL; DWORD mmcssTaskIndex = 0; HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); PersistentAssert(SUCCEEDED(hr), "CoInitializeEx failed"); if (!DisableMMCSS) { mmcssHandle = AvSetMmThreadCharacteristics("Audio", &mmcssTaskIndex); PersistentAssert(mmcssHandle != NULL, "AvSetMmThreadCharacteristics failed"); } while (stillPlaying) { HRESULT hr; // // In Timer Driven mode, we want to wait for half the desired latency in milliseconds. // // That way we'll wake up half way through the processing period to pull the // next set of samples from the engine. // DWORD waitResult = WaitForMultipleObjects(2, waitArray, FALSE, _EngineLatencyInMS / 2); switch (waitResult) { case WAIT_OBJECT_0 + 0: // _ShutdownEvent stillPlaying = false; // We're done, exit the loop. break; case WAIT_OBJECT_0 + 1: // _StreamSwitchEvent PersistentSignalError("StreamSwitch event unexpected"); stillPlaying = false; break; case WAIT_TIMEOUT: // Timeout // // We need to retrieve the next buffer of samples from the audio capturer. // BYTE *pData; UINT32 framesAvailable; DWORD flags; // // Find out how much capture data is available. We need to make sure we don't run over the length // of our capture buffer. We'll discard any samples that don't fit in the buffer. // UINT64 CaptureStartTime; hr = _CaptureClient->GetBuffer(&pData, &framesAvailable, &flags, NULL, &CaptureStartTime); if (SUCCEEDED(hr)) { UINT32 framesToCopy = min(framesAvailable, static_cast<UINT32>((_CaptureBufferSize - _CurrentCaptureIndex) / _FrameSize)); const UINT BytesToCopy = framesToCopy * _FrameSize; if (framesToCopy != 0) { // // The flags on capture tell us information about the data. // // We only really care about the silent flag since we want to put frames of silence into the buffer // when we receive silence. We rely on the fact that a logical bit 0 is silence for both float and int formats. // if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { // // Fill 0s from the capture buffer to the output buffer. // ZeroMemory(&_CaptureBuffer[_CurrentCaptureIndex], BytesToCopy); } else { // // Copy data from the audio engine buffer to the output buffer. // CopyMemory(&_CaptureBuffer[_CurrentCaptureIndex], pData, BytesToCopy); } // // Bump the capture buffer pointer. // if(_Compressor == NULL) { _CurrentCaptureIndex += BytesToCopy; } } hr = _CaptureClient->ReleaseBuffer(framesAvailable); PersistentAssert(SUCCEEDED(hr), "_CaptureClient->ReleaseBuffer failed"); if(_Compressor && framesToCopy != 0) { _Compressor->AudioSample32Bit2Channel((float *)_CaptureBuffer, framesToCopy, CaptureStartTime); } } break; } } if (!DisableMMCSS) { AvRevertMmThreadCharacteristics(mmcssHandle); } CoUninitialize(); return 0; }
UINT MMDeviceAudioSource::GetNextBuffer(float curVolume) { UINT captureSize = 0; HRESULT err = mmCapture->GetNextPacketSize(&captureSize); if(FAILED(err)) { RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetNextPacketSize failed")); return NoAudioAvailable; } float *outputBuffer = NULL; if(captureSize) { LPBYTE captureBuffer; DWORD dwFlags = 0; UINT numAudioFrames = 0; UINT64 devPosition; UINT64 qpcTimestamp; err = mmCapture->GetBuffer(&captureBuffer, &numAudioFrames, &dwFlags, &devPosition, &qpcTimestamp); if(FAILED(err)) { RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetBuffer failed")); return NoAudioAvailable; } QWORD newTimestamp; if(dwFlags & AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR) { RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: woa woa woa, getting timestamp errors from the audio subsystem. device = %s"), GetDeviceName().Array()); if(!bBrokenTimestamp) newTimestamp = lastUsedTimestamp + numAudioFrames*1000/inputSamplesPerSec; } else { if(!bBrokenTimestamp) newTimestamp = qpcTimestamp/10000; /*UINT64 freq; mmClock->GetFrequency(&freq); Log(TEXT("position: %llu, numAudioFrames: %u, freq: %llu, newTimestamp: %llu, test: %llu"), devPosition, numAudioFrames, freq, newTimestamp, devPosition*8000/freq);*/ } //have to do this crap to account for broken devices or device drivers. absolutely unbelievable. if(!bFirstFrameReceived) { LARGE_INTEGER clockFreq; QueryPerformanceFrequency(&clockFreq); QWORD curTime = GetQPCTimeMS(clockFreq.QuadPart); if(newTimestamp < (curTime-1000) || newTimestamp > (curTime+1000)) { bBrokenTimestamp = true; Log(TEXT("MMDeviceAudioSource::GetNextBuffer: Got bad audio timestamp offset %lld from device: '%s', timestamps for this device will be calculated. curTime: %llu, newTimestamp: %llu"), (LONGLONG)(newTimestamp - curTime), GetDeviceName().Array(), curTime, newTimestamp); lastUsedTimestamp = newTimestamp = curTime; } else lastUsedTimestamp = newTimestamp; bFirstFrameReceived = true; } if(tempBuffer.Num() < numAudioFrames*2) tempBuffer.SetSize(numAudioFrames*2); outputBuffer = tempBuffer.Array(); float *tempOut = outputBuffer; //------------------------------------------------------------ // channel upmix/downmix if(inputChannels == 1) { UINT numFloats = numAudioFrames; float *inputTemp = (float*)captureBuffer; float *outputTemp = outputBuffer; if(App->SSE2Available() && (UPARAM(inputTemp) & 0xF) == 0 && (UPARAM(outputTemp) & 0xF) == 0) { UINT alignedFloats = numFloats & 0xFFFFFFFC; for(UINT i=0; i<alignedFloats; i += 4) { __m128 inVal = _mm_load_ps(inputTemp+i); __m128 outVal1 = _mm_unpacklo_ps(inVal, inVal); __m128 outVal2 = _mm_unpackhi_ps(inVal, inVal); _mm_store_ps(outputTemp+(i*2), outVal1); _mm_store_ps(outputTemp+(i*2)+4, outVal2); } numFloats -= alignedFloats; inputTemp += alignedFloats; outputTemp += alignedFloats*2; } while(numFloats--) { float inputVal = *inputTemp; *(outputTemp++) = inputVal; *(outputTemp++) = inputVal; inputTemp++; } } else if(inputChannels == 2) //straight up copy { if(App->SSE2Available()) SSECopy(outputBuffer, captureBuffer, numAudioFrames*2*sizeof(float)); else mcpy(outputBuffer, captureBuffer, numAudioFrames*2*sizeof(float)); } else { //todo: downmix optimization, also support for other speaker configurations than ones I can merely "think" of. ugh. float *inputTemp = (float*)captureBuffer; float *outputTemp = outputBuffer; if(inputChannelMask == KSAUDIO_SPEAKER_QUAD) { UINT numFloats = numAudioFrames*4; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float rear = (inputTemp[2]+inputTemp[3])*surroundMix; *(outputTemp++) = left - rear; *(outputTemp++) = right + rear; inputTemp += 4; } } else if(inputChannelMask == KSAUDIO_SPEAKER_2POINT1) { UINT numFloats = numAudioFrames*3; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float lfe = inputTemp[2]*lowFreqMix; *(outputTemp++) = left + lfe; *(outputTemp++) = right + lfe; inputTemp += 3; } } else if(inputChannelMask == KSAUDIO_SPEAKER_4POINT1) { UINT numFloats = numAudioFrames*5; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float lfe = inputTemp[2]*lowFreqMix; float rear = (inputTemp[3]+inputTemp[4])*surroundMix; *(outputTemp++) = left + lfe - rear; *(outputTemp++) = right + lfe + rear; inputTemp += 5; } } else if(inputChannelMask == KSAUDIO_SPEAKER_SURROUND) { UINT numFloats = numAudioFrames*4; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float center = inputTemp[2]*centerMix; float rear = inputTemp[3]*(surroundMix*dbMinus3); *(outputTemp++) = left + center - rear; *(outputTemp++) = right + center + rear; inputTemp += 4; } } //don't think this will work for both else if(inputChannelMask == KSAUDIO_SPEAKER_5POINT1) { UINT numFloats = numAudioFrames*6; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float center = inputTemp[2]*centerMix; float lowFreq = inputTemp[3]*lowFreqMix; float rear = (inputTemp[4]+inputTemp[5])*surroundMix; *(outputTemp++) = left + center + lowFreq - rear; *(outputTemp++) = right + center + lowFreq + rear; inputTemp += 6; } } //todo ------------------ //not sure if my 5.1/7.1 downmixes are correct else if(inputChannelMask == KSAUDIO_SPEAKER_5POINT1_SURROUND) { UINT numFloats = numAudioFrames*6; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float center = inputTemp[2]*centerMix; float lowFreq = inputTemp[3]*lowFreqMix; float sideLeft = inputTemp[4]*dbMinus3; float sideRight = inputTemp[5]*dbMinus3; *(outputTemp++) = left + center + sideLeft + lowFreq; *(outputTemp++) = right + center + sideRight + lowFreq; inputTemp += 6; } } else if(inputChannelMask == KSAUDIO_SPEAKER_7POINT1) { UINT numFloats = numAudioFrames*8; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float center = inputTemp[2]*(centerMix*dbMinus3); float lowFreq = inputTemp[3]*lowFreqMix; float rear = (inputTemp[4]+inputTemp[5])*surroundMix; float centerLeft = inputTemp[6]*dbMinus6; float centerRight = inputTemp[7]*dbMinus6; *(outputTemp++) = left + centerLeft + center + lowFreq - rear; *(outputTemp++) = right + centerRight + center + lowFreq + rear; inputTemp += 8; } } else if(inputChannelMask == KSAUDIO_SPEAKER_7POINT1_SURROUND) { UINT numFloats = numAudioFrames*8; float *endTemp = inputTemp+numFloats; while(inputTemp < endTemp) { float left = inputTemp[0]; float right = inputTemp[1]; float center = inputTemp[2]*centerMix; float lowFreq = inputTemp[3]*lowFreqMix; float rear = (inputTemp[4]+inputTemp[5])*(surroundMix*dbMinus3); float sideLeft = inputTemp[6]*dbMinus6; float sideRight = inputTemp[7]*dbMinus6; *(outputTemp++) = left + sideLeft + center + lowFreq - rear; *(outputTemp++) = right + sideLeft + center + lowFreq + rear; inputTemp += 8; } } } mmCapture->ReleaseBuffer(numAudioFrames); //------------------------------------------------------------ // resample if(bResample) { UINT frameAdjust = UINT((double(numAudioFrames) * resampleRatio) + 1.0); UINT newFrameSize = frameAdjust*2; if(tempResampleBuffer.Num() < newFrameSize) tempResampleBuffer.SetSize(newFrameSize); SRC_DATA data; data.src_ratio = resampleRatio; data.data_in = tempBuffer.Array(); data.input_frames = numAudioFrames; data.data_out = tempResampleBuffer.Array(); data.output_frames = frameAdjust; data.end_of_input = 0; int err = src_process(resampler, &data); if(err) { RUNONCE AppWarning(TEXT("Was unable to resample audio")); return NoAudioAvailable; } if(data.input_frames_used != numAudioFrames) { RUNONCE AppWarning(TEXT("Failed to downsample buffer completely, which shouldn't actually happen because it should be using 10ms of samples")); return NoAudioAvailable; } numAudioFrames = data.output_frames_gen; } //----------------------------------------------------------------------------- // sort all audio frames into 10 millisecond increments (done because not all devices output in 10ms increments) // NOTE: 0.457+ - instead of using the timestamps from windows, just compare and make sure it stays within a 100ms of their timestamps float *newBuffer = (bResample) ? tempResampleBuffer.Array() : tempBuffer.Array(); if(storageBuffer.Num() == 0 && numAudioFrames == 441) { lastUsedTimestamp += 10; if(!bBrokenTimestamp) { QWORD difVal = GetQWDif(newTimestamp, lastUsedTimestamp); if(difVal > 70) lastUsedTimestamp = newTimestamp; } if(lastUsedTimestamp > lastSentTimestamp) { QWORD adjustVal = (lastUsedTimestamp-lastSentTimestamp); if(adjustVal < 10) lastUsedTimestamp += 10-adjustVal; AudioSegment &newSegment = *audioSegments.CreateNew(); newSegment.audioData.CopyArray(newBuffer, numAudioFrames*2); newSegment.timestamp = lastUsedTimestamp; MultiplyAudioBuffer(newSegment.audioData.Array(), numAudioFrames*2, curVolume); lastSentTimestamp = lastUsedTimestamp; } } else { UINT storedFrames = storageBuffer.Num(); storageBuffer.AppendArray(newBuffer, numAudioFrames*2); if(storageBuffer.Num() >= (441*2)) { lastUsedTimestamp += 10; if(!bBrokenTimestamp) { QWORD difVal = GetQWDif(newTimestamp, lastUsedTimestamp); if(difVal > 70) lastUsedTimestamp = newTimestamp - (QWORD(storedFrames)/2*1000/44100); } //------------------------ // add new data if(lastUsedTimestamp > lastSentTimestamp) { QWORD adjustVal = (lastUsedTimestamp-lastSentTimestamp); if(adjustVal < 10) lastUsedTimestamp += 10-adjustVal; AudioSegment &newSegment = *audioSegments.CreateNew(); newSegment.audioData.CopyArray(storageBuffer.Array(), (441*2)); newSegment.timestamp = lastUsedTimestamp; MultiplyAudioBuffer(newSegment.audioData.Array(), 441*2, curVolume); storageBuffer.RemoveRange(0, (441*2)); } //------------------------ // if still data pending (can happen) while(storageBuffer.Num() >= (441*2)) { lastUsedTimestamp += 10; if(lastUsedTimestamp > lastSentTimestamp) { QWORD adjustVal = (lastUsedTimestamp-lastSentTimestamp); if(adjustVal < 10) lastUsedTimestamp += 10-adjustVal; AudioSegment &newSegment = *audioSegments.CreateNew(); newSegment.audioData.CopyArray(storageBuffer.Array(), (441*2)); storageBuffer.RemoveRange(0, (441*2)); MultiplyAudioBuffer(newSegment.audioData.Array(), 441*2, curVolume); newSegment.timestamp = lastUsedTimestamp; lastSentTimestamp = lastUsedTimestamp; } } } } //----------------------------------------------------------------------------- return ContinueAudioRequest; } return NoAudioAvailable; }
bool MMDeviceAudioSource::GetNextBuffer(void **buffer, UINT *numFrames, QWORD *timestamp) { UINT captureSize = 0; bool bFirstRun = true; HRESULT hRes; UINT64 devPosition, qpcTimestamp; LPBYTE captureBuffer; UINT32 numFramesRead; DWORD dwFlags = 0; while (true) { if (inputBufferSize >= sampleWindowSize*GetChannelCount()) { if (bFirstRun) { lastQPCTimestamp += 10; } else if (bIsMic && !bUseQPC) { captureSize = 0; mmCapture->GetNextPacketSize(&captureSize); //throws away worthless mic data that's sampling faster than the desktop buffer. //disgusting fix for stupid worthless mic issues. if (captureSize > 0) { ++numTimesInARowNewDataSeen; if (numTimesInARowNewDataSeen > angerThreshold) { if (SUCCEEDED(mmCapture->GetBuffer(&captureBuffer, &numFramesRead, &dwFlags, &devPosition, &qpcTimestamp))) { mmCapture->ReleaseBuffer(numFramesRead); numTimesInARowNewDataSeen = 0; } } } else { numTimesInARowNewDataSeen = 0; } } firstTimestamp = GetTimestamp(lastQPCTimestamp); break; } //--------------------------------------------------------- hRes = mmCapture->GetNextPacketSize(&captureSize); if (FAILED(hRes)) { RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetNextPacketSize failed, result = %08lX"), hRes); return false; } if (!captureSize) return false; //--------------------------------------------------------- hRes = mmCapture->GetBuffer(&captureBuffer, &numFramesRead, &dwFlags, &devPosition, &qpcTimestamp); if (FAILED(hRes)) { RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetBuffer failed, result = %08lX"), hRes); return false; } UINT totalFloatsRead = numFramesRead*GetChannelCount(); if (bConvert) { if (convertBuffer.Num() < totalFloatsRead) convertBuffer.SetSize(totalFloatsRead); short *shortBuffer = (short*)captureBuffer; for (UINT i = 0; i < totalFloatsRead; i++) convertBuffer[i] = float(shortBuffer[i])*(1.0f/32767.0f); captureBuffer = (LPBYTE)convertBuffer.Array(); } if (inputBufferSize) { double timeAdjust = double(inputBufferSize/GetChannelCount()); timeAdjust /= (double(GetSamplesPerSec())*0.0000001); qpcTimestamp -= UINT64(timeAdjust); } qpcTimestamp /= 10000; lastQPCTimestamp = qpcTimestamp; //--------------------------------------------------------- UINT newInputBufferSize = inputBufferSize + totalFloatsRead; if (newInputBufferSize > inputBuffer.Num()) inputBuffer.SetSize(newInputBufferSize); mcpy(inputBuffer.Array()+inputBufferSize, captureBuffer, totalFloatsRead*sizeof(float)); inputBufferSize = newInputBufferSize; mmCapture->ReleaseBuffer(numFramesRead); bFirstRun = false; } *numFrames = sampleWindowSize; *buffer = (void*)inputBuffer.Array(); *timestamp = firstTimestamp; /*if (bIsMic) { static QWORD lastTimestamp = 0; if (firstTimestamp != lastTimestamp+10) Log(TEXT("A: %llu, difference: %llu"), firstTimestamp, firstTimestamp-lastTimestamp); lastTimestamp = firstTimestamp; }*/ return true; }
bool MMDeviceAudioSource::GetNextBuffer(void **buffer, UINT *numFrames, QWORD *timestamp) { UINT captureSize = 0; bool bFirstRun = true; HRESULT hRes; while (true) { if (inputBufferSize >= sampleWindowSize*GetChannelCount()) { if (bFirstRun) lastQPCTimestamp += 10; firstTimestamp = GetTimestamp(lastQPCTimestamp); break; } //--------------------------------------------------------- hRes = mmCapture->GetNextPacketSize(&captureSize); if (FAILED(hRes)) { RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetNextPacketSize failed, result = %08lX"), hRes); return false; } if (!captureSize) return false; //--------------------------------------------------------- LPBYTE captureBuffer; UINT32 numFramesRead; DWORD dwFlags = 0; UINT64 devPosition, qpcTimestamp; hRes = mmCapture->GetBuffer(&captureBuffer, &numFramesRead, &dwFlags, &devPosition, &qpcTimestamp); if (FAILED(hRes)) { RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetBuffer failed, result = %08lX"), hRes); return false; } if (inputBufferSize) { double timeAdjust = double(inputBufferSize/GetChannelCount()); timeAdjust /= (double(GetSamplesPerSec())*0.0000001); qpcTimestamp -= UINT64(timeAdjust); } /*if (!bIsMic) { Log(TEXT("f: %u, i: %u, qpc: %llu"), numFramesRead, inputBufferSize != 0, qpcTimestamp); }*/ qpcTimestamp /= 10000; lastQPCTimestamp = qpcTimestamp; //--------------------------------------------------------- UINT totalFloatsRead = numFramesRead*GetChannelCount(); UINT newInputBufferSize = inputBufferSize + totalFloatsRead; if (newInputBufferSize > inputBuffer.Num()) inputBuffer.SetSize(newInputBufferSize); SSECopy(inputBuffer.Array()+inputBufferSize, captureBuffer, totalFloatsRead*sizeof(float)); inputBufferSize = newInputBufferSize; mmCapture->ReleaseBuffer(numFramesRead); bFirstRun = false; } *numFrames = sampleWindowSize; *buffer = (void*)inputBuffer.Array(); *timestamp = firstTimestamp; return true; }
bool MMDeviceAudioSource::GetNextBuffer(void **buffer, UINT *numFrames, QWORD *timestamp) { UINT captureSize = 0; bool bFirstRun = true; HRESULT hRes; UINT64 devPosition, qpcTimestamp; LPBYTE captureBuffer; UINT32 numFramesRead; DWORD dwFlags = 0; if (deviceLost) { QWORD timeVal = GetQPCTimeMS(); QWORD timer = (timeVal - reinitTimer); if (timer > 1000) { if (Reinitialize()) { Log(L"Device '%s' reacquired.", strDeviceName.Array()); StartCapture(); } reinitTimer = timeVal; } return false; } while (true) { if (inputBufferSize >= sampleWindowSize*GetChannelCount()) { if (bFirstRun) lastQPCTimestamp += 10; firstTimestamp = GetTimestamp(lastQPCTimestamp); break; } //--------------------------------------------------------- hRes = mmCapture->GetNextPacketSize(&captureSize); if (FAILED(hRes)) { if (hRes == AUDCLNT_E_DEVICE_INVALIDATED) { FreeData(); deviceLost = true; Log(L"Audio device '%s' has been lost, attempting to reinitialize", strDeviceName.Array()); reinitTimer = GetQPCTimeMS(); return false; } RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetNextPacketSize failed, result = %08lX"), hRes); return false; } if (!captureSize) return false; //--------------------------------------------------------- hRes = mmCapture->GetBuffer(&captureBuffer, &numFramesRead, &dwFlags, &devPosition, &qpcTimestamp); if (FAILED(hRes)) { RUNONCE AppWarning(TEXT("MMDeviceAudioSource::GetBuffer: GetBuffer failed, result = %08lX"), hRes); return false; } UINT totalFloatsRead = numFramesRead*GetChannelCount(); if (inputBufferSize) { double timeAdjust = double(inputBufferSize/GetChannelCount()); timeAdjust /= (double(GetSamplesPerSec())*0.0000001); qpcTimestamp -= UINT64(timeAdjust); } qpcTimestamp /= 10000; lastQPCTimestamp = qpcTimestamp; //--------------------------------------------------------- UINT newInputBufferSize = inputBufferSize + totalFloatsRead; if (newInputBufferSize > inputBuffer.Num()) inputBuffer.SetSize(newInputBufferSize); mcpy(inputBuffer.Array()+inputBufferSize, captureBuffer, totalFloatsRead*sizeof(float)); inputBufferSize = newInputBufferSize; mmCapture->ReleaseBuffer(numFramesRead); bFirstRun = false; } *numFrames = sampleWindowSize; *buffer = (void*)inputBuffer.Array(); *timestamp = firstTimestamp; /*if (bIsMic) { static QWORD lastTimestamp = 0; if (firstTimestamp != lastTimestamp+10) Log(TEXT("A: %llu, difference: %llu"), firstTimestamp, firstTimestamp-lastTimestamp); lastTimestamp = firstTimestamp; }*/ return true; }
void LoopbackCaptureFor(IMMDevice* mmDevice, std::string filename, int secs) { // open new file MMIOINFO mi = { 0 }; // some flags cause mmioOpen write to this buffer // but not any that we're using std::wstring wsFilename(filename.begin(), filename.end()); // mmioOpen wants a wstring HMMIO file = mmioOpen(const_cast<LPWSTR>(wsFilename.c_str()), &mi, MMIO_WRITE | MMIO_CREATE); time_t startTime = time(nullptr); // activate an IAudioClient IAudioClient* audioClient; HRESULT hr = mmDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&audioClient); if (FAILED(hr)) { fprintf(stderr, "IMMDevice::Activate(IAudioClient) failed: hr = 0x%08x", hr); return; } // get the default device periodicity REFERENCE_TIME hnsDefaultDevicePeriod; hr = audioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, nullptr); if (FAILED(hr)) { fprintf(stderr, "IAudioClient::GetDevicePeriod failed: hr = 0x%08x\n", hr); audioClient->Release(); return; } // get the default device format WAVEFORMATEX* waveform; hr = audioClient->GetMixFormat(&waveform); if (FAILED(hr)) { fprintf(stderr, "IAudioClient::GetMixFormat failed: hr = 0x%08x\n", hr); CoTaskMemFree(waveform); audioClient->Release(); return; } // coerce int-16 wave format // can do this in-place since we're not changing the size of the format // also, the engine will auto-convert from float to int for us switch (waveform->wFormatTag) { case WAVE_FORMAT_IEEE_FLOAT: waveform->wFormatTag = WAVE_FORMAT_PCM; waveform->wBitsPerSample = BITS_PER_SAMPLE; waveform->nBlockAlign = BLOCK_ALIGN; waveform->nAvgBytesPerSec = BYTE_RATE; break; case WAVE_FORMAT_EXTENSIBLE: { // naked scope for case-local variable PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(waveform); if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat)) { pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; pEx->Samples.wValidBitsPerSample = BITS_PER_SAMPLE; waveform->wBitsPerSample = BITS_PER_SAMPLE; waveform->nBlockAlign = waveform->nChannels * BYTE_PER_SAMPLE; waveform->nAvgBytesPerSec = waveform->nBlockAlign * waveform->nSamplesPerSec; } break; } } MMCKINFO ckRIFF = { 0 }; MMCKINFO ckData = { 0 }; hr = WriteWaveHeader(file, waveform, &ckRIFF, &ckData); // create a periodic waitable timer HANDLE hWakeUp = CreateWaitableTimer(nullptr, FALSE, nullptr); UINT32 nBlockAlign = waveform->nBlockAlign; // call IAudioClient::Initialize // note that AUDCLNT_STREAMFLAGS_LOOPBACK and AUDCLNT_STREAMFLAGS_EVENTCALLBACK // do not work together... // the "data ready" event never gets set // so we're going to do a timer-driven loop hr = audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, 0, 0, waveform, 0); if (FAILED(hr)) { fprintf(stderr, "IAudioClient::Initialize failed: hr = 0x%08x\n", hr); CloseHandle(hWakeUp); audioClient->Release(); return; } // free up waveform CoTaskMemFree(waveform); // activate an IAudioCaptureClient IAudioCaptureClient* audioCaptureClient; hr = audioClient->GetService(__uuidof(IAudioCaptureClient), (void**)&audioCaptureClient); // register with MMCSS DWORD nTaskIndex = 0; HANDLE hTask = AvSetMmThreadCharacteristics(L"Capture", &nTaskIndex); if (hTask == nullptr) { DWORD dwErr = GetLastError(); fprintf(stderr, "AvSetMmThreadCharacteristics failed: last error = %u\n", dwErr); audioCaptureClient->Release(); CloseHandle(hWakeUp); audioClient->Release(); return; } // set the waitable timer LARGE_INTEGER liFirstFire; liFirstFire.QuadPart = -hnsDefaultDevicePeriod / 2; // negative means relative time LONG lTimeBetweenFires = (LONG)hnsDefaultDevicePeriod / 2 / (10 * 1000); // convert to milliseconds if (!SetWaitableTimer(hWakeUp, &liFirstFire, lTimeBetweenFires, nullptr, nullptr, FALSE)) { DWORD dwErr = GetLastError(); fprintf(stderr, "SetWaitableTimer failed: last error = %u\n", dwErr); AvRevertMmThreadCharacteristics(hTask); audioCaptureClient->Release(); CloseHandle(hWakeUp); audioClient->Release(); return; } // call IAudioClient::Start hr = audioClient->Start(); // loopback capture loop DWORD dwWaitResult; UINT32 frames = 0; for (UINT32 passes = 0; ; passes++) { // drain data while it is available UINT32 nextPacketSize; for (hr = audioCaptureClient->GetNextPacketSize(&nextPacketSize); SUCCEEDED(hr) && nextPacketSize > 0; hr = audioCaptureClient->GetNextPacketSize(&nextPacketSize)) { // get the captured data BYTE* data; UINT32 framesToRead; DWORD dwFlags; hr = audioCaptureClient->GetBuffer(&data, &framesToRead, &dwFlags, nullptr, nullptr); if (FAILED(hr)) { fprintf(stderr, "IAudioCaptureClient::GetBuffer failed on pass %u after %u frames: hr = 0x%08x\n", passes, frames, hr); audioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); audioCaptureClient->Release(); CloseHandle(hWakeUp); audioClient->Release(); return; } // this type of error seems to happen often, ignore it if (dwFlags == AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) ; else if (dwFlags != 0) { fprintf(stderr, "IAudioCaptureClient::GetBuffer set flags to 0x%08x on pass %u after %u frames\n", dwFlags, passes, frames); audioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); audioCaptureClient->Release(); CloseHandle(hWakeUp); audioClient->Release(); return; } if (framesToRead == 0) { fprintf(stderr, "IAudioCaptureClient::GetBuffer said to read 0 frames on pass %u after %u frames\n", passes, frames); audioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); audioCaptureClient->Release(); CloseHandle(hWakeUp); audioClient->Release(); return; } LONG lBytesToWrite = framesToRead * nBlockAlign; #pragma prefast(suppress: __WARNING_INCORRECT_ANNOTATION, "IAudioCaptureClient::GetBuffer SAL annotation implies a 1-byte buffer") LONG lBytesWritten = mmioWrite(file, reinterpret_cast<PCHAR>(data), lBytesToWrite); if (lBytesToWrite != lBytesWritten) { fprintf(stderr, "mmioWrite wrote %u bytes on pass %u after %u frames: expected %u bytes\n", lBytesWritten, passes, frames, lBytesToWrite); audioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); audioCaptureClient->Release(); CloseHandle(hWakeUp); audioClient->Release(); return; } frames += framesToRead; hr = audioCaptureClient->ReleaseBuffer(framesToRead); } dwWaitResult = WaitForSingleObject(hWakeUp, INFINITE); if (time(nullptr) - startTime > secs) break; } FinishWaveFile(file, &ckData, &ckRIFF); audioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); audioCaptureClient->Release(); CloseHandle(hWakeUp); audioClient->Release(); // everything went well... fixup the fact chunk in the file MMRESULT result = mmioClose(file, 0); file = nullptr; if (result != MMSYSERR_NOERROR) { fprintf(stderr, "mmioClose failed: MMSYSERR = %u\n", result); return; } // reopen the file in read/write mode mi = { 0 }; file = mmioOpen(const_cast<LPWSTR>(wsFilename.c_str()), &mi, MMIO_READWRITE); if (file == nullptr) { fprintf(stderr, "mmioOpen(\"%ls\", ...) failed. wErrorRet == %u\n", filename, mi.wErrorRet); return; } // descend into the RIFF/WAVE chunk ckRIFF = { 0 }; ckRIFF.ckid = MAKEFOURCC('W', 'A', 'V', 'E'); // this is right for mmioDescend result = mmioDescend(file, &ckRIFF, nullptr, MMIO_FINDRIFF); if (result != MMSYSERR_NOERROR) { fprintf(stderr, "mmioDescend(\"WAVE\") failed: MMSYSERR = %u\n", result); return; } // descend into the fact chunk MMCKINFO ckFact = { 0 }; ckFact.ckid = MAKEFOURCC('f', 'a', 'c', 't'); result = mmioDescend(file, &ckFact, &ckRIFF, MMIO_FINDCHUNK); if (result != MMSYSERR_NOERROR) { fprintf(stderr, "mmioDescend(\"fact\") failed: MMSYSERR = %u\n", result); return; } // write the correct data to the fact chunk LONG lBytesWritten = mmioWrite(file, reinterpret_cast<PCHAR>(&frames), sizeof(frames)); if (lBytesWritten != sizeof(frames)) { fprintf(stderr, "Updating the fact chunk wrote %u bytes; expected %u\n", lBytesWritten, (UINT32)sizeof(frames)); return; } // ascend out of the fact chunk result = mmioAscend(file, &ckFact, 0); if (result != MMSYSERR_NOERROR) fprintf(stderr, "mmioAscend(\"fact\") failed: MMSYSERR = %u\n", result); }
//HRESULT LoopbackCapture( // IMMDevice *pMMDevice, // bool bInt16, // HANDLE hStartedEvent, // HANDLE hStopEvent, // PUINT32 pnFrames, // HMMIO hFile, // AudioBuffer *pBuffer //) HRESULT LoopbackCapture::Process() { HRESULT hr; // activate an IAudioClient IAudioClient *pAudioClient; hr = pMMDevice->Activate( __uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&pAudioClient ); if (FAILED(hr)) { printf("IMMDevice::Activate(IAudioClient) failed: hr = 0x%08x", hr); return hr; } // get the default device periodicity REFERENCE_TIME hnsDefaultDevicePeriod; hr = pAudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, NULL); if (FAILED(hr)) { printf("IAudioClient::GetDevicePeriod failed: hr = 0x%08x\n", hr); pAudioClient->Release(); return hr; } // get the default device format WAVEFORMATEX *pwfx; hr = pAudioClient->GetMixFormat(&pwfx); if (FAILED(hr)) { printf("IAudioClient::GetMixFormat failed: hr = 0x%08x\n", hr); CoTaskMemFree(pwfx); pAudioClient->Release(); return hr; } if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(pwfx); //pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; printf("WAVE_FORMAT_EXTENSIBLE\n"); if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat)) { printf("float\n"); }// else if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_PCM, pEx->SubFormat)) { printf("PCM\n"); }//KSDATAFORMAT_SUBTYPE_WAVEFORMATEX else if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_WAVEFORMATEX, pEx->SubFormat)) { printf("WAVEFORMATEX\n"); } } if (bInt16) { // coerce int-16 wave format // can do this in-place since we're not changing the size of the format // also, the engine will auto-convert from float to int for us switch (pwfx->wFormatTag) { case WAVE_FORMAT_IEEE_FLOAT: pwfx->wFormatTag = WAVE_FORMAT_PCM; pwfx->wBitsPerSample = 16; pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; break; case WAVE_FORMAT_EXTENSIBLE: { // naked scope for case-local variable PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(pwfx); if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat)) { pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; pEx->Samples.wValidBitsPerSample = 16; pwfx->wBitsPerSample = 16; pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; } else { printf("Don't know how to coerce mix format to int-16\n"); CoTaskMemFree(pwfx); pAudioClient->Release(); return E_UNEXPECTED; } } break; default: printf("Don't know how to coerce WAVEFORMATEX with wFormatTag = 0x%08x to int-16\n", pwfx->wFormatTag); CoTaskMemFree(pwfx); pAudioClient->Release(); return E_UNEXPECTED; } } MMCKINFO ckRIFF = {0}; MMCKINFO ckData = {0}; if (hFile!=NULL) hr = WriteWaveHeader(hFile, pwfx, &ckRIFF, &ckData); if (pBuffer) { bool isFloat = false; switch (pwfx->wFormatTag) { case WAVE_FORMAT_IEEE_FLOAT: isFloat = true; break; case WAVE_FORMAT_EXTENSIBLE: { // naked scope for case-local variable PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(pwfx); if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat)) { isFloat = true; } } break; default: break; } pBuffer->SetAudioInfo(pwfx->nSamplesPerSec,pwfx->nBlockAlign,pwfx->nChannels,pwfx->wBitsPerSample,isFloat); } if (FAILED(hr)) { // WriteWaveHeader does its own logging CoTaskMemFree(pwfx); pAudioClient->Release(); return hr; } // create a periodic waitable timer HANDLE hWakeUp = CreateWaitableTimer(NULL, FALSE, NULL); if (NULL == hWakeUp) { DWORD dwErr = GetLastError(); printf("CreateWaitableTimer failed: last error = %u\n", dwErr); CoTaskMemFree(pwfx); pAudioClient->Release(); return HRESULT_FROM_WIN32(dwErr); } UINT32 nBlockAlign = pwfx->nBlockAlign; UINT32 nChannels = pwfx->nChannels; nFrames = 0; // call IAudioClient::Initialize // note that AUDCLNT_STREAMFLAGS_LOOPBACK and AUDCLNT_STREAMFLAGS_EVENTCALLBACK // do not work together... // the "data ready" event never gets set // so we're going to do a timer-driven loop hr = pAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, 0, 0, pwfx, 0 ); if (FAILED(hr)) { printf("IAudioClient::Initialize failed: hr = 0x%08x\n", hr); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } CoTaskMemFree(pwfx); // activate an IAudioCaptureClient IAudioCaptureClient *pAudioCaptureClient; hr = pAudioClient->GetService( __uuidof(IAudioCaptureClient), (void**)&pAudioCaptureClient ); if (FAILED(hr)) { printf("IAudioClient::GetService(IAudioCaptureClient) failed: hr 0x%08x\n", hr); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } // register with MMCSS DWORD nTaskIndex = 0; HANDLE hTask = AvSetMmThreadCharacteristics(L"Capture", &nTaskIndex); if (NULL == hTask) { DWORD dwErr = GetLastError(); printf("AvSetMmThreadCharacteristics failed: last error = %u\n", dwErr); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return HRESULT_FROM_WIN32(dwErr); } // set the waitable timer LARGE_INTEGER liFirstFire; liFirstFire.QuadPart = -hnsDefaultDevicePeriod / 2; // negative means relative time LONG lTimeBetweenFires = (LONG)hnsDefaultDevicePeriod / 2 / (10 * 1000); // convert to milliseconds BOOL bOK = SetWaitableTimer( hWakeUp, &liFirstFire, lTimeBetweenFires, NULL, NULL, FALSE ); if (!bOK) { DWORD dwErr = GetLastError(); printf("SetWaitableTimer failed: last error = %u\n", dwErr); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return HRESULT_FROM_WIN32(dwErr); } // call IAudioClient::Start hr = pAudioClient->Start(); if (FAILED(hr)) { printf("IAudioClient::Start failed: hr = 0x%08x\n", hr); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } SetEvent(hStartedEvent); // loopback capture loop HANDLE waitArray[2] = { hStopEvent, hWakeUp }; DWORD dwWaitResult; DWORD immdState; bool bDone = false; bool bFirstPacket = true; for (UINT32 nPasses = 0; !bDone; nPasses++) { dwWaitResult = WaitForMultipleObjects( ARRAYSIZE(waitArray), waitArray, FALSE, INFINITE ); if (WAIT_OBJECT_0 == dwWaitResult) { //printf("Received stop event after %u passes and %u frames\n", nPasses, nFrames); bDone = true; continue; // exits loop } if (WAIT_OBJECT_0 + 1 != dwWaitResult) { printf("Unexpected WaitForMultipleObjects return value %u on pass %u after %u frames\n", dwWaitResult, nPasses, nFrames); pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return E_UNEXPECTED; } printf("'"); // got a "wake up" event - see if there's data UINT32 nNextPacketSize; hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize); if (FAILED(hr)) { if (hr == AUDCLNT_E_SERVICE_NOT_RUNNING) printf("AUDCLNT_E_SERVICE_NOT_RUNNING : \n"); else if (hr == AUDCLNT_E_DEVICE_INVALIDATED) printf("AUDCLNT_E_DEVICE_INVALIDATED : \n"); else printf("UNKNOWN ERROR!!! : \n"); printf("IAudioCaptureClient::GetNextPacketSize failed on pass %u after %u frames: hr = 0x%08x\n", nPasses, nFrames, hr); pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } if (0 == nNextPacketSize) { // no data yet continue; } // get the captured data BYTE *pData; UINT32 nNumFramesToRead; DWORD dwFlags; hr = pAudioCaptureClient->GetBuffer( &pData, &nNumFramesToRead, &dwFlags, NULL, NULL ); if (FAILED(hr)) { printf("IAudioCaptureClient::GetBuffer failed on pass %u after %u frames: hr = 0x%08x\n", nPasses, nFrames, hr); pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } if (bFirstPacket && AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY == dwFlags) { printf("Probably spurious glitch reported on first packet\n"); } else if (dwFlags & AUDCLNT_BUFFERFLAGS_SILENT) { printf("#"); } else if (dwFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) { printf("!"); } else if (0 != dwFlags) { printf("IAudioCaptureClient::GetBuffer set flags to 0x%08x on pass %u after %u frames\n", dwFlags, nPasses, nFrames); pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return E_UNEXPECTED; } if (0 == nNumFramesToRead) { // no data yet continue; } //if (0 == nNumFramesToRead) { // printf("IAudioCaptureClient::GetBuffer said to read 0 frames on pass %u after %u frames\n", nPasses, nFrames); // pAudioClient->Stop(); // CancelWaitableTimer(hWakeUp); // AvRevertMmThreadCharacteristics(hTask); // pAudioCaptureClient->Release(); // CloseHandle(hWakeUp); // pAudioClient->Release(); // return E_UNEXPECTED; //} LONG lBytesToWrite = nNumFramesToRead * nBlockAlign; #pragma prefast(suppress: __WARNING_INCORRECT_ANNOTATION, "IAudioCaptureClient::GetBuffer SAL annotation implies a 1-byte buffer") if (hFile!=NULL) { LONG lBytesWritten = mmioWrite(hFile, reinterpret_cast<PCHAR>(pData), lBytesToWrite); if (lBytesToWrite != lBytesWritten) { printf("mmioWrite wrote %u bytes on pass %u after %u frames: expected %u bytes\n", lBytesWritten, nPasses, nFrames, lBytesToWrite); pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return E_UNEXPECTED; } } if (pBuffer) { //switch (nBlockAlign/nChannels) //{ //case 1: // ShowPCM((unsigned char*)pData,nNumFramesToRead,nChannels,1024,60,"SYS_Byte"); // break; //case 2: // ShowPCM((short*)pData,nNumFramesToRead,nChannels,1024,60,"SYS_Short"); // break; //case 4: // ShowPCM((int*)pData,nNumFramesToRead,nChannels,1024,60,"SYS_Int"); // //ShowPCM((float*)pData,nNumFramesToRead,nChannels,1024,60,"SYS_float"); // break; //} pBuffer->PushBuffer(pData,lBytesToWrite); } nFrames += nNumFramesToRead; hr = pAudioCaptureClient->ReleaseBuffer(nNumFramesToRead); if (FAILED(hr)) { printf("IAudioCaptureClient::ReleaseBuffer failed on pass %u after %u frames: hr = 0x%08x\n", nPasses, nFrames, hr); pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } bFirstPacket = false; } // capture loop if (hFile!=NULL) hr = FinishWaveFile(hFile, &ckData, &ckRIFF); if (FAILED(hr)) { // FinishWaveFile does it's own logging pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; }
int main(int argc, char *argv[]) { CoInitialize(nullptr); listDevices(); IAudioClient *pAudioClient; IMMDevice *device; getDefaultDevice(&device); HRESULT hr = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&pAudioClient); if (FAILED(hr)) { printf("IMMDevice::Activate(IAudioClient) failed: hr = 0x%08x", hr); return hr; } REFERENCE_TIME hnsDefaultDevicePeriod; hr = pAudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, nullptr); if (FAILED(hr)) { printf("IAudioClient::GetDevicePeriod failed: hr = 0x%08x\n", hr); pAudioClient->Release(); return hr; } // get the default device format WAVEFORMATEX *pwfx; hr = pAudioClient->GetMixFormat(&pwfx); if (FAILED(hr)) { printf("IAudioClient::GetMixFormat failed: hr = 0x%08x\n", hr); CoTaskMemFree(pwfx); pAudioClient->Release(); return hr; } DVAR(pwfx->wFormatTag); DVAR(pwfx->wBitsPerSample); DVAR(pwfx->nBlockAlign); DVAR(pwfx->nAvgBytesPerSec); switch (pwfx->wFormatTag) { case WAVE_FORMAT_IEEE_FLOAT: pwfx->wFormatTag = WAVE_FORMAT_PCM; pwfx->wBitsPerSample = 16; pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; break; case WAVE_FORMAT_EXTENSIBLE: { // naked scope for case-local variable PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(pwfx); if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat)) { pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; pEx->Samples.wValidBitsPerSample = 16; pwfx->wBitsPerSample = 16; pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; } else { printf("Don't know how to coerce mix format to int-16\n"); CoTaskMemFree(pwfx); pAudioClient->Release(); return E_UNEXPECTED; } } break; default: printf("Don't know how to coerce WAVEFORMATEX with wFormatTag = 0x%08x to int-16\n", pwfx->wFormatTag); CoTaskMemFree(pwfx); pAudioClient->Release(); return E_UNEXPECTED; } DVAR(pwfx->wFormatTag); DVAR(pwfx->wBitsPerSample); DVAR(pwfx->nBlockAlign); DVAR(pwfx->nAvgBytesPerSec); hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, 0, 0, pwfx, 0 ); if (FAILED(hr)) { printf("IAudioClient::Initialize failed: hr = 0x%08x\n", hr); pAudioClient->Release(); return hr; } IAudioCaptureClient *pAudioCaptureClient; hr = pAudioClient->GetService(__uuidof(IAudioCaptureClient), (void**)&pAudioCaptureClient); if (FAILED(hr)) { printf("IAudioClient::GetService(IAudioCaptureClient) failed: hr 0x%08x\n", hr); pAudioClient->Release(); return hr; } hr = pAudioClient->Start(); if (FAILED(hr)) { printf("IAudioClient::Start failed: hr = 0x%08x\n", hr); pAudioCaptureClient->Release(); pAudioClient->Release(); return hr; } for (int i = 0; i < 10; ++i) { UINT32 nNextPacketSize; hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize); if (FAILED(hr)) { printf("IAudioCaptureClient::GetNextPacketSize failed on pass %u after %u frames: hr = 0x%08x\n", 0, 0, hr); pAudioClient->Stop(); pAudioCaptureClient->Release(); pAudioClient->Release(); return hr; } // get the captured data BYTE *pData; UINT32 nNumFramesToRead; DWORD dwFlags; hr = pAudioCaptureClient->GetBuffer(&pData, &nNumFramesToRead, &dwFlags, nullptr, nullptr); if (FAILED(hr)) { printf("IAudioCaptureClient::GetBuffer failed on pass %u after %u frames: hr = 0x%08x\n", 0, 0, hr); pAudioClient->Stop(); pAudioCaptureClient->Release(); pAudioClient->Release(); return hr; } DVAR(nNumFramesToRead); // if (bFirstPacket && AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY == dwFlags) { // printf("Probably spurious glitch reported on first packet\n"); if (0 != dwFlags && AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY != dwFlags) { printf("IAudioCaptureClient::GetBuffer set flags to 0x%08x on pass %u after %u frames\n", dwFlags, 0, 0); // pAudioClient->Stop(); // pAudioCaptureClient->Release(); // pAudioClient->Release(); // return E_UNEXPECTED; } else DVAR((int)*pData); if (0 == nNumFramesToRead) { printf("IAudioCaptureClient::GetBuffer said to read 0 frames on pass %u after %u frames\n", 0, 0); pAudioClient->Stop(); pAudioCaptureClient->Release(); pAudioClient->Release(); return E_UNEXPECTED; } UINT32 nBlockAlign = pwfx->nBlockAlign; LONG lBytesToWrite = nNumFramesToRead * nBlockAlign; hr = pAudioCaptureClient->ReleaseBuffer(nNumFramesToRead); } pAudioClient->Stop(); pAudioCaptureClient->Release(); pAudioClient->Release(); CoUninitialize(); return 0; }
int _tmain(int argc, _TCHAR* argv[]) { IMMDeviceEnumerator *enumerator = 0; IMMDevice *device = 0; FILE *f; f=fopen("c:/1.wav","w"); CoInitialize(0); CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**) &enumerator); enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); HANDLE processOutWrite, processOutRead, processInWrite, processInRead; /*wchar_t processCommand[2000]; { FILE* commandFile; fopen_s(&commandFile, "command.txt", "r"); char cmd[2000]; fread(cmd, sizeof(char), 2000, commandFile); fclose(commandFile); size_t count; mbstowcs_s(&count, processCommand, cmd, 2000); }*/ /*{ //create pipes for plink process SECURITY_ATTRIBUTES pipeAttributes = {0}; pipeAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); pipeAttributes.bInheritHandle = TRUE; pipeAttributes.lpSecurityDescriptor= NULL; CreatePipe(&processOutRead, &processOutWrite, &pipeAttributes, 0); CreatePipe(&processInRead, &processInWrite, &pipeAttributes, 0); STARTUPINFO startupInfo; ZeroMemory(&startupInfo, sizeof(STARTUPINFO)); startupInfo.cb = sizeof(STARTUPINFO); startupInfo.hStdError = processOutWrite; startupInfo.hStdOutput = processOutWrite; startupInfo.hStdInput = processInRead; startupInfo.dwFlags |= STARTF_USESTDHANDLES; PROCESS_INFORMATION processInfo = {0}; //launch process CreateProcess(NULL, processCommand, NULL, NULL, TRUE, 0, NULL, NULL, &startupInfo, &processInfo); //wait for plink to connect to minimze sound delay (magic number) Sleep(2500); }*/ HRESULT hr; // activate an IAudioClient IAudioClient *audioClient; hr = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**) &audioClient); if (FAILED(hr)) { printf("IMMDevice::Activate(IAudioClient) failed: hr = 0x%08x", hr); return hr; } // get the default device format WAVEFORMATEX *waveFormat; hr = audioClient->GetMixFormat(&waveFormat); if (FAILED(hr)) { printf("IAudioClient::GetMixFormat failed: hr = 0x%08x\n", hr); CoTaskMemFree(waveFormat); audioClient->Release(); return hr; } // coerce int-16 wave format // can do this in-place since we're not changing the size of the format // also, the engine will auto-convert from float to int for us switch (waveFormat->wFormatTag) { case WAVE_FORMAT_IEEE_FLOAT: waveFormat->wFormatTag = WAVE_FORMAT_PCM; waveFormat->wBitsPerSample = 16; waveFormat->nBlockAlign = waveFormat->nChannels * waveFormat->wBitsPerSample / 8; waveFormat->nAvgBytesPerSec = waveFormat->nBlockAlign * waveFormat->nSamplesPerSec; break; case WAVE_FORMAT_EXTENSIBLE: { // naked scope for case-local variable PWAVEFORMATEXTENSIBLE waveFormatEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(waveFormat); if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, waveFormatEx->SubFormat)) { waveFormatEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; waveFormatEx->Samples.wValidBitsPerSample = 16; waveFormat->wBitsPerSample = 16; waveFormat->nBlockAlign = waveFormat->nChannels * waveFormat->wBitsPerSample / 8; waveFormat->nAvgBytesPerSec = waveFormat->nBlockAlign * waveFormat->nSamplesPerSec; } else { printf("Don't know how to coerce mix format to int-16\n"); CoTaskMemFree(waveFormat); audioClient->Release(); return E_UNEXPECTED; } } break; default: printf("Don't know how to coerce WAVEFORMATEX with wFormatTag = 0x%08x to int-16\n", waveFormat->wFormatTag); CoTaskMemFree(waveFormat); audioClient->Release(); return E_UNEXPECTED; } UINT32 blockAlign = waveFormat->nBlockAlign; // call IAudioClient::Initialize // note that AUDCLNT_STREAMFLAGS_LOOPBACK and AUDCLNT_STREAMFLAGS_EVENTCALLBACK do not work together... // the "data ready" event never gets set, so we're going to do a timer-driven loop hr = audioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, 10000000, 0, waveFormat, 0 ); if (FAILED(hr)) { printf("IAudioClient::Initialize failed: hr = 0x%08x\n", hr); audioClient->Release(); return hr; } CoTaskMemFree(waveFormat); // activate an IAudioCaptureClient IAudioCaptureClient *audioCaptureClient; hr = audioClient->GetService(__uuidof(IAudioCaptureClient), (void**) &audioCaptureClient); if (FAILED(hr)) { printf("IAudioClient::GetService(IAudioCaptureClient) failed: hr 0x%08x\n", hr); audioClient->Release(); return hr; } hr = audioClient->Start(); if (FAILED(hr)) { printf("IAudioClient::Start failed: hr = 0x%08x\n", hr); audioCaptureClient->Release(); audioClient->Release(); return hr; } // loopback capture loop for (UINT32 i = 0; true; i++) { UINT32 nextPacketSize; hr = audioCaptureClient->GetNextPacketSize(&nextPacketSize); if (FAILED(hr)) { printf("IAudioCaptureClient::GetNextPacketSize failed on pass %u: hr = 0x%08x\n", i, hr); audioClient->Stop(); audioCaptureClient->Release(); audioClient->Release(); return hr; } if (nextPacketSize == 0) { // no data yet continue; } // get the captured data BYTE *data; UINT32 frameCount; DWORD bufferFlags; hr = audioCaptureClient->GetBuffer(&data, &frameCount, &bufferFlags, NULL, NULL); if (FAILED(hr)) { printf("IAudioCaptureClient::GetBuffer failed on pass %u: hr = 0x%08x\n", i, hr); audioClient->Stop(); audioCaptureClient->Release(); audioClient->Release(); return hr; } if (bufferFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) { printf("IAudioCaptureClient::GetBuffer reports 'data discontinuity' on pass %u\n", i); } if (bufferFlags & AUDCLNT_BUFFERFLAGS_SILENT) { printf("IAudioCaptureClient::GetBuffer reports 'silent' on pass %u\n", i); } if (bufferFlags & AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR) { printf("IAudioCaptureClient::GetBuffer reports 'timestamp error' on pass %u\n", i); } if (frameCount == 0) { printf("IAudioCaptureClient::GetBuffer said to read 0 frames on pass %u\n", i); audioClient->Stop(); audioCaptureClient->Release(); audioClient->Release(); return E_UNEXPECTED; } LONG bytesToWrite = frameCount * blockAlign; DWORD bytesWritten; printf("Recording:%d\n",bytesToWrite); fwrite(data,bytesToWrite,1,f); /*WriteFile(processInWrite,reinterpret_cast<PCHAR>(data), bytesToWrite, &bytesWritten, NULL); if (bytesWritten != bytesToWrite) { printf("WriteFile: tried to write %d bytes, but %d bytes written\n", bytesToWrite, bytesWritten); } char buf[10000]; DWORD count; DWORD bytesAvailable; PeekNamedPipe(processOutRead, NULL, 0, 0, &bytesAvailable, NULL); if (bytesAvailable > 0) { ReadFile(processOutRead, buf, 10000, &count, NULL); std::cout.write(buf, count); }*/ hr = audioCaptureClient->ReleaseBuffer(frameCount); if (FAILED(hr)) { printf("IAudioCaptureClient::ReleaseBuffer failed on pass %u: hr = 0x%08x\n", i, hr); audioClient->Stop(); audioCaptureClient->Release(); audioClient->Release(); return hr; } } // capture loop audioClient->Stop(); audioCaptureClient->Release(); audioClient->Release(); fclose(f); return 0; }
HRESULT LoopbackCapture( IMMDevice *pMMDevice, bool bInt16, HANDLE hStartedEvent, HANDLE hStopEvent, PUINT32 pnFrames, bool bMono, INT32 iSampleRateDivisor ) { HRESULT hr; SimpleTcpServer server; // Wait for client connection before attempting any audio capture server.setup(); server.waitForClient(); // activate an IAudioClient IAudioClient *pAudioClient; hr = pMMDevice->Activate( __uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&pAudioClient ); if (FAILED(hr)) { printf("IMMDevice::Activate(IAudioClient) failed: hr = 0x%08x", hr); return hr; } // get the default device periodicity REFERENCE_TIME hnsDefaultDevicePeriod; hr = pAudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, NULL); if (FAILED(hr)) { printf("IAudioClient::GetDevicePeriod failed: hr = 0x%08x\n", hr); pAudioClient->Release(); return hr; } // get the default device format WAVEFORMATEX *pwfx; hr = pAudioClient->GetMixFormat(&pwfx); if (FAILED(hr)) { printf("IAudioClient::GetMixFormat failed: hr = 0x%08x\n", hr); CoTaskMemFree(pwfx); pAudioClient->Release(); return hr; } if (bInt16) { // coerce int-16 wave format // can do this in-place since we're not changing the size of the format // also, the engine will auto-convert from float to int for us switch (pwfx->wFormatTag) { case WAVE_FORMAT_IEEE_FLOAT: pwfx->wFormatTag = WAVE_FORMAT_PCM; pwfx->wBitsPerSample = 16; pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; break; case WAVE_FORMAT_EXTENSIBLE: { // naked scope for case-local variable PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(pwfx); if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat)) { pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; pEx->Samples.wValidBitsPerSample = 16; pwfx->wBitsPerSample = 16; pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; } else { printf("Don't know how to coerce mix format to int-16\n"); CoTaskMemFree(pwfx); pAudioClient->Release(); return E_UNEXPECTED; } } break; default: printf("Don't know how to coerce WAVEFORMATEX with wFormatTag = 0x%08x to int-16\n", pwfx->wFormatTag); CoTaskMemFree(pwfx); pAudioClient->Release(); return E_UNEXPECTED; } } // create a periodic waitable timer HANDLE hWakeUp = CreateWaitableTimer(NULL, FALSE, NULL); if (NULL == hWakeUp) { DWORD dwErr = GetLastError(); printf("CreateWaitableTimer failed: last error = %u\n", dwErr); CoTaskMemFree(pwfx); pAudioClient->Release(); return HRESULT_FROM_WIN32(dwErr); } UINT32 nBlockAlign = pwfx->nBlockAlign; UINT32 nBufferSize; *pnFrames = 0; // call IAudioClient::Initialize // note that AUDCLNT_STREAMFLAGS_LOOPBACK and AUDCLNT_STREAMFLAGS_EVENTCALLBACK // do not work together... // the "data ready" event never gets set // so we're going to do a timer-driven loop hr = pAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, 0, 0, pwfx, 0 ); if (FAILED(hr)) { printf("IAudioClient::Initialize failed: hr = 0x%08x\n", hr); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } CoTaskMemFree(pwfx); // Get the buffer size hr = pAudioClient->GetBufferSize(&nBufferSize); if (FAILED(hr)) { printf("IAudioClient::GetBufferSize failed: hr = 0x%08x\n", hr); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } // Configure the server. The buffer size returned is in frames // so assume stereo, 16 bits per sample to convert from frames to bytes server.configure( bMono, iSampleRateDivisor, nBufferSize * 2 * 2); // activate an IAudioCaptureClient IAudioCaptureClient *pAudioCaptureClient; hr = pAudioClient->GetService( __uuidof(IAudioCaptureClient), (void**)&pAudioCaptureClient ); if (FAILED(hr)) { printf("IAudioClient::GetService(IAudioCaptureClient) failed: hr 0x%08x\n", hr); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } // register with MMCSS DWORD nTaskIndex = 0; HANDLE hTask = AvSetMmThreadCharacteristics(L"Capture", &nTaskIndex); if (NULL == hTask) { DWORD dwErr = GetLastError(); printf("AvSetMmThreadCharacteristics failed: last error = %u\n", dwErr); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return HRESULT_FROM_WIN32(dwErr); } // set the waitable timer LARGE_INTEGER liFirstFire; liFirstFire.QuadPart = -hnsDefaultDevicePeriod / 2; // negative means relative time LONG lTimeBetweenFires = (LONG)hnsDefaultDevicePeriod / 2 / (10 * 1000); // convert to milliseconds BOOL bOK = SetWaitableTimer( hWakeUp, &liFirstFire, lTimeBetweenFires, NULL, NULL, FALSE ); if (!bOK) { DWORD dwErr = GetLastError(); printf("SetWaitableTimer failed: last error = %u\n", dwErr); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return HRESULT_FROM_WIN32(dwErr); } // call IAudioClient::Start hr = pAudioClient->Start(); if (FAILED(hr)) { printf("IAudioClient::Start failed: hr = 0x%08x\n", hr); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } SetEvent(hStartedEvent); // loopback capture loop HANDLE waitArray[2] = { hStopEvent, hWakeUp }; DWORD dwWaitResult; bool bDone = false; for (UINT32 nPasses = 0; !bDone; nPasses++) { // drain data while it is available UINT32 nNextPacketSize; for ( hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize); SUCCEEDED(hr) && nNextPacketSize > 0; hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize) ) { // get the captured data BYTE *pData; UINT32 nNumFramesToRead; DWORD dwFlags; hr = pAudioCaptureClient->GetBuffer( &pData, &nNumFramesToRead, &dwFlags, NULL, NULL ); if (FAILED(hr)) { printf("IAudioCaptureClient::GetBuffer failed on pass %u after %u frames: hr = 0x%08x\n", nPasses, *pnFrames, hr); pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } #ifdef _DEBUG if (0 != dwFlags) { printf("[ignoring] IAudioCaptureClient::GetBuffer set flags to 0x%08x on pass %u after %u frames\n", dwFlags, nPasses, *pnFrames); } #endif if (0 == nNumFramesToRead) { printf("IAudioCaptureClient::GetBuffer said to read 0 frames on pass %u after %u frames\n", nPasses, *pnFrames); pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return E_UNEXPECTED; } LONG lBytesToWrite = nNumFramesToRead * nBlockAlign; if (server.sendData(reinterpret_cast<const char*>(pData), lBytesToWrite) != 0) { printf("Error sending data to peer\n"); pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return E_UNEXPECTED; } *pnFrames += nNumFramesToRead; hr = pAudioCaptureClient->ReleaseBuffer(nNumFramesToRead); if (FAILED(hr)) { printf("IAudioCaptureClient::ReleaseBuffer failed on pass %u after %u frames: hr = 0x%08x\n", nPasses, *pnFrames, hr); pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } } if (FAILED(hr)) { printf("IAudioCaptureClient::GetNextPacketSize failed on pass %u after %u frames: hr = 0x%08x\n", nPasses, *pnFrames, hr); pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return hr; } dwWaitResult = WaitForMultipleObjects( ARRAYSIZE(waitArray), waitArray, FALSE, INFINITE ); if (WAIT_OBJECT_0 == dwWaitResult) { printf("Received stop event after %u passes and %u frames\n", nPasses, *pnFrames); bDone = true; continue; // exits loop } if (WAIT_OBJECT_0 + 1 != dwWaitResult) { printf("Unexpected WaitForMultipleObjects return value %u on pass %u after %u frames\n", dwWaitResult, nPasses, *pnFrames); pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); return E_UNEXPECTED; } } // capture loop pAudioClient->Stop(); CancelWaitableTimer(hWakeUp); AvRevertMmThreadCharacteristics(hTask); pAudioCaptureClient->Release(); CloseHandle(hWakeUp); pAudioClient->Release(); server.shutdown(); return hr; }