diff options
Diffstat (limited to 'audio/out/ao_wasapi_utils.c')
-rw-r--r--[-rwxr-xr-x] | audio/out/ao_wasapi_utils.c | 807 |
1 files changed, 358 insertions, 449 deletions
diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index be81f0e730..d29c6bed7d 100755..100644 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -18,21 +18,18 @@ */ #include <math.h> -#include <libavutil/common.h> +#include <wchar.h> #include <windows.h> +#include <initguid.h> #include <errors.h> #include <ksguid.h> #include <ksmedia.h> -#include <audioclient.h> -#include <endpointvolume.h> -#include <mmdeviceapi.h> #include <avrt.h> -#include "audio/out/ao_wasapi_utils.h" - #include "audio/format.h" -#include "osdep/io.h" #include "osdep/timer.h" +#include "osdep/io.h" +#include "ao_wasapi.h" #define MIXER_DEFAULT_LABEL L"mpv - video player" @@ -97,8 +94,8 @@ static const GUID *format_to_subtype(int format) return &KSDATAFORMAT_SUBTYPE_PCM; } -// "solve" the under-determined inverse of format_to_subtype by -// assuming the input subtype is "special" (i.e. IEC61937) +// "solve" the under-determined inverse of format_to_subtype by assuming the +// input subtype is "special" (i.e. IEC61937) static int special_subtype_to_format(const GUID *subtype) { for (int i = 0; wasapi_fmt_table[i].format; i++) { if (IsEqualGUID(subtype, wasapi_fmt_table[i].subtype)) @@ -107,84 +104,15 @@ static int special_subtype_to_format(const GUID *subtype) { return 0; } -char *mp_GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid) -{ - snprintf(buf, buf_size, - "{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}", - (unsigned) guid->Data1, guid->Data2, guid->Data3, - guid->Data4[0], guid->Data4[1], - guid->Data4[2], guid->Data4[3], - guid->Data4[4], guid->Data4[5], - guid->Data4[6], guid->Data4[7]); - return buf; -} - char *mp_PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey) { buf = mp_GUID_to_str_buf(buf, buf_size, &pkey->fmtid); size_t guid_len = strnlen(buf, buf_size); - snprintf(buf + guid_len, buf_size - guid_len, ",%"PRIu32, (uint32_t) pkey->pid); + snprintf(buf + guid_len, buf_size - guid_len, ",%"PRIu32, + (uint32_t) pkey->pid); return buf; } -static char *wasapi_explain_err(const HRESULT hr) -{ -#define E(x) case x : return # x ; - switch (hr) { - E(S_OK) - E(S_FALSE) - E(E_FAIL) - E(E_OUTOFMEMORY) - E(E_POINTER) - E(E_HANDLE) - E(E_NOTIMPL) - E(E_INVALIDARG) - E(E_PROP_ID_UNSUPPORTED) - E(REGDB_E_IIDNOTREG) - E(CO_E_NOTINITIALIZED) - E(AUDCLNT_E_NOT_INITIALIZED) - E(AUDCLNT_E_ALREADY_INITIALIZED) - E(AUDCLNT_E_WRONG_ENDPOINT_TYPE) - E(AUDCLNT_E_DEVICE_INVALIDATED) - E(AUDCLNT_E_NOT_STOPPED) - E(AUDCLNT_E_BUFFER_TOO_LARGE) - E(AUDCLNT_E_OUT_OF_ORDER) - E(AUDCLNT_E_UNSUPPORTED_FORMAT) - E(AUDCLNT_E_INVALID_SIZE) - E(AUDCLNT_E_DEVICE_IN_USE) - E(AUDCLNT_E_BUFFER_OPERATION_PENDING) - E(AUDCLNT_E_THREAD_NOT_REGISTERED) - E(AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED) - E(AUDCLNT_E_ENDPOINT_CREATE_FAILED) - E(AUDCLNT_E_SERVICE_NOT_RUNNING) - E(AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED) - E(AUDCLNT_E_EXCLUSIVE_MODE_ONLY) - E(AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL) - E(AUDCLNT_E_EVENTHANDLE_NOT_SET) - E(AUDCLNT_E_INCORRECT_BUFFER_SIZE) - E(AUDCLNT_E_BUFFER_SIZE_ERROR) - E(AUDCLNT_E_CPUUSAGE_EXCEEDED) - E(AUDCLNT_E_BUFFER_ERROR) - E(AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) - E(AUDCLNT_E_INVALID_DEVICE_PERIOD) - E(AUDCLNT_E_INVALID_STREAM_FLAG) - E(AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE) - E(AUDCLNT_E_RESOURCES_INVALIDATED) - E(AUDCLNT_S_BUFFER_EMPTY) - E(AUDCLNT_S_THREAD_ALREADY_REGISTERED) - E(AUDCLNT_S_POSITION_STALLED) - default: - return "<Unknown>"; - } -#undef E -} - -char *mp_HRESULT_to_str_buf(char *buf, size_t buf_size, HRESULT hr) -{ - snprintf(buf, buf_size, "%s (0x%"PRIx32")", - wasapi_explain_err(hr), (uint32_t) hr); - return buf; -} static void update_waveformat_datarate(WAVEFORMATEXTENSIBLE *wformat) { WAVEFORMATEX *wf = &wformat->Format; @@ -203,15 +131,15 @@ static void set_waveformat(WAVEFORMATEXTENSIBLE *wformat, wformat->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); wformat->SubFormat = *format_to_subtype(format); - wformat->Samples.wValidBitsPerSample = valid_bits ? valid_bits : wformat->Format.wBitsPerSample; + wformat->Samples.wValidBitsPerSample = + valid_bits ? valid_bits : wformat->Format.wBitsPerSample; wformat->dwChannelMask = mp_chmap_to_waveext(channels); update_waveformat_datarate(wformat); } -// This implicitly transforms all pcm formats to: -// interleaved / signed (except 8-bit is unsigned) / waveext channel order. -// "Special" formats should be exempt as they should already -// satisfy these properties. +// This implicitly transforms all pcm formats to: interleaved / signed (except +// 8-bit is unsigned) / waveext channel order. "Special" formats should be +// exempt as they should already satisfy these properties. static void set_waveformat_with_ao(WAVEFORMATEXTENSIBLE *wformat, struct ao *ao) { struct mp_chmap channels = ao->channels; @@ -256,7 +184,8 @@ static int format_from_waveformat(WAVEFORMATEX *wf) WAVEFORMATEXTENSIBLE *wformat = (WAVEFORMATEXTENSIBLE *)wf; if (IsEqualGUID(&wformat->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) { format = wf->wBitsPerSample == 8 ? AF_FORMAT_U8 : AF_FORMAT_S32; - } else if (IsEqualGUID(&wformat->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { + } else if (IsEqualGUID(&wformat->SubFormat, + &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { format = AF_FORMAT_FLOAT; } else { format = special_subtype_to_format(&wformat->SubFormat); @@ -274,16 +203,17 @@ static int format_from_waveformat(WAVEFORMATEX *wf) } // https://msdn.microsoft.com/en-us/library/windows/hardware/ff538802%28v=vs.85%29.aspx: // Since mpv doesn't have the notion of "valid bits", we just specify a - // format with the container size. The least significant, "invalid" - // bits will be excess precision ignored by wasapi. - // The change_bytes operations should be a no-op for properly - // configured "special" formats, otherwise it will return 0. + // format with the container size. The least significant, "invalid" bits + // will be excess precision ignored by wasapi. The change_bytes operations + // should be a no-op for properly configured "special" formats, otherwise it + // will return 0. if (wf->wBitsPerSample % 8) return 0; return af_fmt_change_bytes(format, wf->wBitsPerSample / 8); } -static bool chmap_from_waveformat(struct mp_chmap *channels, const WAVEFORMATEX *wf) +static bool chmap_from_waveformat(struct mp_chmap *channels, + const WAVEFORMATEX *wf) { if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { WAVEFORMATEXTENSIBLE *wformat = (WAVEFORMATEXTENSIBLE *)wf; @@ -311,7 +241,8 @@ static char *waveformat_to_str_buf(char *buf, size_t buf_size, WAVEFORMATEX *wf) snprintf(validstr, sizeof(validstr), " (%u valid)", valid_bits); snprintf(buf, buf_size, "%s %s%s @ %uhz", - mp_chmap_to_str(&channels), af_fmt_to_str(format_from_waveformat(wf)), + mp_chmap_to_str(&channels), + af_fmt_to_str(format_from_waveformat(wf)), validstr, (unsigned) wf->nSamplesPerSec); return buf; } @@ -326,7 +257,8 @@ static void waveformat_copy(WAVEFORMATEXTENSIBLE* dst, WAVEFORMATEX* src) } } -static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf, AUDCLNT_SHAREMODE share_mode) +static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf, + AUDCLNT_SHAREMODE share_mode) { struct wasapi_state *state = ao->priv; int format = format_from_waveformat(wf); @@ -336,7 +268,8 @@ static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf, AUDCLNT_SHAREMODE sha return false; } - // Do not touch the ao for passthrough, just assume that we set WAVEFORMATEX correctly. + // Do not touch the ao for passthrough, just assume that we set WAVEFORMATEX + // correctly. if (af_fmt_is_pcm(format)) { struct mp_chmap channels; if (!chmap_from_waveformat(&channels, wf)) { @@ -356,7 +289,8 @@ static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf, AUDCLNT_SHAREMODE sha static bool try_format_exclusive(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat) { struct wasapi_state *state = ao->priv; - MP_VERBOSE(ao, "Trying %s (exclusive)\n", waveformat_to_str(&wformat->Format)); + MP_VERBOSE(ao, "Trying %s (exclusive)\n", + waveformat_to_str(&wformat->Format)); HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient, AUDCLNT_SHAREMODE_EXCLUSIVE, &wformat->Format, NULL); @@ -469,7 +403,8 @@ static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat) if (!wformat->Format.nSamplesPerSec) { if (search_samplerates(ao, wformat, &entry)) { mp_chmap_sel_add_map(&chmap_sel, &entry); - MP_VERBOSE(ao, "%s is supported\n", waveformat_to_str(&wformat->Format)); + MP_VERBOSE(ao, "%s is supported\n", + waveformat_to_str(&wformat->Format)); } } else { change_waveformat_channels(wformat, &entry); @@ -493,8 +428,8 @@ static bool find_formats_exclusive(struct ao *ao, bool do_search) WAVEFORMATEXTENSIBLE wformat; set_waveformat_with_ao(&wformat, ao); - // Try the requested format as is. If that doesn't work, and the - // do_search argument is set, do the pcm format search. + // Try the requested format as is. If that doesn't work, and the do_search + // argument is set, do the pcm format search. if (!try_format_exclusive_with_spdif_fallback(ao, &wformat) && (!do_search || !search_channels(ao, &wformat))) return false; @@ -549,7 +484,8 @@ static bool find_formats_shared(struct ao *ao) af_fmt_to_str(ao->format), ao->samplerate); return true; exit_label: - MP_ERR(state, "Error finding shared mode format: %s\n", mp_HRESULT_to_str(hr)); + MP_ERR(state, "Error finding shared mode format: %s\n", + mp_HRESULT_to_str(hr)); return false; } @@ -586,7 +522,8 @@ static HRESULT init_clock(struct wasapi_state *state) { atomic_store(&state->sample_count, 0); - MP_VERBOSE(state, "IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n", + MP_VERBOSE(state, + "IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n", (uint64_t) state->clock_frequency); return S_OK; @@ -596,27 +533,66 @@ exit_label: return hr; } -static HRESULT init_session_display(struct wasapi_state *state) { - wchar_t path[MAX_PATH+12] = {0}; - +static void init_session_display(struct wasapi_state *state) { HRESULT hr = IAudioClient_GetService(state->pAudioClient, &IID_IAudioSessionControl, (void **)&state->pSessionControl); EXIT_ON_ERROR(hr); + wchar_t path[MAX_PATH+12] = {0}; GetModuleFileNameW(NULL, path, MAX_PATH); lstrcatW(path, L",-IDI_ICON1"); - - hr = IAudioSessionControl_SetDisplayName(state->pSessionControl, MIXER_DEFAULT_LABEL, NULL); - EXIT_ON_ERROR(hr); hr = IAudioSessionControl_SetIconPath(state->pSessionControl, path, NULL); - EXIT_ON_ERROR(hr); + if (FAILED(hr)) { + // don't goto exit_label here since SetDisplayName might still work + MP_WARN(state, "Error setting audio session icon: %s\n", + mp_HRESULT_to_str(hr)); + } - return S_OK; + hr = IAudioSessionControl_SetDisplayName(state->pSessionControl, + MIXER_DEFAULT_LABEL, NULL); + EXIT_ON_ERROR(hr); + return; exit_label: + // if we got here then the session control is useless - release it + SAFE_RELEASE(state->pSessionControl, + IAudioSessionControl_Release(state->pSessionControl)); MP_WARN(state, "Error setting audio session display name: %s\n", mp_HRESULT_to_str(hr)); - return S_OK; // No reason to abort initialization. + return; +} + +static void init_volume_control(struct wasapi_state *state) +{ + HRESULT hr; + if (state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE) { + MP_DBG(state, "Activating pEndpointVolume interface\n"); + hr = IMMDeviceActivator_Activate(state->pDevice, + &IID_IAudioEndpointVolume, + CLSCTX_ALL, NULL, + (void **)&state->pEndpointVolume); + EXIT_ON_ERROR(hr); + + MP_DBG(state, "IAudioEndpointVolume::QueryHardwareSupport\n"); + hr = IAudioEndpointVolume_QueryHardwareSupport(state->pEndpointVolume, + &state->vol_hw_support); + EXIT_ON_ERROR(hr); + } else { + MP_DBG(state, "IAudioClient::Initialize pAudioVolume\n"); + hr = IAudioClient_GetService(state->pAudioClient, + &IID_ISimpleAudioVolume, + (void **)&state->pAudioVolume); + EXIT_ON_ERROR(hr); + } + return; +exit_label: + state->vol_hw_support = 0; + SAFE_RELEASE(state->pEndpointVolume, + IAudioEndpointVolume_Release(state->pEndpointVolume)); + SAFE_RELEASE(state->pAudioVolume, + ISimpleAudioVolume_Release(state->pAudioVolume)); + MP_WARN(state, "Error setting up volume control: %s\n", + mp_HRESULT_to_str(hr)); } static HRESULT fix_format(struct ao *ao) @@ -625,15 +601,18 @@ static HRESULT fix_format(struct ao *ao) REFERENCE_TIME devicePeriod, bufferDuration, bufferPeriod; MP_DBG(state, "IAudioClient::GetDevicePeriod\n"); - HRESULT hr = IAudioClient_GetDevicePeriod(state->pAudioClient,&devicePeriod, NULL); - MP_VERBOSE(state, "Device period: %.2g ms\n", (double) devicePeriod / 10000.0 ); - - /* integer multiple of device period close to 50ms */ - bufferPeriod = bufferDuration = ceil(50.0 * 10000.0 / devicePeriod) * devicePeriod; - - /* handle unsupported buffer size */ - /* hopefully this shouldn't happen because of the above integer device period */ - /* http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875%28v=vs.85%29.aspx */ + HRESULT hr = IAudioClient_GetDevicePeriod(state->pAudioClient,&devicePeriod, + NULL); + MP_VERBOSE(state, "Device period: %.2g ms\n", + (double) devicePeriod / 10000.0 ); + + // integer multiple of device period close to 50ms + bufferPeriod = bufferDuration = + ceil(50.0 * 10000.0 / devicePeriod) * devicePeriod; + + // handle unsupported buffer size hopefully this shouldn't happen because of + // the above integer device period + // http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875%28v=vs.85%29.aspx int retries=0; reinit: if (state->share_mode == AUDCLNT_SHAREMODE_SHARED) @@ -647,20 +626,22 @@ reinit: bufferPeriod, &(state->format.Format), NULL); - /* something about buffer sizes on Win7 */ + // something about buffer sizes on Win7 if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { if (retries > 0) { EXIT_ON_ERROR(hr); } else { retries ++; } - MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s, used %lld * 100ns\n", + MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s," + "used %lld * 100ns\n", mp_HRESULT_to_str(hr), bufferDuration); - IAudioClient_GetBufferSize(state->pAudioClient, &state->bufferFrameCount); - bufferPeriod = bufferDuration = - (REFERENCE_TIME) ((10000.0 * 1000 / state->format.Format.nSamplesPerSec * - state->bufferFrameCount) + 0.5); + IAudioClient_GetBufferSize(state->pAudioClient, + &state->bufferFrameCount); + bufferPeriod = bufferDuration = (REFERENCE_TIME) (0.5 + + (10000.0 * 1000 / state->format.Format.nSamplesPerSec + * state->bufferFrameCount)); IAudioClient_Release(state->pAudioClient); state->pAudioClient = NULL; @@ -677,12 +658,6 @@ reinit: (void **)&state->pRenderClient); EXIT_ON_ERROR(hr); - MP_DBG(state, "IAudioClient::Initialize pAudioVolume\n"); - hr = IAudioClient_GetService(state->pAudioClient, - &IID_ISimpleAudioVolume, - (void **)&state->pAudioVolume); - EXIT_ON_ERROR(hr); - MP_DBG(state, "IAudioClient::Initialize IAudioClient_SetEventHandle\n"); hr = IAudioClient_SetEventHandle(state->pAudioClient, state->hWake); EXIT_ON_ERROR(hr); @@ -693,20 +668,17 @@ reinit: EXIT_ON_ERROR(hr); ao->device_buffer = state->bufferFrameCount; - state->buffer_block_size = state->format.Format.nChannels * - state->format.Format.wBitsPerSample / 8 * - state->bufferFrameCount; - bufferDuration = - (REFERENCE_TIME) ((10000.0 * 1000 / state->format.Format.nSamplesPerSec * - state->bufferFrameCount) + 0.5); + bufferDuration = (REFERENCE_TIME) (0.5 + + (10000.0 * 1000 / state->format.Format.nSamplesPerSec + * state->bufferFrameCount)); MP_VERBOSE(state, "Buffer frame count: %"PRIu32" (%.2g ms)\n", state->bufferFrameCount, (double) bufferDuration / 10000.0 ); hr = init_clock(state); EXIT_ON_ERROR(hr); - hr = init_session_display(state); - EXIT_ON_ERROR(hr); + init_session_display(state); + init_volume_control(state); state->hTask = AvSetMmThreadCharacteristics(L"Pro Audio", &(DWORD){0}); if (!state->hTask) { @@ -714,338 +686,327 @@ reinit: mp_LastError_to_str()); } - MP_VERBOSE(state, "Format fixed. Using %lld byte buffer block size\n", - (long long) state->buffer_block_size); - return S_OK; exit_label: MP_ERR(state, "Error initializing device: %s\n", mp_HRESULT_to_str(hr)); return hr; } -static char* get_device_id(IMMDevice *pDevice) { - if (!pDevice) - return NULL; - - LPWSTR devid = NULL; - char *idstr = NULL; - - HRESULT hr = IMMDevice_GetId(pDevice, &devid); - EXIT_ON_ERROR(hr); - - idstr = mp_to_utf8(NULL, devid); - - if (strstr(idstr, "{0.0.0.00000000}.")) { - char *stripped = talloc_strdup(NULL, idstr + strlen("{0.0.0.00000000}.")); - talloc_free(idstr); - idstr = stripped; - } - -exit_label: - SAFE_RELEASE(devid, CoTaskMemFree(devid)); - return idstr; -} - -static char* get_device_name(IMMDevice *pDevice) { - if (!pDevice) - return NULL; +struct device_desc { + LPWSTR deviceID; + char *id; + char *name; +}; - IPropertyStore *pProps = NULL; +static char* get_device_name(struct mp_log *l, void *talloc_ctx, IMMDevice *pDevice) +{ char *namestr = NULL; + IPropertyStore *pProps = NULL; + PROPVARIANT devname; + PropVariantInit(&devname); HRESULT hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProps); EXIT_ON_ERROR(hr); - PROPVARIANT devname; - PropVariantInit(&devname); - - hr = IPropertyStore_GetValue(pProps, &mp_PKEY_Device_FriendlyName, &devname); + hr = IPropertyStore_GetValue(pProps, &mp_PKEY_Device_FriendlyName, + &devname); EXIT_ON_ERROR(hr); - namestr = mp_to_utf8(NULL, devname.pwszVal); + namestr = mp_to_utf8(talloc_ctx, devname.pwszVal); exit_label: + if (FAILED(hr)) + mp_warn(l, "Failed getting device name: %s\n", mp_HRESULT_to_str(hr)); PropVariantClear(&devname); SAFE_RELEASE(pProps, IPropertyStore_Release(pProps)); - return namestr; + return namestr ? namestr : talloc_strdup(talloc_ctx, ""); } -static char* get_device_desc(IMMDevice *pDevice) { - if (!pDevice) +static struct device_desc *get_device_desc(struct mp_log *l, IMMDevice *pDevice) +{ + LPWSTR deviceID; + HRESULT hr = IMMDevice_GetId(pDevice, &deviceID); + if (FAILED(hr)) { + mp_err(l, "Failed getting device id: %s\n", mp_HRESULT_to_str(hr)); return NULL; + } + struct device_desc *d = talloc_zero(NULL, struct device_desc); + d->deviceID = talloc_memdup(d, deviceID, + (wcslen(deviceID) + 1) * sizeof(wchar_t)); + SAFE_RELEASE(deviceID, CoTaskMemFree(deviceID)); + + char *full_id = mp_to_utf8(NULL, d->deviceID); + bstr id = bstr0(full_id); + bstr_eatstart0(&id, "{0.0.0.00000000}."); + d->id = bstrdup0(d, id); + talloc_free(full_id); + + d->name = get_device_name(l, d, pDevice); + return d; +} - IPropertyStore *pProps = NULL; - char *desc = NULL; +struct enumerator { + struct mp_log *log; + IMMDeviceEnumerator *pEnumerator; + IMMDeviceCollection *pDevices; + int count; +}; - HRESULT hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProps); - EXIT_ON_ERROR(hr); +static void destroy_enumerator(struct enumerator *e) +{ + if (!e) + return; + SAFE_RELEASE(e->pDevices, IMMDeviceCollection_Release(e->pDevices)); + SAFE_RELEASE(e->pEnumerator, IMMDeviceEnumerator_Release(e->pEnumerator)); + talloc_free(e); +} - PROPVARIANT devdesc; - PropVariantInit(&devdesc); +static struct enumerator *create_enumerator(struct mp_log *log) +{ + struct enumerator *e = talloc_zero(NULL, struct enumerator); + e->log = log; + HRESULT hr = CoCreateInstance( + &CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, + (void **)&e->pEnumerator); + EXIT_ON_ERROR(hr); - hr = IPropertyStore_GetValue(pProps, &mp_PKEY_Device_DeviceDesc, &devdesc); + hr = IMMDeviceEnumerator_EnumAudioEndpoints( + e->pEnumerator, eRender, DEVICE_STATE_ACTIVE, &e->pDevices); EXIT_ON_ERROR(hr); - desc = mp_to_utf8(NULL, devdesc.pwszVal); + hr = IMMDeviceCollection_GetCount(e->pDevices, &e->count); + EXIT_ON_ERROR(hr); + return e; exit_label: - PropVariantClear(&devdesc); - SAFE_RELEASE(pProps, IPropertyStore_Release(pProps)); - return desc; + mp_err(log, "Error getting device enumerator: %s\n", mp_HRESULT_to_str(hr)); + destroy_enumerator(e); + return NULL; } -// frees *idstr -static int device_id_match(char *idstr, char *candidate) { - if (idstr == NULL || candidate == NULL) - return 0; - - int found = 0; -#define FOUND(x) do { found = (x); goto end; } while(0) - if (strcmp(idstr, candidate) == 0) - FOUND(1); - if (strstr(idstr, "{0.0.0.00000000}.")) { - char *start = idstr + strlen("{0.0.0.00000000}."); - if (strcmp(start, candidate) == 0) - FOUND(1); +static struct device_desc *device_desc_for_num(struct enumerator *e, int i) +{ + IMMDevice *pDevice = NULL; + HRESULT hr = IMMDeviceCollection_Item(e->pDevices, i, &pDevice); + if (FAILED(hr)) { + MP_ERR(e, "Failed getting device #%d: %s\n", i, mp_HRESULT_to_str(hr)); + return NULL; } -#undef FOUND -end: - talloc_free(idstr); - return found; + struct device_desc *d = get_device_desc(e->log, pDevice); + SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice)); + return d; } -void wasapi_list_devs(struct ao *ao, struct ao_device_list *list) +static struct device_desc *default_device_desc(struct enumerator *e) { - struct wasapi_state *state = ao->priv; - IMMDeviceCollection *pDevices = NULL; IMMDevice *pDevice = NULL; - char *name = NULL, *id = NULL; - - HRESULT hr = IMMDeviceEnumerator_EnumAudioEndpoints(state->pEnumerator, eRender, - DEVICE_STATE_ACTIVE, &pDevices); - EXIT_ON_ERROR(hr); - - int count; - hr = IMMDeviceCollection_GetCount(pDevices, &count); - EXIT_ON_ERROR(hr); - if (count > 0) - MP_VERBOSE(ao, "Output devices:\n"); - - for (int i = 0; i < count; i++) { - hr = IMMDeviceCollection_Item(pDevices, i, &pDevice); - EXIT_ON_ERROR(hr); - - name = get_device_name(pDevice); - id = get_device_id(pDevice); - if (!id) { - hr = E_FAIL; - EXIT_ON_ERROR(hr); - } - char *safe_name = name ? name : ""; - ao_device_list_add(list, ao, &(struct ao_device_desc){id, safe_name}); - - MP_VERBOSE(ao, "#%d, GUID: \'%s\', name: \'%s\'\n", i, id, safe_name); + HRESULT hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint( + e->pEnumerator, eRender, eMultimedia, &pDevice); + if (FAILED(hr)) { + MP_ERR(e, "Error from GetDefaultAudioEndpoint: %s\n", + mp_HRESULT_to_str(hr)); + return NULL; + } + struct device_desc *d = get_device_desc(e->log, pDevice); + SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice)); + return d; +} - talloc_free(name); - talloc_free(id); - SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice)); +void wasapi_list_devs(struct ao *ao, struct ao_device_list *list) +{ + struct enumerator *enumerator = create_enumerator(ao->log); + if (!enumerator) + return; + + for (int i = 0; i < enumerator->count; i++) { + struct device_desc *d = device_desc_for_num(enumerator, i); + if (!d) + goto exit_label; + ao_device_list_add(list, ao, &(struct ao_device_desc){d->id, d->name}); + talloc_free(d); } - SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices)); - return; exit_label: - MP_ERR(ao, "Error enumerating devices: %s\n", mp_HRESULT_to_str(hr)); - talloc_free(name); - talloc_free(id); - SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice)); - SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices)); + destroy_enumerator(enumerator); } -static HRESULT load_default_device(struct ao *ao, IMMDeviceEnumerator* pEnumerator, - IMMDevice **ppDevice) +static HRESULT load_device(struct mp_log *l, + IMMDevice **ppDevice, LPWSTR deviceID) { - HRESULT hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, - eRender, eMultimedia, - ppDevice); + IMMDeviceEnumerator *pEnumerator = NULL; + HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, + &IID_IMMDeviceEnumerator, + (void **)&pEnumerator); EXIT_ON_ERROR(hr); - char *id = get_device_id(*ppDevice); - MP_VERBOSE(ao, "Default device ID: %s\n", id); - talloc_free(id); + hr = IMMDeviceEnumerator_GetDevice(pEnumerator, deviceID, ppDevice); + EXIT_ON_ERROR(hr); - return S_OK; exit_label: - MP_ERR(ao , "Error loading default device: %s\n", mp_HRESULT_to_str(hr)); + if (FAILED(hr)) + mp_err(l, "Error loading selected device: %s\n", mp_HRESULT_to_str(hr)); + SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator)); return hr; } -static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumerator, - IMMDevice **ppDevice, char *search) +static LPWSTR select_device(struct mp_log *l, struct device_desc *d) { - HRESULT hr; - IMMDeviceCollection *pDevices = NULL; - IMMDevice *pTempDevice = NULL; - LPWSTR deviceID = NULL; - - char *end; - int devno = strtol(search, &end, 10); - - char *devid = NULL; - if (end == search || *end) - devid = search; - - int search_err = 0; - - if (devid == NULL) { - hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eRender, - DEVICE_STATE_ACTIVE, &pDevices); - EXIT_ON_ERROR(hr); - - int count; - IMMDeviceCollection_GetCount(pDevices, &count); + if (!d) + return NULL; + mp_verbose(l, "Selecting device \'%s\' (%s)\n", d->id, d->name); + return talloc_memdup(NULL, d->deviceID, + (wcslen(d->deviceID) + 1) * sizeof(wchar_t)); +} - if (devno >= count) { - MP_ERR(ao, "No device #%d\n", devno); - } else { - MP_VERBOSE(ao, "Finding device #%d\n", devno); - hr = IMMDeviceCollection_Item(pDevices, devno, &pTempDevice); - EXIT_ON_ERROR(hr); +LPWSTR find_deviceID(struct ao *ao) +{ + LPWSTR deviceID = NULL; + struct wasapi_state *state = ao->priv; + bstr device = bstr_strip(bstr0(state->opt_device)); + if (!device.len) + device = bstr_strip(bstr0(ao->device)); + + MP_DBG(ao, "Find device \'%.*s\'\n", BSTR_P(device)); + + struct device_desc *d = NULL; + struct enumerator *enumerator = create_enumerator(ao->log); + if (!enumerator) + goto exit_label; + + if (!device.len) { + MP_VERBOSE(ao, "No device specified. Selecting default.\n"); + d = default_device_desc(enumerator); + deviceID = select_device(ao->log, d); + goto exit_label; + } - hr = IMMDevice_GetId(pTempDevice, &deviceID); - EXIT_ON_ERROR(hr); + // try selecting by number + bstr rest; + long long devno = bstrtoll(device, &rest, 10); + if (!rest.len && 0 <= devno && devno < enumerator->count) { + MP_VERBOSE(ao, "Selecting device by number: #%lld\n", devno); + d = device_desc_for_num(enumerator, devno); + deviceID = select_device(ao->log, d); + goto exit_label; + } - MP_VERBOSE(ao, "Found device #%d\n", devno); + // select by id or name + bstr_eatstart0(&device, "{0.0.0.00000000}."); + for (int i = 0; i < enumerator->count; i++) { + d = device_desc_for_num(enumerator, i); + if (!d) + goto exit_label; + + if (bstrcmp(device, bstr_strip(bstr0(d->id))) == 0) { + MP_VERBOSE(ao, "Selecting device by id: \'%.*s\'\n", BSTR_P(device)); + deviceID = select_device(ao->log, d); + goto exit_label; } - } else { - hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eRender, - DEVICE_STATE_ACTIVE|DEVICE_STATE_UNPLUGGED, - &pDevices); - EXIT_ON_ERROR(hr); - - int count; - IMMDeviceCollection_GetCount(pDevices, &count); - MP_VERBOSE(ao, "Finding device %s\n", devid); - - IMMDevice *prevDevice = NULL; - - for (int i = 0; i < count; i++) { - hr = IMMDeviceCollection_Item(pDevices, i, &pTempDevice); - EXIT_ON_ERROR(hr); - - if (device_id_match(get_device_id(pTempDevice), devid)) { - hr = IMMDevice_GetId(pTempDevice, &deviceID); - EXIT_ON_ERROR(hr); - break; - } - char *desc = get_device_desc(pTempDevice); - if (strstr(desc, devid)) { - if (deviceID) { - char *name; - if (!search_err) { - MP_ERR(ao, "Multiple matching devices found\n"); - name = get_device_name(prevDevice); - MP_ERR(ao, "%s\n", name); - talloc_free(name); - search_err = 1; - } - name = get_device_name(pTempDevice); - MP_ERR(ao, "%s\n", name); - talloc_free(name); - } - hr = IMMDevice_GetId(pTempDevice, &deviceID); - prevDevice = pTempDevice; + if (bstrcmp(device, bstr_strip(bstr0(d->name))) == 0) { + if (!state->deviceID) { + MP_VERBOSE(ao, "Selecting device by name: \'%.*s\'\n", BSTR_P(device)); + deviceID = select_device(ao->log, d); + } else { + MP_WARN(ao, "Multiple devices matched \'%.*s\'." + "Ignoring device \'%s\' (%s).\n", + BSTR_P(device), d->id, d->name); } - talloc_free(desc); - - SAFE_RELEASE(pTempDevice, IMMDevice_Release(pTempDevice)); } - - if (deviceID == NULL) - MP_ERR(ao, "Could not find device %s\n", devid); + SAFE_RELEASE(d, talloc_free(d)); } - SAFE_RELEASE(pTempDevice, IMMDevice_Release(pTempDevice)); - SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices)); - - if (deviceID == NULL || search_err) { - hr = E_NOTFOUND; - } else { - MP_VERBOSE(ao, "Loading device %S\n", deviceID); - - hr = IMMDeviceEnumerator_GetDevice(pEnumerator, deviceID, ppDevice); - - if (FAILED(hr)) - MP_ERR(ao, "Could not load requested device\n"); - } + if (!deviceID) + MP_ERR(ao, "Failed to find device \'%.*s\'\n", BSTR_P(device)); exit_label: - SAFE_RELEASE(pTempDevice, IMMDevice_Release(pTempDevice)); - SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices)); - - CoTaskMemFree(deviceID); - return hr; + talloc_free(d); + destroy_enumerator(enumerator); + return deviceID; } -HRESULT wasapi_setup_proxies(struct wasapi_state *state) { - HRESULT hr; - -#define UNMARSHAL(type, to, from) do { \ - hr = CoGetInterfaceAndReleaseStream((from), &(type), (void **)&(to)); \ - (from) = NULL; \ - EXIT_ON_ERROR(hr); \ -} while (0) - - UNMARSHAL(IID_ISimpleAudioVolume, state->pAudioVolumeProxy, state->sAudioVolume); - UNMARSHAL(IID_IAudioEndpointVolume, state->pEndpointVolumeProxy, state->sEndpointVolume); - UNMARSHAL(IID_IAudioSessionControl, state->pSessionControlProxy, state->sSessionControl); - -#undef UNMARSHAL - - return S_OK; +static void *unmarshal(struct wasapi_state *state, REFIID type, IStream **from) +{ + if (!*from) + return NULL; + void *to_proxy = NULL; + HRESULT hr = CoGetInterfaceAndReleaseStream(*from, type, &to_proxy); + *from = NULL; // the stream is released even on failure + EXIT_ON_ERROR(hr); + return to_proxy; exit_label: - MP_ERR(state, "Error reading COM proxy: %s\n", mp_HRESULT_to_str(hr)); - return hr; + MP_WARN(state, "Error reading COM proxy: %s\n", mp_HRESULT_to_str(hr)); + return to_proxy; } -void wasapi_release_proxies(wasapi_state *state) { - SAFE_RELEASE(state->pAudioVolumeProxy, IUnknown_Release(state->pAudioVolumeProxy)); - SAFE_RELEASE(state->pEndpointVolumeProxy, IUnknown_Release(state->pEndpointVolumeProxy)); - SAFE_RELEASE(state->pSessionControlProxy, IUnknown_Release(state->pSessionControlProxy)); +void wasapi_receive_proxies(struct wasapi_state *state) { + state->pAudioVolumeProxy = unmarshal(state, &IID_ISimpleAudioVolume, + &state->sAudioVolume); + state->pEndpointVolumeProxy = unmarshal(state, &IID_IAudioEndpointVolume, + &state->sEndpointVolume); + state->pSessionControlProxy = unmarshal(state, &IID_IAudioSessionControl, + &state->sSessionControl); } -static HRESULT create_proxies(struct wasapi_state *state) { - HRESULT hr; - -#define MARSHAL(type, to, from) do { \ - hr = CreateStreamOnHGlobal(NULL, TRUE, &(to)); \ - EXIT_ON_ERROR(hr); \ - hr = CoMarshalInterThreadI |