From 243a2976a8721283e8c6ff1e6cb77230762fe9ab Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Sat, 26 Dec 2015 12:57:16 -0800 Subject: [PATCH] ao_wasapi: rewrite device listing and selection Unify and clean up listing and selection. Use common enumerator code for both operations to avoid duplication or inconsistencies. Maintain, but significatnly simplify manual device selection by id, name or number. This actually fixes loading by name which didn't really work before since the "name" displayed by --audio-device=help differed from that used to match the selection, which used the device "description" instead. Save the selected deviceID in the private structure for later loading. This will permit moving the device selection into the main thread in a future commit. --- audio/out/ao_wasapi.h | 2 + audio/out/ao_wasapi_utils.c | 424 ++++++++++++++++-------------------- 2 files changed, 194 insertions(+), 232 deletions(-) diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h index f3ffb4820f..f54e63a783 100755 --- a/audio/out/ao_wasapi.h +++ b/audio/out/ao_wasapi.h @@ -63,6 +63,8 @@ typedef struct wasapi_state { // for setting the audio thread priority HANDLE hTask; + // ID of the device to use + LPWSTR deviceID; // WASAPI object handles owned and used by audio thread IMMDevice *pDevice; IAudioClient *pAudioClient; diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index d465c17eaf..fc2f8bb7aa 100755 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -766,276 +766,246 @@ exit_label: return hr; } -static char* get_device_id(IMMDevice *pDevice) { - if (!pDevice) - return NULL; +struct device_desc { + LPWSTR deviceID; + char *id; + char *name; +}; - 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; - - 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); 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) +{ + struct device_desc *d = talloc_zero(NULL, struct device_desc); + 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)); + talloc_free(d); return NULL; + } + d->deviceID = talloc_memdup(d, deviceID, + (wcslen(deviceID) + 1) * sizeof(wchar_t)); + SAFE_RELEASE(deviceID, CoTaskMemFree(deviceID)); - IPropertyStore *pProps = NULL; - char *desc = NULL; + 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); - HRESULT hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProps); - EXIT_ON_ERROR(hr); - - PROPVARIANT devdesc; - PropVariantInit(&devdesc); - - hr = IPropertyStore_GetValue(pProps, &mp_PKEY_Device_DeviceDesc, &devdesc); - EXIT_ON_ERROR(hr); - - desc = mp_to_utf8(NULL, devdesc.pwszVal); - -exit_label: - PropVariantClear(&devdesc); - SAFE_RELEASE(pProps, IPropertyStore_Release(pProps)); - return desc; + d->name = get_device_name(l, d, pDevice); + return d; } -// frees *idstr -static int device_id_match(char *idstr, char *candidate) { - if (idstr == NULL || candidate == NULL) - return 0; +struct enumerator { + struct mp_log *log; + IMMDeviceEnumerator *pEnumerator; + IMMDeviceCollection *pDevices; + int count; +}; - 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 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); +} + +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 = IMMDeviceEnumerator_EnumAudioEndpoints( + e->pEnumerator, eRender, DEVICE_STATE_ACTIVE, &e->pDevices); + EXIT_ON_ERROR(hr); + + hr = IMMDeviceCollection_GetCount(e->pDevices, &e->count); + EXIT_ON_ERROR(hr); + + return e; +exit_label: + mp_err(log, "Error getting device enumerator: %s\n", mp_HRESULT_to_str(hr)); + destroy_enumerator(e); + return NULL; +} + +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; +} + +static struct device_desc *default_device_desc(struct enumerator *e) +{ + IMMDevice *pDevice = NULL; + 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; } void wasapi_list_devs(struct ao *ao, struct ao_device_list *list) { - struct wasapi_state *state = ao->priv; - IMMDeviceCollection *pDevices = NULL; - IMMDevice *pDevice = NULL; - char *name = NULL, *id = NULL; + struct enumerator *enumerator = create_enumerator(ao->log); + if (!enumerator) + return; - 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); - - talloc_free(name); - talloc_free(id); - SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice)); + 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)); +exit_label: + destroy_enumerator(enumerator); +} + +static void select_device(struct wasapi_state *state, struct device_desc *d) +{ + MP_VERBOSE(state, "Selecting device \'%s\' (%s)\n", d->id, d->name); + state->deviceID = talloc_memdup(NULL, d->deviceID, + (wcslen(d->deviceID) + 1) * sizeof(wchar_t)); 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)); } -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 HRESULT find_device(struct ao *ao) { - HRESULT hr; - IMMDeviceCollection *pDevices = NULL; - IMMDevice *pTempDevice = NULL; - 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)); - char *end; - int devno = strtol(search, &end, 10); + MP_DBG(ao, "Find device \'%.*s\'\n", BSTR_P(device)); - char *devid = NULL; - if (end == search || *end) - devid = search; + struct device_desc *d = NULL; + struct enumerator *enumerator = create_enumerator(ao->log); + if (!enumerator) + goto exit_label; - 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 (devno >= count) { - MP_ERR(ao, "No device #%d\n", devno); + if (!device.len) { + d = default_device_desc(enumerator); + if (d) { + MP_VERBOSE(ao, "No device specified. Selecting default\n"); + select_device(state, d); } else { - MP_VERBOSE(ao, "Finding device #%d\n", devno); - hr = IMMDeviceCollection_Item(pDevices, devno, &pTempDevice); - EXIT_ON_ERROR(hr); - - hr = IMMDevice_GetId(pTempDevice, &deviceID); - EXIT_ON_ERROR(hr); - - MP_VERBOSE(ao, "Found device #%d\n", devno); + MP_ERR(ao, "Failed to get default device.\n"); } - } 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; - } - talloc_free(desc); - - SAFE_RELEASE(pTempDevice, IMMDevice_Release(pTempDevice)); - } - - if (deviceID == NULL) - MP_ERR(ao, "Could not find device %s\n", devid); + goto exit_label; } - 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"); + // try selecting by number + bstr rest; + long long devno = bstrtoll(device, &rest, 10); + if (!rest.len && 0 <= devno && devno < enumerator->count) { + d = device_desc_for_num(enumerator, devno); + if (d) { + MP_VERBOSE(ao, "Selecting device by number: #%lld\n", devno); + select_device(state, d); + } else { + MP_ERR(ao, "Failed to get device #%lld.\n", devno); + } + goto exit_label; } + // 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) { + MP_ERR(ao, "Failed to get device #%d.\n", i); + goto exit_label; + } + + if (bstrcmp(device, bstr_strip(bstr0(d->id))) == 0) { + MP_VERBOSE(ao, "Selecting device by id: \'%.*s\'\n", BSTR_P(device)); + select_device(state, d); + goto exit_label; + } + + if (bstrcmp(device, bstr_strip(bstr0(d->name))) == 0) { + if (!state->deviceID) { + MP_VERBOSE(ao, "Selecting device by name: \'%.*s\'\n", BSTR_P(device)); + select_device(state, d); + } else { + MP_WARN(ao, "Multiple devices matched \'%.*s\'." + "Ignoring device \'%s\' (%s).\n", + BSTR_P(device), d->id, d->name); + } + } + SAFE_RELEASE(d, talloc_free(d)); + } + + if (!state->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 state->deviceID ? S_OK : E_FAIL; } static void *unmarshal(struct wasapi_state *state, REFIID type, IStream **from) @@ -1137,22 +1107,11 @@ retry: ; (void **)&state->pEnumerator); EXIT_ON_ERROR(hr); - char *device = state->opt_device; - if (!device || !device[0]) - device = ao->device; - - - if (!device || !device[0]) { - hr = load_default_device(ao, state->pEnumerator, &state->pDevice); - } else { - hr = find_and_load_device(ao, state->pEnumerator, &state->pDevice, - device); - } + hr = find_device(ao); EXIT_ON_ERROR(hr); - char *name = get_device_name(state->pDevice); - MP_VERBOSE(ao, "Device loaded: %s\n", name); - talloc_free(name); + hr = load_device(ao->log, &state->pDevice, state->deviceID); + EXIT_ON_ERROR(hr); MP_DBG(ao, "Activating pAudioClient interface\n"); hr = IMMDeviceActivator_Activate(state->pDevice, &IID_IAudioClient, @@ -1210,6 +1169,7 @@ void wasapi_thread_uninit(struct ao *ao) SAFE_RELEASE(state->pSessionControl, IAudioSessionControl_Release(state->pSessionControl)); SAFE_RELEASE(state->pAudioClient, IAudioClient_Release(state->pAudioClient)); SAFE_RELEASE(state->pDevice, IMMDevice_Release(state->pDevice)); + SAFE_RELEASE(state->deviceID, talloc_free(state->deviceID)); SAFE_RELEASE(state->pEnumerator, IMMDeviceEnumerator_Release(state->pEnumerator)); SAFE_RELEASE(state->hTask, AvRevertMmThreadCharacteristics(state->hTask)); MP_DBG(ao, "Thread uninit done\n");