29#include <audiosessiontypes.h>
30#include <audioclient.h>
36#include <combaseapi.h>
43#include <mmdeviceapi.h>
64#define MOD_NAME "[WASAPI cap.] "
65#define REFTIMES_PER_SEC 10000000
66#define REFTIMES_PER_MILLISEC 10000
72const static GUID
IDevice_FriendlyName = { 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0 } };
74#pragma GCC diagnostic push
75#pragma GCC diagnostic ignored "-Wmissing-braces"
78#pragma GCC diagnostic pop
81using std::ostringstream;
96static string get_name(IMMDevice *pDevice);
100#define SAFE_RELEASE(u) \
101 do { if ((u) != NULL) (u)->Release(); (u) = NULL; } while(0)
102#undef THROW_IF_FAILED
103#define THROW_IF_FAILED(cmd) do { HRESULT hr = cmd; if (!SUCCEEDED(hr)) { ostringstream oss; oss << #cmd << ": " << hresult_to_str(hr); throw ug_runtime_error(oss.str()); } } while(0)
107 *available_devices =
nullptr;
109 bool com_initialized =
false;
114 IMMDeviceEnumerator *enumerator =
nullptr;
115 IMMDeviceCollection *pEndpoints =
nullptr;
119 (
void **) &enumerator));
121 THROW_IF_FAILED(enumerator->EnumAudioEndpoints(eCapture, DEVICE_STATEMASK_ALL, &pEndpoints));
125 IMMDevice *pDevice =
nullptr;
126 LPWSTR pwszID = NULL;
130 *available_devices = (
struct device_info *) realloc(*available_devices, (*dev_count + 1) *
sizeof(
struct device_info));
131 memset(&(*available_devices)[*dev_count], 0,
sizeof(
struct device_info));
132 snprintf((*available_devices)[*dev_count].
dev,
sizeof (*available_devices)[*dev_count].
dev,
":%u", i);
133 snprintf((*available_devices)[*dev_count].
name,
sizeof (*available_devices)[*dev_count].
name,
"WASAPI %s",
get_name(pDevice).c_str());
139 CoTaskMemFree(pwszID);
147 *available_devices = (
struct device_info *)realloc(
148 *available_devices, (*dev_count + 1) *
sizeof(
struct device_info));
149 memset(&(*available_devices)[*dev_count], 0,
153 "WASAPI computer audio output");
159 IPropertyStore *pProps = NULL;
161 LPWSTR pwszID = NULL;
163 PropVariantInit(&varName);
164 HRESULT hr = pDevice->OpenPropertyStore(STGM_READ, &pProps);
168 out = varName.pwszVal;
172 CoTaskMemFree(pwszID);
173 PropVariantClear(&varName);
174 return win_wstr_to_str(out.c_str());
178 IMMDevice *pDevice =
nullptr;
179 if (!SUCCEEDED(enumerator->GetDefaultAudioEndpoint(dataFlow, eConsole, &pDevice))) {
182 LPWSTR pwszID =
nullptr;
183 if (!SUCCEEDED(pDevice->GetId(&pwszID))) {
187 string ret = win_wstr_to_str(pwszID);
194 col() <<
"Usage:\n" <<
195 SBOLD(
SRED(
"\t-s wasapi") <<
"[:<index>|:<ID>]") <<
196 "\n\nAvailable devices:\n";
198 IMMDeviceEnumerator *enumerator =
nullptr;
199 IMMDeviceCollection *pEndpoints =
nullptr;
200 bool com_initialized =
false;
207 (
void **) &enumerator));
209 THROW_IF_FAILED(enumerator->EnumAudioEndpoints(eCapture, DEVICE_STATEMASK_ALL, &pEndpoints));
213 IMMDevice *pDevice =
nullptr;
214 LPWSTR pwszID = NULL;
218 string dev_id = win_wstr_to_str(pwszID);
219 col() << (dev_id == default_dev_id ?
"(*)" :
"") <<
"\t" <<
SBOLD(i) <<
") " <<
SBOLD(
get_name(pDevice)) <<
" (ID: " << dev_id <<
")\n";
224 CoTaskMemFree(pwszID);
231 col() <<
" " <<
SBOLD(
"loopback") <<
") " <<
SBOLD(
"computer audio output") <<
" (ID: loopback)\n";
237 wchar_t deviceID[1024] = L
"";
238 WAVEFORMATEX *pwfx = NULL;
239 enum { IDX_LOOP = -2, IDX_DFL = -1 };
241 if (strlen(cfg) > 0) {
242 if (strcmp(cfg,
"help") == 0) {
246 if (isdigit(cfg[0])) {
248 }
else if (strcmp(cfg,
"loopback") == 0) {
251 mbtowc(deviceID, cfg, (
sizeof deviceID / 2) - 1);
259 IMMDeviceEnumerator *enumerator =
nullptr;
263 (
void **) &enumerator));
264 if (wcslen(deviceID) > 0) {
266 }
else if (index >= 0) {
267 IMMDeviceCollection *pEndpoints =
nullptr;
269 THROW_IF_FAILED(enumerator->EnumAudioEndpoints(eCapture, DEVICE_STATEMASK_ALL, &pEndpoints));
273 if (i == (
UINT) index) {
284 index == IDX_DFL ? eCapture : eRender, eConsole,
291 (
void **)&s->pAudioClient));
293 auto friendlyName =
get_name(s->pDevice);
294 if (!friendlyName.empty()) {
296 << friendlyName <<
"\n";
309 pwfx->wBitsPerSample = 16;
315 switch (pwfx->wFormatTag) {
316 case WAVE_FORMAT_IEEE_FLOAT:
317 pwfx->wFormatTag = WAVE_FORMAT_PCM;
318 pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
319 pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
322 case WAVE_FORMAT_EXTENSIBLE:
325 PWAVEFORMATEXTENSIBLE waveFormatEx =
reinterpret_cast<PWAVEFORMATEXTENSIBLE
>(pwfx);
328 waveFormatEx->Samples.wValidBitsPerSample =
329 pwfx->wBitsPerSample;
330 pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
331 pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
340 oss <<
"Don't know how to coerce WAVEFORMATEX with wFormatTag = 0x" << setw(8) << setfill(
'0') <<
" to PCM";
345 AUDCLNT_SHAREMODE_SHARED,
346 index == IDX_LOOP ? AUDCLNT_STREAMFLAGS_LOOPBACK : 0,
347 hnsRequestedDuration,
352 UINT32 bufferFrameCount;
354 s->frame.bps = pwfx->wBitsPerSample / 8;
355 s->frame.sample_rate = pwfx->nSamplesPerSec;
356 s->frame.ch_count = pwfx->nChannels;
357 s->frame.max_size = bufferFrameCount * s->frame.bps * s->frame.ch_count;
358 s->frame.data = (
char *) malloc(s->frame.max_size);
384#define FAIL_IF_NOT(cmd) do {HRESULT hr = cmd; if (hr != S_OK) { LOG(LOG_LEVEL_ERROR) << MOD_NAME << #cmd << ": " << hresult_to_str(hr) << "\n"; return nullptr;}} while(0)
389 UINT32 packetLength = 0;
390 FAIL_IF_NOT(s->pCaptureClient->GetNextPacketSize(&packetLength));
391 if (packetLength == 0) {
395 FAIL_IF_NOT(s->pCaptureClient->GetNextPacketSize(&packetLength));
397 s->frame.data_len = 0;
399 while (packetLength != 0) {
400 UINT32 numFramesAvailable;
407 &
flags, NULL, NULL));
415 int count = numFramesAvailable * s->frame.bps * s->frame.ch_count;
416 if (s->frame.data_len +
count > s->frame.max_size) {
417 count = s->frame.max_size - s->frame.data_len;
419 memcpy(s->frame.data + s->frame.data_len, pData,
count);
421 s->frame.data_len +=
count;
422 FAIL_IF_NOT(s->pCaptureClient->ReleaseBuffer(
count / s->frame.ch_count / s->frame.bps));
423 if (s->frame.data_len == s->frame.max_size) {
427 FAIL_IF_NOT(s->pCaptureClient->GetNextPacketSize(&packetLength));
const char unsigned int flags
int void(** deleter)(void *))
This file contains definition of some common audio types.
#define AUDIO_CAPTURE_ABI_VERSION
static void audio_cap_wasapi_done(void *state)
static const PROPERTYKEY PKEY_Device_FriendlyName
#define THROW_IF_FAILED(cmd)
static const struct audio_capture_info acap_wasapi_info
static void audio_cap_wasapi_probe(struct device_info **available_devices, int *dev_count, void(**deleter)(void *))
static struct audio_frame * audio_cap_wasapi_read(void *state)
static void * audio_cap_wasapi_init(struct module *parent, const char *cfg)
const IID IID_IAudioCaptureClient
string wasapi_get_default_device_id(EDataFlow dataFlow, IMMDeviceEnumerator *enumerator)
static const GUID IDevice_FriendlyName
const GUID KSDATAFORMAT_SUBTYPE_PCM
const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
static string get_name(IMMDevice *pDevice)
#define LOG_LEVEL_NOTICE
information that may be interesting
#define LOG_LEVEL_ERROR
general errors
#define LOG_LEVEL_WARNING
less severe errors
unsigned int audio_capture_bps
unsigned int audio_capture_sample_rate
unsigned int audio_capture_channels
@ LIBRARY_CLASS_AUDIO_CAPTURE
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
#define snprintf_ch(str,...)
shortcut for snprintf(var, sizeof var...), var must be a char array
const CLSID CLSID_MMDeviceEnumerator
const IID IID_IAudioClient
const IID IID_IMMDeviceEnumerator
struct used to comunicate with audio capture/playback device
IAudioCaptureClient * pCaptureClient
IAudioClient * pAudioClient
This file contains some common data types.
bool com_initialize(bool *com_initialized, const char *err_prefix)
void com_uninitialize(bool *com_initialized)