summaryrefslogtreecommitdiffstats
path: root/audio/out/ao_wasapi_utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'audio/out/ao_wasapi_utils.c')
-rw-r--r--[-rwxr-xr-x]audio/out/ao_wasapi_utils.c807
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