wasapi: Reworked for new SDL3 audio API, other win32 fixes.

The WinRT code has _also_ be updated, but it has not been
tested or compiled, yet.
main
Ryan C. Gordon 2023-07-14 19:55:30 -04:00
parent dc04f85646
commit c58d95c343
No known key found for this signature in database
GPG Key ID: FA148B892AB48044
10 changed files with 654 additions and 556 deletions

View File

@ -1082,7 +1082,7 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec)
return 0; return 0;
} }
// this expects the device lock to be held. // this expects the device lock to be held. !!! FIXME: no it doesn't...?
static void ClosePhysicalAudioDevice(SDL_AudioDevice *device) static void ClosePhysicalAudioDevice(SDL_AudioDevice *device)
{ {
SDL_assert(current_audio.impl.ProvidesOwnCallbackThread || ((device->thread == NULL) == (SDL_AtomicGet(&device->thread_alive) == 0))); SDL_assert(current_audio.impl.ProvidesOwnCallbackThread || ((device->thread == NULL) == (SDL_AtomicGet(&device->thread_alive) == 0)));
@ -1688,3 +1688,45 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
SDL_AudioDeviceDisconnected(current_default_device); // Call again, now that we're not the default; this will remove from device list, send removal events, and destroy the SDL_AudioDevice. SDL_AudioDeviceDisconnected(current_default_device); // Call again, now that we're not the default; this will remove from device list, send removal events, and destroy the SDL_AudioDevice.
} }
} }
int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames)
{
SDL_bool kill_device = SDL_FALSE;
const int orig_buffer_size = device->buffer_size;
const SDL_bool iscapture = device->iscapture;
if ((device->spec.format != newspec->format) || (device->spec.channels != newspec->channels) || (device->spec.freq != newspec->freq)) {
SDL_memcpy(&device->spec, newspec, sizeof (newspec));
for (SDL_LogicalAudioDevice *logdev = device->logical_devices; !kill_device && (logdev != NULL); logdev = logdev->next) {
for (SDL_AudioStream *stream = logdev->bound_streams; !kill_device && (stream != NULL); stream = stream->next_binding) {
if (SDL_SetAudioStreamFormat(stream, iscapture ? &device->spec : NULL, iscapture ? NULL : &device->spec) == -1) {
kill_device = SDL_TRUE;
}
}
}
}
if (!kill_device) {
device->sample_frames = new_sample_frames;
SDL_UpdatedAudioDeviceFormat(device);
if (device->work_buffer && (device->buffer_size > orig_buffer_size)) {
SDL_aligned_free(device->work_buffer);
device->work_buffer = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), device->buffer_size);
if (!device->work_buffer) {
kill_device = SDL_TRUE;
}
}
}
return kill_device ? -1 : 0;
}
int SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames)
{
SDL_LockMutex(device->lock);
const int retval = SDL_AudioDeviceFormatChangedAlreadyLocked(device, newspec, new_sample_frames);
SDL_UnlockMutex(device->lock);
return retval;
}

View File

@ -879,7 +879,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
// if they are upsampling and we end up needing less than a frame of input, we reject it because it would cause artifacts on future reads to eat a full input frame. // if they are upsampling and we end up needing less than a frame of input, we reject it because it would cause artifacts on future reads to eat a full input frame.
// however, if the stream is flushed, we would just be padding any remaining input with silence anyhow, so use it up. // however, if the stream is flushed, we would just be padding any remaining input with silence anyhow, so use it up.
if (stream->flushed) { if (stream->flushed) {
SDL_assert(((input_frames * src_sample_frame_size) + future_buffer_filled_frames) <= stream->future_buffer_allocation); SDL_assert(((size_t) ((input_frames * src_sample_frame_size) + future_buffer_filled_frames)) <= stream->future_buffer_allocation);
// leave input_frames alone; this will just shuffle what's available from the future buffer and pad with silence as appropriate, below. // leave input_frames alone; this will just shuffle what's available from the future buffer and pad with silence as appropriate, below.
} else { } else {
return 0; return 0;

View File

@ -85,6 +85,12 @@ extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device);
// Backends should call this if the system default device changes. // Backends should call this if the system default device changes.
extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device); extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device);
// Backends should call this if a device's format is changing (opened or not); SDL will update state and carry on with the new format.
extern int SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
// Same as above, but assume the device is already locked.
extern int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
// Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. DOES NOT LOCK THE DEVICE. // Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. DOES NOT LOCK THE DEVICE.
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle); extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle);

File diff suppressed because it is too large Load Diff

View File

@ -31,37 +31,38 @@ extern "C" {
struct SDL_PrivateAudioData struct SDL_PrivateAudioData
{ {
SDL_AtomicInt refcount;
WCHAR *devid; WCHAR *devid;
WAVEFORMATEX *waveformat; WAVEFORMATEX *waveformat;
IAudioClient *client; IAudioClient *client;
IAudioRenderClient *render; IAudioRenderClient *render;
IAudioCaptureClient *capture; IAudioCaptureClient *capture;
SDL_AudioStream *capturestream;
HANDLE event; HANDLE event;
HANDLE task; HANDLE task;
SDL_bool coinitialized; SDL_bool coinitialized;
int framesize; int framesize;
int default_device_generation;
SDL_bool device_lost; SDL_bool device_lost;
void *activation_handler; void *activation_handler;
SDL_AtomicInt just_activated;
}; };
/* win32 and winrt implementations call into these. */ /* win32 and winrt implementations call into these. */
int WASAPI_PrepDevice(SDL_AudioDevice *_this, const SDL_bool updatestream); int WASAPI_PrepDevice(SDL_AudioDevice *device);
void WASAPI_RefDevice(SDL_AudioDevice *_this); void WASAPI_DisconnectDevice(SDL_AudioDevice *device);
void WASAPI_UnrefDevice(SDL_AudioDevice *_this);
// BE CAREFUL: if you are holding the device lock and proxy to the management thread with wait_until_complete, and grab the lock again, you will deadlock.
typedef int (*ManagementThreadTask)(void *userdata);
int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_until_complete);
/* These are functions that are implemented differently for Windows vs WinRT. */ /* These are functions that are implemented differently for Windows vs WinRT. */
// UNLESS OTHERWISE NOTED THESE ALL HAPPEN ON THE MANAGEMENT THREAD.
int WASAPI_PlatformInit(void); int WASAPI_PlatformInit(void);
void WASAPI_PlatformDeinit(void); void WASAPI_PlatformDeinit(void);
void WASAPI_EnumerateEndpoints(void); void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture);
int WASAPI_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture); int WASAPI_ActivateDevice(SDL_AudioDevice *device);
int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery); void WASAPI_PlatformThreadInit(SDL_AudioDevice *device); // this happens on the audio device thread, not the management thread.
void WASAPI_PlatformThreadInit(SDL_AudioDevice *_this); void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device); // this happens on the audio device thread, not the management thread.
void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *_this);
void WASAPI_PlatformDeleteActivationHandler(void *handler); void WASAPI_PlatformDeleteActivationHandler(void *handler);
void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -49,7 +49,7 @@ static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x
int WASAPI_PlatformInit(void) int WASAPI_PlatformInit(void)
{ {
if (SDL_IMMDevice_Init() < 0) { if (SDL_IMMDevice_Init() < 0) { // this will call WIN_CoInitialize for us!
return -1; /* This is set by SDL_IMMDevice_Init */ return -1; /* This is set by SDL_IMMDevice_Init */
} }
@ -72,72 +72,65 @@ void WASAPI_PlatformDeinit(void)
pAvSetMmThreadCharacteristicsW = NULL; pAvSetMmThreadCharacteristicsW = NULL;
pAvRevertMmThreadCharacteristics = NULL; pAvRevertMmThreadCharacteristics = NULL;
SDL_IMMDevice_Quit(); SDL_IMMDevice_Quit(); // This will call WIN_CoUninitialize for us!
} }
void WASAPI_PlatformThreadInit(SDL_AudioDevice *_this) void WASAPI_PlatformThreadInit(SDL_AudioDevice *device)
{ {
/* this thread uses COM. */ /* this thread uses COM. */
if (SUCCEEDED(WIN_CoInitialize())) { /* can't report errors, hope it worked! */ if (SUCCEEDED(WIN_CoInitialize())) { /* can't report errors, hope it worked! */
_this->hidden->coinitialized = SDL_TRUE; device->hidden->coinitialized = SDL_TRUE;
} }
/* Set this thread to very high "Pro Audio" priority. */ /* Set this thread to very high "Pro Audio" priority. */
if (pAvSetMmThreadCharacteristicsW) { if (pAvSetMmThreadCharacteristicsW) {
DWORD idx = 0; DWORD idx = 0;
_this->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx); device->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx);
} }
} }
void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *_this) void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device)
{ {
/* Set this thread back to normal priority. */ /* Set this thread back to normal priority. */
if (_this->hidden->task && pAvRevertMmThreadCharacteristics) { if (device->hidden->task && pAvRevertMmThreadCharacteristics) {
pAvRevertMmThreadCharacteristics(_this->hidden->task); pAvRevertMmThreadCharacteristics(device->hidden->task);
_this->hidden->task = NULL; device->hidden->task = NULL;
} }
if (_this->hidden->coinitialized) { if (device->hidden->coinitialized) {
WIN_CoUninitialize(); WIN_CoUninitialize();
_this->hidden->coinitialized = SDL_FALSE; device->hidden->coinitialized = SDL_FALSE;
} }
} }
int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery) int WASAPI_ActivateDevice(SDL_AudioDevice *device)
{ {
IMMDevice *device = NULL; IMMDevice *immdevice = NULL;
HRESULT ret; if (SDL_IMMDevice_Get(device, &immdevice, device->iscapture) < 0) {
device->hidden->client = NULL;
if (SDL_IMMDevice_Get(_this->hidden->devid, &device, _this->iscapture) < 0) {
_this->hidden->client = NULL;
return -1; /* This is already set by SDL_IMMDevice_Get */ return -1; /* This is already set by SDL_IMMDevice_Get */
} }
/* this is not async in standard win32, yay! */ /* this is _not_ async in standard win32, yay! */
ret = IMMDevice_Activate(device, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&_this->hidden->client); HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client);
IMMDevice_Release(device); IMMDevice_Release(immdevice);
if (FAILED(ret)) { if (FAILED(ret)) {
SDL_assert(_this->hidden->client == NULL); SDL_assert(device->hidden->client == NULL);
return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret); return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret);
} }
SDL_assert(_this->hidden->client != NULL); SDL_assert(device->hidden->client != NULL);
if (WASAPI_PrepDevice(_this, isrecovery) == -1) { /* not async, fire it right away. */ if (WASAPI_PrepDevice(device) == -1) { /* not async, fire it right away. */
return -1; return -1;
} }
return 0; /* good to go. */ return 0; /* good to go. */
} }
void WASAPI_EnumerateEndpoints(void) void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{ {
SDL_IMMDevice_EnumerateEndpoints(SDL_FALSE); SDL_IMMDevice_EnumerateEndpoints(default_output, default_capture);
}
int WASAPI_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture)
{
return SDL_IMMDevice_GetDefaultAudioInfo(name, spec, iscapture);
} }
void WASAPI_PlatformDeleteActivationHandler(void *handler) void WASAPI_PlatformDeleteActivationHandler(void *handler)
@ -146,4 +139,9 @@ void WASAPI_PlatformDeleteActivationHandler(void *handler)
SDL_assert(!"This function should have only been called on WinRT."); SDL_assert(!"This function should have only been called on WinRT.");
} }
void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device)
{
SDL_IMMDevice_FreeDeviceHandle(device);
}
#endif /* SDL_AUDIO_DRIVER_WASAPI && !defined(__WINRT__) */ #endif /* SDL_AUDIO_DRIVER_WASAPI && !defined(__WINRT__) */

View File

@ -53,21 +53,16 @@ using namespace Microsoft::WRL;
static Platform::String ^ SDL_PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0"; static Platform::String ^ SDL_PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0";
static void WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid);
static void WASAPI_RemoveDevice(const SDL_bool iscapture, LPCWSTR devid); static SDL_bool FindWinRTAudioDeviceCallback(SDL_AudioDevice *device, void *userdata)
extern "C" { {
SDL_AtomicInt SDL_IMMDevice_DefaultPlaybackGeneration; return (SDL_wcscmp((LPCWSTR) device->handle, (LPCWSTR) userdata) == 0) ? SDL_TRUE : SDL_FALSE;
SDL_AtomicInt SDL_IMMDevice_DefaultCaptureGeneration;
} }
/* This is a list of device id strings we have inflight, so we have consistent pointers to the same device. */ static SDL_AudioDevice *FindWinRTAudioDevice(LPCWSTR devid)
typedef struct DevIdList
{ {
WCHAR *str; return SDL_FindPhysicalAudioDeviceByCallback(FindWinRTAudioDeviceCallback, (void *) devid);
struct DevIdList *next; }
} DevIdList;
static DevIdList *deviceid_list = NULL;
class SDL_WasapiDeviceEventHandler class SDL_WasapiDeviceEventHandler
{ {
@ -80,9 +75,10 @@ class SDL_WasapiDeviceEventHandler
void OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args); void OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args);
void OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args); void OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args);
void OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args); void OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args);
SDL_Semaphore *completed; void WaitForCompletion();
private: private:
SDL_Semaphore *completed_semaphore;
const SDL_bool iscapture; const SDL_bool iscapture;
DeviceWatcher ^ watcher; DeviceWatcher ^ watcher;
Windows::Foundation::EventRegistrationToken added_handler; Windows::Foundation::EventRegistrationToken added_handler;
@ -93,10 +89,11 @@ class SDL_WasapiDeviceEventHandler
}; };
SDL_WasapiDeviceEventHandler::SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture) SDL_WasapiDeviceEventHandler::SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture)
: iscapture(_iscapture), completed(SDL_CreateSemaphore(0)) : iscapture(_iscapture), completed_semaphore(SDL_CreateSemaphore(0))
{ {
if (!completed) if (!completed_semaphore) {
return; // uhoh. return; // uhoh.
}
Platform::String ^ selector = _iscapture ? MediaDevice::GetAudioCaptureSelector() : MediaDevice::GetAudioRenderSelector(); Platform::String ^ selector = _iscapture ? MediaDevice::GetAudioCaptureSelector() : MediaDevice::GetAudioRenderSelector();
Platform::Collections::Vector<Platform::String ^> properties; Platform::Collections::Vector<Platform::String ^> properties;
@ -128,9 +125,10 @@ SDL_WasapiDeviceEventHandler::~SDL_WasapiDeviceEventHandler()
watcher->Stop(); watcher->Stop();
watcher = nullptr; watcher = nullptr;
} }
if (completed) {
SDL_DestroySemaphore(completed); if (completed_semaphore) {
completed = nullptr; SDL_DestroySemaphore(completed_semaphore);
completed_semaphore = nullptr;
} }
if (iscapture) { if (iscapture) {
@ -142,21 +140,34 @@ SDL_WasapiDeviceEventHandler::~SDL_WasapiDeviceEventHandler()
void SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher ^ sender, DeviceInformation ^ info) void SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher ^ sender, DeviceInformation ^ info)
{ {
/* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever).
In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for
phones and tablets, where you might have an internal speaker and a headphone jack and expect both to be
available and switch automatically. (!!! FIXME...?) */
SDL_assert(sender == this->watcher); SDL_assert(sender == this->watcher);
char *utf8dev = WIN_StringToUTF8(info->Name->Data()); char *utf8dev = WIN_StringToUTF8(info->Name->Data());
if (utf8dev) { if (utf8dev) {
WAVEFORMATEXTENSIBLE fmt; SDL_AudioSpec spec;
SDL_zero(spec);
Platform::Object ^ obj = info->Properties->Lookup(SDL_PKEY_AudioEngine_DeviceFormat); Platform::Object ^ obj = info->Properties->Lookup(SDL_PKEY_AudioEngine_DeviceFormat);
if (obj) { if (obj) {
IPropertyValue ^ property = (IPropertyValue ^) obj; IPropertyValue ^ property = (IPropertyValue ^) obj;
Platform::Array<unsigned char> ^ data; Platform::Array<unsigned char> ^ data;
property->GetUInt8Array(&data); property->GetUInt8Array(&data);
SDL_memcpy(&fmt, data->Data, SDL_min(data->Length, sizeof(WAVEFORMATEXTENSIBLE))); WAVEFORMATEXTENSIBLE fmt;
} else {
SDL_zero(fmt); SDL_zero(fmt);
SDL_memcpy(&fmt, data->Data, SDL_min(data->Length, sizeof(WAVEFORMATEXTENSIBLE)));
spec.channels = (Uint8)fmt->Format.nChannels;
spec.freq = fmt->Format.nSamplesPerSec;
spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt);
} }
WASAPI_AddDevice(this->iscapture, utf8dev, &fmt, info->Id->Data()); LPWSTR devid = SDL_wcsdup(devid);
if (devid) {
SDL_AddAudioDevice(this->iscapture, utf8dev, spec.channels ? &spec : NULL, devid);
}
SDL_free(utf8dev); SDL_free(utf8dev);
} }
} }
@ -164,7 +175,7 @@ void SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher ^ sender, DeviceI
void SDL_WasapiDeviceEventHandler::OnDeviceRemoved(DeviceWatcher ^ sender, DeviceInformationUpdate ^ info) void SDL_WasapiDeviceEventHandler::OnDeviceRemoved(DeviceWatcher ^ sender, DeviceInformationUpdate ^ info)
{ {
SDL_assert(sender == this->watcher); SDL_assert(sender == this->watcher);
WASAPI_RemoveDevice(this->iscapture, info->Id->Data()); WASAPI_DisconnectDevice(FindWinRTAudioDevice(info->Id->Data()));
} }
void SDL_WasapiDeviceEventHandler::OnDeviceUpdated(DeviceWatcher ^ sender, DeviceInformationUpdate ^ args) void SDL_WasapiDeviceEventHandler::OnDeviceUpdated(DeviceWatcher ^ sender, DeviceInformationUpdate ^ args)
@ -175,19 +186,30 @@ void SDL_WasapiDeviceEventHandler::OnDeviceUpdated(DeviceWatcher ^ sender, Devic
void SDL_WasapiDeviceEventHandler::OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args) void SDL_WasapiDeviceEventHandler::OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args)
{ {
SDL_assert(sender == this->watcher); SDL_assert(sender == this->watcher);
SDL_PostSemaphore(this->completed); if (this->completed_semaphore) {
SDL_PostSemaphore(this->completed_semaphore);
}
} }
void SDL_WasapiDeviceEventHandler::OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args) void SDL_WasapiDeviceEventHandler::OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args)
{ {
SDL_assert(!this->iscapture); SDL_assert(!this->iscapture);
SDL_AtomicAdd(&SDL_IMMDevice_DefaultPlaybackGeneration, 1); SDL_DefaultAudioDeviceChanged(FindWinRTAudioDevice(args->Id->Data()));
} }
void SDL_WasapiDeviceEventHandler::OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args) void SDL_WasapiDeviceEventHandler::OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args)
{ {
SDL_assert(this->iscapture); SDL_assert(this->iscapture);
SDL_AtomicAdd(&SDL_IMMDevice_DefaultCaptureGeneration, 1); SDL_DefaultAudioDeviceChanged(FindWinRTAudioDevice(args->Id->Data()));
}
void SDL_WasapiDeviceEventHandler::WaitForCompletion()
{
if (this->completed_semaphore) {
SDL_WaitSemaphore(this->completed_semaphore);
SDL_DestroySemaphore(this->completed_semaphore);
this->completed_semaphore = nullptr;
}
} }
static SDL_WasapiDeviceEventHandler *playback_device_event_handler; static SDL_WasapiDeviceEventHandler *playback_device_event_handler;
@ -195,54 +217,63 @@ static SDL_WasapiDeviceEventHandler *capture_device_event_handler;
int WASAPI_PlatformInit(void) int WASAPI_PlatformInit(void)
{ {
SDL_AtomicSet(&SDL_IMMDevice_DefaultPlaybackGeneration, 1);
SDL_AtomicSet(&SDL_IMMDevice_DefaultCaptureGeneration, 1);
return 0; return 0;
} }
void WASAPI_PlatformDeinit(void) void WASAPI_PlatformDeinit(void)
{ {
DevIdList *devidlist;
DevIdList *next;
delete playback_device_event_handler; delete playback_device_event_handler;
playback_device_event_handler = nullptr; playback_device_event_handler = nullptr;
delete capture_device_event_handler; delete capture_device_event_handler;
capture_device_event_handler = nullptr; capture_device_event_handler = nullptr;
for (devidlist = deviceid_list; devidlist; devidlist = next) {
next = devidlist->next;
SDL_free(devidlist->str);
SDL_free(devidlist);
}
deviceid_list = NULL;
} }
void WASAPI_EnumerateEndpoints(void) void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{ {
Platform::String ^ defdevid;
// DeviceWatchers will fire an Added event for each existing device at // DeviceWatchers will fire an Added event for each existing device at
// startup, so we don't need to enumerate them separately before // startup, so we don't need to enumerate them separately before
// listening for updates. // listening for updates.
playback_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_FALSE); playback_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_FALSE);
playback_device_event_handler->WaitForCompletion();
defdevid = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
if (defdevid) {
*default_output = FindWinRTAudioDevice(defdevid->Data());
}
capture_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_TRUE); capture_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_TRUE);
SDL_WaitSemaphore(playback_device_event_handler->completed); capture_device_event_handler->WaitForCompletion();
SDL_WaitSemaphore(capture_device_event_handler->completed); defdevid = MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default)
if (defdevid) {
*default_capture = FindWinRTAudioDevice(defdevid->Data());
}
} }
struct SDL_WasapiActivationHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>, FtmBase, IActivateAudioInterfaceCompletionHandler> class SDL_WasapiActivationHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>, FtmBase, IActivateAudioInterfaceCompletionHandler>
{ {
SDL_WasapiActivationHandler() : device(nullptr) {} public:
STDMETHOD(ActivateCompleted) SDL_WasapiActivationHandler() : completion_semaphore(SDL_CreateSemaphore(0)) { SDL_assert(completion_semaphore != NULL); }
(IActivateAudioInterfaceAsyncOperation *operation); STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation *operation);
SDL_AudioDevice *device; void WaitForCompletion();
private:
SDL_Semaphore *completion_semaphore;
}; };
void SDL_WasapiActivationHandler::WaitForCompletion()
{
if (completion_semaphore) {
SDL_WaitSemaphore(completion_semaphore);
SDL_DestroySemaphore(completion_semaphore);
completion_semaphore = NULL;
}
}
HRESULT HRESULT
SDL_WasapiActivationHandler::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *async) SDL_WasapiActivationHandler::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *async)
{ {
// 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. // 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.
SDL_AtomicSet(&device->hidden->just_activated, 1); SDL_PostSemaphore(completion_semaphore);
WASAPI_UnrefDevice(device);
return S_OK; return S_OK;
} }
@ -251,24 +282,10 @@ void WASAPI_PlatformDeleteActivationHandler(void *handler)
((SDL_WasapiActivationHandler *)handler)->Release(); ((SDL_WasapiActivationHandler *)handler)->Release();
} }
int WASAPI_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) int WASAPI_ActivateDevice(SDL_AudioDevice *device)
{ {
return SDL_Unsupported(); LPCWSTR devid = (LPCWSTR) device->handle;
} SDL_assert(devid != NULL);
int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery)
{
LPCWSTR devid = _this->hidden->devid;
Platform::String ^ defdevid;
if (devid == nullptr) {
defdevid = _this->iscapture ? MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default) : MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
if (defdevid) {
devid = defdevid->Data();
}
}
SDL_AtomicSet(&_this->hidden->just_activated, 0);
ComPtr<SDL_WasapiActivationHandler> handler = Make<SDL_WasapiActivationHandler>(); ComPtr<SDL_WasapiActivationHandler> handler = Make<SDL_WasapiActivationHandler>();
if (handler == nullptr) { if (handler == nullptr) {
@ -276,10 +293,8 @@ int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery)
} }
handler.Get()->AddRef(); // we hold a reference after ComPtr destructs on return, causing a Release, and Release ourselves in WASAPI_PlatformDeleteActivationHandler(), etc. handler.Get()->AddRef(); // we hold a reference after ComPtr destructs on return, causing a Release, and Release ourselves in WASAPI_PlatformDeleteActivationHandler(), etc.
handler.Get()->device = _this; device->hidden->activation_handler = handler.Get();
_this->hidden->activation_handler = handler.Get();
WASAPI_RefDevice(_this); /* completion handler will unref it. */
IActivateAudioInterfaceAsyncOperation *async = nullptr; IActivateAudioInterfaceAsyncOperation *async = nullptr;
const HRESULT ret = ActivateAudioInterfaceAsync(devid, __uuidof(IAudioClient), nullptr, handler.Get(), &async); const HRESULT ret = ActivateAudioInterfaceAsync(devid, __uuidof(IAudioClient), nullptr, handler.Get(), &async);
@ -288,21 +303,11 @@ int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery)
async->Release(); async->Release();
} }
handler.Get()->Release(); handler.Get()->Release();
WASAPI_UnrefDevice(_this);
return WIN_SetErrorFromHRESULT("WASAPI can't activate requested audio endpoint", ret); return WIN_SetErrorFromHRESULT("WASAPI can't activate requested audio endpoint", ret);
} }
/* Spin until the async operation is complete. // !!! FIXME: the problems in SDL2 that needed this to be synchronous are _probably_ solved by SDL3, and this can block indefinitely if a user prompt is shown to get permission to use a microphone.
* If we don't PrepDevice before leaving this function, the bug list gets LONG: handler.WaitForCompletion(); // block here until we have an answer, so this is synchronous to us after all.
* - device.spec is not filled with the correct information
* - The 'obtained' spec will be wrong for ALLOW_CHANGE properties
* - SDL_AudioStreams will/will not be allocated at the right time
* - SDL_assert(device->callbackspec.size == device->spec.size) will fail
* - When the assert is ignored, skipping or a buffer overflow will occur
*/
while (!SDL_AtomicCAS(&_this->hidden->just_activated, 1, 0)) {
SDL_Delay(1);
}
HRESULT activateRes = S_OK; HRESULT activateRes = S_OK;
IUnknown *iunknown = nullptr; IUnknown *iunknown = nullptr;
@ -314,114 +319,31 @@ int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery)
return WIN_SetErrorFromHRESULT("Failed to activate WASAPI device", activateRes); return WIN_SetErrorFromHRESULT("Failed to activate WASAPI device", activateRes);
} }
iunknown->QueryInterface(IID_PPV_ARGS(&_this->hidden->client)); iunknown->QueryInterface(IID_PPV_ARGS(&device->hidden->client));
if (!_this->hidden->client) { if (!device->hidden->client) {
return SDL_SetError("Failed to query WASAPI client interface"); return SDL_SetError("Failed to query WASAPI client interface");
} }
if (WASAPI_PrepDevice(_this, isrecovery) == -1) { if (WASAPI_PrepDevice(device) == -1) {
return -1; return -1;
} }
return 0; return 0;
} }
void WASAPI_PlatformThreadInit(SDL_AudioDevice *_this) void WASAPI_PlatformThreadInit(SDL_AudioDevice *device)
{ {
// !!! FIXME: set this thread to "Pro Audio" priority. // !!! FIXME: set this thread to "Pro Audio" priority.
} }
void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *_this) void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device)
{ {
// !!! FIXME: set this thread to "Pro Audio" priority. // !!! FIXME: set this thread to "Pro Audio" priority.
} }
/* Everything below was copied from SDL_wasapi.c, before it got moved to SDL_immdevice.c! */ void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device)
static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
extern "C" SDL_AudioFormat
WaveFormatToSDLFormat(WAVEFORMATEX *waveformat)
{ {
if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) { SDL_free(device->handle);
return SDL_AUDIO_F32SYS;
} else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 16)) {
return SDL_AUDIO_S16SYS;
} else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 32)) {
return SDL_AUDIO_S32SYS;
} else if (waveformat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
const WAVEFORMATEXTENSIBLE *ext = (const WAVEFORMATEXTENSIBLE *)waveformat;
if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
return SDL_AUDIO_F32SYS;
} else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 16)) {
return SDL_AUDIO_S16SYS;
} else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
return SDL_AUDIO_S32SYS;
}
}
return 0;
}
static void WASAPI_RemoveDevice(const SDL_bool iscapture, LPCWSTR devid)
{
DevIdList *i;
DevIdList *next;
DevIdList *prev = NULL;
for (i = deviceid_list; i; i = next) {
next = i->next;
if (SDL_wcscmp(i->str, devid) == 0) {
if (prev) {
prev->next = next;
} else {
deviceid_list = next;
}
SDL_RemoveAudioDevice(iscapture, i->str);
SDL_free(i->str);
SDL_free(i);
} else {
prev = i;
}
}
}
static void WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid)
{
DevIdList *devidlist;
SDL_AudioSpec spec;
/* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever).
In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for
phones and tablets, where you might have an internal speaker and a headphone jack and expect both to be
available and switch automatically. (!!! FIXME...?) */
/* see if we already have this one. */
for (devidlist = deviceid_list; devidlist; devidlist = devidlist->next) {
if (SDL_wcscmp(devidlist->str, devid) == 0) {
return; /* we already have this. */
}
}
devidlist = (DevIdList *)SDL_malloc(sizeof(*devidlist));
if (devidlist == NULL) {
return; /* oh well. */
}
devid = SDL_wcsdup(devid);
if (!devid) {
SDL_free(devidlist);
return; /* oh well. */
}
devidlist->str = (WCHAR *)devid;
devidlist->next = deviceid_list;
deviceid_list = devidlist;
SDL_zero(spec);
spec.channels = (Uint8)fmt->Format.nChannels;
spec.freq = fmt->Format.nSamplesPerSec;
spec.format = WaveFormatToSDLFormat((WAVEFORMATEX *)fmt);
SDL_AddAudioDevice(iscapture, devname, &spec, (void *)devid);
} }
#endif // SDL_AUDIO_DRIVER_WASAPI && defined(__WINRT__) #endif // SDL_AUDIO_DRIVER_WASAPI && defined(__WINRT__)

View File

@ -147,7 +147,7 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const SDL_bool iscapture, const char *
SDL_zero(spec); SDL_zero(spec);
spec.channels = (Uint8)fmt->Format.nChannels; spec.channels = (Uint8)fmt->Format.nChannels;
spec.freq = fmt->Format.nSamplesPerSec; spec.freq = fmt->Format.nSamplesPerSec;
spec.format = WaveFormatToSDLFormat((WAVEFORMATEX *)fmt); spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt);
device = SDL_AddAudioDevice(iscapture, devname, &spec, handle); device = SDL_AddAudioDevice(iscapture, devname, &spec, handle);
} }
@ -164,7 +164,6 @@ typedef struct SDLMMNotificationClient
{ {
const IMMNotificationClientVtbl *lpVtbl; const IMMNotificationClientVtbl *lpVtbl;
SDL_AtomicInt refcount; SDL_AtomicInt refcount;
SDL_bool useguid;
} SDLMMNotificationClient; } SDLMMNotificationClient;
static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_QueryInterface(IMMNotificationClient *client, REFIID iid, void **ppv) static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_QueryInterface(IMMNotificationClient *client, REFIID iid, void **ppv)
@ -370,7 +369,7 @@ static void EnumerateEndpointsForFlow(const SDL_bool iscapture, SDL_AudioDevice
GetMMDeviceInfo(immdevice, &devname, &fmt, &dsoundguid); GetMMDeviceInfo(immdevice, &devname, &fmt, &dsoundguid);
if (devname) { if (devname) {
SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(iscapture, devname, &fmt, devid, &dsoundguid); SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(iscapture, devname, &fmt, devid, &dsoundguid);
if (default_device && default_devid && SDL_wcscmp(default_devid, devid)) { if (default_device && default_devid && SDL_wcscmp(default_devid, devid) == 0) {
*default_device = sdldevice; *default_device = sdldevice;
} }
SDL_free(devname); SDL_free(devname);
@ -395,7 +394,7 @@ void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_Audi
IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)&notification_client); IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)&notification_client);
} }
SDL_AudioFormat WaveFormatToSDLFormat(WAVEFORMATEX *waveformat) SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat)
{ {
if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) { if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) {
return SDL_AUDIO_F32SYS; return SDL_AUDIO_F32SYS;

View File

@ -35,6 +35,6 @@ void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_Audi
LPGUID SDL_IMMDevice_GetDirectSoundGUID(SDL_AudioDevice *device); LPGUID SDL_IMMDevice_GetDirectSoundGUID(SDL_AudioDevice *device);
LPCWSTR SDL_IMMDevice_GetDevID(SDL_AudioDevice *device); LPCWSTR SDL_IMMDevice_GetDevID(SDL_AudioDevice *device);
void SDL_IMMDevice_FreeDeviceHandle(SDL_AudioDevice *device); void SDL_IMMDevice_FreeDeviceHandle(SDL_AudioDevice *device);
SDL_AudioFormat WaveFormatToSDLFormat(WAVEFORMATEX *waveformat); SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat);
#endif /* SDL_IMMDEVICE_H */ #endif /* SDL_IMMDEVICE_H */

View File

@ -39,7 +39,7 @@ static SDL_AudioStream *stream;
static void fillerup(void) static void fillerup(void)
{ {
if (SDL_GetAudioStreamAvailable(stream) < (wave.soundlen / 2)) { if (SDL_GetAudioStreamAvailable(stream) < (int) ((wave.soundlen / 2))) {
SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen); SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen);
} }
} }