31#include <audioclient.h>
32#include <audiosessiontypes.h>
36#include <combaseapi.h>
43#include <mmdeviceapi.h>
63#define DEFAULT_WASAPI_BUFLEN_MS 67
64#define MOD_NAME "[WASAPI play.] "
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"
77#pragma GCC diagnostic pop
81using std::ostringstream;
97#define SAFE_RELEASE(u) \
98 do { if ((u) != NULL) (u)->Release(); (u) = NULL; } while(0)
100#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)
104 IPropertyStore *pProps = NULL;
106 LPWSTR pwszID = NULL;
108 PropVariantInit(&varName);
109 HRESULT hr = pDevice->OpenPropertyStore(STGM_READ, &pProps);
113 out = varName.pwszVal;
117 CoTaskMemFree(pwszID);
118 PropVariantClear(&varName);
119 return win_wstr_to_str(out.c_str());
125 *available_devices = (
struct device_info *) malloc(0);
128 IMMDeviceEnumerator *enumerator =
nullptr;
129 IMMDeviceCollection *pEndpoints =
nullptr;
130 bool com_initialized =
false;
137 (
void **) &enumerator));
139 THROW_IF_FAILED(enumerator->EnumAudioEndpoints(eRender, DEVICE_STATEMASK_ALL, &pEndpoints));
143 IMMDevice *pDevice =
nullptr;
144 LPWSTR pwszID = NULL;
148 *available_devices = (
struct device_info *) realloc(*available_devices, (*dev_count + 1) *
sizeof(
struct device_info));
149 memset(&(*available_devices)[*dev_count], 0,
sizeof(
struct device_info));
150 snprintf((*available_devices)[*dev_count].
dev,
sizeof (*available_devices)[*dev_count].
dev,
":%u", i);
151 snprintf((*available_devices)[*dev_count].
name,
sizeof (*available_devices)[*dev_count].
name,
"WASAPI %s",
get_name(pDevice).c_str());
157 CoTaskMemFree(pwszID);
167 col() <<
"Usage:\n" <<
168 SBOLD(
SRED(
"\t-r wasapi") <<
"[:<index>|:<ID>] --param audio-buffer-len=<ms>") <<
"\n" <<
169 "\nAvailable devices:\n";
171 bool com_initialized =
false;
175 IMMDeviceEnumerator *enumerator =
nullptr;
176 IMMDeviceCollection *pEndpoints =
nullptr;
180 (
void **) &enumerator));
182 THROW_IF_FAILED(enumerator->EnumAudioEndpoints(eRender, DEVICE_STATEMASK_ALL, &pEndpoints));
187 IMMDevice *pDevice =
nullptr;
188 LPWSTR pwszID = NULL;
192 string dev_id = win_wstr_to_str(pwszID);
193 col() << (dev_id == default_dev_id ?
"(*)" :
"") <<
"\t" <<
SBOLD(i) <<
") " <<
SBOLD(
get_name(pDevice)) <<
" (ID: " << dev_id <<
")\n";
198 CoTaskMemFree(pwszID);
209 wchar_t deviceID[1024] = L
"";
211 if (strlen(cfg) > 0) {
212 if (strcmp(cfg,
"help") == 0) {
216 if (isdigit(cfg[0])) {
219 mbtowc(deviceID, cfg, (
sizeof deviceID / 2) - 1);
227 IMMDeviceEnumerator *enumerator =
nullptr;
231 (
void **) &enumerator));
232 if (wcslen(deviceID) > 0) {
234 }
else if (index != -1) {
235 IMMDeviceCollection *pEndpoints =
nullptr;
237 THROW_IF_FAILED(enumerator->EnumAudioEndpoints(eRender, DEVICE_STATEMASK_ALL, &pEndpoints));
241 if (i == (
UINT) index) {
251 THROW_IF_FAILED(enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &s->pDevice));
257 (
void **)&s->pAudioClient));
259 auto friendlyName =
get_name(s->pDevice);
260 if (!friendlyName.empty()) {
262 << friendlyName <<
"\n";
284 case 1:
return KSAUDIO_SPEAKER_MONO;
285 case 2:
return KSAUDIO_SPEAKER_STEREO;
286 case 3: *
count = 4;
return KSAUDIO_SPEAKER_SURROUND;
287 case 4:
return KSAUDIO_SPEAKER_QUAD;
288 case 5: *
count = 6;
return KSAUDIO_SPEAKER_5POINT1_SURROUND;
289 case 6:
return KSAUDIO_SPEAKER_5POINT1;
290 case 7: *
count = 7;
return KSAUDIO_SPEAKER_5POINT1_SURROUND;
291 case 8:
return KSAUDIO_SPEAKER_7POINT1;
292 default: *
count = 8;
return KSAUDIO_SPEAKER_7POINT1;
298 WAVEFORMATEXTENSIBLE
fmt;
301 fmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
302 fmt.Format.nChannels =
desc->ch_count;
303 fmt.Format.wBitsPerSample =
desc->bps * 8;
304 fmt.Format.nSamplesPerSec =
desc->sample_rate;
305 fmt.Format.nBlockAlign =
fmt.Format.nChannels *
fmt.Format.wBitsPerSample / 8;
306 fmt.Format.nAvgBytesPerSec =
fmt.Format.nSamplesPerSec *
fmt.Format.nBlockAlign;
307 fmt.Format.cbSize =
sizeof(WAVEFORMATEXTENSIBLE) -
sizeof(WAVEFORMATEX);
308 fmt.Samples.wValidBitsPerSample =
fmt.Format.wBitsPerSample;
316 WAVEFORMATEXTENSIBLE
fmt;
317 WAVEFORMATEX *closestMatch;
328 hr = s->pAudioClient->IsFormatSupported(
329 AUDCLNT_SHAREMODE_SHARED,
330 (WAVEFORMATEX *) &
fmt,
332 if (hr != S_OK && hr != S_FALSE) {
337 desc.ch_count = closestMatch->nChannels;
338 desc.bps = closestMatch->wBitsPerSample / 8;
339 desc.sample_rate = closestMatch->nSamplesPerSec;
340 CoTaskMemFree(closestMatch);
351#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 false;}} while(0)
358 assert(buflen_ms > 0);
362 FAIL_IF_NOT(s->pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, bufferDuration, 0,
363 (WAVEFORMATEX *) &
fmt, NULL));
366 FAIL_IF_NOT(s->pAudioClient->GetBufferSize(&s->bufferSize));
374#define CHECK(cmd) do {HRESULT hr = cmd; if (hr != S_OK) { LOG(LOG_LEVEL_ERROR) << MOD_NAME << #cmd ": " << hresult_to_str(hr) << "\n"; return;}} while(0)
378 UINT32 numFramesPadding;
380 CHECK(s->pAudioClient->GetCurrentPadding(&numFramesPadding));
383 if (s->bufferSize - numFramesPadding < NumFramesRequested) {
385 NumFramesRequested = s->bufferSize - numFramesPadding;
388 CHECK(s->pRenderClient->GetBuffer(NumFramesRequested, &data));
389 memcpy(data, buffer->
data, NumFramesRequested * buffer->
ch_count * buffer->
bps);
390 CHECK(s->pRenderClient->ReleaseBuffer(NumFramesRequested, 0));
int void(** deleter)(void *))
This file contains definition of some common audio types.
#define AUDIO_PLAYBACK_CTL_QUERY_FORMAT
#define AUDIO_PLAYBACK_ABI_VERSION
#define LOG_LEVEL_NOTICE
information that may be interesting
#define LOG_LEVEL_INFO
normal reporting
#define LOG_LEVEL_ERROR
general errors
#define LOG_LEVEL_WARNING
less severe errors
const char * get_commandline_param(const char *key)
@ LIBRARY_CLASS_AUDIO_PLAYBACK
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
static void * audio_play_wasapi_init(const char *cfg)
static void audio_play_wasapi_done(void *state)
#define DEFAULT_WASAPI_BUFLEN_MS
static const PROPERTYKEY PKEY_Device_FriendlyName
static const struct audio_playback_info aplay_wasapi_info
static bool audio_play_wasapi_reconfigure(void *state, struct audio_desc desc)
#define THROW_IF_FAILED(cmd)
static DWORD get_channel_mask(int *count)
static void audio_play_wasapi_put_frame(void *state, const struct audio_frame *buffer)
static void audio_play_wasapi_probe(struct device_info **available_devices, int *dev_count, void(**deleter)(void *))
const IID IID_IAudioRenderClient
static WAVEFORMATEXTENSIBLE audio_format_to_waveformatex(struct audio_desc *desc)
const CLSID CLSID_MMDeviceEnumerator
const IID IID_IAudioClient
string wasapi_get_default_device_id(EDataFlow dataFlow, IMMDeviceEnumerator *enumerator)
static const GUID IDevice_FriendlyName
static const GUID UG_KSDATAFORMAT_SUBTYPE_PCM
static void audio_play_wasapi_help()
static bool audio_play_wasapi_ctl(void *state, int request, void *data, size_t *len)
const IID IID_IMMDeviceEnumerator
static string get_name(IMMDevice *pDevice)
#define REFTIMES_PER_MILLISEC
struct used to comunicate with audio capture/playback device
IAudioRenderClient * pRenderClient
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)