UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
wasapi.cpp
Go to the documentation of this file.
1
5/*
6 * Copyright (c) 2019-2023 CESNET, z. s. p. o.
7 * All rights reserved.
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
29#include <audiosessiontypes.h> // for AUDCLNT_STREAMFLAGS_LOOPBACK, _AUDC...
30#include <audioclient.h>
31#include <basetsd.h> // for HRESULT, UINT, LPWSTR, GUID, UINT32
32#include <cctype> // for isdigit
33#include <cstdio> // for mesnprintf
34#include <cstdlib> // for realloc, atoi, malloc, mbtowc, free
35#include <cstring> // for NULL, memset, strcmp, strlen, wcslen
36#include <combaseapi.h> // for CoTaskMemFree, CoCreateInstance
37#include <guiddef.h> // for IsEqualGUID
38#include <iomanip>
39#include <iostream>
40#include <ksmedia.h> // for KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
41#include <mediaobj.h> // for REFERENCE_TIME
42#include <mmeapi.h> // for WAVEFORMATEX, WAVE_FORMAT_PCM
43#include <mmdeviceapi.h>
44#include <mmreg.h> // for PWAVEFORMATEXTENSIBLE, WAVE_FORMAT_...
45#include <objbase.h> // for STGM_READ
46#include <propidl.h> // for PropVariantClear, PropVariantInit
47#include <propsys.h> // for IPropertyStore
48#include <sstream>
49#include <string>
50#include <synchapi.h> // for Sleep
51#include <winerror.h> // for SUCCEEDED, S_OK, FAILED
52
53#include "audio/audio_capture.h"
54#include "audio/types.h"
55#include "debug.h"
56#include "host.h" // for audio_capture_channels, audio_captu...
57#include "lib_common.h"
58#include "types.h" // for device_info
59#include "ug_runtime_error.hpp"
60#include "utils/color_out.h"
61#include "utils/macros.h" // for snprintf_ch
62#include "utils/windows.h"
63
64#define MOD_NAME "[WASAPI cap.] "
65#define REFTIMES_PER_SEC 10000000
66#define REFTIMES_PER_MILLISEC 10000
67
68//const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
69//const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
70//const IID IID_IAudioClient = __uuidof(IAudioClient);
71const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
72const static GUID IDevice_FriendlyName = { 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0 } };
73const static PROPERTYKEY PKEY_Device_FriendlyName = { IDevice_FriendlyName, 14 };
74#pragma GCC diagnostic push
75#pragma GCC diagnostic ignored "-Wmissing-braces" // not our issue - defined by Mingw-w64
76const GUID KSDATAFORMAT_SUBTYPE_PCM = { STATIC_KSDATAFORMAT_SUBTYPE_PCM };
77const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { STATIC_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT };
78#pragma GCC diagnostic pop
79
80using std::cout;
81using std::ostringstream;
82using std::setfill;
83using std::setw;
84using std::string;
85using std::wstring;
86
90 IMMDevice *pDevice;
91 IAudioClient *pAudioClient;
92 IAudioCaptureClient *pCaptureClient;
93 UINT32 bufferSize;
94};
95
96static string get_name(IMMDevice *pDevice);
97static void show_help();
98string wasapi_get_default_device_id(EDataFlow dataFlow, IMMDeviceEnumerator *enumerator);
99
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)
104static void audio_cap_wasapi_probe(struct device_info **available_devices, int *dev_count, void (**deleter)(void *))
105{
106 *deleter = free;
107 *available_devices = nullptr;
108 *dev_count = 0;
109 bool com_initialized = false;
110
111 if (!com_initialize(&com_initialized, MOD_NAME)) {
112 return;
113 }
114 IMMDeviceEnumerator *enumerator = nullptr;
115 IMMDeviceCollection *pEndpoints = nullptr;
116
117 try {
118 THROW_IF_FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator,
119 (void **) &enumerator));
120
121 THROW_IF_FAILED(enumerator->EnumAudioEndpoints(eCapture, DEVICE_STATEMASK_ALL, &pEndpoints));
122 UINT count;
123 THROW_IF_FAILED(pEndpoints->GetCount(&count));
124 for (UINT i = 0; i < count; ++i) {
125 IMMDevice *pDevice = nullptr;
126 LPWSTR pwszID = NULL;
127 try {
128 THROW_IF_FAILED(pEndpoints->Item(i, &pDevice));
129 THROW_IF_FAILED(pDevice->GetId(&pwszID));
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());
134 ++*dev_count;
135 } catch (ug_runtime_error &e) {
136 LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Device " << i << ": " << e.what() << "\n";
137 }
138 SAFE_RELEASE(pDevice);
139 CoTaskMemFree(pwszID);
140 }
141 } catch (...) {
142 }
143 SAFE_RELEASE(enumerator);
144 SAFE_RELEASE(pEndpoints);
145 com_uninitialize(&com_initialized);
146 // add looopback
147 *available_devices = (struct device_info *)realloc(
148 *available_devices, (*dev_count + 1) * sizeof(struct device_info));
149 memset(&(*available_devices)[*dev_count], 0,
150 sizeof(struct device_info));
151 snprintf_ch((*available_devices)[*dev_count].dev, ":loopback");
152 snprintf_ch((*available_devices)[*dev_count].name,
153 "WASAPI computer audio output");
154 *dev_count += 1;
155}
156
157static string get_name(IMMDevice *pDevice) {
158 wstring out;
159 IPropertyStore *pProps = NULL;
160 PROPVARIANT varName;
161 LPWSTR pwszID = NULL;
162 // Initialize container for property value.
163 PropVariantInit(&varName);
164 HRESULT hr = pDevice->OpenPropertyStore(STGM_READ, &pProps);
165 if (!FAILED(hr)) {
166 hr = pProps->GetValue(PKEY_Device_FriendlyName, &varName);
167 if (!FAILED(hr)) {
168 out = varName.pwszVal;
169 }
170 }
171 SAFE_RELEASE(pProps);
172 CoTaskMemFree(pwszID);
173 PropVariantClear(&varName);
174 return win_wstr_to_str(out.c_str());
175}
176
177string wasapi_get_default_device_id(EDataFlow dataFlow, IMMDeviceEnumerator *enumerator) {
178 IMMDevice *pDevice = nullptr;
179 if (!SUCCEEDED(enumerator->GetDefaultAudioEndpoint(dataFlow, eConsole, &pDevice))) {
180 return {};
181 }
182 LPWSTR pwszID = nullptr;
183 if (!SUCCEEDED(pDevice->GetId(&pwszID))) {
184 SAFE_RELEASE(pDevice);
185 return {};
186 }
187 string ret = win_wstr_to_str(pwszID);
188 SAFE_RELEASE(pDevice);
189
190 return ret;
191}
192
193static void show_help() {
194 col() << "Usage:\n" <<
195 SBOLD(SRED("\t-s wasapi") << "[:<index>|:<ID>]") <<
196 "\n\nAvailable devices:\n";
197
198 IMMDeviceEnumerator *enumerator = nullptr;
199 IMMDeviceCollection *pEndpoints = nullptr;
200 bool com_initialized = false;
201 if (!com_initialize(&com_initialized, MOD_NAME)) {
202 return;
203 }
204
205 try {
206 THROW_IF_FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator,
207 (void **) &enumerator));
208 string default_dev_id = wasapi_get_default_device_id(eCapture, enumerator);
209 THROW_IF_FAILED(enumerator->EnumAudioEndpoints(eCapture, DEVICE_STATEMASK_ALL, &pEndpoints));
210 UINT count;
211 THROW_IF_FAILED(pEndpoints->GetCount(&count));
212 for (UINT i = 0; i < count; ++i) {
213 IMMDevice *pDevice = nullptr;
214 LPWSTR pwszID = NULL;
215 try {
216 THROW_IF_FAILED(pEndpoints->Item(i, &pDevice));
217 THROW_IF_FAILED(pDevice->GetId(&pwszID));
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";
220 } catch (ug_runtime_error &e) {
221 LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Device " << i << ": " << e.what() << "\n";
222 }
223 SAFE_RELEASE(pDevice);
224 CoTaskMemFree(pwszID);
225 }
226 } catch (...) {
227 }
228 SAFE_RELEASE(enumerator);
229 SAFE_RELEASE(pEndpoints);
230 com_uninitialize(&com_initialized);
231 col() << " " << SBOLD("loopback") << ") " << SBOLD("computer audio output") << " (ID: loopback)\n";
232}
233
234static void * audio_cap_wasapi_init(struct module *parent, const char *cfg)
235{
236 UNUSED(parent);
237 wchar_t deviceID[1024] = L"";
238 WAVEFORMATEX *pwfx = NULL;
239 enum { IDX_LOOP = -2, IDX_DFL = -1 };
240 int index = IDX_DFL;
241 if (strlen(cfg) > 0) {
242 if (strcmp(cfg, "help") == 0) {
243 show_help();
244 return INIT_NOERR;
245 }
246 if (isdigit(cfg[0])) {
247 index = atoi(cfg);
248 } else if (strcmp(cfg, "loopback") == 0) {
249 index = IDX_LOOP;
250 } else {
251 mbtowc(deviceID, cfg, (sizeof deviceID / 2) - 1);
252 }
253 }
254 auto s = new state_acap_wasapi();
255 if (!com_initialize(&s->com_initialized, MOD_NAME)) {
256 delete s;
257 return nullptr;
258 }
259 IMMDeviceEnumerator *enumerator = nullptr;
260 try {
261
262 THROW_IF_FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator,
263 (void **) &enumerator));
264 if (wcslen(deviceID) > 0) {
265 THROW_IF_FAILED(enumerator->GetDevice(deviceID, &s->pDevice));
266 } else if (index >= 0) {
267 IMMDeviceCollection *pEndpoints = nullptr;
268 try {
269 THROW_IF_FAILED(enumerator->EnumAudioEndpoints(eCapture, DEVICE_STATEMASK_ALL, &pEndpoints));
270 UINT count;
271 THROW_IF_FAILED(pEndpoints->GetCount(&count));
272 for (UINT i = 0; i < count; ++i) {
273 if (i == (UINT) index) {
274 THROW_IF_FAILED(pEndpoints->Item(i, &s->pDevice));
275 break;
276 }
277 }
278 } catch (ug_runtime_error &e) { // just continue with the next
279 LOG(LOG_LEVEL_WARNING) << MOD_NAME << e.what() << "\n";
280 }
281 SAFE_RELEASE(pEndpoints);
282 } else {// default device
283 THROW_IF_FAILED(enumerator->GetDefaultAudioEndpoint(
284 index == IDX_DFL ? eCapture : eRender, eConsole,
285 &s->pDevice));
286 }
287 if (!s->pDevice) {
288 throw ug_runtime_error("Device not found!");
289 }
290 THROW_IF_FAILED(s->pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL,
291 (void **)&s->pAudioClient));
292
293 auto friendlyName = get_name(s->pDevice);
294 if (!friendlyName.empty()) {
295 LOG(LOG_LEVEL_NOTICE) << MOD_NAME << "Using device: "
296 << friendlyName << "\n";
297 }
298
299 REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
300 // get the mixer format
301 THROW_IF_FAILED(s->pAudioClient->GetMixFormat(&pwfx));
302 // set our preferences
303 if (audio_capture_channels != 0) {
304 pwfx->nChannels = audio_capture_channels;
305 }
307 pwfx->nSamplesPerSec = audio_capture_sample_rate;
308 }
309 pwfx->wBitsPerSample = 16;
310 if (audio_capture_bps) {
311 pwfx->wBitsPerSample = audio_capture_bps * 8;
312 }
313 // coerce the format
314 // taken from https://github.com/Riateche/SoundStreamer/blob/master/SoundStreamer/SoundStreamer.cpp
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;
320 break;
321
322 case WAVE_FORMAT_EXTENSIBLE:
323 {
324 // naked scope for case-local variable
325 PWAVEFORMATEXTENSIBLE waveFormatEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(pwfx);
326 if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, waveFormatEx->SubFormat)) {
327 waveFormatEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
328 waveFormatEx->Samples.wValidBitsPerSample =
329 pwfx->wBitsPerSample;
330 pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
331 pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
332 } else {
333 throw ug_runtime_error("Don't know how to coerce mix format to int-16");
334 }
335 }
336 break;
337
338 default:
339 ostringstream oss;
340 oss << "Don't know how to coerce WAVEFORMATEX with wFormatTag = 0x" << setw(8) << setfill('0') << " to PCM";
341 throw ug_runtime_error(oss.str());
342 }
343
344 THROW_IF_FAILED(s->pAudioClient->Initialize(
345 AUDCLNT_SHAREMODE_SHARED,
346 index == IDX_LOOP ? AUDCLNT_STREAMFLAGS_LOOPBACK : 0,
347 hnsRequestedDuration,
348 0,
349 pwfx,
350 NULL));
351
352 UINT32 bufferFrameCount;
353 THROW_IF_FAILED(s->pAudioClient->GetBufferSize(&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);
359
360 THROW_IF_FAILED(s->pAudioClient->GetService(IID_IAudioCaptureClient, (void **) &s->pCaptureClient));
361 THROW_IF_FAILED(s->pAudioClient->Start());
362
363 } catch (ug_runtime_error &e) {
364 LOG(LOG_LEVEL_ERROR) << MOD_NAME << e.what() << "\n";
365 if (audio_capture_channels != 0) {
366 LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Maybe wrong number of channels? Try using default.";
367 }
368 CoUninitialize();
369 delete s;
370 s = nullptr;
371 }
372
373 SAFE_RELEASE(enumerator);
374 CoTaskMemFree(pwfx);
375 return s;
376}
377
378static void audio_cap_wasapi_done(void *state)
379{
380 CoUninitialize();
381 delete static_cast<state_acap_wasapi *>(state);
382}
383
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)
386{
387 auto s = static_cast<state_acap_wasapi *>(state);
388
389 UINT32 packetLength = 0;
390 FAIL_IF_NOT(s->pCaptureClient->GetNextPacketSize(&packetLength));
391 if (packetLength == 0) {
392 Sleep(10);
393 }
394
395 FAIL_IF_NOT(s->pCaptureClient->GetNextPacketSize(&packetLength));
396
397 s->frame.data_len = 0;
398
399 while (packetLength != 0) {
400 UINT32 numFramesAvailable;
401 BYTE *pData;
402 DWORD flags;
403 // Get the available data in the shared buffer.
404 FAIL_IF_NOT(s->pCaptureClient->GetBuffer(
405 &pData,
406 &numFramesAvailable,
407 &flags, NULL, NULL));
408
409 //if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
410 //{
411 // pData = NULL; // Tell CopyData to write silence.
412 //}
413
414 // Copy the available capture data to the audio sink.
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;
418 }
419 memcpy(s->frame.data + s->frame.data_len, pData, count);
420 //fprintf(stderr, "%d\n", 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) {
424 return &s->frame;
425 }
426
427 FAIL_IF_NOT(s->pCaptureClient->GetNextPacketSize(&packetLength));
428 }
429 return &s->frame;
430}
431
438
440
441/* vim: set expandtab sw=8: */
442
#define UNUSED(x)
void ** state
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)
Definition wasapi.cpp:378
static const PROPERTYKEY PKEY_Device_FriendlyName
Definition wasapi.cpp:73
#define THROW_IF_FAILED(cmd)
Definition wasapi.cpp:103
static const struct audio_capture_info acap_wasapi_info
Definition wasapi.cpp:432
static void audio_cap_wasapi_probe(struct device_info **available_devices, int *dev_count, void(**deleter)(void *))
Definition wasapi.cpp:104
static struct audio_frame * audio_cap_wasapi_read(void *state)
Definition wasapi.cpp:385
static void * audio_cap_wasapi_init(struct module *parent, const char *cfg)
Definition wasapi.cpp:234
#define SAFE_RELEASE(u)
Definition wasapi.cpp:100
static void show_help()
Definition wasapi.cpp:193
const IID IID_IAudioCaptureClient
Definition wasapi.cpp:71
#define REFTIMES_PER_SEC
Definition wasapi.cpp:65
#define MOD_NAME
Definition wasapi.cpp:64
string wasapi_get_default_device_id(EDataFlow dataFlow, IMMDeviceEnumerator *enumerator)
Definition wasapi.cpp:177
static const GUID IDevice_FriendlyName
Definition wasapi.cpp:72
const GUID KSDATAFORMAT_SUBTYPE_PCM
Definition wasapi.cpp:76
#define FAIL_IF_NOT(cmd)
Definition wasapi.cpp:384
const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
Definition wasapi.cpp:77
static string get_name(IMMDevice *pDevice)
Definition wasapi.cpp:157
#define SRED(x)
Definition color_out.h:85
#define SBOLD(x)
Definition color_out.h:80
unsigned char BYTE
Definition crypt_aes.h:71
u32 UINT
Definition cuda_dxt.cu:13
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG(level)
Definition debug.h:313
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
int count
Definition hd-rum.c:44
unsigned int audio_capture_bps
Definition host.cpp:164
unsigned int audio_capture_sample_rate
Definition host.cpp:165
unsigned int audio_capture_channels
Definition host.cpp:163
#define INIT_NOERR
Definition host.h:73
@ LIBRARY_CLASS_AUDIO_CAPTURE
Definition lib_common.h:76
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
Definition lib_common.h:144
#define snprintf_ch(str,...)
shortcut for snprintf(var, sizeof var...), var must be a char array
Definition macros.h:101
const CLSID CLSID_MMDeviceEnumerator
Definition wasapi.cpp:68
const IID IID_IAudioClient
Definition wasapi.cpp:70
const IID IID_IMMDeviceEnumerator
Definition wasapi.cpp:69
const char * parent
Definition settings.cpp:198
const char * name
Definition settings.cpp:193
struct used to comunicate with audio capture/playback device
Definition types.h:99
char dev[1024]
Definition types.h:383
IAudioCaptureClient * pCaptureClient
Definition wasapi.cpp:92
IAudioClient * pAudioClient
Definition wasapi.cpp:91
UINT32 bufferSize
Definition wasapi.cpp:93
IMMDevice * pDevice
Definition wasapi.cpp:90
struct audio_frame frame
Definition wasapi.cpp:89
This file contains some common data types.
@ FAILED
bool com_initialize(bool *com_initialized, const char *err_prefix)
Definition windows.c:65
void com_uninitialize(bool *com_initialized)
Definition windows.c:94