Win7 以后使用 Core Audio APIs 采集音频 IMMDevice IAudioClient IAudioCaptureClient
CAudioCapture::CAudioCapture()
{
m_bRunning = false;
m_hnsDefaultDevicePeriod = 0;
m_pWfx = NULL;
m_DataFlow = eCapture;
m_hEventStarted = nullptr;
m_hEventStop = nullptr;
m_pDevice = nullptr;
m_hThreadCapture = nullptr;
m_hTimerWakeUp = nullptr;
m_hTask = nullptr;
memset(&m_par, 0, sizeof(m_par));
m_pAudioClient = NULL;
m_pAudioCaptureClient = NULL;
m_hEventStarted = CreateEvent(nullptr, true, false, nullptr);
m_hEventStop = CreateEvent(nullptr, true, false, nullptr);
m_hTimerWakeUp = CreateWaitableTimer(nullptr, false, nullptr);
}
CAudioCapture::~CAudioCapture()
{
SafRelease(m_pAudioClient);
SafRelease(m_pAudioCaptureClient);
SafRelease(m_pDevice);
SafCloseHandle(m_hEventStarted);
SafCloseHandle(m_hEventStop);
if (NOT_NULLPTR(m_hTask))
{
AvRevertMmThreadCharacteristics(m_hTask);
m_hTask = nullptr;
}
if (m_pWfx != NULL)
{
CoTaskMemFree(m_pWfx);
m_pWfx = NULL;
}
if (NOT_NULLPTR(m_hTimerWakeUp))
{
CloseHandle(m_hTimerWakeUp);
m_hTimerWakeUp = nullptr;
}
SafCloseHandle(m_hThreadCapture);
}
bool CAudioCapture::Init()
{
HRESULT hr = S_OK;
if (m_pWfx != NULL)
{
CoTaskMemFree(m_pWfx);
m_pWfx = NULL;
}
hr = m_pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&m_pAudioClient);
if (FAILED(hr))
{
return false;
}
hr = m_pAudioClient->GetMixFormat(&m_pWfx);
if (FAILED(hr) || m_pWfx == NULL)
{
return false;
}
if (m_par.audioConfig.channels != 0 || m_par.audioConfig.samplesRate != 0 || m_par.audioConfig.bitsPerSample != 0 )
{
switch (m_pWfx->wFormatTag)
{
case WAVE_FORMAT_IEEE_FLOAT:
{
BOOL bChanged = FALSE;
WAVEFORMATEX *pwfx = m_pWfx;
if (m_par.audioConfig.bitsPerSample != 0)
{
pwfx->wBitsPerSample = m_par.audioConfig.bitsPerSample;
pwfx->wFormatTag = WAVE_FORMAT_PCM;
bChanged = TRUE;
}
if (m_par.audioConfig.samplesRate != 0)
{
pwfx->nSamplesPerSec = m_par.audioConfig.samplesRate;
pwfx->wFormatTag = WAVE_FORMAT_PCM;
bChanged = TRUE;
}
if (m_par.audioConfig.channels != 0)
{
pwfx->nChannels = m_par.audioConfig.channels;
pwfx->wFormatTag = WAVE_FORMAT_PCM;
bChanged = TRUE;
}
if (bChanged)
{
pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
}
break;
}
case WAVE_FORMAT_EXTENSIBLE:
{
// naked scope for case-local variable
WAVEFORMATEX *pwfx = m_pWfx;
PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(m_pWfx);
if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat))
{
BOOL bChanged = FALSE;
if (m_par.audioConfig.bitsPerSample != 0)
{
pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
// pwfx->wFormatTag = WAVE_FORMAT_PCM;
pEx->Samples.wValidBitsPerSample = m_par.audioConfig.bitsPerSample;
pwfx->wBitsPerSample = m_par.audioConfig.bitsPerSample;
bChanged = TRUE;
}
if (m_par.audioConfig.samplesRate != 0)
{
pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
// pwfx->wFormatTag = WAVE_FORMAT_PCM;
pwfx->nSamplesPerSec = m_par.audioConfig.samplesRate;
bChanged = TRUE;
}
if (m_par.audioConfig.channels != 0)
{
pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
// pwfx->wFormatTag = WAVE_FORMAT_PCM;
pwfx->nChannels = m_par.audioConfig.channels;
bChanged = TRUE;
}
if (bChanged)
{
pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
}
}
else
{
return false;
}
break;
}
}
}
DWORD StreamFlags = (m_DataFlow == eRender ? AUDCLNT_STREAMFLAGS_LOOPBACK : 0);
REFERENCE_TIME hnsRequestedDuration = 0;
if (m_par.audioConfig.fps != 0)
{
hnsRequestedDuration = REFTIMES_PER_MILLISEC * (1000/ m_par.audioConfig.fps);
}
else
{
hnsRequestedDuration = REFTIMES_PER_MILLISEC * 10;
}
hr = m_pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, StreamFlags, hnsRequestedDuration, 0, m_pWfx, 0);
if (FAILED(hr))
{
return false;
}
hr = m_pAudioClient->GetService(__uuidof(IAudioCaptureClient), (void**)&m_pAudioCaptureClient);
if (FAILED(hr) || m_pAudioCaptureClient == NULL)
{
return false;
}
hr = m_pAudioClient->GetDevicePeriod(&m_hnsDefaultDevicePeriod, nullptr);
if (FAILED(hr))
{
return false;
}
return true;
}
bool CAudioCapture::Start()
{
if (m_bRunning)
{
return true;
}
SafCloseHandle(m_hThreadCapture);
ResetEvent(m_hEventStop);
m_hThreadCapture = (HANDLE)_beginthreadex(nullptr, 0, &CAudioCapture::_CaptureThreadProc, this, 0, nullptr);
if (IS_NULLPTR(m_hThreadCapture))
{
return false;
}
HANDLE ahWaits[2] = { m_hEventStarted, m_hThreadCapture };
DWORD dwWaitResult = WaitForMultipleObjects(sizeof(ahWaits) / sizeof(ahWaits[0]), ahWaits, false, INFINITE);
if (WAIT_OBJECT_0 != dwWaitResult)
{
SafCloseHandle(m_hThreadCapture);
return false;
}
return true;
}
void CAudioCapture::Stop()
{
SetEvent(m_hEventStop);
if (m_hThreadCapture != NULL)
{
WaitForSingleObject(m_hThreadCapture, 500);
SafCloseHandle(m_hThreadCapture);
}
}
static wchar_t * GetDataFlowName(EDataFlow dataFlow)
{
if (dataFlow == eRender)
{
return L"Capture";
}
else
{
return L"Audio";
}
}
void CAudioCapture::CaptureThread()
{
HRESULT hr = S_OK;
LARGE_INTEGER liFirstFire;
liFirstFire.QuadPart = -m_hnsDefaultDevicePeriod / 2;
LONG lTimeBetweenFires = (LONG)m_hnsDefaultDevicePeriod / REFTIMES_PER_MILLISEC / 2;
#pragma warning(disable:4800)
bool bOk = (bool)SetWaitableTimer(m_hTimerWakeUp, &liFirstFire, lTimeBetweenFires, nullptr, nullptr, false);
if (IS_FALSE(bOk))
{
return;
}
hr = m_pAudioClient->Start();
if (FAILED(hr))
{
CancelWaitableTimer(m_hTimerWakeUp);
return;
}
SetEvent(m_hEventStarted);
HANDLE ahWait[2] = { m_hEventStop, m_hTimerWakeUp };
DWORD dwWaitResult;
UINT32 uiNextPacketSize(0);
BYTE *pData = nullptr;
UINT32 uiNumFramesToRead;
DWORD dwFlags;
while (true)
{
dwWaitResult = WaitForMultipleObjects(sizeof(ahWait) / sizeof(ahWait[0]), ahWait, false, INFINITE);
if (WAIT_OBJECT_0 + 1 != dwWaitResult)
{
break;
}
hr = m_pAudioCaptureClient->GetNextPacketSize(&uiNextPacketSize);
if (FAILED(hr))
{
break;
}
while (uiNextPacketSize != 0)
{
hr = m_pAudioCaptureClient->GetBuffer(
&pData,
&uiNumFramesToRead,
&dwFlags,
nullptr,
nullptr);
if (FAILED(hr))
{
break;
}
if (0 != uiNumFramesToRead)
{
if (m_par.callback != NULL)
{
m_par.callback(pData, uiNumFramesToRead* m_pWfx->nBlockAlign, m_par.usrData);
}
//pEventHandle->OnCaptureData(pData, uiNumFramesToRead * pWfx->nBlockAlign);
}
m_pAudioCaptureClient->ReleaseBuffer(uiNumFramesToRead);
hr = m_pAudioCaptureClient->GetNextPacketSize(&uiNextPacketSize);
if (FAILED(hr))
{
break;
}
}
}
m_pAudioClient->Stop();
CancelWaitableTimer(m_hTimerWakeUp);
}
UINT __stdcall CAudioCapture::_CaptureThreadProc(LPVOID param)
{
CAudioCapture *_this = (CAudioCapture *)param;
_this->m_bRunning = true;
DWORD nTaskIndex = 0;
_this->m_hTask = AvSetMmThreadCharacteristics(GetDataFlowName(_this->m_DataFlow), &nTaskIndex);
if (IS_NULLPTR(_this->m_hTask))
{
return 0;
}
_this->CaptureThread();
if (NOT_NULLPTR(_this->m_hTask))
{
AvRevertMmThreadCharacteristics(_this->m_hTask);
_this->m_hTask = nullptr;
}
_this->m_bRunning = false;
return 0;
}
static IMMDevice *findAudioDev(const char *id)
{
IMMDeviceEnumerator *pMMDeviceEnumerator = nullptr;
HRESULT hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL,
__uuidof(IMMDeviceEnumerator),
(void**)&pMMDeviceEnumerator);
if (FAILED(hr))
{
return NULL;
}
#if 1
IMMDevice *pDevice = nullptr;
wchar_t devId[400];
UTF8ToUnicode(id, devId, sizeof(devId)/sizeof(wchar_t));
pMMDeviceEnumerator->GetDevice(devId, &pDevice);
SafRelease(pMMDeviceEnumerator);
return pDevice;
#else
UINT count = 0;
IMMDeviceCollection *collect = nullptr;
hr = pMMDeviceEnumerator->EnumAudioEndpoints(eAll, DEF_DEVICE_STATE, &collect);
SafRelease(pMMDeviceEnumerator);
if (collect != NULL)
{
collect->GetCount(&count);
for (UINT i = 0; i < count; i++)
{
IMMDevice *pDevice = nullptr;
hr = collect->Item(i, &pDevice);
if (hr == S_OK && pDevice != NULL)
{
LPWSTR strid = NULL;
hr = pDevice->GetId(&strid);
if (hr == S_OK && strid != NULL)
{
char devPath[400];
UnicodeToUTF8(strid, devPath, sizeof(devPath));
CoTaskMemFree(strid);
strid = NULL;
if (strcmpix(devPath, id) == 0)
{
SafRelease(collect);
return pDevice;
}
}
}
SafRelease(pDevice);
}
}
SafRelease(collect);
return NULL;
#endif
}