From 227f0e3f392d61018886f4af98bcf7f6692af13a Mon Sep 17 00:00:00 2001 From: Jonathan Yong <10walls@gmail.com> Date: Sat, 8 Nov 2014 06:46:58 -0800 Subject: ao/wasapi: fix leaked deviceID --- audio/out/ao_wasapi_utils.c | 1 + 1 file changed, 1 insertion(+) diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 043183d32a..9ffaab43fb 100755 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -826,6 +826,7 @@ exit_label: SAFE_RELEASE(pTempDevice, IMMDevice_Release(pTempDevice)); SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices)); SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator)); + CoTaskMemFree(deviceID); return hr; } -- cgit v1.2.3 From 6eb5c6d186c6cd6bd945e408bfc9292e8d17d1da Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Wed, 12 Nov 2014 00:40:30 -0800 Subject: ao/wasapi: reenable the reset function the race condition that necessitated disabling this was fixed in e4403523131a69a92a8418bb3714090a408680c7 --- audio/out/ao_wasapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index 66de466bbc..c9a079ffc9 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -342,7 +342,7 @@ const struct ao_driver audio_out_wasapi = { .init = init, .uninit = uninit, .control = control, - //.reset = audio_reset, <- doesn't wait for audio callback to return + .reset = audio_reset, .resume = audio_resume, .list_devs = list_devs, .priv_size = sizeof(wasapi_state), -- cgit v1.2.3 From d4393be0f92f04922c5530d16b6db808d0b683ed Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Fri, 14 Nov 2014 08:03:12 -0800 Subject: ao/wasapi: make calling of thread_init consistent with thread_uninit --- audio/out/ao_wasapi.c | 2 +- audio/out/ao_wasapi_utils.c | 4 +++- audio/out/ao_wasapi_utils.h | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index c9a079ffc9..68317703d5 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -126,7 +126,7 @@ static DWORD __stdcall ThreadLoop(void *lpParameter) QS_POSTMESSAGE | QS_SENDMESSAGE); switch (waitstatus) { case WAIT_OBJECT_0: /*shutdown*/ - wasapi_thread_uninit(state); + wasapi_thread_uninit(ao); goto exit_label; case (WAIT_OBJECT_0 + 1): /* feed */ thread_feed(ao); diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 9ffaab43fb..921b0dbc6b 100755 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -976,8 +976,10 @@ exit_label: return -1; } -void wasapi_thread_uninit(wasapi_state *state) +void wasapi_thread_uninit(struct ao *ao) { + struct wasapi_state *state = (struct wasapi_state *)ao->priv; + if (state->pAudioClient) IAudioClient_Stop(state->pAudioClient); diff --git a/audio/out/ao_wasapi_utils.h b/audio/out/ao_wasapi_utils.h index ee171ebb3c..41167b9189 100755 --- a/audio/out/ao_wasapi_utils.h +++ b/audio/out/ao_wasapi_utils.h @@ -38,7 +38,7 @@ int wasapi_validate_device(struct mp_log *log, const m_option_t *opt, struct bstr name, struct bstr param); int wasapi_thread_init(struct ao *ao); -void wasapi_thread_uninit(wasapi_state *state); +void wasapi_thread_uninit(struct ao *ao); HRESULT wasapi_setup_proxies(wasapi_state *state); void wasapi_release_proxies(wasapi_state *state); -- cgit v1.2.3 From e28102f1a8d4c5284fa2d2eb13fe4147eefcd02b Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Mon, 17 Nov 2014 02:48:02 -0800 Subject: ao/wasapi: improve error messages and add more debug statements also enforce more consistency in the exit codes and error handling thanks to Jonathan Yong <10walls@gmail.com> --- audio/out/ao_wasapi.c | 55 ++++++++------ audio/out/ao_wasapi.h | 2 +- audio/out/ao_wasapi_utils.c | 179 +++++++++++++++++++++++++------------------- audio/out/ao_wasapi_utils.h | 2 +- 4 files changed, 140 insertions(+), 98 deletions(-) diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index 68317703d5..c2c28d2e39 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -50,7 +50,8 @@ static double get_device_delay(struct wasapi_state *state) { case S_OK: case S_FALSE: break; default: - MP_ERR(state, "IAudioClock::GetPosition returned %s\n", wasapi_explain_err(hr)); + MP_ERR(state, "IAudioClock::GetPosition returned %s (0x%"PRIx32")\n", + wasapi_explain_err(hr), (uint32_t)hr); } LARGE_INTEGER qpc_count; @@ -65,7 +66,7 @@ static double get_device_delay(struct wasapi_state *state) { double diff = sample_count - position; double delay = diff / state->format.Format.nSamplesPerSec; - MP_TRACE(state, "device delay: %g samples (%g ms)\n", diff, delay * 1000); + MP_TRACE(state, "Device delay: %g samples (%g ms)\n", diff, delay * 1000); return delay; } @@ -103,7 +104,8 @@ static void thread_feed(struct ao *ao) return; exit_label: - MP_ERR(state, "thread_feed fails with %"PRIx32"!\n", (uint32_t)hr); + MP_ERR(state, "Error feeding audio: %s (0x%"PRIx32")\n", + wasapi_explain_err(hr), (uint32_t)hr); return; } @@ -113,20 +115,27 @@ static DWORD __stdcall ThreadLoop(void *lpParameter) if (!ao || !ao->priv) return -1; struct wasapi_state *state = (struct wasapi_state *)ao->priv; - if (wasapi_thread_init(ao)) + int thread_ret; + + state->init_ret = wasapi_thread_init(ao); + SetEvent(state->init_done); + if (state->init_ret != S_OK) { + thread_ret = -1; goto exit_label; + } MSG msg; - DWORD waitstatus = WAIT_FAILED; + DWORD waitstatus; HANDLE playcontrol[] = {state->hUninit, state->hFeed, state->hForceFeed, NULL}; - MP_VERBOSE(ao, "Entering dispatch loop!\n"); + MP_DBG(ao, "Entering dispatch loop\n"); while (1) { /* watch events */ waitstatus = MsgWaitForMultipleObjects(3, playcontrol, FALSE, INFINITE, QS_POSTMESSAGE | QS_SENDMESSAGE); switch (waitstatus) { case WAIT_OBJECT_0: /*shutdown*/ wasapi_thread_uninit(ao); + thread_ret = 0; goto exit_label; case (WAIT_OBJECT_0 + 1): /* feed */ thread_feed(ao); @@ -140,8 +149,10 @@ static DWORD __stdcall ThreadLoop(void *lpParameter) DispatchMessage(&msg); } break; - case WAIT_FAILED: /* ??? */ - return -1; + default: + MP_ERR(ao, "Unhandled case in thread loop"); + thread_ret = -1; + goto exit_label; } } exit_label: @@ -149,7 +160,8 @@ exit_label: while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { DispatchMessage(&msg); } - return state->init_ret; + + return thread_ret; } static void closehandles(struct ao *ao) @@ -165,22 +177,22 @@ static void closehandles(struct ao *ao) static void uninit(struct ao *ao) { - MP_VERBOSE(ao, "uninit!\n"); + MP_DBG(ao, "Uninit wasapi\n"); struct wasapi_state *state = (struct wasapi_state *)ao->priv; wasapi_release_proxies(state); SetEvent(state->hUninit); /* wait up to 10 seconds */ if (WaitForSingleObject(state->threadLoop, 10000) == WAIT_TIMEOUT) - MP_ERR(ao, "audio loop thread refuses to abort!"); + MP_ERR(ao, "Audio loop thread refuses to abort"); if (state->VistaBlob.hAvrt) FreeLibrary(state->VistaBlob.hAvrt); closehandles(ao); - MP_VERBOSE(ao, "uninit END!\n"); + MP_DBG(ao, "Uninit wasapi done\n"); } static int init(struct ao *ao) { - MP_VERBOSE(ao, "init!\n"); + MP_DBG(ao, "Init wasapi\n"); ao->format = af_fmt_from_planar(ao->format); struct mp_chmap_sel sel = {0}; mp_chmap_sel_add_waveext(&sel); @@ -212,23 +224,24 @@ static int init(struct ao *ao) /* failed to init events */ return -1; } - state->init_ret = -1; + state->init_ret = E_FAIL; state->threadLoop = (HANDLE)CreateThread(NULL, 0, &ThreadLoop, ao, 0, NULL); if (!state->threadLoop) { /* failed to init thread */ - MP_ERR(ao, "fail to create thread!\n"); + MP_ERR(ao, "Failed to create thread\n"); return -1; } WaitForSingleObject(state->init_done, INFINITE); /* wait on init complete */ - if (state->init_ret) { + if (state->init_ret != S_OK) { if (!ao->probing) { - MP_ERR(ao, "thread_init failed!\n"); + MP_ERR(ao, "Received failure from audio thread\n"); } - } else - MP_VERBOSE(ao, "Init Done!\n"); + return -1; + } + MP_DBG(ao, "Init wasapi done\n"); wasapi_setup_proxies(state); - return state->init_ret; + return 0; } static int control(struct ao *ao, enum aocontrol cmd, void *arg) @@ -249,7 +262,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) /* check to see if user manually changed volume through mixer; this information is used in exclusive mode for restoring the mixer volume on uninit */ if (state->audio_volume != state->previous_volume) { - MP_VERBOSE(state, "mixer difference: %.2g now, expected %.2g\n", + MP_VERBOSE(state, "Mixer difference: %.2g now, expected %.2g\n", state->audio_volume, state->previous_volume); state->initial_volume = state->audio_volume; } diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h index 36428b4aea..55cf79d8e0 100755 --- a/audio/out/ao_wasapi.h +++ b/audio/out/ao_wasapi.h @@ -35,7 +35,7 @@ typedef struct wasapi_state { HANDLE threadLoop; /* Init phase */ - int init_ret; + HRESULT init_ret; HANDLE init_done; int share_mode; diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 921b0dbc6b..e54cbcd0bc 100755 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -85,6 +85,10 @@ const char *wasapi_explain_err(const HRESULT hr) #define E(x) case x : return # x ; switch (hr) { E(S_OK) + E(E_FAIL) + E(E_OUTOFMEMORY) + E(E_POINTER) + E(E_INVALIDARG) E(AUDCLNT_E_NOT_INITIALIZED) E(AUDCLNT_E_ALREADY_INITIALIZED) E(AUDCLNT_E_WRONG_ENDPOINT_TYPE) @@ -160,7 +164,7 @@ static int set_ao_format(struct wasapi_state *state, WAVEFORMATEXTENSIBLE wformat) { if (wformat.SubFormat.Data1 != 1 && wformat.SubFormat.Data1 != 3) { - MP_ERR(ao, "unknown SubFormat %"PRIu32"\n", + MP_ERR(ao, "Unknown SubFormat %"PRIu32"\n", (uint32_t)wformat.SubFormat.Data1); return 0; } @@ -196,7 +200,7 @@ static int try_format(struct wasapi_state *state, if (!af_format) return 0; - MP_VERBOSE(ao, "trying %dch %s @ %dhz\n", + MP_VERBOSE(ao, "Trying %dch %s @ %dhz\n", channels.num, af_fmt_to_str(af_format), samplerate); union WAVEFMT u; @@ -220,7 +224,7 @@ static int try_format(struct wasapi_state *state, if (hr == S_FALSE) { if (set_ao_format(state, ao, wformat)) { - MP_VERBOSE(ao, "accepted as %dch %s @ %dhz\n", + MP_VERBOSE(ao, "Accepted as %dch %s @ %dhz\n", ao->channels.num, af_fmt_to_str(ao->format), ao->samplerate); return 1; @@ -283,7 +287,7 @@ static int try_passthrough(struct wasapi_state *state, union WAVEFMT u; u.extensible = &wformat; - MP_VERBOSE(ao, "trying passthrough for %s...\n", af_fmt_to_str(ao->format)); + MP_VERBOSE(ao, "Trying passthrough for %s...\n", af_fmt_to_str(ao->format)); HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient, state->share_mode, @@ -304,7 +308,7 @@ static int find_formats(struct ao *const ao) if (try_passthrough(state, ao)) return 0; - MP_ERR(ao, "couldn't use passthrough!"); + MP_ERR(ao, "Couldn't use passthrough"); if (!state->opt_exclusive) MP_ERR(ao, " (try exclusive mode)"); MP_ERR(ao, "\n"); @@ -326,7 +330,7 @@ static int find_formats(struct ao *const ao) return 0; } - MP_WARN(ao, "couldn't use default mix format!\n"); + MP_WARN(ao, "Couldn't use default mix format\n"); } /* Exclusive mode, we have to guess. */ @@ -397,13 +401,13 @@ static int find_formats(struct ao *const ao) bits = start_bits; mp_chmap_from_channels(&ao->channels, 2); } else { - MP_ERR(ao, "couldn't find acceptable audio format!\n"); + MP_ERR(ao, "Couldn't find acceptable audio format\n"); return -1; } } } -static int init_clock(struct wasapi_state *state) { +static HRESULT init_clock(struct wasapi_state *state) { HRESULT hr; hr = IAudioClient_GetService(state->pAudioClient, @@ -417,16 +421,17 @@ static int init_clock(struct wasapi_state *state) { atomic_store(&state->sample_count, 0); - MP_VERBOSE(state, "IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n", (uint64_t) state->clock_frequency); + MP_VERBOSE(state, "IAudioClock::GetFrequency gave a frequency of %"PRIu64".\n", + (uint64_t) state->clock_frequency); - return 0; + return S_OK; exit_label: - MP_ERR(state, "init_clock failed with %s, unable to obtain the audio device's timing!\n", - wasapi_explain_err(hr)); - return 1; + MP_ERR(state, "Error obtaining the audio device's timing: %s (0x%"PRIx32")\n", + wasapi_explain_err(hr), (uint32_t)hr); + return hr; } -static int init_session_display(struct wasapi_state *state) { +static HRESULT init_session_display(struct wasapi_state *state) { HRESULT hr; wchar_t path[MAX_PATH+12] = {0}; @@ -443,15 +448,14 @@ static int init_session_display(struct wasapi_state *state) { hr = IAudioSessionControl_SetIconPath(state->pSessionControl, path, NULL); EXIT_ON_ERROR(hr); - return 0; - + return S_OK; exit_label: - MP_ERR(state, "init_session_display failed with %s.\n", - wasapi_explain_err(hr)); - return 1; + MP_ERR(state, "Error setting audio session display name: %s (0x%"PRIx32")\n", + wasapi_explain_err(hr), (uint32_t)hr); + return hr; } -static int fix_format(struct wasapi_state *state) +static HRESULT fix_format(struct wasapi_state *state) { HRESULT hr; double offset = 0.5; @@ -459,10 +463,13 @@ static int fix_format(struct wasapi_state *state) /* cargo cult code to negotiate buffer block size, affected by hardware/drivers combinations, gradually grow it to 10s, by 0.5s, consider failure if it still doesn't work */ + MP_DBG(state, "IAudioClient::GetDevicePeriod\n"); hr = IAudioClient_GetDevicePeriod(state->pAudioClient, &state->defaultRequestedDuration, &state->minRequestedDuration); + reinit: + MP_DBG(state, "IAudioClient::Initialize\n"); hr = IAudioClient_Initialize(state->pAudioClient, state->share_mode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, @@ -472,10 +479,12 @@ reinit: NULL); /* something about buffer sizes on Win7, fixme might loop forever */ if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { - MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s, used %lld * 100ns\n", - wasapi_explain_err(hr), state->defaultRequestedDuration); - if (offset > 10.0) - goto exit_label; /* is 10 enough to break out of the loop?*/ + MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s (0x%"PRIx32"), used %lld * 100ns\n", + wasapi_explain_err(hr), (uint32_t)hr, state->defaultRequestedDuration); + if (offset > 10.0) { + hr = E_FAIL; + EXIT_ON_ERROR(hr); + } IAudioClient_GetBufferSize(state->pAudioClient, &state->bufferFrameCount); state->defaultRequestedDuration = (REFERENCE_TIME)((10000.0 * 1000 / state->format.Format.nSamplesPerSec * @@ -489,19 +498,22 @@ reinit: goto reinit; } EXIT_ON_ERROR(hr); + + MP_DBG(state, "IAudioClient::Initialize pRenderClient\n"); hr = IAudioClient_GetService(state->pAudioClient, &IID_IAudioRenderClient, (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); - if (!state->hFeed) - goto exit_label; + MP_DBG(state, "IAudioClient::Initialize IAudioClient_SetEventHandle\n"); hr = IAudioClient_SetEventHandle(state->pAudioClient, state->hFeed); EXIT_ON_ERROR(hr); + MP_DBG(state, "IAudioClient::Initialize IAudioClient_GetBufferSize\n"); hr = IAudioClient_GetBufferSize(state->pAudioClient, &state->bufferFrameCount); EXIT_ON_ERROR(hr); @@ -509,20 +521,22 @@ reinit: state->format.Format.wBitsPerSample / 8 * state->bufferFrameCount; - if (init_clock(state)) - return 1; - if (init_session_display(state)) - return 1; + hr = init_clock(state); + EXIT_ON_ERROR(hr); + + hr = init_session_display(state); + EXIT_ON_ERROR(hr); state->hTask = state->VistaBlob.pAvSetMmThreadCharacteristicsW(L"Pro Audio", &state->taskIndex); - MP_VERBOSE(state, "fix_format OK, using %lld byte buffer block size!\n", + MP_VERBOSE(state, "Format fixed. Using %lld byte buffer block size\n", (long long) state->buffer_block_size); - return 0; + + return S_OK; exit_label: - MP_ERR(state, "fix_format fails with %s, failed to determine buffer block size!\n", - wasapi_explain_err(hr)); - return 1; + MP_ERR(state, "Error initializing device: %s (0x%"PRIx32")\n", + wasapi_explain_err(hr), (uint32_t)hr); + return hr; } static char* get_device_id(IMMDevice *pDevice) { @@ -683,8 +697,8 @@ static HRESULT enumerate_with_state(struct mp_log *log, struct ao *ao, talloc_free(defid); SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices)); SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator)); - return hr; + return S_OK; exit_label: talloc_free(defid); SAFE_RELEASE(pDevice, IMMDevice_Release(pDevice)); @@ -708,8 +722,8 @@ int wasapi_enumerate_devices(struct mp_log *log, struct ao *ao, CoUninitialize(); return 0; exit_label: - mp_err(log, "Error enumerating devices: HRESULT %08"PRIx32" \"%s\"\n", - (uint32_t)hr, wasapi_explain_err(hr)); + mp_err(log, "Error enumerating devices: %s (0x%"PRIx32")\n", + wasapi_explain_err(hr), (uint32_t)hr); CoUninitialize(); return 1; } @@ -746,16 +760,16 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice, IMMDeviceCollection_GetCount(pDevices, &count); if (devno >= count) { - MP_ERR(ao, "no device #%d!\n", devno); + MP_ERR(ao, "No device #%d\n", devno); } else { - MP_VERBOSE(ao, "finding device #%d\n", devno); + 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_VERBOSE(ao, "Found device #%d\n", devno); } } else { hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eRender, @@ -766,7 +780,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice, int count; IMMDeviceCollection_GetCount(pDevices, &count); - MP_VERBOSE(ao, "finding device %s\n", devid); + MP_VERBOSE(ao, "Finding device %s\n", devid); IMMDevice *prevDevice = NULL; @@ -784,7 +798,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice, if (deviceID) { char *name; if (!search_err) { - MP_ERR(ao, "multiple matching devices found!\n"); + MP_ERR(ao, "Multiple matching devices found\n"); name = get_device_name(prevDevice); MP_ERR(ao, "%s\n", name); talloc_free(name); @@ -803,7 +817,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice, } if (deviceID == NULL) { - MP_ERR(ao, "could not find device %s!\n", devid); + MP_ERR(ao, "Could not find device %s\n", devid); } } @@ -813,12 +827,12 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice, if (deviceID == NULL || search_err) { hr = E_NOTFOUND; } else { - MP_VERBOSE(ao, "loading device %S\n", deviceID); + 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"); + MP_ERR(ao, "Could not load requested device\n"); } } @@ -838,7 +852,7 @@ int wasapi_validate_device(struct mp_log *log, const m_option_t *opt, return M_OPT_EXIT; } - mp_dbg(log, "validating device=%s\n", param.start); + mp_dbg(log, "Validating device=%s\n", param.start); char *end; int devno = (int) strtol(param.start, &end, 10); @@ -871,8 +885,8 @@ HRESULT wasapi_setup_proxies(struct wasapi_state *state) { exit_label: if (hr != S_OK) { - MP_ERR(state, "error reading COM proxy: %08x %s\n", (unsigned int)hr, - wasapi_explain_err(hr)); + MP_ERR(state, "Error reading COM proxy: %s (0x%"PRIx32")\n", + wasapi_explain_err(hr), (uint32_t)hr); } return hr; } @@ -907,10 +921,12 @@ exit_label: return hr; } -int wasapi_thread_init(struct ao *ao) +HRESULT wasapi_thread_init(struct ao *ao) { struct wasapi_state *state = (struct wasapi_state *)ao->priv; HRESULT hr; + MP_DBG(ao, "Init wasapi thread\n"); + state->initial_volume = -1.0; CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); char *device = state->opt_device; @@ -929,7 +945,7 @@ int wasapi_thread_init(struct ao *ao) SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator)); char *id = get_device_id(state->pDevice); - MP_VERBOSE(ao, "default device ID: %s\n", id); + MP_VERBOSE(ao, "Default device ID: %s\n", id); talloc_free(id); } else { hr = find_and_load_device(ao, &state->pDevice, device); @@ -937,43 +953,57 @@ int wasapi_thread_init(struct ao *ao) EXIT_ON_ERROR(hr); char *name = get_device_name(state->pDevice); - MP_VERBOSE(ao, "device loaded: %s\n", name); + MP_VERBOSE(ao, "Device loaded: %s\n", name); talloc_free(name); + MP_DBG(ao, "Activating pAudioClient interface\n"); hr = IMMDeviceActivator_Activate(state->pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&state->pAudioClient); EXIT_ON_ERROR(hr); + MP_DBG(ao, "Activating pEndpointVolume interface\n"); hr = IMMDeviceActivator_Activate(state->pDevice, &IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void **)&state->pEndpointVolume); EXIT_ON_ERROR(hr); - IAudioEndpointVolume_QueryHardwareSupport(state->pEndpointVolume, - &state->vol_hw_support); - state->init_ret = find_formats(ao); /* Probe support formats */ - if (state->init_ret) - goto exit_label; - if (!fix_format(state)) { /* now that we're sure what format to use */ - EXIT_ON_ERROR(create_proxies(state)); + MP_DBG(ao, "Query hardware volume support\n"); + hr = IAudioEndpointVolume_QueryHardwareSupport(state->pEndpointVolume, + &state->vol_hw_support); + if ( hr != S_OK ) + MP_WARN(ao, "Query hardware volume control: %s (0x%"PRIx32")\n", + wasapi_explain_err(hr), (uint32_t)hr); - if (state->opt_exclusive) - IAudioEndpointVolume_GetMasterVolumeLevelScalar(state->pEndpointVolume, - &state->initial_volume); - else - ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume, - &state->initial_volume); + MP_DBG(ao, "Probing formats\n"); + if (find_formats(ao)){ + hr = E_FAIL; + EXIT_ON_ERROR(hr); + } - state->previous_volume = state->initial_volume; + MP_DBG(ao, "Fixing format\n"); + hr = fix_format(state); + EXIT_ON_ERROR(hr); - MP_VERBOSE(ao, "thread_init OK!\n"); - SetEvent(state->init_done); - return state->init_ret; - } + MP_DBG(ao, "Creating proxies\n"); + hr = create_proxies(state); + EXIT_ON_ERROR(hr); + + MP_DBG(ao, "Read volume levels\n"); + if (state->opt_exclusive) + IAudioEndpointVolume_GetMasterVolumeLevelScalar(state->pEndpointVolume, + &state->initial_volume); + else + ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume, + &state->initial_volume); + + state->previous_volume = state->initial_volume; + + MP_DBG(ao, "Init wasapi thread done\n"); + return S_OK; exit_label: - state->init_ret = -1; - SetEvent(state->init_done); - return -1; + MP_ERR(state, "Error setting up audio thread: %s (0x%"PRIx32")\n", + wasapi_explain_err(hr), (uint32_t)hr); + return hr; } void wasapi_thread_uninit(struct ao *ao) @@ -997,6 +1027,5 @@ void wasapi_thread_uninit(struct ao *ao) if (state->hTask) state->VistaBlob.pAvRevertMmThreadCharacteristics(state->hTask); - CoUninitialize(); - ExitThread(0); + MP_DBG(ao, "Thread uninit done\n"); } diff --git a/audio/out/ao_wasapi_utils.h b/audio/out/ao_wasapi_utils.h index 41167b9189..d0ba4f0222 100755 --- a/audio/out/ao_wasapi_utils.h +++ b/audio/out/ao_wasapi_utils.h @@ -37,7 +37,7 @@ int wasapi_enumerate_devices(struct mp_log *log, struct ao *ao, int wasapi_validate_device(struct mp_log *log, const m_option_t *opt, struct bstr name, struct bstr param); -int wasapi_thread_init(struct ao *ao); +HRESULT wasapi_thread_init(struct ao *ao); void wasapi_thread_uninit(struct ao *ao); HRESULT wasapi_setup_proxies(wasapi_state *state); -- cgit v1.2.3 From 3da6f723c6ceba468412f2282ab4f7958ca6f5d7 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Fri, 14 Nov 2014 22:47:28 -0800 Subject: ao/wasapi: tidy up better on failure Before, failures, particularly in the thread loop init, could lead to a bad state for the duration of mpvs execution. Make sure that everything that was initialized gets properly and safely uninitialized. --- audio/out/ao_wasapi.c | 23 +++++++++++------------ audio/out/ao_wasapi_utils.c | 15 +++++++++++++-- audio/out/ao_wasapi_utils.h | 2 ++ 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index c2c28d2e39..42317f7b0e 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -116,6 +116,7 @@ static DWORD __stdcall ThreadLoop(void *lpParameter) return -1; struct wasapi_state *state = (struct wasapi_state *)ao->priv; int thread_ret; + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); state->init_ret = wasapi_thread_init(ao); SetEvent(state->init_done); @@ -124,7 +125,7 @@ static DWORD __stdcall ThreadLoop(void *lpParameter) goto exit_label; } - MSG msg; + DWORD waitstatus; HANDLE playcontrol[] = {state->hUninit, state->hFeed, state->hForceFeed, NULL}; @@ -134,7 +135,6 @@ static DWORD __stdcall ThreadLoop(void *lpParameter) QS_POSTMESSAGE | QS_SENDMESSAGE); switch (waitstatus) { case WAIT_OBJECT_0: /*shutdown*/ - wasapi_thread_uninit(ao); thread_ret = 0; goto exit_label; case (WAIT_OBJECT_0 + 1): /* feed */ @@ -145,9 +145,7 @@ static DWORD __stdcall ThreadLoop(void *lpParameter) SetEvent(state->hFeedDone); break; case (WAIT_OBJECT_0 + 3): /* messages to dispatch (COM marshalling) */ - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { - DispatchMessage(&msg); - } + wasapi_dispatch(); break; default: MP_ERR(ao, "Unhandled case in thread loop"); @@ -156,11 +154,9 @@ static DWORD __stdcall ThreadLoop(void *lpParameter) } } exit_label: - /* dispatch any possible pending messages */ - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { - DispatchMessage(&msg); - } + wasapi_thread_uninit(ao); + CoUninitialize(); return thread_ret; } @@ -224,6 +220,7 @@ static int init(struct ao *ao) /* failed to init events */ return -1; } + state->init_ret = E_FAIL; state->threadLoop = (HANDLE)CreateThread(NULL, 0, &ThreadLoop, ao, 0, NULL); if (!state->threadLoop) { @@ -231,16 +228,18 @@ static int init(struct ao *ao) MP_ERR(ao, "Failed to create thread\n"); return -1; } + WaitForSingleObject(state->init_done, INFINITE); /* wait on init complete */ if (state->init_ret != S_OK) { - if (!ao->probing) { + if (!ao->probing) MP_ERR(ao, "Received failure from audio thread\n"); - } + if (state->VistaBlob.hAvrt) + FreeLibrary(state->VistaBlob.hAvrt); return -1; } - MP_DBG(ao, "Init wasapi done\n"); wasapi_setup_proxies(state); + MP_DBG(ao, "Init wasapi done\n"); return 0; } diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index e54cbcd0bc..69bbefe5ef 100755 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -921,13 +921,20 @@ exit_label: return hr; } +void wasapi_dispatch(void) +{ + /* dispatch any possible pending messages */ + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + DispatchMessage(&msg); +} + HRESULT wasapi_thread_init(struct ao *ao) { struct wasapi_state *state = (struct wasapi_state *)ao->priv; HRESULT hr; MP_DBG(ao, "Init wasapi thread\n"); state->initial_volume = -1.0; - CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); char *device = state->opt_device; if (!device || !device[0]) @@ -1010,10 +1017,14 @@ void wasapi_thread_uninit(struct ao *ao) { struct wasapi_state *state = (struct wasapi_state *)ao->priv; + wasapi_dispatch(); + if (state->pAudioClient) IAudioClient_Stop(state->pAudioClient); - if (state->opt_exclusive) + if (state->opt_exclusive && + state->pEndpointVolume && + state->initial_volume > 0 ) IAudioEndpointVolume_SetMasterVolumeLevelScalar(state->pEndpointVolume, state->initial_volume, NULL); diff --git a/audio/out/ao_wasapi_utils.h b/audio/out/ao_wasapi_utils.h index d0ba4f0222..24d8daffbc 100755 --- a/audio/out/ao_wasapi_utils.h +++ b/audio/out/ao_wasapi_utils.h @@ -37,6 +37,8 @@ int wasapi_enumerate_devices(struct mp_log *log, struct ao *ao, int wasapi_validate_device(struct mp_log *log, const m_option_t *opt, struct bstr name, struct bstr param); + +void wasapi_dispatch(void); HRESULT wasapi_thread_init(struct ao *ao); void wasapi_thread_uninit(struct ao *ao); -- cgit v1.2.3 From f7c26230eb5ed3e3fc8517f069b1ef863aef8260 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Mon, 17 Nov 2014 03:28:41 -0800 Subject: ao/wasapi: fix possible null dereference of pDevice IMMDeviceEnumerator::GetDefaultAudioEndpoint may set pDevice to null on failure. http://msdn.microsoft.com/en-us/library/windows/desktop/dd371401%28v=vs.85%29.aspx --- audio/out/ao_wasapi_utils.c | 1 + 1 file changed, 1 insertion(+) diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 69bbefe5ef..cf94220c4e 100755 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -950,6 +950,7 @@ HRESULT wasapi_thread_init(struct ao *ao) eRender, eConsole, &state->pDevice); SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator)); + EXIT_ON_ERROR(hr); char *id = get_device_id(state->pDevice); MP_VERBOSE(ao, "Default device ID: %s\n", id); -- cgit v1.2.3 From e8dbdf1eb99a5a1b3c5d3749bd1c59696e6cc620 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Mon, 17 Nov 2014 02:13:09 -0800 Subject: ao/wasapi: put loading of default device in it's own function --- audio/out/ao_wasapi_utils.c | 47 +++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index cf94220c4e..1f329d33b4 100755 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -728,6 +728,33 @@ exit_label: return 1; } +static HRESULT load_default_device(struct ao *ao, IMMDevice **ppDevice) +{ + HRESULT hr; + struct wasapi_state *state = (struct wasapi_state *)ao->priv; + + IMMDeviceEnumerator *pEnumerator; + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, + &IID_IMMDeviceEnumerator, (void**)&pEnumerator); + EXIT_ON_ERROR(hr); + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, + eRender, eConsole, + ppDevice); + SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator)); + 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(state, "Error loading default device: %s (0x%"PRIx32")\n", + wasapi_explain_err(hr), (uint32_t)hr); + return hr; +} + static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice, char *search) { @@ -940,24 +967,10 @@ HRESULT wasapi_thread_init(struct ao *ao) if (!device || !device[0]) device = ao->device; - if (!device || !device[0]) { - IMMDeviceEnumerator *pEnumerator; - hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, - &IID_IMMDeviceEnumerator, (void**)&pEnumerator); - EXIT_ON_ERROR(hr); - - hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, - eRender, eConsole, - &state->pDevice); - SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator)); - EXIT_ON_ERROR(hr); - - char *id = get_device_id(state->pDevice); - MP_VERBOSE(ao, "Default device ID: %s\n", id); - talloc_free(id); - } else { + if (!device || !device[0]) + hr = load_default_device(ao, &state->pDevice); + else hr = find_and_load_device(ao, &state->pDevice, device); - } EXIT_ON_ERROR(hr); char *name = get_device_name(state->pDevice); -- cgit v1.2.3 From 497df443c0a171d7bab193686df93edb189bc54a Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Mon, 17 Nov 2014 04:01:28 -0800 Subject: ao/wasapi: look for "multimedia" default device instead of "console" console is more for system notifications / voice command, mpv is most certainly multimedia http://msdn.microsoft.com/en-us/library/windows/desktop/dd370842%28v=vs.85%29.aspx --- audio/out/ao_wasapi_utils.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 1f329d33b4..c3a8eaa2b7 100755 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -649,7 +649,7 @@ static HRESULT enumerate_with_state(struct mp_log *log, struct ao *ao, EXIT_ON_ERROR(hr); hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, - eRender, eConsole, + eRender, eMultimedia, &pDevice); EXIT_ON_ERROR(hr); @@ -739,7 +739,7 @@ static HRESULT load_default_device(struct ao *ao, IMMDevice **ppDevice) EXIT_ON_ERROR(hr); hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, - eRender, eConsole, + eRender, eMultimedia, ppDevice); SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator)); EXIT_ON_ERROR(hr); -- cgit v1.2.3 From f29f16663ab5687c8cd84234cb5f26aaa66abb97 Mon Sep 17 00:00:00 2001 From: Jonathan Yong <10walls@gmail.com> Date: Mon, 17 Nov 2014 03:37:51 -0800 Subject: ao/wasapi: new wasapi device monitoring interface Implement skeleton IMMNotificationClient to watch for changes in the sound device. This will make recovery possible from changes shared mode sample rate, bit depth, "enhancements"/effects and even graceful device removal. http://msdn.microsoft.com/en-us/library/windows/desktop/dd371417%28v=vs.85%29.aspx Signed-off-by: Kevin Mitchell --- audio/out/ao_wasapi.h | 8 ++ audio/out/ao_wasapi_changenotify.c | 171 +++++++++++++++++++++++++++++++++++++ wscript_build.py | 1 + 3 files changed, 180 insertions(+) create mode 100755 audio/out/ao_wasapi_changenotify.c diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h index 55cf79d8e0..43c81a89d2 100755 --- a/audio/out/ao_wasapi.h +++ b/audio/out/ao_wasapi.h @@ -30,6 +30,14 @@ #include "osdep/atomics.h" +typedef struct change_notify { + IMMNotificationClient client; + LPWSTR monitored; /* Monitored device */ +} change_notify; + +HRESULT wasapi_change_init(struct change_notify *change, IMMDevice *monitor); +void wasapi_change_free(struct change_notify *change); + typedef struct wasapi_state { struct mp_log *log; HANDLE threadLoop; diff --git a/audio/out/ao_wasapi_changenotify.c b/audio/out/ao_wasapi_changenotify.c new file mode 100755 index 0000000000..d5db1a9e89 --- /dev/null +++ b/audio/out/ao_wasapi_changenotify.c @@ -0,0 +1,171 @@ +/* + * This file is part of mpv. + * + * Original author: Jonathan Yong <10walls@gmail.com> + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + */ + +#define COBJMACROS 1 +#define _WIN32_WINNT 0x600 + +#include +#include +#include +#include +#include +#include + +#include "ao_wasapi.h" +#include "ao_wasapi_utils.h" + +static int GUID_compare(const GUID *l, const GUID *r) +{ + unsigned int i; + if (l->Data1 != r->Data1) return 1; + if (l->Data2 != r->Data2) return 1; + if (l->Data3 != r->Data3) return 1; + for (i = 0; i < 8; i++) { + if (l->Data4[i] != r->Data4[i]) return 1; + } + return 0; +} + +static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_QueryInterface( + IMMNotificationClient* This, REFIID riid, void **ppvObject) +{ + /* Compatible with IMMNotificationClient and IUnknown */ + if (!GUID_compare(&IID_IMMNotificationClient, riid) || + !GUID_compare(&IID_IUnknown, riid)) { + *ppvObject = (void *)This; + return S_OK; + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } +} + +/* these are required, but not actually used */ +static ULONG STDMETHODCALLTYPE sIMMNotificationClient_AddRef( + IMMNotificationClient *This) +{ + return 1; +} + +/* MSDN says it should free itself, but we're static */ +static ULONG STDMETHODCALLTYPE sIMMNotificationClient_Release( + IMMNotificationClient *This) +{ + return 1; +} + +static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceStateChanged( + IMMNotificationClient *This, + LPCWSTR pwstrDeviceId, + DWORD dwNewState) +{ + change_notify *change = (change_notify *)This; + + if (!wcscmp(change->monitored, pwstrDeviceId)){ + switch (dwNewState) { + case DEVICE_STATE_DISABLED: + case DEVICE_STATE_NOTPRESENT: + case DEVICE_STATE_UNPLUGGED: + + case DEVICE_STATE_ACTIVE: + default: + return S_OK; + } + } + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceAdded( + IMMNotificationClient *This, + LPCWSTR pwstrDeviceId) +{ + change_notify *change = (change_notify *)This; + + return S_OK; +} + +/* maybe MPV can go over to the prefered device once it is plugged in? */ +static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceRemoved( + IMMNotificationClient *This, + LPCWSTR pwstrDeviceId) +{ + change_notify *change = (change_notify *)This; + + if (!wcscmp(change->monitored, pwstrDeviceId)) { + + } + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDefaultDeviceChanged( + IMMNotificationClient *This, + EDataFlow flow, + ERole role, + LPCWSTR pwstrDeviceId) +{ + change_notify *change = (change_notify *)This; + + if (!wcscmp(change->monitored, pwstrDeviceId)) { + + } + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnPropertyValueChanged( + IMMNotificationClient *This, + LPCWSTR pwstrDeviceId, + const PROPERTYKEY key) +{ + change_notify *change = (change_notify *)This; + + if (!wcscmp(change->monitored, pwstrDeviceId)) { + + } + return S_OK; +} + +static CONST_VTBL IMMNotificationClientVtbl sIMMDeviceEnumeratorVtbl_vtbl = { + .QueryInterface = sIMMNotificationClient_QueryInterface, + .AddRef = sIMMNotificationClient_AddRef, + .Release = sIMMNotificationClient_Release, + .OnDeviceStateChanged = sIMMNotificationClient_OnDeviceStateChanged, + .OnDeviceAdded = sIMMNotificationClient_OnDeviceAdded, + .OnDeviceRemoved = sIMMNotificationClient_OnDeviceRemoved, + .OnDefaultDeviceChanged = sIMMNotificationClient_OnDefaultDeviceChanged, + .OnPropertyValueChanged = sIMMNotificationClient_OnPropertyValueChanged, +}; + +HRESULT wasapi_change_init(struct change_notify *change, IMMDevice *monitor) +{ + HRESULT ret; + + if ( ((ret = IMMDevice_GetId(monitor, &change->monitored)) != S_OK) ) { + wasapi_change_free(change); + return ret; + } + + ret = S_OK; + change->client.lpVtbl = &sIMMDeviceEnumeratorVtbl_vtbl; + return ret; +} + +void wasapi_change_free(struct change_notify *change) +{ + if (change->monitored) CoTaskMemFree(change->monitored); +} diff --git a/wscript_build.py b/wscript_build.py index a2c32494aa..4fefb87004 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -153,6 +153,7 @@ def build(ctx): ( "audio/out/ao_sndio.c", "sndio" ), ( "audio/out/ao_wasapi.c", "wasapi" ), ( "audio/out/ao_wasapi_utils.c", "wasapi" ), + ( "audio/out/ao_wasapi_changenotify.c", "wasapi" ), ( "audio/out/pull.c" ), ( "audio/out/push.c" ), -- cgit v1.2.3 From e647f202ed82b553639a79832bc9909121bde372 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Thu, 13 Nov 2014 01:10:00 -0800 Subject: ao/wasapi: add convenience functions for change notifiy --- audio/out/ao_wasapi_changenotify.c | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/audio/out/ao_wasapi_changenotify.c b/audio/out/ao_wasapi_changenotify.c index d5db1a9e89..7ff6b16d0d 100755 --- a/audio/out/ao_wasapi_changenotify.c +++ b/audio/out/ao_wasapi_changenotify.c @@ -42,6 +42,55 @@ static int GUID_compare(const GUID *l, const GUID *r) return 0; } +static int PKEY_compare(const PROPERTYKEY *l, const PROPERTYKEY *r) +{ + if (GUID_compare(&l->fmtid, &r->fmtid)) return 1; + if (l->pid != r->pid) return 1; + return 0; +} + +static char *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}", + 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; +} + +static char *PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey) +{ + buf = 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 ); + return buf; +} + +#define PKEY_to_str(pkey) PKEY_to_str_buf((char[42]){0}, 42, (pkey)) + +static char* ERole_to_str(ERole role) +{ + switch(role){ + case eConsole: return "console"; + case eMultimedia: return "multimedia"; + case eCommunications: return "communications"; + default: return ""; + } +} + +static char* EDataFlow_to_str(EDataFlow flow) +{ + switch(flow){ + case eRender: return "render"; + case eCapture: return "capture"; + case eAll: return "all"; + default: return ""; + } +} + static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_QueryInterface( IMMNotificationClient* This, REFIID riid, void **ppvObject) { -- cgit v1.2.3 From 6c512892d4691e004c453af5f0c7f3837d87b7fb Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Thu, 13 Nov 2014 01:09:47 -0800 Subject: ao/wasapi: request reset on appropriate events on changes to PKEY_AudioEngine_DeviceFormat, device status, and default device. call ao_reload directly in the change_notify "methods". this requires keeping a device enumerator around for the duration of execution, rather than just for initially querying devices --- audio/out/ao_wasapi.c | 5 -- audio/out/ao_wasapi.h | 16 ++++-- audio/out/ao_wasapi_changenotify.c | 102 ++++++++++++++++++++++++++++++------- audio/out/ao_wasapi_utils.c | 50 ++++++++---------- 4 files changed, 117 insertions(+), 56 deletions(-) diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index 42317f7b0e..6d66e5bdc3 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -36,11 +36,6 @@ #include "osdep/timer.h" #include "osdep/io.h" -#define EXIT_ON_ERROR(hres) \ - do { if (FAILED(hres)) { goto exit_label; } } while(0) -#define SAFE_RELEASE(unk, release) \ - do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0) - static double get_device_delay(struct wasapi_state *state) { UINT64 sample_count = atomic_load(&state->sample_count); UINT64 position, qpc_position; diff --git a/audio/out/ao_wasapi.h b/audio/out/ao_wasapi.h index 43c81a89d2..7e7cc6cd06 100755 --- a/audio/out/ao_wasapi.h +++ b/audio/out/ao_wasapi.h @@ -31,12 +31,18 @@ #include "osdep/atomics.h" typedef struct change_notify { - IMMNotificationClient client; + IMMNotificationClient client; /* this must be first in the structure! */ LPWSTR monitored; /* Monitored device */ + struct ao *ao; } change_notify; -HRESULT wasapi_change_init(struct change_notify *change, IMMDevice *monitor); -void wasapi_change_free(struct change_notify *change); +HRESULT wasapi_change_init(struct ao* ao); +void wasapi_change_uninit(struct ao* ao); + +#define EXIT_ON_ERROR(hres) \ + do { if (FAILED(hres)) { goto exit_label; } } while(0) +#define SAFE_RELEASE(unk, release) \ + do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0) typedef struct wasapi_state { struct mp_log *log; @@ -70,6 +76,8 @@ typedef struct wasapi_state { ISimpleAudioVolume *pAudioVolume; IAudioEndpointVolume *pEndpointVolume; IAudioSessionControl *pSessionControl; + IMMDeviceEnumerator *pEnumerator; + HANDLE hFeed; /* wasapi event */ HANDLE hForceFeed; /* forces writing a buffer (e.g. before audio_resume) */ HANDLE hFeedDone; /* set only after a hForceFeed */ @@ -108,6 +116,8 @@ typedef struct wasapi_state { HANDLE (WINAPI *pAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD); WINBOOL (WINAPI *pAvRevertMmThreadCharacteristics)(HANDLE); } VistaBlob; + + change_notify change; } wasapi_state; #endif diff --git a/audio/out/ao_wasapi_changenotify.c b/audio/out/ao_wasapi_changenotify.c index 7ff6b16d0d..26bc67d56d 100755 --- a/audio/out/ao_wasapi_changenotify.c +++ b/audio/out/ao_wasapi_changenotify.c @@ -125,13 +125,16 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceStateChanged( DWORD dwNewState) { change_notify *change = (change_notify *)This; + struct ao *ao = change->ao; - if (!wcscmp(change->monitored, pwstrDeviceId)){ + if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)){ switch (dwNewState) { case DEVICE_STATE_DISABLED: case DEVICE_STATE_NOTPRESENT: case DEVICE_STATE_UNPLUGGED: - + MP_VERBOSE(ao, + "OnDeviceStateChange triggered - requesting ao reload\n"); + ao_request_reload(ao); case DEVICE_STATE_ACTIVE: default: return S_OK; @@ -145,7 +148,11 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceAdded( LPCWSTR pwstrDeviceId) { change_notify *change = (change_notify *)This; + struct ao *ao = change->ao; + MP_VERBOSE(ao, "OnDeviceAdded triggered\n"); + if(pwstrDeviceId) + MP_VERBOSE(ao, "New device %S\n",pwstrDeviceId); return S_OK; } @@ -155,9 +162,11 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceRemoved( LPCWSTR pwstrDeviceId) { change_notify *change = (change_notify *)This; + struct ao *ao = change->ao; - if (!wcscmp(change->monitored, pwstrDeviceId)) { - + if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)) { + MP_VERBOSE(ao, "OnDeviceRemoved triggered - requesting ao reload\n"); + ao_request_reload(ao); } return S_OK; } @@ -169,10 +178,33 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDefaultDeviceChanged( LPCWSTR pwstrDeviceId) { change_notify *change = (change_notify *)This; + struct ao *ao = change->ao; + struct wasapi_state *state = (struct wasapi_state *)ao->priv; + + MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered for role:%s flow:%s\n", + ERole_to_str(role), EDataFlow_to_str(flow)); + if(pwstrDeviceId) + MP_VERBOSE(ao, "New default device %S\n", pwstrDeviceId); - if (!wcscmp(change->monitored, pwstrDeviceId)) { + /* don't care about "eCapture" or non-"eMultimedia" roles */ + if ( flow == eCapture || + role != eMultimedia ) return S_OK; + /* stay on the device the user specified */ + if (state->opt_device) { + MP_VERBOSE(ao, "Staying on specified device \"%s\"", state->opt_device); + return S_OK; } + + /* don't reload if already on the new default */ + if ( pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId) ){ + MP_VERBOSE(ao, "Already using default device, no reload required\n"); + return S_OK; + } + + /* if we got here, we need to reload */ + ao_request_reload(ao); + MP_VERBOSE(ao, "Requesting ao reload\n"); return S_OK; } @@ -182,9 +214,18 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnPropertyValueChanged( const PROPERTYKEY key) { change_notify *change = (change_notify *)This; - - if (!wcscmp(change->monitored, pwstrDeviceId)) { - + struct ao *ao = change->ao; + + if (pwstrDeviceId && !wcscmp(change->monitored, pwstrDeviceId)) { + MP_VERBOSE(ao, "OnPropertyValueChanged triggered\n"); + MP_VERBOSE(ao, "Changed property: "); + if (!PKEY_compare(&PKEY_AudioEngine_DeviceFormat, &key)) { + MP_VERBOSE(change->ao, + "PKEY_AudioEngine_DeviceFormat - requesting ao reload\n"); + ao_request_reload(change->ao); + } else { + MP_VERBOSE(ao, "%s\n", PKEY_to_str(&key)); + } } return S_OK; } @@ -200,21 +241,44 @@ static CONST_VTBL IMMNotificationClientVtbl sIMMDeviceEnumeratorVtbl_vtbl = { .OnPropertyValueChanged = sIMMNotificationClient_OnPropertyValueChanged, }; -HRESULT wasapi_change_init(struct change_notify *change, IMMDevice *monitor) -{ - HRESULT ret; - - if ( ((ret = IMMDevice_GetId(monitor, &change->monitored)) != S_OK) ) { - wasapi_change_free(change); - return ret; - } - ret = S_OK; +HRESULT wasapi_change_init(struct ao *ao) +{ + MP_DBG(ao, "Setting up monitoring on playback device\n"); + struct wasapi_state *state = (struct wasapi_state *)ao->priv; + struct change_notify *change = &state->change; + /* COM voodoo to emulate c++ class */ change->client.lpVtbl = &sIMMDeviceEnumeratorVtbl_vtbl; - return ret; + + /* so the callbacks can access the ao */ + change->ao = ao; + + /* get the device string to compare with the pwstrDeviceId argument in callbacks */ + HRESULT hr = IMMDevice_GetId(state->pDevice, &change->monitored); + EXIT_ON_ERROR(hr); + + /* register the change notification client */ + hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback( + state->pEnumerator, (IMMNotificationClient *)change); + EXIT_ON_ERROR(hr); + + MP_VERBOSE(state, "Monitoring changes in device: %S\n", state->change.monitored); + return hr; +exit_label: + MP_ERR(state, "Error setting up device change monitoring: %s\n", + wasapi_explain_err(hr)); + wasapi_change_uninit(ao); + return hr; } -void wasapi_change_free(struct change_notify *change) +void wasapi_change_uninit(struct ao *ao) { + struct wasapi_state *state = (struct wasapi_state *)ao->priv; + struct change_notify *change = &state->change; + + if( state->pEnumerator && change->client.lpVtbl ) + IMMDeviceEnumerator_UnregisterEndpointNotificationCallback( + state->pEnumerator, (IMMNotificationClient *)change); + if (change->monitored) CoTaskMemFree(change->monitored); } diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index c3a8eaa2b7..937c144650 100755 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -33,11 +33,6 @@ #define MIXER_DEFAULT_LABEL L"mpv - video player" -#define EXIT_ON_ERROR(hres) \ - do { if (FAILED(hres)) { goto exit_label; } } while(0) -#define SAFE_RELEASE(unk, release) \ - do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0) - #ifndef PKEY_Device_FriendlyName DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, @@ -728,20 +723,12 @@ exit_label: return 1; } -static HRESULT load_default_device(struct ao *ao, IMMDevice **ppDevice) +static HRESULT load_default_device(struct ao *ao, IMMDeviceEnumerator* pEnumerator, + IMMDevice **ppDevice) { - HRESULT hr; - struct wasapi_state *state = (struct wasapi_state *)ao->priv; - - IMMDeviceEnumerator *pEnumerator; - hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, - &IID_IMMDeviceEnumerator, (void**)&pEnumerator); - EXIT_ON_ERROR(hr); - - hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, - eRender, eMultimedia, - ppDevice); - SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator)); + HRESULT hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, + eRender, eMultimedia, + ppDevice); EXIT_ON_ERROR(hr); char *id = get_device_id(*ppDevice); @@ -750,16 +737,15 @@ static HRESULT load_default_device(struct ao *ao, IMMDevice **ppDevice) return S_OK; exit_label: - MP_ERR(state, "Error loading default device: %s (0x%"PRIx32")\n", + MP_ERR(ao , "Error loading default device: %s (0x%"PRIx32")\n", wasapi_explain_err(hr), (uint32_t)hr); return hr; } -static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice, - char *search) +static HRESULT find_and_load_device(struct ao *ao, IMMDeviceEnumerator* pEnumerator, + IMMDevice **ppDevice, char *search) { HRESULT hr; - IMMDeviceEnumerator *pEnumerator = NULL; IMMDeviceCollection *pDevices = NULL; IMMDevice *pTempDevice = NULL; LPWSTR deviceID = NULL; @@ -772,10 +758,6 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice, devid = search; } - hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, - &IID_IMMDeviceEnumerator, (void**)&pEnumerator); - EXIT_ON_ERROR(hr); - int search_err = 0; if (devid == NULL) { @@ -866,7 +848,7 @@ static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice, exit_label: SAFE_RELEASE(pTempDevice, IMMDevice_Release(pTempDevice)); SAFE_RELEASE(pDevices, IMMDeviceCollection_Release(pDevices)); - SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator)); + CoTaskMemFree(deviceID); return hr; } @@ -963,14 +945,19 @@ HRESULT wasapi_thread_init(struct ao *ao) MP_DBG(ao, "Init wasapi thread\n"); state->initial_volume = -1.0; + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, + &IID_IMMDeviceEnumerator, (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->pDevice); + hr = load_default_device(ao, state->pEnumerator, &state->pDevice); else - hr = find_and_load_device(ao, &state->pDevice, device); + hr = find_and_load_device(ao, state->pEnumerator, &state->pDevice, device); EXIT_ON_ERROR(hr); char *name = get_device_name(state->pDevice); @@ -1019,6 +1006,8 @@ HRESULT wasapi_thread_init(struct ao *ao) state->previous_volume = state->initial_volume; + wasapi_change_init(ao); + MP_DBG(ao, "Init wasapi thread done\n"); return S_OK; exit_label: @@ -1036,6 +1025,8 @@ void wasapi_thread_uninit(struct ao *ao) if (state->pAudioClient) IAudioClient_Stop(state->pAudioClient); + wasapi_change_uninit(ao); + if (state->opt_exclusive && state->pEndpointVolume && state->initial_volume > 0 ) @@ -1049,6 +1040,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->pEnumerator, IMMDeviceEnumerator_Release(state->pEnumerator)); if (state->hTask) state->VistaBlob.pAvRevertMmThreadCharacteristics(state->hTask); -- cgit v1.2.3 From 9371990bd14c74815babdd45ed3dd007087f60bc Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Fri, 14 Nov 2014 05:59:22 -0800 Subject: ao/wasapi: add retry loop on AUDCLNT_E_DEVICE_IN_USE this works around reinitializing too fast on device property changes --- audio/out/ao_wasapi_utils.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c index 937c144650..681c32ecde 100755 --- a/audio/out/ao_wasapi_utils.c +++ b/audio/out/ao_wasapi_utils.c @@ -30,6 +30,7 @@ #include "audio/format.h" #include "osdep/io.h" +#include "osdep/timer.h" #define MIXER_DEFAULT_LABEL L"mpv - video player" @@ -943,6 +944,8 @@ HRESULT wasapi_thread_init(struct ao *ao) struct wasapi_state *state = (struct wasapi_state *)ao->priv; HRESULT hr; MP_DBG(ao, "Init wasapi thread\n"); + int64_t retry_wait = 1; +retry: state->initial_volume = -1.0; hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, @@ -990,6 +993,15 @@ HRESULT wasapi_thread_init(struct ao *ao) MP_DBG(ao, "Fixing format\n"); hr = fix_format(state); + if ( (hr == AUDCLNT_E_DEVICE_IN_USE || + hr == AUDCLNT_E_DEVICE_INVALIDATED) && + retry_wait <= 8 ) { + wasapi_thread_uninit(ao); + MP_WARN(ao, "Retrying in %ld us\n", retry_wait); + mp_sleep_us(retry_wait); + retry_wait *= 2; + goto retry; + } EXIT_ON_ERROR(hr); MP_DBG(ao, "Creating proxies\n"); -- cgit v1.2.3 From 4c8b841fc492e21d0c079dc1217424f06beb72b7 Mon Sep 17 00:00:00 2001 From: Kevin Mitchell Date: Mon, 17 Nov 2014 03:15:13 -0800 Subject: ao/wasapi: request ao reload on thread_feed failures Even with change notifications, there are still (rare) cases when the feed thread gets AUDCLIENT_DEVICE_INVALIDATED. So handle failures in thread_feed by requesting ao_reload. --- audio/out/ao_wasapi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index 6d66e5bdc3..dc031d0bc0 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -101,6 +101,8 @@ static void thread_feed(struct ao *ao) exit_label: MP_ERR(state, "Error feeding audio: %s (0x%"PRIx32")\n", wasapi_explain_err(hr), (uint32_t)hr); + MP_VERBOSE(ao, "Requesting ao reload\n"); + ao_request_reload(ao); return; } -- cgit v1.2.3