libsoundio/src/wasapi.cpp

1140 lines
40 KiB
C++
Raw Normal View History

2015-08-13 02:26:07 +00:00
/*
* Copyright (c) 2015 Andrew Kelley
*
* This file is part of libsoundio, which is MIT licensed.
* See http://opensource.org/licenses/MIT
*/
2015-08-08 22:22:50 +00:00
#include "wasapi.hpp"
#include "soundio.hpp"
#include <stdio.h>
2015-08-12 18:01:06 +00:00
// Attempting to use the Windows-supplied versions of these constants resulted
// in `undefined reference` linker errors.
const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
0x00000003,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = {
0x00000001,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
2015-08-11 06:18:08 +00:00
// Adding more common sample rates helps the heuristics; feel free to do that.
static int test_sample_rates[] = {
8000,
11025,
16000,
22050,
32000,
37800,
44056,
44100,
47250,
48000,
50000,
50400,
88200,
96000,
176400,
192000,
352800,
2822400,
5644800,
};
// If you modify this list, also modify `to_wave_format_format` appropriately.
static SoundIoFormat test_formats[] = {
SoundIoFormatU8,
SoundIoFormatS16LE,
SoundIoFormatS24LE,
SoundIoFormatS32LE,
SoundIoFormatFloat32LE,
SoundIoFormatFloat64LE,
};
// If you modify this list, also modify `to_wave_format_layout` appropriately.
static SoundIoChannelLayoutId test_layouts[] = {
SoundIoChannelLayoutIdMono,
SoundIoChannelLayoutIdStereo,
SoundIoChannelLayoutIdQuad,
SoundIoChannelLayoutId4Point0,
SoundIoChannelLayoutId5Point1,
SoundIoChannelLayoutId7Point1,
SoundIoChannelLayoutId5Point1Back,
};
2015-08-11 06:18:08 +00:00
// converts a windows wide string to a UTF-8 encoded char *
// Possible errors:
// * SoundIoErrorNoMem
// * SoundIoErrorEncodingString
static int from_lpwstr(LPWSTR lpwstr, char **out_str, int *out_str_len) {
DWORD flags = 0;
int buf_size = WideCharToMultiByte(CP_UTF8, flags, lpwstr, -1, nullptr, 0, nullptr, nullptr);
if (buf_size == 0)
return SoundIoErrorEncodingString;
2015-08-13 02:26:07 +00:00
char *buf = allocate<char>(buf_size);
2015-08-11 06:18:08 +00:00
if (!buf)
return SoundIoErrorNoMem;
if (WideCharToMultiByte(CP_UTF8, flags, lpwstr, -1, buf, buf_size, nullptr, nullptr) != buf_size) {
free(buf);
return SoundIoErrorEncodingString;
}
*out_str = buf;
2015-08-13 02:26:07 +00:00
*out_str_len = buf_size - 1;
2015-08-11 06:18:08 +00:00
return 0;
}
static void from_channel_mask_layout(UINT channel_mask, SoundIoChannelLayout *layout) {
layout->channel_count = 0;
if (channel_mask & SPEAKER_FRONT_LEFT)
layout->channels[layout->channel_count++] = SoundIoChannelIdFrontLeft;
if (channel_mask & SPEAKER_FRONT_RIGHT)
layout->channels[layout->channel_count++] = SoundIoChannelIdFrontRight;
if (channel_mask & SPEAKER_FRONT_CENTER)
layout->channels[layout->channel_count++] = SoundIoChannelIdFrontCenter;
if (channel_mask & SPEAKER_LOW_FREQUENCY)
layout->channels[layout->channel_count++] = SoundIoChannelIdLfe;
if (channel_mask & SPEAKER_BACK_LEFT)
layout->channels[layout->channel_count++] = SoundIoChannelIdBackLeft;
if (channel_mask & SPEAKER_BACK_RIGHT)
layout->channels[layout->channel_count++] = SoundIoChannelIdBackRight;
if (channel_mask & SPEAKER_FRONT_LEFT_OF_CENTER)
layout->channels[layout->channel_count++] = SoundIoChannelIdFrontLeftCenter;
if (channel_mask & SPEAKER_FRONT_RIGHT_OF_CENTER)
layout->channels[layout->channel_count++] = SoundIoChannelIdFrontRightCenter;
if (channel_mask & SPEAKER_BACK_CENTER)
layout->channels[layout->channel_count++] = SoundIoChannelIdBackCenter;
if (channel_mask & SPEAKER_SIDE_LEFT)
layout->channels[layout->channel_count++] = SoundIoChannelIdSideLeft;
if (channel_mask & SPEAKER_SIDE_RIGHT)
layout->channels[layout->channel_count++] = SoundIoChannelIdSideRight;
if (channel_mask & SPEAKER_TOP_CENTER)
layout->channels[layout->channel_count++] = SoundIoChannelIdTopCenter;
if (channel_mask & SPEAKER_TOP_FRONT_LEFT)
layout->channels[layout->channel_count++] = SoundIoChannelIdTopFrontLeft;
if (channel_mask & SPEAKER_TOP_FRONT_CENTER)
layout->channels[layout->channel_count++] = SoundIoChannelIdTopFrontCenter;
if (channel_mask & SPEAKER_TOP_FRONT_RIGHT)
layout->channels[layout->channel_count++] = SoundIoChannelIdTopFrontRight;
if (channel_mask & SPEAKER_TOP_BACK_LEFT)
layout->channels[layout->channel_count++] = SoundIoChannelIdTopBackLeft;
if (channel_mask & SPEAKER_TOP_BACK_CENTER)
layout->channels[layout->channel_count++] = SoundIoChannelIdTopBackCenter;
if (channel_mask & SPEAKER_TOP_BACK_RIGHT)
layout->channels[layout->channel_count++] = SoundIoChannelIdTopBackRight;
soundio_channel_layout_detect_builtin(layout);
}
static void from_wave_format_layout(WAVEFORMATEXTENSIBLE *wave_format, SoundIoChannelLayout *layout) {
assert(wave_format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE);
layout->channel_count = 0;
from_channel_mask_layout(wave_format->dwChannelMask, layout);
}
static SoundIoFormat from_wave_format_format(WAVEFORMATEXTENSIBLE *wave_format) {
assert(wave_format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE);
bool is_pcm = IsEqualGUID(wave_format->SubFormat, SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM);
bool is_float = IsEqualGUID(wave_format->SubFormat, SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT);
if (wave_format->Samples.wValidBitsPerSample == wave_format->Format.wBitsPerSample) {
if (wave_format->Format.wBitsPerSample == 8) {
if (is_pcm)
return SoundIoFormatU8;
} else if (wave_format->Format.wBitsPerSample == 16) {
if (is_pcm)
return SoundIoFormatS16LE;
} else if (wave_format->Format.wBitsPerSample == 32) {
if (is_pcm)
return SoundIoFormatS32LE;
else if (is_float)
return SoundIoFormatFloat32LE;
} else if (wave_format->Format.wBitsPerSample == 64) {
if (is_float)
return SoundIoFormatFloat64LE;
}
} else if (wave_format->Format.wBitsPerSample == 32 &&
wave_format->Samples.wValidBitsPerSample == 24)
{
return SoundIoFormatS24LE;
}
return SoundIoFormatInvalid;
}
// only needs to support the layouts in test_layouts
static void to_wave_format_layout(const SoundIoChannelLayout *layout, WAVEFORMATEXTENSIBLE *wave_format) {
wave_format->dwChannelMask = 0;
wave_format->Format.nChannels = layout->channel_count;
for (int i = 0; i < layout->channel_count; i += 1) {
SoundIoChannelId channel_id = layout->channels[i];
switch (channel_id) {
case SoundIoChannelIdFrontLeft:
wave_format->dwChannelMask |= SPEAKER_FRONT_LEFT;
break;
case SoundIoChannelIdFrontRight:
wave_format->dwChannelMask |= SPEAKER_FRONT_RIGHT;
break;
case SoundIoChannelIdFrontCenter:
wave_format->dwChannelMask |= SPEAKER_FRONT_CENTER;
break;
case SoundIoChannelIdLfe:
wave_format->dwChannelMask |= SPEAKER_LOW_FREQUENCY;
break;
case SoundIoChannelIdBackLeft:
wave_format->dwChannelMask |= SPEAKER_BACK_LEFT;
break;
case SoundIoChannelIdBackRight:
wave_format->dwChannelMask |= SPEAKER_BACK_RIGHT;
break;
case SoundIoChannelIdFrontLeftCenter:
wave_format->dwChannelMask |= SPEAKER_FRONT_LEFT_OF_CENTER;
break;
case SoundIoChannelIdFrontRightCenter:
wave_format->dwChannelMask |= SPEAKER_FRONT_RIGHT_OF_CENTER;
break;
case SoundIoChannelIdBackCenter:
wave_format->dwChannelMask |= SPEAKER_BACK_CENTER;
break;
case SoundIoChannelIdSideLeft:
wave_format->dwChannelMask |= SPEAKER_SIDE_LEFT;
break;
case SoundIoChannelIdSideRight:
wave_format->dwChannelMask |= SPEAKER_SIDE_RIGHT;
break;
case SoundIoChannelIdTopCenter:
wave_format->dwChannelMask |= SPEAKER_TOP_CENTER;
break;
case SoundIoChannelIdTopFrontLeft:
wave_format->dwChannelMask |= SPEAKER_TOP_FRONT_LEFT;
break;
case SoundIoChannelIdTopFrontCenter:
wave_format->dwChannelMask |= SPEAKER_TOP_FRONT_CENTER;
break;
case SoundIoChannelIdTopFrontRight:
wave_format->dwChannelMask |= SPEAKER_TOP_FRONT_RIGHT;
break;
case SoundIoChannelIdTopBackLeft:
wave_format->dwChannelMask |= SPEAKER_TOP_BACK_LEFT;
break;
case SoundIoChannelIdTopBackCenter:
wave_format->dwChannelMask |= SPEAKER_TOP_BACK_CENTER;
break;
case SoundIoChannelIdTopBackRight:
wave_format->dwChannelMask |= SPEAKER_TOP_BACK_RIGHT;
break;
default:
soundio_panic("to_wave_format_layout: unsupported channel id");
}
}
}
// only needs to support the formats in test_formats
static void to_wave_format_format(SoundIoFormat format, WAVEFORMATEXTENSIBLE *wave_format) {
switch (format) {
case SoundIoFormatU8:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM;
wave_format->Format.wBitsPerSample = 8;
wave_format->Samples.wValidBitsPerSample = 8;
break;
case SoundIoFormatS16LE:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM;
wave_format->Format.wBitsPerSample = 16;
wave_format->Samples.wValidBitsPerSample = 16;
break;
case SoundIoFormatS24LE:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM;
wave_format->Format.wBitsPerSample = 32;
wave_format->Samples.wValidBitsPerSample = 24;
break;
case SoundIoFormatS32LE:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM;
wave_format->Format.wBitsPerSample = 32;
wave_format->Samples.wValidBitsPerSample = 32;
break;
case SoundIoFormatFloat32LE:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
wave_format->Format.wBitsPerSample = 32;
wave_format->Samples.wValidBitsPerSample = 32;
break;
case SoundIoFormatFloat64LE:
wave_format->SubFormat = SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
wave_format->Format.wBitsPerSample = 64;
wave_format->Samples.wValidBitsPerSample = 64;
break;
default:
soundio_panic("to_wave_format_format: unsupported format");
}
}
2015-08-11 06:18:08 +00:00
static SoundIoDeviceAim data_flow_to_aim(EDataFlow data_flow) {
return (data_flow == eRender) ? SoundIoDeviceAimOutput : SoundIoDeviceAimInput;
}
static double from_reference_time(REFERENCE_TIME rt) {
return ((double)rt) / 10000000.0;
}
static void destruct_device(SoundIoDevicePrivate *dev) {
SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi;
if (dw->audio_client)
IUnknown_Release(dw->audio_client);
dw->sample_rates.deinit();
}
struct RefreshDevices {
IMMDeviceCollection *collection;
2015-08-11 06:18:08 +00:00
IMMDevice *mm_device;
2015-08-12 18:01:06 +00:00
IMMDevice *default_render_device;
IMMDevice *default_capture_device;
2015-08-11 06:18:08 +00:00
IMMEndpoint *endpoint;
IPropertyStore *prop_store;
LPWSTR lpwstr;
PROPVARIANT prop_variant_value;
WAVEFORMATEXTENSIBLE *wave_format;
2015-08-11 06:18:08 +00:00
bool prop_variant_value_inited;
SoundIoDevicesInfo *devices_info;
2015-08-13 02:26:07 +00:00
SoundIoDevice *device_shared;
SoundIoDevice *device_raw;
2015-08-12 18:01:06 +00:00
char *default_render_id;
int default_render_id_len;
char *default_capture_id;
int default_capture_id_len;
};
static void deinit_refresh_devices(RefreshDevices *rd) {
2015-08-11 06:18:08 +00:00
soundio_destroy_devices_info(rd->devices_info);
2015-08-13 02:26:07 +00:00
soundio_device_unref(rd->device_shared);
soundio_device_unref(rd->device_raw);
2015-08-11 06:18:08 +00:00
if (rd->mm_device)
IMMDevice_Release(rd->mm_device);
2015-08-12 18:01:06 +00:00
if (rd->default_render_device)
IMMDevice_Release(rd->default_render_device);
if (rd->default_capture_device)
IMMDevice_Release(rd->default_capture_device);
if (rd->collection)
IMMDeviceCollection_Release(rd->collection);
2015-08-11 06:18:08 +00:00
if (rd->lpwstr)
CoTaskMemFree(rd->lpwstr);
if (rd->endpoint)
IMMEndpoint_Release(rd->endpoint);
if (rd->prop_store)
IPropertyStore_Release(rd->prop_store);
if (rd->prop_variant_value_inited)
PropVariantClear(&rd->prop_variant_value);
if (rd->wave_format)
CoTaskMemFree(rd->wave_format);
}
static int detect_valid_layouts(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_format,
SoundIoDevicePrivate *dev, AUDCLNT_SHAREMODE share_mode)
{
SoundIoDevice *device = &dev->pub;
SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi;
HRESULT hr;
device->layout_count = 0;
device->layouts = allocate<SoundIoChannelLayout>(array_length(test_layouts));
if (!device->layouts)
return SoundIoErrorNoMem;
WAVEFORMATEX *closest_match = nullptr;
WAVEFORMATEXTENSIBLE orig_wave_format = *wave_format;
for (int i = 0; i < array_length(test_formats); i += 1) {
SoundIoChannelLayoutId test_layout_id = test_layouts[i];
const SoundIoChannelLayout *test_layout = soundio_channel_layout_get_builtin(test_layout_id);
to_wave_format_layout(test_layout, wave_format);
HRESULT hr = IAudioClient_IsFormatSupported(dw->audio_client, share_mode,
(WAVEFORMATEX*)wave_format, &closest_match);
if (closest_match) {
CoTaskMemFree(closest_match);
closest_match = nullptr;
}
if (hr == S_OK) {
device->layouts[device->layout_count++] = *test_layout;
} else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == S_FALSE || hr == E_INVALIDARG) {
continue;
} else {
*wave_format = orig_wave_format;
return SoundIoErrorOpeningDevice;
}
}
*wave_format = orig_wave_format;
return 0;
}
static int detect_valid_formats(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_format,
SoundIoDevicePrivate *dev, AUDCLNT_SHAREMODE share_mode)
{
SoundIoDevice *device = &dev->pub;
SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi;
HRESULT hr;
device->format_count = 0;
device->formats = allocate<SoundIoFormat>(array_length(test_formats));
if (!device->formats)
return SoundIoErrorNoMem;
WAVEFORMATEX *closest_match = nullptr;
WAVEFORMATEXTENSIBLE orig_wave_format = *wave_format;
for (int i = 0; i < array_length(test_formats); i += 1) {
SoundIoFormat test_format = test_formats[i];
to_wave_format_format(test_format, wave_format);
HRESULT hr = IAudioClient_IsFormatSupported(dw->audio_client, share_mode,
(WAVEFORMATEX*)wave_format, &closest_match);
if (closest_match) {
CoTaskMemFree(closest_match);
closest_match = nullptr;
}
if (hr == S_OK) {
device->formats[device->format_count++] = test_format;
} else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == S_FALSE || hr == E_INVALIDARG) {
continue;
} else {
*wave_format = orig_wave_format;
return SoundIoErrorOpeningDevice;
}
}
*wave_format = orig_wave_format;
return 0;
}
static int add_sample_rate(SoundIoList<SoundIoSampleRateRange> *sample_rates, int *current_min, int the_max) {
int err;
if ((err = sample_rates->add_one()))
return err;
SoundIoSampleRateRange *last_range = &sample_rates->last();
last_range->min = *current_min;
last_range->max = the_max;
return 0;
}
static int do_sample_rate_test(SoundIoDevicePrivate *dev, WAVEFORMATEXTENSIBLE *wave_format,
int test_sample_rate, AUDCLNT_SHAREMODE share_mode, int *current_min, int *last_success_rate)
{
SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi;
WAVEFORMATEX *closest_match = nullptr;
int err;
wave_format->Format.nSamplesPerSec = test_sample_rate;
HRESULT hr = IAudioClient_IsFormatSupported(dw->audio_client, share_mode,
(WAVEFORMATEX*)wave_format, &closest_match);
if (closest_match) {
CoTaskMemFree(closest_match);
closest_match = nullptr;
}
if (hr == S_OK) {
if (*current_min == -1) {
*current_min = test_sample_rate;
}
*last_success_rate = test_sample_rate;
} else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == S_FALSE || hr == E_INVALIDARG) {
if (*current_min != -1) {
if ((err = add_sample_rate(&dw->sample_rates, current_min, *last_success_rate)))
return err;
*current_min = -1;
}
} else {
return SoundIoErrorOpeningDevice;
}
return 0;
}
static int detect_valid_sample_rates(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_format,
SoundIoDevicePrivate *dev, AUDCLNT_SHAREMODE share_mode)
{
SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi;
int err;
DWORD orig_sample_rate = wave_format->Format.nSamplesPerSec;
assert(dw->sample_rates.length == 0);
int current_min = -1;
int last_success_rate = -1;
for (int i = 0; i < array_length(test_sample_rates); i += 1) {
for (int offset = -1; offset <= 1; offset += 1) {
int test_sample_rate = test_sample_rates[i] + offset;
if ((err = do_sample_rate_test(dev, wave_format, test_sample_rate, share_mode,
&current_min, &last_success_rate)))
{
wave_format->Format.nSamplesPerSec = orig_sample_rate;
return err;
}
}
}
if (current_min != -1) {
if ((err = add_sample_rate(&dw->sample_rates, &current_min, last_success_rate))) {
wave_format->Format.nSamplesPerSec = orig_sample_rate;
return err;
}
}
SoundIoDevice *device = &dev->pub;
device->sample_rate_count = dw->sample_rates.length;
device->sample_rates = dw->sample_rates.items;
wave_format->Format.nSamplesPerSec = orig_sample_rate;
return 0;
}
static int refresh_devices(SoundIoPrivate *si) {
2015-08-11 06:18:08 +00:00
SoundIo *soundio = &si->pub;
SoundIoWasapi *siw = &si->backend_data.wasapi;
RefreshDevices rd = {0};
2015-08-11 06:18:08 +00:00
int err;
HRESULT hr;
2015-08-12 18:01:06 +00:00
if (FAILED(hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(siw->device_enumerator, eRender,
eMultimedia, &rd.default_render_device)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if (rd.lpwstr)
CoTaskMemFree(rd.lpwstr);
if (FAILED(hr = IMMDevice_GetId(rd.default_render_device, &rd.lpwstr))) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if ((err = from_lpwstr(rd.lpwstr, &rd.default_render_id, &rd.default_render_id_len))) {
deinit_refresh_devices(&rd);
return err;
}
if (FAILED(hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(siw->device_enumerator, eCapture,
eMultimedia, &rd.default_capture_device)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if (rd.lpwstr)
CoTaskMemFree(rd.lpwstr);
if (FAILED(hr = IMMDevice_GetId(rd.default_capture_device, &rd.lpwstr))) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if ((err = from_lpwstr(rd.lpwstr, &rd.default_capture_id, &rd.default_capture_id_len))) {
deinit_refresh_devices(&rd);
return err;
}
if (FAILED(hr = IMMDeviceEnumerator_EnumAudioEndpoints(siw->device_enumerator,
eAll, DEVICE_STATE_ACTIVE, &rd.collection)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
2015-08-11 06:18:08 +00:00
UINT unsigned_count;
if (FAILED(hr = IMMDeviceCollection_GetCount(rd.collection, &unsigned_count))) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
2015-08-11 06:18:08 +00:00
if (unsigned_count > (UINT)INT_MAX) {
deinit_refresh_devices(&rd);
return SoundIoErrorIncompatibleDevice;
}
int device_count = unsigned_count;
if (!(rd.devices_info = allocate<SoundIoDevicesInfo>(1))) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
}
rd.devices_info->default_input_index = -1;
rd.devices_info->default_output_index = -1;
2015-08-11 06:18:08 +00:00
for (int device_i = 0; device_i < device_count; device_i += 1) {
if (rd.mm_device)
IMMDevice_Release(rd.mm_device);
if (FAILED(hr = IMMDeviceCollection_Item(rd.collection, device_i, &rd.mm_device))) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if (rd.lpwstr)
CoTaskMemFree(rd.lpwstr);
if (FAILED(hr = IMMDevice_GetId(rd.mm_device, &rd.lpwstr))) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
2015-08-13 02:26:07 +00:00
SoundIoDevicePrivate *dev_shared = allocate<SoundIoDevicePrivate>(1);
if (!dev_shared) {
2015-08-11 06:18:08 +00:00
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
}
2015-08-13 02:26:07 +00:00
SoundIoDeviceWasapi *dev_w_shared = &dev_shared->backend_data.wasapi;
dev_shared->destruct = destruct_device;
assert(!rd.device_shared);
rd.device_shared = &dev_shared->pub;
rd.device_shared->ref_count = 1;
rd.device_shared->soundio = soundio;
rd.device_shared->is_raw = false;
SoundIoDevicePrivate *dev_raw = allocate<SoundIoDevicePrivate>(1);
if (!dev_raw) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
}
SoundIoDeviceWasapi *dev_w_raw = &dev_raw->backend_data.wasapi;
dev_raw->destruct = destruct_device;
assert(!rd.device_raw);
rd.device_raw = &dev_raw->pub;
rd.device_raw->ref_count = 1;
rd.device_raw->soundio = soundio;
rd.device_raw->is_raw = true;
2015-08-11 06:18:08 +00:00
int device_id_len;
2015-08-13 02:26:07 +00:00
if ((err = from_lpwstr(rd.lpwstr, &rd.device_shared->id, &device_id_len))) {
2015-08-11 06:18:08 +00:00
deinit_refresh_devices(&rd);
return err;
}
2015-08-13 02:26:07 +00:00
rd.device_raw->id = soundio_str_dupe(rd.device_shared->id, device_id_len);
if (!rd.device_raw->id) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
}
if (FAILED(hr = IMMDevice_Activate(rd.mm_device, IID_IAudioClient,
2015-08-13 02:26:07 +00:00
CLSCTX_ALL, nullptr, (void**)&dev_w_shared->audio_client)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
2015-08-13 02:26:07 +00:00
if (FAILED(hr = IAudioClient_AddRef(dev_w_shared->audio_client))) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
dev_w_raw->audio_client = dev_w_shared->audio_client;
REFERENCE_TIME default_device_period;
REFERENCE_TIME min_device_period;
2015-08-13 02:26:07 +00:00
if (FAILED(hr = IAudioClient_GetDevicePeriod(dev_w_shared->audio_client,
&default_device_period, &min_device_period)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
2015-08-13 02:26:07 +00:00
rd.device_shared->period_duration_current = from_reference_time(default_device_period);
rd.device_shared->period_duration_min = rd.device_shared->period_duration_current;
rd.device_shared->period_duration_max = rd.device_shared->period_duration_current;
rd.device_raw->period_duration_min = from_reference_time(min_device_period);
rd.device_raw->period_duration_max = 4.0;
2015-08-11 06:18:08 +00:00
if (rd.endpoint)
IMMEndpoint_Release(rd.endpoint);
if (FAILED(hr = IMMDevice_QueryInterface(rd.mm_device, IID_IMMEndpoint, (void**)&rd.endpoint))) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
EDataFlow data_flow;
if (FAILED(hr = IMMEndpoint_GetDataFlow(rd.endpoint, &data_flow))) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
2015-08-13 02:26:07 +00:00
rd.device_shared->aim = data_flow_to_aim(data_flow);
rd.device_raw->aim = rd.device_shared->aim;
2015-08-11 06:18:08 +00:00
if (rd.prop_store)
IPropertyStore_Release(rd.prop_store);
if (FAILED(hr = IMMDevice_OpenPropertyStore(rd.mm_device, STGM_READ, &rd.prop_store))) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if (rd.prop_variant_value_inited)
PropVariantClear(&rd.prop_variant_value);
PropVariantInit(&rd.prop_variant_value);
rd.prop_variant_value_inited = true;
if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store,
PKEY_Device_FriendlyName, &rd.prop_variant_value)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if (!rd.prop_variant_value.pwszVal) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
int device_name_len;
2015-08-13 02:26:07 +00:00
if ((err = from_lpwstr(rd.prop_variant_value.pwszVal, &rd.device_shared->name, &device_name_len))) {
2015-08-11 06:18:08 +00:00
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
2015-08-13 02:26:07 +00:00
rd.device_raw->name = soundio_str_dupe(rd.device_shared->name, device_name_len);
if (!rd.device_raw->name) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
}
// Get the format that WASAPI opens the device with for shared streams.
// This is guaranteed to work, so we use this to modulate the sample
// rate while holding the format constant and vice versa.
if (rd.prop_variant_value_inited)
PropVariantClear(&rd.prop_variant_value);
PropVariantInit(&rd.prop_variant_value);
rd.prop_variant_value_inited = true;
if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store, PKEY_AudioEngine_DeviceFormat,
&rd.prop_variant_value)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
WAVEFORMATEXTENSIBLE *valid_wave_format = (WAVEFORMATEXTENSIBLE *)rd.prop_variant_value.blob.pBlobData;
if (valid_wave_format->Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if ((err = detect_valid_sample_rates(&rd, valid_wave_format, dev_raw,
AUDCLNT_SHAREMODE_EXCLUSIVE)))
{
deinit_refresh_devices(&rd);
return err;
}
if ((err = detect_valid_formats(&rd, valid_wave_format, dev_raw,
AUDCLNT_SHAREMODE_EXCLUSIVE)))
{
deinit_refresh_devices(&rd);
return err;
}
if (rd.wave_format)
CoTaskMemFree(rd.wave_format);
2015-08-13 02:26:07 +00:00
if (FAILED(hr = IAudioClient_GetMixFormat(dev_w_shared->audio_client, (WAVEFORMATEX**)&rd.wave_format))) {
2015-08-12 18:01:06 +00:00
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if (rd.wave_format->Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
2015-08-12 18:01:06 +00:00
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
2015-08-13 02:26:07 +00:00
rd.device_shared->sample_rate_current = rd.wave_format->Format.nSamplesPerSec;
rd.device_shared->current_format = from_wave_format_format(rd.wave_format);
// WASAPI performs resampling in shared mode, so any value is valid.
// Let's pick some reasonable min and max values.
2015-08-13 02:26:07 +00:00
rd.device_shared->sample_rate_count = 1;
rd.device_shared->sample_rates = &dev_shared->prealloc_sample_rate_range;
rd.device_shared->sample_rates[0].min = min(SOUNDIO_MIN_SAMPLE_RATE,
rd.device_shared->sample_rate_current);
rd.device_shared->sample_rates[0].max = max(SOUNDIO_MAX_SAMPLE_RATE,
rd.device_shared->sample_rate_current);
2015-08-12 18:01:06 +00:00
if ((err = detect_valid_formats(&rd, rd.wave_format, dev_shared,
AUDCLNT_SHAREMODE_SHARED)))
{
deinit_refresh_devices(&rd);
return err;
}
2015-08-13 02:26:07 +00:00
from_wave_format_layout(rd.wave_format, &rd.device_shared->current_layout);
rd.device_shared->layout_count = 1;
rd.device_shared->layouts = &rd.device_shared->current_layout;
if ((err = detect_valid_layouts(&rd, valid_wave_format, dev_raw,
AUDCLNT_SHAREMODE_EXCLUSIVE)))
{
deinit_refresh_devices(&rd);
return err;
}
2015-08-12 18:01:06 +00:00
2015-08-11 06:18:08 +00:00
SoundIoList<SoundIoDevice *> *device_list;
2015-08-13 02:26:07 +00:00
if (rd.device_shared->aim == SoundIoDeviceAimOutput) {
2015-08-11 06:18:08 +00:00
device_list = &rd.devices_info->output_devices;
2015-08-13 02:26:07 +00:00
if (soundio_streql(rd.device_shared->id, device_id_len,
rd.default_render_id, rd.default_render_id_len))
{
2015-08-12 18:01:06 +00:00
rd.devices_info->default_output_index = device_list->length;
2015-08-13 02:26:07 +00:00
}
2015-08-11 06:18:08 +00:00
} else {
2015-08-13 02:26:07 +00:00
assert(rd.device_shared->aim == SoundIoDeviceAimInput);
2015-08-11 06:18:08 +00:00
device_list = &rd.devices_info->input_devices;
2015-08-13 02:26:07 +00:00
if (soundio_streql(rd.device_shared->id, device_id_len,
rd.default_capture_id, rd.default_capture_id_len))
{
2015-08-12 18:01:06 +00:00
rd.devices_info->default_input_index = device_list->length;
2015-08-13 02:26:07 +00:00
}
}
if ((err = device_list->append(rd.device_shared))) {
deinit_refresh_devices(&rd);
return err;
2015-08-11 06:18:08 +00:00
}
2015-08-13 02:26:07 +00:00
rd.device_shared = nullptr;
2015-08-11 06:18:08 +00:00
2015-08-13 02:26:07 +00:00
if ((err = device_list->append(rd.device_raw))) {
2015-08-11 06:18:08 +00:00
deinit_refresh_devices(&rd);
return err;
}
2015-08-13 02:26:07 +00:00
rd.device_raw = nullptr;
2015-08-11 06:18:08 +00:00
}
soundio_os_mutex_lock(siw->mutex);
soundio_destroy_devices_info(siw->ready_devices_info);
siw->ready_devices_info = rd.devices_info;
soundio->on_events_signal(soundio);
soundio_os_mutex_unlock(siw->mutex);
rd.devices_info = nullptr;
deinit_refresh_devices(&rd);
return 0;
}
static void shutdown_backend(SoundIoPrivate *si, int err) {
SoundIo *soundio = &si->pub;
SoundIoWasapi *siw = &si->backend_data.wasapi;
soundio_os_mutex_lock(siw->mutex);
siw->shutdown_err = err;
soundio->on_events_signal(soundio);
soundio_os_mutex_unlock(siw->mutex);
}
static void device_thread_run(void *arg) {
SoundIoPrivate *si = (SoundIoPrivate *)arg;
SoundIo *soundio = &si->pub;
SoundIoWasapi *siw = &si->backend_data.wasapi;
int err;
HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr,
CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&siw->device_enumerator);
if (FAILED(hr)) {
shutdown_backend(si, SoundIoErrorSystemResources);
if (!siw->have_devices_flag.exchange(true)) {
soundio_os_cond_signal(siw->cond, nullptr);
soundio->on_events_signal(soundio);
}
return;
}
2015-08-13 19:23:22 +00:00
if (FAILED(hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(
siw->device_enumerator, &siw->device_events)))
{
shutdown_backend(si, SoundIoErrorSystemResources);
if (!siw->have_devices_flag.exchange(true)) {
soundio_os_cond_signal(siw->cond, nullptr);
soundio->on_events_signal(soundio);
}
return;
}
for (;;) {
if (!siw->abort_flag.test_and_set())
break;
if (siw->device_scan_queued.exchange(false)) {
err = refresh_devices(si);
if (err)
shutdown_backend(si, err);
if (!siw->have_devices_flag.exchange(true)) {
soundio_os_cond_signal(siw->cond, nullptr);
soundio->on_events_signal(soundio);
}
if (err)
break;
soundio_os_cond_signal(siw->cond, nullptr);
}
2015-08-13 19:23:22 +00:00
soundio_os_cond_wait(siw->scan_devices_cond, nullptr);
}
2015-08-13 19:23:22 +00:00
IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(siw->device_enumerator, &siw->device_events);
IMMDeviceEnumerator_Release(siw->device_enumerator);
siw->device_enumerator = nullptr;
}
static void block_until_have_devices(SoundIoWasapi *siw) {
if (siw->have_devices_flag.load())
return;
while (!siw->have_devices_flag.load())
soundio_os_cond_wait(siw->cond, nullptr);
}
2015-08-08 22:22:50 +00:00
static void flush_events_wasapi(struct SoundIoPrivate *si) {
SoundIo *soundio = &si->pub;
SoundIoWasapi *siw = &si->backend_data.wasapi;
block_until_have_devices(siw);
bool change = false;
bool cb_shutdown = false;
SoundIoDevicesInfo *old_devices_info = nullptr;
soundio_os_mutex_lock(siw->mutex);
if (siw->shutdown_err && !siw->emitted_shutdown_cb) {
siw->emitted_shutdown_cb = true;
cb_shutdown = true;
} else if (siw->ready_devices_info) {
old_devices_info = si->safe_devices_info;
si->safe_devices_info = siw->ready_devices_info;
siw->ready_devices_info = nullptr;
change = true;
}
soundio_os_mutex_unlock(siw->mutex);
if (cb_shutdown)
soundio->on_backend_disconnect(soundio, siw->shutdown_err);
else if (change)
soundio->on_devices_change(soundio);
soundio_destroy_devices_info(old_devices_info);
2015-08-08 22:22:50 +00:00
}
static void wait_events_wasapi(struct SoundIoPrivate *si) {
SoundIoWasapi *siw = &si->backend_data.wasapi;
flush_events_wasapi(si);
soundio_os_cond_wait(siw->cond, nullptr);
2015-08-08 22:22:50 +00:00
}
static void wakeup_wasapi(struct SoundIoPrivate *si) {
SoundIoWasapi *siw = &si->backend_data.wasapi;
soundio_os_cond_signal(siw->cond, nullptr);
2015-08-08 22:22:50 +00:00
}
static void outstream_destroy_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
soundio_panic("TODO");
}
static int outstream_open_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
soundio_panic("TODO");
}
static int outstream_pause_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, bool pause) {
soundio_panic("TODO");
}
static int outstream_start_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
soundio_panic("TODO");
}
static int outstream_begin_write_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os,
SoundIoChannelArea **out_areas, int *frame_count)
{
soundio_panic("TODO");
}
static int outstream_end_write_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
soundio_panic("TODO");
}
static int outstream_clear_buffer_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
soundio_panic("TODO");
}
static void instream_destroy_wasapi(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) {
soundio_panic("TODO");
}
static int instream_open_wasapi(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) {
soundio_panic("TODO");
}
static int instream_pause_wasapi(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is, bool pause) {
soundio_panic("TODO");
}
static int instream_start_wasapi(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) {
soundio_panic("TODO");
}
static int instream_begin_read_wasapi(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is,
SoundIoChannelArea **out_areas, int *frame_count)
{
soundio_panic("TODO");
}
static int instream_end_read_wasapi(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) {
soundio_panic("TODO");
}
static void destroy_wasapi(struct SoundIoPrivate *si) {
SoundIoWasapi *siw = &si->backend_data.wasapi;
if (siw->thread) {
siw->abort_flag.clear();
2015-08-13 19:23:22 +00:00
soundio_os_cond_signal(siw->scan_devices_cond, nullptr);
soundio_os_thread_destroy(siw->thread);
}
if (siw->cond)
soundio_os_cond_destroy(siw->cond);
2015-08-13 19:23:22 +00:00
if (siw->scan_devices_cond)
soundio_os_cond_destroy(siw->scan_devices_cond);
if (siw->mutex)
soundio_os_mutex_destroy(siw->mutex);
soundio_destroy_devices_info(siw->ready_devices_info);
2015-08-08 22:22:50 +00:00
}
2015-08-13 19:23:22 +00:00
static inline SoundIoPrivate *soundio_MMNotificationClient_si(IMMNotificationClient *client) {
SoundIoWasapi *siw = (SoundIoWasapi *)(((char *)client) - offsetof(SoundIoWasapi, device_events));
SoundIoPrivate *si = (SoundIoPrivate *)(((char *)siw) - offsetof(SoundIoPrivate, backend_data));
return si;
}
static STDMETHODIMP soundio_MMNotificationClient_QueryInterface(IMMNotificationClient *client,
REFIID riid, void **ppv)
{
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IMMNotificationClient)) {
*ppv = client;
IUnknown_AddRef(client);
return S_OK;
} else {
*ppv = nullptr;
return E_NOINTERFACE;
}
}
static STDMETHODIMP_(ULONG) soundio_MMNotificationClient_AddRef(IMMNotificationClient *client) {
SoundIoPrivate *si = soundio_MMNotificationClient_si(client);
SoundIoWasapi *siw = &si->backend_data.wasapi;
return InterlockedIncrement(&siw->device_events_refs);
}
static STDMETHODIMP_(ULONG) soundio_MMNotificationClient_Release(IMMNotificationClient *client) {
SoundIoPrivate *si = soundio_MMNotificationClient_si(client);
SoundIoWasapi *siw = &si->backend_data.wasapi;
return InterlockedDecrement(&siw->device_events_refs);
}
static HRESULT queue_device_scan(IMMNotificationClient *client) {
SoundIoPrivate *si = soundio_MMNotificationClient_si(client);
SoundIoWasapi *siw = &si->backend_data.wasapi;
siw->device_scan_queued.store(true);
soundio_os_cond_signal(siw->scan_devices_cond, nullptr);
return S_OK;
}
static STDMETHODIMP soundio_MMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *client,
LPCWSTR wid, DWORD state)
{
return queue_device_scan(client);
}
static STDMETHODIMP soundio_MMNotificationClient_OnDeviceAdded(IMMNotificationClient *client, LPCWSTR wid) {
return queue_device_scan(client);
}
static STDMETHODIMP soundio_MMNotificationClient_OnDeviceRemoved(IMMNotificationClient *client, LPCWSTR wid) {
return queue_device_scan(client);
}
static STDMETHODIMP soundio_MMNotificationClient_OnDefaultDeviceChange(IMMNotificationClient *client,
EDataFlow flow, ERole role, LPCWSTR wid)
{
return queue_device_scan(client);
}
static STDMETHODIMP soundio_MMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *client,
LPCWSTR wid, const PROPERTYKEY key)
{
return queue_device_scan(client);
}
static struct IMMNotificationClientVtbl soundio_MMNotificationClient = {
soundio_MMNotificationClient_QueryInterface,
soundio_MMNotificationClient_AddRef,
soundio_MMNotificationClient_Release,
soundio_MMNotificationClient_OnDeviceStateChanged,
soundio_MMNotificationClient_OnDeviceAdded,
soundio_MMNotificationClient_OnDeviceRemoved,
soundio_MMNotificationClient_OnDefaultDeviceChange,
soundio_MMNotificationClient_OnPropertyValueChanged,
};
2015-08-08 22:22:50 +00:00
int soundio_wasapi_init(SoundIoPrivate *si) {
SoundIoWasapi *siw = &si->backend_data.wasapi;
int err;
siw->have_devices_flag.store(false);
siw->device_scan_queued.store(true);
siw->abort_flag.test_and_set();
siw->mutex = soundio_os_mutex_create();
if (!siw->mutex) {
destroy_wasapi(si);
return SoundIoErrorNoMem;
}
siw->cond = soundio_os_cond_create();
if (!siw->cond) {
destroy_wasapi(si);
return SoundIoErrorNoMem;
}
2015-08-13 19:23:22 +00:00
siw->scan_devices_cond = soundio_os_cond_create();
if (!siw->scan_devices_cond) {
destroy_wasapi(si);
return SoundIoErrorNoMem;
}
siw->device_events.lpVtbl = &soundio_MMNotificationClient;
siw->device_events_refs = 1;
if ((err = soundio_os_thread_create(device_thread_run, si, false, &siw->thread))) {
destroy_wasapi(si);
return err;
}
2015-08-08 22:22:50 +00:00
si->destroy = destroy_wasapi;
si->flush_events = flush_events_wasapi;
si->wait_events = wait_events_wasapi;
si->wakeup = wakeup_wasapi;
si->outstream_open = outstream_open_wasapi;
si->outstream_destroy = outstream_destroy_wasapi;
si->outstream_start = outstream_start_wasapi;
si->outstream_begin_write = outstream_begin_write_wasapi;
si->outstream_end_write = outstream_end_write_wasapi;
si->outstream_clear_buffer = outstream_clear_buffer_wasapi;
si->outstream_pause = outstream_pause_wasapi;
si->instream_open = instream_open_wasapi;
si->instream_destroy = instream_destroy_wasapi;
si->instream_start = instream_start_wasapi;
si->instream_begin_read = instream_begin_read_wasapi;
si->instream_end_read = instream_end_read_wasapi;
si->instream_pause = instream_pause_wasapi;
return 0;
}