X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faudio_output%2Fmmdevice.c;h=912247caa4e6516a6230e4f2e18f5ead0e460e1f;hb=f6c636aeaff535dcab1e8e99e7c40eca874ec6bf;hp=7ec91eb149c49feb21a5fdc00acd8c8deb529c64;hpb=b9f095473e0ce9ce5223a21a55921e857b3d20cb;p=vlc diff --git a/modules/audio_output/mmdevice.c b/modules/audio_output/mmdevice.c index 7ec91eb149..912247caa4 100644 --- a/modules/audio_output/mmdevice.c +++ b/modules/audio_output/mmdevice.c @@ -45,7 +45,7 @@ DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, DEFINE_GUID (GUID_VLC_AUD_OUT, 0x4533f59d, 0x59ee, 0x00c6, 0xad, 0xb2, 0xc6, 0x8b, 0x50, 0x1a, 0x66, 0x55); -static int TryEnter(vlc_object_t *obj) +static int TryEnterMTA(vlc_object_t *obj) { HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (unlikely(FAILED(hr))) @@ -55,16 +55,16 @@ static int TryEnter(vlc_object_t *obj) } return 0; } -#define TryEnter(o) TryEnter(VLC_OBJECT(o)) +#define TryEnterMTA(o) TryEnterMTA(VLC_OBJECT(o)) -static void Enter(void) +static void EnterMTA(void) { HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (unlikely(FAILED(hr))) abort(); } -static void Leave(void) +static void LeaveMTA(void) { CoUninitialize(); } @@ -72,7 +72,7 @@ static void Leave(void) struct aout_sys_t { audio_output_t *aout; - aout_api_t *api; /**< Audio output back-end API */ + aout_stream_t *stream; /**< Underlying audio output stream */ IMMDeviceEnumerator *it; /**< Device enumerator, NULL when exiting */ /*TODO: IMMNotificationClient*/ @@ -80,17 +80,31 @@ struct aout_sys_t IMMDevice *dev; /**< Selected output device, NULL if none */ IAudioSessionManager *manager; /**< Session for the output device */ struct IAudioSessionEvents session_events; + ISimpleAudioVolume *volume; /**< Volume setter */ LONG refs; - HANDLE device_changed; + HANDLE device_changed; /**< Event to reset thread */ + HANDLE device_ready; /**< Event when thread is reset */ vlc_thread_t thread; /**< Thread for audio session control */ }; +/* NOTE: The Core Audio API documentation totally fails to specify the thread + * safety (or lack thereof) of the interfaces. This code takes the most + * restrictive assumption, no thread safety: The background thread (MMThread) + * only runs at specified times, namely between the device_ready and + * device_changed events (effectively, a thread barrier but only Windows 8 + * provides thread barriers natively). + * + * The audio output owner (i.e. the audio output core) is responsible for + * serializing callbacks. This code only needs to be concerned with + * synchronization between the set of audio output callbacks, MMThread() + * and (trivially) the device and session notifications. */ + static int vlc_FromHR(audio_output_t *aout, HRESULT hr) { /* Restart on unplug */ if (unlikely(hr == AUDCLNT_E_DEVICE_INVALIDATED)) - var_TriggerCallback(aout, "audio-device"); + aout_RestartRequest(aout, AOUT_RESTART_OUTPUT); return SUCCEEDED(hr) ? 0 : -1; } @@ -98,7 +112,11 @@ static int vlc_FromHR(audio_output_t *aout, HRESULT hr) static int TimeGet(audio_output_t *aout, mtime_t *restrict delay) { aout_sys_t *sys = aout->sys; - HRESULT hr = aout_api_TimeGet(sys->api, delay); + HRESULT hr; + + EnterMTA(); + hr = aout_stream_TimeGet(sys->stream, delay); + LeaveMTA(); return SUCCEEDED(hr) ? 0 : -1; } @@ -106,7 +124,11 @@ static int TimeGet(audio_output_t *aout, mtime_t *restrict delay) static void Play(audio_output_t *aout, block_t *block) { aout_sys_t *sys = aout->sys; - HRESULT hr = aout_api_Play(sys->api, block); + HRESULT hr; + + EnterMTA(); + hr = aout_stream_Play(sys->stream, block); + LeaveMTA(); vlc_FromHR(aout, hr); } @@ -114,7 +136,11 @@ static void Play(audio_output_t *aout, block_t *block) static void Pause(audio_output_t *aout, bool paused, mtime_t date) { aout_sys_t *sys = aout->sys; - HRESULT hr = aout_api_Pause(sys->api, paused); + HRESULT hr; + + EnterMTA(); + hr = aout_stream_Pause(sys->stream, paused); + LeaveMTA(); vlc_FromHR(aout, hr); (void) date; @@ -124,153 +150,54 @@ static void Flush(audio_output_t *aout, bool wait) { aout_sys_t *sys = aout->sys; + EnterMTA(); + if (wait) - return; /* Drain not implemented */ + { /* Loosy drain emulation */ + mtime_t delay; + + if (SUCCEEDED(aout_stream_TimeGet(sys->stream, &delay))) + Sleep((delay / (CLOCK_FREQ / 1000)) + 1); + } + else + aout_stream_Flush(sys->stream); + + LeaveMTA(); - aout_api_Flush(sys->api); } static int VolumeSet(audio_output_t *aout, float vol) { - aout_sys_t *sys = aout->sys; - ISimpleAudioVolume *volume; - HRESULT hr; - - if (sys->manager == NULL) + ISimpleAudioVolume *volume = aout->sys->volume; + if (volume == NULL) return -1; - hr = IAudioSessionManager_GetSimpleAudioVolume(sys->manager, - &GUID_VLC_AUD_OUT, - FALSE, &volume); - if (FAILED(hr)) - { - msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr); + if (TryEnterMTA(aout)) return -1; - } - hr = ISimpleAudioVolume_SetMasterVolume(volume, vol, NULL); - ISimpleAudioVolume_Release(volume); + HRESULT hr = ISimpleAudioVolume_SetMasterVolume(volume, vol, NULL); if (FAILED(hr)) - { msg_Err(aout, "cannot set volume (error 0x%lx)", hr); - return -1; - } - return 0; + LeaveMTA(); + + return FAILED(hr) ? -1 : 0; } static int MuteSet(audio_output_t *aout, bool mute) { - aout_sys_t *sys = aout->sys; - ISimpleAudioVolume *volume; - HRESULT hr; - - if (sys->manager == NULL) + ISimpleAudioVolume *volume = aout->sys->volume; + if (volume == NULL) return -1; - hr = IAudioSessionManager_GetSimpleAudioVolume(sys->manager, - &GUID_VLC_AUD_OUT, - FALSE, &volume); - if (FAILED(hr)) - { - msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr); + if (TryEnterMTA(aout)) return -1; - } - hr = ISimpleAudioVolume_SetMute(volume, mute ? TRUE : FALSE, NULL); - ISimpleAudioVolume_Release(volume); + HRESULT hr = ISimpleAudioVolume_SetMute(volume, mute ? TRUE : FALSE, NULL); if (FAILED(hr)) - { - msg_Err(aout, "cannot set mute (error 0x%lx)", hr); - return -1; - } - return 0; -} - -/*** Audio devices ***/ -static int DeviceChanged(vlc_object_t *obj, const char *varname, - vlc_value_t prev, vlc_value_t cur, void *data) -{ - /* FIXME: This does not work. sys->dev, sys->manager and sys->api must be - * recreated. Those pointers are protected by the aout lock, which - * serializes accesses to the audio_output_t. Unfortunately, - * aout lock cannot be taken from a variable callback. - * Solution: add device_change callback to audio_output_t. */ - aout_ChannelsRestart(obj, varname, prev, cur, data); - return VLC_SUCCESS; -} - -static void GetDevices(vlc_object_t *obj, IMMDeviceEnumerator *it) -{ - HRESULT hr; - vlc_value_t val, text; - - var_Create (obj, "audio-device", VLC_VAR_STRING | VLC_VAR_HASCHOICE); - text.psz_string = _("Audio Device"); - var_Change (obj, "audio-device", VLC_VAR_SETTEXT, &text, NULL); - - /* TODO: implement IMMNotificationClient for hotplug devices */ - IMMDeviceCollection *devs; - hr = IMMDeviceEnumerator_EnumAudioEndpoints(it, eRender, - DEVICE_STATE_ACTIVE, &devs); - if (FAILED(hr)) - { - msg_Warn (obj, "cannot enumerate audio endpoints (error 0x%lx)", hr); - return; - } - - UINT n; - hr = IMMDeviceCollection_GetCount(devs, &n); - if (FAILED(hr)) - { - msg_Warn (obj, "cannot count audio endpoints (error 0x%lx)", hr); - n = 0; - } - else - msg_Dbg(obj, "Available Windows Audio devices:"); - - while (n > 0) - { - IMMDevice *dev; - - hr = IMMDeviceCollection_Item(devs, --n, &dev); - if (FAILED(hr)) - continue; - - /* Unique device ID */ - LPWSTR devid; - hr = IMMDevice_GetId(dev, &devid); - if (FAILED(hr)) - { - IMMDevice_Release(dev); - continue; - } - val.psz_string = FromWide(devid); - CoTaskMemFree(devid); - text.psz_string = val.psz_string; - - /* User-readable device name */ - IPropertyStore *props; - hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props); - if (SUCCEEDED(hr)) - { - PROPVARIANT v; - - PropVariantInit(&v); - hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v); - if (SUCCEEDED(hr)) - text.psz_string = FromWide(v.pwszVal); - PropVariantClear(&v); - IPropertyStore_Release(props); - } - IMMDevice_Release(dev); + msg_Err(aout, "cannot set volume (error 0x%lx)", hr); + LeaveMTA(); - msg_Dbg(obj, "%s (%s)", val.psz_string, text.psz_string); - var_Change(obj, "audio-device", VLC_VAR_ADDCHOICE, &val, &text); - if (likely(text.psz_string != val.psz_string)) - free(text.psz_string); - free(val.psz_string); - } - IMMDeviceCollection_Release(devs); + return FAILED(hr) ? -1 : 0; } /*** Audio session events ***/ @@ -392,12 +319,36 @@ vlc_AudioSessionEvents_OnStateChanged(IAudioSessionEvents *this, static STDMETHODIMP vlc_AudioSessionEvents_OnSessionDisconnected(IAudioSessionEvents *this, - AudioSessionDisconnectReason reason) + AudioSessionDisconnectReason reason) { aout_sys_t *sys = vlc_AudioSessionEvents_sys(this); audio_output_t *aout = sys->aout; - msg_Dbg(aout, "session disconnected: reason %d", reason); + switch (reason) + { + case DisconnectReasonDeviceRemoval: + msg_Warn(aout, "session disconnected: %s", "device removed"); + break; + case DisconnectReasonServerShutdown: + msg_Err(aout, "session disconnected: %s", "service stopped"); + return S_OK; + case DisconnectReasonFormatChanged: + msg_Warn(aout, "session disconnected: %s", "format changed"); + break; + case DisconnectReasonSessionLogoff: + msg_Err(aout, "session disconnected: %s", "user logged off"); + return S_OK; + case DisconnectReasonSessionDisconnected: + msg_Err(aout, "session disconnected: %s", "session disconnected"); + return S_OK; + case DisconnectReasonExclusiveModeOverride: + msg_Err(aout, "session disconnected: %s", "stream overriden"); + return S_OK; + default: + msg_Warn(aout, "session disconnected: unknown reason %d", reason); + return S_OK; + } + /* NOTE: audio decoder thread should get invalidated device and restart */ return S_OK; } @@ -430,45 +381,165 @@ static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name) } #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n) +static void MMSession(audio_output_t *aout, aout_sys_t *sys) +{ + IAudioSessionControl *control; + HRESULT hr; + + /* Register session control */ + if (sys->manager != NULL) + { + hr = IAudioSessionManager_GetSimpleAudioVolume(sys->manager, + &GUID_VLC_AUD_OUT, + FALSE, &sys->volume); + if (FAILED(hr)) + msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr); + + hr = IAudioSessionManager_GetAudioSessionControl(sys->manager, + &GUID_VLC_AUD_OUT, 0, + &control); + if (FAILED(hr)) + msg_Err(aout, "cannot get session control (error 0x%lx)", hr); + } + else + { + sys->volume = NULL; + control = NULL; + } + + if (control != NULL) + { + wchar_t *ua = var_InheritWide(aout, "user-agent"); + IAudioSessionControl_SetDisplayName(control, ua, NULL); + free(ua); + + IAudioSessionControl_RegisterAudioSessionNotification(control, + &sys->session_events); + } + + if (sys->volume != NULL) + { /* Get current values (_after_ changes notification registration) */ + BOOL mute; + float level; + + hr = ISimpleAudioVolume_GetMute(sys->volume, &mute); + if (FAILED(hr)) + msg_Err(aout, "cannot get mute (error 0x%lx)", hr); + else + aout_MuteReport(aout, mute != FALSE); + + hr = ISimpleAudioVolume_GetMasterVolume(sys->volume, &level); + if (FAILED(hr)) + msg_Err(aout, "cannot get mute (error 0x%lx)", hr); + else + aout_VolumeReport(aout, level); + } + + SetEvent(sys->device_ready); + /* Wait until device change or exit */ + WaitForSingleObject(sys->device_changed, INFINITE); + + /* Deregister session control */ + if (control != NULL) + { + IAudioSessionControl_UnregisterAudioSessionNotification(control, + &sys->session_events); + IAudioSessionControl_Release(control); + } + + if (sys->volume != NULL) + ISimpleAudioVolume_Release(sys->volume); +} + /** MMDevice audio output thread. * This thread takes cares of the audio session control. Inconveniently enough, * the audio session control interface must: * - be created and destroyed from the same thread, and * - survive across VLC audio output calls. * The only way to reconcile both requirements is a custom thread. + * The thread also ensure that the COM Multi-Thread Apartment is continuously + * referenced so that MMDevice objects are not destroyed early. */ static void *MMThread(void *data) { audio_output_t *aout = data; aout_sys_t *sys = aout->sys; - IAudioSessionControl *control; + + EnterMTA(); + while (sys->it != NULL) + MMSession(aout, sys); + LeaveMTA(); + return NULL; +} + +/*** Audio devices ***/ +static int DevicesEnum(audio_output_t *aout) +{ + aout_sys_t *sys = aout->sys; HRESULT hr; + IMMDeviceCollection *devs; - /* Register session control */ - hr = IAudioSessionManager_GetAudioSessionControl(sys->manager, - &GUID_VLC_AUD_OUT, 0, - &control); + hr = IMMDeviceEnumerator_EnumAudioEndpoints(sys->it, eRender, + DEVICE_STATE_ACTIVE, &devs); if (FAILED(hr)) { - msg_Err(aout, "cannot get session control (error 0x%lx)", hr); - return NULL; + msg_Warn(aout, "cannot enumerate audio endpoints (error 0x%lx)", hr); + return -1; } - wchar_t *ua = var_InheritWide(aout, "user-agent"); - IAudioSessionControl_SetDisplayName(control, ua, NULL); - free(ua); + UINT count; + hr = IMMDeviceCollection_GetCount(devs, &count); + if (FAILED(hr)) + { + msg_Warn(aout, "cannot count audio endpoints (error 0x%lx)", hr); + count = 0; + } - sys->session_events.lpVtbl = &vlc_AudioSessionEvents; - IAudioSessionControl_RegisterAudioSessionNotification(control, - &sys->session_events); + unsigned n = 0; - WaitForSingleObject(sys->device_changed, INFINITE); + for (UINT i = 0; i < count; i++) + { + IMMDevice *dev; + char *id, *name = NULL; - /* Deregister session control */ - IAudioSessionControl_UnregisterAudioSessionNotification(control, - &sys->session_events); - IAudioSessionControl_Release(control); - return NULL; + hr = IMMDeviceCollection_Item(devs, i, &dev); + if (FAILED(hr)) + continue; + + /* Unique device ID */ + LPWSTR devid; + hr = IMMDevice_GetId(dev, &devid); + if (FAILED(hr)) + { + IMMDevice_Release(dev); + continue; + } + id = FromWide(devid); + CoTaskMemFree(devid); + + /* User-readable device name */ + IPropertyStore *props; + hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props); + if (SUCCEEDED(hr)) + { + PROPVARIANT v; + + PropVariantInit(&v); + hr = IPropertyStore_GetValue(props, &PKEY_Device_FriendlyName, &v); + if (SUCCEEDED(hr)) + name = FromWide(v.pwszVal); + PropVariantClear(&v); + IPropertyStore_Release(props); + } + IMMDevice_Release(dev); + + aout_HotplugReport(aout, id, (name != NULL) ? name : id); + free(name); + free(id); + n++; + } + IMMDeviceCollection_Release(devs); + return n; } /** @@ -479,6 +550,8 @@ static HRESULT OpenDevice(audio_output_t *aout, const char *devid) aout_sys_t *sys = aout->sys; HRESULT hr; + assert(sys->dev == NULL); + if (devid != NULL) /* Device selected explicitly */ { msg_Dbg(aout, "using selected device %s", devid); @@ -495,41 +568,41 @@ static HRESULT OpenDevice(audio_output_t *aout, const char *devid) else /* Default device selected by policy */ { msg_Dbg(aout, "using default device"); - msg_Err(aout, "DOING..."); hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(sys->it, eRender, eConsole, &sys->dev); - msg_Err(aout, "DONE!"); } + assert(sys->manager == NULL); if (FAILED(hr)) { msg_Err(aout, "cannot get device (error 0x%lx)", hr); - goto error; + goto out; } /* Create session manager (for controls even w/o active audio client) */ void *pv; hr = IMMDevice_Activate(sys->dev, &IID_IAudioSessionManager, CLSCTX_ALL, NULL, &pv); + if (FAILED(hr)) + msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr); + else + sys->manager = pv; + + /* Report actual device */ + LPWSTR wdevid; + hr = IMMDevice_GetId(sys->dev, &wdevid); if (SUCCEEDED(hr)) { - sys->manager = pv; - if (vlc_clone(&sys->thread, MMThread, aout, VLC_THREAD_PRIORITY_LOW)) + char *id = FromWide(wdevid); + CoTaskMemFree(wdevid); + if (likely(id != NULL)) { - IAudioSessionManager_Release(sys->manager); - sys->manager = NULL; + aout_DeviceReport(aout, id); + free(id); } } - else - msg_Err(aout, "cannot activate session manager (error 0x%lx)", hr); - - return S_OK; - -error: - if (sys->dev != NULL) - { - IMMDevice_Release(sys->dev); - sys->dev = NULL; - } +out: + SetEvent(sys->device_changed); + WaitForSingleObject(sys->device_ready, INFINITE); return hr; } @@ -540,37 +613,92 @@ static void CloseDevice(audio_output_t *aout) { aout_sys_t *sys = aout->sys; + assert(sys->dev != NULL); + if (sys->manager != NULL) { - SetEvent(sys->device_changed); - vlc_join(sys->thread, NULL); - IAudioSessionManager_Release(sys->manager); sys->manager = NULL; } + IMMDevice_Release(sys->dev); sys->dev = NULL; } +static int DeviceSelect(audio_output_t *aout, const char *id) +{ + aout_sys_t *sys = aout->sys; + HRESULT hr; + + if (TryEnterMTA(aout)) + return -1; + + if (sys->dev != NULL) + CloseDevice(aout); + + hr = OpenDevice(aout, id); + while (hr == AUDCLNT_E_DEVICE_INVALIDATED) + hr = OpenDevice(aout, NULL); /* Fallback to default device */ + LeaveMTA(); + + if (sys->stream != NULL) + /* Request restart of stream with the new device */ + aout_RestartRequest(aout, AOUT_RESTART_OUTPUT); + return FAILED(hr) ? -1 : 0; +} + +/** + * Callback for aout_stream_t to create a stream on the device. + * This can instantiate an IAudioClient or IDirectSound(8) object. + */ +static HRESULT ActivateDevice(void *opaque, REFIID iid, PROPVARIANT *actparms, + void **restrict pv) +{ + IMMDevice *dev = opaque; + + return IMMDevice_Activate(dev, iid, CLSCTX_ALL, actparms, pv); +} + static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt) { aout_sys_t *sys = aout->sys; + HRESULT hr; - assert (sys->api == NULL); - if (sys->dev == NULL) + assert (sys->stream == NULL); + /* Open the default device if required (to deal with restarts) */ + if (sys->dev == NULL && FAILED(DeviceSelect(aout, NULL))) return -1; - sys->api = aout_api_Start(aout, fmt, sys->dev, &GUID_VLC_AUD_OUT); - return (sys->api != NULL) ? 0 : -1; + aout_stream_t *s = vlc_object_create(aout, sizeof (*s)); + if (unlikely(s == NULL)) + return -1; + + s->owner.device = sys->dev; + s->owner.activate = ActivateDevice; + + EnterMTA(); + hr = aout_stream_Start(s, fmt, &GUID_VLC_AUD_OUT); + if (SUCCEEDED(hr)) + sys->stream = s; + else + vlc_object_release(s); + LeaveMTA(); + + return vlc_FromHR(aout, hr); } static void Stop(audio_output_t *aout) { aout_sys_t *sys = aout->sys; - assert (sys->api != NULL); - aout_api_Stop(sys->api); - sys->api = NULL; + assert (sys->stream != NULL); + + EnterMTA(); + aout_stream_Stop(sys->stream); + LeaveMTA(); + + vlc_object_release(sys->stream); + sys->stream = NULL; } static int Open(vlc_object_t *obj) @@ -587,35 +715,40 @@ static int Open(vlc_object_t *obj) if (unlikely(sys == NULL)) return VLC_ENOMEM; + aout->sys = sys; sys->aout = aout; - sys->api = NULL; + sys->stream = NULL; sys->it = NULL; + sys->dev = NULL; + sys->manager = NULL; + sys->session_events.lpVtbl = &vlc_AudioSessionEvents; sys->refs = 1; + sys->device_changed = CreateEvent(NULL, FALSE, FALSE, NULL); - if (unlikely(sys->device_changed == NULL)) - abort(); + sys->device_ready = CreateEvent(NULL, FALSE, FALSE, NULL); + if (unlikely(sys->device_changed == NULL || sys->device_ready == NULL)) + goto error; /* Initialize MMDevice API */ - if (TryEnter(aout)) + if (TryEnterMTA(aout)) goto error; hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, &pv); - Leave(); if (FAILED(hr)) { + LeaveMTA(); msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr); goto error; } sys->it = pv; - GetDevices(obj, sys->it); + if (vlc_clone(&sys->thread, MMThread, aout, VLC_THREAD_PRIORITY_LOW)) + goto error; + WaitForSingleObject(sys->device_ready, INFINITE); - /* Get a device to start with */ - aout->sys = sys; - do - hr = OpenDevice(aout, NULL); - while (hr == AUDCLNT_E_DEVICE_INVALIDATED); + DeviceSelect(aout, NULL); /* Get a device to start with */ + LeaveMTA(); /* leave MTA after thread has entered MTA */ aout->start = Start; aout->stop = Stop; @@ -625,10 +758,20 @@ static int Open(vlc_object_t *obj) aout->flush = Flush; aout->volume_set = VolumeSet; aout->mute_set = MuteSet; - var_AddCallback (aout, "audio-device", DeviceChanged, NULL); + aout->device_select = DeviceSelect; + DevicesEnum(aout); return VLC_SUCCESS; error: + if (sys->it != NULL) + { + IMMDeviceEnumerator_Release(sys->it); + LeaveMTA(); + } + if (sys->device_ready != NULL) + CloseHandle(sys->device_ready); + if (sys->device_changed != NULL) + CloseHandle(sys->device_changed); free(sys); return VLC_EGENERIC; } @@ -638,13 +781,18 @@ static void Close(vlc_object_t *obj) audio_output_t *aout = (audio_output_t *)obj; aout_sys_t *sys = aout->sys; - var_DelCallback (aout, "audio-device", DeviceChanged, NULL); - var_Destroy (aout, "audio-device"); - + EnterMTA(); /* enter MTA before thread leaves MTA */ if (sys->dev != NULL) CloseDevice(aout); IMMDeviceEnumerator_Release(sys->it); + sys->it = NULL; + + SetEvent(sys->device_changed); + vlc_join(sys->thread, NULL); + LeaveMTA(); + + CloseHandle(sys->device_ready); CloseHandle(sys->device_changed); free(sys); } @@ -652,7 +800,7 @@ static void Close(vlc_object_t *obj) vlc_module_begin() set_shortname("MMDevice") set_description(N_("Windows Multimedia Device output")) - set_capability("audio output", 150) + set_capability("audio output", /*150*/0) set_category(CAT_AUDIO) set_subcategory(SUBCAT_AUDIO_AOUT) add_shortcut("wasapi")