X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Faudio_output%2Fmmdevice.c;h=912247caa4e6516a6230e4f2e18f5ead0e460e1f;hb=7b5250473ca12e6a806a128441a39e7a417c8ba2;hp=90735ff52914ef54965dfaacd4633c3035b1f53c;hpb=d68da462c2d2457b7ec50c57fda2b50909fef619;p=vlc diff --git a/modules/audio_output/mmdevice.c b/modules/audio_output/mmdevice.c index 90735ff529..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,30 +72,39 @@ static void Leave(void) struct aout_sys_t { audio_output_t *aout; - aout_api_t *api; /**< Audio output back-end API */ - IMMDeviceEnumerator *it; - IMMDevice *dev; /**< Selected output device */ - IAudioSessionManager *manager; /**< Session for the output device */ + aout_stream_t *stream; /**< Underlying audio output stream */ + IMMDeviceEnumerator *it; /**< Device enumerator, NULL when exiting */ /*TODO: IMMNotificationClient*/ + + 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; - CRITICAL_SECTION lock; - CONDITION_VARIABLE request_wait; - CONDITION_VARIABLE reply_wait; - - bool killed; /**< Flag to terminate the thread */ - bool running; /**< Whether the thread is running */ - int8_t mute; /**< Requested mute state or negative value */ - float volume; /**< Requested volume or negative value */ + 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; } @@ -103,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; } @@ -111,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); } @@ -119,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; @@ -129,121 +150,54 @@ static void Flush(audio_output_t *aout, bool wait) { aout_sys_t *sys = aout->sys; - if (wait) - return; /* Drain not implemented */ + EnterMTA(); - aout_api_Flush(sys->api); -} + if (wait) + { /* Loosy drain emulation */ + mtime_t delay; -static int VolumeSet(audio_output_t *aout, float vol) -{ - aout_sys_t *sys = aout->sys; + if (SUCCEEDED(aout_stream_TimeGet(sys->stream, &delay))) + Sleep((delay / (CLOCK_FREQ / 1000)) + 1); + } + else + aout_stream_Flush(sys->stream); - EnterCriticalSection(&sys->lock); - sys->volume = vol; - LeaveCriticalSection(&sys->lock); + LeaveMTA(); - WakeConditionVariable(&sys->request_wait); - return 0; } -static int MuteSet(audio_output_t *aout, bool mute) +static int VolumeSet(audio_output_t *aout, float vol) { - aout_sys_t *sys = aout->sys; + ISimpleAudioVolume *volume = aout->sys->volume; + if (volume == NULL) + return -1; - EnterCriticalSection(&sys->lock); - sys->mute = mute; - LeaveCriticalSection(&sys->lock); + if (TryEnterMTA(aout)) + return -1; - WakeConditionVariable(&sys->request_wait); - return 0; -} + HRESULT hr = ISimpleAudioVolume_SetMasterVolume(volume, vol, NULL); + if (FAILED(hr)) + msg_Err(aout, "cannot set volume (error 0x%lx)", hr); + LeaveMTA(); -/*** 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; + return FAILED(hr) ? -1 : 0; } -static void GetDevices(vlc_object_t *obj, IMMDeviceEnumerator *it) +static int MuteSet(audio_output_t *aout, bool mute) { - 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); + ISimpleAudioVolume *volume = aout->sys->volume; + if (volume == NULL) + return -1; - /* 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; - } + if (TryEnterMTA(aout)) + return -1; - UINT n; - hr = IMMDeviceCollection_GetCount(devs, &n); + HRESULT hr = ISimpleAudioVolume_SetMute(volume, mute ? TRUE : FALSE, NULL); 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; + msg_Err(aout, "cannot set volume (error 0x%lx)", hr); + LeaveMTA(); - 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_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 ***/ @@ -365,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; } @@ -403,104 +381,324 @@ static wchar_t *var_InheritWide(vlc_object_t *obj, const char *name) } #define var_InheritWide(o,n) var_InheritWide(VLC_OBJECT(o),n) -/** MMDevice audio output thread. - * A number of Core Audio Interfaces must be deleted from the same thread than - * they were created from... */ -static void MMThread(void *data) +static void MMSession(audio_output_t *aout, aout_sys_t *sys) { - audio_output_t *aout = data; - aout_sys_t *sys = aout->sys; IAudioSessionControl *control; - ISimpleAudioVolume *volume; HRESULT hr; - Enter(); - /* Instantiate thread-invariable interfaces */ - hr = IAudioSessionManager_GetAudioSessionControl(sys->manager, - &GUID_VLC_AUD_OUT, 0, - &control); - if (FAILED(hr)) - msg_Warn(aout, "cannot get session control (error 0x%lx)", 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); - sys->session_events.lpVtbl = &vlc_AudioSessionEvents; IAudioSessionControl_RegisterAudioSessionNotification(control, &sys->session_events); } - hr = IAudioSessionManager_GetSimpleAudioVolume(sys->manager, - &GUID_VLC_AUD_OUT, FALSE, - &volume); + 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; + + 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; + + hr = IMMDeviceEnumerator_EnumAudioEndpoints(sys->it, eRender, + DEVICE_STATE_ACTIVE, &devs); + if (FAILED(hr)) + { + msg_Warn(aout, "cannot enumerate audio endpoints (error 0x%lx)", hr); + return -1; + } + + UINT count; + hr = IMMDeviceCollection_GetCount(devs, &count); if (FAILED(hr)) - msg_Err(aout, "cannot get simple volume (error 0x%lx)", hr); + { + msg_Warn(aout, "cannot count audio endpoints (error 0x%lx)", hr); + count = 0; + } - EnterCriticalSection(&sys->lock); - sys->running = true; - WakeConditionVariable(&sys->reply_wait); + unsigned n = 0; - while (!sys->killed) + for (UINT i = 0; i < count; i++) { - /* Update volume */ - if (sys->volume >= 0.f) + IMMDevice *dev; + char *id, *name = NULL; + + hr = IMMDeviceCollection_Item(devs, i, &dev); + if (FAILED(hr)) + continue; + + /* Unique device ID */ + LPWSTR devid; + hr = IMMDevice_GetId(dev, &devid); + if (FAILED(hr)) { - hr = ISimpleAudioVolume_SetMasterVolume(volume, sys->volume, NULL); - if (FAILED(hr)) - msg_Err(aout, "cannot set volume (error 0x%lx)", hr); - sys->volume = -1.f; + IMMDevice_Release(dev); + continue; } + id = FromWide(devid); + CoTaskMemFree(devid); - /* Update mute state */ - if (sys->mute >= 0) + /* User-readable device name */ + IPropertyStore *props; + hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &props); + if (SUCCEEDED(hr)) { - hr = ISimpleAudioVolume_SetMute(volume, sys->mute, NULL); - if (FAILED(hr)) - msg_Err(aout, "cannot set mute (error 0x%lx)", hr); - sys->mute = -1; + 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); - SleepConditionVariableCS(&sys->request_wait, &sys->lock, INFINITE); + aout_HotplugReport(aout, id, (name != NULL) ? name : id); + free(name); + free(id); + n++; } - LeaveCriticalSection(&sys->lock); + IMMDeviceCollection_Release(devs); + return n; +} - if (volume != NULL) - ISimpleAudioVolume_Release(volume); - if (control != NULL) +/** + * Opens the selected audio output device. + */ +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 */ { - IAudioSessionControl_UnregisterAudioSessionNotification(control, - &sys->session_events); - IAudioSessionControl_Release(control); + msg_Dbg(aout, "using selected device %s", devid); + + wchar_t *wdevid = ToWide(devid); + if (likely(wdevid != NULL)) + { + hr = IMMDeviceEnumerator_GetDevice(sys->it, wdevid, &sys->dev); + free (wdevid); + } + else + hr = E_OUTOFMEMORY; + } + else /* Default device selected by policy */ + { + msg_Dbg(aout, "using default device"); + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(sys->it, eRender, + eConsole, &sys->dev); + } + assert(sys->manager == NULL); + if (FAILED(hr)) + { + msg_Err(aout, "cannot get device (error 0x%lx)", hr); + goto out; } - Leave(); - EnterCriticalSection(&sys->lock); - sys->running = false; - LeaveCriticalSection(&sys->lock); - WakeConditionVariable(&sys->reply_wait); + /* 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)) + { + char *id = FromWide(wdevid); + CoTaskMemFree(wdevid); + if (likely(id != NULL)) + { + aout_DeviceReport(aout, id); + free(id); + } + } +out: + SetEvent(sys->device_changed); + WaitForSingleObject(sys->device_ready, INFINITE); + return hr; +} + +/** + * Closes the opened audio output device (if any). + */ +static void CloseDevice(audio_output_t *aout) +{ + aout_sys_t *sys = aout->sys; + + assert(sys->dev != NULL); + + if (sys->manager != 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) @@ -513,80 +711,45 @@ static int Open(vlc_object_t *obj) /* Fallback to other plugin until pass-through is implemented */ return VLC_EGENERIC; - /* Initialize MMDevice API */ - if (TryEnter(aout)) - return VLC_EGENERIC; - aout_sys_t *sys = malloc(sizeof (*sys)); 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; - InitializeCriticalSection(&sys->lock); - InitializeConditionVariable(&sys->request_wait); - InitializeConditionVariable(&sys->reply_wait); - sys->killed = false; - sys->running = false; - sys->volume = -1.f; - sys->mute = -1; + + sys->device_changed = CreateEvent(NULL, FALSE, FALSE, NULL); + sys->device_ready = CreateEvent(NULL, FALSE, FALSE, NULL); + if (unlikely(sys->device_changed == NULL || sys->device_ready == NULL)) + goto error; + + /* Initialize MMDevice API */ + if (TryEnterMTA(aout)) + goto error; hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, &pv); if (FAILED(hr)) { + LeaveMTA(); msg_Dbg(aout, "cannot create device enumerator (error 0x%lx)", hr); - Leave(); goto error; } sys->it = pv; - GetDevices(obj, sys->it); - /* Get audio device according to policy */ - wchar_t *devid = var_InheritWide(aout, "audio-device"); - if (devid != NULL) - { - msg_Dbg (aout, "using selected device %ls", devid); - hr = IMMDeviceEnumerator_GetDevice (sys->it, devid, &sys->dev); - if (FAILED(hr)) - msg_Err(aout, "cannot get device %ls (error 0x%lx)", devid, hr); - free (devid); - } - if (sys->dev == NULL) - { - msg_Dbg (aout, "using default device"); - hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(sys->it, eRender, - eConsole, &sys->dev); - if (FAILED(hr)) - msg_Err(aout, "cannot get default device (error 0x%lx)", hr); - } - if (sys->dev == NULL) - /* TODO: VLC should be able to start without devices, so long as - * a device becomes available before Start() is called. */ - goto error; - - 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; - - /* Note: thread handle released by CRT, ignore it. */ - if (_beginthread(MMThread, 0, aout) == (uintptr_t)-1) + if (vlc_clone(&sys->thread, MMThread, aout, VLC_THREAD_PRIORITY_LOW)) goto error; + WaitForSingleObject(sys->device_ready, INFINITE); - EnterCriticalSection(&sys->lock); - while (!sys->running) - SleepConditionVariableCS(&sys->reply_wait, &sys->lock, INFINITE); - LeaveCriticalSection(&sys->lock); - Leave(); + DeviceSelect(aout, NULL); /* Get a device to start with */ + LeaveMTA(); /* leave MTA after thread has entered MTA */ - aout->sys = sys; aout->start = Start; aout->stop = Stop; aout->time_get = TimeGet; @@ -595,16 +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->manager != NULL) - IAudioSessionManager_Release(sys->manager); - if (sys->dev != NULL) - IMMDevice_Release(sys->dev); if (sys->it != NULL) + { IMMDeviceEnumerator_Release(sys->it); - DeleteCriticalSection(&sys->lock); + LeaveMTA(); + } + if (sys->device_ready != NULL) + CloseHandle(sys->device_ready); + if (sys->device_changed != NULL) + CloseHandle(sys->device_changed); free(sys); return VLC_EGENERIC; } @@ -614,31 +781,26 @@ static void Close(vlc_object_t *obj) audio_output_t *aout = (audio_output_t *)obj; aout_sys_t *sys = aout->sys; - EnterCriticalSection(&sys->lock); - sys->killed = true; - WakeConditionVariable(&sys->request_wait); - while (sys->running) - SleepConditionVariableCS(&sys->reply_wait, &sys->lock, INFINITE); - LeaveCriticalSection(&sys->lock); - - var_DelCallback (aout, "audio-device", DeviceChanged, NULL); - var_Destroy (aout, "audio-device"); - - Enter(); + EnterMTA(); /* enter MTA before thread leaves MTA */ + if (sys->dev != NULL) + CloseDevice(aout); - if (sys->manager != NULL) - IAudioSessionManager_Release(sys->manager); - IMMDevice_Release(sys->dev); IMMDeviceEnumerator_Release(sys->it); - Leave(); + sys->it = NULL; + + SetEvent(sys->device_changed); + vlc_join(sys->thread, NULL); + LeaveMTA(); + CloseHandle(sys->device_ready); + CloseHandle(sys->device_changed); free(sys); } 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")