sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_wasapi_winrt.cpp (11725B)


      1 /*
      2   Simple DirectMedia Layer
      3   Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
      4 
      5   This software is provided 'as-is', without any express or implied
      6   warranty.  In no event will the authors be held liable for any damages
      7   arising from the use of this software.
      8 
      9   Permission is granted to anyone to use this software for any purpose,
     10   including commercial applications, and to alter it and redistribute it
     11   freely, subject to the following restrictions:
     12 
     13   1. The origin of this software must not be misrepresented; you must not
     14      claim that you wrote the original software. If you use this software
     15      in a product, an acknowledgment in the product documentation would be
     16      appreciated but is not required.
     17   2. Altered source versions must be plainly marked as such, and must not be
     18      misrepresented as being the original software.
     19   3. This notice may not be removed or altered from any source distribution.
     20 */
     21 #include "../../SDL_internal.h"
     22 
     23 // This is C++/CX code that the WinRT port uses to talk to WASAPI-related
     24 //  system APIs. The C implementation of these functions, for non-WinRT apps,
     25 //  is in SDL_wasapi_win32.c. The code in SDL_wasapi.c is used by both standard
     26 //  Windows and WinRT builds to deal with audio and calls into these functions.
     27 
     28 #if SDL_AUDIO_DRIVER_WASAPI && defined(__WINRT__)
     29 
     30 #include <Windows.h>
     31 #include <windows.ui.core.h>
     32 #include <windows.devices.enumeration.h>
     33 #include <windows.media.devices.h>
     34 #include <wrl/implements.h>
     35 
     36 extern "C" {
     37 #include "../../core/windows/SDL_windows.h"
     38 #include "SDL_audio.h"
     39 #include "SDL_timer.h"
     40 #include "../SDL_audio_c.h"
     41 #include "../SDL_sysaudio.h"
     42 }
     43 
     44 #define COBJMACROS
     45 #include <mmdeviceapi.h>
     46 #include <audioclient.h>
     47 
     48 #include "SDL_wasapi.h"
     49 
     50 using namespace Windows::Devices::Enumeration;
     51 using namespace Windows::Media::Devices;
     52 using namespace Windows::Foundation;
     53 using namespace Microsoft::WRL;
     54 
     55 class SDL_WasapiDeviceEventHandler
     56 {
     57 public:
     58     SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture);
     59     ~SDL_WasapiDeviceEventHandler();
     60     void OnDeviceAdded(DeviceWatcher^ sender, DeviceInformation^ args);
     61     void OnDeviceRemoved(DeviceWatcher^ sender, DeviceInformationUpdate^ args);
     62     void OnDeviceUpdated(DeviceWatcher^ sender, DeviceInformationUpdate^ args);
     63     void OnEnumerationCompleted(DeviceWatcher^ sender, Platform::Object^ args);
     64     void OnDefaultRenderDeviceChanged(Platform::Object^ sender, DefaultAudioRenderDeviceChangedEventArgs^ args);
     65     void OnDefaultCaptureDeviceChanged(Platform::Object^ sender, DefaultAudioCaptureDeviceChangedEventArgs^ args);
     66     SDL_semaphore* completed;
     67 
     68 private:
     69     const SDL_bool iscapture;
     70     DeviceWatcher^ watcher;
     71     Windows::Foundation::EventRegistrationToken added_handler;
     72     Windows::Foundation::EventRegistrationToken removed_handler;
     73     Windows::Foundation::EventRegistrationToken updated_handler;
     74     Windows::Foundation::EventRegistrationToken completed_handler;
     75     Windows::Foundation::EventRegistrationToken default_changed_handler;
     76 };
     77 
     78 SDL_WasapiDeviceEventHandler::SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture)
     79     : iscapture(_iscapture)
     80     , completed(SDL_CreateSemaphore(0))
     81     , watcher(DeviceInformation::CreateWatcher(_iscapture ? DeviceClass::AudioCapture : DeviceClass::AudioRender))
     82 {
     83     if (!watcher || !completed)
     84         return;  // uhoh.
     85 
     86     // !!! FIXME: this doesn't need a lambda here, I think, if I make SDL_WasapiDeviceEventHandler a proper C++/CX class. --ryan.
     87     added_handler = watcher->Added += ref new TypedEventHandler<DeviceWatcher^, DeviceInformation^>([this](DeviceWatcher^ sender, DeviceInformation^ args) { OnDeviceAdded(sender, args); } );
     88     removed_handler = watcher->Removed += ref new TypedEventHandler<DeviceWatcher^, DeviceInformationUpdate^>([this](DeviceWatcher^ sender, DeviceInformationUpdate^ args) { OnDeviceRemoved(sender, args); } );
     89     updated_handler = watcher->Updated += ref new TypedEventHandler<DeviceWatcher^, DeviceInformationUpdate^>([this](DeviceWatcher^ sender, DeviceInformationUpdate^ args) { OnDeviceUpdated(sender, args); } );
     90     completed_handler = watcher->EnumerationCompleted += ref new TypedEventHandler<DeviceWatcher^, Platform::Object^>([this](DeviceWatcher^ sender, Platform::Object^ args) { OnEnumerationCompleted(sender, args); } );
     91     if (iscapture) {
     92         default_changed_handler = MediaDevice::DefaultAudioCaptureDeviceChanged += ref new TypedEventHandler<Platform::Object^, DefaultAudioCaptureDeviceChangedEventArgs^>([this](Platform::Object^ sender, DefaultAudioCaptureDeviceChangedEventArgs^ args) { OnDefaultCaptureDeviceChanged(sender, args); } );
     93     } else {
     94         default_changed_handler = MediaDevice::DefaultAudioRenderDeviceChanged += ref new TypedEventHandler<Platform::Object^, DefaultAudioRenderDeviceChangedEventArgs^>([this](Platform::Object^ sender, DefaultAudioRenderDeviceChangedEventArgs^ args) { OnDefaultRenderDeviceChanged(sender, args); } );
     95     }
     96     watcher->Start();
     97 }
     98 
     99 SDL_WasapiDeviceEventHandler::~SDL_WasapiDeviceEventHandler()
    100 {
    101     if (watcher) {
    102         watcher->Added -= added_handler;
    103         watcher->Removed -= removed_handler;
    104         watcher->Updated -= updated_handler;
    105         watcher->EnumerationCompleted -= completed_handler;
    106         watcher->Stop();
    107         watcher = nullptr;
    108     }
    109     if (completed) {
    110         SDL_DestroySemaphore(completed);
    111         completed = nullptr;
    112     }
    113 
    114     if (iscapture) {
    115         MediaDevice::DefaultAudioCaptureDeviceChanged -= default_changed_handler;
    116     } else {
    117         MediaDevice::DefaultAudioRenderDeviceChanged -= default_changed_handler;
    118     }
    119 }
    120 
    121 void
    122 SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher^ sender, DeviceInformation^ info)
    123 {
    124     SDL_assert(sender == this->watcher);
    125     char *utf8dev = WIN_StringToUTF8(info->Name->Data());
    126     if (utf8dev) {
    127         WASAPI_AddDevice(this->iscapture, utf8dev, info->Id->Data());
    128         SDL_free(utf8dev);
    129     }
    130 }
    131 
    132 void
    133 SDL_WasapiDeviceEventHandler::OnDeviceRemoved(DeviceWatcher^ sender, DeviceInformationUpdate^ info)
    134 {
    135     SDL_assert(sender == this->watcher);
    136     WASAPI_RemoveDevice(this->iscapture, info->Id->Data());
    137 }
    138 
    139 void
    140 SDL_WasapiDeviceEventHandler::OnDeviceUpdated(DeviceWatcher^ sender, DeviceInformationUpdate^ args)
    141 {
    142     SDL_assert(sender == this->watcher);
    143 }
    144 
    145 void
    146 SDL_WasapiDeviceEventHandler::OnEnumerationCompleted(DeviceWatcher^ sender, Platform::Object^ args)
    147 {
    148     SDL_assert(sender == this->watcher);
    149     SDL_SemPost(this->completed);
    150 }
    151 
    152 void
    153 SDL_WasapiDeviceEventHandler::OnDefaultRenderDeviceChanged(Platform::Object^ sender, DefaultAudioRenderDeviceChangedEventArgs^ args)
    154 {
    155     SDL_assert(this->iscapture);
    156     SDL_AtomicAdd(&WASAPI_DefaultPlaybackGeneration, 1);
    157 }
    158 
    159 void
    160 SDL_WasapiDeviceEventHandler::OnDefaultCaptureDeviceChanged(Platform::Object^ sender, DefaultAudioCaptureDeviceChangedEventArgs^ args)
    161 {
    162     SDL_assert(!this->iscapture);
    163     SDL_AtomicAdd(&WASAPI_DefaultCaptureGeneration, 1);
    164 }
    165 
    166 
    167 static SDL_WasapiDeviceEventHandler *playback_device_event_handler;
    168 static SDL_WasapiDeviceEventHandler *capture_device_event_handler;
    169 
    170 int WASAPI_PlatformInit(void)
    171 {
    172     return 0;
    173 }
    174 
    175 void WASAPI_PlatformDeinit(void)
    176 {
    177     delete playback_device_event_handler;
    178     playback_device_event_handler = nullptr;
    179     delete capture_device_event_handler;
    180     capture_device_event_handler = nullptr;
    181 }
    182 
    183 void WASAPI_EnumerateEndpoints(void)
    184 {
    185     // DeviceWatchers will fire an Added event for each existing device at
    186     //  startup, so we don't need to enumerate them separately before
    187     //  listening for updates.
    188     playback_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_FALSE);
    189     capture_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_TRUE);
    190     SDL_SemWait(playback_device_event_handler->completed);
    191     SDL_SemWait(capture_device_event_handler->completed);
    192 }
    193 
    194 struct SDL_WasapiActivationHandler : public RuntimeClass< RuntimeClassFlags< ClassicCom >, FtmBase, IActivateAudioInterfaceCompletionHandler >
    195 {
    196     SDL_WasapiActivationHandler() : device(nullptr) {}
    197     STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation *operation);
    198     SDL_AudioDevice *device;
    199 };
    200 
    201 HRESULT
    202 SDL_WasapiActivationHandler::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *async)
    203 {
    204     // Just set a flag, since we're probably in a different thread. We'll pick it up and init everything on our own thread to prevent races.
    205     SDL_AtomicSet(&device->hidden->just_activated, 1);
    206     WASAPI_UnrefDevice(device);
    207     return S_OK;
    208 }
    209 
    210 void
    211 WASAPI_PlatformDeleteActivationHandler(void *handler)
    212 {
    213     ((SDL_WasapiActivationHandler *) handler)->Release();
    214 }
    215 
    216 int
    217 WASAPI_ActivateDevice(_THIS, const SDL_bool isrecovery)
    218 {
    219     LPCWSTR devid = _this->hidden->devid;
    220     Platform::String^ defdevid;
    221 
    222     if (devid == nullptr) {
    223         defdevid = _this->iscapture ? MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default) : MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
    224         if (defdevid) {
    225             devid = defdevid->Data();
    226         }
    227     }
    228 
    229     SDL_AtomicSet(&_this->hidden->just_activated, 0);
    230 
    231     ComPtr<SDL_WasapiActivationHandler> handler = Make<SDL_WasapiActivationHandler>();
    232     if (handler == nullptr) {
    233         return SDL_SetError("Failed to allocate WASAPI activation handler");
    234     }
    235 
    236     handler.Get()->AddRef();  // we hold a reference after ComPtr destructs on return, causing a Release, and Release ourselves in WASAPI_PlatformDeleteActivationHandler(), etc.
    237     handler.Get()->device = _this;
    238     _this->hidden->activation_handler = handler.Get();
    239 
    240     WASAPI_RefDevice(_this);  /* completion handler will unref it. */
    241     IActivateAudioInterfaceAsyncOperation *async = nullptr;
    242     const HRESULT ret = ActivateAudioInterfaceAsync(devid, __uuidof(IAudioClient), nullptr, handler.Get(), &async);
    243 
    244     if (FAILED(ret) || async == nullptr) {
    245         if (async != nullptr) {
    246             async->Release();
    247         }
    248         handler.Get()->Release();
    249         WASAPI_UnrefDevice(_this);
    250         return WIN_SetErrorFromHRESULT("WASAPI can't activate requested audio endpoint", ret);
    251     }
    252 
    253     /* Spin until the async operation is complete.
    254      * If we don't PrepDevice before leaving this function, the bug list gets LONG:
    255      * - device.spec is not filled with the correct information
    256      * - The 'obtained' spec will be wrong for ALLOW_CHANGE properties
    257      * - SDL_AudioStreams will/will not be allocated at the right time
    258      * - SDL_assert(device->callbackspec.size == device->spec.size) will fail
    259      * - When the assert is ignored, skipping or a buffer overflow will occur
    260      */
    261     while (!SDL_AtomicCAS(&_this->hidden->just_activated, 1, 0)) {
    262         SDL_Delay(1);
    263     }
    264 
    265     HRESULT activateRes = S_OK;
    266     IUnknown *iunknown = nullptr;
    267     const HRESULT getActivateRes = async->GetActivateResult(&activateRes, &iunknown);
    268     async->Release();
    269     if (FAILED(getActivateRes)) {
    270         return WIN_SetErrorFromHRESULT("Failed to get WASAPI activate result", getActivateRes);
    271     } else if (FAILED(activateRes)) {
    272         return WIN_SetErrorFromHRESULT("Failed to activate WASAPI device", activateRes);
    273     }
    274 
    275     iunknown->QueryInterface(IID_PPV_ARGS(&_this->hidden->client));
    276     if (!_this->hidden->client) {
    277         return SDL_SetError("Failed to query WASAPI client interface");
    278     }
    279 
    280     if (WASAPI_PrepDevice(_this, isrecovery) == -1) {
    281         return -1;
    282     }
    283 
    284     return 0;
    285 }
    286 
    287 void
    288 WASAPI_PlatformThreadInit(_THIS)
    289 {
    290     // !!! FIXME: set this thread to "Pro Audio" priority.
    291 }
    292 
    293 void
    294 WASAPI_PlatformThreadDeinit(_THIS)
    295 {
    296     // !!! FIXME: set this thread to "Pro Audio" priority.
    297 }
    298 
    299 #endif  // SDL_AUDIO_DRIVER_WASAPI && defined(__WINRT__)
    300 
    301 /* vi: set ts=4 sw=4 expandtab: */