0
0
mirror of https://github.com/mpv-player/mpv.git synced 2024-09-20 12:02:23 +02:00

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.
This commit is contained in:
Kevin Mitchell 2015-12-26 12:57:16 -08:00
parent c725f39bae
commit 243a2976a8
2 changed files with 194 additions and 232 deletions

View File

@ -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;

View File

@ -766,278 +766,248 @@ 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);
d->name = get_device_name(l, d, pDevice);
return d;
}
struct enumerator {
struct mp_log *log;
IMMDeviceEnumerator *pEnumerator;
IMMDeviceCollection *pDevices;
int count;
};
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);
PROPVARIANT devdesc;
PropVariantInit(&devdesc);
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;
}
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;
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));
}
SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices));
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);
}
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);
EXIT_ON_ERROR(hr);
char *id = get_device_id(*ppDevice);
MP_VERBOSE(ao, "Default device ID: %s\n", id);
talloc_free(id);
return S_OK;
exit_label:
MP_ERR(ao , "Error loading default device: %s\n", mp_HRESULT_to_str(hr));
return hr;
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 find_and_load_device(struct ao *ao,
IMMDeviceEnumerator* pEnumerator,
IMMDevice **ppDevice, char *search)
static HRESULT load_device(struct mp_log *l,
IMMDevice **ppDevice, LPWSTR deviceID)
{
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);
IMMDeviceEnumerator *pEnumerator = NULL;
HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
&IID_IMMDeviceEnumerator,
(void **)&pEnumerator);
EXIT_ON_ERROR(hr);
int count;
IMMDeviceCollection_GetCount(pDevices, &count);
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);
hr = IMMDevice_GetId(pTempDevice, &deviceID);
EXIT_ON_ERROR(hr);
MP_VERBOSE(ao, "Found device #%d\n", devno);
}
} 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);
}
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");
}
EXIT_ON_ERROR(hr);
exit_label:
SAFE_RELEASE(pTempDevice, IMMDevice_Release(pTempDevice));
SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices));
CoTaskMemFree(deviceID);
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_device(struct ao *ao)
{
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_ERR(ao, "Failed to get default device.\n");
}
goto exit_label;
}
// 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:
talloc_free(d);
destroy_enumerator(enumerator);
return state->deviceID ? S_OK : E_FAIL;
}
static void *unmarshal(struct wasapi_state *state, REFIID type, IStream **from)
{
if (!*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");