summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Mitchell <kevmitch@gmail.com>2015-12-26 12:57:16 -0800
committerKevin Mitchell <kevmitch@gmail.com>2016-01-04 07:41:21 -0800
commit243a2976a8721283e8c6ff1e6cb77230762fe9ab (patch)
treebd5dc2530c6485d67a66f6292e46d3baf1d8196d
parentc725f39baef33c15f10d3fbc4b68569c7115131a (diff)
downloadmpv-243a2976a8721283e8c6ff1e6cb77230762fe9ab.tar.bz2
mpv-243a2976a8721283e8c6ff1e6cb77230762fe9ab.tar.xz
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.
-rwxr-xr-xaudio/out/ao_wasapi.h2
-rwxr-xr-xaudio/out/ao_wasapi_utils.c410
2 files changed, 187 insertions, 225 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;
-
- 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);
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));
+
+ 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 void select_device(struct wasapi_state *state, struct device_desc *d)
{
- HRESULT hr =
- IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator,
- eRender, eMultimedia,
- ppDevice);
+ 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;
+}
+
+static HRESULT load_device(struct mp_log *l,
+ IMMDevice **ppDevice, LPWSTR deviceID)
+{
+ 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;
-
- 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 (devno >= count) {
- MP_ERR(ao, "No device #%d\n", devno);
+ 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) {
+ 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);
+ goto exit_label;
+ }
- MP_VERBOSE(ao, "Finding device %s\n", devid);
+ // 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;
+ }
- IMMDevice *prevDevice = NULL;
+ // 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;
+ }
- for (int i = 0; i < count; i++) {
- hr = IMMDeviceCollection_Item(pDevices, i, &pTempDevice);
- EXIT_ON_ERROR(hr);
+ 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 (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));
+ select_device(state, 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 (!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");