Files
ANSCORE/MediaClient/media/audio_capture_win.cpp

628 lines
14 KiB
C++
Raw Permalink Normal View History

2026-03-28 16:54:11 +11:00
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#include "sys_inc.h"
#include "audio_capture_win.h"
#include "lock.h"
#include <Functiondiscoverykeys_devpkey.h>
#include <mmreg.h>
/**************************************************************************************/
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
const IID IID_IMMEndpoint = __uuidof(IMMEndpoint);
/***************************************************************************************/
static void * audioCaptureThread(void * argv)
{
CWAudioCapture *capture = (CWAudioCapture *)argv;
capture->captureThread();
return NULL;
}
/***************************************************************************************/
CWAudioCapture::CWAudioCapture()
: CAudioCapture()
, m_pAudioClient(NULL)
, m_pCaptureClient(NULL)
{
}
CWAudioCapture::~CWAudioCapture()
{
stopCapture();
}
CAudioCapture * CWAudioCapture::getInstance(int devid)
{
if (devid < 0 || devid >= MAX_AUDIO_DEV_NUMS)
{
return NULL;
}
sys_os_mutex_enter(m_pInstMutex);
if (NULL == m_pInstance[devid])
{
m_pInstance[devid] = new CWAudioCapture;
if (m_pInstance[devid])
{
m_pInstance[devid]->m_nRefCnt++;
m_pInstance[devid]->m_nDevIndex = devid;
}
}
else
{
m_pInstance[devid]->m_nRefCnt++;
}
sys_os_mutex_leave(m_pInstMutex);
return m_pInstance[devid];
}
int CWAudioCapture::getDeviceNums()
{
UINT count = 0;
HRESULT hr;
IMMDeviceCollection * pEndpoints = NULL;
hr = getDeviceCollection(eAll, &pEndpoints);
if (FAILED(hr))
{
log_print(HT_LOG_ERR, "%s, getDeviceCollection failed. hr=%ld", __FUNCTION__, hr);
return 0;
}
hr = pEndpoints->GetCount(&count);
pEndpoints->Release();
return count > MAX_AUDIO_DEV_NUMS ? MAX_AUDIO_DEV_NUMS : count;
}
void CWAudioCapture::listDevice()
{
UINT i, count = 0;
HRESULT hr;
IMMDeviceCollection * pEndpoints = NULL;
printf("\r\nAvailable audio capture device : \r\n\r\n");
hr = getDeviceCollection(eAll, &pEndpoints);
if (FAILED(hr))
{
log_print(HT_LOG_ERR, "%s, getDeviceCollection failed. hr=%ld", __FUNCTION__, hr);
return;
}
hr = pEndpoints->GetCount(&count);
for (i = 0; i < count; i++)
{
IMMDevice * pDevice = NULL;
hr = pEndpoints->Item(i, &pDevice);
if (FAILED(hr))
{
log_print(HT_LOG_WARN, "%s, pEndpoints->Item failed. hr=%ld", __FUNCTION__, hr);
continue;
}
PROPVARIANT varName;
// Initialize container for property value.
PropVariantInit(&varName);
hr = getDeviceName(pDevice, &varName);
if (FAILED(hr))
{
pDevice->Release();
log_print(HT_LOG_WARN, "%s, getDeviceName failed. hr=%ld", __FUNCTION__, hr);
continue;
}
printf("index : %d, name : %ws\r\n", i, varName.pwszVal);
PropVariantClear(&varName);
pDevice->Release();
}
pEndpoints->Release();
}
int CWAudioCapture::getDeviceIndex(const char * name)
{
UINT i, count = 0, index = 0, size;
HRESULT hr;
IMMDeviceCollection * pEndpoints = NULL;
wchar_t * wszname = NULL;
hr = getDeviceCollection(eAll, &pEndpoints);
if (FAILED(hr))
{
log_print(HT_LOG_ERR, "%s, getDeviceCollection failed. hr=%ld", __FUNCTION__, hr);
return 0;
}
size = MultiByteToWideChar(CP_ACP, 0, name, -1, NULL, 0);
wszname = (wchar_t *)malloc(size * sizeof(wchar_t));
if (wszname)
{
MultiByteToWideChar(CP_ACP, 0, name, -1, wszname, size);
}
hr = pEndpoints->GetCount(&count);
for (i = 0; i < count && i < MAX_AUDIO_DEV_NUMS; i++)
{
IMMDevice * pDevice = NULL;
hr = pEndpoints->Item(i, &pDevice);
if (FAILED(hr))
{
log_print(HT_LOG_WARN, "%s, pEndpoints->Item failed. hr=%ld", __FUNCTION__, hr);
continue;
}
PROPVARIANT varName;
// Initialize container for property value.
PropVariantInit(&varName);
hr = getDeviceName(pDevice, &varName);
if (FAILED(hr))
{
pDevice->Release();
log_print(HT_LOG_WARN, "%s, getDeviceName failed. hr=%ld", __FUNCTION__, hr);
continue;
}
if (wszname != NULL && _wcsicmp(varName.pwszVal, wszname) == 0)
{
index = i;
PropVariantClear(&varName);
pDevice->Release();
break;
}
PropVariantClear(&varName);
pDevice->Release();
}
pEndpoints->Release();
if (wszname)
{
free(wszname);
}
return index;
}
BOOL CWAudioCapture::getDeviceName(int index, char * name, int namesize)
{
UINT count = 0;
HRESULT hr;
IMMDevice * pDevice = NULL;
IMMDeviceCollection * pEndpoints = NULL;
PROPVARIANT varName;
hr = getDeviceCollection(eAll, &pEndpoints);
if (FAILED(hr))
{
log_print(HT_LOG_ERR, "%s, getDeviceCollection failed. hr=%ld", __FUNCTION__, hr);
return FALSE;
}
hr = pEndpoints->GetCount(&count);
if (FAILED(hr))
{
pEndpoints->Release();
log_print(HT_LOG_ERR, "%s, GetCount failed. hr=%ld", __FUNCTION__, hr);
return FALSE;
}
if (index < 0 || (UINT)index >= count || index >= MAX_AUDIO_DEV_NUMS)
{
pEndpoints->Release();
log_print(HT_LOG_ERR, "%s, invalid index %d, count=%d\r\n", __FUNCTION__, index, count);
return FALSE;
}
hr = pEndpoints->Item(index, &pDevice);
if (FAILED(hr))
{
pEndpoints->Release();
log_print(HT_LOG_ERR, "%s, pEndpoints->Item failed. hr=%ld", __FUNCTION__, hr);
return FALSE;
}
pEndpoints->Release();
// Initialize container for property value.
PropVariantInit(&varName);
hr = getDeviceName(pDevice, &varName);
if (FAILED(hr))
{
pDevice->Release();
log_print(HT_LOG_WARN, "%s, getDeviceName failed. hr=%ld", __FUNCTION__, hr);
return FALSE;
}
WideCharToMultiByte(CP_ACP, 0, varName.pwszVal, -1, name, namesize, NULL, NULL);
PropVariantClear(&varName);
pDevice->Release();
return TRUE;
}
BOOL CWAudioCapture::initCapture(int codec, int samplerate, int channels, int bitrate)
{
CLock lock(m_pMutex);
if (m_bInited)
{
return TRUE;
}
int samplefmt;
DWORD flags = 0;
HRESULT hr;
IMMDevice * pDevice = NULL;
WAVEFORMATEX * pwfx = NULL;
hr = getDeviceByIndex(m_nDevIndex, &pDevice);
if (FAILED(hr))
{
log_print(HT_LOG_ERR, "%s, getDeviceByIndex failed. index=%d, hr=%ld",
__FUNCTION__, m_nDevIndex, hr);
return FALSE;
}
hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&m_pAudioClient);
if (FAILED(hr))
{
log_print(HT_LOG_ERR, "%s, pDevice->Activate failed. hr=%ld", __FUNCTION__, hr);
goto done;
}
hr = m_pAudioClient->GetMixFormat(&pwfx);
if (FAILED(hr))
{
log_print(HT_LOG_ERR, "%s, m_pAudioClient->GetMixFormat failed. hr=%ld", __FUNCTION__, hr);
goto done;
}
samplefmt = getSampleFmt(pwfx);
if (samplefmt == AV_SAMPLE_FMT_NONE)
{
log_print(HT_LOG_ERR, "%s, getSampleFmt failed", __FUNCTION__);
goto done;
}
if (getDataFlow(pDevice) == eRender)
{
flags = AUDCLNT_STREAMFLAGS_LOOPBACK;
}
hr = m_pAudioClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
flags,
1000000, // 100ms
0,
pwfx,
NULL);
if (FAILED(hr))
{
log_print(HT_LOG_ERR, "%s, m_pAudioClient->Initialize failed. hr=%ld", __FUNCTION__, hr);
goto done;
}
hr = m_pAudioClient->GetService(IID_IAudioCaptureClient, (void**)&m_pCaptureClient);
if (FAILED(hr))
{
log_print(HT_LOG_ERR, "%s, m_pAudioClient->GetService failed. hr=%ld", __FUNCTION__, hr);
goto done;
}
AudioEncoderParam params;
memset(&params, 0, sizeof(params));
params.SrcChannels = pwfx->nChannels;
params.SrcSamplefmt = (AVSampleFormat) samplefmt;
params.SrcSamplerate = pwfx->nSamplesPerSec;
params.DstChannels = channels;
params.DstSamplefmt = AV_SAMPLE_FMT_S16;
params.DstSamplerate = samplerate;
params.DstBitrate = bitrate;
params.DstCodec = codec;
if (m_encoder.init(&params) == FALSE)
{
log_print(HT_LOG_ERR, "%s, init encoder failed", __FUNCTION__);
goto done;
}
m_nChannels = channels;
m_nSampleRate = samplerate;
m_nBitrate = bitrate;
m_nSampleSize = pwfx->wBitsPerSample * pwfx->nChannels;
m_bInited = TRUE;
done:
if (pDevice)
{
pDevice->Release();
}
if (pwfx)
{
CoTaskMemFree(pwfx);
}
if (!m_bInited)
{
if (m_pAudioClient)
{
m_pAudioClient->Release();
m_pAudioClient = NULL;
}
if (m_pCaptureClient)
{
m_pCaptureClient->Release();
m_pCaptureClient = NULL;
}
}
return m_bInited;
}
BOOL CWAudioCapture::startCapture()
{
CLock lock(m_pMutex);
if (!m_bInited)
{
return FALSE;
}
if (m_bCapture)
{
return TRUE;
}
HRESULT hr = m_pAudioClient->Start();
if (FAILED(hr))
{
log_print(HT_LOG_ERR, "%s, m_pAudioClient->Start failed. hr=%ld", __FUNCTION__, hr);
return FALSE;
}
m_bCapture = TRUE;
m_hCapture = sys_os_create_thread((void *)audioCaptureThread, this);
return (m_hCapture ? TRUE : FALSE);
}
void CWAudioCapture::stopCapture(void)
{
CLock lock(m_pMutex);
m_bCapture = FALSE;
while (m_hCapture)
{
usleep(10*1000);
}
if (m_pAudioClient)
{
m_pAudioClient->Stop();
m_pAudioClient->Release();
m_pAudioClient = NULL;
}
if (m_pCaptureClient)
{
m_pCaptureClient->Release();
m_pCaptureClient = NULL;
}
m_bInited = FALSE;
}
BOOL CWAudioCapture::capture()
{
HRESULT hr;
UINT32 packetLength = 0;
hr = m_pCaptureClient->GetNextPacketSize(&packetLength);
if (FAILED(hr) || packetLength == 0)
{
return FALSE;
}
BYTE * data;
DWORD flags;
UINT32 size;
hr = m_pCaptureClient->GetBuffer(&data, &size, &flags, NULL, NULL);
if (FAILED(hr))
{
return FALSE;
}
m_encoder.encode(data, size * (m_nSampleSize / 8));
hr = m_pCaptureClient->ReleaseBuffer(size);
return TRUE;
}
void CWAudioCapture::captureThread()
{
while (m_bCapture)
{
capture();
usleep(10*1000);
}
m_hCapture = 0;
}
HRESULT CWAudioCapture::getDeviceCollection(EDataFlow dataFlow, IMMDeviceCollection ** ppEndpoints)
{
HRESULT hr;
IMMDeviceEnumerator * pEnumerator = NULL;
hr = CoCreateInstance(
CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**)&pEnumerator);
if (FAILED(hr))
{
return hr;
}
hr = pEnumerator->EnumAudioEndpoints(dataFlow, DEVICE_STATE_ACTIVE, ppEndpoints);
pEnumerator->Release();
return hr;
}
HRESULT CWAudioCapture::getDeviceName(IMMDevice * pDevice, PROPVARIANT * pName)
{
HRESULT hr;
IPropertyStore * pProps = NULL;
hr = pDevice->OpenPropertyStore(STGM_READ, &pProps);
if (FAILED(hr))
{
return hr;
}
// Get the endpoint's friendly-name property.
hr = pProps->GetValue(PKEY_Device_FriendlyName, pName);
pProps->Release();
return hr;
}
HRESULT CWAudioCapture::getDeviceByIndex(int index, IMMDevice ** ppDevice)
{
UINT count = 0;
HRESULT hr;
IMMDeviceCollection * pEndpoints = NULL;
hr = getDeviceCollection(eAll, &pEndpoints);
if (FAILED(hr))
{
return hr;
}
hr = pEndpoints->GetCount(&count);
if ((UINT) index >= count)
{
pEndpoints->Release();
return E_INVALIDARG;
}
hr = pEndpoints->Item(index, ppDevice);
pEndpoints->Release();
return hr;
}
int CWAudioCapture::getSampleFmt(WAVEFORMATEX * pwfx)
{
int fmt = AV_SAMPLE_FMT_NONE;
if ((pwfx->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (pwfx->wBitsPerSample == 32))
{
fmt = AV_SAMPLE_FMT_FLT;
}
else if ((pwfx->wFormatTag == WAVE_FORMAT_PCM) && (pwfx->wBitsPerSample == 16))
{
fmt = AV_SAMPLE_FMT_S16;
}
else if ((pwfx->wFormatTag == WAVE_FORMAT_PCM) && (pwfx->wBitsPerSample == 32))
{
fmt = AV_SAMPLE_FMT_S32;
}
else if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
{
const WAVEFORMATEXTENSIBLE * ext = (const WAVEFORMATEXTENSIBLE *) pwfx;
if (IsEqualGUID(ext->SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) && (pwfx->wBitsPerSample == 32))
{
fmt = AV_SAMPLE_FMT_FLT;
}
else if (IsEqualGUID(ext->SubFormat, KSDATAFORMAT_SUBTYPE_PCM) && (pwfx->wBitsPerSample == 16))
{
fmt = AV_SAMPLE_FMT_S16;
}
else if (IsEqualGUID(ext->SubFormat, KSDATAFORMAT_SUBTYPE_PCM) && (pwfx->wBitsPerSample == 32))
{
fmt = AV_SAMPLE_FMT_S32;
}
}
return fmt;
}
int CWAudioCapture::getDataFlow(IMMDevice * pDevice)
{
HRESULT hr;
EDataFlow dataflow = eCapture;
IMMEndpoint * pEndpoint;
hr = pDevice->QueryInterface(IID_IMMEndpoint, (void **)&pEndpoint);
if (FAILED(hr))
{
return dataflow;
}
hr = pEndpoint->GetDataFlow(&dataflow);
pEndpoint->Release();
return dataflow;
}