From 905c4fff5bb88b200701ca8ff3a8d4dff802182d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 12 May 2023 23:37:02 -0400 Subject: [PATCH 001/138] audio: First shot at the SDL3 audio subsystem redesign! This is a work in progress! (and this commit will probably get force-pushed over at some point). --- CMakeLists.txt | 6 + docs/README-migration.md | 105 +- include/SDL3/SDL_audio.h | 1010 ++++------- include/SDL3/SDL_test_common.h | 4 +- src/audio/SDL_audio.c | 2248 +++++++++++-------------- src/audio/SDL_audio_c.h | 55 +- src/audio/SDL_audiocvt.c | 109 +- src/audio/SDL_audiodev.c | 2 +- src/audio/SDL_sysaudio.h | 251 ++- src/audio/SDL_wave.c | 66 +- src/audio/disk/SDL_diskaudio.c | 88 +- src/audio/dummy/SDL_dummyaudio.c | 31 +- src/audio/dummy/SDL_dummyaudio.h | 2 + src/audio/pulseaudio/SDL_pulseaudio.c | 207 ++- src/dynapi/SDL_dynapi.sym | 54 +- src/dynapi/SDL_dynapi_overrides.h | 54 +- src/dynapi/SDL_dynapi_procs.h | 54 +- src/test/SDL_test_common.c | 31 +- test/loopwave.c | 61 +- 19 files changed, 1974 insertions(+), 2464 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e5a28b55f..de86f90c10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -353,6 +353,12 @@ set_option(SDL_CLANG_TIDY "Run clang-tidy static analysis" OFF) set(SDL_VENDOR_INFO "" CACHE STRING "Vendor name and/or version to add to SDL_REVISION") +set(SDL_OSS OFF) +set(SDL_ALSA OFF) +set(SDL_JACK OFF) +set(SDL_PIPEWIRE OFF) +set(SDL_SNDIO OFF) + cmake_dependent_option(SDL_SHARED "Build a shared version of the library" ${SDL_SHARED_DEFAULT} ${SDL_SHARED_AVAILABLE} OFF) option(SDL_STATIC "Build a static version of the library" ${SDL_STATIC_DEFAULT}) option(SDL_TEST_LIBRARY "Build the SDL3_test library" ON) diff --git a/docs/README-migration.md b/docs/README-migration.md index 19b30414b8..0a3c0fd5ed 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -53,13 +53,100 @@ The following structures have been renamed: ## SDL_audio.h +The audio subsystem in SDL3 is dramatically different than SDL2. There is no longer an audio callback; instead you bind SDL_AudioStreams to devices. + +The SDL 1.2 audio compatibility API has also been removed, as it was a simplified version of the audio callback interface. + +If your app depends on the callback method, you can use the single-header library at https://github.com/libsdl-org/SDL3_audio_callback (to be written!) to simulate it on top of SDL3's new API. + +In SDL2, you might have done something like this to play audio: + +```c + void SDLCALL MyAudioCallback(void *userdata, Uint8 * stream, int len) + { + /* calculate a little more audio here, maybe using `userdata`, write it to `stream` */ + } + + /* ...somewhere near startup... */ + SDL_AudioSpec my_desired_audio_format; + SDL_zero(my_desired_audio_format); + my_desired_audio_format.format = AUDIO_S16; + my_desired_audio_format.channels = 2; + my_desired_audio_format.freq = 44100; + my_desired_audio_format.samples = 1024; + my_desired_audio_format.callback = MyAudioCallback; + my_desired_audio_format.userdata = &my_audio_callback_user_data; + SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(NULL, 0, &my_desired_audio_format, NULL, 0); + SDL_PauseAudioDevice(my_audio_device, 0); +``` + +in SDL3: + +```c + /* ...somewhere near startup... */ + my_desired_audio_format.callback = MyAudioCallback; /* etc */ + SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(0, SDL_AUDIO_S16, 2, 44100); + SDL_AudioSteam *stream = SDL_CreateAndBindAudioStream(my_audio_device, SDL_AUDIO_S16, 2, 44100); + + /* ...in your main loop... */ + /* calculate a little more audio into `buf`, add it to `stream` */ + SDL_PutAudioStreamData(stream, buf, buflen); +``` + SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_AUDIO, which will properly refcount the subsystems. You can choose a specific audio driver using SDL_AUDIO_DRIVER hint. -SDL_PauseAudioDevice() is only used to pause audio playback. Use SDL_PlayAudioDevice() to start playing audio. +The `SDL_AUDIO_ALLOW_*` symbols have been removed; now one may request the format they desire from the audio device, but ultimately SDL_AudioStream will manage the difference. One can use SDL_GetAudioDeviceFormat() to see what the final format is, if any "allowed" changes should be accomodated by the app. + +SDL_AudioDeviceID no longer represents an open audio device's handle, it's now the device's instance ID that the device owns as long as it exists on the system. The separation between device instances and device indexes is gone. + +Devices are opened by device instance ID, and a new handle is not generated by the open operation; instead, opens of the same device instance are reference counted. This allows any device to be opened multiple times, possibly by unrelated pieces of code. + +Devices are not opened by an arbitrary string name anymore, but by device instance ID (or 0 to request a reasonable default, like a NULL string in SDL2). In SDL2, the string was used to open both a standard list of system devices, but also allowed for arbitrary devices, such as hostnames of network sound servers. In SDL3, many of the backends that supported arbitrary device names are obsolete and have been removed; of those that remain, arbitrary devices will be opened with a device ID of 0 and an SDL_hint, so specific end-users can set an environment variable to fit their needs and apps don't have to concern themselves with it. + +Many functions that would accept a device index and an `iscapture` parameter now just take an SDL_AudioDeviceID, as they are unique across all devices, instead of separate indices into output and capture device lists. + +Rather than iterating over audio devices using a device index, there is a new function, SDL_GetAudioDevices(), to get the current list of devices, and new functions to get information about devices from their instance ID: + +```c +{ + if (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0) { + int i, num_devices; + SDL_AudioDeviceID *devices = SDL_GetAudioDevices(/*iscapture=*/SDL_FALSE, &num_devices); + if (devices) { + for (i = 0; i < num_devices; ++i) { + SDL_AudioDeviceID instance_id = devices[i]; + char *name = SDL_GetAudioDeviceName(instance_id); + SDL_Log("AudioDevice %" SDL_PRIu32 ": %s\n", instance_id, name); + SDL_free(name); + } + SDL_free(devices); + } + SDL_QuitSubSystem(SDL_INIT_AUDIO); + } +} +``` + +SDL_LockAudioDevice() and SDL_UnlockAudioDevice() have been removed, since there is no callback in another thread to protect. Internally, SDL's audio subsystem and SDL_AudioStream maintain their own locks internally, so audio streams are safe to use from any thread. + +SDL_PauseAudioDevice() has been removed; unbinding an audio stream from a device with SDL_UnbindAudioStream() will leave the stream still containing any unconsumed data, effectively pausing it until rebound with SDL_BindAudioStream() again. Devices act like they are "paused" after open, like SDL2, until a stream is bound to it. + +SDL_GetAudioDeviceStatus() has been removed; there is no more concept of "pausing" a device, just whether streams are bound, so please keep track of your audio streams! + +SDL_QueueAudio(), SDL_DequeueAudio, and SDL_ClearQueuedAudio and SDL_GetQueuedAudioSize() have been removed; an SDL_AudioStream bound to a device provides the exact same functionality. + +APIs that use channel counts used to use a Uint8 for the channel; now they use int. + +SDL_AudioSpec has been removed; things that used it have simply started taking separate arguments for format, channel, and sample rate. SDL_GetSilenceValueForFormat() can provide the information from the SDL_AudioSpec's `silence` field. The other SDL_AudioSpec fields aren't relevant anymore. + +SDL_GetAudioDeviceSpec() is removed; use SDL_GetAudioDeviceFormat() instead. + +SDL_MixAudio() has been removed, as it relied on legacy SDL 1.2 quirks; SDL_MixAudioFormat() remains and offers the same functionality. + +SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_AUDIO, which will properly refcount the subsystems. You can choose a specific audio driver using SDL_AUDIO_DRIVER hint. SDL_FreeWAV has been removed and calls can be replaced with SDL_free. -SDL_AudioCVT interface is removed, SDL_AudioStream interface or SDL_ConvertAudioSamples() helper function can be used. +SDL_AudioCVT interface has been removed, the SDL_AudioStream interface (for audio supplied in pieces) or the new SDL_ConvertAudioSamples() function (for converting a complete audio buffer in one call) can be used instead. Code that used to look like this: ```c @@ -103,6 +190,8 @@ If you need to convert U16 audio data to a still-supported format at runtime, th } ``` +All remaining `AUDIO_*` symbols have been renamed to `SDL_AUDIO_*` for API consistency, but othewise are identical in value and usage. + In SDL2, SDL_AudioStream would convert/resample audio data during input (via SDL_AudioStreamPut). In SDL3, it does this work when requesting audio (via SDL_GetAudioStreamData, which would have been SDL_AudioStreamPut in SDL2. The way you use an AudioStream is roughly the same, just be aware that the workload moved to a different phase. In SDL2, SDL_AudioStreamAvailable() returns 0 if passed a NULL stream. In SDL3, the equivalent SDL_GetAudioStreamAvailable() call returns -1 and sets an error string, which matches other audiostream APIs' behavior. @@ -118,17 +207,25 @@ The following functions have been renamed: The following functions have been removed: +* SDL_GetNumAudioDevices() +* SDL_GetAudioDeviceSpec() * SDL_ConvertAudio() * SDL_BuildAudioCVT() * SDL_OpenAudio() * SDL_CloseAudio() * SDL_PauseAudio() +* SDL_PauseAudioDevice * SDL_GetAudioStatus() +* SDL_GetAudioDeviceStatus() * SDL_LockAudio() +* SDL_LockAudioDevice() * SDL_UnlockAudio() +* SDL_UnlockAudioDevice() * SDL_MixAudio() - -Use the SDL_AudioDevice functions instead. +* SDL_QueueAudio() +* SDL_DequeueAudio() +* SDL_ClearAudioQueue() +* SDL_GetQueuedAudioSize() The following symbols have been renamed: * AUDIO_F32 => SDL_AUDIO_F32 diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 1dd39fc6f9..43ecc1ea12 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -43,6 +43,17 @@ extern "C" { #endif +/* + * For multi-channel audio, the default SDL channel mapping is: + * 2: FL FR (stereo) + * 3: FL FR LFE (2.1 surround) + * 4: FL FR BL BR (quad) + * 5: FL FR LFE BL BR (4.1 surround) + * 6: FL FR FC LFE SL SR (5.1 surround - last two can also be BL BR) + * 7: FL FR FC LFE BC SL SR (6.1 surround) + * 8: FL FR FC LFE BL BR SL SR (7.1 surround) + */ + /** * \brief Audio format flags. * @@ -128,62 +139,19 @@ typedef Uint16 SDL_AudioFormat; #endif /* @} */ -/** - * \name Allow change flags - * - * Which audio format changes are allowed when opening a device. - */ -/* @{ */ -#define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001 -#define SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002 -#define SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004 -#define SDL_AUDIO_ALLOW_SAMPLES_CHANGE 0x00000008 -#define SDL_AUDIO_ALLOW_ANY_CHANGE (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE) -/* @} */ - /* @} *//* Audio flags */ -/** - * This function is called when the audio device needs more data. - * - * \param userdata An application-specific parameter saved in - * the SDL_AudioSpec structure - * \param stream A pointer to the audio data buffer. - * \param len The length of that buffer in bytes. - * - * Once the callback returns, the buffer will no longer be valid. - * Stereo samples are stored in a LRLRLR ordering. - * - * You can choose to avoid callbacks and use SDL_QueueAudio() instead, if - * you like. Just open your audio device with a NULL callback. +/* SDL_AudioStream is an audio conversion interface. + - It can handle resampling data in chunks without generating + artifacts, when it doesn't have the complete buffer available. + - It can handle incoming data in any variable size. + - It can handle input/output format changes on the fly. + - You push data as you have it, and pull it when you need it + - It can also function as a basic audio data queue even if you + just have sound that needs to pass from one place to another. */ -typedef void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream, - int len); - -/** - * The calculated values in this structure are calculated by SDL_OpenAudioDevice(). - * - * For multi-channel audio, the default SDL channel mapping is: - * 2: FL FR (stereo) - * 3: FL FR LFE (2.1 surround) - * 4: FL FR BL BR (quad) - * 5: FL FR LFE BL BR (4.1 surround) - * 6: FL FR FC LFE SL SR (5.1 surround - last two can also be BL BR) - * 7: FL FR FC LFE BC SL SR (6.1 surround) - * 8: FL FR FC LFE BL BR SL SR (7.1 surround) - */ -typedef struct SDL_AudioSpec -{ - int freq; /**< DSP frequency -- samples per second */ - SDL_AudioFormat format; /**< Audio data format */ - Uint8 channels; /**< Number of channels: 1 mono, 2 stereo */ - Uint8 silence; /**< Audio buffer silence value (calculated) */ - Uint16 samples; /**< Audio buffer size in sample FRAMES (total samples divided by channel count) */ - Uint16 padding; /**< Necessary for some compile environments */ - Uint32 size; /**< Audio buffer size in bytes (calculated) */ - SDL_AudioCallback callback; /**< Callback that feeds the audio device (NULL to use SDL_QueueAudio()). */ - void *userdata; /**< Userdata passed to callback (ignored for NULL callbacks). */ -} SDL_AudioSpec; +struct SDL_AudioStream; /* this is opaque to the outside world. */ +typedef struct SDL_AudioStream SDL_AudioStream; /* Function prototypes */ @@ -213,6 +181,8 @@ typedef struct SDL_AudioSpec * * \since This function is available since SDL 3.0.0. * + * \threadsafety It is safe to call this function from any thread. + * * \sa SDL_GetAudioDriver */ extern DECLSPEC int SDLCALL SDL_GetNumAudioDrivers(void); @@ -233,6 +203,8 @@ extern DECLSPEC int SDLCALL SDL_GetNumAudioDrivers(void); * \returns the name of the audio driver at the requested index, or NULL if an * invalid index was specified. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_GetNumAudioDrivers @@ -252,445 +224,278 @@ extern DECLSPEC const char *SDLCALL SDL_GetAudioDriver(int index); * \returns the name of the current audio driver or NULL if no driver has been * initialized. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. */ extern DECLSPEC const char *SDLCALL SDL_GetCurrentAudioDriver(void); /** - * SDL Audio Device IDs. + * SDL Audio Device instance IDs. */ typedef Uint32 SDL_AudioDeviceID; + /** - * Get the number of built-in audio devices. + * Get a list of currently-connected audio output devices. * - * This function is only valid after successfully initializing the audio - * subsystem. + * This returns of list of available devices that play sound, perhaps + * to speakers or headphones ("output" devices). If you want devices + * that record audio, like a microphone ("capture" devices), use + * SDL_GetAudioCaptureDevices() instead. * - * Note that audio capture support is not implemented as of SDL 2.0.4, so the - * `iscapture` parameter is for future expansion and should always be zero for - * now. - * - * This function will return -1 if an explicit list of devices can't be - * determined. Returning -1 is not an error. For example, if SDL is set up to - * talk to a remote audio server, it can't list every one available on the - * Internet, but it will still allow a specific host to be specified in - * SDL_OpenAudioDevice(). - * - * In many common cases, when this function returns a value <= 0, it can still - * successfully open the default device (NULL for first argument of - * SDL_OpenAudioDevice()). - * - * This function may trigger a complete redetect of available hardware. It - * should not be called for each iteration of a loop, but rather once at the - * start of a loop: - * - * ```c - * // Don't do this: - * for (int i = 0; i < SDL_GetNumAudioDevices(0); i++) - * - * // do this instead: - * const int count = SDL_GetNumAudioDevices(0); - * for (int i = 0; i < count; ++i) { do_something_here(); } - * ``` - * - * \param iscapture zero to request playback devices, non-zero to request - * recording devices - * \returns the number of available devices exposed by the current driver or - * -1 if an explicit list of devices can't be determined. A return - * value of -1 does not necessarily mean an error condition. + * \param count a pointer filled in with the number of devices returned + * \returns a 0 terminated array of device instance IDs which should be + * freed with SDL_free(), or NULL on error; call SDL_GetError() for + * more details. * * \since This function is available since SDL 3.0.0. * - * \sa SDL_GetAudioDeviceName + * \threadsafety It is safe to call this function from any thread. + * * \sa SDL_OpenAudioDevice */ -extern DECLSPEC int SDLCALL SDL_GetNumAudioDevices(int iscapture); +extern DECLSPEC SDL_AudioDeviceID *SDLCALL SDL_GetAudioOutputDevices(int *count); + +/** + * Get a list of currently-connected audio capture devices. + * + * This returns of list of available devices that record audio, like a + * microphone ("capture" devices). If you want devices + * that play sound, perhaps to speakers or headphones ("output" devices), + * use SDL_GetAudioOutputDevices() instead. + * + * \param count a pointer filled in with the number of devices returned + * \returns a 0 terminated array of device instance IDs which should be + * freed with SDL_free(), or NULL on error; call SDL_GetError() for + * more details. + * + * \since This function is available since SDL 3.0.0. + * + * \threadsafety It is safe to call this function from any thread. + * + * \sa SDL_OpenAudioDevice + */ +extern DECLSPEC SDL_AudioDeviceID *SDLCALL SDL_GetAudioCaptureDevices(int *count); /** * Get the human-readable name of a specific audio device. * - * This function is only valid after successfully initializing the audio - * subsystem. The values returned by this function reflect the latest call to - * SDL_GetNumAudioDevices(); re-call that function to redetect available - * hardware. + * The string returned by this function is UTF-8 encoded. The caller should + * call SDL_free on the return value when done with it. * - * The string returned by this function is UTF-8 encoded, read-only, and - * managed internally. You are not to free it. If you need to keep the string - * for any length of time, you should make your own copy of it, as it will be - * invalid next time any of several other SDL functions are called. - * - * \param index the index of the audio device; valid values range from 0 to - * SDL_GetNumAudioDevices() - 1 - * \param iscapture non-zero to query the list of recording devices, zero to - * query the list of output devices. - * \returns the name of the audio device at the requested index, or NULL on - * error. + * \param devid the instance ID of the device to query. + * \returns the name of the audio device, or NULL on error. * * \since This function is available since SDL 3.0.0. * + * \threadsafety It is safe to call this function from any thread. + * * \sa SDL_GetNumAudioDevices * \sa SDL_GetDefaultAudioInfo */ -extern DECLSPEC const char *SDLCALL SDL_GetAudioDeviceName(int index, - int iscapture); +extern DECLSPEC char *SDLCALL SDL_GetAudioDeviceName(SDL_AudioDeviceID devid); /** - * Get the preferred audio format of a specific audio device. + * Get the current audio format of a specific audio device. * - * This function is only valid after a successfully initializing the audio - * subsystem. The values returned by this function reflect the latest call to - * SDL_GetNumAudioDevices(); re-call that function to redetect available - * hardware. + * For an opened device, this will report the format the device is + * currently using. If the device isn't yet opened, this will report + * the device's preferred format (or a reasonable default if this + * can't be determined). * - * `spec` will be filled with the sample rate, sample format, and channel - * count. - * - * \param index the index of the audio device; valid values range from 0 to - * SDL_GetNumAudioDevices() - 1 - * \param iscapture non-zero to query the list of recording devices, zero to - * query the list of output devices. - * \param spec The SDL_AudioSpec to be initialized by this function. + * \param devid the instance ID of the device to query. + * \param fmt On return, will be set to the device's data format. Can be NULL. + * \param channels On return, will be set to the device's channel count. Can be NULL. + * \param freq On return, will be set to the device's sample rate. Can be NULL. * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GetNumAudioDevices - * \sa SDL_GetDefaultAudioInfo - */ -extern DECLSPEC int SDLCALL SDL_GetAudioDeviceSpec(int index, - int iscapture, - SDL_AudioSpec *spec); - - -/** - * Get the name and preferred format of the default audio device. - * - * Some (but not all!) platforms have an isolated mechanism to get information - * about the "default" device. This can actually be a completely different - * device that's not in the list you get from SDL_GetAudioDeviceSpec(). It can - * even be a network address! (This is discussed in SDL_OpenAudioDevice().) - * - * As a result, this call is not guaranteed to be performant, as it can query - * the sound server directly every time, unlike the other query functions. You - * should call this function sparingly! - * - * `spec` will be filled with the sample rate, sample format, and channel - * count, if a default device exists on the system. If `name` is provided, - * will be filled with either a dynamically-allocated UTF-8 string or NULL. - * - * \param name A pointer to be filled with the name of the default device (can - * be NULL). Please call SDL_free() when you are done with this - * pointer! - * \param spec The SDL_AudioSpec to be initialized by this function. - * \param iscapture non-zero to query the default recording device, zero to - * query the default output device. - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. + * \threadsafety It is safe to call this function from any thread. * * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GetAudioDeviceName - * \sa SDL_GetAudioDeviceSpec - * \sa SDL_OpenAudioDevice */ -extern DECLSPEC int SDLCALL SDL_GetDefaultAudioInfo(char **name, - SDL_AudioSpec *spec, - int iscapture); +extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioFormat *fmt, int *channels, int *freq); /** * Open a specific audio device. * - * Passing in a `device` name of NULL requests the most reasonable default. - * The `device` name is a UTF-8 string reported by SDL_GetAudioDeviceName(), - * but some drivers allow arbitrary and driver-specific strings, such as a - * hostname/IP address for a remote audio server, or a filename in the - * diskaudio driver. + * Passing in a `devid` name of zero requests the most reasonable default. * - * An opened audio device starts out paused, and should be enabled for playing - * by calling SDL_PlayAudioDevice(devid) when you are ready for your audio - * callback function to be called. Since the audio driver may modify the - * requested size of the audio buffer, you should allocate any local mixing - * buffers after you open the audio device. + * You can open both output and capture devices through this function. + * Output devices will take data from bound audio streams, mix it, and + * send it to the hardware. Capture devices will feed any bound audio + * streams with a copy of any incoming data. * - * The audio callback runs in a separate thread in most cases; you can prevent - * race conditions between your callback and other threads without fully - * pausing playback with SDL_LockAudioDevice(). For more information about the - * callback, see SDL_AudioSpec. + * An opened audio device starts out with no audio streams bound. To + * start audio playing, bind a stream and supply audio data to it. Unlike + * SDL2, there is no audio callback; you only bind audio streams and + * make sure they have data flowing into them. * - * Managing the audio spec via 'desired' and 'obtained': + * You may request a specific format for the audio device, but there is + * no promise the device will honor that request for several reasons. As + * such, it's only meant to be a hint as to what data your app will + * provide. Audio streams will accept data in whatever format you specify and + * manage conversion for you as appropriate. SDL_GetAudioDeviceFormat can + * tell you the preferred format for the device before opening and the + * actual format the device is using after opening. * - * When filling in the desired audio spec structure: + * It's legal to open the same device ID more than once; in the end, you must + * close it the same number of times. This allows libraries to open a device + * separate from the main app and bind its own streams without conflicting. * - * - `desired->freq` should be the frequency in sample-frames-per-second (Hz). - * - `desired->format` should be the audio format (`SDL_AUDIO_S16SYS`, etc). - * - `desired->samples` is the desired size of the audio buffer, in _sample - * frames_ (with stereo output, two samples--left and right--would make a - * single sample frame). This number should be a power of two, and may be - * adjusted by the audio driver to a value more suitable for the hardware. - * Good values seem to range between 512 and 8096 inclusive, depending on - * the application and CPU speed. Smaller values reduce latency, but can - * lead to underflow if the application is doing heavy processing and cannot - * fill the audio buffer in time. Note that the number of sample frames is - * directly related to time by the following formula: `ms = - * (sampleframes*1000)/freq` - * - `desired->size` is the size in _bytes_ of the audio buffer, and is - * calculated by SDL_OpenAudioDevice(). You don't initialize this. - * - `desired->silence` is the value used to set the buffer to silence, and is - * calculated by SDL_OpenAudioDevice(). You don't initialize this. - * - `desired->callback` should be set to a function that will be called when - * the audio device is ready for more data. It is passed a pointer to the - * audio buffer, and the length in bytes of the audio buffer. This function - * usually runs in a separate thread, and so you should protect data - * structures that it accesses by calling SDL_LockAudioDevice() and - * SDL_UnlockAudioDevice() in your code. Alternately, you may pass a NULL - * pointer here, and call SDL_QueueAudio() with some frequency, to queue - * more audio samples to be played (or for capture devices, call - * SDL_DequeueAudio() with some frequency, to obtain audio samples). - * - `desired->userdata` is passed as the first parameter to your callback - * function. If you passed a NULL callback, this value is ignored. + * This function returns the opened device ID on success, so that if you + * open a device of 0, you'll have a real ID to bind streams to, but this + * does not generate new instance IDs. Unlike SDL2, these IDs are assigned + * to each unique device on the system, open or not, so if you request a + * specific device, you'll get that same device ID back. * - * `allowed_changes` can have the following flags OR'd together: + * Some backends might offer arbitrary devices (for example, a networked + * audio protocol that can connect to an arbitrary server). For these, as + * a change from SDL2, you should open a device ID of zero and use an SDL + * hint to specify the target if you care, or otherwise let the backend + * figure out a reasonable default. Most backends don't offer anything like + * this, and often this would be an end user setting an environment + * variable for their custom need, and not something an application should + * specifically manage. * - * - `SDL_AUDIO_ALLOW_FREQUENCY_CHANGE` - * - `SDL_AUDIO_ALLOW_FORMAT_CHANGE` - * - `SDL_AUDIO_ALLOW_CHANNELS_CHANGE` - * - `SDL_AUDIO_ALLOW_SAMPLES_CHANGE` - * - `SDL_AUDIO_ALLOW_ANY_CHANGE` - * - * These flags specify how SDL should behave when a device cannot offer a - * specific feature. If the application requests a feature that the hardware - * doesn't offer, SDL will always try to get the closest equivalent. - * - * For example, if you ask for float32 audio format, but the sound card only - * supports int16, SDL will set the hardware to int16. If you had set - * SDL_AUDIO_ALLOW_FORMAT_CHANGE, SDL will change the format in the `obtained` - * structure. If that flag was *not* set, SDL will prepare to convert your - * callback's float32 audio to int16 before feeding it to the hardware and - * will keep the originally requested format in the `obtained` structure. - * - * The resulting audio specs, varying depending on hardware and on what - * changes were allowed, will then be written back to `obtained`. - * - * If your application can only handle one specific data format, pass a zero - * for `allowed_changes` and let SDL transparently handle any differences. - * - * \param device a UTF-8 string reported by SDL_GetAudioDeviceName() or a - * driver-specific name as appropriate. NULL requests the most - * reasonable default device. - * \param iscapture non-zero to specify a device should be opened for - * recording, not playback - * \param desired an SDL_AudioSpec structure representing the desired output - * format - * \param obtained an SDL_AudioSpec structure filled in with the actual output - * format - * \param allowed_changes 0, or one or more flags OR'd together - * \returns a valid device ID that is > 0 on success or 0 on failure; call - * SDL_GetError() for more information. - * - * For compatibility with SDL 1.2, this will never return 1, since - * SDL reserves that ID for the legacy SDL_OpenAudio() function. + * \param devid the device instance id to open. 0 requests the most + * reasonable default device. + * \param fmt the requested device format (`SDL_AUDIO_S16`, etc) + * \param channels the requested device channels (1==mono, 2==stereo, etc). + * \param freq the requested device frequency in sample-frames-per-second (Hz) + * \returns The device ID on success, 0 on error; call SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. * + * \threadsafety It is safe to call this function from any thread. + * * \sa SDL_CloseAudioDevice - * \sa SDL_GetAudioDeviceName - * \sa SDL_LockAudioDevice - * \sa SDL_PlayAudioDevice - * \sa SDL_PauseAudioDevice - * \sa SDL_UnlockAudioDevice + * \sa SDL_GetAudioDeviceFormat */ -extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice( - const char *device, - int iscapture, - const SDL_AudioSpec *desired, - SDL_AudioSpec *obtained, - int allowed_changes); - +extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(SDL_AudioDeviceID devid, SDL_AudioFormat fmt, int channels, int freq); /** - * \name Audio state + * Close a previously-opened audio device. * - * Get the current audio state. - */ -/* @{ */ -typedef enum -{ - SDL_AUDIO_STOPPED = 0, - SDL_AUDIO_PLAYING, - SDL_AUDIO_PAUSED -} SDL_AudioStatus; - -/** - * Use this function to get the current audio state of an audio device. + * The application should close open audio devices once they are no longer + * needed. Audio devices can be opened multiple times; when they are closed + * an equal number of times, its resources are freed, any bound streams are + * unbound, and any audio will stop playing. * - * \param dev the ID of an audio device previously opened with - * SDL_OpenAudioDevice() - * \returns the SDL_AudioStatus of the specified audio device. + * This function may block briefly while pending audio data is played by the + * hardware, so that applications don't drop the last buffer of data they + * supplied. + * + * \param devid an audio device previously opened with SDL_OpenAudioDevice() * * \since This function is available since SDL 3.0.0. * - * \sa SDL_PlayAudioDevice - * \sa SDL_PauseAudioDevice + * \threadsafety It is safe to call this function from any thread. + * + * \sa SDL_OpenAudioDevice */ -extern DECLSPEC SDL_AudioStatus SDLCALL SDL_GetAudioDeviceStatus(SDL_AudioDeviceID dev); -/* @} *//* Audio State */ +extern DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID devid); /** - * Use this function to play audio on a specified device. + * Bind a list of audio streams to an audio device. * - * Newly-opened audio devices start in the paused state, so you must call this - * function after opening the specified audio device to start playing sound. - * This allows you to safely initialize data for your callback function after - * opening the audio device. Silence will be written to the audio device while - * paused, and the audio callback is guaranteed to not be called. Pausing one - * device does not prevent other unpaused devices from running their - * callbacks. + * Audio data will flow through any bound streams. For an output device, data + * for all bound streams will be mixed together and fed to the device. For a + * capture device, a copy of recorded data will be provided to each bound + * stream. * - * \param dev a device opened by SDL_OpenAudioDevice() - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. + * Audio streams can only be bound to an open device. This operation is + * atomic--all streams bound in the same call will start processing at the same + * time, so they can stay in sync. Also: either all streams will be bound or + * none of them will be. + * + * It is an error to bind an already-bound stream; it must be explicitly unbound + * first. + * + * Binding a stream to a device will set its output format for output devices, + * and its input format for capture devices, so they match the device's + * settings. The caller is welcome to change the other end of the stream's + * format at any time. + * + * \param devid an audio device to bind a stream to. + * \param streams an array of audio streams to unbind. + * \param num_streams Number streams listed in the `streams` array. + * \returns 0 on success, -1 on error; call SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. * - * \sa SDL_LockAudioDevice - * \sa SDL_PauseAudioDevice + * \threadsafety It is safe to call this function from any thread. + * + * \sa SDL_BindAudioStreams + * \sa SDL_UnbindAudioStreams + * \sa SDL_UnbindAudioStream */ -extern DECLSPEC int SDLCALL SDL_PlayAudioDevice(SDL_AudioDeviceID dev); - - +extern DECLSPEC int SDLCALL SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int num_streams); /** - * Use this function to pause audio playback on a specified device. + * Bind a single audio stream to an audio device. * - * This function pauses the audio callback processing for a given device. - * Silence will be written to the audio device while paused, and the audio - * callback is guaranteed to not be called. Pausing one device does not - * prevent other unpaused devices from running their callbacks. + * This is a convenience function, equivalent to calling + * `SDL_BindAudioStreams(devid, &stream, 1)`. * - * If you just need to protect a few variables from race conditions vs your - * callback, you shouldn't pause the audio device, as it will lead to dropouts - * in the audio playback. Instead, you should use SDL_LockAudioDevice(). - * - * \param dev a device opened by SDL_OpenAudioDevice() - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. + * \param devid an audio device to bind a stream to. + * \param stream an audio stream to bind to a device. + * \returns 0 on success, -1 on error; call SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. * - * \sa SDL_LockAudioDevice - * \sa SDL_PlayAudioDevice + * \threadsafety It is safe to call this function from any thread. + * + * \sa SDL_BindAudioStreams + * \sa SDL_UnbindAudioStreams + * \sa SDL_UnbindAudioStream */ -extern DECLSPEC int SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID dev); - +extern DECLSPEC int SDLCALL SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_AudioStream *stream); /** - * Load the audio data of a WAVE file into memory. + * Unbind a list of audio streams from their audio devices. * - * Loading a WAVE file requires `src`, `spec`, `audio_buf` and `audio_len` to - * be valid pointers. The entire data portion of the file is then loaded into - * memory and decoded if necessary. + * The streams being unbound do not all have to be on the same device. + * All streams on the same device will be unbound atomically (data will stop flowing + * through them all unbound streams on the same device at the same time). * - * Supported formats are RIFF WAVE files with the formats PCM (8, 16, 24, and - * 32 bits), IEEE Float (32 bits), Microsoft ADPCM and IMA ADPCM (4 bits), and - * A-law and mu-law (8 bits). Other formats are currently unsupported and - * cause an error. + * Unbinding a stream that isn't bound to a device is a legal no-op. * - * If this function succeeds, the pointer returned by it is equal to `spec` - * and the pointer to the audio data allocated by the function is written to - * `audio_buf` and its length in bytes to `audio_len`. The SDL_AudioSpec - * members `freq`, `channels`, and `format` are set to the values of the audio - * data in the buffer. The `samples` member is set to a sane default and all - * others are set to zero. - * - * It's necessary to use SDL_free() to free the audio data returned in - * `audio_buf` when it is no longer used. - * - * Because of the underspecification of the .WAV format, there are many - * problematic files in the wild that cause issues with strict decoders. To - * provide compatibility with these files, this decoder is lenient in regards - * to the truncation of the file, the fact chunk, and the size of the RIFF - * chunk. The hints `SDL_HINT_WAVE_RIFF_CHUNK_SIZE`, - * `SDL_HINT_WAVE_TRUNCATION`, and `SDL_HINT_WAVE_FACT_CHUNK` can be used to - * tune the behavior of the loading process. - * - * Any file that is invalid (due to truncation, corruption, or wrong values in - * the headers), too big, or unsupported causes an error. Additionally, any - * critical I/O error from the data source will terminate the loading process - * with an error. The function returns NULL on error and in all cases (with - * the exception of `src` being NULL), an appropriate error message will be - * set. - * - * It is required that the data source supports seeking. - * - * Example: - * - * ```c - * SDL_LoadWAV_RW(SDL_RWFromFile("sample.wav", "rb"), 1, &spec, &buf, &len); - * ``` - * - * Note that the SDL_LoadWAV macro does this same thing for you, but in a less - * messy way: - * - * ```c - * SDL_LoadWAV("sample.wav", &spec, &buf, &len); - * ``` - * - * \param src The data source for the WAVE data - * \param freesrc if SDL_TRUE, calls SDL_RWclose() on `src` before returning, - * even in the case of an error - * \param spec An SDL_AudioSpec that will be filled in with the wave file's - * format details - * \param audio_buf A pointer filled with the audio data, allocated by the - * function - * \param audio_len A pointer filled with the length of the audio data buffer - * in bytes - * \returns This function, if successfully called, returns `spec`, which will - * be filled with the audio data format of the wave source data. - * `audio_buf` will be filled with a pointer to an allocated buffer - * containing the audio data, and `audio_len` is filled with the - * length of that audio buffer in bytes. - * - * This function returns NULL if the .WAV file cannot be opened, uses - * an unknown data format, or is corrupt; call SDL_GetError() for - * more information. - * - * When the application is done with the data returned in - * `audio_buf`, it should call SDL_free() to dispose of it. + * \param streams an array of audio streams to unbind. + * \param num_streams Number streams listed in the `streams` array. * * \since This function is available since SDL 3.0.0. * - * \sa SDL_free - * \sa SDL_LoadWAV + * \threadsafety It is safe to call this function from any thread. + * + * \sa SDL_BindAudioStreams + * \sa SDL_BindAudioStream + * \sa SDL_UnbindAudioStream */ -extern DECLSPEC SDL_AudioSpec *SDLCALL SDL_LoadWAV_RW(SDL_RWops * src, - SDL_bool freesrc, - SDL_AudioSpec * spec, - Uint8 ** audio_buf, - Uint32 * audio_len); +extern DECLSPEC void SDLCALL SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams); /** - * Loads a WAV from a file. - * Compatibility convenience function. + * Unbind a single audio stream from its audio device. + * + * This is a convenience function, equivalent to calling + * `SDL_UnbindAudioStreams(&stream, 1)`. + * + * \param stream an audio stream to unbind from a device. + * + * \since This function is available since SDL 3.0.0. + * + * \threadsafety It is safe to call this function from any thread. + * + * \sa SDL_BindAudioStream + * \sa SDL_BindAudioStreams + * \sa SDL_UnbindAudioStreams */ -#define SDL_LoadWAV(file, spec, audio_buf, audio_len) \ - SDL_LoadWAV_RW(SDL_RWFromFile(file, "rb"),1, spec,audio_buf,audio_len) +extern DECLSPEC void SDLCALL SDL_UnbindAudioStream(SDL_AudioStream *stream); -/* SDL_AudioStream is an audio conversion interface. - - It can handle resampling data in chunks without generating - artifacts, when it doesn't have the complete buffer available. - - It can handle incoming data in any variable size. - - You push data as you have it, and pull it when you need it - - It can also function as a basic audio data queue even if you - just have sound that needs to pass from one place to another. - */ -struct SDL_AudioStream; /* this is opaque to the outside world. */ -typedef struct SDL_AudioStream SDL_AudioStream; - /** * Create a new audio stream. * @@ -924,6 +729,134 @@ extern DECLSPEC int SDLCALL SDL_ClearAudioStream(SDL_AudioStream *stream); */ extern DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream); + +/** + * Convenience function to create and bind an audio stream in one step. + * + * This manages the creation of an audio stream, and setting its format + * correctly to match both the app and the audio device's needs. This is + * optional, but slightly less cumbersome to set up for a common use case. + * + * The format parameters (`fmt`, `channels`, `freq`) represent the app's side + * of the audio stream. That is, for recording audio, this will be the output + * format, and for playing audio, this will be the input format. This function + * will set the other side of the audio stream to the device's format. + * + * \param devid an audio device to bind a stream to. This must be an opened device, and can not be zero. + * \param fmt the audio stream's format (`SDL_AUDIO_S16`, etc) + * \param channels the audio stream's channel count (1==mono, 2==stereo, etc). + * \param freq the audio stream's frequency in sample-frames-per-second (Hz) + * \returns a bound audio stream on success, ready to use. NULL on error; call SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \threadsafety It is safe to call this function from any thread. + * + * \sa SDL_BindAudioStreams + * \sa SDL_UnbindAudioStreams + * \sa SDL_UnbindAudioStream + */ +extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAndBindAudioStream(SDL_AudioDeviceID devid, SDL_AudioFormat fmt, int channels, int freq); + + +/** + * Load the audio data of a WAVE file into memory. + * + * Loading a WAVE file requires `src`, `spec`, `audio_buf` and `audio_len` to + * be valid pointers. The entire data portion of the file is then loaded into + * memory and decoded if necessary. + * + * If `freesrc` is non-zero, the data source gets automatically closed and + * freed before the function returns. + * + * Supported formats are RIFF WAVE files with the formats PCM (8, 16, 24, and + * 32 bits), IEEE Float (32 bits), Microsoft ADPCM and IMA ADPCM (4 bits), and + * A-law and mu-law (8 bits). Other formats are currently unsupported and + * cause an error. + * + * If this function succeeds, the return value is zero and the pointer to the + * audio data allocated by the function is written to `audio_buf` and its + * length in bytes to `audio_len`. The SDL_AudioSpec members `freq`, + * `channels`, and `format` are set to the values of the audio data in the + * buffer. The `samples` member is set to a sane default and all + * others are set to zero. + * + * It's necessary to use SDL_free() to free the audio data returned in + * `audio_buf` when it is no longer used. + * + * Because of the underspecification of the .WAV format, there are many + * problematic files in the wild that cause issues with strict decoders. To + * provide compatibility with these files, this decoder is lenient in regards + * to the truncation of the file, the fact chunk, and the size of the RIFF + * chunk. The hints `SDL_HINT_WAVE_RIFF_CHUNK_SIZE`, + * `SDL_HINT_WAVE_TRUNCATION`, and `SDL_HINT_WAVE_FACT_CHUNK` can be used to + * tune the behavior of the loading process. + * + * Any file that is invalid (due to truncation, corruption, or wrong values in + * the headers), too big, or unsupported causes an error. Additionally, any + * critical I/O error from the data source will terminate the loading process + * with an error. The function returns NULL on error and in all cases (with + * the exception of `src` being NULL), an appropriate error message will be + * set. + * + * It is required that the data source supports seeking. + * + * Example: + * + * ```c + * SDL_LoadWAV_RW(SDL_RWFromFile("sample.wav", "rb"), 1, &spec, &buf, &len); + * ``` + * + * Note that the SDL_LoadWAV macro does this same thing for you, but in a less + * messy way: + * + * ```c + * SDL_LoadWAV("sample.wav", &spec, &buf, &len); + * ``` + * + * \param src The data source for the WAVE data + * \param freesrc If non-zero, SDL will _always_ free the data source + * \param fmt A pointer to an SDL_AudioFormat that will be set to the + * WAVE data's format on successful return. + * \param channels A pointer to an int that will be set to the + * WAVE data's channel count on successful return. + * \param freq A pointer to an int that will be set to the + * WAVE data's sample rate in Hz on successful return. + * \param audio_buf A pointer filled with the audio data, allocated by the + * function. + * \param audio_len A pointer filled with the length of the audio data buffer + * in bytes + * \returns This function, if successfully called, returns 0. `audio_buf` + * will be filled with a pointer to an allocated buffer + * containing the audio data, and `audio_len` is filled with the + * length of that audio buffer in bytes. + * + * This function returns -1 if the .WAV file cannot be opened, uses + * an unknown data format, or is corrupt; call SDL_GetError() for + * more information. + * + * When the application is done with the data returned in + * `audio_buf`, it should call SDL_free() to dispose of it. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_free + * \sa SDL_LoadWAV + */ +extern DECLSPEC int SDLCALL SDL_LoadWAV_RW(SDL_RWops * src, int freesrc, + SDL_AudioFormat *fmt, int *channels, int *freq, + Uint8 ** audio_buf, + Uint32 * audio_len); + +/** + * Loads a WAV from a file. + * Compatibility convenience function. + */ +#define SDL_LoadWAV(file, fmt, channels, freq, audio_buf, audio_len) \ + SDL_LoadWAV_RW(SDL_RWFromFile(file, "rb"), 1, fmt, channels, freq, audio_buf, audio_len) + + + #define SDL_MIX_MAXVOLUME 128 /** @@ -963,263 +896,6 @@ extern DECLSPEC int SDLCALL SDL_MixAudioFormat(Uint8 * dst, SDL_AudioFormat format, Uint32 len, int volume); -/** - * Queue more audio on non-callback devices. - * - * If you are looking to retrieve queued audio from a non-callback capture - * device, you want SDL_DequeueAudio() instead. SDL_QueueAudio() will return - * -1 to signify an error if you use it with capture devices. - * - * SDL offers two ways to feed audio to the device: you can either supply a - * callback that SDL triggers with some frequency to obtain more audio (pull - * method), or you can supply no callback, and then SDL will expect you to - * supply data at regular intervals (push method) with this function. - * - * There are no limits on the amount of data you can queue, short of - * exhaustion of address space. Queued data will drain to the device as - * necessary without further intervention from you. If the device needs audio - * but there is not enough queued, it will play silence to make up the - * difference. This means you will have skips in your audio playback if you - * aren't routinely queueing sufficient data. - * - * This function copies the supplied data, so you are safe to free it when the - * function returns. This function is thread-safe, but queueing to the same - * device from two threads at once does not promise which buffer will be - * queued first. - * - * You may not queue audio on a device that is using an application-supplied - * callback; doing so returns an error. You have to use the audio callback or - * queue audio with this function, but not both. - * - * You should not call SDL_LockAudio() on the device before queueing; SDL - * handles locking internally for this function. - * - * Note that SDL does not support planar audio. You will need to resample from - * planar audio formats into a non-planar one (see SDL_AudioFormat) before - * queuing audio. - * - * \param dev the device ID to which we will queue audio - * \param data the data to queue to the device for later playback - * \param len the number of bytes (not samples!) to which `data` points - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_ClearQueuedAudio - * \sa SDL_GetQueuedAudioSize - */ -extern DECLSPEC int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID dev, const void *data, Uint32 len); - -/** - * Dequeue more audio on non-callback devices. - * - * If you are looking to queue audio for output on a non-callback playback - * device, you want SDL_QueueAudio() instead. SDL_DequeueAudio() will always - * return 0 if you use it with playback devices. - * - * SDL offers two ways to retrieve audio from a capture device: you can either - * supply a callback that SDL triggers with some frequency as the device - * records more audio data, (push method), or you can supply no callback, and - * then SDL will expect you to retrieve data at regular intervals (pull - * method) with this function. - * - * There are no limits on the amount of data you can queue, short of - * exhaustion of address space. Data from the device will keep queuing as - * necessary without further intervention from you. This means you will - * eventually run out of memory if you aren't routinely dequeueing data. - * - * Capture devices will not queue data when paused; if you are expecting to - * not need captured audio for some length of time, use SDL_PauseAudioDevice() - * to stop the capture device from queueing more data. This can be useful - * during, say, level loading times. When unpaused, capture devices will start - * queueing data from that point, having flushed any capturable data available - * while paused. - * - * This function is thread-safe, but dequeueing from the same device from two - * threads at once does not promise which thread will dequeue data first. - * - * You may not dequeue audio from a device that is using an - * application-supplied callback; doing so returns an error. You have to use - * the audio callback, or dequeue audio with this function, but not both. - * - * You should not call SDL_LockAudio() on the device before dequeueing; SDL - * handles locking internally for this function. - * - * \param dev the device ID from which we will dequeue audio - * \param data a pointer into where audio data should be copied - * \param len the number of bytes (not samples!) to which (data) points - * \returns the number of bytes dequeued, which could be less than requested; - * call SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_ClearQueuedAudio - * \sa SDL_GetQueuedAudioSize - */ -extern DECLSPEC Uint32 SDLCALL SDL_DequeueAudio(SDL_AudioDeviceID dev, void *data, Uint32 len); - -/** - * Get the number of bytes of still-queued audio. - * - * For playback devices: this is the number of bytes that have been queued for - * playback with SDL_QueueAudio(), but have not yet been sent to the hardware. - * - * Once we've sent it to the hardware, this function can not decide the exact - * byte boundary of what has been played. It's possible that we just gave the - * hardware several kilobytes right before you called this function, but it - * hasn't played any of it yet, or maybe half of it, etc. - * - * For capture devices, this is the number of bytes that have been captured by - * the device and are waiting for you to dequeue. This number may grow at any - * time, so this only informs of the lower-bound of available data. - * - * You may not queue or dequeue audio on a device that is using an - * application-supplied callback; calling this function on such a device - * always returns 0. You have to use the audio callback or queue audio, but - * not both. - * - * You should not call SDL_LockAudio() on the device before querying; SDL - * handles locking internally for this function. - * - * \param dev the device ID of which we will query queued audio size - * \returns the number of bytes (not samples!) of queued audio. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_ClearQueuedAudio - * \sa SDL_QueueAudio - * \sa SDL_DequeueAudio - */ -extern DECLSPEC Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID dev); - -/** - * Drop any queued audio data waiting to be sent to the hardware. - * - * Immediately after this call, SDL_GetQueuedAudioSize() will return 0. For - * output devices, the hardware will start playing silence if more audio isn't - * queued. For capture devices, the hardware will start filling the empty - * queue with new data if the capture device isn't paused. - * - * This will not prevent playback of queued audio that's already been sent to - * the hardware, as we can not undo that, so expect there to be some fraction - * of a second of audio that might still be heard. This can be useful if you - * want to, say, drop any pending music or any unprocessed microphone input - * during a level change in your game. - * - * You may not queue or dequeue audio on a device that is using an - * application-supplied callback; calling this function on such a device - * always returns 0. You have to use the audio callback or queue audio, but - * not both. - * - * You should not call SDL_LockAudio() on the device before clearing the - * queue; SDL handles locking internally for this function. - * - * This function always succeeds and thus returns void. - * - * \param dev the device ID of which to clear the audio queue - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_GetQueuedAudioSize - * \sa SDL_QueueAudio - * \sa SDL_DequeueAudio - */ -extern DECLSPEC int SDLCALL SDL_ClearQueuedAudio(SDL_AudioDeviceID dev); - - -/** - * \name Audio lock functions - * - * The lock manipulated by these functions protects the callback function. - * During a SDL_LockAudio()/SDL_UnlockAudio() pair, you can be guaranteed that - * the callback function is not running. Do not call these from the callback - * function or you will cause deadlock. - */ -/* @{ */ - -/** - * Use this function to lock out the audio callback function for a specified - * device. - * - * The lock manipulated by these functions protects the audio callback - * function specified in SDL_OpenAudioDevice(). During a - * SDL_LockAudioDevice()/SDL_UnlockAudioDevice() pair, you can be guaranteed - * that the callback function for that device is not running, even if the - * device is not paused. While a device is locked, any other unpaused, - * unlocked devices may still run their callbacks. - * - * Calling this function from inside your audio callback is unnecessary. SDL - * obtains this lock before calling your function, and releases it when the - * function returns. - * - * You should not hold the lock longer than absolutely necessary. If you hold - * it too long, you'll experience dropouts in your audio playback. Ideally, - * your application locks the device, sets a few variables and unlocks again. - * Do not do heavy work while holding the lock for a device. - * - * It is safe to lock the audio device multiple times, as long as you unlock - * it an equivalent number of times. The callback will not run until the - * device has been unlocked completely in this way. If your application fails - * to unlock the device appropriately, your callback will never run, you might - * hear repeating bursts of audio, and SDL_CloseAudioDevice() will probably - * deadlock. - * - * Internally, the audio device lock is a mutex; if you lock from two threads - * at once, not only will you block the audio callback, you'll block the other - * thread. - * - * \param dev the ID of the device to be locked - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_UnlockAudioDevice - */ -extern DECLSPEC int SDLCALL SDL_LockAudioDevice(SDL_AudioDeviceID dev); - -/** - * Use this function to unlock the audio callback function for a specified - * device. - * - * This function should be paired with a previous SDL_LockAudioDevice() call. - * - * \param dev the ID of the device to be unlocked - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_LockAudioDevice - */ -extern DECLSPEC void SDLCALL SDL_UnlockAudioDevice(SDL_AudioDeviceID dev); -/* @} *//* Audio lock functions */ - -/** - * Use this function to shut down audio processing and close the audio device. - * - * The application should close open audio devices once they are no longer - * needed. Calling this function will wait until the device's audio callback - * is not running, release the audio hardware and then clean up internal - * state. No further audio will play from this device once this function - * returns. - * - * This function may block briefly while pending audio data is played by the - * hardware, so that applications don't drop the last buffer of data they - * supplied. - * - * The device ID is invalid as soon as the device is closed, and is eligible - * for reuse in a new SDL_OpenAudioDevice() call immediately. - * - * \param dev an audio device previously opened with SDL_OpenAudioDevice() - * - * \since This function is available since SDL 3.0.0. - * - * \sa SDL_OpenAudioDevice - */ -extern DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID dev); - /** * Convert some audio data of one format to another format. * @@ -1253,16 +929,34 @@ extern DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID dev); * \sa SDL_CreateAudioStream */ extern DECLSPEC int SDLCALL SDL_ConvertAudioSamples(SDL_AudioFormat src_format, - Uint8 src_channels, + int src_channels, int src_rate, const Uint8 *src_data, int src_len, SDL_AudioFormat dst_format, - Uint8 dst_channels, + int dst_channels, int dst_rate, Uint8 **dst_data, int *dst_len); + +/** + * Get the appropriate memset value for silencing an audio format. + * + * The value returned by this function can be used as the second + * argument to memset (or SDL_memset) to set an audio buffer in + * a specific format to silence. + * + * \param format the audio data format to query. + * \returns A byte value that can be passed to memset. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + */ +extern DECLSPEC int SDLCALL SDL_GetSilenceValueForFormat(SDL_AudioFormat format); + + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/include/SDL3/SDL_test_common.h b/include/SDL3/SDL_test_common.h index 4c151471c8..f2d66e51e7 100644 --- a/include/SDL3/SDL_test_common.h +++ b/include/SDL3/SDL_test_common.h @@ -97,7 +97,9 @@ typedef struct /* Audio info */ const char *audiodriver; - SDL_AudioSpec audiospec; + SDL_AudioFormat audio_format; + int audio_channels; + int audio_freq; SDL_AudioDeviceID audio_id; /* GL settings */ diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 80f6558868..05d6e83210 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -20,15 +20,14 @@ */ #include "SDL_internal.h" -/* Allow access to a raw mixing buffer */ - #include "SDL_audio_c.h" #include "SDL_sysaudio.h" #include "../thread/SDL_systhread.h" #include "../SDL_utils_c.h" +extern void Android_JNI_AudioSetThreadPriority(int, int); /* we need this on Android in the audio device threads. */ + static SDL_AudioDriver current_audio; -static SDL_AudioDevice *open_devices[16]; /* Available audio drivers */ static const AudioBootStrap *const bootstrap[] = { @@ -101,672 +100,6 @@ static const AudioBootStrap *const bootstrap[] = { NULL }; -static SDL_AudioDevice *get_audio_device(SDL_AudioDeviceID id) -{ - id--; - if ((id >= SDL_arraysize(open_devices)) || (open_devices[id] == NULL)) { - SDL_SetError("Invalid audio device ID"); - return NULL; - } - - return open_devices[id]; -} - -int get_max_num_audio_dev(void) -{ - return SDL_arraysize(open_devices); -} - -SDL_AudioDevice *get_audio_dev(SDL_AudioDeviceID id) -{ - return open_devices[id]; -} - -/* stubs for audio drivers that don't need a specific entry point... */ -static void SDL_AudioDetectDevices_Default(void) -{ - /* you have to write your own implementation if these assertions fail. */ - SDL_assert(current_audio.impl.OnlyHasDefaultOutputDevice); - SDL_assert(current_audio.impl.OnlyHasDefaultCaptureDevice || !current_audio.impl.HasCaptureSupport); - - SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)((size_t)0x1)); - if (current_audio.impl.HasCaptureSupport) { - SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)((size_t)0x2)); - } -} - -static void SDL_AudioThreadInit_Default(SDL_AudioDevice *_this) -{ /* no-op. */ -} - -static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *_this) -{ /* no-op. */ -} - -static void SDL_AudioWaitDevice_Default(SDL_AudioDevice *_this) -{ /* no-op. */ -} - -static void SDL_AudioPlayDevice_Default(SDL_AudioDevice *_this) -{ /* no-op. */ -} - -static Uint8 *SDL_AudioGetDeviceBuf_Default(SDL_AudioDevice *_this) -{ - return NULL; -} - -static int SDL_AudioCaptureFromDevice_Default(SDL_AudioDevice *_this, void *buffer, int buflen) -{ - return -1; /* just fail immediately. */ -} - -static void SDL_AudioFlushCapture_Default(SDL_AudioDevice *_this) -{ /* no-op. */ -} - -static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *_this) -{ /* no-op. */ -} - -static void SDL_AudioDeinitialize_Default(void) -{ /* no-op. */ -} - -static void SDL_AudioFreeDeviceHandle_Default(void *handle) -{ /* no-op. */ -} - -static int SDL_AudioOpenDevice_Default(SDL_AudioDevice *_this, const char *devname) -{ - return SDL_Unsupported(); -} - -static void SDL_AudioLockDevice_Default(SDL_AudioDevice *device) -{ - SDL_LockMutex(device->mixer_lock); -} - -static void SDL_AudioUnlockDevice_Default(SDL_AudioDevice *device) -{ - SDL_UnlockMutex(device->mixer_lock); -} - -static void finish_audio_entry_points_init(void) -{ - /* - * Fill in stub functions for unused driver entry points. This lets us - * blindly call them without having to check for validity first. - */ - -#define FILL_STUB(x) \ - if (current_audio.impl.x == NULL) { \ - current_audio.impl.x = SDL_Audio##x##_Default; \ - } - FILL_STUB(DetectDevices); - FILL_STUB(OpenDevice); - FILL_STUB(ThreadInit); - FILL_STUB(ThreadDeinit); - FILL_STUB(WaitDevice); - FILL_STUB(PlayDevice); - FILL_STUB(GetDeviceBuf); - FILL_STUB(CaptureFromDevice); - FILL_STUB(FlushCapture); - FILL_STUB(CloseDevice); - FILL_STUB(LockDevice); - FILL_STUB(UnlockDevice); - FILL_STUB(FreeDeviceHandle); - FILL_STUB(Deinitialize); -#undef FILL_STUB -} - -/* device hotplug support... */ - -static int add_audio_device(const char *name, SDL_AudioSpec *spec, void *handle, SDL_AudioDeviceItem **devices, int *devCount) -{ - int retval = -1; - SDL_AudioDeviceItem *item; - const SDL_AudioDeviceItem *i; - int dupenum = 0; - - SDL_assert(handle != NULL); /* we reserve NULL, audio backends can't use it. */ - SDL_assert(name != NULL); - - item = (SDL_AudioDeviceItem *)SDL_malloc(sizeof(SDL_AudioDeviceItem)); - if (!item) { - return SDL_OutOfMemory(); - } - - item->original_name = SDL_strdup(name); - if (!item->original_name) { - SDL_free(item); - return SDL_OutOfMemory(); - } - - item->dupenum = 0; - item->name = item->original_name; - if (spec != NULL) { - SDL_copyp(&item->spec, spec); - } else { - SDL_zero(item->spec); - } - item->handle = handle; - - SDL_LockMutex(current_audio.detectionLock); - - for (i = *devices; i != NULL; i = i->next) { - if (SDL_strcmp(name, i->original_name) == 0) { - dupenum = i->dupenum + 1; - break; /* stop at the highest-numbered dupe. */ - } - } - - if (dupenum) { - const size_t len = SDL_strlen(name) + 16; - char *replacement = (char *)SDL_malloc(len); - if (!replacement) { - SDL_UnlockMutex(current_audio.detectionLock); - SDL_free(item->original_name); - SDL_free(item); - return SDL_OutOfMemory(); - } - - (void)SDL_snprintf(replacement, len, "%s (%d)", name, dupenum + 1); - item->dupenum = dupenum; - item->name = replacement; - } - - item->next = *devices; - *devices = item; - retval = (*devCount)++; /* !!! FIXME: this should be an atomic increment */ - - SDL_UnlockMutex(current_audio.detectionLock); - - return retval; -} - -static SDL_INLINE int add_capture_device(const char *name, SDL_AudioSpec *spec, void *handle) -{ - SDL_assert(current_audio.impl.HasCaptureSupport); - return add_audio_device(name, spec, handle, ¤t_audio.inputDevices, ¤t_audio.inputDeviceCount); -} - -static SDL_INLINE int add_output_device(const char *name, SDL_AudioSpec *spec, void *handle) -{ - return add_audio_device(name, spec, handle, ¤t_audio.outputDevices, ¤t_audio.outputDeviceCount); -} - -static void free_device_list(SDL_AudioDeviceItem **devices, int *devCount) -{ - SDL_AudioDeviceItem *item, *next; - for (item = *devices; item != NULL; item = next) { - next = item->next; - if (item->handle != NULL) { - current_audio.impl.FreeDeviceHandle(item->handle); - } - /* these two pointers are the same if not a duplicate devname */ - if (item->name != item->original_name) { - SDL_free(item->name); - } - SDL_free(item->original_name); - SDL_free(item); - } - *devices = NULL; - *devCount = 0; -} - -/* The audio backends call this when a new device is plugged in. */ -void SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, SDL_AudioSpec *spec, void *handle) -{ - const int device_index = iscapture ? add_capture_device(name, spec, handle) : add_output_device(name, spec, handle); - if (device_index != -1) { - /* Post the event, if desired */ - if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_ADDED)) { - SDL_Event event; - event.type = SDL_EVENT_AUDIO_DEVICE_ADDED; - event.common.timestamp = 0; - event.adevice.which = device_index; - event.adevice.iscapture = iscapture; - SDL_PushEvent(&event); - } - } -} - -/* The audio backends call this when a currently-opened device is lost. */ -void SDL_OpenedAudioDeviceDisconnected(SDL_AudioDevice *device) -{ - SDL_assert(get_audio_device(device->id) == device); - - if (!SDL_AtomicGet(&device->enabled)) { - return; /* don't report disconnects more than once. */ - } - - if (SDL_AtomicGet(&device->shutdown)) { - return; /* don't report disconnect if we're trying to close device. */ - } - - /* Ends the audio callback and mark the device as STOPPED, but the - app still needs to close the device to free resources. */ - current_audio.impl.LockDevice(device); - SDL_AtomicSet(&device->enabled, 0); - current_audio.impl.UnlockDevice(device); - - /* Post the event, if desired */ - if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_REMOVED)) { - SDL_Event event; - event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED; - event.common.timestamp = 0; - event.adevice.which = device->id; - event.adevice.iscapture = device->iscapture ? 1 : 0; - SDL_PushEvent(&event); - } -} - -static void mark_device_removed(void *handle, SDL_AudioDeviceItem *devices, SDL_bool *removedFlag) -{ - SDL_AudioDeviceItem *item; - SDL_assert(handle != NULL); - for (item = devices; item != NULL; item = item->next) { - if (item->handle == handle) { - item->handle = NULL; - *removedFlag = SDL_TRUE; - return; - } - } -} - -/* The audio backends call this when a device is removed from the system. */ -void SDL_RemoveAudioDevice(const SDL_bool iscapture, void *handle) -{ - int device_index; - SDL_AudioDevice *device = NULL; - SDL_bool device_was_opened = SDL_FALSE; - - SDL_LockMutex(current_audio.detectionLock); - if (iscapture) { - mark_device_removed(handle, current_audio.inputDevices, ¤t_audio.captureDevicesRemoved); - } else { - mark_device_removed(handle, current_audio.outputDevices, ¤t_audio.outputDevicesRemoved); - } - for (device_index = 0; device_index < SDL_arraysize(open_devices); device_index++) { - device = open_devices[device_index]; - if (device != NULL && device->handle == handle) { - device_was_opened = SDL_TRUE; - SDL_OpenedAudioDeviceDisconnected(device); - break; - } - } - - /* Devices that aren't opened, as of 2.24.0, will post an - SDL_EVENT_AUDIO_DEVICE_REMOVED event with the `which` field set to zero. - Apps can use this to decide if they need to refresh a list of - available devices instead of closing an opened one. - Note that opened devices will send the non-zero event in - SDL_OpenedAudioDeviceDisconnected(). */ - if (!device_was_opened) { - if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_REMOVED)) { - SDL_Event event; - event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED; - event.common.timestamp = 0; - event.adevice.which = 0; - event.adevice.iscapture = iscapture ? 1 : 0; - SDL_PushEvent(&event); - } - } - - SDL_UnlockMutex(current_audio.detectionLock); - - current_audio.impl.FreeDeviceHandle(handle); -} - -/* buffer queueing support... */ - -static void SDLCALL SDL_BufferQueueDrainCallback(void *userdata, Uint8 *stream, int len) -{ - /* this function always holds the mixer lock before being called. */ - SDL_AudioDevice *device = (SDL_AudioDevice *)userdata; - size_t dequeued; - - SDL_assert(device != NULL); /* this shouldn't ever happen, right?! */ - SDL_assert(!device->iscapture); /* this shouldn't ever happen, right?! */ - SDL_assert(len >= 0); /* this shouldn't ever happen, right?! */ - - dequeued = SDL_ReadFromDataQueue(device->buffer_queue, stream, len); - stream += dequeued; - len -= (int)dequeued; - - if (len > 0) { /* fill any remaining space in the stream with silence. */ - SDL_assert(SDL_GetDataQueueSize(device->buffer_queue) == 0); - SDL_memset(stream, device->callbackspec.silence, len); - } -} - -static void SDLCALL SDL_BufferQueueFillCallback(void *userdata, Uint8 *stream, int len) -{ - /* this function always holds the mixer lock before being called. */ - SDL_AudioDevice *device = (SDL_AudioDevice *)userdata; - - SDL_assert(device != NULL); /* this shouldn't ever happen, right?! */ - SDL_assert(device->iscapture); /* this shouldn't ever happen, right?! */ - SDL_assert(len >= 0); /* this shouldn't ever happen, right?! */ - - /* note that if this needs to allocate more space and run out of memory, - we have no choice but to quietly drop the data and hope it works out - later, but you probably have bigger problems in this case anyhow. */ - SDL_WriteToDataQueue(device->buffer_queue, stream, len); -} - -int SDL_QueueAudio(SDL_AudioDeviceID devid, const void *data, Uint32 len) -{ - SDL_AudioDevice *device = get_audio_device(devid); - int rc = 0; - - if (!device) { - return -1; /* get_audio_device() will have set the error state */ - } else if (device->iscapture) { - return SDL_SetError("This is a capture device, queueing not allowed"); - } else if (device->callbackspec.callback != SDL_BufferQueueDrainCallback) { - return SDL_SetError("Audio device has a callback, queueing not allowed"); - } - - if (len > 0) { - current_audio.impl.LockDevice(device); - rc = SDL_WriteToDataQueue(device->buffer_queue, data, len); - current_audio.impl.UnlockDevice(device); - } - - return rc; -} - -Uint32 SDL_DequeueAudio(SDL_AudioDeviceID devid, void *data, Uint32 len) -{ - SDL_AudioDevice *device = get_audio_device(devid); - Uint32 rc; - - if ((len == 0) || /* nothing to do? */ - (!device) || /* called with bogus device id */ - (!device->iscapture) || /* playback devices can't dequeue */ - (device->callbackspec.callback != SDL_BufferQueueFillCallback)) { /* not set for queueing */ - return 0; /* just report zero bytes dequeued. */ - } - - current_audio.impl.LockDevice(device); - rc = (Uint32)SDL_ReadFromDataQueue(device->buffer_queue, data, len); - current_audio.impl.UnlockDevice(device); - return rc; -} - -Uint32 SDL_GetQueuedAudioSize(SDL_AudioDeviceID devid) -{ - Uint32 retval = 0; - SDL_AudioDevice *device = get_audio_device(devid); - - if (!device) { - return 0; - } - - /* Nothing to do unless we're set up for queueing. */ - if (device->callbackspec.callback == SDL_BufferQueueDrainCallback || - device->callbackspec.callback == SDL_BufferQueueFillCallback) { - current_audio.impl.LockDevice(device); - retval = (Uint32)SDL_GetDataQueueSize(device->buffer_queue); - current_audio.impl.UnlockDevice(device); - } - - return retval; -} - -int SDL_ClearQueuedAudio(SDL_AudioDeviceID devid) -{ - SDL_AudioDevice *device = get_audio_device(devid); - if (!device) { - return SDL_InvalidParamError("devid"); - } - - /* Blank out the device and release the mutex. Free it afterwards. */ - current_audio.impl.LockDevice(device); - - /* Keep up to two packets in the pool to reduce future memory allocation pressure. */ - SDL_ClearDataQueue(device->buffer_queue, SDL_AUDIOBUFFERQUEUE_PACKETLEN * 2); - - current_audio.impl.UnlockDevice(device); - return 0; -} - -#ifdef SDL_AUDIO_DRIVER_ANDROID -extern void Android_JNI_AudioSetThreadPriority(int, int); -#endif - -/* The general mixing thread function */ -static int SDLCALL SDL_RunAudio(void *devicep) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *)devicep; - void *udata = device->callbackspec.userdata; - SDL_AudioCallback callback = device->callbackspec.callback; - int data_len = 0; - Uint8 *data; - - SDL_assert(!device->iscapture); - -#ifdef SDL_AUDIO_DRIVER_ANDROID - { - /* Set thread priority to THREAD_PRIORITY_AUDIO */ - Android_JNI_AudioSetThreadPriority(device->iscapture, device->id); - } -#else - /* The audio mixing is always a high priority thread */ - SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL); -#endif - - /* Perform any thread setup */ - device->threadid = SDL_ThreadID(); - current_audio.impl.ThreadInit(device); - - /* Loop, filling the audio buffers */ - while (!SDL_AtomicGet(&device->shutdown)) { - data_len = device->callbackspec.size; - - /* Fill the current buffer with sound */ - if (!device->stream && SDL_AtomicGet(&device->enabled)) { - SDL_assert((Uint32)data_len == device->spec.size); - data = current_audio.impl.GetDeviceBuf(device); - } else { - /* if the device isn't enabled, we still write to the - work_buffer, so the app's callback will fire with - a regular frequency, in case they depend on that - for timing or progress. They can use hotplug - now to know if the device failed. - Streaming playback uses work_buffer, too. */ - data = NULL; - } - - if (data == NULL) { - data = device->work_buffer; - } - - /* !!! FIXME: this should be LockDevice. */ - SDL_LockMutex(device->mixer_lock); - if (SDL_AtomicGet(&device->paused)) { - SDL_memset(data, device->callbackspec.silence, data_len); - } else { - callback(udata, data, data_len); - } - SDL_UnlockMutex(device->mixer_lock); - - if (device->stream) { - /* Stream available audio to device, converting/resampling. */ - /* if this fails...oh well. We'll play silence here. */ - SDL_PutAudioStreamData(device->stream, data, data_len); - - while (SDL_GetAudioStreamAvailable(device->stream) >= ((int)device->spec.size)) { - int got; - data = SDL_AtomicGet(&device->enabled) ? current_audio.impl.GetDeviceBuf(device) : NULL; - got = SDL_GetAudioStreamData(device->stream, data ? data : device->work_buffer, device->spec.size); - SDL_assert((got <= 0) || ((Uint32)got == device->spec.size)); - - if (data == NULL) { /* device is having issues... */ - const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq); - SDL_Delay(delay); /* wait for as long as this buffer would have played. Maybe device recovers later? */ - } else { - if ((Uint32)got != device->spec.size) { - SDL_memset(data, device->spec.silence, device->spec.size); - } - current_audio.impl.PlayDevice(device); - current_audio.impl.WaitDevice(device); - } - } - } else if (data == device->work_buffer) { - /* nothing to do; pause like we queued a buffer to play. */ - const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq); - SDL_Delay(delay); - } else { /* writing directly to the device. */ - /* queue this buffer and wait for it to finish playing. */ - current_audio.impl.PlayDevice(device); - current_audio.impl.WaitDevice(device); - } - } - - /* Wait for the audio to drain. */ - SDL_Delay(((device->spec.samples * 1000) / device->spec.freq) * 2); - - current_audio.impl.ThreadDeinit(device); - - return 0; -} - -/* !!! FIXME: this needs to deal with device spec changes. */ -/* The general capture thread function */ -static int SDLCALL SDL_CaptureAudio(void *devicep) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *)devicep; - const int silence = (int)device->spec.silence; - const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq); - const int data_len = device->spec.size; - Uint8 *data; - void *udata = device->callbackspec.userdata; - SDL_AudioCallback callback = device->callbackspec.callback; - - SDL_assert(device->iscapture); - -#ifdef SDL_AUDIO_DRIVER_ANDROID - { - /* Set thread priority to THREAD_PRIORITY_AUDIO */ - Android_JNI_AudioSetThreadPriority(device->iscapture, device->id); - } -#else - /* The audio mixing is always a high priority thread */ - SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); -#endif - - /* Perform any thread setup */ - device->threadid = SDL_ThreadID(); - current_audio.impl.ThreadInit(device); - - /* Loop, filling the audio buffers */ - while (!SDL_AtomicGet(&device->shutdown)) { - int still_need; - Uint8 *ptr; - - if (SDL_AtomicGet(&device->paused)) { - SDL_Delay(delay); /* just so we don't cook the CPU. */ - if (device->stream) { - SDL_ClearAudioStream(device->stream); - } - current_audio.impl.FlushCapture(device); /* dump anything pending. */ - continue; - } - - /* Fill the current buffer with sound */ - still_need = data_len; - - /* Use the work_buffer to hold data read from the device. */ - data = device->work_buffer; - SDL_assert(data != NULL); - - ptr = data; - - /* We still read from the device when "paused" to keep the state sane, - and block when there isn't data so this thread isn't eating CPU. - But we don't process it further or call the app's callback. */ - - if (!SDL_AtomicGet(&device->enabled)) { - SDL_Delay(delay); /* try to keep callback firing at normal pace. */ - } else { - while (still_need > 0) { - const int rc = current_audio.impl.CaptureFromDevice(device, ptr, still_need); - SDL_assert(rc <= still_need); /* device should not overflow buffer. :) */ - if (rc > 0) { - still_need -= rc; - ptr += rc; - } else { /* uhoh, device failed for some reason! */ - SDL_OpenedAudioDeviceDisconnected(device); - break; - } - } - } - - if (still_need > 0) { - /* Keep any data we already read, silence the rest. */ - SDL_memset(ptr, silence, still_need); - } - - if (device->stream) { - /* if this fails...oh well. */ - SDL_PutAudioStreamData(device->stream, data, data_len); - - while (SDL_GetAudioStreamAvailable(device->stream) >= ((int)device->callbackspec.size)) { - const int got = SDL_GetAudioStreamData(device->stream, device->work_buffer, device->callbackspec.size); - SDL_assert((got < 0) || ((Uint32)got == device->callbackspec.size)); - if ((Uint32)got != device->callbackspec.size) { - SDL_memset(device->work_buffer, device->spec.silence, device->callbackspec.size); - } - - /* !!! FIXME: this should be LockDevice. */ - SDL_LockMutex(device->mixer_lock); - if (!SDL_AtomicGet(&device->paused)) { - callback(udata, device->work_buffer, device->callbackspec.size); - } - SDL_UnlockMutex(device->mixer_lock); - } - } else { /* feeding user callback directly without streaming. */ - /* !!! FIXME: this should be LockDevice. */ - SDL_LockMutex(device->mixer_lock); - if (!SDL_AtomicGet(&device->paused)) { - callback(udata, data, device->callbackspec.size); - } - SDL_UnlockMutex(device->mixer_lock); - } - } - - current_audio.impl.FlushCapture(device); - - current_audio.impl.ThreadDeinit(device); - - return 0; -} - -static SDL_AudioFormat SDL_ParseAudioFormat(const char *string) -{ -#define CHECK_FMT_STRING(x) \ - if (SDL_strcmp(string, #x) == 0) \ - return SDL_AUDIO_##x - CHECK_FMT_STRING(U8); - CHECK_FMT_STRING(S8); - CHECK_FMT_STRING(S16LSB); - CHECK_FMT_STRING(S16MSB); - CHECK_FMT_STRING(S16); - CHECK_FMT_STRING(S32LSB); - CHECK_FMT_STRING(S32MSB); - CHECK_FMT_STRING(S32SYS); - CHECK_FMT_STRING(S32); - CHECK_FMT_STRING(F32LSB); - CHECK_FMT_STRING(F32MSB); - CHECK_FMT_STRING(F32SYS); - CHECK_FMT_STRING(F32); -#undef CHECK_FMT_STRING - return 0; -} - int SDL_GetNumAudioDrivers(void) { return SDL_arraysize(bootstrap) - 1; @@ -780,16 +113,314 @@ const char *SDL_GetAudioDriver(int index) return NULL; } +const char *SDL_GetCurrentAudioDriver(void) +{ + return current_audio.name; +} + +/* device management and hotplug... */ + + +/* SDL_AudioDevice, in SDL3, represents a piece of hardware, whether it is in use or not, so these objects exist as long as + the system-level device is available. + + Devices get destroyed for three reasons: + - They were lost to the system (a USB cable is kicked out, etc). + - They failed for some other unlikely reason at the API level (which is _also_ probably a USB cable being kicked out). + - We are shutting down, so all allocated resources are being freed. + + They are _not_ destroyed because we are done using them (when we "close" a playing device). +*/ +static void CloseAudioDevice(SDL_AudioDevice *device); + + +/* this must not be called while `device` is still in a device list, or while a device's audio thread is still running (except if the thread calls this while shutting down). */ +static void DestroyAudioDevice(SDL_AudioDevice *device) +{ + SDL_AudioStream *stream; + SDL_AudioStream *next; + + if (!device) { + return; + } + + if (SDL_AtomicGet(&device->refcount) > 0) { + SDL_AtomicSet(&device->refcount, 0); /* it's going down NOW. */ + CloseAudioDevice(device); + } + + /* unbind any still-bound streams... */ + SDL_LockMutex(device->lock); + for (stream = device->bound_streams; stream != NULL; stream = next) { + SDL_LockMutex(stream->lock); + next = stream->next_binding; + stream->next_binding = NULL; + stream->prev_binding = NULL; + stream->bound_device = NULL; + SDL_UnlockMutex(stream->lock); + } + SDL_UnlockMutex(device->lock); + + current_audio.impl.FreeDeviceHandle(device->handle); + + SDL_DestroyMutex(device->lock); + SDL_free(device->work_buffer); + SDL_free(device->name); + SDL_free(device); +} + +static SDL_AudioDevice *CreateAudioDevice(const char *name, SDL_bool iscapture, SDL_AudioFormat fmt, int channels, int freq, void *handle, SDL_AudioDevice **devices, SDL_AtomicInt *device_count) +{ + SDL_AudioDevice *device; + + SDL_assert(name != NULL); + + if (SDL_AtomicGet(¤t_audio.shutting_down)) { + return NULL; /* we're shutting down, don't add any devices that are hotplugged at the last possible moment. */ + } + + device = (SDL_AudioDevice *)SDL_calloc(1, sizeof(SDL_AudioDevice)); + if (!device) { + SDL_OutOfMemory(); + return NULL; + } + + device->name = SDL_strdup(name); + if (!device->name) { + SDL_free(device); + SDL_OutOfMemory(); + return NULL; + } + + device->lock = SDL_CreateMutex(); + if (!device->lock) { + SDL_free(device->name); + SDL_free(device); + return NULL; + } + + SDL_AtomicSet(&device->shutdown, 0); + SDL_AtomicSet(&device->condemned, 0); + device->iscapture = iscapture; + device->format = device->default_format = fmt; + device->channels = device->default_channels = channels; + device->freq = device->default_freq = freq; + device->silence_value = SDL_GetSilenceValueForFormat(device->format); + device->handle = handle; + device->prev = NULL; + + /* Assign an instance id! Start at 2, in case there are things from the SDL2 era that still think 1 is a special value. */ + /* There's no reasonable scenario where this rolls over, but just in case, we wrap it in a loop. */ + /* Also, make sure capture instance IDs are even, and output IDs are odd, so we know what kind of device it is from just the ID. */ + do { + device->instance_id = (SDL_AudioDeviceID) (SDL_AtomicIncRef(¤t_audio.last_device_instance_id) + 1); + } while ( ((iscapture && (device->instance_id & 1)) || (!iscapture && ((device->instance_id & 1) == 0))) || (device->instance_id < 2) ); + + SDL_LockRWLockForWriting(current_audio.device_list_lock); + device->next = *devices; + *devices = device; + SDL_AtomicIncRef(device_count); + SDL_UnlockRWLock(current_audio.device_list_lock); + + return device; +} + +static SDL_AudioDevice *CreateAudioCaptureDevice(const char *name, SDL_AudioFormat fmt, int channels, int freq, void *handle) +{ + SDL_assert(current_audio.impl.HasCaptureSupport); + return CreateAudioDevice(name, SDL_TRUE, fmt, channels, freq, handle, ¤t_audio.capture_devices, ¤t_audio.capture_device_count); +} + +static SDL_AudioDevice *CreateAudioOutputDevice(const char *name, SDL_AudioFormat fmt, int channels, int freq, void *handle) +{ + return CreateAudioDevice(name, SDL_FALSE, fmt, channels, freq, handle, ¤t_audio.output_devices, ¤t_audio.output_device_count); +} + +/* The audio backends call this when a new device is plugged in. */ +SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, SDL_AudioFormat fmt, int channels, int freq, void *handle) +{ + SDL_AudioDevice *device; + + if (fmt == 0) { + fmt = DEFAULT_AUDIO_FORMAT; + } + if (channels == 0) { + channels = DEFAULT_AUDIO_CHANNELS; + } + if (freq == 0) { + freq = DEFAULT_AUDIO_FREQUENCY; + } + + device = iscapture ? CreateAudioCaptureDevice(name, fmt, channels, freq, handle) : CreateAudioOutputDevice(name, fmt, channels, freq, handle); + if (device) { + /* Post the event, if desired */ + if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_ADDED)) { + SDL_Event event; + event.type = SDL_EVENT_AUDIO_DEVICE_ADDED; + event.common.timestamp = 0; + event.adevice.which = device->instance_id; + event.adevice.iscapture = iscapture; + SDL_PushEvent(&event); + } + } + return device; +} + +/* Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread. */ +void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) +{ + SDL_bool was_live = SDL_FALSE; + SDL_bool should_destroy = SDL_TRUE; + + if (!device) { + return; + } + + /* take it out of the device list. */ + SDL_LockRWLockForWriting(current_audio.device_list_lock); + SDL_LockMutex(device->lock); /* make sure nothing else is messing with the device before continuing. */ + if (device == current_audio.output_devices) { + SDL_assert(device->prev == NULL); + current_audio.output_devices = device->next; + was_live = SDL_TRUE; + } else if (device == current_audio.capture_devices) { + SDL_assert(device->prev == NULL); + current_audio.capture_devices = device->next; + was_live = SDL_TRUE; + } + if (device->prev != NULL) { + device->prev->next = device->next; + was_live = SDL_TRUE; + } + if (device->next != NULL) { + device->next->prev = device->prev; + was_live = SDL_TRUE; + } + SDL_UnlockRWLock(current_audio.device_list_lock); + + /* now device is not in the list, and we own it, so no one should be able to find it again, except the audio thread, which holds a pointer! */ + SDL_AtomicSet(&device->condemned, 1); + SDL_AtomicSet(&device->shutdown, 1); /* tell audio thread to terminate. */ + if (device->thread) { + should_destroy = SDL_FALSE; /* if there's an audio thread, don't free until thread is terminating. */ + } + SDL_UnlockMutex(device->lock); + + /* Post the event, if we haven't tried to before and if it's desired */ + if (was_live && SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_REMOVED)) { + SDL_Event event; + event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED; + event.common.timestamp = 0; + event.adevice.which = device->instance_id; + event.adevice.iscapture = device->iscapture ? 1 : 0; + SDL_PushEvent(&event); + } + + if (should_destroy) { + DestroyAudioDevice(device); + } +} + + +/* stubs for audio drivers that don't need a specific entry point... */ + +static void SDL_AudioDetectDevices_Default(void) +{ + /* you have to write your own implementation if these assertions fail. */ + SDL_assert(current_audio.impl.OnlyHasDefaultOutputDevice); + SDL_assert(current_audio.impl.OnlyHasDefaultCaptureDevice || !current_audio.impl.HasCaptureSupport); + + SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, 0, 0, 0, (void *)((size_t)0x1)); + if (current_audio.impl.HasCaptureSupport) { + SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, 0, 0, 0, (void *)((size_t)0x2)); + } +} + +static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device) +{ /* no-op. */ +} + +static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) +{ /* no-op. */ +} + +static void SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) +{ /* no-op. */ +} + +static void SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, int buffer_size) +{ /* no-op. */ +} + +static Uint8 *SDL_AudioGetDeviceBuf_Default(SDL_AudioDevice *device, int *buffer_size) +{ + *buffer_size = 0; + return NULL; +} + +static int SDL_AudioCaptureFromDevice_Default(SDL_AudioDevice *device, void *buffer, int buflen) +{ + return -1; /* just fail immediately. */ +} + +static void SDL_AudioFlushCapture_Default(SDL_AudioDevice *device) +{ /* no-op. */ +} + +static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) +{ /* no-op. */ +} + +static void SDL_AudioDeinitialize_Default(void) +{ /* no-op. */ +} + +static void SDL_AudioFreeDeviceHandle_Default(void *handle) +{ /* no-op. */ +} + +static int SDL_AudioOpenDevice_Default(SDL_AudioDevice *device) +{ + return SDL_Unsupported(); +} + +/* Fill in stub functions for unused driver entry points. This lets us blindly call them without having to check for validity first. */ +static void CompleteAudioEntryPoints(void) +{ + #define FILL_STUB(x) if (!current_audio.impl.x) { current_audio.impl.x = SDL_Audio##x##_Default; } + FILL_STUB(DetectDevices); + FILL_STUB(OpenDevice); + FILL_STUB(ThreadInit); + FILL_STUB(ThreadDeinit); + FILL_STUB(WaitDevice); + FILL_STUB(PlayDevice); + FILL_STUB(GetDeviceBuf); + FILL_STUB(CaptureFromDevice); + FILL_STUB(FlushCapture); + FILL_STUB(CloseDevice); + FILL_STUB(FreeDeviceHandle); + FILL_STUB(Deinitialize); + #undef FILL_STUB +} + +/* !!! FIXME: the video subsystem does SDL_VideoInit, not SDL_InitVideo. Make this match. */ int SDL_InitAudio(const char *driver_name) { + SDL_RWLock *device_list_lock = NULL; + SDL_bool initialized = SDL_FALSE; + SDL_bool tried_to_init = SDL_FALSE; int i; - SDL_bool initialized = SDL_FALSE, tried_to_init = SDL_FALSE; if (SDL_GetCurrentAudioDriver()) { SDL_QuitAudio(); /* shutdown driver if already running. */ } - SDL_zeroa(open_devices); + SDL_ChooseAudioConverters(); + + device_list_lock = SDL_CreateRWLock(); /* create this early, so if it fails we don't have to tear down the whole audio subsystem. */ + if (!device_list_lock) { + return -1; + } /* Select the proper audio driver */ if (driver_name == NULL) { @@ -797,43 +428,46 @@ int SDL_InitAudio(const char *driver_name) } if (driver_name != NULL && *driver_name != 0) { - const char *driver_attempt = driver_name; - while (driver_attempt != NULL && *driver_attempt != 0 && !initialized) { - const char *driver_attempt_end = SDL_strchr(driver_attempt, ','); - size_t driver_attempt_len = (driver_attempt_end != NULL) ? (driver_attempt_end - driver_attempt) - : SDL_strlen(driver_attempt); -#ifdef SDL_AUDIO_DRIVER_DSOUND - /* SDL 1.2 uses the name "dsound", so we'll support both. */ - if (driver_attempt_len == SDL_strlen("dsound") && - (SDL_strncasecmp(driver_attempt, "dsound", driver_attempt_len) == 0)) { - driver_attempt = "directsound"; - driver_attempt_len = SDL_strlen("directsound"); - } -#endif + char *driver_name_copy = SDL_strdup(driver_name); + const char *driver_attempt = driver_name_copy; -#ifdef SDL_AUDIO_DRIVER_PULSEAUDIO - /* SDL 1.2 uses the name "pulse", so we'll support both. */ - if (driver_attempt_len == SDL_strlen("pulse") && - (SDL_strncasecmp(driver_attempt, "pulse", driver_attempt_len) == 0)) { - driver_attempt = "pulseaudio"; - driver_attempt_len = SDL_strlen("pulseaudio"); + if (driver_name_copy == NULL) { + SDL_DestroyRWLock(device_list_lock); + return SDL_OutOfMemory(); + } + + while (driver_attempt != NULL && *driver_attempt != 0 && !initialized) { + char *driver_attempt_end = SDL_strchr(driver_attempt, ','); + if (driver_attempt_end != NULL) { + *driver_attempt_end = '\0'; + } + + /* SDL 1.2 uses the name "dsound", so we'll support both. */ + if (SDL_strcmp(driver_attempt, "dsound") == 0) { + driver_attempt = "directsound"; + } else if (SDL_strcmp(driver_attempt, "pulse") == 0) { /* likewise, "pulse" was renamed to "pulseaudio" */ + driver_attempt = "pulseaudio"; } -#endif for (i = 0; bootstrap[i]; ++i) { - if ((driver_attempt_len == SDL_strlen(bootstrap[i]->name)) && - (SDL_strncasecmp(bootstrap[i]->name, driver_attempt, driver_attempt_len) == 0)) { + if (SDL_strcasecmp(bootstrap[i]->name, driver_attempt) == 0) { tried_to_init = SDL_TRUE; SDL_zero(current_audio); - current_audio.name = bootstrap[i]->name; - current_audio.desc = bootstrap[i]->desc; - initialized = bootstrap[i]->init(¤t_audio.impl); + SDL_AtomicSet(¤t_audio.last_device_instance_id, 2); /* start past 1 because of SDL2's legacy interface. */ + current_audio.device_list_lock = device_list_lock; + if (bootstrap[i]->init(¤t_audio.impl)) { + current_audio.name = bootstrap[i]->name; + current_audio.desc = bootstrap[i]->desc; + initialized = SDL_TRUE; + } break; } } driver_attempt = (driver_attempt_end != NULL) ? (driver_attempt_end + 1) : NULL; } + + SDL_free(driver_name_copy); } else { for (i = 0; (!initialized) && (bootstrap[i]); ++i) { if (bootstrap[i]->demand_only) { @@ -842,9 +476,13 @@ int SDL_InitAudio(const char *driver_name) tried_to_init = SDL_TRUE; SDL_zero(current_audio); - current_audio.name = bootstrap[i]->name; - current_audio.desc = bootstrap[i]->desc; - initialized = bootstrap[i]->init(¤t_audio.impl); + SDL_AtomicSet(¤t_audio.last_device_instance_id, 2); /* start past 1 because of SDL2's legacy interface. */ + current_audio.device_list_lock = device_list_lock; + if (bootstrap[i]->init(¤t_audio.impl)) { + current_audio.name = bootstrap[i]->name; + current_audio.desc = bootstrap[i]->desc; + initialized = SDL_TRUE; + } } } @@ -859,12 +497,11 @@ int SDL_InitAudio(const char *driver_name) } SDL_zero(current_audio); + SDL_DestroyRWLock(device_list_lock); return -1; /* No driver was available, so fail. */ } - current_audio.detectionLock = SDL_CreateMutex(); - - finish_audio_entry_points_init(); + CompleteAudioEntryPoints(); /* Make sure we have a list of devices available at startup. */ current_audio.impl.DetectDevices(); @@ -872,624 +509,754 @@ int SDL_InitAudio(const char *driver_name) return 0; } -/* - * Get the current audio driver name - */ -const char *SDL_GetCurrentAudioDriver(void) +void SDL_QuitAudio(void) { - return current_audio.name; -} + SDL_AudioDevice *devices = NULL; + SDL_AudioDevice *i; -/* Clean out devices that we've removed but had to keep around for stability. */ -static void clean_out_device_list(SDL_AudioDeviceItem **devices, int *devCount, SDL_bool *removedFlag) -{ - SDL_AudioDeviceItem *item = *devices; - SDL_AudioDeviceItem *prev = NULL; - int total = 0; + if (!current_audio.name) { /* not initialized?! */ + return; + } - while (item) { - SDL_AudioDeviceItem *next = item->next; - if (item->handle != NULL) { - total++; - prev = item; - } else { - if (prev) { - prev->next = next; - } else { - *devices = next; - } - /* these two pointers are the same if not a duplicate devname */ - if (item->name != item->original_name) { - SDL_free(item->name); - } - SDL_free(item->original_name); - SDL_free(item); + /* merge device lists so we don't have to duplicate work below. */ + SDL_LockRWLockForWriting(current_audio.device_list_lock); + SDL_AtomicSet(¤t_audio.shutting_down, 1); + for (i = current_audio.output_devices; i != NULL; i = i->next) { + devices = i; + } + if (!devices) { + devices = current_audio.capture_devices; + } else { + SDL_assert(devices->next == NULL); + devices->next = current_audio.capture_devices; + devices = current_audio.output_devices; + } + current_audio.output_devices = NULL; + current_audio.capture_devices = NULL; + SDL_UnlockRWLock(current_audio.device_list_lock); + + /* mark all devices for shutdown so all threads can begin to terminate. */ + for (i = devices; i != NULL; i = i->next) { + SDL_AtomicSet(&i->shutdown, 1); + } + + /* now wait on any audio threads. */ + for (i = devices; i != NULL; i = i->next) { + if (i->thread) { + SDL_assert(!SDL_AtomicGet(&i->condemned)); /* these shouldn't have been in the device list still, and thread should have detached. */ + SDL_WaitThread(i->thread, NULL); + i->thread = NULL; } - item = next; } - *devCount = total; - *removedFlag = SDL_FALSE; + while (devices) { + i = devices->next; + DestroyAudioDevice(devices); + devices = i; + } + + /* Free the driver data */ + current_audio.impl.Deinitialize(); + + SDL_DestroyRWLock(current_audio.device_list_lock); + + SDL_zero(current_audio); } -int SDL_GetNumAudioDevices(int iscapture) + + + +/* Output device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort. */ + +void SDL_OutputAudioThreadSetup(SDL_AudioDevice *device) { - int retval = 0; + SDL_assert(!device->iscapture); - if (!SDL_GetCurrentAudioDriver()) { - return -1; + /* The audio mixing is always a high priority thread */ +#ifdef SDL_AUDIO_DRIVER_ANDROID + Android_JNI_AudioSetThreadPriority(SDL_FALSE, device->id); +#else + SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL); +#endif + + /* Perform any thread setup */ + current_audio.impl.ThreadInit(device); +} + +SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) +{ + SDL_bool retval = SDL_TRUE; + SDL_AudioStream *stream; + int buffer_size = device->buffer_size; + Uint8 *mix_buffer; + + SDL_assert(!device->iscapture); + + SDL_LockMutex(device->lock); + + if (SDL_AtomicGet(&device->shutdown)) { + SDL_UnlockMutex(device->lock); + return SDL_FALSE; /* we're done, shut it down. */ } - SDL_LockMutex(current_audio.detectionLock); - if (iscapture && current_audio.captureDevicesRemoved) { - clean_out_device_list(¤t_audio.inputDevices, ¤t_audio.inputDeviceCount, ¤t_audio.captureDevicesRemoved); + mix_buffer = current_audio.impl.GetDeviceBuf(device, &buffer_size); + if (!mix_buffer) { + retval = SDL_FALSE; + } else { + SDL_assert(buffer_size <= device->buffer_size); /* you can ask for less, but not more. */ + SDL_memset(mix_buffer, device->silence_value, buffer_size); /* start with silence. */ + for (stream = device->bound_streams; stream != NULL; stream = stream->next_binding) { + /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. */ + /* (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind the same stream to different devices at the same time, though.) */ + const int br = SDL_GetAudioStreamData(stream, device->work_buffer, buffer_size); + if (br < 0) { + /* oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. */ + retval = SDL_FALSE; + break; + } else if (br > 0) { /* it's okay if we get less than requested, we mix what we have. */ + if (SDL_MixAudioFormat(mix_buffer, device->work_buffer, device->format, br, SDL_MIX_MAXVOLUME) < 0) { /* !!! FIXME: allow streams to specify gain? */ + SDL_assert(!"We probably ended up with some totally unexpected audio format here"); + retval = SDL_FALSE; /* uh...? */ + break; + } + } + } + /* !!! FIXME: have PlayDevice return a value and do disconnects in here with it. */ + current_audio.impl.PlayDevice(device, buffer_size); /* this SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice! */ } - if (!iscapture && current_audio.outputDevicesRemoved) { - clean_out_device_list(¤t_audio.outputDevices, ¤t_audio.outputDeviceCount, ¤t_audio.outputDevicesRemoved); - } + SDL_UnlockMutex(device->lock); - retval = iscapture ? current_audio.inputDeviceCount : current_audio.outputDeviceCount; - SDL_UnlockMutex(current_audio.detectionLock); + if (!retval) { + SDL_AudioDeviceDisconnected(device); /* doh. */ + } return retval; } -const char *SDL_GetAudioDeviceName(int index, int iscapture) +void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device) { - SDL_AudioDeviceItem *item; - int i; - const char *retval; + const int samples = (device->buffer_size / (SDL_AUDIO_BITSIZE(device->format) / 8)) / device->channels; + SDL_assert(!device->iscapture); + /* Wait for the audio to drain. */ /* !!! FIXME: don't bother waiting if device is lost. */ + SDL_Delay(((samples * 1000) / device->freq) * 2); + current_audio.impl.ThreadDeinit(device); + if (SDL_AtomicGet(&device->condemned)) { + SDL_DetachThread(device->thread); /* no one is waiting for us, just detach ourselves. */ + device->thread = NULL; + DestroyAudioDevice(device); + } +} + +/* thread entry point */ +static int SDLCALL OutputAudioThread(void *devicep) +{ + SDL_AudioDevice *device = (SDL_AudioDevice *)devicep; + SDL_assert(device != NULL); + SDL_assert(!device->iscapture); + SDL_OutputAudioThreadSetup(device); + do { + current_audio.impl.WaitDevice(device); + } while (SDL_OutputAudioThreadIterate(device)); + + SDL_OutputAudioThreadShutdown(device); + return 0; +} + + + +/* Capture device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort. */ + +void SDL_CaptureAudioThreadSetup(SDL_AudioDevice *device) +{ + SDL_assert(device->iscapture); + + /* The audio mixing is always a high priority thread */ +#ifdef SDL_AUDIO_DRIVER_ANDROID + Android_JNI_AudioSetThreadPriority(SDL_TRUE, device->id); +#else + SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); +#endif + + /* Perform any thread setup */ + current_audio.impl.ThreadInit(device); +} + +SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device) +{ + SDL_bool retval = SDL_TRUE; + + SDL_assert(device->iscapture); + + SDL_LockMutex(device->lock); + + if (SDL_AtomicGet(&device->shutdown)) { + retval = SDL_FALSE; /* we're done, shut it down. */ + } else if (device->bound_streams == NULL) { + current_audio.impl.FlushCapture(device); /* nothing wants data, dump anything pending. */ + } else { + const int rc = current_audio.impl.CaptureFromDevice(device, device->work_buffer, device->buffer_size); + if (rc < 0) { /* uhoh, device failed for some reason! */ + retval = SDL_FALSE; + } else if (rc > 0) { /* queue the new data to each bound stream. */ + SDL_AudioStream *stream; + for (stream = device->bound_streams; stream != NULL; stream = stream->next_binding) { + /* this will hold a lock on `stream` while putting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. */ + /* (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind the same stream to different devices at the same time, though.) */ + if (SDL_PutAudioStreamData(stream, device->work_buffer, rc) < 0) { + /* oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. */ + retval = SDL_FALSE; + break; + } + } + } + } + + SDL_UnlockMutex(device->lock); + + if (!retval) { + SDL_AudioDeviceDisconnected(device); /* doh. */ + } + + return retval; +} + +void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device) +{ + SDL_assert(device->iscapture); + current_audio.impl.FlushCapture(device); + current_audio.impl.ThreadDeinit(device); + if (SDL_AtomicGet(&device->condemned)) { + DestroyAudioDevice(device); + } +} + +/* thread entry point */ +static int SDLCALL CaptureAudioThread(void *devicep) +{ + SDL_AudioDevice *device = (SDL_AudioDevice *)devicep; + SDL_assert(device != NULL); + SDL_assert(device->iscapture); + SDL_CaptureAudioThreadSetup(device); + while (SDL_CaptureAudioThreadIterate(device)) { /* spin, CaptureAudioThreadIterate will block if necessary. !!! FIXME: maybe this is bad. */ } + SDL_CaptureAudioThreadShutdown(device); + return 0; +} + + +static SDL_AudioDeviceID *GetAudioDevices(int *reqcount, SDL_AudioDevice **devices, SDL_AtomicInt *device_count) +{ + SDL_AudioDeviceID *retval = NULL; + int num_devices = 0; if (!SDL_GetCurrentAudioDriver()) { SDL_SetError("Audio subsystem is not initialized"); return NULL; } - SDL_LockMutex(current_audio.detectionLock); - item = iscapture ? current_audio.inputDevices : current_audio.outputDevices; - i = iscapture ? current_audio.inputDeviceCount : current_audio.outputDeviceCount; - if (index >= 0 && index < i) { - for (i--; i > index; i--, item = item->next) { - SDL_assert(item != NULL); - } - SDL_assert(item != NULL); - retval = item->name; + SDL_LockRWLockForReading(current_audio.device_list_lock); + num_devices = SDL_AtomicGet(device_count); + retval = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID)); + if (retval == NULL) { + num_devices = 0; + SDL_OutOfMemory(); } else { - SDL_InvalidParamError("index"); - retval = NULL; + const SDL_AudioDevice *dev = *devices; + int i; + for (i = 0; i < num_devices; i++) { + SDL_assert(dev != NULL); + SDL_assert(!SDL_AtomicGet((SDL_AtomicInt *) &dev->condemned)); /* shouldn't be in the list if pending deletion. */ + retval[i] = dev->instance_id; + dev = dev->next; + } + SDL_assert(dev == NULL); /* did the whole list? */ + retval[num_devices] = 0; /* null-terminated. */ + } + SDL_UnlockRWLock(current_audio.device_list_lock); + + if (reqcount != NULL) { + *reqcount = num_devices; } - SDL_UnlockMutex(current_audio.detectionLock); return retval; } -int SDL_GetAudioDeviceSpec(int index, int iscapture, SDL_AudioSpec *spec) +SDL_AudioDeviceID *SDL_GetAudioOutputDevices(int *count) { - SDL_AudioDeviceItem *item; - int i, retval; + return GetAudioDevices(count, ¤t_audio.output_devices, ¤t_audio.output_device_count); +} - if (spec == NULL) { - return SDL_InvalidParamError("spec"); - } +SDL_AudioDeviceID *SDL_GetAudioCaptureDevices(int *count) +{ + return GetAudioDevices(count, ¤t_audio.capture_devices, ¤t_audio.capture_device_count); +} + + +/* this finds the device object associated with `devid` and locks it for use. */ +static SDL_AudioDevice *ObtainAudioDevice(SDL_AudioDeviceID devid) +{ + /* capture instance ids are even and output devices are odd, so we know which list to iterate from the devid. */ + const SDL_bool iscapture = (devid & 1) ? SDL_FALSE : SDL_TRUE; + SDL_AudioDevice *dev = NULL; if (!SDL_GetCurrentAudioDriver()) { - return SDL_SetError("Audio subsystem is not initialized"); + SDL_SetError("Audio subsystem is not initialized"); + return NULL; } - SDL_LockMutex(current_audio.detectionLock); - item = iscapture ? current_audio.inputDevices : current_audio.outputDevices; - i = iscapture ? current_audio.inputDeviceCount : current_audio.outputDeviceCount; - if (index >= 0 && index < i) { - for (i--; i > index; i--, item = item->next) { - SDL_assert(item != NULL); + SDL_LockRWLockForReading(current_audio.device_list_lock); + + for (dev = iscapture ? current_audio.capture_devices : current_audio.output_devices; dev != NULL; dev = dev->next) { + if (dev->instance_id == devid) { /* found it? */ + SDL_LockMutex(dev->lock); /* caller must unlock. */ + SDL_assert(!SDL_AtomicGet(&dev->condemned)); /* shouldn't be in the list if pending deletion. */ + break; } - SDL_assert(item != NULL); - SDL_copyp(spec, &item->spec); - retval = 0; - } else { - retval = SDL_InvalidParamError("index"); } - SDL_UnlockMutex(current_audio.detectionLock); - return retval; + SDL_UnlockRWLock(current_audio.device_list_lock); + + if (!dev) { + SDL_SetError("Invalid audio device instance ID"); + } + + return dev; } -int SDL_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) +SDL_AudioDevice *SDL_ObtainAudioDeviceByHandle(void *handle) { - if (spec == NULL) { - return SDL_InvalidParamError("spec"); - } + SDL_AudioDevice *dev = NULL; if (!SDL_GetCurrentAudioDriver()) { - return SDL_SetError("Audio subsystem is not initialized"); + SDL_SetError("Audio subsystem is not initialized"); + return NULL; } - if (current_audio.impl.GetDefaultAudioInfo == NULL) { - return SDL_Unsupported(); + SDL_LockRWLockForReading(current_audio.device_list_lock); + + for (dev = current_audio.output_devices; dev != NULL; dev = dev->next) { + if (dev->handle == handle) { /* found it? */ + SDL_LockMutex(dev->lock); /* caller must unlock. */ + SDL_assert(!SDL_AtomicGet(&dev->condemned)); /* shouldn't be in the list if pending deletion. */ + break; + } } - return current_audio.impl.GetDefaultAudioInfo(name, spec, iscapture); + if (!dev) { + for (dev = current_audio.capture_devices; dev != NULL; dev = dev->next) { + if (dev->handle == handle) { /* found it? */ + SDL_LockMutex(dev->lock); /* caller must unlock. */ + SDL_assert(!SDL_AtomicGet(&dev->condemned)); /* shouldn't be in the list if pending deletion. */ + break; + } + } + } + + SDL_UnlockRWLock(current_audio.device_list_lock); + + if (!dev) { + SDL_SetError("Device handle not found"); + } + + return dev; } -static void close_audio_device(SDL_AudioDevice *device) +char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) { + char *retval; + SDL_AudioDevice *device = ObtainAudioDevice(devid); if (!device) { - return; + return NULL; } - /* make sure the device is paused before we do anything else, so the - audio callback definitely won't fire again. */ - current_audio.impl.LockDevice(device); - SDL_AtomicSet(&device->paused, 1); - SDL_AtomicSet(&device->shutdown, 1); - SDL_AtomicSet(&device->enabled, 0); - current_audio.impl.UnlockDevice(device); + retval = SDL_strdup(device->name); + if (!retval) { + SDL_OutOfMemory(); + } + SDL_UnlockMutex(device->lock); + + return retval; +} + +int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioFormat *fmt, int *channels, int *freq) +{ + SDL_AudioDevice *device = ObtainAudioDevice(devid); + if (!device) { + return -1; + } + + if (fmt) { + *fmt = device->format; + } + if (channels) { + *channels = device->channels; + } + if (freq) { + *freq = device->freq; + } + + SDL_UnlockMutex(device->lock); + + return 0; +} + +/* this expects the device lock to be held. */ +static void CloseAudioDevice(SDL_AudioDevice *device) +{ if (device->thread != NULL) { + SDL_AtomicSet(&device->shutdown, 1); SDL_WaitThread(device->thread, NULL); - } - if (device->mixer_lock != NULL) { - SDL_DestroyMutex(device->mixer_lock); + device->thread = NULL; + SDL_AtomicSet(&device->shutdown, 0); } - SDL_free(device->work_buffer); - SDL_DestroyAudioStream(device->stream); + current_audio.impl.CloseDevice(device); - if (device->id > 0) { - SDL_AudioDevice *opendev = open_devices[device->id - 1]; - SDL_assert((opendev == device) || (opendev == NULL)); - if (opendev == device) { - open_devices[device->id - 1] = NULL; + if (device->work_buffer) { + SDL_aligned_free(device->work_buffer); + device->work_buffer = NULL; + } + + device->format = device->default_format; + device->channels = device->default_channels; + device->freq = device->default_freq; + device->sample_frames = 0; + device->silence_value = SDL_GetSilenceValueForFormat(device->format); +} + +void SDL_CloseAudioDevice(SDL_AudioDeviceID devid) +{ + SDL_AudioDevice *device = ObtainAudioDevice(devid); + if (device) { /* if NULL, maybe it was already lost? */ + if (SDL_AtomicDecRef(&device->refcount)) { + SDL_UnlockMutex(device->lock); /* can't hold the lock or the audio thread will deadlock while we WaitThread it. */ + CloseAudioDevice(device); + } else { + if (SDL_AtomicGet(&device->refcount) < 0) { + SDL_AtomicSet(&device->refcount, 0); /* something closed more than it should, force this back to zero. Best we can do. */ + } + SDL_UnlockMutex(device->lock); /* can't hold the lock or the audio thread will deadlock while we WaitThread it. */ + } + } +} + + +static SDL_AudioFormat ParseAudioFormatString(const char *string) +{ + if (string) { +#define CHECK_FMT_STRING(x) \ + if (SDL_strcmp(string, #x) == 0) \ + return SDL_AUDIO_##x + CHECK_FMT_STRING(U8); + CHECK_FMT_STRING(S8); + CHECK_FMT_STRING(S16LSB); + CHECK_FMT_STRING(S16MSB); + CHECK_FMT_STRING(S16); + CHECK_FMT_STRING(S32LSB); + CHECK_FMT_STRING(S32MSB); + CHECK_FMT_STRING(S32SYS); + CHECK_FMT_STRING(S32); + CHECK_FMT_STRING(F32LSB); + CHECK_FMT_STRING(F32MSB); + CHECK_FMT_STRING(F32SYS); + CHECK_FMT_STRING(F32); +#undef CHECK_FMT_STRING + } + return 0; +} + +static void PrepareAudioFormat(SDL_AudioFormat *fmt, int *channels, int *freq) +{ + const char *env; + + if (*freq == 0) { + *freq = DEFAULT_AUDIO_FREQUENCY; + env = SDL_getenv("SDL_AUDIO_FREQUENCY"); + if (env != NULL) { + const int val = SDL_atoi(env); + if (val > 0) { + *freq = val; + } } } - if (device->hidden != NULL) { - current_audio.impl.CloseDevice(device); + if (*channels == 0) { + *channels = DEFAULT_AUDIO_CHANNELS; + env = SDL_getenv("SDL_AUDIO_CHANNELS"); + if (env != NULL) { + const int val = SDL_atoi(env); + if (val > 0) { + *channels = val; + } + } } - SDL_DestroyDataQueue(device->buffer_queue); - - SDL_free(device); + if (*fmt == 0) { + const SDL_AudioFormat val = ParseAudioFormatString(SDL_getenv("SDL_AUDIO_FORMAT")); + *fmt = (val != 0) ? val : DEFAULT_AUDIO_FORMAT; + } } -static Uint16 GetDefaultSamplesFromFreq(int freq) +static int GetDefaultSampleFramesFromFreq(int freq) { - /* Pick a default of ~46 ms at desired frequency */ - /* !!! FIXME: remove this when the non-Po2 resampling is in. */ - const Uint16 max_sample = (Uint16)((freq / 1000) * 46); - Uint16 current_sample = 1; + /* Pick the closest power-of-two to ~46 ms at desired frequency */ + const int max_sample = ((freq / 1000) * 46); + int current_sample = 1; while (current_sample < max_sample) { current_sample *= 2; } return current_sample; } -/* - * Sanity check desired AudioSpec for SDL_OpenAudio() in (orig). - * Fills in a sanitized copy in (prepared). - * Returns non-zero if okay, zero on fatal parameters in (orig). - */ -static int prepare_audiospec(const SDL_AudioSpec *orig, SDL_AudioSpec *prepared) +void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device) { - SDL_copyp(prepared, orig); - - if (orig->freq == 0) { - static const int DEFAULT_FREQ = 22050; - const char *env = SDL_getenv("SDL_AUDIO_FREQUENCY"); - if (env != NULL) { - int freq = SDL_atoi(env); - prepared->freq = freq != 0 ? freq : DEFAULT_FREQ; - } else { - prepared->freq = DEFAULT_FREQ; - } - } - - if (orig->format == 0) { - const char *env = SDL_getenv("SDL_AUDIO_FORMAT"); - if (env != NULL) { - const SDL_AudioFormat format = SDL_ParseAudioFormat(env); - prepared->format = format != 0 ? format : SDL_AUDIO_S16; - } else { - prepared->format = SDL_AUDIO_S16; - } - } - - if (orig->channels == 0) { - const char *env = SDL_getenv("SDL_AUDIO_CHANNELS"); - if (env != NULL) { - Uint8 channels = (Uint8)SDL_atoi(env); - prepared->channels = channels != 0 ? channels : 2; - } else { - prepared->channels = 2; - } - } else if (orig->channels > 8) { - SDL_SetError("Unsupported number of audio channels."); - return 0; - } - - if (orig->samples == 0) { - const char *env = SDL_getenv("SDL_AUDIO_SAMPLES"); - if (env != NULL) { - Uint16 samples = (Uint16)SDL_atoi(env); - prepared->samples = samples != 0 ? samples : GetDefaultSamplesFromFreq(prepared->freq); - } else { - prepared->samples = GetDefaultSamplesFromFreq(prepared->freq); - } - } - - /* Calculate the silence and size of the audio specification */ - SDL_CalculateAudioSpec(prepared); - - return 1; + device->silence_value = SDL_GetSilenceValueForFormat(device->format); + device->buffer_size = device->sample_frames * (SDL_AUDIO_BITSIZE(device->format) / 8) * device->channels; } -static SDL_AudioDeviceID open_audio_device(const char *devname, int iscapture, - const SDL_AudioSpec *desired, SDL_AudioSpec *obtained, - int allowed_changes, int min_id) +/* this expects the device lock to be held. */ +static int OpenAudioDevice(SDL_AudioDevice *device, SDL_AudioFormat fmt, int channels, int freq) +{ + SDL_assert(SDL_AtomicGet(&device->refcount) == 1); + + PrepareAudioFormat(&fmt, &channels, &freq); + + /* we allow the device format to change if it's better than the current settings (by various definitions of "better"). This prevents + something low quality, like an old game using S8/8000Hz audio, from ruining a music thing playing at CD quality that tries to open later. + (or some VoIP library that opens for mono output ruining your surround-sound game because it got there first). */ + /* These are just requests! The backend may change any of these values during OpenDevice method! */ + device->format = (SDL_AUDIO_BITSIZE(device->default_format) >= SDL_AUDIO_BITSIZE(fmt)) ? device->default_format : fmt; + device->freq = SDL_max(device->default_freq, freq); + device->channels = SDL_max(device->default_channels, channels); + device->sample_frames = GetDefaultSampleFramesFromFreq(device->freq); + SDL_UpdatedAudioDeviceFormat(device); /* start this off sane. */ + + if (current_audio.impl.OpenDevice(device) < 0) { + CloseAudioDevice(device); /* clean up anything the backend left half-initialized. */ + return -1; + } + + SDL_UpdatedAudioDeviceFormat(device); /* in case the backend changed things and forgot to call this. */ + + /* Allocate a scratch audio buffer */ + device->work_buffer = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), device->buffer_size); + if (device->work_buffer == NULL) { + CloseAudioDevice(device); + return SDL_OutOfMemory(); + } + + /* Start the audio thread if necessary */ + if (!current_audio.impl.ProvidesOwnCallbackThread) { + const size_t stacksize = 64 * 1024; /* We only need a little space, so make the stack tiny. We may adjust this as necessary later. */ + char threadname[64]; + + (void)SDL_snprintf(threadname, sizeof(threadname), "SDLAudio%c%d", (device->iscapture) ? 'C' : 'P', (int) device->instance_id); + device->thread = SDL_CreateThreadInternal(device->iscapture ? CaptureAudioThread : OutputAudioThread, threadname, stacksize, device); + + if (device->thread == NULL) { + CloseAudioDevice(device); + return SDL_SetError("Couldn't create audio thread"); + } + } + + return 0; +} + +SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, SDL_AudioFormat fmt, int channels, int freq) +{ + SDL_AudioDevice *device = ObtainAudioDevice(devid); /* !!! FIXME: need to choose default device for devid==0 */ + int retval = 0; + + if (device) { + retval = device->instance_id; + if (SDL_AtomicIncRef(&device->refcount) == 0) { + if (OpenAudioDevice(device, fmt, channels, freq) == -1) { + retval = 0; + } + } + SDL_UnlockMutex(device->lock); + } + + return retval; +} + +int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int num_streams) { - const SDL_bool is_internal_thread = (desired->callback == NULL); - SDL_AudioDeviceID id = 0; - SDL_AudioSpec _obtained; SDL_AudioDevice *device; - SDL_bool build_stream; - void *handle = NULL; - int i = 0; + int retval = 0; + int i; - if (!SDL_GetCurrentAudioDriver()) { - SDL_SetError("Audio subsystem is not initialized"); - return 0; + if (num_streams == 0) { + return 0; /* nothing to do */ + } else if (num_streams < 0) { + return SDL_InvalidParamError("num_streams"); + } else if (streams == NULL) { + return SDL_InvalidParamError("streams"); + } else if ((device = ObtainAudioDevice(devid)) == NULL) { + return -1; /* ObtainAudioDevice set the error message. */ + } else if (SDL_AtomicGet(&device->refcount) == 0) { + SDL_UnlockMutex(device->lock); + return SDL_SetError("Device is not opened"); } - if (iscapture && !current_audio.impl.HasCaptureSupport) { - SDL_SetError("No capture support"); - return 0; - } + SDL_assert(!device->bound_streams || (device->bound_streams->prev_binding == NULL)); - SDL_LockMutex(current_audio.detectionLock); - /* Find an available device ID... */ - for (id = min_id - 1; id < SDL_arraysize(open_devices); id++) { - if (open_devices[id] == NULL) { + /* lock all the streams upfront, so we can verify they aren't bound elsewhere and add them all in one block, as this is intended to add everything or nothing. */ + for (i = 0; i < num_streams; i++) { + SDL_AudioStream *stream = streams[i]; + if (stream == NULL) { + retval = SDL_SetError("Stream #%d is NULL", i); + } else { + SDL_LockMutex(stream->lock); + SDL_assert((stream->bound_device == NULL) == ((stream->prev_binding == NULL) || (stream->next_binding == NULL))); + if (stream->bound_device) { + retval = SDL_SetError("Stream #%d is already bound to a device", i); + } + } + + if (retval != 0) { + int j; + for (j = 0; j <= i; j++) { + SDL_UnlockMutex(streams[j]->lock); + } break; } } - if (id == SDL_arraysize(open_devices)) { - SDL_SetError("Too many open audio devices"); - SDL_UnlockMutex(current_audio.detectionLock); - return 0; - } + if (retval == 0) { + /* Now that everything is verified, chain everything together. */ + for (i = 0; i < num_streams; i++) { + SDL_AudioStream *stream = streams[i]; + SDL_AudioFormat src_format, dst_format; + int src_channels, dst_channels; + int src_rate, dst_rate; - if (!obtained) { - obtained = &_obtained; - } - if (!prepare_audiospec(desired, obtained)) { - SDL_UnlockMutex(current_audio.detectionLock); - return 0; - } - - /* If app doesn't care about a specific device, let the user override. */ - if (devname == NULL) { - devname = SDL_getenv("SDL_AUDIO_DEVICE_NAME"); - } - - /* - * Catch device names at the high level for the simple case... - * This lets us have a basic "device enumeration" for systems that - * don't have multiple devices, but makes sure the device name is - * always NULL when it hits the low level. - * - * Also make sure that the simple case prevents multiple simultaneous - * opens of the default system device. - */ - - if ((iscapture) && (current_audio.impl.OnlyHasDefaultCaptureDevice)) { - if ((devname) && (SDL_strcmp(devname, DEFAULT_INPUT_DEVNAME) != 0)) { - SDL_SetError("No such device"); - SDL_UnlockMutex(current_audio.detectionLock); - return 0; - } - devname = NULL; - - for (i = 0; i < SDL_arraysize(open_devices); i++) { - if ((open_devices[i]) && (open_devices[i]->iscapture)) { - SDL_SetError("Audio device already open"); - SDL_UnlockMutex(current_audio.detectionLock); - return 0; + /* set the proper end of the stream to the device's format. */ + SDL_GetAudioStreamFormat(stream, &src_format, &src_channels, &src_rate, &dst_format, &dst_channels, &dst_rate); + if (device->iscapture) { + SDL_SetAudioStreamFormat(stream, device->format, device->channels, device->freq, dst_format, dst_channels, dst_rate); + } else { + SDL_SetAudioStreamFormat(stream, src_format, src_channels, src_rate, device->format, device->channels, device->freq); } - } - } else if ((!iscapture) && (current_audio.impl.OnlyHasDefaultOutputDevice)) { - if ((devname) && (SDL_strcmp(devname, DEFAULT_OUTPUT_DEVNAME) != 0)) { - SDL_UnlockMutex(current_audio.detectionLock); - SDL_SetError("No such device"); - return 0; - } - devname = NULL; - for (i = 0; i < SDL_arraysize(open_devices); i++) { - if ((open_devices[i]) && (!open_devices[i]->iscapture)) { - SDL_UnlockMutex(current_audio.detectionLock); - SDL_SetError("Audio device already open"); - return 0; + stream->bound_device = device; + stream->prev_binding = NULL; + stream->next_binding = device->bound_streams; + if (device->bound_streams) { + device->bound_streams->prev_binding = stream; } + device->bound_streams = stream; + + SDL_UnlockMutex(stream->lock); } - } else if (devname != NULL) { - /* if the app specifies an exact string, we can pass the backend - an actual device handle thingey, which saves them the effort of - figuring out what device this was (such as, reenumerating - everything again to find the matching human-readable name). - It might still need to open a device based on the string for, - say, a network audio server, but this optimizes some cases. */ - SDL_AudioDeviceItem *item; - for (item = iscapture ? current_audio.inputDevices : current_audio.outputDevices; item; item = item->next) { - if ((item->handle != NULL) && (SDL_strcmp(item->name, devname) == 0)) { - handle = item->handle; - break; + } + + SDL_UnlockMutex(device->lock); + + return retval; +} + +int SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_AudioStream *stream) +{ + return SDL_BindAudioStreams(devid, &stream, 1); +} + +void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams) +{ + int i; + + /* to prevent deadlock when holding both locks, we _must_ lock the device first, and the stream second, as that is the order the audio thread will do it. + But this means we have an unlikely, pathological case where a stream could change its binding between when we lookup its bound device and when we lock everything, + so we double-check here. */ + for (i = 0; i < num_streams; i++) { + SDL_AudioStream *stream = streams[i]; + if (!stream) { + continue; /* nothing to do, it's a NULL stream. */ + } + + while (SDL_TRUE) { + SDL_AudioDevice *bounddev; + + SDL_LockMutex(stream->lock); /* lock to check this and then release it, in case the device isn't locked yet. */ + bounddev = stream->bound_device; + SDL_UnlockMutex(stream->lock); + + /* lock in correct order. */ + if (bounddev) { + SDL_LockMutex(bounddev->lock); /* this requires recursive mutexes, since we're likely locking the same device multiple times. */ + } + SDL_LockMutex(stream->lock); + + if (bounddev == stream->bound_device) { + break; /* the binding didn't change in the small window where it could, so we're good. */ + } else { + SDL_UnlockMutex(stream->lock); /* it changed bindings! Try again. */ + if (bounddev) { + SDL_UnlockMutex(bounddev->lock); + } } } } - if (!current_audio.impl.AllowsArbitraryDeviceNames) { - /* has to be in our device list, or the default device. */ - if ((handle == NULL) && (devname != NULL)) { - SDL_SetError("No such device."); - SDL_UnlockMutex(current_audio.detectionLock); - return 0; + /* everything is locked, start unbinding streams. */ + for (i = 0; i < num_streams; i++) { + SDL_AudioStream *stream = streams[i]; + if (stream && stream->bound_device) { + if (stream->bound_device->bound_streams == stream) { + SDL_assert(stream->prev_binding == NULL); + stream->bound_device->bound_streams = stream->next_binding; + } + if (stream->prev_binding) { + stream->prev_binding->next_binding = stream->next_binding; + } + if (stream->next_binding) { + stream->next_binding->prev_binding = stream->prev_binding; + } + stream->prev_binding = stream->next_binding = NULL; } } - device = (SDL_AudioDevice *)SDL_calloc(1, sizeof(SDL_AudioDevice)); - if (device == NULL) { - SDL_OutOfMemory(); - SDL_UnlockMutex(current_audio.detectionLock); - return 0; - } - device->id = id + 1; - device->spec = *obtained; - device->iscapture = iscapture ? SDL_TRUE : SDL_FALSE; - device->handle = handle; - - SDL_AtomicSet(&device->shutdown, 0); /* just in case. */ - SDL_AtomicSet(&device->paused, 1); - SDL_AtomicSet(&device->enabled, 1); - - /* Create a mutex for locking the sound buffers */ - if (current_audio.impl.LockDevice == SDL_AudioLockDevice_Default) { - device->mixer_lock = SDL_CreateMutex(); - if (device->mixer_lock == NULL) { - close_audio_device(device); - SDL_UnlockMutex(current_audio.detectionLock); - SDL_SetError("Couldn't create mixer lock"); - return 0; + /* Finalize and unlock everything. */ + for (i = 0; i < num_streams; i++) { + SDL_AudioStream *stream = streams[i]; + if (stream && stream->bound_device) { + SDL_AudioDevice *dev = stream->bound_device; + stream->bound_device = NULL; + SDL_UnlockMutex(stream->lock); + if (dev) { + SDL_UnlockMutex(dev->lock); + } } } +} - /* For backends that require a power-of-two value for spec.samples, take the - * value we got from 'desired' and round up to the nearest value - */ - if (!current_audio.impl.SupportsNonPow2Samples && device->spec.samples > 0) { - int samples = SDL_powerof2(device->spec.samples); - if (samples <= SDL_MAX_UINT16) { - device->spec.samples = (Uint16)samples; - } else { - SDL_SetError("Couldn't hold sample count %d\n", samples); - return 0; - } - } +void SDL_UnbindAudioStream(SDL_AudioStream *stream) +{ + SDL_UnbindAudioStreams(&stream, 1); +} - if (current_audio.impl.OpenDevice(device, devname) < 0) { - close_audio_device(device); - SDL_UnlockMutex(current_audio.detectionLock); - return 0; - } - /* if your target really doesn't need it, set it to 0x1 or something. */ - /* otherwise, close_audio_device() won't call impl.CloseDevice(). */ - SDL_assert(device->hidden != NULL); - - /* See if we need to do any conversion */ - build_stream = SDL_FALSE; - if (obtained->freq != device->spec.freq) { - if (allowed_changes & SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) { - obtained->freq = device->spec.freq; - } else { - build_stream = SDL_TRUE; - } - } - if (obtained->format != device->spec.format) { - if (allowed_changes & SDL_AUDIO_ALLOW_FORMAT_CHANGE) { - obtained->format = device->spec.format; - } else { - build_stream = SDL_TRUE; - } - } - if (obtained->channels != device->spec.channels) { - if (allowed_changes & SDL_AUDIO_ALLOW_CHANNELS_CHANGE) { - obtained->channels = device->spec.channels; - } else { - build_stream = SDL_TRUE; - } - } - if (device->spec.samples != obtained->samples) { - if (allowed_changes & SDL_AUDIO_ALLOW_SAMPLES_CHANGE) { - obtained->samples = device->spec.samples; - } else { - build_stream = SDL_TRUE; - } - } - - SDL_CalculateAudioSpec(obtained); /* recalc after possible changes. */ - - device->callbackspec = *obtained; - - if (build_stream) { +SDL_AudioStream *SDL_CreateAndBindAudioStream(SDL_AudioDeviceID devid, SDL_AudioFormat fmt, int channels, int freq) +{ + SDL_AudioStream *stream = NULL; + SDL_AudioDevice *device = ObtainAudioDevice(devid); + if (device) { + const SDL_bool iscapture = (devid & 1) ? SDL_FALSE : SDL_TRUE; /* capture instance ids are even and output devices are odd */ if (iscapture) { - device->stream = SDL_CreateAudioStream(device->spec.format, - device->spec.channels, device->spec.freq, - obtained->format, obtained->channels, obtained->freq); + stream = SDL_CreateAudioStream(device->format, device->channels, device->freq, fmt, channels, freq); } else { - device->stream = SDL_CreateAudioStream(obtained->format, obtained->channels, - obtained->freq, device->spec.format, - device->spec.channels, device->spec.freq); + stream = SDL_CreateAudioStream(fmt, channels, freq, device->format, device->channels, device->freq); } - if (!device->stream) { - close_audio_device(device); - SDL_UnlockMutex(current_audio.detectionLock); - return 0; + if (stream) { + if (SDL_BindAudioStream(devid, stream) == -1) { + SDL_DestroyAudioStream(stream); + stream = NULL; + } } + SDL_UnlockMutex(device->lock); } - - if (device->spec.callback == NULL) { /* use buffer queueing? */ - /* pool a few packets to start. Enough for two callbacks. */ - device->buffer_queue = SDL_CreateDataQueue(SDL_AUDIOBUFFERQUEUE_PACKETLEN, obtained->size * 2); - if (!device->buffer_queue) { - close_audio_device(device); - SDL_UnlockMutex(current_audio.detectionLock); - SDL_SetError("Couldn't create audio buffer queue"); - return 0; - } - device->callbackspec.callback = iscapture ? SDL_BufferQueueFillCallback : SDL_BufferQueueDrainCallback; - device->callbackspec.userdata = device; - } - - /* Allocate a scratch audio buffer */ - device->work_buffer_len = build_stream ? device->callbackspec.size : 0; - if (device->spec.size > device->work_buffer_len) { - device->work_buffer_len = device->spec.size; - } - SDL_assert(device->work_buffer_len > 0); - - device->work_buffer = (Uint8 *)SDL_malloc(device->work_buffer_len); - if (device->work_buffer == NULL) { - close_audio_device(device); - SDL_UnlockMutex(current_audio.detectionLock); - SDL_OutOfMemory(); - return 0; - } - - open_devices[id] = device; /* add it to our list of open devices. */ - - /* Start the audio thread if necessary */ - if (!current_audio.impl.ProvidesOwnCallbackThread) { - /* Start the audio thread */ - /* !!! FIXME: we don't force the audio thread stack size here if it calls into user code, but maybe we should? */ - /* buffer queueing callback only needs a few bytes, so make the stack tiny. */ - const size_t stacksize = is_internal_thread ? 64 * 1024 : 0; - char threadname[64]; - - (void)SDL_snprintf(threadname, sizeof(threadname), "SDLAudio%c%" SDL_PRIu32, (iscapture) ? 'C' : 'P', device->id); - device->thread = SDL_CreateThreadInternal(iscapture ? SDL_CaptureAudio : SDL_RunAudio, threadname, stacksize, device); - - if (device->thread == NULL) { - close_audio_device(device); - SDL_SetError("Couldn't create audio thread"); - SDL_UnlockMutex(current_audio.detectionLock); - return 0; - } - } - SDL_UnlockMutex(current_audio.detectionLock); - - return device->id; -} - -SDL_AudioDeviceID SDL_OpenAudioDevice(const char *device, int iscapture, - const SDL_AudioSpec *desired, SDL_AudioSpec *obtained, - int allowed_changes) -{ - return open_audio_device(device, iscapture, desired, obtained, - allowed_changes, 2); -} - -SDL_AudioStatus SDL_GetAudioDeviceStatus(SDL_AudioDeviceID devid) -{ - SDL_AudioDevice *device = get_audio_device(devid); - SDL_AudioStatus status = SDL_AUDIO_STOPPED; - if (device && SDL_AtomicGet(&device->enabled)) { - if (SDL_AtomicGet(&device->paused)) { - status = SDL_AUDIO_PAUSED; - } else { - status = SDL_AUDIO_PLAYING; - } - } - return status; -} - -int SDL_PauseAudioDevice(SDL_AudioDeviceID devid) -{ - SDL_AudioDevice *device = get_audio_device(devid); - if (!device) { - return SDL_InvalidParamError("devid"); - } - current_audio.impl.LockDevice(device); - SDL_AtomicSet(&device->paused, 1); - current_audio.impl.UnlockDevice(device); - return 0; -} - -int SDL_PlayAudioDevice(SDL_AudioDeviceID devid) -{ - SDL_AudioDevice *device = get_audio_device(devid); - if (!device) { - return SDL_InvalidParamError("devid"); - } - current_audio.impl.LockDevice(device); - SDL_AtomicSet(&device->paused, 0); - current_audio.impl.UnlockDevice(device); - return 0; -} - -int SDL_LockAudioDevice(SDL_AudioDeviceID devid) -{ - /* Obtain a lock on the mixing buffers */ - SDL_AudioDevice *device = get_audio_device(devid); - if (!device) { - return SDL_InvalidParamError("devid"); - } - current_audio.impl.LockDevice(device); - return 0; -} - -void SDL_UnlockAudioDevice(SDL_AudioDeviceID devid) -{ - /* Obtain a lock on the mixing buffers */ - SDL_AudioDevice *device = get_audio_device(devid); - if (!device) { - return; - } - current_audio.impl.UnlockDevice(device); -} - -void SDL_CloseAudioDevice(SDL_AudioDeviceID devid) -{ - SDL_AudioDevice *device = get_audio_device(devid); - if (!device) { - return; - } - close_audio_device(device); -} - -void SDL_QuitAudio(void) -{ - SDL_AudioDeviceID i; - - if (!current_audio.name) { /* not initialized?! */ - return; - } - - for (i = 0; i < SDL_arraysize(open_devices); i++) { - close_audio_device(open_devices[i]); - } - - free_device_list(¤t_audio.outputDevices, ¤t_audio.outputDeviceCount); - free_device_list(¤t_audio.inputDevices, ¤t_audio.inputDeviceCount); - - /* Free the driver data */ - current_audio.impl.Deinitialize(); - - SDL_DestroyMutex(current_audio.detectionLock); - - SDL_zero(current_audio); - SDL_zeroa(open_devices); + return stream; } #define NUM_FORMATS 8 @@ -1515,69 +1282,8 @@ const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format) return &format_list[0][NUM_FORMATS]; /* not found; return what looks like a list with only a zero in it. */ } -Uint8 SDL_GetSilenceValueForFormat(const SDL_AudioFormat format) +int SDL_GetSilenceValueForFormat(SDL_AudioFormat format) { return (format == SDL_AUDIO_U8) ? 0x80 : 0x00; } -void SDL_CalculateAudioSpec(SDL_AudioSpec *spec) -{ - spec->silence = SDL_GetSilenceValueForFormat(spec->format); - spec->size = SDL_AUDIO_BITSIZE(spec->format) / 8; - spec->size *= spec->channels; - spec->size *= spec->samples; -} - -/* !!! FIXME: move this to SDL_audiocvt.c */ -int SDL_ConvertAudioSamples(SDL_AudioFormat src_format, Uint8 src_channels, int src_rate, const Uint8 *src_data, int src_len, - SDL_AudioFormat dst_format, Uint8 dst_channels, int dst_rate, Uint8 **dst_data, int *dst_len) -{ - int ret = -1; - SDL_AudioStream *stream = NULL; - Uint8 *dst = NULL; - int dstlen = 0; - - if (dst_data) { - *dst_data = NULL; - } - - if (dst_len) { - *dst_len = 0; - } - - if (src_data == NULL) { - return SDL_InvalidParamError("src_data"); - } else if (src_len < 0) { - return SDL_InvalidParamError("src_len"); - } else if (dst_data == NULL) { - return SDL_InvalidParamError("dst_data"); - } else if (dst_len == NULL) { - return SDL_InvalidParamError("dst_len"); - } - - stream = SDL_CreateAudioStream(src_format, src_channels, src_rate, dst_format, dst_channels, dst_rate); - if (stream != NULL) { - if ((SDL_PutAudioStreamData(stream, src_data, src_len) == 0) && (SDL_FlushAudioStream(stream) == 0)) { - dstlen = SDL_GetAudioStreamAvailable(stream); - if (dstlen >= 0) { - dst = (Uint8 *)SDL_malloc(dstlen); - if (!dst) { - SDL_OutOfMemory(); - } else { - ret = (SDL_GetAudioStreamData(stream, dst, dstlen) >= 0) ? 0 : -1; - } - } - } - } - - if (ret == -1) { - SDL_free(dst); - } else { - *dst_data = dst; - *dst_len = dstlen; - } - - SDL_DestroyAudioStream(stream); - return ret; -} - diff --git a/src/audio/SDL_audio_c.h b/src/audio/SDL_audio_c.h index d69184a54c..7b00ceacc9 100644 --- a/src/audio/SDL_audio_c.h +++ b/src/audio/SDL_audio_c.h @@ -22,59 +22,8 @@ #ifndef SDL_audio_c_h_ #define SDL_audio_c_h_ -#include "SDL_internal.h" +/* !!! FIXME: remove this header and have things just include SDL_sysaudio.h directly. */ -#define DEBUG_AUDIOSTREAM 0 -#define DEBUG_AUDIO_CONVERT 0 - -#if DEBUG_AUDIO_CONVERT -#define LOG_DEBUG_AUDIO_CONVERT(from, to) SDL_Log("SDL_AUDIO_CONVERT: Converting %s to %s.\n", from, to); -#else -#define LOG_DEBUG_AUDIO_CONVERT(from, to) -#endif - -/* Functions and variables exported from SDL_audio.c for SDL_sysaudio.c */ - -/* Function to get a list of audio formats, ordered most similar to `format` to least, 0-terminated. Don't free results. */ -const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format); - -/* Function to calculate the size and silence for a SDL_AudioSpec */ -extern Uint8 SDL_GetSilenceValueForFormat(const SDL_AudioFormat format); -extern void SDL_CalculateAudioSpec(SDL_AudioSpec *spec); - -/* Must be called at least once before using converters (SDL_CreateAudioStream will call it). */ -extern void SDL_ChooseAudioConverters(void); - -/* These pointers get set during SDL_ChooseAudioConverters() to various SIMD implementations. */ -extern void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples); -extern void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples); -extern void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples); -extern void (*SDL_Convert_S32_to_F32)(float *dst, const Sint32 *src, int num_samples); -extern void (*SDL_Convert_F32_to_S8)(Sint8 *dst, const float *src, int num_samples); -extern void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples); -extern void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples); -extern void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples); - -/** - * Use this function to initialize a particular audio driver. - * - * This function is used internally, and should not be used unless you have a - * specific need to designate the audio driver you want to use. You should - * normally use SDL_Init() or SDL_InitSubSystem(). - * - * \param driver_name the name of the desired audio driver - * \returns 0 on success or a negative error code on failure; call - * SDL_GetError() for more information. - */ -extern int SDL_InitAudio(const char *driver_name); - -/** - * Use this function to shut down audio if you initialized it with SDL_InitAudio(). - * - * This function is used internally, and should not be used unless you have a - * specific need to specify the audio driver you want to use. You should - * normally use SDL_Quit() or SDL_QuitSubSystem(). - */ -extern void SDL_QuitAudio(void); +#include "SDL_sysaudio.h" #endif /* SDL_audio_c_h_ */ diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index 299dfd1075..f5d7b9c314 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -434,49 +434,6 @@ static void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_fo SDL_assert(src == dst); /* if we got here, we _had_ to have done _something_. Otherwise, we should have memcpy'd! */ } -struct SDL_AudioStream -{ - SDL_DataQueue *queue; - SDL_Mutex *lock; /* this is just a copy of `queue`'s mutex. We share a lock. */ - - Uint8 *work_buffer; /* used for scratch space during data conversion/resampling. */ - Uint8 *history_buffer; /* history for left padding and future sample rate changes. */ - Uint8 *future_buffer; /* stuff that left the queue for the right padding and will be next read's data. */ - float *left_padding; /* left padding for resampling. */ - float *right_padding; /* right padding for resampling. */ - - SDL_bool flushed; - - size_t work_buffer_allocation; - size_t history_buffer_allocation; - size_t future_buffer_allocation; - size_t resampler_padding_allocation; - - int resampler_padding_frames; - int history_buffer_frames; - int future_buffer_filled_frames; - - int max_sample_frame_size; - - int src_sample_frame_size; - SDL_AudioFormat src_format; - int src_channels; - int src_rate; - - int dst_sample_frame_size; - SDL_AudioFormat dst_format; - int dst_channels; - int dst_rate; - - int pre_resample_channels; - int packetlen; -}; - -static int GetMemsetSilenceValue(const SDL_AudioFormat fmt) -{ - return (fmt == SDL_AUDIO_U8) ? 0x80 : 0x00; -} - /* figure out the largest thing we might need for ConvertAudio, which might grow data in-place. */ static int CalculateMaxSampleFrameSize(SDL_AudioFormat src_format, int src_channels, SDL_AudioFormat dst_format, int dst_channels) { @@ -560,7 +517,7 @@ static int SetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioFormat src_for if (stream->future_buffer) { ConvertAudio(stream->future_buffer_filled_frames, stream->future_buffer, stream->src_format, stream->src_channels, future_buffer, src_format, src_channels); } else if (future_buffer != NULL) { - SDL_memset(future_buffer, GetMemsetSilenceValue(src_format), future_buffer_allocation); + SDL_memset(future_buffer, SDL_GetSilenceValueForFormat(src_format), future_buffer_allocation); } if (stream->history_buffer) { @@ -568,10 +525,10 @@ static int SetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioFormat src_for ConvertAudio(history_buffer_frames, stream->history_buffer, stream->src_format, stream->src_channels, history_buffer, src_format, src_channels); } else { ConvertAudio(prev_history_buffer_frames, stream->history_buffer, stream->src_format, stream->src_channels, history_buffer + ((history_buffer_frames - prev_history_buffer_frames) * src_sample_frame_size), src_format, src_channels); - SDL_memset(history_buffer, GetMemsetSilenceValue(src_format), (history_buffer_frames - prev_history_buffer_frames) * src_sample_frame_size); /* silence oldest history samples. */ + SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(src_format), (history_buffer_frames - prev_history_buffer_frames) * src_sample_frame_size); /* silence oldest history samples. */ } } else if (history_buffer != NULL) { - SDL_memset(history_buffer, GetMemsetSilenceValue(src_format), history_buffer_allocation); + SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(src_format), history_buffer_allocation); } if (future_buffer != stream->future_buffer) { @@ -612,6 +569,8 @@ SDL_AudioStream *SDL_CreateAudioStream(SDL_AudioFormat src_format, int packetlen = 4096; /* !!! FIXME: good enough for now. */ SDL_AudioStream *retval; + /* !!! FIXME: fail if audio isn't initialized? */ + if (!SDL_IsSupportedChannelCount(src_channels)) { SDL_InvalidParamError("src_channels"); return NULL; @@ -917,7 +876,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le stream->future_buffer_filled_frames = future_buffer_filled_frames; if (br < cpy) { /* we couldn't fill the future buffer with enough padding! */ if (stream->flushed) { /* that's okay, we're flushing, just silence the still-needed padding. */ - SDL_memset(future_buffer + (future_buffer_filled_frames * src_sample_frame_size), GetMemsetSilenceValue(src_format), cpy - br); + SDL_memset(future_buffer + (future_buffer_filled_frames * src_sample_frame_size), SDL_GetSilenceValueForFormat(src_format), cpy - br); } else { /* Drastic measures: steal from the work buffer! */ const int stealcpyframes = SDL_min(workbuf_frames, cpyframes - brframes); const int stealcpy = stealcpyframes * src_sample_frame_size; @@ -1100,7 +1059,7 @@ int SDL_ClearAudioStream(SDL_AudioStream *stream) SDL_LockMutex(stream->lock); SDL_ClearDataQueue(stream->queue, (size_t)stream->packetlen * 2); - SDL_memset(stream->history_buffer, GetMemsetSilenceValue(stream->src_format), stream->history_buffer_frames * stream->src_channels * sizeof (float)); + SDL_memset(stream->history_buffer, SDL_GetSilenceValueForFormat(stream->src_format), stream->history_buffer_frames * stream->src_channels * sizeof (float)); stream->future_buffer_filled_frames = 0; stream->flushed = SDL_FALSE; SDL_UnlockMutex(stream->lock); @@ -1110,6 +1069,7 @@ int SDL_ClearAudioStream(SDL_AudioStream *stream) void SDL_DestroyAudioStream(SDL_AudioStream *stream) { if (stream) { + SDL_UnbindAudioStream(stream); /* do not destroy stream->lock! it's a copy of `stream->queue`'s mutex, so destroying the queue will handle it. */ SDL_DestroyDataQueue(stream->queue); SDL_aligned_free(stream->work_buffer); @@ -1120,3 +1080,56 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream) SDL_free(stream); } } + +int SDL_ConvertAudioSamples(SDL_AudioFormat src_format, int src_channels, int src_rate, const Uint8 *src_data, int src_len, + SDL_AudioFormat dst_format, int dst_channels, int dst_rate, Uint8 **dst_data, int *dst_len) +{ + int ret = -1; + SDL_AudioStream *stream = NULL; + Uint8 *dst = NULL; + int dstlen = 0; + + if (dst_data) { + *dst_data = NULL; + } + + if (dst_len) { + *dst_len = 0; + } + + if (src_data == NULL) { + return SDL_InvalidParamError("src_data"); + } else if (src_len < 0) { + return SDL_InvalidParamError("src_len"); + } else if (dst_data == NULL) { + return SDL_InvalidParamError("dst_data"); + } else if (dst_len == NULL) { + return SDL_InvalidParamError("dst_len"); + } + + stream = SDL_CreateAudioStream(src_format, src_channels, src_rate, dst_format, dst_channels, dst_rate); + if (stream != NULL) { + if ((SDL_PutAudioStreamData(stream, src_data, src_len) == 0) && (SDL_FlushAudioStream(stream) == 0)) { + dstlen = SDL_GetAudioStreamAvailable(stream); + if (dstlen >= 0) { + dst = (Uint8 *)SDL_malloc(dstlen); + if (!dst) { + SDL_OutOfMemory(); + } else { + ret = (SDL_GetAudioStreamData(stream, dst, dstlen) >= 0) ? 0 : -1; + } + } + } + } + + if (ret == -1) { + SDL_free(dst); + } else { + *dst_data = dst; + *dst_len = dstlen; + } + + SDL_DestroyAudioStream(stream); + return ret; +} + diff --git a/src/audio/SDL_audiodev.c b/src/audio/SDL_audiodev.c index b26740dbee..50bcddf86c 100644 --- a/src/audio/SDL_audiodev.c +++ b/src/audio/SDL_audiodev.c @@ -63,7 +63,7 @@ static void test_device(const int iscapture, const char *fname, int flags, int ( * information, making this information inaccessible at * enumeration time */ - SDL_AddAudioDevice(iscapture, fname, NULL, (void *)(uintptr_t)dummyhandle); + SDL_AddAudioDevice(iscapture, fname, 0, 0, 0, (void *)(uintptr_t)dummyhandle, ); } } } diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 7c6fd29851..7fa95c8120 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -24,60 +24,90 @@ #define SDL_sysaudio_h_ #include "../SDL_dataqueue.h" -#include "./SDL_audio_c.h" + +#define DEBUG_AUDIOSTREAM 0 +#define DEBUG_AUDIO_CONVERT 0 + +#if DEBUG_AUDIO_CONVERT +#define LOG_DEBUG_AUDIO_CONVERT(from, to) SDL_Log("SDL_AUDIO_CONVERT: Converting %s to %s.\n", from, to); +#else +#define LOG_DEBUG_AUDIO_CONVERT(from, to) +#endif + +/* These pointers get set during SDL_ChooseAudioConverters() to various SIMD implementations. */ +extern void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples); +extern void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples); +extern void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples); +extern void (*SDL_Convert_S32_to_F32)(float *dst, const Sint32 *src, int num_samples); +extern void (*SDL_Convert_F32_to_S8)(Sint8 *dst, const float *src, int num_samples); +extern void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples); +extern void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples); +extern void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples); + /* !!! FIXME: These are wordy and unlocalized... */ #define DEFAULT_OUTPUT_DEVNAME "System audio output device" #define DEFAULT_INPUT_DEVNAME "System audio capture device" +/* these are used when no better specifics are known. We default to CD audio quality. */ +#define DEFAULT_AUDIO_FORMAT SDL_AUDIO_S16 +#define DEFAULT_AUDIO_CHANNELS 2 +#define DEFAULT_AUDIO_FREQUENCY 44100 + + /* The SDL audio driver */ typedef struct SDL_AudioDevice SDL_AudioDevice; +/* Used by src/SDL.c to initialize a particular audio driver. */ +extern int SDL_InitAudio(const char *driver_name); + +/* Used by src/SDL.c to shut down previously-initialized audio. */ +extern void SDL_QuitAudio(void); + +/* Function to get a list of audio formats, ordered most similar to `format` to least, 0-terminated. Don't free results. */ +const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format); + +/* Must be called at least once before using converters (SDL_CreateAudioStream will call it !!! FIXME but probably shouldn't). */ +extern void SDL_ChooseAudioConverters(void); + /* Audio targets should call this as devices are added to the system (such as a USB headset being plugged in), and should also be called for for every device found during DetectDevices(). */ -extern void SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, SDL_AudioSpec *spec, void *handle); +extern SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, SDL_AudioFormat fmt, int channels, int freq, void *handle); -/* Audio targets should call this as devices are removed, so SDL can update - its list of available devices. */ -extern void SDL_RemoveAudioDevice(const SDL_bool iscapture, void *handle); +/* Audio targets should call this if an opened audio device is lost. + This can happen due to i/o errors, or a device being unplugged, etc. */ +extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device); -/* Audio targets should call this if an opened audio device is lost while - being used. This can happen due to i/o errors, or a device being unplugged, - etc. If the device is totally gone, please also call SDL_RemoveAudioDevice() - as appropriate so SDL's list of devices is accurate. */ -extern void SDL_OpenedAudioDeviceDisconnected(SDL_AudioDevice *device); +/* Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. Locks the device! You must unlock!! */ +extern SDL_AudioDevice *SDL_ObtainAudioDeviceByHandle(void *handle); -/* This is the size of a packet when using SDL_QueueAudio(). We allocate - these as necessary and pool them, under the assumption that we'll - eventually end up with a handful that keep recycling, meeting whatever - the app needs. We keep packing data tightly as more arrives to avoid - wasting space, and if we get a giant block of data, we'll split them - into multiple packets behind the scenes. My expectation is that most - apps will have 2-3 of these in the pool. 8k should cover most needs, but - if this is crippling for some embedded system, we can #ifdef this. - The system preallocates enough packets for 2 callbacks' worth of data. */ -#define SDL_AUDIOBUFFERQUEUE_PACKETLEN (8 * 1024) +/* Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct. */ +extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device); + + +/* These functions are the heart of the audio threads. Backends can call them directly if they aren't using the SDL-provided thread. */ +extern void SDL_OutputAudioThreadSetup(SDL_AudioDevice *device); +extern SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device); +extern void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device); +extern void SDL_CaptureAudioThreadSetup(SDL_AudioDevice *device); +extern SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device); +extern void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device); typedef struct SDL_AudioDriverImpl { void (*DetectDevices)(void); - int (*OpenDevice)(SDL_AudioDevice *_this, const char *devname); - void (*ThreadInit)(SDL_AudioDevice *_this); /* Called by audio thread at start */ - void (*ThreadDeinit)(SDL_AudioDevice *_this); /* Called by audio thread at end */ - void (*WaitDevice)(SDL_AudioDevice *_this); - void (*PlayDevice)(SDL_AudioDevice *_this); - Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *_this); - int (*CaptureFromDevice)(SDL_AudioDevice *_this, void *buffer, int buflen); - void (*FlushCapture)(SDL_AudioDevice *_this); - void (*CloseDevice)(SDL_AudioDevice *_this); - void (*LockDevice)(SDL_AudioDevice *_this); - void (*UnlockDevice)(SDL_AudioDevice *_this); + int (*OpenDevice)(SDL_AudioDevice *device); + void (*ThreadInit)(SDL_AudioDevice *device); /* Called by audio thread at start */ + void (*ThreadDeinit)(SDL_AudioDevice *device); /* Called by audio thread at end */ + void (*WaitDevice)(SDL_AudioDevice *device); + void (*PlayDevice)(SDL_AudioDevice *device, int buffer_size); + Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size); + int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen); + void (*FlushCapture)(SDL_AudioDevice *device); + void (*CloseDevice)(SDL_AudioDevice *device); void (*FreeDeviceHandle)(void *handle); /**< SDL is done with handle from SDL_AddAudioDevice() */ void (*Deinitialize)(void); - int (*GetDefaultAudioInfo)(char **name, SDL_AudioSpec *spec, int iscapture); - - /* !!! FIXME: add pause(), so we can optimize instead of mixing silence. */ /* Some flags to push duplicate code into the core and reduce #ifdefs. */ SDL_bool ProvidesOwnCallbackThread; @@ -88,81 +118,120 @@ typedef struct SDL_AudioDriverImpl SDL_bool SupportsNonPow2Samples; } SDL_AudioDriverImpl; -typedef struct SDL_AudioDeviceItem -{ - void *handle; - char *name; - char *original_name; - SDL_AudioSpec spec; - int dupenum; - struct SDL_AudioDeviceItem *next; -} SDL_AudioDeviceItem; - typedef struct SDL_AudioDriver { - /* * * */ - /* The name of this audio driver */ - const char *name; - - /* * * */ - /* The description of this audio driver */ - const char *desc; - - SDL_AudioDriverImpl impl; - - /* A mutex for device detection */ - SDL_Mutex *detectionLock; - SDL_bool captureDevicesRemoved; - SDL_bool outputDevicesRemoved; - int outputDeviceCount; - int inputDeviceCount; - SDL_AudioDeviceItem *outputDevices; - SDL_AudioDeviceItem *inputDevices; + const char *name; /* The name of this audio driver */ + const char *desc; /* The description of this audio driver */ + SDL_AudioDriverImpl impl; /* the backend's interface */ + SDL_RWLock *device_list_lock; /* A mutex for device detection */ + SDL_AudioDevice *output_devices; /* the list of currently-available audio output devices. */ + SDL_AudioDevice *capture_devices; /* the list of currently-available audio capture devices. */ + SDL_AtomicInt output_device_count; + SDL_AtomicInt capture_device_count; + SDL_AtomicInt last_device_instance_id; /* increments on each device add to provide unique instance IDs */ + SDL_AtomicInt shutting_down; /* non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs. */ } SDL_AudioDriver; -/* Define the SDL audio driver structure */ +struct SDL_AudioStream +{ + SDL_DataQueue *queue; + SDL_Mutex *lock; /* this is just a copy of `queue`'s mutex. We share a lock. */ + + Uint8 *work_buffer; /* used for scratch space during data conversion/resampling. */ + Uint8 *history_buffer; /* history for left padding and future sample rate changes. */ + Uint8 *future_buffer; /* stuff that left the queue for the right padding and will be next read's data. */ + float *left_padding; /* left padding for resampling. */ + float *right_padding; /* right padding for resampling. */ + + SDL_bool flushed; + + size_t work_buffer_allocation; + size_t history_buffer_allocation; + size_t future_buffer_allocation; + size_t resampler_padding_allocation; + + int resampler_padding_frames; + int history_buffer_frames; + int future_buffer_filled_frames; + + int max_sample_frame_size; + + int src_sample_frame_size; + SDL_AudioFormat src_format; + int src_channels; + int src_rate; + + int dst_sample_frame_size; + SDL_AudioFormat dst_format; + int dst_channels; + int dst_rate; + + int pre_resample_channels; + int packetlen; + + SDL_AudioDevice *bound_device; + SDL_AudioStream *next_binding; + SDL_AudioStream *prev_binding; +}; + struct SDL_AudioDevice { - /* * * */ - /* Data common to all devices */ - SDL_AudioDeviceID id; + /* A mutex for locking access to this struct */ + SDL_Mutex *lock; + + /* human-readable name of the device. ("SoundBlaster Pro 16") */ + char *name; + + /* the unique instance ID of this device. */ + SDL_AudioDeviceID instance_id; + + /* a way for the backend to identify this device _when not opened_ */ + void *handle; /* The device's current audio specification */ - SDL_AudioSpec spec; + SDL_AudioFormat format; + int freq; + int channels; + Uint32 buffer_size; - /* The callback's expected audio specification (converted vs device's spec). */ - SDL_AudioSpec callbackspec; + /* The device's default audio specification */ + SDL_AudioFormat default_format; + int default_freq; + int default_channels; - /* Stream that converts and resamples. NULL if not needed. */ - SDL_AudioStream *stream; + /* Number of sample frames the devices wants per-buffer. */ + int sample_frames; - /* Current state flags */ - SDL_AtomicInt shutdown; /* true if we are signaling the play thread to end. */ - SDL_AtomicInt enabled; /* true if device is functioning and connected. */ - SDL_AtomicInt paused; + /* Value to use for SDL_memset to silence a buffer in this device's format */ + int silence_value; + + /* non-zero if we are signaling the audio thread to end. */ + SDL_AtomicInt shutdown; + + /* non-zero if we want the device to be destroyed (so audio thread knows to do it on termination). */ + SDL_AtomicInt condemned; + + /* SDL_TRUE if this is a capture device instead of an output device */ SDL_bool iscapture; - /* Scratch buffer used in the bridge between SDL and the user callback. */ + /* Scratch buffer used for mixing. */ Uint8 *work_buffer; - /* Size, in bytes, of work_buffer. */ - Uint32 work_buffer_len; - - /* A mutex for locking the mixing buffers */ - SDL_Mutex *mixer_lock; - /* A thread to feed the audio device */ SDL_Thread *thread; - SDL_threadID threadid; - /* Queued buffers (if app not using callback). */ - SDL_DataQueue *buffer_queue; - - /* * * */ /* Data private to this driver */ struct SDL_PrivateAudioData *hidden; - void *handle; + /* Each device open increases the refcount. We actually close the system device when this hits zero again. */ + SDL_AtomicInt refcount; + + /* double-linked list of all audio streams currently bound to this device. */ + SDL_AudioStream *bound_streams; + + /* double-linked list of all devices. */ + struct SDL_AudioDevice *prev; + struct SDL_AudioDevice *next; }; typedef struct AudioBootStrap @@ -170,7 +239,7 @@ typedef struct AudioBootStrap const char *name; const char *desc; SDL_bool (*init)(SDL_AudioDriverImpl *impl); - SDL_bool demand_only; /* 1==request explicitly, or it won't be available. */ + SDL_bool demand_only; /* if SDL_TRUE: request explicitly, or it won't be available. */ } AudioBootStrap; /* Not all of these are available in a given build. Use #ifdefs, etc. */ @@ -188,8 +257,8 @@ extern AudioBootStrap HAIKUAUDIO_bootstrap; extern AudioBootStrap COREAUDIO_bootstrap; extern AudioBootStrap DISKAUDIO_bootstrap; extern AudioBootStrap DUMMYAUDIO_bootstrap; -extern AudioBootStrap aaudio_bootstrap; -extern AudioBootStrap openslES_bootstrap; +extern AudioBootStrap aaudio_bootstrap; /* !!! FIXME: capitalize this to match the others */ +extern AudioBootStrap openslES_bootstrap; /* !!! FIXME: capitalize this to match the others */ extern AudioBootStrap ANDROIDAUDIO_bootstrap; extern AudioBootStrap PS2AUDIO_bootstrap; extern AudioBootStrap PSPAUDIO_bootstrap; diff --git a/src/audio/SDL_wave.c b/src/audio/SDL_wave.c index 3f442042c8..2e3175333a 100644 --- a/src/audio/SDL_wave.c +++ b/src/audio/SDL_wave.c @@ -1241,7 +1241,7 @@ static int LAW_Decode(WaveFile *file, Uint8 **audio_buf, Uint32 *audio_len) dst = (Sint16 *)src; - /* Work backwards, since we're expanding in-place. SDL_AudioSpec.format will + /* Work backwards, since we're expanding in-place. `format` will * inform the caller about the byte order. */ i = sample_count; @@ -1667,15 +1667,13 @@ static int WaveCheckFormat(WaveFile *file, size_t datalength) if (format->channels == 0) { return SDL_SetError("Invalid number of channels"); - } else if (format->channels > 255) { - /* Limit given by SDL_AudioSpec.channels. */ - return SDL_SetError("Number of channels exceeds limit of 255"); + } else if (format->channels > INT_MAX) { + return SDL_SetError("Number of channels exceeds limit of %d", INT_MAX); } if (format->frequency == 0) { return SDL_SetError("Invalid sample rate"); } else if (format->frequency > INT_MAX) { - /* Limit given by SDL_AudioSpec.freq. */ return SDL_SetError("Sample rate exceeds limit of %d", INT_MAX); } @@ -1766,7 +1764,7 @@ static int WaveCheckFormat(WaveFile *file, size_t datalength) return 0; } -static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) +static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioFormat *fmt, int *channels, int *freq, Uint8 **audio_buf, Uint32 *audio_len) { int result; Uint32 chunkcount = 0; @@ -2025,13 +2023,11 @@ static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioSpec *spec, Uint8 * break; } - /* Setting up the SDL_AudioSpec. All unsupported formats were filtered out + /* Setting up the specs. All unsupported formats were filtered out * by checks earlier in this function. */ - SDL_zerop(spec); - spec->freq = format->frequency; - spec->channels = (Uint8)format->channels; - spec->samples = 4096; /* Good default buffer size */ + *freq = format->frequency; + *channels = (Uint8)format->channels; switch (format->encoding) { case MS_ADPCM_CODE: @@ -2039,22 +2035,22 @@ static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioSpec *spec, Uint8 * case ALAW_CODE: case MULAW_CODE: /* These can be easily stored in the byte order of the system. */ - spec->format = SDL_AUDIO_S16SYS; + *fmt = SDL_AUDIO_S16SYS; break; case IEEE_FLOAT_CODE: - spec->format = SDL_AUDIO_F32LSB; + *fmt = SDL_AUDIO_F32LSB; break; case PCM_CODE: switch (format->bitspersample) { case 8: - spec->format = SDL_AUDIO_U8; + *fmt = SDL_AUDIO_U8; break; case 16: - spec->format = SDL_AUDIO_S16LSB; + *fmt = SDL_AUDIO_S16LSB; break; case 24: /* Has been shifted to 32 bits. */ case 32: - spec->format = SDL_AUDIO_S32LSB; + *fmt = SDL_AUDIO_S32LSB; break; default: /* Just in case something unexpected happened in the checks. */ @@ -2063,8 +2059,6 @@ static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioSpec *spec, Uint8 * break; } - spec->silence = SDL_GetSilenceValueForFormat(spec->format); - /* Report the end position back to the cleanup code. */ if (RIFFlengthknown) { chunk->position = RIFFend; @@ -2075,39 +2069,37 @@ static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioSpec *spec, Uint8 * return 0; } -SDL_AudioSpec *SDL_LoadWAV_RW(SDL_RWops *src, SDL_bool freesrc, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) +int SDL_LoadWAV_RW(SDL_RWops *src, int freesrc, SDL_AudioFormat *fmt, int *channels, int *freq, Uint8 **audio_buf, Uint32 *audio_len) { int result = -1; WaveFile file; - SDL_zero(file); - /* Make sure we are passed a valid data source */ if (src == NULL) { - /* Error may come from RWops. */ - goto done; - } else if (spec == NULL) { - SDL_InvalidParamError("spec"); - goto done; + return -1; /* Error may come from RWops. */ + } else if (fmt == NULL) { + return SDL_InvalidParamError("fmt"); + } else if (channels == NULL) { + return SDL_InvalidParamError("channels"); + } else if (freq == NULL) { + return SDL_InvalidParamError("freq"); } else if (audio_buf == NULL) { - SDL_InvalidParamError("audio_buf"); - goto done; + return SDL_InvalidParamError("audio_buf"); } else if (audio_len == NULL) { - SDL_InvalidParamError("audio_len"); - goto done; + return SDL_InvalidParamError("audio_len"); } *audio_buf = NULL; *audio_len = 0; + SDL_zero(file); file.riffhint = WaveGetRiffSizeHint(); file.trunchint = WaveGetTruncationHint(); file.facthint = WaveGetFactChunkHint(); - result = WaveLoad(src, &file, spec, audio_buf, audio_len); + result = WaveLoad(src, &file, fmt, channels, freq, audio_buf, audio_len); if (result < 0) { SDL_free(*audio_buf); - spec = NULL; audio_buf = NULL; audio_len = 0; } @@ -2119,13 +2111,5 @@ SDL_AudioSpec *SDL_LoadWAV_RW(SDL_RWops *src, SDL_bool freesrc, SDL_AudioSpec *s WaveFreeChunkData(&file.chunk); SDL_free(file.decoderdata); -done: - if (freesrc && src) { - SDL_RWclose(src); - } - if (result == 0) { - return spec; - } else { - return NULL; - } + return result; } diff --git a/src/audio/disk/SDL_diskaudio.c b/src/audio/disk/SDL_diskaudio.c index d351ed90a0..2e83a111b8 100644 --- a/src/audio/disk/SDL_diskaudio.c +++ b/src/audio/disk/SDL_diskaudio.c @@ -24,10 +24,6 @@ /* Output raw audio data to a file. */ -#ifdef HAVE_STDIO_H -#include -#endif - #include "../SDL_audio_c.h" #include "SDL_diskaudio.h" @@ -40,34 +36,34 @@ #define DISKENVR_IODELAY "SDL_DISKAUDIODELAY" /* This function waits until it is possible to write a full sound buffer */ -static void DISKAUDIO_WaitDevice(SDL_AudioDevice *_this) +static void DISKAUDIO_WaitDevice(SDL_AudioDevice *device) { - SDL_Delay(_this->hidden->io_delay); + SDL_Delay(device->hidden->io_delay); } -static void DISKAUDIO_PlayDevice(SDL_AudioDevice *_this) +static void DISKAUDIO_PlayDevice(SDL_AudioDevice *device, int buffer_size) { - const Sint64 written = SDL_RWwrite(_this->hidden->io, - _this->hidden->mixbuf, - _this->spec.size); + const Sint64 written = SDL_RWwrite(device->hidden->io, + device->hidden->mixbuf, + buffer_size); /* If we couldn't write, assume fatal error for now */ - if (written != _this->spec.size) { - SDL_OpenedAudioDeviceDisconnected(_this); + if (written != buffer_size) { + SDL_AudioDeviceDisconnected(device); } #ifdef DEBUG_AUDIO - fprintf(stderr, "Wrote %d bytes of audio data\n", (int) written); + SDL_Log("DISKAUDIO: Wrote %d bytes of audio data", (int) written); #endif } -static Uint8 *DISKAUDIO_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *DISKAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - return _this->hidden->mixbuf; + return device->hidden->mixbuf; } -static int DISKAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static int DISKAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { - struct SDL_PrivateAudioData *h = _this->hidden; + struct SDL_PrivateAudioData *h = device->hidden; const int origbuflen = buflen; SDL_Delay(h->io_delay); @@ -83,70 +79,66 @@ static int DISKAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int } /* if we ran out of file, just write silence. */ - SDL_memset(buffer, _this->spec.silence, buflen); + SDL_memset(buffer, device->silence_value, buflen); return origbuflen; } -static void DISKAUDIO_FlushCapture(SDL_AudioDevice *_this) +static void DISKAUDIO_FlushCapture(SDL_AudioDevice *device) { /* no op...we don't advance the file pointer or anything. */ } -static void DISKAUDIO_CloseDevice(SDL_AudioDevice *_this) +static void DISKAUDIO_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden->io != NULL) { - SDL_RWclose(_this->hidden->io); + if (device->hidden->io != NULL) { + SDL_RWclose(device->hidden->io); } - SDL_free(_this->hidden->mixbuf); - SDL_free(_this->hidden); + SDL_free(device->hidden->mixbuf); + SDL_free(device->hidden); } -static const char *get_filename(const SDL_bool iscapture, const char *devname) +static const char *get_filename(const SDL_bool iscapture) { + const char *devname = SDL_getenv(iscapture ? DISKENVR_INFILE : DISKENVR_OUTFILE); if (devname == NULL) { - devname = SDL_getenv(iscapture ? DISKENVR_INFILE : DISKENVR_OUTFILE); - if (devname == NULL) { - devname = iscapture ? DISKDEFAULT_INFILE : DISKDEFAULT_OUTFILE; - } + devname = iscapture ? DISKDEFAULT_INFILE : DISKDEFAULT_OUTFILE; } return devname; } -static int DISKAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device) { - void *handle = _this->handle; - /* handle != NULL means "user specified the placeholder name on the fake detected device list" */ - SDL_bool iscapture = _this->iscapture; - const char *fname = get_filename(iscapture, handle ? NULL : devname); + SDL_bool iscapture = device->iscapture; + const char *fname = get_filename(iscapture); const char *envr = SDL_getenv(DISKENVR_IODELAY); - _this->hidden = (struct SDL_PrivateAudioData *) - SDL_malloc(sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + device->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc(sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(_this->hidden); + SDL_zerop(device->hidden); if (envr != NULL) { - _this->hidden->io_delay = SDL_atoi(envr); + device->hidden->io_delay = SDL_atoi(envr); } else { - _this->hidden->io_delay = ((_this->spec.samples * 1000) / _this->spec.freq); + device->hidden->io_delay = ((device->sample_frames * 1000) / device->freq); } - /* Open the audio device */ - _this->hidden->io = SDL_RWFromFile(fname, iscapture ? "rb" : "wb"); - if (_this->hidden->io == NULL) { + /* Open the "audio device" */ + device->hidden->io = SDL_RWFromFile(fname, iscapture ? "rb" : "wb"); + if (device->hidden->io == NULL) { return -1; } /* Allocate mixing buffer */ if (!iscapture) { - _this->hidden->mixbuf = (Uint8 *)SDL_malloc(_this->spec.size); - if (_this->hidden->mixbuf == NULL) { + device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); + if (device->hidden->mixbuf == NULL) { return SDL_OutOfMemory(); } - SDL_memset(_this->hidden->mixbuf, _this->spec.silence, _this->spec.size); + SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); } SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, @@ -161,8 +153,8 @@ static int DISKAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) static void DISKAUDIO_DetectDevices(void) { - SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1); - SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2); + SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, 0, 0, 0, (void *)0x1); + SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, 0, 0, 0, (void *)0x2); } static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl) diff --git a/src/audio/dummy/SDL_dummyaudio.c b/src/audio/dummy/SDL_dummyaudio.c index 1f12a1a2af..ce1ecca5dc 100644 --- a/src/audio/dummy/SDL_dummyaudio.c +++ b/src/audio/dummy/SDL_dummyaudio.c @@ -25,20 +25,35 @@ #include "../SDL_audio_c.h" #include "SDL_dummyaudio.h" -static int DUMMYAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) -{ - _this->hidden = (void *)0x1; /* just something non-NULL */ +/* !!! FIXME: add a dummy WaitDevice to simulate real audio better? */ - return 0; /* always succeeds. */ +static int DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device) +{ + device->hidden = (struct SDL_PrivateAudioData *) SDL_malloc(device->buffer_size); + if (!device->hidden) { + return SDL_OutOfMemory(); + } + return 0; /* don't change reported device format. */ } -static int DUMMYAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device) +{ + SDL_free(device->hidden); + device->hidden = NULL; +} + +static Uint8 *DUMMYAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) +{ + return (Uint8 *) device->hidden; +} + +static int DUMMYAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { /* Delay to make this sort of simulate real audio input. */ - SDL_Delay((_this->spec.samples * 1000) / _this->spec.freq); + SDL_Delay((device->sample_frames * 1000) / device->freq); /* always return a full buffer of silence. */ - SDL_memset(buffer, _this->spec.silence, buflen); + SDL_memset(buffer, device->silence_value, buflen); return buflen; } @@ -46,6 +61,8 @@ static SDL_bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl) { /* Set the function pointers */ impl->OpenDevice = DUMMYAUDIO_OpenDevice; + impl->CloseDevice = DUMMYAUDIO_CloseDevice; + impl->GetDeviceBuf = DUMMYAUDIO_GetDeviceBuf; impl->CaptureFromDevice = DUMMYAUDIO_CaptureFromDevice; impl->OnlyHasDefaultOutputDevice = SDL_TRUE; diff --git a/src/audio/dummy/SDL_dummyaudio.h b/src/audio/dummy/SDL_dummyaudio.h index a4bddb44d9..78709ac002 100644 --- a/src/audio/dummy/SDL_dummyaudio.h +++ b/src/audio/dummy/SDL_dummyaudio.h @@ -25,6 +25,8 @@ #include "../SDL_sysaudio.h" +/* !!! FIXME: none of this is actually used. Dump this whole file. */ + struct SDL_PrivateAudioData { /* The file descriptor for the audio device */ diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 5a13a06d8f..62a6d8284f 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -359,12 +359,6 @@ failed: return -1; } -/* This function waits until it is possible to write a full sound buffer */ -static void PULSEAUDIO_WaitDevice(SDL_AudioDevice *_this) -{ - /* this is a no-op; we wait in PULSEAUDIO_PlayDevice now. */ -} - static void WriteCallback(pa_stream *p, size_t nbytes, void *userdata) { struct SDL_PrivateAudioData *h = (struct SDL_PrivateAudioData *)userdata; @@ -373,50 +367,58 @@ static void WriteCallback(pa_stream *p, size_t nbytes, void *userdata) PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } -static void PULSEAUDIO_PlayDevice(SDL_AudioDevice *_this) +/* This function waits until it is possible to write a full sound buffer */ +static void PULSEAUDIO_WaitDevice(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *h = _this->hidden; - int available = h->mixlen; - int written = 0; - int cpy; + struct SDL_PrivateAudioData *h = device->hidden; /*printf("PULSEAUDIO PLAYDEVICE START! mixlen=%d\n", available);*/ PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); - while (SDL_AtomicGet(&_this->enabled) && (available > 0)) { - cpy = SDL_min(h->bytes_requested, available); - if (cpy) { - if (PULSEAUDIO_pa_stream_write(h->stream, h->mixbuf + written, cpy, NULL, 0LL, PA_SEEK_RELATIVE) < 0) { - SDL_OpenedAudioDeviceDisconnected(_this); - break; - } - /*printf("PULSEAUDIO FEED! nbytes=%u\n", (unsigned int) cpy);*/ - h->bytes_requested -= cpy; - written += cpy; - available -= cpy; - } + while (!SDL_AtomicGet(&device->shutdown) && (h->bytes_requested < (device->buffer_size / 2))) { + /*printf("PULSEAUDIO WAIT IN WAITDEVICE!\n");*/ + PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); - if (available > 0) { - /* let WriteCallback fire if necessary. */ - /*printf("PULSEAUDIO WAIT IN PLAYDEVICE!\n");*/ - PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); - - if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) { - /*printf("PULSEAUDIO DEVICE FAILURE IN PLAYDEVICE!\n");*/ - SDL_OpenedAudioDeviceDisconnected(_this); - break; - } + if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) { + /*printf("PULSEAUDIO DEVICE FAILURE IN WAITDEVICE!\n");*/ + SDL_AudioDeviceDisconnected(device); + break; } } - PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); +} + +static void PULSEAUDIO_PlayDevice(SDL_AudioDevice *device, int buffer_size) +{ + struct SDL_PrivateAudioData *h = device->hidden; + const int available = buffer_size; + int rc; + + /*printf("PULSEAUDIO PLAYDEVICE START! mixlen=%d\n", available);*/ + + SDL_assert(h->bytes_requested >= available); + + PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); + rc = PULSEAUDIO_pa_stream_write(h->stream, h->mixbuf, available, NULL, 0LL, PA_SEEK_RELATIVE); + PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); + + if (rc < 0) { + SDL_AudioDeviceDisconnected(device); + return; + } + + /*printf("PULSEAUDIO FEED! nbytes=%u\n", (unsigned int) available);*/ + h->bytes_requested -= available; + /*printf("PULSEAUDIO PLAYDEVICE END! written=%d\n", written);*/ } -static Uint8 *PULSEAUDIO_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *PULSEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - return _this->hidden->mixbuf; + struct SDL_PrivateAudioData *h = device->hidden; + *buffer_size = SDL_min(*buffer_size, h->bytes_requested); + return device->hidden->mixbuf; } static void ReadCallback(pa_stream *p, size_t nbytes, void *userdata) @@ -425,16 +427,16 @@ static void ReadCallback(pa_stream *p, size_t nbytes, void *userdata) PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); /* the capture code queries what it needs, we just need to signal to end any wait */ } -static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { - struct SDL_PrivateAudioData *h = _this->hidden; + struct SDL_PrivateAudioData *h = device->hidden; const void *data = NULL; size_t nbytes = 0; int retval = 0; PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); - while (SDL_AtomicGet(&_this->enabled)) { + while (!SDL_AtomicGet(&device->shutdown)) { if (h->capturebuf != NULL) { const int cpy = SDL_min(buflen, h->capturelen); SDL_memcpy(buffer, h->capturebuf, cpy); @@ -449,17 +451,17 @@ static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, in break; } - while (SDL_AtomicGet(&_this->enabled) && (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0)) { + while (!SDL_AtomicGet(&device->shutdown) && (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0)) { PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) { /*printf("PULSEAUDIO DEVICE FAILURE IN CAPTUREFROMDEVICE!\n");*/ - SDL_OpenedAudioDeviceDisconnected(_this); + SDL_AudioDeviceDisconnected(device); retval = -1; break; } } - if ((retval == -1) || !SDL_AtomicGet(&_this->enabled)) { /* in case this happened while we were blocking. */ + if ((retval == -1) || SDL_AtomicGet(&device->shutdown)) { /* in case this happened while we were blocking. */ retval = -1; break; } @@ -483,9 +485,9 @@ static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, in return retval; } -static void PULSEAUDIO_FlushCapture(SDL_AudioDevice *_this) +static void PULSEAUDIO_FlushCapture(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *h = _this->hidden; + struct SDL_PrivateAudioData *h = device->hidden; const void *data = NULL; size_t nbytes = 0; @@ -497,11 +499,11 @@ static void PULSEAUDIO_FlushCapture(SDL_AudioDevice *_this) h->capturelen = 0; } - while (SDL_AtomicGet(&_this->enabled) && (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0)) { + while (!SDL_AtomicGet(&device->shutdown) && (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0)) { PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) { /*printf("PULSEAUDIO DEVICE FAILURE IN FLUSHCAPTURE!\n");*/ - SDL_OpenedAudioDeviceDisconnected(_this); + SDL_AudioDeviceDisconnected(device); break; } @@ -515,22 +517,22 @@ static void PULSEAUDIO_FlushCapture(SDL_AudioDevice *_this) PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); } -static void PULSEAUDIO_CloseDevice(SDL_AudioDevice *_this) +static void PULSEAUDIO_CloseDevice(SDL_AudioDevice *device) { PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); - if (_this->hidden->stream) { - if (_this->hidden->capturebuf != NULL) { - PULSEAUDIO_pa_stream_drop(_this->hidden->stream); + if (device->hidden->stream) { + if (device->hidden->capturebuf != NULL) { + PULSEAUDIO_pa_stream_drop(device->hidden->stream); } - PULSEAUDIO_pa_stream_disconnect(_this->hidden->stream); - PULSEAUDIO_pa_stream_unref(_this->hidden->stream); + PULSEAUDIO_pa_stream_disconnect(device->hidden->stream); + PULSEAUDIO_pa_stream_unref(device->hidden->stream); } PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); - SDL_free(_this->hidden->mixbuf); - SDL_free(_this->hidden->device_name); - SDL_free(_this->hidden); + SDL_free(device->hidden->mixbuf); + SDL_free(device->hidden->device_name); + SDL_free(device->hidden); } static void SinkDeviceNameCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data) @@ -573,8 +575,9 @@ static void PulseStreamStateChangeCallback(pa_stream *stream, void *userdata) PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); /* just signal any waiting code, it can look up the details. */ } -static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) { + const SDL_bool iscapture = device->iscapture; struct SDL_PrivateAudioData *h = NULL; SDL_AudioFormat test_format; const SDL_AudioFormat *closefmts; @@ -582,7 +585,6 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) pa_buffer_attr paattr; pa_channel_map pacmap; pa_stream_flags_t flags = 0; - SDL_bool iscapture = _this->iscapture; int format = PA_SAMPLE_INVALID; int retval = 0; @@ -590,14 +592,14 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) SDL_assert(pulseaudio_context != NULL); /* Initialize all variables that we clean on shutdown */ - h = _this->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + h = device->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(_this->hidden); + SDL_zerop(device->hidden); /* Try for a closest match on audio format */ - closefmts = SDL_ClosestAudioFormats(_this->spec.format); + closefmts = SDL_ClosestAudioFormats(device->format); while ((test_format = *(closefmts++)) != 0) { #ifdef DEBUG_AUDIO fprintf(stderr, "Trying format 0x%4.4x\n", test_format); @@ -632,27 +634,27 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) if (!test_format) { return SDL_SetError("pulseaudio: Unsupported audio format"); } - _this->spec.format = test_format; + device->format = test_format; paspec.format = format; /* Calculate the final parameters for this audio specification */ - SDL_CalculateAudioSpec(&_this->spec); + SDL_UpdatedAudioDeviceFormat(device); /* Allocate mixing buffer */ if (!iscapture) { - h->mixlen = _this->spec.size; + h->mixlen = device->buffer_size; h->mixbuf = (Uint8 *)SDL_malloc(h->mixlen); if (h->mixbuf == NULL) { return SDL_OutOfMemory(); } - SDL_memset(h->mixbuf, _this->spec.silence, _this->spec.size); + SDL_memset(h->mixbuf, device->silence_value, device->buffer_size); } - paspec.channels = _this->spec.channels; - paspec.rate = _this->spec.freq; + paspec.channels = device->channels; + paspec.rate = device->freq; /* Reduced prebuffering compared to the defaults. */ - paattr.fragsize = _this->spec.size; + paattr.fragsize = device->buffer_size; paattr.tlength = h->mixlen; paattr.prebuf = -1; paattr.maxlength = -1; @@ -661,14 +663,13 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); - if (!FindDeviceName(h, iscapture, _this->handle)) { + if (!FindDeviceName(h, iscapture, device->handle)) { retval = SDL_SetError("Requested PulseAudio sink/source missing?"); } else { const char *name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME); /* The SDL ALSA output hints us that we use Windows' channel mapping */ /* https://bugzilla.libsdl.org/show_bug.cgi?id=110 */ - PULSEAUDIO_pa_channel_map_init_auto(&pacmap, _this->spec.channels, - PA_CHANNEL_MAP_WAVEEX); + PULSEAUDIO_pa_channel_map_init_auto(&pacmap, device->channels, PA_CHANNEL_MAP_WAVEEX); h->stream = PULSEAUDIO_pa_stream_new( pulseaudio_context, @@ -747,26 +748,18 @@ static SDL_AudioFormat PulseFormatToSDLFormat(pa_sample_format_t format) /* This is called when PulseAudio adds an output ("sink") device. */ static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data) { - SDL_AudioSpec spec; - SDL_bool add = (SDL_bool)((intptr_t)data); if (i) { - spec.freq = i->sample_spec.rate; - spec.channels = i->sample_spec.channels; - spec.format = PulseFormatToSDLFormat(i->sample_spec.format); - spec.silence = 0; - spec.samples = 0; - spec.size = 0; - spec.callback = NULL; - spec.userdata = NULL; + const SDL_bool add = (SDL_bool)((intptr_t)data); + const SDL_AudioFormat fmt = PulseFormatToSDLFormat(i->sample_spec.format); + const int channels = i->sample_spec.channels; + const int freq = i->sample_spec.rate; if (add) { - SDL_AddAudioDevice(SDL_FALSE, i->description, &spec, (void *)((intptr_t)i->index + 1)); + SDL_AddAudioDevice(SDL_FALSE, i->description, fmt, channels, freq, (void *)((intptr_t)i->index + 1)); } if (default_sink_path != NULL && SDL_strcmp(i->name, default_sink_path) == 0) { - if (default_sink_name != NULL) { - SDL_free(default_sink_name); - } + SDL_free(default_sink_name); default_sink_name = SDL_strdup(i->description); } } @@ -776,30 +769,20 @@ static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, /* This is called when PulseAudio adds a capture ("source") device. */ static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data) { - SDL_AudioSpec spec; - SDL_bool add = (SDL_bool)((intptr_t)data); - if (i) { - /* Maybe skip "monitor" sources. These are just output from other sinks. */ - if (include_monitors || (i->monitor_of_sink == PA_INVALID_INDEX)) { - spec.freq = i->sample_spec.rate; - spec.channels = i->sample_spec.channels; - spec.format = PulseFormatToSDLFormat(i->sample_spec.format); - spec.silence = 0; - spec.samples = 0; - spec.size = 0; - spec.callback = NULL; - spec.userdata = NULL; + /* Maybe skip "monitor" sources. These are just output from other sinks. */ + if (i && (include_monitors || (i->monitor_of_sink == PA_INVALID_INDEX))) { + const SDL_bool add = (SDL_bool)((intptr_t)data); + const SDL_AudioFormat fmt = PulseFormatToSDLFormat(i->sample_spec.format); + const int channels = i->sample_spec.channels; + const int freq = i->sample_spec.rate; - if (add) { - SDL_AddAudioDevice(SDL_TRUE, i->description, &spec, (void *)((intptr_t)i->index + 1)); - } + if (add) { + SDL_AddAudioDevice(SDL_TRUE, i->description, fmt, channels, freq, (void *)((intptr_t)i->index + 1)); + } - if (default_source_path != NULL && SDL_strcmp(i->name, default_source_path) == 0) { - if (default_source_name != NULL) { - SDL_free(default_source_name); - } - default_source_name = SDL_strdup(i->description); - } + if (default_source_path != NULL && SDL_strcmp(i->name, default_source_path) == 0) { + SDL_free(default_source_name); + default_source_name = SDL_strdup(i->description); } } PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); @@ -839,7 +822,11 @@ static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint3 PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, (void *)((intptr_t)added))); } else if (removed && (sink || source)) { /* removes we can handle just with the device index. */ - SDL_RemoveAudioDevice(source != 0, (void *)((intptr_t)idx + 1)); + SDL_AudioDevice *device = SDL_ObtainAudioDeviceByHandle((void *)((intptr_t)idx + 1)); /* !!! FIXME: maybe just have a "disconnect by handle" function instead. */ + if (device) { + SDL_UnlockMutex(device->lock); /* AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. */ + SDL_AudioDeviceDisconnected(device); + } } } PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); @@ -893,6 +880,7 @@ static void PULSEAUDIO_DetectDevices(void) SDL_DestroySemaphore(ready_sem); } +#if 0 static int PULSEAUDIO_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) { int i; @@ -923,6 +911,7 @@ static int PULSEAUDIO_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int } return SDL_SetError("Could not find default PulseAudio device"); } +#endif static void PULSEAUDIO_Deinitialize(void) { @@ -970,7 +959,9 @@ static SDL_bool PULSEAUDIO_Init(SDL_AudioDriverImpl *impl) impl->Deinitialize = PULSEAUDIO_Deinitialize; impl->CaptureFromDevice = PULSEAUDIO_CaptureFromDevice; impl->FlushCapture = PULSEAUDIO_FlushCapture; + #if 0 impl->GetDefaultAudioInfo = PULSEAUDIO_GetDefaultAudioInfo; + #endif impl->HasCaptureSupport = SDL_TRUE; impl->SupportsNonPow2Samples = SDL_TRUE; diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 751abf72b7..7fca72a597 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -35,12 +35,9 @@ SDL3_0.0.0 { SDL_BroadcastCondition; SDL_CaptureMouse; SDL_CleanupTLS; - SDL_ClearAudioStream; SDL_ClearComposition; SDL_ClearError; SDL_ClearHints; - SDL_ClearQueuedAudio; - SDL_CloseAudioDevice; SDL_CloseGamepad; SDL_CloseJoystick; SDL_CloseSensor; @@ -50,7 +47,6 @@ SDL3_0.0.0 { SDL_ConvertPixels; SDL_ConvertSurface; SDL_ConvertSurfaceFormat; - SDL_CreateAudioStream; SDL_CreateColorCursor; SDL_CreateCondition; SDL_CreateCursor; @@ -82,8 +78,6 @@ SDL3_0.0.0 { SDL_DelHintCallback; SDL_Delay; SDL_DelayNS; - SDL_DequeueAudio; - SDL_DestroyAudioStream; SDL_DestroyCondition; SDL_DestroyCursor; SDL_DestroyMutex; @@ -114,7 +108,6 @@ SDL3_0.0.0 { SDL_FillSurfaceRects; SDL_FilterEvents; SDL_FlashWindow; - SDL_FlushAudioStream; SDL_FlushEvent; SDL_FlushEvents; SDL_GDKGetTaskQueue; @@ -150,27 +143,18 @@ SDL3_0.0.0 { SDL_GetAndroidSDKVersion; SDL_GetAssertionHandler; SDL_GetAssertionReport; - SDL_GetAudioDeviceName; - SDL_GetAudioDeviceSpec; - SDL_GetAudioDeviceStatus; - SDL_GetAudioDriver; - SDL_GetAudioStreamAvailable; - SDL_GetAudioStreamData; - SDL_GetAudioStreamFormat; SDL_GetBasePath; SDL_GetCPUCacheLineSize; SDL_GetCPUCount; SDL_GetClipboardData; SDL_GetClipboardText; SDL_GetClosestFullscreenDisplayMode; - SDL_GetCurrentAudioDriver; SDL_GetCurrentDisplayMode; SDL_GetCurrentDisplayOrientation; SDL_GetCurrentRenderOutputSize; SDL_GetCurrentVideoDriver; SDL_GetCursor; SDL_GetDefaultAssertionHandler; - SDL_GetDefaultAudioInfo; SDL_GetDefaultCursor; SDL_GetDesktopDisplayMode; SDL_GetDisplayBounds; @@ -267,8 +251,6 @@ SDL3_0.0.0 { SDL_GetMouseState; SDL_GetNaturalDisplayOrientation; SDL_GetNumAllocations; - SDL_GetNumAudioDevices; - SDL_GetNumAudioDrivers; SDL_GetNumGamepadMappings; SDL_GetNumGamepadTouchpadFingers; SDL_GetNumGamepadTouchpads; @@ -291,7 +273,6 @@ SDL3_0.0.0 { SDL_GetPreferredLocales; SDL_GetPrimaryDisplay; SDL_GetPrimarySelectionText; - SDL_GetQueuedAudioSize; SDL_GetRGB; SDL_GetRGBA; SDL_GetRectAndLineIntersection; @@ -461,8 +442,6 @@ SDL3_0.0.0 { SDL_LoadFile_RW; SDL_LoadFunction; SDL_LoadObject; - SDL_LoadWAV_RW; - SDL_LockAudioDevice; SDL_LockJoysticks; SDL_LockMutex; SDL_LockRWLockForReading; @@ -494,7 +473,6 @@ SDL3_0.0.0 { SDL_Metal_DestroyView; SDL_Metal_GetLayer; SDL_MinimizeWindow; - SDL_MixAudioFormat; SDL_MouseIsHaptic; SDL_NumHaptics; SDL_OnApplicationDidBecomeActive; @@ -504,12 +482,10 @@ SDL3_0.0.0 { SDL_OnApplicationWillEnterForeground; SDL_OnApplicationWillResignActive; SDL_OnApplicationWillTerminate; - SDL_OpenAudioDevice; SDL_OpenGamepad; SDL_OpenJoystick; SDL_OpenSensor; SDL_OpenURL; - SDL_PauseAudioDevice; SDL_PeepEvents; SDL_PlayAudioDevice; SDL_PollEvent; @@ -517,9 +493,7 @@ SDL3_0.0.0 { SDL_PremultiplyAlpha; SDL_PumpEvents; SDL_PushEvent; - SDL_PutAudioStreamData; SDL_QueryTexture; - SDL_QueueAudio; SDL_Quit; SDL_QuitSubSystem; SDL_RWFromConstMem; @@ -672,7 +646,6 @@ SDL3_0.0.0 { SDL_TryLockRWLockForWriting; SDL_TryWaitSemaphore; SDL_UnloadObject; - SDL_UnlockAudioDevice; SDL_UnlockJoysticks; SDL_UnlockMutex; SDL_UnlockRWLock; @@ -879,6 +852,33 @@ SDL3_0.0.0 { SDL_strnlen; SDL_AddGamepadMappingsFromFile; SDL_ReloadGamepadMappings; + SDL_GetNumAudioDrivers; + SDL_GetAudioDriver; + SDL_GetCurrentAudioDriver; + SDL_GetAudioOutputDevices; + SDL_GetAudioCaptureDevices; + SDL_GetAudioDeviceName; + SDL_GetAudioDeviceFormat; + SDL_OpenAudioDevice; + SDL_CloseAudioDevice; + SDL_BindAudioStreams; + SDL_BindAudioStream; + SDL_UnbindAudioStreams; + SDL_UnbindAudioStream; + SDL_CreateAudioStream; + SDL_GetAudioStreamFormat; + SDL_SetAudioStreamFormat; + SDL_PutAudioStreamData; + SDL_GetAudioStreamData; + SDL_GetAudioStreamAvailable; + SDL_FlushAudioStream; + SDL_ClearAudioStream; + SDL_DestroyAudioStream; + SDL_CreateAndBindAudioStream; + SDL_LoadWAV_RW; + SDL_MixAudioFormat; + SDL_ConvertAudioSamples; + SDL_GetSilenceValueForFormat; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 3fefcb1954..7bb20c97ce 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -59,12 +59,9 @@ #define SDL_BroadcastCondition SDL_BroadcastCondition_REAL #define SDL_CaptureMouse SDL_CaptureMouse_REAL #define SDL_CleanupTLS SDL_CleanupTLS_REAL -#define SDL_ClearAudioStream SDL_ClearAudioStream_REAL #define SDL_ClearComposition SDL_ClearComposition_REAL #define SDL_ClearError SDL_ClearError_REAL #define SDL_ClearHints SDL_ClearHints_REAL -#define SDL_ClearQueuedAudio SDL_ClearQueuedAudio_REAL -#define SDL_CloseAudioDevice SDL_CloseAudioDevice_REAL #define SDL_CloseGamepad SDL_CloseGamepad_REAL #define SDL_CloseJoystick SDL_CloseJoystick_REAL #define SDL_CloseSensor SDL_CloseSensor_REAL @@ -74,7 +71,6 @@ #define SDL_ConvertPixels SDL_ConvertPixels_REAL #define SDL_ConvertSurface SDL_ConvertSurface_REAL #define SDL_ConvertSurfaceFormat SDL_ConvertSurfaceFormat_REAL -#define SDL_CreateAudioStream SDL_CreateAudioStream_REAL #define SDL_CreateColorCursor SDL_CreateColorCursor_REAL #define SDL_CreateCondition SDL_CreateCondition_REAL #define SDL_CreateCursor SDL_CreateCursor_REAL @@ -106,8 +102,6 @@ #define SDL_DelHintCallback SDL_DelHintCallback_REAL #define SDL_Delay SDL_Delay_REAL #define SDL_DelayNS SDL_DelayNS_REAL -#define SDL_DequeueAudio SDL_DequeueAudio_REAL -#define SDL_DestroyAudioStream SDL_DestroyAudioStream_REAL #define SDL_DestroyCondition SDL_DestroyCondition_REAL #define SDL_DestroyCursor SDL_DestroyCursor_REAL #define SDL_DestroyMutex SDL_DestroyMutex_REAL @@ -138,7 +132,6 @@ #define SDL_FillSurfaceRects SDL_FillSurfaceRects_REAL #define SDL_FilterEvents SDL_FilterEvents_REAL #define SDL_FlashWindow SDL_FlashWindow_REAL -#define SDL_FlushAudioStream SDL_FlushAudioStream_REAL #define SDL_FlushEvent SDL_FlushEvent_REAL #define SDL_FlushEvents SDL_FlushEvents_REAL #define SDL_GDKGetTaskQueue SDL_GDKGetTaskQueue_REAL @@ -174,27 +167,18 @@ #define SDL_GetAndroidSDKVersion SDL_GetAndroidSDKVersion_REAL #define SDL_GetAssertionHandler SDL_GetAssertionHandler_REAL #define SDL_GetAssertionReport SDL_GetAssertionReport_REAL -#define SDL_GetAudioDeviceName SDL_GetAudioDeviceName_REAL -#define SDL_GetAudioDeviceSpec SDL_GetAudioDeviceSpec_REAL -#define SDL_GetAudioDeviceStatus SDL_GetAudioDeviceStatus_REAL -#define SDL_GetAudioDriver SDL_GetAudioDriver_REAL -#define SDL_GetAudioStreamAvailable SDL_GetAudioStreamAvailable_REAL -#define SDL_GetAudioStreamData SDL_GetAudioStreamData_REAL -#define SDL_GetAudioStreamFormat SDL_GetAudioStreamFormat_REAL #define SDL_GetBasePath SDL_GetBasePath_REAL #define SDL_GetCPUCacheLineSize SDL_GetCPUCacheLineSize_REAL #define SDL_GetCPUCount SDL_GetCPUCount_REAL #define SDL_GetClipboardData SDL_GetClipboardData_REAL #define SDL_GetClipboardText SDL_GetClipboardText_REAL #define SDL_GetClosestFullscreenDisplayMode SDL_GetClosestFullscreenDisplayMode_REAL -#define SDL_GetCurrentAudioDriver SDL_GetCurrentAudioDriver_REAL #define SDL_GetCurrentDisplayMode SDL_GetCurrentDisplayMode_REAL #define SDL_GetCurrentDisplayOrientation SDL_GetCurrentDisplayOrientation_REAL #define SDL_GetCurrentRenderOutputSize SDL_GetCurrentRenderOutputSize_REAL #define SDL_GetCurrentVideoDriver SDL_GetCurrentVideoDriver_REAL #define SDL_GetCursor SDL_GetCursor_REAL #define SDL_GetDefaultAssertionHandler SDL_GetDefaultAssertionHandler_REAL -#define SDL_GetDefaultAudioInfo SDL_GetDefaultAudioInfo_REAL #define SDL_GetDefaultCursor SDL_GetDefaultCursor_REAL #define SDL_GetDesktopDisplayMode SDL_GetDesktopDisplayMode_REAL #define SDL_GetDisplayBounds SDL_GetDisplayBounds_REAL @@ -291,8 +275,6 @@ #define SDL_GetMouseState SDL_GetMouseState_REAL #define SDL_GetNaturalDisplayOrientation SDL_GetNaturalDisplayOrientation_REAL #define SDL_GetNumAllocations SDL_GetNumAllocations_REAL -#define SDL_GetNumAudioDevices SDL_GetNumAudioDevices_REAL -#define SDL_GetNumAudioDrivers SDL_GetNumAudioDrivers_REAL #define SDL_GetNumGamepadMappings SDL_GetNumGamepadMappings_REAL #define SDL_GetNumGamepadTouchpadFingers SDL_GetNumGamepadTouchpadFingers_REAL #define SDL_GetNumGamepadTouchpads SDL_GetNumGamepadTouchpads_REAL @@ -315,7 +297,6 @@ #define SDL_GetPreferredLocales SDL_GetPreferredLocales_REAL #define SDL_GetPrimaryDisplay SDL_GetPrimaryDisplay_REAL #define SDL_GetPrimarySelectionText SDL_GetPrimarySelectionText_REAL -#define SDL_GetQueuedAudioSize SDL_GetQueuedAudioSize_REAL #define SDL_GetRGB SDL_GetRGB_REAL #define SDL_GetRGBA SDL_GetRGBA_REAL #define SDL_GetRectAndLineIntersection SDL_GetRectAndLineIntersection_REAL @@ -485,8 +466,6 @@ #define SDL_LoadFile_RW SDL_LoadFile_RW_REAL #define SDL_LoadFunction SDL_LoadFunction_REAL #define SDL_LoadObject SDL_LoadObject_REAL -#define SDL_LoadWAV_RW SDL_LoadWAV_RW_REAL -#define SDL_LockAudioDevice SDL_LockAudioDevice_REAL #define SDL_LockJoysticks SDL_LockJoysticks_REAL #define SDL_LockMutex SDL_LockMutex_REAL #define SDL_LockRWLockForReading SDL_LockRWLockForReading_REAL @@ -518,7 +497,6 @@ #define SDL_Metal_DestroyView SDL_Metal_DestroyView_REAL #define SDL_Metal_GetLayer SDL_Metal_GetLayer_REAL #define SDL_MinimizeWindow SDL_MinimizeWindow_REAL -#define SDL_MixAudioFormat SDL_MixAudioFormat_REAL #define SDL_MouseIsHaptic SDL_MouseIsHaptic_REAL #define SDL_NumHaptics SDL_NumHaptics_REAL #define SDL_OnApplicationDidBecomeActive SDL_OnApplicationDidBecomeActive_REAL @@ -528,12 +506,10 @@ #define SDL_OnApplicationWillEnterForeground SDL_OnApplicationWillEnterForeground_REAL #define SDL_OnApplicationWillResignActive SDL_OnApplicationWillResignActive_REAL #define SDL_OnApplicationWillTerminate SDL_OnApplicationWillTerminate_REAL -#define SDL_OpenAudioDevice SDL_OpenAudioDevice_REAL #define SDL_OpenGamepad SDL_OpenGamepad_REAL #define SDL_OpenJoystick SDL_OpenJoystick_REAL #define SDL_OpenSensor SDL_OpenSensor_REAL #define SDL_OpenURL SDL_OpenURL_REAL -#define SDL_PauseAudioDevice SDL_PauseAudioDevice_REAL #define SDL_PeepEvents SDL_PeepEvents_REAL #define SDL_PlayAudioDevice SDL_PlayAudioDevice_REAL #define SDL_PollEvent SDL_PollEvent_REAL @@ -541,9 +517,7 @@ #define SDL_PremultiplyAlpha SDL_PremultiplyAlpha_REAL #define SDL_PumpEvents SDL_PumpEvents_REAL #define SDL_PushEvent SDL_PushEvent_REAL -#define SDL_PutAudioStreamData SDL_PutAudioStreamData_REAL #define SDL_QueryTexture SDL_QueryTexture_REAL -#define SDL_QueueAudio SDL_QueueAudio_REAL #define SDL_Quit SDL_Quit_REAL #define SDL_QuitSubSystem SDL_QuitSubSystem_REAL #define SDL_RWFromConstMem SDL_RWFromConstMem_REAL @@ -696,7 +670,6 @@ #define SDL_TryLockRWLockForWriting SDL_TryLockRWLockForWriting_REAL #define SDL_TryWaitSemaphore SDL_TryWaitSemaphore_REAL #define SDL_UnloadObject SDL_UnloadObject_REAL -#define SDL_UnlockAudioDevice SDL_UnlockAudioDevice_REAL #define SDL_UnlockJoysticks SDL_UnlockJoysticks_REAL #define SDL_UnlockMutex SDL_UnlockMutex_REAL #define SDL_UnlockRWLock SDL_UnlockRWLock_REAL @@ -905,3 +878,30 @@ #define SDL_strnlen SDL_strnlen_REAL #define SDL_AddGamepadMappingsFromFile SDL_AddGamepadMappingsFromFile_REAL #define SDL_ReloadGamepadMappings SDL_ReloadGamepadMappings_REAL +#define SDL_GetNumAudioDrivers SDL_GetNumAudioDrivers_REAL +#define SDL_GetAudioDriver SDL_GetAudioDriver_REAL +#define SDL_GetCurrentAudioDriver SDL_GetCurrentAudioDriver_REAL +#define SDL_GetAudioOutputDevices SDL_GetAudioOutputDevices_REAL +#define SDL_GetAudioCaptureDevices SDL_GetAudioCaptureDevices_REAL +#define SDL_GetAudioDeviceName SDL_GetAudioDeviceName_REAL +#define SDL_GetAudioDeviceFormat SDL_GetAudioDeviceFormat_REAL +#define SDL_OpenAudioDevice SDL_OpenAudioDevice_REAL +#define SDL_CloseAudioDevice SDL_CloseAudioDevice_REAL +#define SDL_BindAudioStreams SDL_BindAudioStreams_REAL +#define SDL_BindAudioStream SDL_BindAudioStream_REAL +#define SDL_UnbindAudioStreams SDL_UnbindAudioStreams_REAL +#define SDL_UnbindAudioStream SDL_UnbindAudioStream_REAL +#define SDL_CreateAudioStream SDL_CreateAudioStream_REAL +#define SDL_GetAudioStreamFormat SDL_GetAudioStreamFormat_REAL +#define SDL_SetAudioStreamFormat SDL_SetAudioStreamFormat_REAL +#define SDL_PutAudioStreamData SDL_PutAudioStreamData_REAL +#define SDL_GetAudioStreamData SDL_GetAudioStreamData_REAL +#define SDL_GetAudioStreamAvailable SDL_GetAudioStreamAvailable_REAL +#define SDL_FlushAudioStream SDL_FlushAudioStream_REAL +#define SDL_ClearAudioStream SDL_ClearAudioStream_REAL +#define SDL_DestroyAudioStream SDL_DestroyAudioStream_REAL +#define SDL_CreateAndBindAudioStream SDL_CreateAndBindAudioStream_REAL +#define SDL_LoadWAV_RW SDL_LoadWAV_RW_REAL +#define SDL_MixAudioFormat SDL_MixAudioFormat_REAL +#define SDL_ConvertAudioSamples SDL_ConvertAudioSamples_REAL +#define SDL_GetSilenceValueForFormat SDL_GetSilenceValueForFormat_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 7b93149fb7..a934315967 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -141,12 +141,9 @@ SDL_DYNAPI_PROC(int,SDL_BlitSurfaceUncheckedScaled,(SDL_Surface *a, const SDL_Re SDL_DYNAPI_PROC(int,SDL_BroadcastCondition,(SDL_Condition *a),(a),return) SDL_DYNAPI_PROC(int,SDL_CaptureMouse,(SDL_bool a),(a),return) SDL_DYNAPI_PROC(void,SDL_CleanupTLS,(void),(),) -SDL_DYNAPI_PROC(int,SDL_ClearAudioStream,(SDL_AudioStream *a),(a),return) SDL_DYNAPI_PROC(void,SDL_ClearComposition,(void),(),) SDL_DYNAPI_PROC(void,SDL_ClearError,(void),(),) SDL_DYNAPI_PROC(void,SDL_ClearHints,(void),(),) -SDL_DYNAPI_PROC(int,SDL_ClearQueuedAudio,(SDL_AudioDeviceID a),(a),return) -SDL_DYNAPI_PROC(void,SDL_CloseAudioDevice,(SDL_AudioDeviceID a),(a),) SDL_DYNAPI_PROC(void,SDL_CloseGamepad,(SDL_Gamepad *a),(a),) SDL_DYNAPI_PROC(void,SDL_CloseJoystick,(SDL_Joystick *a),(a),) SDL_DYNAPI_PROC(void,SDL_CloseSensor,(SDL_Sensor *a),(a),) @@ -156,7 +153,6 @@ SDL_DYNAPI_PROC(int,SDL_ConvertEventToRenderCoordinates,(SDL_Renderer *a, SDL_Ev SDL_DYNAPI_PROC(int,SDL_ConvertPixels,(int a, int b, Uint32 c, const void *d, int e, Uint32 f, void *g, int h),(a,b,c,d,e,f,g,h),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_ConvertSurface,(SDL_Surface *a, const SDL_PixelFormat *b),(a,b),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_ConvertSurfaceFormat,(SDL_Surface *a, Uint32 b),(a,b),return) -SDL_DYNAPI_PROC(SDL_AudioStream*,SDL_CreateAudioStream,(SDL_AudioFormat a, int b, int c, SDL_AudioFormat d, int e, int f),(a,b,c,d,e,f),return) SDL_DYNAPI_PROC(SDL_Cursor*,SDL_CreateColorCursor,(SDL_Surface *a, int b, int c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_Condition*,SDL_CreateCondition,(void),(),return) SDL_DYNAPI_PROC(SDL_Cursor*,SDL_CreateCursor,(const Uint8 *a, const Uint8 *b, int c, int d, int e, int f),(a,b,c,d,e,f),return) @@ -185,8 +181,6 @@ SDL_DYNAPI_PROC(void,SDL_DelEventWatch,(SDL_EventFilter a, void *b),(a,b),) SDL_DYNAPI_PROC(void,SDL_DelHintCallback,(const char *a, SDL_HintCallback b, void *c),(a,b,c),) SDL_DYNAPI_PROC(void,SDL_Delay,(Uint32 a),(a),) SDL_DYNAPI_PROC(void,SDL_DelayNS,(Uint64 a),(a),) -SDL_DYNAPI_PROC(Uint32,SDL_DequeueAudio,(SDL_AudioDeviceID a, void *b, Uint32 c),(a,b,c),return) -SDL_DYNAPI_PROC(void,SDL_DestroyAudioStream,(SDL_AudioStream *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyCondition,(SDL_Condition *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyCursor,(SDL_Cursor *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyMutex,(SDL_Mutex *a),(a),) @@ -216,7 +210,6 @@ SDL_DYNAPI_PROC(int,SDL_FillSurfaceRect,(SDL_Surface *a, const SDL_Rect *b, Uint SDL_DYNAPI_PROC(int,SDL_FillSurfaceRects,(SDL_Surface *a, const SDL_Rect *b, int c, Uint32 d),(a,b,c,d),return) SDL_DYNAPI_PROC(void,SDL_FilterEvents,(SDL_EventFilter a, void *b),(a,b),) SDL_DYNAPI_PROC(int,SDL_FlashWindow,(SDL_Window *a, SDL_FlashOperation b),(a,b),return) -SDL_DYNAPI_PROC(int,SDL_FlushAudioStream,(SDL_AudioStream *a),(a),return) SDL_DYNAPI_PROC(void,SDL_FlushEvent,(Uint32 a),(a),) SDL_DYNAPI_PROC(void,SDL_FlushEvents,(Uint32 a, Uint32 b),(a,b),) SDL_DYNAPI_PROC(int,SDL_GL_BindTexture,(SDL_Texture *a, float *b, float *c),(a,b,c),return) @@ -249,27 +242,18 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_GamepadHasSensor,(SDL_Gamepad *a, SDL_SensorType b) SDL_DYNAPI_PROC(SDL_bool,SDL_GamepadSensorEnabled,(SDL_Gamepad *a, SDL_SensorType b),(a,b),return) SDL_DYNAPI_PROC(SDL_AssertionHandler,SDL_GetAssertionHandler,(void **a),(a),return) SDL_DYNAPI_PROC(const SDL_AssertData*,SDL_GetAssertionReport,(void),(),return) -SDL_DYNAPI_PROC(const char*,SDL_GetAudioDeviceName,(int a, int b),(a,b),return) -SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceSpec,(int a, int b, SDL_AudioSpec *c),(a,b,c),return) -SDL_DYNAPI_PROC(SDL_AudioStatus,SDL_GetAudioDeviceStatus,(SDL_AudioDeviceID a),(a),return) -SDL_DYNAPI_PROC(const char*,SDL_GetAudioDriver,(int a),(a),return) -SDL_DYNAPI_PROC(int,SDL_GetAudioStreamAvailable,(SDL_AudioStream *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_GetAudioStreamData,(SDL_AudioStream *a, void *b, int c),(a,b,c),return) -SDL_DYNAPI_PROC(int,SDL_GetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat *b, int *c, int *d, SDL_AudioFormat *e, int *f, int *g),(a,b,c,d,e,f,g),return) SDL_DYNAPI_PROC(char*,SDL_GetBasePath,(void),(),return) SDL_DYNAPI_PROC(int,SDL_GetCPUCacheLineSize,(void),(),return) SDL_DYNAPI_PROC(int,SDL_GetCPUCount,(void),(),return) SDL_DYNAPI_PROC(void*,SDL_GetClipboardData,(const char *a, size_t *b),(a,b),return) SDL_DYNAPI_PROC(char*,SDL_GetClipboardText,(void),(),return) SDL_DYNAPI_PROC(const SDL_DisplayMode*,SDL_GetClosestFullscreenDisplayMode,(SDL_DisplayID a, int b, int c, float d, SDL_bool e),(a,b,c,d,e),return) -SDL_DYNAPI_PROC(const char*,SDL_GetCurrentAudioDriver,(void),(),return) SDL_DYNAPI_PROC(const SDL_DisplayMode*,SDL_GetCurrentDisplayMode,(SDL_DisplayID a),(a),return) SDL_DYNAPI_PROC(SDL_DisplayOrientation,SDL_GetCurrentDisplayOrientation,(SDL_DisplayID a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetCurrentRenderOutputSize,(SDL_Renderer *a, int *b, int *c),(a,b,c),return) SDL_DYNAPI_PROC(const char*,SDL_GetCurrentVideoDriver,(void),(),return) SDL_DYNAPI_PROC(SDL_Cursor*,SDL_GetCursor,(void),(),return) SDL_DYNAPI_PROC(SDL_AssertionHandler,SDL_GetDefaultAssertionHandler,(void),(),return) -SDL_DYNAPI_PROC(int,SDL_GetDefaultAudioInfo,(char **a, SDL_AudioSpec *b, int c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_Cursor*,SDL_GetDefaultCursor,(void),(),return) SDL_DYNAPI_PROC(const SDL_DisplayMode*,SDL_GetDesktopDisplayMode,(SDL_DisplayID a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetDisplayBounds,(SDL_DisplayID a, SDL_Rect *b),(a,b),return) @@ -366,8 +350,6 @@ SDL_DYNAPI_PROC(SDL_Window*,SDL_GetMouseFocus,(void),(),return) SDL_DYNAPI_PROC(Uint32,SDL_GetMouseState,(float *a, float *b),(a,b),return) SDL_DYNAPI_PROC(SDL_DisplayOrientation,SDL_GetNaturalDisplayOrientation,(SDL_DisplayID a),(a),return) SDL_DYNAPI_PROC(int,SDL_GetNumAllocations,(void),(),return) -SDL_DYNAPI_PROC(int,SDL_GetNumAudioDevices,(int a),(a),return) -SDL_DYNAPI_PROC(int,SDL_GetNumAudioDrivers,(void),(),return) SDL_DYNAPI_PROC(int,SDL_GetNumGamepadMappings,(void),(),return) SDL_DYNAPI_PROC(int,SDL_GetNumGamepadTouchpadFingers,(SDL_Gamepad *a, int b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_GetNumGamepadTouchpads,(SDL_Gamepad *a),(a),return) @@ -390,7 +372,6 @@ SDL_DYNAPI_PROC(char*,SDL_GetPrefPath,(const char *a, const char *b),(a,b),retur SDL_DYNAPI_PROC(SDL_Locale*,SDL_GetPreferredLocales,(void),(),return) SDL_DYNAPI_PROC(SDL_DisplayID,SDL_GetPrimaryDisplay,(void),(),return) SDL_DYNAPI_PROC(char*,SDL_GetPrimarySelectionText,(void),(),return) -SDL_DYNAPI_PROC(Uint32,SDL_GetQueuedAudioSize,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(void,SDL_GetRGB,(Uint32 a, const SDL_PixelFormat *b, Uint8 *c, Uint8 *d, Uint8 *e),(a,b,c,d,e),) SDL_DYNAPI_PROC(void,SDL_GetRGBA,(Uint32 a, const SDL_PixelFormat *b, Uint8 *c, Uint8 *d, Uint8 *e, Uint8 *f),(a,b,c,d,e,f),) SDL_DYNAPI_PROC(SDL_bool,SDL_GetRectAndLineIntersection,(const SDL_Rect *a, int *b, int *c, int *d, int *e),(a,b,c,d,e),return) @@ -553,8 +534,6 @@ SDL_DYNAPI_PROC(void*,SDL_LoadFile,(const char *a, size_t *b),(a,b),return) SDL_DYNAPI_PROC(void*,SDL_LoadFile_RW,(SDL_RWops *a, size_t *b, SDL_bool c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_FunctionPointer,SDL_LoadFunction,(void *a, const char *b),(a,b),return) SDL_DYNAPI_PROC(void*,SDL_LoadObject,(const char *a),(a),return) -SDL_DYNAPI_PROC(SDL_AudioSpec*,SDL_LoadWAV_RW,(SDL_RWops *a, SDL_bool b, SDL_AudioSpec *c, Uint8 **d, Uint32 *e),(a,b,c,d,e),return) -SDL_DYNAPI_PROC(int,SDL_LockAudioDevice,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(void,SDL_LockJoysticks,(void),(),) SDL_DYNAPI_PROC(int,SDL_LockMutex,(SDL_Mutex *a),(a),return) SDL_DYNAPI_PROC(int,SDL_LockRWLockForReading,(SDL_RWLock *a),(a),return) @@ -578,7 +557,6 @@ SDL_DYNAPI_PROC(SDL_MetalView,SDL_Metal_CreateView,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(void,SDL_Metal_DestroyView,(SDL_MetalView a),(a),) SDL_DYNAPI_PROC(void*,SDL_Metal_GetLayer,(SDL_MetalView a),(a),return) SDL_DYNAPI_PROC(int,SDL_MinimizeWindow,(SDL_Window *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_MixAudioFormat,(Uint8 *a, const Uint8 *b, SDL_AudioFormat c, Uint32 d, int e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(int,SDL_MouseIsHaptic,(void),(),return) SDL_DYNAPI_PROC(int,SDL_NumHaptics,(void),(),return) SDL_DYNAPI_PROC(void,SDL_OnApplicationDidBecomeActive,(void),(),) @@ -587,12 +565,10 @@ SDL_DYNAPI_PROC(void,SDL_OnApplicationDidReceiveMemoryWarning,(void),(),) SDL_DYNAPI_PROC(void,SDL_OnApplicationWillEnterForeground,(void),(),) SDL_DYNAPI_PROC(void,SDL_OnApplicationWillResignActive,(void),(),) SDL_DYNAPI_PROC(void,SDL_OnApplicationWillTerminate,(void),(),) -SDL_DYNAPI_PROC(SDL_AudioDeviceID,SDL_OpenAudioDevice,(const char *a, int b, const SDL_AudioSpec *c, SDL_AudioSpec *d, int e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(SDL_Gamepad*,SDL_OpenGamepad,(SDL_JoystickID a),(a),return) SDL_DYNAPI_PROC(SDL_Joystick*,SDL_OpenJoystick,(SDL_JoystickID a),(a),return) SDL_DYNAPI_PROC(SDL_Sensor*,SDL_OpenSensor,(SDL_SensorID a),(a),return) SDL_DYNAPI_PROC(int,SDL_OpenURL,(const char *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_PauseAudioDevice,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(int,SDL_PeepEvents,(SDL_Event *a, int b, SDL_eventaction c, Uint32 d, Uint32 e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(int,SDL_PlayAudioDevice,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(int,SDL_PollEvent,(SDL_Event *a),(a),return) @@ -600,9 +576,7 @@ SDL_DYNAPI_PROC(int,SDL_PostSemaphore,(SDL_Semaphore *a),(a),return) SDL_DYNAPI_PROC(int,SDL_PremultiplyAlpha,(int a, int b, Uint32 c, const void *d, int e, Uint32 f, void *g, int h),(a,b,c,d,e,f,g,h),return) SDL_DYNAPI_PROC(void,SDL_PumpEvents,(void),(),) SDL_DYNAPI_PROC(int,SDL_PushEvent,(SDL_Event *a),(a),return) -SDL_DYNAPI_PROC(int,SDL_PutAudioStreamData,(SDL_AudioStream *a, const void *b, int c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_QueryTexture,(SDL_Texture *a, Uint32 *b, int *c, int *d, int *e),(a,b,c,d,e),return) -SDL_DYNAPI_PROC(int,SDL_QueueAudio,(SDL_AudioDeviceID a, const void *b, Uint32 c),(a,b,c),return) SDL_DYNAPI_PROC(void,SDL_Quit,(void),(),) SDL_DYNAPI_PROC(void,SDL_QuitSubSystem,(Uint32 a),(a),) SDL_DYNAPI_PROC(SDL_RWops*,SDL_RWFromConstMem,(const void *a, size_t b),(a,b),return) @@ -751,7 +725,6 @@ SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForReading,(SDL_RWLock *a),(a),return) SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForWriting,(SDL_RWLock *a),(a),return) SDL_DYNAPI_PROC(int,SDL_TryWaitSemaphore,(SDL_Semaphore *a),(a),return) SDL_DYNAPI_PROC(void,SDL_UnloadObject,(void *a),(a),) -SDL_DYNAPI_PROC(void,SDL_UnlockAudioDevice,(SDL_AudioDeviceID a),(a),) SDL_DYNAPI_PROC(void,SDL_UnlockJoysticks,(void),(),) SDL_DYNAPI_PROC(int,SDL_UnlockMutex,(SDL_Mutex *a),(a),return) SDL_DYNAPI_PROC(int,SDL_UnlockRWLock,(SDL_RWLock *a),(a),return) @@ -950,3 +923,30 @@ SDL_DYNAPI_PROC(size_t,SDL_wcsnlen,(const wchar_t *a, size_t b),(a,b),return) SDL_DYNAPI_PROC(size_t,SDL_strnlen,(const char *a, size_t b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_AddGamepadMappingsFromFile,(const char *a),(a),return) SDL_DYNAPI_PROC(int,SDL_ReloadGamepadMappings,(void),(),return) +SDL_DYNAPI_PROC(int,SDL_GetNumAudioDrivers,(void),(),return) +SDL_DYNAPI_PROC(const char*,SDL_GetAudioDriver,(int a),(a),return) +SDL_DYNAPI_PROC(const char*,SDL_GetCurrentAudioDriver,(void),(),return) +SDL_DYNAPI_PROC(SDL_AudioDeviceID*,SDL_GetAudioOutputDevices,(int *a),(a),return) +SDL_DYNAPI_PROC(SDL_AudioDeviceID*,SDL_GetAudioCaptureDevices,(int *a),(a),return) +SDL_DYNAPI_PROC(char*,SDL_GetAudioDeviceName,(SDL_AudioDeviceID a),(a),return) +SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceFormat,(SDL_AudioDeviceID a, SDL_AudioFormat *b, int *c, int *d),(a,b,c,d),return) +SDL_DYNAPI_PROC(SDL_AudioDeviceID,SDL_OpenAudioDevice,(SDL_AudioDeviceID a, SDL_AudioFormat b, int c, int d),(a,b,c,d),return) +SDL_DYNAPI_PROC(void,SDL_CloseAudioDevice,(SDL_AudioDeviceID a),(a),) +SDL_DYNAPI_PROC(int,SDL_BindAudioStreams,(SDL_AudioDeviceID a, SDL_AudioStream **b, int c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_BindAudioStream,(SDL_AudioDeviceID a, SDL_AudioStream *b),(a,b),return) +SDL_DYNAPI_PROC(void,SDL_UnbindAudioStreams,(SDL_AudioStream **a, int b),(a,b),) +SDL_DYNAPI_PROC(void,SDL_UnbindAudioStream,(SDL_AudioStream *a),(a),) +SDL_DYNAPI_PROC(SDL_AudioStream*,SDL_CreateAudioStream,(SDL_AudioFormat a, int b, int c, SDL_AudioFormat d, int e, int f),(a,b,c,d,e,f),return) +SDL_DYNAPI_PROC(int,SDL_GetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat *b, int *c, int *d, SDL_AudioFormat *e, int *f, int *g),(a,b,c,d,e,f,g),return) +SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat b, int c, int d, SDL_AudioFormat e, int f, int g),(a,b,c,d,e,f,g),return) +SDL_DYNAPI_PROC(int,SDL_PutAudioStreamData,(SDL_AudioStream *a, const void *b, int c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_GetAudioStreamData,(SDL_AudioStream *a, void *b, int c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_GetAudioStreamAvailable,(SDL_AudioStream *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_FlushAudioStream,(SDL_AudioStream *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_ClearAudioStream,(SDL_AudioStream *a),(a),return) +SDL_DYNAPI_PROC(void,SDL_DestroyAudioStream,(SDL_AudioStream *a),(a),) +SDL_DYNAPI_PROC(SDL_AudioStream*,SDL_CreateAndBindAudioStream,(SDL_AudioDeviceID a, SDL_AudioFormat b, int c, int d),(a,b,c,d),return) +SDL_DYNAPI_PROC(int,SDL_LoadWAV_RW,(SDL_RWops *a, int b, SDL_AudioFormat *c, int *d, int *e, Uint8 **f, Uint32 *g),(a,b,c,d,e,f,g),return) +SDL_DYNAPI_PROC(int,SDL_MixAudioFormat,(Uint8 *a, const Uint8 *b, SDL_AudioFormat c, Uint32 d, int e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(int,SDL_ConvertAudioSamples,(SDL_AudioFormat a, int b, int c, const Uint8 *d, int e, SDL_AudioFormat f, int g, int h, Uint8 **i, int *j),(a,b,c,d,e,f,g,h,i,j),return) +SDL_DYNAPI_PROC(int,SDL_GetSilenceValueForFormat,(SDL_AudioFormat a),(a),return) diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index f621ef7ad8..25f7400b94 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -117,10 +117,9 @@ SDLTest_CommonState *SDLTest_CommonCreateState(char **argv, Uint32 flags) state->logical_presentation = SDL_LOGICAL_PRESENTATION_DISABLED; state->logical_scale_mode = SDL_SCALEMODE_LINEAR; state->num_windows = 1; - state->audiospec.freq = 22050; - state->audiospec.format = SDL_AUDIO_S16; - state->audiospec.channels = 2; - state->audiospec.samples = 2048; + state->audio_freq = 22050; + state->audio_format = SDL_AUDIO_S16; + state->audio_channels = 2; /* Set some very sane GL defaults */ state->gl_red_size = 8; @@ -604,7 +603,7 @@ int SDLTest_CommonArg(SDLTest_CommonState *state, int index) if (!argv[index]) { return -1; } - state->audiospec.freq = SDL_atoi(argv[index]); + state->audio_freq = SDL_atoi(argv[index]); return 2; } if (SDL_strcasecmp(argv[index], "--format") == 0) { @@ -613,23 +612,23 @@ int SDLTest_CommonArg(SDLTest_CommonState *state, int index) return -1; } if (SDL_strcasecmp(argv[index], "U8") == 0) { - state->audiospec.format = SDL_AUDIO_U8; + state->audio_format = SDL_AUDIO_U8; return 2; } if (SDL_strcasecmp(argv[index], "S8") == 0) { - state->audiospec.format = SDL_AUDIO_S8; + state->audio_format = SDL_AUDIO_S8; return 2; } if (SDL_strcasecmp(argv[index], "S16") == 0) { - state->audiospec.format = SDL_AUDIO_S16; + state->audio_format = SDL_AUDIO_S16; return 2; } if (SDL_strcasecmp(argv[index], "S16LE") == 0) { - state->audiospec.format = SDL_AUDIO_S16LSB; + state->audio_format = SDL_AUDIO_S16LSB; return 2; } if (SDL_strcasecmp(argv[index], "S16BE") == 0) { - state->audiospec.format = SDL_AUDIO_S16MSB; + state->audio_format = SDL_AUDIO_S16MSB; return 2; } @@ -642,15 +641,7 @@ int SDLTest_CommonArg(SDLTest_CommonState *state, int index) if (!argv[index]) { return -1; } - state->audiospec.channels = (Uint8) SDL_atoi(argv[index]); - return 2; - } - if (SDL_strcasecmp(argv[index], "--samples") == 0) { - ++index; - if (!argv[index]) { - return -1; - } - state->audiospec.samples = (Uint16) SDL_atoi(argv[index]); + state->audio_channels = (Uint8) SDL_atoi(argv[index]); return 2; } } @@ -1452,7 +1443,7 @@ SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state) SDL_GetCurrentAudioDriver()); } - state->audio_id = SDL_OpenAudioDevice(NULL, 0, &state->audiospec, NULL, 0); + state->audio_id = SDL_OpenAudioDevice(0, state->audio_format, state->audio_channels, state->audio_freq); if (!state->audio_id) { SDL_Log("Couldn't open audio: %s\n", SDL_GetError()); return SDL_FALSE; diff --git a/test/loopwave.c b/test/loopwave.c index ac5f84d352..871fc8eec2 100644 --- a/test/loopwave.c +++ b/test/loopwave.c @@ -28,13 +28,15 @@ static struct { - SDL_AudioSpec spec; + SDL_AudioFormat fmt; + int channels; + int freq; Uint8 *sound; /* Pointer to wave data */ Uint32 soundlen; /* Length of wave data */ - int soundpos; /* Current play position */ } wave; static SDL_AudioDeviceID device; +static SDL_AudioStream *stream; /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ static void @@ -51,6 +53,8 @@ static void close_audio(void) { if (device != 0) { + SDL_DestroyAudioStream(stream); + stream = NULL; SDL_CloseAudioDevice(device); device = 0; } @@ -59,16 +63,21 @@ close_audio(void) static void open_audio(void) { - /* Initialize fillerup() variables */ - device = SDL_OpenAudioDevice(NULL, SDL_FALSE, &wave.spec, NULL, 0); + SDL_AudioDeviceID *devices = SDL_GetAudioOutputDevices(NULL); + device = devices ? SDL_OpenAudioDevice(devices[0], wave.fmt, wave.channels, wave.freq) : 0; + SDL_free(devices); if (!device) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s\n", SDL_GetError()); SDL_free(wave.sound); quit(2); } - - /* Let the audio run */ - SDL_PlayAudioDevice(device); + stream = SDL_CreateAndBindAudioStream(device, wave.fmt, wave.channels, wave.freq); + if (!stream) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create audio stream: %s\n", SDL_GetError()); + SDL_CloseAudioDevice(device); + SDL_free(wave.sound); + quit(2); + } } #ifndef __EMSCRIPTEN__ @@ -79,36 +88,24 @@ static void reopen_audio(void) } #endif -static void SDLCALL -fillerup(void *unused, Uint8 *stream, int len) -{ - Uint8 *waveptr; - int waveleft; - - /* Set up the pointers */ - waveptr = wave.sound + wave.soundpos; - waveleft = wave.soundlen - wave.soundpos; - - /* Go! */ - while (waveleft <= len) { - SDL_memcpy(stream, waveptr, waveleft); - stream += waveleft; - len -= waveleft; - waveptr = wave.sound; - waveleft = wave.soundlen; - wave.soundpos = 0; - } - SDL_memcpy(stream, waveptr, len); - wave.soundpos += len; -} static int done = 0; +static void fillerup(void) +{ + if (SDL_GetAudioStreamAvailable(stream) < (wave.soundlen / 2)) { + SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen); + } +} + + #ifdef __EMSCRIPTEN__ static void loop(void) { if (done || (SDL_GetAudioDeviceStatus(device) != SDL_AUDIO_PLAYING)) { emscripten_cancel_main_loop(); + } else { + fillerup(); } } #endif @@ -162,13 +159,11 @@ int main(int argc, char *argv[]) } /* Load the wave file into memory */ - if (SDL_LoadWAV(filename, &wave.spec, &wave.sound, &wave.soundlen) == NULL) { + if (SDL_LoadWAV(filename, &wave.fmt, &wave.channels, &wave.freq, &wave.sound, &wave.soundlen) == -1) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError()); quit(1); } - wave.spec.callback = fillerup; - /* Show the list of available drivers */ SDL_Log("Available audio drivers:"); for (i = 0; i < SDL_GetNumAudioDrivers(); ++i) { @@ -196,6 +191,8 @@ int main(int argc, char *argv[]) reopen_audio(); } } + + fillerup(); SDL_Delay(100); } #endif From 56b1bc21980b18f0f55a3dba3fed93130e0cbb65 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sun, 28 May 2023 22:39:39 -0400 Subject: [PATCH 002/138] audio: SDL_AudioStream now has callbacks for Get and Put operations. This allows code to feed a stream (or feed from a stream) on-demand, which is to say: it can efficiently simulate the SDL2 audio callback. --- docs/README-migration.md | 22 +++- include/SDL3/SDL_audio.h | 165 ++++++++++++++++++++++++++++++ src/audio/SDL_audiocvt.c | 58 +++++++++++ src/audio/SDL_sysaudio.h | 5 + src/dynapi/SDL_dynapi.sym | 4 + src/dynapi/SDL_dynapi_overrides.h | 4 + src/dynapi/SDL_dynapi_procs.h | 4 + test/loopwave.c | 34 ++++-- 8 files changed, 286 insertions(+), 10 deletions(-) diff --git a/docs/README-migration.md b/docs/README-migration.md index 0a3c0fd5ed..1e8265c528 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -53,11 +53,11 @@ The following structures have been renamed: ## SDL_audio.h -The audio subsystem in SDL3 is dramatically different than SDL2. There is no longer an audio callback; instead you bind SDL_AudioStreams to devices. +The audio subsystem in SDL3 is dramatically different than SDL2. The primary way to play audio is no longer an audio callback; instead you bind SDL_AudioStreams to devices. The SDL 1.2 audio compatibility API has also been removed, as it was a simplified version of the audio callback interface. -If your app depends on the callback method, you can use the single-header library at https://github.com/libsdl-org/SDL3_audio_callback (to be written!) to simulate it on top of SDL3's new API. +If your app depends on the callback method, there is a similar approach you can take. But first, this is the new approach: In SDL2, you might have done something like this to play audio: @@ -84,7 +84,6 @@ in SDL3: ```c /* ...somewhere near startup... */ - my_desired_audio_format.callback = MyAudioCallback; /* etc */ SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(0, SDL_AUDIO_S16, 2, 44100); SDL_AudioSteam *stream = SDL_CreateAndBindAudioStream(my_audio_device, SDL_AUDIO_S16, 2, 44100); @@ -93,6 +92,23 @@ in SDL3: SDL_PutAudioStreamData(stream, buf, buflen); ``` +If you absolutely require the callback method, SDL_AudioStreams can use a callback whenever more data is to be read from them, which can be used to simulate SDL2 semantics: + +```c + void SDLCALL MyAudioCallback(SDL_AudioStream *stream, int len, void *userdata) + { + /* calculate a little more audio here, maybe using `userdata`, write it to `stream` */ + SDL_PutAudioStreamData(stream, newdata, len); + } + + /* ...somewhere near startup... */ + SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(0, SDL_AUDIO_S16, 2, 44100); + SDL_AudioSteam *stream = SDL_CreateAndBindAudioStream(my_audio_device, SDL_AUDIO_S16, 2, 44100); + SDL_SetAudioStreamGetCallback(stream, MyAudioCallback); + + /* MyAudioCallback will be called whenever the device requests more audio data. */ +``` + SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_AUDIO, which will properly refcount the subsystems. You can choose a specific audio driver using SDL_AUDIO_DRIVER hint. The `SDL_AUDIO_ALLOW_*` symbols have been removed; now one may request the format they desire from the audio device, but ultimately SDL_AudioStream will manage the difference. One can use SDL_GetAudioDeviceFormat() to see what the final format is, if any "allowed" changes should be accomodated by the app. diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 43ecc1ea12..1e073cdb22 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -713,6 +713,171 @@ extern DECLSPEC int SDLCALL SDL_FlushAudioStream(SDL_AudioStream *stream); */ extern DECLSPEC int SDLCALL SDL_ClearAudioStream(SDL_AudioStream *stream); +/** + * Lock an audio stream for serialized access. + * + * Each SDL_AudioStream has an internal mutex it uses to + * protect its data structures from threading conflicts. This function + * allows an app to lock that mutex, which could be useful if + * registering callbacks on this stream. + * + * One does not need to lock a stream to use in it most cases, + * as the stream manages this lock internally. However, this lock + * is held during callbacks, which may run from arbitrary threads + * at any time, so if an app needs to protect shared data during + * those callbacks, locking the stream guarantees that the + * callback is not running while the lock is held. + * + * As this is just a wrapper over SDL_LockMutex for an internal + * lock, it has all the same attributes (recursive locks are + * allowed, etc). + * + * \param stream The audio stream to lock. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \threadsafety It is safe to call this function from any thread. + * + * \sa SDL_UnlockAudioStream + * \sa SDL_SetAudioStreamPutCallback + * \sa SDL_SetAudioStreamGetCallback + */ +extern DECLSPEC int SDLCALL SDL_LockAudioStream(SDL_AudioStream *stream); + + +/** + * Unlock an audio stream for serialized access. + * + * This unlocks an audio stream after a call to SDL_LockAudioStream. + * + * \param stream The audio stream to unlock. + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \threadsafety You should only call this from the same thread that + * previously called SDL_LockAudioStream. + * + * \sa SDL_LockAudioStream + * \sa SDL_SetAudioStreamPutCallback + * \sa SDL_SetAudioStreamGetCallback + */ +extern DECLSPEC int SDLCALL SDL_UnlockAudioStream(SDL_AudioStream *stream); + +/** + * A callback that fires when data passes through an SDL_AudioStream. + * + * Apps can (optionally) register a callback with an audio stream that + * is called when data is added with SDL_PutAudioStreamData, or requested + * with SDL_GetAudioStreamData. These callbacks may run from any + * thread, so if you need to protect shared data, you should use + * SDL_LockAudioStream to serialize access; this lock will be held by + * before your callback is called, so your callback does not need to + * manage the lock explicitly. + * + * \param stream The SDL audio stream associated with this callback. + * \param approx_request The _approximate_ amout of data, in bytes, that is requested. + * This might be slightly overestimated due to buffering or + * resampling, and may change from call to call anyhow. + * \param userdata An opaque pointer provided by the app for their personal use. + */ +typedef void (SDLCALL *SDL_AudioStreamRequestCallback)(SDL_AudioStream *stream, int approx_request, void *userdata); + +/** + * Set a callback that runs when data is requested from an audio stream. + * + * This callback is called _before_ data is obtained from the stream, + * giving the callback the chance to add more on-demand. + * + * The callback can (optionally) call SDL_PutAudioStreamData() to add + * more audio to the stream during this call; if needed, the request + * that triggered this callback will obtain the new data immediately. + * + * The callback's `approx_request` argument is roughly how many bytes + * of _unconverted_ data (in the stream's input format) is needed by + * the caller, although this may overestimate a little for safety. + * This takes into account how much is already in the stream and only + * asks for any extra necessary to resolve the request, which means + * the callback may be asked for zero bytes, and a different amount + * on each call. + * + * The callback is not required to supply exact amounts; it is allowed + * to supply too much or too little or none at all. The caller will + * get what's available, up to the amount they requested, regardless + * of this callback's outcome. + * + * Clearing or flushing an audio stream does not call this callback. + * + * This function obtains the stream's lock, which means any existing + * callback (get or put) in progress will finish running before setting + * the new callback. + * + * Setting a NULL function turns off the callback. + * + * \param stream the audio stream to set the new callback on. + * \param callback the new callback function to call when data is added to the stream. + * \param userdata an opaque pointer provided to the callback for its own personal use. + * \returns 0 on success, -1 on error. This only fails if `stream` is NULL. + * + * \since This function is available since SDL 3.0.0. + * + * \threadsafety It is safe to call this function from any thread. + * + * \sa SDL_SetAudioStreamPutCallback + */ +extern DECLSPEC int SDLCALL SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamRequestCallback callback, void *userdata); + +/** + * Set a callback that runs when data is added to an audio stream. + * + * This callback is called _after_ the data is added to the stream, + * giving the callback the chance to obtain it immediately. + * + * The callback can (optionally) call SDL_GetAudioStreamData() to + * obtain audio from the stream during this call. + * + * The callback's `approx_request` argument is how many bytes + * of _converted_ data (in the stream's output format) was provided + * by the caller, although this may underestimate a little for safety. + * This value might be less than what is currently available in the + * stream, if data was already there, and might be less than the + * caller provided if the stream needs to keep a buffer to aid in + * resampling. Which means the callback may be provided with zero + * bytes, and a different amount on each call. + * + * The callback may call SDL_GetAudioStreamAvailable to see the + * total amount currently available to read from the stream, instead + * of the total provided by the current call. + * + * The callback is not required to obtain all data. It is allowed + * to read less or none at all. Anything not read now simply remains + * in the stream for later access. + * + * Clearing or flushing an audio stream does not call this callback. + * + * This function obtains the stream's lock, which means any existing + * callback (get or put) in progress will finish running before setting + * the new callback. + * + * Setting a NULL function turns off the callback. + * + * \param stream the audio stream to set the new callback on. + * \param callback the new callback function to call when data is added to the stream. + * \param userdata an opaque pointer provided to the callback for its own personal use. + * \returns 0 on success, -1 on error. This only fails if `stream` is NULL. + * + * \since This function is available since SDL 3.0.0. + * + * \threadsafety It is safe to call this function from any thread. + * + * \sa SDL_SetAudioStreamGetCallback + */ +extern DECLSPEC int SDLCALL SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamRequestCallback callback, void *userdata); + + /** * Free an audio stream * diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index f5d7b9c314..eec84107a4 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -629,6 +629,40 @@ SDL_AudioStream *SDL_CreateAudioStream(SDL_AudioFormat src_format, return retval; } +int SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamRequestCallback callback, void *userdata) +{ + if (!stream) { + return SDL_InvalidParamError("stream"); + } + SDL_LockMutex(stream->lock); + stream->get_callback = callback; + stream->get_callback_userdata = userdata; + SDL_UnlockMutex(stream->lock); + return 0; +} + +int SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamRequestCallback callback, void *userdata) +{ + if (!stream) { + return SDL_InvalidParamError("stream"); + } + SDL_LockMutex(stream->lock); + stream->put_callback = callback; + stream->put_callback_userdata = userdata; + SDL_UnlockMutex(stream->lock); + return 0; +} + +int SDL_LockAudioStream(SDL_AudioStream *stream) +{ + return stream ? SDL_LockMutex(stream->lock) : SDL_InvalidParamError("stream"); +} + +int SDL_UnlockAudioStream(SDL_AudioStream *stream) +{ + return stream ? SDL_UnlockMutex(stream->lock) : SDL_InvalidParamError("stream"); +} + int SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioFormat *src_format, int *src_channels, int *src_rate, SDL_AudioFormat *dst_format, int *dst_channels, int *dst_rate) { if (!stream) { @@ -696,6 +730,8 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) SDL_LockMutex(stream->lock); + const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0; + if ((len % stream->src_sample_frame_size) != 0) { SDL_UnlockMutex(stream->lock); return SDL_SetError("Can't add partial sample frames"); @@ -704,6 +740,11 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) /* just queue the data, we convert/resample when dequeueing. */ retval = SDL_WriteToDataQueue(stream->queue, buf, len); stream->flushed = SDL_FALSE; + + if (stream->put_callback) { + stream->put_callback(stream, SDL_GetAudioStreamAvailable(stream) - prev_available, stream->put_callback_userdata); + } + SDL_UnlockMutex(stream->lock); return retval; @@ -978,6 +1019,23 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) len -= len % stream->dst_sample_frame_size; /* chop off any fractional sample frame. */ + // give the callback a chance to fill in more stream data if it wants. + if (stream->get_callback) { + int approx_request = len / stream->dst_sample_frame_size; /* start with sample frames desired */ + if (stream->src_rate != stream->dst_rate) { + /* calculate difference in dataset size after resampling. Use a Uint64 so the multiplication doesn't overflow. */ + approx_request = (size_t) ((((Uint64) approx_request) * stream->src_rate) / stream->dst_rate); + if (!stream->flushed) { /* do we need to fill the future buffer to accomodate this, too? */ + approx_request += stream->future_buffer_filled_frames - stream->resampler_padding_frames; + } + } + + approx_request *= stream->src_sample_frame_size; // convert sample frames to bytes. + const int already_have = SDL_GetAudioStreamAvailable(stream); + approx_request -= SDL_min(approx_request, already_have); // we definitely have this much output already packed in. + stream->get_callback(stream, approx_request, stream->get_callback_userdata); + } + /* we convert in chunks, so we don't end up allocating a massive work buffer, etc. */ while (len > 0) { /* didn't ask for a whole sample frame, nothing to do */ const int chunk_size = 1024 * 1024; /* !!! FIXME: a megabyte might be overly-aggressive. */ diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 7fa95c8120..95350d8f37 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -137,6 +137,11 @@ struct SDL_AudioStream SDL_DataQueue *queue; SDL_Mutex *lock; /* this is just a copy of `queue`'s mutex. We share a lock. */ + SDL_AudioStreamRequestCallback get_callback; + void *get_callback_userdata; + SDL_AudioStreamRequestCallback put_callback; + void *put_callback_userdata; + Uint8 *work_buffer; /* used for scratch space during data conversion/resampling. */ Uint8 *history_buffer; /* history for left padding and future sample rate changes. */ Uint8 *future_buffer; /* stuff that left the queue for the right padding and will be next read's data. */ diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 7fca72a597..4cd53c2d68 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -879,6 +879,10 @@ SDL3_0.0.0 { SDL_MixAudioFormat; SDL_ConvertAudioSamples; SDL_GetSilenceValueForFormat; + SDL_LockAudioStream; + SDL_UnlockAudioStream; + SDL_SetAudioStreamGetCallback; + SDL_SetAudioStreamPutCallback; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 7bb20c97ce..6d623744b5 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -905,3 +905,7 @@ #define SDL_MixAudioFormat SDL_MixAudioFormat_REAL #define SDL_ConvertAudioSamples SDL_ConvertAudioSamples_REAL #define SDL_GetSilenceValueForFormat SDL_GetSilenceValueForFormat_REAL +#define SDL_LockAudioStream SDL_LockAudioStream_REAL +#define SDL_UnlockAudioStream SDL_UnlockAudioStream_REAL +#define SDL_SetAudioStreamGetCallback SDL_SetAudioStreamGetCallback_REAL +#define SDL_SetAudioStreamPutCallback SDL_SetAudioStreamPutCallback_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index a934315967..359d4552a9 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -950,3 +950,7 @@ SDL_DYNAPI_PROC(int,SDL_LoadWAV_RW,(SDL_RWops *a, int b, SDL_AudioFormat *c, int SDL_DYNAPI_PROC(int,SDL_MixAudioFormat,(Uint8 *a, const Uint8 *b, SDL_AudioFormat c, Uint32 d, int e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(int,SDL_ConvertAudioSamples,(SDL_AudioFormat a, int b, int c, const Uint8 *d, int e, SDL_AudioFormat f, int g, int h, Uint8 **i, int *j),(a,b,c,d,e,f,g,h,i,j),return) SDL_DYNAPI_PROC(int,SDL_GetSilenceValueForFormat,(SDL_AudioFormat a),(a),return) +SDL_DYNAPI_PROC(int,SDL_LockAudioStream,(SDL_AudioStream *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_UnlockAudioStream,(SDL_AudioStream *a),(a),return) +SDL_DYNAPI_PROC(int,SDL_SetAudioStreamGetCallback,(SDL_AudioStream *a, SDL_AudioStreamRequestCallback b, void *c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_SetAudioStreamPutCallback,(SDL_AudioStream *a, SDL_AudioStreamRequestCallback b, void *c),(a,b,c),return) diff --git a/test/loopwave.c b/test/loopwave.c index 871fc8eec2..127b3f4ce8 100644 --- a/test/loopwave.c +++ b/test/loopwave.c @@ -33,11 +33,36 @@ static struct int freq; Uint8 *sound; /* Pointer to wave data */ Uint32 soundlen; /* Length of wave data */ + Uint32 soundpos; } wave; static SDL_AudioDeviceID device; static SDL_AudioStream *stream; +static void SDLCALL +fillerup(SDL_AudioStream *stream, int len, void *unused) +{ + Uint8 *waveptr; + int waveleft; + + /*SDL_Log("CALLBACK WANTS %d MORE BYTES!", len);*/ + + /* Set up the pointers */ + waveptr = wave.sound + wave.soundpos; + waveleft = wave.soundlen - wave.soundpos; + + /* Go! */ + while (waveleft <= len) { + SDL_PutAudioStreamData(stream, waveptr, waveleft); + len -= waveleft; + waveptr = wave.sound; + waveleft = wave.soundlen; + wave.soundpos = 0; + } + SDL_PutAudioStreamData(stream, waveptr, len); + wave.soundpos += len; +} + /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ static void quit(int rc) @@ -78,6 +103,8 @@ open_audio(void) SDL_free(wave.sound); quit(2); } + + SDL_SetAudioStreamGetCallback(stream, fillerup, NULL); } #ifndef __EMSCRIPTEN__ @@ -91,12 +118,6 @@ static void reopen_audio(void) static int done = 0; -static void fillerup(void) -{ - if (SDL_GetAudioStreamAvailable(stream) < (wave.soundlen / 2)) { - SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen); - } -} #ifdef __EMSCRIPTEN__ @@ -192,7 +213,6 @@ int main(int argc, char *argv[]) } } - fillerup(); SDL_Delay(100); } #endif From e6aaed7d799620516e0c0df4e6ea4df3066e46e7 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 29 May 2023 23:24:21 -0400 Subject: [PATCH 003/138] include: Audio is not, and has not been, a raw mixing buffer for a long time. --- include/SDL3/SDL_audio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 1e073cdb22..9c0491bd81 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -24,7 +24,7 @@ /** * \file SDL_audio.h * - * \brief Access to the raw audio mixing buffer for the SDL library. + * \brief Audio functionality for the SDL library. */ #ifndef SDL_audio_h_ From 26525f5fd39da742250326e1541f520a81c33e2f Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 30 May 2023 01:08:22 -0400 Subject: [PATCH 004/138] audio: Readd SDL_AudioSpec, but just with format/channels/freq fields. --- docs/README-migration.md | 2 +- include/SDL3/SDL_audio.h | 109 ++++++----------- src/audio/SDL_audio.c | 129 +++++++++---------- src/audio/SDL_audiocvt.c | 170 ++++++++++++++------------ src/audio/SDL_sysaudio.h | 21 +--- src/audio/SDL_wave.c | 29 ++--- src/audio/disk/SDL_diskaudio.c | 6 +- src/audio/dummy/SDL_dummyaudio.c | 2 +- src/audio/pulseaudio/SDL_pulseaudio.c | 28 +++-- src/dynapi/SDL_dynapi_procs.h | 22 ++-- src/test/SDL_test_common.c | 3 +- test/loopwave.c | 10 +- 12 files changed, 244 insertions(+), 287 deletions(-) diff --git a/docs/README-migration.md b/docs/README-migration.md index 1e8265c528..ffa2a38547 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -152,7 +152,7 @@ SDL_QueueAudio(), SDL_DequeueAudio, and SDL_ClearQueuedAudio and SDL_GetQueuedAu APIs that use channel counts used to use a Uint8 for the channel; now they use int. -SDL_AudioSpec has been removed; things that used it have simply started taking separate arguments for format, channel, and sample rate. SDL_GetSilenceValueForFormat() can provide the information from the SDL_AudioSpec's `silence` field. The other SDL_AudioSpec fields aren't relevant anymore. +SDL_AudioSpec has been reduced; now it only holds format, channel, and sample rate. SDL_GetSilenceValueForFormat() can provide the information from the SDL_AudioSpec's `silence` field. The other SDL2 SDL_AudioSpec fields aren't relevant anymore. SDL_GetAudioDeviceSpec() is removed; use SDL_GetAudioDeviceFormat() instead. diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 9c0491bd81..3e7468cf3e 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -141,6 +141,13 @@ typedef Uint16 SDL_AudioFormat; /* @} *//* Audio flags */ +typedef struct SDL_AudioSpec +{ + SDL_AudioFormat format; /**< Audio data format */ + int channels; /**< Number of channels: 1 mono, 2 stereo, etc */ + int freq; /**< sample rate: sample frames per second */ +} SDL_AudioSpec; + /* SDL_AudioStream is an audio conversion interface. - It can handle resampling data in chunks without generating artifacts, when it doesn't have the complete buffer available. @@ -305,9 +312,7 @@ extern DECLSPEC char *SDLCALL SDL_GetAudioDeviceName(SDL_AudioDeviceID devid); * can't be determined). * * \param devid the instance ID of the device to query. - * \param fmt On return, will be set to the device's data format. Can be NULL. - * \param channels On return, will be set to the device's channel count. Can be NULL. - * \param freq On return, will be set to the device's sample rate. Can be NULL. + * \param spec On return, will be filled with device details. * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * @@ -315,7 +320,7 @@ extern DECLSPEC char *SDLCALL SDL_GetAudioDeviceName(SDL_AudioDeviceID devid); * * \since This function is available since SDL 3.0.0. */ -extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioFormat *fmt, int *channels, int *freq); +extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec); /** @@ -362,9 +367,7 @@ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SD * * \param devid the device instance id to open. 0 requests the most * reasonable default device. - * \param fmt the requested device format (`SDL_AUDIO_S16`, etc) - * \param channels the requested device channels (1==mono, 2==stereo, etc). - * \param freq the requested device frequency in sample-frames-per-second (Hz) + * \param spec the requested device configuration * \returns The device ID on success, 0 on error; call SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. @@ -374,7 +377,7 @@ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SD * \sa SDL_CloseAudioDevice * \sa SDL_GetAudioDeviceFormat */ -extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(SDL_AudioDeviceID devid, SDL_AudioFormat fmt, int channels, int freq); +extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec); /** @@ -499,12 +502,8 @@ extern DECLSPEC void SDLCALL SDL_UnbindAudioStream(SDL_AudioStream *stream); /** * Create a new audio stream. * - * \param src_format The format of the source audio - * \param src_channels The number of channels of the source audio - * \param src_rate The sampling rate of the source audio - * \param dst_format The format of the desired audio output - * \param dst_channels The number of channels of the desired audio output - * \param dst_rate The sampling rate of the desired audio output + * \param src_spec The format details of the input audio + * \param dst_spec The format details of the output audio * \returns 0 on success, or -1 on error. * * \threadsafety It is safe to call this function from any thread. @@ -519,26 +518,15 @@ extern DECLSPEC void SDLCALL SDL_UnbindAudioStream(SDL_AudioStream *stream); * \sa SDL_ChangeAudioStreamOutput * \sa SDL_DestroyAudioStream */ -extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAudioStream(SDL_AudioFormat src_format, - int src_channels, - int src_rate, - SDL_AudioFormat dst_format, - int dst_channels, - int dst_rate); +extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec); /** * Query the current format of an audio stream. * * \param stream the SDL_AudioStream to query. - * \param src_format Where to store the input audio format; ignored if NULL. - * \param src_channels Where to store the input channel count; ignored if - * NULL. - * \param src_rate Where to store the input sample rate; ignored if NULL. - * \param dst_format Where to store the output audio format; ignored if NULL. - * \param dst_channels Where to store the output channel count; ignored if - * NULL. - * \param dst_rate Where to store the output sample rate; ignored if NULL. + * \param src_spec Where to store the input audio format; ignored if NULL. + * \param dst_spec Where to store the output audio format; ignored if NULL. * \returns 0 on success, or -1 on error. * * \threadsafety It is safe to call this function from any thread, as it holds @@ -547,12 +535,8 @@ extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAudioStream(SDL_AudioFormat s * \since This function is available since SDL 3.0.0. */ extern DECLSPEC int SDLCALL SDL_GetAudioStreamFormat(SDL_AudioStream *stream, - SDL_AudioFormat *src_format, - int *src_channels, - int *src_rate, - SDL_AudioFormat *dst_format, - int *dst_channels, - int *dst_rate); + SDL_AudioSpec *src_spec, + SDL_AudioSpec *dst_spec); /** * Change the input and output formats of an audio stream. @@ -562,12 +546,8 @@ extern DECLSPEC int SDLCALL SDL_GetAudioStreamFormat(SDL_AudioStream *stream, * must provide data in the new input formats. * * \param stream The stream the format is being changed - * \param src_format The format of the audio input - * \param src_channels The number of channels of the audio input - * \param src_rate The sampling rate of the audio input - * \param dst_format The format of the desired audio output - * \param dst_channels The number of channels of the desired audio output - * \param dst_rate The sampling rate of the desired audio output + * \param src_spec The new format of the audio input; if NULL, it is not changed. + * \param dst_spec The new format of the audio output; if NULL, it is not changed. * \returns 0 on success, or -1 on error. * * \threadsafety It is safe to call this function from any thread, as it holds @@ -581,12 +561,8 @@ extern DECLSPEC int SDLCALL SDL_GetAudioStreamFormat(SDL_AudioStream *stream, * \sa SDL_GetAudioStreamAvailable */ extern DECLSPEC int SDLCALL SDL_SetAudioStreamFormat(SDL_AudioStream *stream, - SDL_AudioFormat src_format, - int src_channels, - int src_rate, - SDL_AudioFormat dst_format, - int dst_channels, - int dst_rate); + const SDL_AudioSpec *src_spec, + const SDL_AudioSpec *dst_spec); /** * Add data to be converted/resampled to the stream. @@ -902,15 +878,13 @@ extern DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream); * correctly to match both the app and the audio device's needs. This is * optional, but slightly less cumbersome to set up for a common use case. * - * The format parameters (`fmt`, `channels`, `freq`) represent the app's side - * of the audio stream. That is, for recording audio, this will be the output - * format, and for playing audio, this will be the input format. This function - * will set the other side of the audio stream to the device's format. + * The `spec` parameter represents the app's side of the audio stream. That + * is, for recording audio, this will be the output format, and for playing + * audio, this will be the input format. This function will set the other + * side of the audio stream to the device's format. * * \param devid an audio device to bind a stream to. This must be an opened device, and can not be zero. - * \param fmt the audio stream's format (`SDL_AUDIO_S16`, etc) - * \param channels the audio stream's channel count (1==mono, 2==stereo, etc). - * \param freq the audio stream's frequency in sample-frames-per-second (Hz) + * \param spec the audio stream's input format * \returns a bound audio stream on success, ready to use. NULL on error; call SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. @@ -921,7 +895,7 @@ extern DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream); * \sa SDL_UnbindAudioStreams * \sa SDL_UnbindAudioStream */ -extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAndBindAudioStream(SDL_AudioDeviceID devid, SDL_AudioFormat fmt, int channels, int freq); +extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAndBindAudioStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec); /** @@ -981,12 +955,8 @@ extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAndBindAudioStream(SDL_AudioD * * \param src The data source for the WAVE data * \param freesrc If non-zero, SDL will _always_ free the data source - * \param fmt A pointer to an SDL_AudioFormat that will be set to the - * WAVE data's format on successful return. - * \param channels A pointer to an int that will be set to the - * WAVE data's channel count on successful return. - * \param freq A pointer to an int that will be set to the - * WAVE data's sample rate in Hz on successful return. + * \param spec A pointer to an SDL_AudioSpec that will be set to the + * WAVE data's format details on successful return. * \param audio_buf A pointer filled with the audio data, allocated by the * function. * \param audio_len A pointer filled with the length of the audio data buffer @@ -1009,8 +979,7 @@ extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAndBindAudioStream(SDL_AudioD * \sa SDL_LoadWAV */ extern DECLSPEC int SDLCALL SDL_LoadWAV_RW(SDL_RWops * src, int freesrc, - SDL_AudioFormat *fmt, int *channels, int *freq, - Uint8 ** audio_buf, + SDL_AudioSpec * spec, Uint8 ** audio_buf, Uint32 * audio_len); /** @@ -1074,14 +1043,10 @@ extern DECLSPEC int SDLCALL SDL_MixAudioFormat(Uint8 * dst, * use, so it's also less efficient than using one directly, if you need to * convert multiple times. * - * \param src_format The format of the source audio - * \param src_channels The number of channels of the source audio - * \param src_rate The sampling rate of the source audio + * \param src_spec The format details of the input audio * \param src_data The audio data to be converted * \param src_len The len of src_data - * \param dst_format The format of the desired audio output - * \param dst_channels The number of channels of the desired audio output - * \param dst_rate The sampling rate of the desired audio output + * \param dst_spec The format details of the output audio * \param dst_data Will be filled with a pointer to converted audio data, * which should be freed with SDL_free(). On error, it will be * NULL. @@ -1093,14 +1058,10 @@ extern DECLSPEC int SDLCALL SDL_MixAudioFormat(Uint8 * dst, * * \sa SDL_CreateAudioStream */ -extern DECLSPEC int SDLCALL SDL_ConvertAudioSamples(SDL_AudioFormat src_format, - int src_channels, - int src_rate, +extern DECLSPEC int SDLCALL SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, - SDL_AudioFormat dst_format, - int dst_channels, - int dst_rate, + const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len); diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 05d6e83210..060980ee77 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -169,7 +169,7 @@ static void DestroyAudioDevice(SDL_AudioDevice *device) SDL_free(device); } -static SDL_AudioDevice *CreateAudioDevice(const char *name, SDL_bool iscapture, SDL_AudioFormat fmt, int channels, int freq, void *handle, SDL_AudioDevice **devices, SDL_AtomicInt *device_count) +static SDL_AudioDevice *CreateAudioDevice(const char *name, SDL_bool iscapture, const SDL_AudioSpec *spec, void *handle, SDL_AudioDevice **devices, SDL_AtomicInt *device_count) { SDL_AudioDevice *device; @@ -202,10 +202,9 @@ static SDL_AudioDevice *CreateAudioDevice(const char *name, SDL_bool iscapture, SDL_AtomicSet(&device->shutdown, 0); SDL_AtomicSet(&device->condemned, 0); device->iscapture = iscapture; - device->format = device->default_format = fmt; - device->channels = device->default_channels = channels; - device->freq = device->default_freq = freq; - device->silence_value = SDL_GetSilenceValueForFormat(device->format); + SDL_memcpy(&device->spec, spec, sizeof (SDL_AudioSpec)); + SDL_memcpy(&device->default_spec, spec, sizeof (SDL_AudioSpec)); + device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format); device->handle = handle; device->prev = NULL; @@ -225,33 +224,34 @@ static SDL_AudioDevice *CreateAudioDevice(const char *name, SDL_bool iscapture, return device; } -static SDL_AudioDevice *CreateAudioCaptureDevice(const char *name, SDL_AudioFormat fmt, int channels, int freq, void *handle) +static SDL_AudioDevice *CreateAudioCaptureDevice(const char *name, const SDL_AudioSpec *spec, void *handle) { SDL_assert(current_audio.impl.HasCaptureSupport); - return CreateAudioDevice(name, SDL_TRUE, fmt, channels, freq, handle, ¤t_audio.capture_devices, ¤t_audio.capture_device_count); + return CreateAudioDevice(name, SDL_TRUE, spec, handle, ¤t_audio.capture_devices, ¤t_audio.capture_device_count); } -static SDL_AudioDevice *CreateAudioOutputDevice(const char *name, SDL_AudioFormat fmt, int channels, int freq, void *handle) +static SDL_AudioDevice *CreateAudioOutputDevice(const char *name, const SDL_AudioSpec *spec, void *handle) { - return CreateAudioDevice(name, SDL_FALSE, fmt, channels, freq, handle, ¤t_audio.output_devices, ¤t_audio.output_device_count); + return CreateAudioDevice(name, SDL_FALSE, spec, handle, ¤t_audio.output_devices, ¤t_audio.output_device_count); } /* The audio backends call this when a new device is plugged in. */ -SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, SDL_AudioFormat fmt, int channels, int freq, void *handle) +SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, const SDL_AudioSpec *inspec, void *handle) { SDL_AudioDevice *device; + SDL_AudioSpec spec; - if (fmt == 0) { - fmt = DEFAULT_AUDIO_FORMAT; - } - if (channels == 0) { - channels = DEFAULT_AUDIO_CHANNELS; - } - if (freq == 0) { - freq = DEFAULT_AUDIO_FREQUENCY; + if (!inspec) { + spec.format = DEFAULT_AUDIO_FORMAT; + spec.channels = DEFAULT_AUDIO_CHANNELS; + spec.freq = DEFAULT_AUDIO_FREQUENCY; + } else { + spec.format = (inspec->format != 0) ? inspec->format : DEFAULT_AUDIO_FORMAT; + spec.channels = (inspec->channels != 0) ? inspec->channels : DEFAULT_AUDIO_CHANNELS; + spec.freq = (inspec->freq != 0) ? inspec->freq : DEFAULT_AUDIO_FREQUENCY; } - device = iscapture ? CreateAudioCaptureDevice(name, fmt, channels, freq, handle) : CreateAudioOutputDevice(name, fmt, channels, freq, handle); + device = iscapture ? CreateAudioCaptureDevice(name, &spec, handle) : CreateAudioOutputDevice(name, &spec, handle); if (device) { /* Post the event, if desired */ if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_ADDED)) { @@ -330,9 +330,9 @@ static void SDL_AudioDetectDevices_Default(void) SDL_assert(current_audio.impl.OnlyHasDefaultOutputDevice); SDL_assert(current_audio.impl.OnlyHasDefaultCaptureDevice || !current_audio.impl.HasCaptureSupport); - SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, 0, 0, 0, (void *)((size_t)0x1)); + SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)((size_t)0x1)); if (current_audio.impl.HasCaptureSupport) { - SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, 0, 0, 0, (void *)((size_t)0x2)); + SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)((size_t)0x2)); } } @@ -614,7 +614,7 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) retval = SDL_FALSE; break; } else if (br > 0) { /* it's okay if we get less than requested, we mix what we have. */ - if (SDL_MixAudioFormat(mix_buffer, device->work_buffer, device->format, br, SDL_MIX_MAXVOLUME) < 0) { /* !!! FIXME: allow streams to specify gain? */ + if (SDL_MixAudioFormat(mix_buffer, device->work_buffer, device->spec.format, br, SDL_MIX_MAXVOLUME) < 0) { /* !!! FIXME: allow streams to specify gain? */ SDL_assert(!"We probably ended up with some totally unexpected audio format here"); retval = SDL_FALSE; /* uh...? */ break; @@ -636,10 +636,10 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device) { - const int samples = (device->buffer_size / (SDL_AUDIO_BITSIZE(device->format) / 8)) / device->channels; + const int samples = (device->buffer_size / (SDL_AUDIO_BITSIZE(device->spec.format) / 8)) / device->spec.channels; SDL_assert(!device->iscapture); /* Wait for the audio to drain. */ /* !!! FIXME: don't bother waiting if device is lost. */ - SDL_Delay(((samples * 1000) / device->freq) * 2); + SDL_Delay(((samples * 1000) / device->spec.freq) * 2); current_audio.impl.ThreadDeinit(device); if (SDL_AtomicGet(&device->condemned)) { SDL_DetachThread(device->thread); /* no one is waiting for us, just detach ourselves. */ @@ -878,23 +878,18 @@ char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) return retval; } -int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioFormat *fmt, int *channels, int *freq) +int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec) { + if (!spec) { + return SDL_InvalidParamError("spec"); + } + SDL_AudioDevice *device = ObtainAudioDevice(devid); if (!device) { return -1; } - if (fmt) { - *fmt = device->format; - } - if (channels) { - *channels = device->channels; - } - if (freq) { - *freq = device->freq; - } - + SDL_memcpy(spec, &device->spec, sizeof (SDL_AudioSpec)); SDL_UnlockMutex(device->lock); return 0; @@ -917,11 +912,9 @@ static void CloseAudioDevice(SDL_AudioDevice *device) device->work_buffer = NULL; } - device->format = device->default_format; - device->channels = device->default_channels; - device->freq = device->default_freq; + SDL_memcpy(&device->spec, &device->default_spec, sizeof (SDL_AudioSpec)); device->sample_frames = 0; - device->silence_value = SDL_GetSilenceValueForFormat(device->format); + device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format); } void SDL_CloseAudioDevice(SDL_AudioDeviceID devid) @@ -965,35 +958,35 @@ static SDL_AudioFormat ParseAudioFormatString(const char *string) return 0; } -static void PrepareAudioFormat(SDL_AudioFormat *fmt, int *channels, int *freq) +static void PrepareAudioFormat(SDL_AudioSpec *spec) { const char *env; - if (*freq == 0) { - *freq = DEFAULT_AUDIO_FREQUENCY; - env = SDL_getenv("SDL_AUDIO_FREQUENCY"); + if (spec->freq == 0) { + spec->freq = DEFAULT_AUDIO_FREQUENCY; + env = SDL_getenv("SDL_AUDIO_FREQUENCY"); // !!! FIXME: should be a hint? if (env != NULL) { const int val = SDL_atoi(env); if (val > 0) { - *freq = val; + spec->freq = val; } } } - if (*channels == 0) { - *channels = DEFAULT_AUDIO_CHANNELS; + if (spec->channels == 0) { + spec->channels = DEFAULT_AUDIO_CHANNELS; env = SDL_getenv("SDL_AUDIO_CHANNELS"); if (env != NULL) { const int val = SDL_atoi(env); if (val > 0) { - *channels = val; + spec->channels = val; } } } - if (*fmt == 0) { + if (spec->format == 0) { const SDL_AudioFormat val = ParseAudioFormatString(SDL_getenv("SDL_AUDIO_FORMAT")); - *fmt = (val != 0) ? val : DEFAULT_AUDIO_FORMAT; + spec->format = (val != 0) ? val : DEFAULT_AUDIO_FORMAT; } } @@ -1010,25 +1003,27 @@ static int GetDefaultSampleFramesFromFreq(int freq) void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device) { - device->silence_value = SDL_GetSilenceValueForFormat(device->format); - device->buffer_size = device->sample_frames * (SDL_AUDIO_BITSIZE(device->format) / 8) * device->channels; + device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format); + device->buffer_size = device->sample_frames * (SDL_AUDIO_BITSIZE(device->spec.format) / 8) * device->spec.channels; } /* this expects the device lock to be held. */ -static int OpenAudioDevice(SDL_AudioDevice *device, SDL_AudioFormat fmt, int channels, int freq) +static int OpenAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec) { SDL_assert(SDL_AtomicGet(&device->refcount) == 1); - PrepareAudioFormat(&fmt, &channels, &freq); + SDL_AudioSpec spec; + SDL_memcpy(&spec, inspec, sizeof (SDL_AudioSpec)); + PrepareAudioFormat(&spec); /* we allow the device format to change if it's better than the current settings (by various definitions of "better"). This prevents something low quality, like an old game using S8/8000Hz audio, from ruining a music thing playing at CD quality that tries to open later. (or some VoIP library that opens for mono output ruining your surround-sound game because it got there first). */ /* These are just requests! The backend may change any of these values during OpenDevice method! */ - device->format = (SDL_AUDIO_BITSIZE(device->default_format) >= SDL_AUDIO_BITSIZE(fmt)) ? device->default_format : fmt; - device->freq = SDL_max(device->default_freq, freq); - device->channels = SDL_max(device->default_channels, channels); - device->sample_frames = GetDefaultSampleFramesFromFreq(device->freq); + device->spec.format = (SDL_AUDIO_BITSIZE(device->default_spec.format) >= SDL_AUDIO_BITSIZE(spec.format)) ? device->default_spec.format : spec.format; + device->spec.freq = SDL_max(device->default_spec.freq, spec.freq); + device->spec.channels = SDL_max(device->default_spec.channels, spec.channels); + device->sample_frames = GetDefaultSampleFramesFromFreq(device->spec.freq); SDL_UpdatedAudioDeviceFormat(device); /* start this off sane. */ if (current_audio.impl.OpenDevice(device) < 0) { @@ -1062,7 +1057,7 @@ static int OpenAudioDevice(SDL_AudioDevice *device, SDL_AudioFormat fmt, int cha return 0; } -SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, SDL_AudioFormat fmt, int channels, int freq) +SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec) { SDL_AudioDevice *device = ObtainAudioDevice(devid); /* !!! FIXME: need to choose default device for devid==0 */ int retval = 0; @@ -1070,7 +1065,7 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, SDL_AudioFormat f if (device) { retval = device->instance_id; if (SDL_AtomicIncRef(&device->refcount) == 0) { - if (OpenAudioDevice(device, fmt, channels, freq) == -1) { + if (OpenAudioDevice(device, spec) == -1) { retval = 0; } } @@ -1127,16 +1122,14 @@ int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int /* Now that everything is verified, chain everything together. */ for (i = 0; i < num_streams; i++) { SDL_AudioStream *stream = streams[i]; - SDL_AudioFormat src_format, dst_format; - int src_channels, dst_channels; - int src_rate, dst_rate; + SDL_AudioSpec src_spec, dst_spec; /* set the proper end of the stream to the device's format. */ - SDL_GetAudioStreamFormat(stream, &src_format, &src_channels, &src_rate, &dst_format, &dst_channels, &dst_rate); + SDL_GetAudioStreamFormat(stream, &src_spec, &dst_spec); if (device->iscapture) { - SDL_SetAudioStreamFormat(stream, device->format, device->channels, device->freq, dst_format, dst_channels, dst_rate); + SDL_SetAudioStreamFormat(stream, &device->spec, &dst_spec); } else { - SDL_SetAudioStreamFormat(stream, src_format, src_channels, src_rate, device->format, device->channels, device->freq); + SDL_SetAudioStreamFormat(stream, &src_spec, &device->spec); } stream->bound_device = device; @@ -1236,16 +1229,16 @@ void SDL_UnbindAudioStream(SDL_AudioStream *stream) } -SDL_AudioStream *SDL_CreateAndBindAudioStream(SDL_AudioDeviceID devid, SDL_AudioFormat fmt, int channels, int freq) +SDL_AudioStream *SDL_CreateAndBindAudioStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec) { SDL_AudioStream *stream = NULL; SDL_AudioDevice *device = ObtainAudioDevice(devid); if (device) { const SDL_bool iscapture = (devid & 1) ? SDL_FALSE : SDL_TRUE; /* capture instance ids are even and output devices are odd */ if (iscapture) { - stream = SDL_CreateAudioStream(device->format, device->channels, device->freq, fmt, channels, freq); + stream = SDL_CreateAudioStream(&device->spec, spec); } else { - stream = SDL_CreateAudioStream(fmt, channels, freq, device->format, device->channels, device->freq); + stream = SDL_CreateAudioStream(spec, &device->spec); } if (stream) { diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index eec84107a4..d61aa75ca2 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -446,13 +446,20 @@ static int CalculateMaxSampleFrameSize(SDL_AudioFormat src_format, int src_chann } /* this assumes you're holding the stream's lock (or are still creating the stream). */ -static int SetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioFormat src_format, int src_channels, int src_rate, SDL_AudioFormat dst_format, int dst_channels, int dst_rate) +static int SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec) { /* If increasing channels, do it after resampling, since we'd just do more work to resample duplicate channels. If we're decreasing, do it first so we resample the interpolated data instead of interpolating the resampled data (!!! FIXME: decide if that works in practice, though!). This is decided in pre_resample_channels. */ + + const SDL_AudioFormat src_format = src_spec->format; + const int src_channels = src_spec->channels; + const int src_rate = src_spec->freq; + const SDL_AudioFormat dst_format = dst_spec->format; + const int dst_channels = dst_spec->channels; + const int dst_rate = dst_spec->freq; const int src_sample_frame_size = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels; const int dst_sample_frame_size = (SDL_AUDIO_BITSIZE(dst_format) / 8) * dst_channels; const int max_sample_frame_size = CalculateMaxSampleFrameSize(src_format, src_channels, dst_format, dst_channels); @@ -515,16 +522,16 @@ static int SetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioFormat src_for /* copy to new buffers and/or convert data; ConvertAudio will do a simple memcpy if format matches, and nothing at all if the buffer hasn't changed */ if (stream->future_buffer) { - ConvertAudio(stream->future_buffer_filled_frames, stream->future_buffer, stream->src_format, stream->src_channels, future_buffer, src_format, src_channels); + ConvertAudio(stream->future_buffer_filled_frames, stream->future_buffer, stream->src_spec.format, stream->src_spec.channels, future_buffer, src_format, src_channels); } else if (future_buffer != NULL) { SDL_memset(future_buffer, SDL_GetSilenceValueForFormat(src_format), future_buffer_allocation); } if (stream->history_buffer) { if (history_buffer_frames <= prev_history_buffer_frames) { - ConvertAudio(history_buffer_frames, stream->history_buffer, stream->src_format, stream->src_channels, history_buffer, src_format, src_channels); + ConvertAudio(history_buffer_frames, stream->history_buffer, stream->src_spec.format, stream->src_spec.channels, history_buffer, src_format, src_channels); } else { - ConvertAudio(prev_history_buffer_frames, stream->history_buffer, stream->src_format, stream->src_channels, history_buffer + ((history_buffer_frames - prev_history_buffer_frames) * src_sample_frame_size), src_format, src_channels); + ConvertAudio(prev_history_buffer_frames, stream->history_buffer, stream->src_spec.format, stream->src_spec.channels, history_buffer + ((history_buffer_frames - prev_history_buffer_frames) * src_sample_frame_size), src_format, src_channels); SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(src_format), (history_buffer_frames - prev_history_buffer_frames) * src_sample_frame_size); /* silence oldest history samples. */ } } else if (history_buffer != NULL) { @@ -547,53 +554,56 @@ static int SetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioFormat src_for stream->history_buffer_frames = history_buffer_frames; stream->max_sample_frame_size = max_sample_frame_size; stream->src_sample_frame_size = src_sample_frame_size; - stream->src_format = src_format; - stream->src_channels = src_channels; - stream->src_rate = src_rate; stream->dst_sample_frame_size = dst_sample_frame_size; - stream->dst_format = dst_format; - stream->dst_channels = dst_channels; - stream->dst_rate = dst_rate; stream->pre_resample_channels = pre_resample_channels; + if (src_spec != &stream->src_spec) { + SDL_memcpy(&stream->src_spec, src_spec, sizeof (SDL_AudioSpec)); + } + + if (dst_spec != &stream->dst_spec) { + SDL_memcpy(&stream->dst_spec, dst_spec, sizeof (SDL_AudioSpec)); + } + return 0; } -SDL_AudioStream *SDL_CreateAudioStream(SDL_AudioFormat src_format, - int src_channels, - int src_rate, - SDL_AudioFormat dst_format, - int dst_channels, - int dst_rate) +SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec) { int packetlen = 4096; /* !!! FIXME: good enough for now. */ SDL_AudioStream *retval; /* !!! FIXME: fail if audio isn't initialized? */ - if (!SDL_IsSupportedChannelCount(src_channels)) { - SDL_InvalidParamError("src_channels"); + if (!src_spec) { + SDL_InvalidParamError("src_spec"); return NULL; - } else if (!SDL_IsSupportedChannelCount(dst_channels)) { - SDL_InvalidParamError("dst_channels"); + } else if (!dst_spec) { + SDL_InvalidParamError("dst_spec"); return NULL; - } else if (src_rate <= 0) { - SDL_InvalidParamError("src_rate"); + } else if (!SDL_IsSupportedChannelCount(src_spec->channels)) { + SDL_InvalidParamError("src_spec->channels"); return NULL; - } else if (dst_rate <= 0) { - SDL_InvalidParamError("dst_rate"); + } else if (!SDL_IsSupportedChannelCount(dst_spec->channels)) { + SDL_InvalidParamError("dst_spec->channels"); return NULL; - } else if (src_rate >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) { + } else if (src_spec->freq <= 0) { + SDL_InvalidParamError("src_spec->freq"); + return NULL; + } else if (dst_spec->freq <= 0) { + SDL_InvalidParamError("dst_spec->freq"); + return NULL; + } else if (src_spec->freq >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) { SDL_SetError("Source rate is too high"); return NULL; - } else if (dst_rate >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) { + } else if (dst_spec->freq >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) { SDL_SetError("Destination rate is too high"); return NULL; - } else if (!SDL_IsSupportedAudioFormat(src_format)) { - SDL_InvalidParamError("src_format"); + } else if (!SDL_IsSupportedAudioFormat(src_spec->format)) { + SDL_InvalidParamError("src_spec->format"); return NULL; - } else if (!SDL_IsSupportedAudioFormat(dst_format)) { - SDL_InvalidParamError("dst_format"); + } else if (!SDL_IsSupportedAudioFormat(dst_spec->format)) { + SDL_InvalidParamError("dst_spec->format"); return NULL; } @@ -615,13 +625,11 @@ SDL_AudioStream *SDL_CreateAudioStream(SDL_AudioFormat src_format, /* Make sure we've chosen audio conversion functions (SIMD, scalar, etc.) */ SDL_ChooseAudioConverters(); /* !!! FIXME: let's do this during SDL_Init? */ - retval->src_sample_frame_size = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels; - retval->src_format = src_format; - retval->src_channels = src_channels; - retval->src_rate = src_rate; + retval->src_sample_frame_size = (SDL_AUDIO_BITSIZE(src_spec->format) / 8) * src_spec->channels; retval->packetlen = packetlen; + SDL_memcpy(&retval->src_spec, src_spec, sizeof (SDL_AudioSpec)); - if (SetAudioStreamFormat(retval, src_format, src_channels, src_rate, dst_format, dst_channels, dst_rate) == -1) { + if (SetAudioStreamFormat(retval, src_spec, dst_spec) == -1) { SDL_DestroyAudioStream(retval); return NULL; } @@ -663,50 +671,56 @@ int SDL_UnlockAudioStream(SDL_AudioStream *stream) return stream ? SDL_UnlockMutex(stream->lock) : SDL_InvalidParamError("stream"); } -int SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioFormat *src_format, int *src_channels, int *src_rate, SDL_AudioFormat *dst_format, int *dst_channels, int *dst_rate) +int SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, SDL_AudioSpec *dst_spec) { if (!stream) { return SDL_InvalidParamError("stream"); } SDL_LockMutex(stream->lock); - #define GETAUDIOSTREAMFIELD(x) if (x) { *x = stream->x; } - GETAUDIOSTREAMFIELD(src_format); - GETAUDIOSTREAMFIELD(src_channels); - GETAUDIOSTREAMFIELD(src_rate); - GETAUDIOSTREAMFIELD(dst_format); - GETAUDIOSTREAMFIELD(dst_channels); - GETAUDIOSTREAMFIELD(dst_rate); - #undef GETAUDIOSTREAMFIELD + if (src_spec) { + SDL_memcpy(src_spec, &stream->src_spec, sizeof (SDL_AudioSpec)); + } + if (dst_spec) { + SDL_memcpy(dst_spec, &stream->dst_spec, sizeof (SDL_AudioSpec)); + } SDL_UnlockMutex(stream->lock); return 0; } -int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioFormat src_format, int src_channels, int src_rate, SDL_AudioFormat dst_format, int dst_channels, int dst_rate) +int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec) { int retval; if (!stream) { return SDL_InvalidParamError("stream"); - } else if (!SDL_IsSupportedAudioFormat(src_format)) { - return SDL_InvalidParamError("src_format"); - } else if (!SDL_IsSupportedChannelCount(src_channels)) { - return SDL_InvalidParamError("src_channels"); - } else if (src_rate <= 0) { - return SDL_InvalidParamError("src_rate"); - } else if (src_rate >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) { - return SDL_SetError("Source rate is too high"); - } else if (!SDL_IsSupportedAudioFormat(dst_format)) { - return SDL_InvalidParamError("dst_format"); - } else if (!SDL_IsSupportedChannelCount(dst_channels)) { - return SDL_InvalidParamError("dst_channels"); - } else if (dst_rate <= 0) { - return SDL_InvalidParamError("dst_rate"); - } else if (dst_rate >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) { - return SDL_SetError("Destination rate is too high"); + } + + if (src_spec) { + if (!SDL_IsSupportedAudioFormat(src_spec->format)) { + return SDL_InvalidParamError("src_spec->format"); + } else if (!SDL_IsSupportedChannelCount(src_spec->channels)) { + return SDL_InvalidParamError("src_spec->channels"); + } else if (src_spec->freq <= 0) { + return SDL_InvalidParamError("src_spec->freq"); + } else if (src_spec->freq >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) { + return SDL_SetError("Source rate is too high"); + } + } + + if (dst_spec) { + if (!SDL_IsSupportedAudioFormat(dst_spec->format)) { + return SDL_InvalidParamError("dst_spec->format"); + } else if (!SDL_IsSupportedChannelCount(dst_spec->channels)) { + return SDL_InvalidParamError("dst_spec->channels"); + } else if (dst_spec->freq <= 0) { + return SDL_InvalidParamError("dst_spec->freq"); + } else if (dst_spec->freq >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) { + return SDL_SetError("Destination rate is too high"); + } } SDL_LockMutex(stream->lock); - retval = SetAudioStreamFormat(stream, src_format, src_channels, src_rate, dst_format, dst_channels, dst_rate); + retval = SetAudioStreamFormat(stream, src_spec ? src_spec : &stream->src_spec, dst_spec ? dst_spec : &stream->dst_spec); SDL_UnlockMutex(stream->lock); return retval; @@ -797,9 +811,9 @@ static int CalculateAudioStreamWorkBufSize(const SDL_AudioStream *stream, int le workbuflen = inputlen; } - if (stream->dst_rate != stream->src_rate) { + if (stream->dst_spec.freq != stream->src_spec.freq) { /* calculate requested sample frames needed before resampling. Use a Uint64 so the multiplication doesn't overflow. */ - const int input_frames = ((int) ((((Uint64) workbuf_frames) * stream->src_rate) / stream->dst_rate)); + const int input_frames = ((int) ((((Uint64) workbuf_frames) * stream->src_spec.freq) / stream->dst_spec.freq)); inputlen = input_frames * stream->max_sample_frame_size; if (inputlen > workbuflen) { workbuflen = inputlen; @@ -820,13 +834,13 @@ static int CalculateAudioStreamWorkBufSize(const SDL_AudioStream *stream, int le static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int len) { const int max_available = SDL_GetAudioStreamAvailable(stream); - const SDL_AudioFormat src_format = stream->src_format; - const int src_channels = stream->src_channels; - const int src_rate = stream->src_rate; + const SDL_AudioFormat src_format = stream->src_spec.format; + const int src_channels = stream->src_spec.channels; + const int src_rate = stream->src_spec.freq; const int src_sample_frame_size = stream->src_sample_frame_size; - const SDL_AudioFormat dst_format = stream->dst_format; - const int dst_channels = stream->dst_channels; - const int dst_rate = stream->dst_rate; + const SDL_AudioFormat dst_format = stream->dst_spec.format; + const int dst_channels = stream->dst_spec.channels; + const int dst_rate = stream->dst_spec.freq; const int dst_sample_frame_size = stream->dst_sample_frame_size; const int max_sample_frame_size = stream->max_sample_frame_size; const int pre_resample_channels = stream->pre_resample_channels; @@ -1022,9 +1036,9 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) // give the callback a chance to fill in more stream data if it wants. if (stream->get_callback) { int approx_request = len / stream->dst_sample_frame_size; /* start with sample frames desired */ - if (stream->src_rate != stream->dst_rate) { + if (stream->src_spec.freq != stream->dst_spec.freq) { /* calculate difference in dataset size after resampling. Use a Uint64 so the multiplication doesn't overflow. */ - approx_request = (size_t) ((((Uint64) approx_request) * stream->src_rate) / stream->dst_rate); + approx_request = (size_t) ((((Uint64) approx_request) * stream->src_spec.freq) / stream->dst_spec.freq); if (!stream->flushed) { /* do we need to fill the future buffer to accomodate this, too? */ approx_request += stream->future_buffer_filled_frames - stream->resampler_padding_frames; } @@ -1091,13 +1105,13 @@ int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream) count += stream->future_buffer_filled_frames; /* sample frames after resampling */ - if (stream->src_rate != stream->dst_rate) { + if (stream->src_spec.freq != stream->dst_spec.freq) { if (!stream->flushed) { /* have to save some samples for padding. They aren't available until more data is added or the stream is flushed. */ count = (count < ((size_t) stream->resampler_padding_frames)) ? 0 : (count - stream->resampler_padding_frames); } /* calculate difference in dataset size after resampling. Use a Uint64 so the multiplication doesn't overflow. */ - count = (size_t) ((((Uint64) count) * stream->dst_rate) / stream->src_rate); + count = (size_t) ((((Uint64) count) * stream->dst_spec.freq) / stream->src_spec.freq); } /* convert from sample frames to bytes in destination format. */ @@ -1117,7 +1131,7 @@ int SDL_ClearAudioStream(SDL_AudioStream *stream) SDL_LockMutex(stream->lock); SDL_ClearDataQueue(stream->queue, (size_t)stream->packetlen * 2); - SDL_memset(stream->history_buffer, SDL_GetSilenceValueForFormat(stream->src_format), stream->history_buffer_frames * stream->src_channels * sizeof (float)); + SDL_memset(stream->history_buffer, SDL_GetSilenceValueForFormat(stream->src_spec.format), stream->history_buffer_frames * stream->src_spec.channels * sizeof (float)); stream->future_buffer_filled_frames = 0; stream->flushed = SDL_FALSE; SDL_UnlockMutex(stream->lock); @@ -1139,8 +1153,8 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream) } } -int SDL_ConvertAudioSamples(SDL_AudioFormat src_format, int src_channels, int src_rate, const Uint8 *src_data, int src_len, - SDL_AudioFormat dst_format, int dst_channels, int dst_rate, Uint8 **dst_data, int *dst_len) +int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, + const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len) { int ret = -1; SDL_AudioStream *stream = NULL; @@ -1165,7 +1179,7 @@ int SDL_ConvertAudioSamples(SDL_AudioFormat src_format, int src_channels, int sr return SDL_InvalidParamError("dst_len"); } - stream = SDL_CreateAudioStream(src_format, src_channels, src_rate, dst_format, dst_channels, dst_rate); + stream = SDL_CreateAudioStream(src_spec, dst_spec); if (stream != NULL) { if ((SDL_PutAudioStreamData(stream, src_data, src_len) == 0) && (SDL_FlushAudioStream(stream) == 0)) { dstlen = SDL_GetAudioStreamAvailable(stream); diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 95350d8f37..9674151d8a 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -73,7 +73,7 @@ extern void SDL_ChooseAudioConverters(void); /* Audio targets should call this as devices are added to the system (such as a USB headset being plugged in), and should also be called for for every device found during DetectDevices(). */ -extern SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, SDL_AudioFormat fmt, int channels, int freq, void *handle); +extern SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, const SDL_AudioSpec *spec, void *handle); /* Audio targets should call this if an opened audio device is lost. This can happen due to i/o errors, or a device being unplugged, etc. */ @@ -159,17 +159,12 @@ struct SDL_AudioStream int history_buffer_frames; int future_buffer_filled_frames; - int max_sample_frame_size; + SDL_AudioSpec src_spec; + SDL_AudioSpec dst_spec; int src_sample_frame_size; - SDL_AudioFormat src_format; - int src_channels; - int src_rate; - int dst_sample_frame_size; - SDL_AudioFormat dst_format; - int dst_channels; - int dst_rate; + int max_sample_frame_size; int pre_resample_channels; int packetlen; @@ -194,15 +189,11 @@ struct SDL_AudioDevice void *handle; /* The device's current audio specification */ - SDL_AudioFormat format; - int freq; - int channels; + SDL_AudioSpec spec; Uint32 buffer_size; /* The device's default audio specification */ - SDL_AudioFormat default_format; - int default_freq; - int default_channels; + SDL_AudioSpec default_spec; /* Number of sample frames the devices wants per-buffer. */ int sample_frames; diff --git a/src/audio/SDL_wave.c b/src/audio/SDL_wave.c index 2e3175333a..9a3c3ea754 100644 --- a/src/audio/SDL_wave.c +++ b/src/audio/SDL_wave.c @@ -1764,7 +1764,7 @@ static int WaveCheckFormat(WaveFile *file, size_t datalength) return 0; } -static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioFormat *fmt, int *channels, int *freq, Uint8 **audio_buf, Uint32 *audio_len) +static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) { int result; Uint32 chunkcount = 0; @@ -2026,8 +2026,9 @@ static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioFormat *fmt, int *c /* Setting up the specs. All unsupported formats were filtered out * by checks earlier in this function. */ - *freq = format->frequency; - *channels = (Uint8)format->channels; + spec->freq = format->frequency; + spec->channels = (Uint8)format->channels; + spec->format = 0; switch (format->encoding) { case MS_ADPCM_CODE: @@ -2035,22 +2036,22 @@ static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioFormat *fmt, int *c case ALAW_CODE: case MULAW_CODE: /* These can be easily stored in the byte order of the system. */ - *fmt = SDL_AUDIO_S16SYS; + spec->format = SDL_AUDIO_S16SYS; break; case IEEE_FLOAT_CODE: - *fmt = SDL_AUDIO_F32LSB; + spec->format = SDL_AUDIO_F32LSB; break; case PCM_CODE: switch (format->bitspersample) { case 8: - *fmt = SDL_AUDIO_U8; + spec->format = SDL_AUDIO_U8; break; case 16: - *fmt = SDL_AUDIO_S16LSB; + spec->format = SDL_AUDIO_S16LSB; break; case 24: /* Has been shifted to 32 bits. */ case 32: - *fmt = SDL_AUDIO_S32LSB; + spec->format = SDL_AUDIO_S32LSB; break; default: /* Just in case something unexpected happened in the checks. */ @@ -2069,7 +2070,7 @@ static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioFormat *fmt, int *c return 0; } -int SDL_LoadWAV_RW(SDL_RWops *src, int freesrc, SDL_AudioFormat *fmt, int *channels, int *freq, Uint8 **audio_buf, Uint32 *audio_len) +int SDL_LoadWAV_RW(SDL_RWops *src, int freesrc, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) { int result = -1; WaveFile file; @@ -2077,12 +2078,8 @@ int SDL_LoadWAV_RW(SDL_RWops *src, int freesrc, SDL_AudioFormat *fmt, int *chann /* Make sure we are passed a valid data source */ if (src == NULL) { return -1; /* Error may come from RWops. */ - } else if (fmt == NULL) { - return SDL_InvalidParamError("fmt"); - } else if (channels == NULL) { - return SDL_InvalidParamError("channels"); - } else if (freq == NULL) { - return SDL_InvalidParamError("freq"); + } else if (spec == NULL) { + return SDL_InvalidParamError("spec"); } else if (audio_buf == NULL) { return SDL_InvalidParamError("audio_buf"); } else if (audio_len == NULL) { @@ -2097,7 +2094,7 @@ int SDL_LoadWAV_RW(SDL_RWops *src, int freesrc, SDL_AudioFormat *fmt, int *chann file.trunchint = WaveGetTruncationHint(); file.facthint = WaveGetFactChunkHint(); - result = WaveLoad(src, &file, fmt, channels, freq, audio_buf, audio_len); + result = WaveLoad(src, &file, spec, audio_buf, audio_len); if (result < 0) { SDL_free(*audio_buf); audio_buf = NULL; diff --git a/src/audio/disk/SDL_diskaudio.c b/src/audio/disk/SDL_diskaudio.c index 2e83a111b8..6850e7b03c 100644 --- a/src/audio/disk/SDL_diskaudio.c +++ b/src/audio/disk/SDL_diskaudio.c @@ -123,7 +123,7 @@ static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device) if (envr != NULL) { device->hidden->io_delay = SDL_atoi(envr); } else { - device->hidden->io_delay = ((device->sample_frames * 1000) / device->freq); + device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq); } /* Open the "audio device" */ @@ -153,8 +153,8 @@ static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device) static void DISKAUDIO_DetectDevices(void) { - SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, 0, 0, 0, (void *)0x1); - SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, 0, 0, 0, (void *)0x2); + SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1); + SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2); } static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl) diff --git a/src/audio/dummy/SDL_dummyaudio.c b/src/audio/dummy/SDL_dummyaudio.c index ce1ecca5dc..c8d10ef8f5 100644 --- a/src/audio/dummy/SDL_dummyaudio.c +++ b/src/audio/dummy/SDL_dummyaudio.c @@ -50,7 +50,7 @@ static Uint8 *DUMMYAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) static int DUMMYAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { /* Delay to make this sort of simulate real audio input. */ - SDL_Delay((device->sample_frames * 1000) / device->freq); + SDL_Delay((device->sample_frames * 1000) / device->spec.freq); /* always return a full buffer of silence. */ SDL_memset(buffer, device->silence_value, buflen); diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 62a6d8284f..514357e378 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -599,7 +599,7 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) SDL_zerop(device->hidden); /* Try for a closest match on audio format */ - closefmts = SDL_ClosestAudioFormats(device->format); + closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { #ifdef DEBUG_AUDIO fprintf(stderr, "Trying format 0x%4.4x\n", test_format); @@ -634,7 +634,7 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) if (!test_format) { return SDL_SetError("pulseaudio: Unsupported audio format"); } - device->format = test_format; + device->spec.format = test_format; paspec.format = format; /* Calculate the final parameters for this audio specification */ @@ -650,8 +650,8 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) SDL_memset(h->mixbuf, device->silence_value, device->buffer_size); } - paspec.channels = device->channels; - paspec.rate = device->freq; + paspec.channels = device->spec.channels; + paspec.rate = device->spec.freq; /* Reduced prebuffering compared to the defaults. */ paattr.fragsize = device->buffer_size; @@ -669,7 +669,7 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) const char *name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME); /* The SDL ALSA output hints us that we use Windows' channel mapping */ /* https://bugzilla.libsdl.org/show_bug.cgi?id=110 */ - PULSEAUDIO_pa_channel_map_init_auto(&pacmap, device->channels, PA_CHANNEL_MAP_WAVEEX); + PULSEAUDIO_pa_channel_map_init_auto(&pacmap, device->spec.channels, PA_CHANNEL_MAP_WAVEEX); h->stream = PULSEAUDIO_pa_stream_new( pulseaudio_context, @@ -750,12 +750,13 @@ static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, { if (i) { const SDL_bool add = (SDL_bool)((intptr_t)data); - const SDL_AudioFormat fmt = PulseFormatToSDLFormat(i->sample_spec.format); - const int channels = i->sample_spec.channels; - const int freq = i->sample_spec.rate; + SDL_AudioSpec spec; + spec.format = PulseFormatToSDLFormat(i->sample_spec.format); + spec.channels = i->sample_spec.channels; + spec.freq = i->sample_spec.rate; if (add) { - SDL_AddAudioDevice(SDL_FALSE, i->description, fmt, channels, freq, (void *)((intptr_t)i->index + 1)); + SDL_AddAudioDevice(SDL_FALSE, i->description, &spec, (void *)((intptr_t)i->index + 1)); } if (default_sink_path != NULL && SDL_strcmp(i->name, default_sink_path) == 0) { @@ -772,12 +773,13 @@ static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_la /* Maybe skip "monitor" sources. These are just output from other sinks. */ if (i && (include_monitors || (i->monitor_of_sink == PA_INVALID_INDEX))) { const SDL_bool add = (SDL_bool)((intptr_t)data); - const SDL_AudioFormat fmt = PulseFormatToSDLFormat(i->sample_spec.format); - const int channels = i->sample_spec.channels; - const int freq = i->sample_spec.rate; + SDL_AudioSpec spec; + spec.format = PulseFormatToSDLFormat(i->sample_spec.format); + spec.channels = i->sample_spec.channels; + spec.freq = i->sample_spec.rate; if (add) { - SDL_AddAudioDevice(SDL_TRUE, i->description, fmt, channels, freq, (void *)((intptr_t)i->index + 1)); + SDL_AddAudioDevice(SDL_TRUE, i->description, &spec, (void *)((intptr_t)i->index + 1)); } if (default_source_path != NULL && SDL_strcmp(i->name, default_source_path) == 0) { diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 359d4552a9..034a630e02 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -929,28 +929,28 @@ SDL_DYNAPI_PROC(const char*,SDL_GetCurrentAudioDriver,(void),(),return) SDL_DYNAPI_PROC(SDL_AudioDeviceID*,SDL_GetAudioOutputDevices,(int *a),(a),return) SDL_DYNAPI_PROC(SDL_AudioDeviceID*,SDL_GetAudioCaptureDevices,(int *a),(a),return) SDL_DYNAPI_PROC(char*,SDL_GetAudioDeviceName,(SDL_AudioDeviceID a),(a),return) -SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceFormat,(SDL_AudioDeviceID a, SDL_AudioFormat *b, int *c, int *d),(a,b,c,d),return) -SDL_DYNAPI_PROC(SDL_AudioDeviceID,SDL_OpenAudioDevice,(SDL_AudioDeviceID a, SDL_AudioFormat b, int c, int d),(a,b,c,d),return) +SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceFormat,(SDL_AudioDeviceID a, SDL_AudioSpec *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_AudioDeviceID,SDL_OpenAudioDevice,(SDL_AudioDeviceID a, const SDL_AudioSpec *b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_CloseAudioDevice,(SDL_AudioDeviceID a),(a),) SDL_DYNAPI_PROC(int,SDL_BindAudioStreams,(SDL_AudioDeviceID a, SDL_AudioStream **b, int c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_BindAudioStream,(SDL_AudioDeviceID a, SDL_AudioStream *b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_UnbindAudioStreams,(SDL_AudioStream **a, int b),(a,b),) SDL_DYNAPI_PROC(void,SDL_UnbindAudioStream,(SDL_AudioStream *a),(a),) -SDL_DYNAPI_PROC(SDL_AudioStream*,SDL_CreateAudioStream,(SDL_AudioFormat a, int b, int c, SDL_AudioFormat d, int e, int f),(a,b,c,d,e,f),return) -SDL_DYNAPI_PROC(int,SDL_GetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat *b, int *c, int *d, SDL_AudioFormat *e, int *f, int *g),(a,b,c,d,e,f,g),return) -SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat b, int c, int d, SDL_AudioFormat e, int f, int g),(a,b,c,d,e,f,g),return) +SDL_DYNAPI_PROC(SDL_AudioStream*,SDL_CreateAudioStream,(const SDL_AudioSpec *a, const SDL_AudioSpec *b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_GetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioSpec *b, SDL_AudioSpec *c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, const SDL_AudioSpec *b, const SDL_AudioSpec *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_PutAudioStreamData,(SDL_AudioStream *a, const void *b, int c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_GetAudioStreamData,(SDL_AudioStream *a, void *b, int c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_GetAudioStreamAvailable,(SDL_AudioStream *a),(a),return) SDL_DYNAPI_PROC(int,SDL_FlushAudioStream,(SDL_AudioStream *a),(a),return) SDL_DYNAPI_PROC(int,SDL_ClearAudioStream,(SDL_AudioStream *a),(a),return) -SDL_DYNAPI_PROC(void,SDL_DestroyAudioStream,(SDL_AudioStream *a),(a),) -SDL_DYNAPI_PROC(SDL_AudioStream*,SDL_CreateAndBindAudioStream,(SDL_AudioDeviceID a, SDL_AudioFormat b, int c, int d),(a,b,c,d),return) -SDL_DYNAPI_PROC(int,SDL_LoadWAV_RW,(SDL_RWops *a, int b, SDL_AudioFormat *c, int *d, int *e, Uint8 **f, Uint32 *g),(a,b,c,d,e,f,g),return) -SDL_DYNAPI_PROC(int,SDL_MixAudioFormat,(Uint8 *a, const Uint8 *b, SDL_AudioFormat c, Uint32 d, int e),(a,b,c,d,e),return) -SDL_DYNAPI_PROC(int,SDL_ConvertAudioSamples,(SDL_AudioFormat a, int b, int c, const Uint8 *d, int e, SDL_AudioFormat f, int g, int h, Uint8 **i, int *j),(a,b,c,d,e,f,g,h,i,j),return) -SDL_DYNAPI_PROC(int,SDL_GetSilenceValueForFormat,(SDL_AudioFormat a),(a),return) SDL_DYNAPI_PROC(int,SDL_LockAudioStream,(SDL_AudioStream *a),(a),return) SDL_DYNAPI_PROC(int,SDL_UnlockAudioStream,(SDL_AudioStream *a),(a),return) SDL_DYNAPI_PROC(int,SDL_SetAudioStreamGetCallback,(SDL_AudioStream *a, SDL_AudioStreamRequestCallback b, void *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SetAudioStreamPutCallback,(SDL_AudioStream *a, SDL_AudioStreamRequestCallback b, void *c),(a,b,c),return) +SDL_DYNAPI_PROC(void,SDL_DestroyAudioStream,(SDL_AudioStream *a),(a),) +SDL_DYNAPI_PROC(SDL_AudioStream*,SDL_CreateAndBindAudioStream,(SDL_AudioDeviceID a, const SDL_AudioSpec *b),(a,b),return) +SDL_DYNAPI_PROC(int,SDL_LoadWAV_RW,(SDL_RWops *a, int b, SDL_AudioSpec *c, Uint8 **d, Uint32 *e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(int,SDL_MixAudioFormat,(Uint8 *a, const Uint8 *b, SDL_AudioFormat c, Uint32 d, int e),(a,b,c,d,e),return) +SDL_DYNAPI_PROC(int,SDL_ConvertAudioSamples,(const SDL_AudioSpec *a, const Uint8 *b, int c, const SDL_AudioSpec *d, Uint8 **e, int *f),(a,b,c,d,e,f),return) +SDL_DYNAPI_PROC(int,SDL_GetSilenceValueForFormat,(SDL_AudioFormat a),(a),return) diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index 25f7400b94..ec0b988272 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -1443,7 +1443,8 @@ SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state) SDL_GetCurrentAudioDriver()); } - state->audio_id = SDL_OpenAudioDevice(0, state->audio_format, state->audio_channels, state->audio_freq); + const SDL_AudioSpec spec = { state->audio_format, state->audio_channels, state->audio_freq }; + state->audio_id = SDL_OpenAudioDevice(0, &spec); if (!state->audio_id) { SDL_Log("Couldn't open audio: %s\n", SDL_GetError()); return SDL_FALSE; diff --git a/test/loopwave.c b/test/loopwave.c index 127b3f4ce8..15a64bf341 100644 --- a/test/loopwave.c +++ b/test/loopwave.c @@ -28,9 +28,7 @@ static struct { - SDL_AudioFormat fmt; - int channels; - int freq; + SDL_AudioSpec spec; Uint8 *sound; /* Pointer to wave data */ Uint32 soundlen; /* Length of wave data */ Uint32 soundpos; @@ -89,14 +87,14 @@ static void open_audio(void) { SDL_AudioDeviceID *devices = SDL_GetAudioOutputDevices(NULL); - device = devices ? SDL_OpenAudioDevice(devices[0], wave.fmt, wave.channels, wave.freq) : 0; + device = devices ? SDL_OpenAudioDevice(devices[0], &wave.spec) : 0; SDL_free(devices); if (!device) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s\n", SDL_GetError()); SDL_free(wave.sound); quit(2); } - stream = SDL_CreateAndBindAudioStream(device, wave.fmt, wave.channels, wave.freq); + stream = SDL_CreateAndBindAudioStream(device, &wave.spec); if (!stream) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create audio stream: %s\n", SDL_GetError()); SDL_CloseAudioDevice(device); @@ -180,7 +178,7 @@ int main(int argc, char *argv[]) } /* Load the wave file into memory */ - if (SDL_LoadWAV(filename, &wave.fmt, &wave.channels, &wave.freq, &wave.sound, &wave.soundlen) == -1) { + if (SDL_LoadWAV(filename, &wave.spec, &wave.sound, &wave.soundlen) == -1) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError()); quit(1); } From 3d65a2cefe7b8f87bad1b7585ef91995e5df2ae5 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 30 May 2023 01:08:56 -0400 Subject: [PATCH 005/138] audio: Made SDL_LoadWAV a real function, not just a macro. --- include/SDL3/SDL_audio.h | 43 ++++++++++++++++++++++++++----- src/audio/SDL_wave.c | 6 +++++ src/dynapi/SDL_dynapi.sym | 13 +++++----- src/dynapi/SDL_dynapi_overrides.h | 13 +++++----- 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 3e7468cf3e..7f1fd42cd8 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -946,8 +946,8 @@ extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAndBindAudioStream(SDL_AudioD * SDL_LoadWAV_RW(SDL_RWFromFile("sample.wav", "rb"), 1, &spec, &buf, &len); * ``` * - * Note that the SDL_LoadWAV macro does this same thing for you, but in a less - * messy way: + * Note that the SDL_LoadWAV function does this same thing for you, but in a + * less messy way: * * ```c * SDL_LoadWAV("sample.wav", &spec, &buf, &len); @@ -983,11 +983,42 @@ extern DECLSPEC int SDLCALL SDL_LoadWAV_RW(SDL_RWops * src, int freesrc, Uint32 * audio_len); /** - * Loads a WAV from a file. - * Compatibility convenience function. + * Loads a WAV from a file path. + * + * This is a convenience function that is effectively the same as: + * + * ```c + * SDL_LoadWAV_RW(SDL_RWFromFile(path, "rb"), 1, spec, audio_buf, audio_len); + * ``` + * + * Note that in SDL2, this was a preprocessor macro and not a real function. + * + * \param path The file path of the WAV file to open. + * \param spec A pointer to an SDL_AudioSpec that will be set to the + * WAVE data's format details on successful return. + * \param audio_buf A pointer filled with the audio data, allocated by the + * function. + * \param audio_len A pointer filled with the length of the audio data buffer + * in bytes + * \returns This function, if successfully called, returns 0. `audio_buf` + * will be filled with a pointer to an allocated buffer + * containing the audio data, and `audio_len` is filled with the + * length of that audio buffer in bytes. + * + * This function returns -1 if the .WAV file cannot be opened, uses + * an unknown data format, or is corrupt; call SDL_GetError() for + * more information. + * + * When the application is done with the data returned in + * `audio_buf`, it should call SDL_free() to dispose of it. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_free + * \sa SDL_LoadWAV_RW */ -#define SDL_LoadWAV(file, fmt, channels, freq, audio_buf, audio_len) \ - SDL_LoadWAV_RW(SDL_RWFromFile(file, "rb"), 1, fmt, channels, freq, audio_buf, audio_len) +extern DECLSPEC int SDLCALL SDL_LoadWAV(const char *path, SDL_AudioSpec * spec, + Uint8 ** audio_buf, Uint32 * audio_len); diff --git a/src/audio/SDL_wave.c b/src/audio/SDL_wave.c index 9a3c3ea754..e7e5fc0327 100644 --- a/src/audio/SDL_wave.c +++ b/src/audio/SDL_wave.c @@ -2110,3 +2110,9 @@ int SDL_LoadWAV_RW(SDL_RWops *src, int freesrc, SDL_AudioSpec *spec, Uint8 **aud return result; } + +int SDL_LoadWAV(const char *path, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len) +{ + return SDL_LoadWAV_RW(SDL_RWFromFile(path, "rb"), 1, spec, audio_buf, audio_len); +} + diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 4cd53c2d68..e4da9c8d37 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -873,16 +873,17 @@ SDL3_0.0.0 { SDL_GetAudioStreamAvailable; SDL_FlushAudioStream; SDL_ClearAudioStream; - SDL_DestroyAudioStream; - SDL_CreateAndBindAudioStream; - SDL_LoadWAV_RW; - SDL_MixAudioFormat; - SDL_ConvertAudioSamples; - SDL_GetSilenceValueForFormat; SDL_LockAudioStream; SDL_UnlockAudioStream; SDL_SetAudioStreamGetCallback; SDL_SetAudioStreamPutCallback; + SDL_DestroyAudioStream; + SDL_CreateAndBindAudioStream; + SDL_LoadWAV_RW; + SDL_LoadWAV; + SDL_MixAudioFormat; + SDL_ConvertAudioSamples; + SDL_GetSilenceValueForFormat; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 6d623744b5..029c9b0f28 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -899,13 +899,14 @@ #define SDL_GetAudioStreamAvailable SDL_GetAudioStreamAvailable_REAL #define SDL_FlushAudioStream SDL_FlushAudioStream_REAL #define SDL_ClearAudioStream SDL_ClearAudioStream_REAL -#define SDL_DestroyAudioStream SDL_DestroyAudioStream_REAL -#define SDL_CreateAndBindAudioStream SDL_CreateAndBindAudioStream_REAL -#define SDL_LoadWAV_RW SDL_LoadWAV_RW_REAL -#define SDL_MixAudioFormat SDL_MixAudioFormat_REAL -#define SDL_ConvertAudioSamples SDL_ConvertAudioSamples_REAL -#define SDL_GetSilenceValueForFormat SDL_GetSilenceValueForFormat_REAL #define SDL_LockAudioStream SDL_LockAudioStream_REAL #define SDL_UnlockAudioStream SDL_UnlockAudioStream_REAL #define SDL_SetAudioStreamGetCallback SDL_SetAudioStreamGetCallback_REAL #define SDL_SetAudioStreamPutCallback SDL_SetAudioStreamPutCallback_REAL +#define SDL_DestroyAudioStream SDL_DestroyAudioStream_REAL +#define SDL_CreateAndBindAudioStream SDL_CreateAndBindAudioStream_REAL +#define SDL_LoadWAV_RW SDL_LoadWAV_RW_REAL +#define SDL_LoadWAV SDL_LoadWAV_REAL +#define SDL_MixAudioFormat SDL_MixAudioFormat_REAL +#define SDL_ConvertAudioSamples SDL_ConvertAudioSamples_REAL +#define SDL_GetSilenceValueForFormat SDL_GetSilenceValueForFormat_REAL From 7ee24599276861edf45db4a4779c61cd0341f690 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 30 May 2023 01:09:21 -0400 Subject: [PATCH 006/138] audio: Check for unlikely failure case in WAV loaded. I don't think this can fail at the moment, but if WaveCheckFormat goes out of sync with this switch statement at some point, this seems like a good failsafe. --- src/audio/SDL_wave.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/audio/SDL_wave.c b/src/audio/SDL_wave.c index e7e5fc0327..759d34d9e1 100644 --- a/src/audio/SDL_wave.c +++ b/src/audio/SDL_wave.c @@ -2058,6 +2058,8 @@ static int WaveLoad(SDL_RWops *src, WaveFile *file, SDL_AudioSpec *spec, Uint8 * return SDL_SetError("Unexpected %u-bit PCM data format", (unsigned int)format->bitspersample); } break; + default: + return SDL_SetError("Unexpected data format"); } /* Report the end position back to the cleanup code. */ From b2e020958f1b2355167ba4add199092b03138727 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 20 Jun 2023 23:35:24 -0400 Subject: [PATCH 007/138] audio: Wrap device access in opening of logical devices. Now you open an audio device and attach streams, as planned, but each open generates a new logical device. Each logical device has its own streams that are managed as a group, but all streams on all logical devices are mixed into a single buffer for a single OS-level open of the physical device. This allows multiple opens of a device that won't interfere with each other and also clean up just what the opener assigned to their logical device, so all their streams will go away on close but other opens will continue to mix as they were. More or less, this makes things work as expected at the app level, but also gives them the power to group audio streams, and (once added) pause them all at once, etc. --- include/SDL3/SDL_audio.h | 66 +++-- src/audio/SDL_audio.c | 374 +++++++++++++++++--------- src/audio/SDL_sysaudio.h | 44 ++- src/audio/pulseaudio/SDL_pulseaudio.c | 2 +- src/dynapi/SDL_dynapi.sym | 1 + src/dynapi/SDL_dynapi_overrides.h | 1 + src/dynapi/SDL_dynapi_procs.h | 1 + 7 files changed, 330 insertions(+), 159 deletions(-) diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 7f1fd42cd8..5490be5c27 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -141,6 +141,14 @@ typedef Uint16 SDL_AudioFormat; /* @} *//* Audio flags */ +/** + * SDL Audio Device instance IDs. + */ +typedef Uint32 SDL_AudioDeviceID; + +#define SDL_AUDIO_DEVICE_DEFAULT_OUTPUT ((SDL_AudioDeviceID) 0xFFFFFFFF) +#define SDL_AUDIO_DEVICE_DEFAULT_CAPTURE ((SDL_AudioDeviceID) 0xFFFFFFFE) + typedef struct SDL_AudioSpec { SDL_AudioFormat format; /**< Audio data format */ @@ -237,11 +245,6 @@ extern DECLSPEC const char *SDLCALL SDL_GetAudioDriver(int index); */ extern DECLSPEC const char *SDLCALL SDL_GetCurrentAudioDriver(void); -/** - * SDL Audio Device instance IDs. - */ -typedef Uint32 SDL_AudioDeviceID; - /** * Get a list of currently-connected audio output devices. @@ -326,8 +329,6 @@ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SD /** * Open a specific audio device. * - * Passing in a `devid` name of zero requests the most reasonable default. - * * You can open both output and capture devices through this function. * Output devices will take data from bound audio streams, mix it, and * send it to the hardware. Capture devices will feed any bound audio @@ -336,7 +337,22 @@ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SD * An opened audio device starts out with no audio streams bound. To * start audio playing, bind a stream and supply audio data to it. Unlike * SDL2, there is no audio callback; you only bind audio streams and - * make sure they have data flowing into them. + * make sure they have data flowing into them (although, as an optional + * feature, each audio stream may have its own callback, which can be + * used to simulate SDL2's semantics). + * + * If you don't care about opening a specific device, pass a `devid` + * of either `SDL_AUDIO_DEVICE_DEFAULT_OUTPUT` or + * `SDL_AUDIO_DEVICE_DEFAULT_CAPTURE`. In this case, SDL will try to + * pick the most reasonable default, and may also switch between + * physical devices seamlessly later, if the most reasonable default + * changes during the lifetime of this opened device (user changed + * the default in the OS's system preferences, the default got + * unplugged so the system jumped to a new default, the user plugged + * in headphones on a mobile device, etc). Unless you have a good + * reason to choose a specific device, this is probably what you want. + * Requesting the default will also allow the user to specify + * preferences with hints/environment variables. * * You may request a specific format for the audio device, but there is * no promise the device will honor that request for several reasons. As @@ -346,28 +362,30 @@ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SD * tell you the preferred format for the device before opening and the * actual format the device is using after opening. * - * It's legal to open the same device ID more than once; in the end, you must - * close it the same number of times. This allows libraries to open a device - * separate from the main app and bind its own streams without conflicting. + * It's legal to open the same device ID more than once; each successful + * open will generate a new logical SDL_AudioDeviceID that is managed + * separately from others on the same physical device. This allows + * libraries to open a device separately from the main app and bind its own + * streams without conflicting. * - * This function returns the opened device ID on success, so that if you - * open a device of 0, you'll have a real ID to bind streams to, but this - * does not generate new instance IDs. Unlike SDL2, these IDs are assigned - * to each unique device on the system, open or not, so if you request a - * specific device, you'll get that same device ID back. + * This function returns the opened device ID on success. This is a new, + * unique SDL_AudioDeviceID that represents a logical device. * * Some backends might offer arbitrary devices (for example, a networked * audio protocol that can connect to an arbitrary server). For these, as - * a change from SDL2, you should open a device ID of zero and use an SDL + * a change from SDL2, you should open a default device ID and use an SDL * hint to specify the target if you care, or otherwise let the backend * figure out a reasonable default. Most backends don't offer anything like * this, and often this would be an end user setting an environment * variable for their custom need, and not something an application should * specifically manage. * - * \param devid the device instance id to open. 0 requests the most - * reasonable default device. - * \param spec the requested device configuration + * When done with an audio device, possibly at the end of the app's life, + * one should call SDL_CloseAudioDevice() on the returned device id. + * + * \param devid the device instance id to open, or SDL_AUDIO_DEVICE_DEFAULT_OUTPUT or + * SDL_AUDIO_DEVICE_DEFAULT_CAPTURE for the most reasonable default device. + * \param spec the requested device configuration. Can be NULL to use reasonable defaults. * \returns The device ID on success, 0 on error; call SDL_GetError() for more information. * * \since This function is available since SDL 3.0.0. @@ -384,15 +402,13 @@ extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(SDL_AudioDeviceID * Close a previously-opened audio device. * * The application should close open audio devices once they are no longer - * needed. Audio devices can be opened multiple times; when they are closed - * an equal number of times, its resources are freed, any bound streams are - * unbound, and any audio will stop playing. + * needed. * * This function may block briefly while pending audio data is played by the * hardware, so that applications don't drop the last buffer of data they - * supplied. + * supplied if terminating immediately afterwards. * - * \param devid an audio device previously opened with SDL_OpenAudioDevice() + * \param devid an audio device id previously returned by SDL_OpenAudioDevice() * * \since This function is available since SDL 3.0.0. * diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 060980ee77..96fe7fa7de 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -121,37 +121,55 @@ const char *SDL_GetCurrentAudioDriver(void) /* device management and hotplug... */ -/* SDL_AudioDevice, in SDL3, represents a piece of hardware, whether it is in use or not, so these objects exist as long as +/* SDL_AudioDevice, in SDL3, represents a piece of physical hardware, whether it is in use or not, so these objects exist as long as the system-level device is available. - Devices get destroyed for three reasons: + Physical devices get destroyed for three reasons: - They were lost to the system (a USB cable is kicked out, etc). - They failed for some other unlikely reason at the API level (which is _also_ probably a USB cable being kicked out). - We are shutting down, so all allocated resources are being freed. They are _not_ destroyed because we are done using them (when we "close" a playing device). */ -static void CloseAudioDevice(SDL_AudioDevice *device); +static void ClosePhysicalAudioDevice(SDL_AudioDevice *device); -/* this must not be called while `device` is still in a device list, or while a device's audio thread is still running (except if the thread calls this while shutting down). */ -static void DestroyAudioDevice(SDL_AudioDevice *device) +// the loop in assign_audio_device_instance_id relies on this being true. +SDL_COMPILE_TIME_ASSERT(check_lowest_audio_default_value, SDL_AUDIO_DEVICE_DEFAULT_CAPTURE < SDL_AUDIO_DEVICE_DEFAULT_OUTPUT); + +static SDL_AudioDeviceID assign_audio_device_instance_id(SDL_bool iscapture, SDL_bool islogical) { - SDL_AudioStream *stream; + /* Assign an instance id! Start at 2, in case there are things from the SDL2 era that still think 1 is a special value. + There's no reasonable scenario where this rolls over, but just in case, we wrap it in a loop. + Also, make sure we don't assign SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, etc. */ + + // The bottom two bits of the instance id tells you if it's an output device (1<<0), and if it's a physical device (1<<1). Make sure these are right. + const SDL_AudioDeviceID required_mask = (iscapture ? 0 : (1<<0)) | (islogical ? 0 : (1<<1)); + + SDL_AudioDeviceID instance_id; + do { + instance_id = (SDL_AudioDeviceID) (SDL_AtomicIncRef(¤t_audio.last_device_instance_id) + 1); + } while ( (instance_id < 2) || (instance_id >= SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) || ((instance_id & 0x3) != required_mask) ); + return instance_id; +} + +// this assumes you hold the _physical_ device lock for this logical device! This will not unlock the lock or close the physical device! +static void DestroyLogicalAudioDevice(SDL_LogicalAudioDevice *logdev) +{ + // remove ourselves from the physical device's list of logical devices. + if (logdev->next) { + logdev->next->prev = logdev->prev; + } + if (logdev->prev) { + logdev->prev->next = logdev->next; + } + if (logdev->physical_device->logical_devices == logdev) { + logdev->physical_device->logical_devices = logdev->next; + } + + // unbind any still-bound streams... SDL_AudioStream *next; - - if (!device) { - return; - } - - if (SDL_AtomicGet(&device->refcount) > 0) { - SDL_AtomicSet(&device->refcount, 0); /* it's going down NOW. */ - CloseAudioDevice(device); - } - - /* unbind any still-bound streams... */ - SDL_LockMutex(device->lock); - for (stream = device->bound_streams; stream != NULL; stream = next) { + for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = next) { SDL_LockMutex(stream->lock); next = stream->next_binding; stream->next_binding = NULL; @@ -159,8 +177,27 @@ static void DestroyAudioDevice(SDL_AudioDevice *device) stream->bound_device = NULL; SDL_UnlockMutex(stream->lock); } + + SDL_free(logdev); +} + +// this must not be called while `device` is still in a device list, or while a device's audio thread is still running (except if the thread calls this while shutting down). */ +static void DestroyPhysicalAudioDevice(SDL_AudioDevice *device) +{ + if (!device) { + return; + } + + // Destroy any logical devices that still exist... + SDL_LockMutex(device->lock); + while (device->logical_devices != NULL) { + DestroyLogicalAudioDevice(device->logical_devices); + } SDL_UnlockMutex(device->lock); + // it's safe to not hold the lock for this (we can't anyhow, or the audio thread won't quit), because we shouldn't be in the device list at this point. + ClosePhysicalAudioDevice(device); + current_audio.impl.FreeDeviceHandle(device->handle); SDL_DestroyMutex(device->lock); @@ -169,7 +206,7 @@ static void DestroyAudioDevice(SDL_AudioDevice *device) SDL_free(device); } -static SDL_AudioDevice *CreateAudioDevice(const char *name, SDL_bool iscapture, const SDL_AudioSpec *spec, void *handle, SDL_AudioDevice **devices, SDL_AtomicInt *device_count) +static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, SDL_bool iscapture, const SDL_AudioSpec *spec, void *handle, SDL_AudioDevice **devices, SDL_AtomicInt *device_count) { SDL_AudioDevice *device; @@ -208,12 +245,7 @@ static SDL_AudioDevice *CreateAudioDevice(const char *name, SDL_bool iscapture, device->handle = handle; device->prev = NULL; - /* Assign an instance id! Start at 2, in case there are things from the SDL2 era that still think 1 is a special value. */ - /* There's no reasonable scenario where this rolls over, but just in case, we wrap it in a loop. */ - /* Also, make sure capture instance IDs are even, and output IDs are odd, so we know what kind of device it is from just the ID. */ - do { - device->instance_id = (SDL_AudioDeviceID) (SDL_AtomicIncRef(¤t_audio.last_device_instance_id) + 1); - } while ( ((iscapture && (device->instance_id & 1)) || (!iscapture && ((device->instance_id & 1) == 0))) || (device->instance_id < 2) ); + device->instance_id = assign_audio_device_instance_id(iscapture, /*islogical=*/SDL_FALSE); SDL_LockRWLockForWriting(current_audio.device_list_lock); device->next = *devices; @@ -227,12 +259,12 @@ static SDL_AudioDevice *CreateAudioDevice(const char *name, SDL_bool iscapture, static SDL_AudioDevice *CreateAudioCaptureDevice(const char *name, const SDL_AudioSpec *spec, void *handle) { SDL_assert(current_audio.impl.HasCaptureSupport); - return CreateAudioDevice(name, SDL_TRUE, spec, handle, ¤t_audio.capture_devices, ¤t_audio.capture_device_count); + return CreatePhysicalAudioDevice(name, SDL_TRUE, spec, handle, ¤t_audio.capture_devices, ¤t_audio.capture_device_count); } static SDL_AudioDevice *CreateAudioOutputDevice(const char *name, const SDL_AudioSpec *spec, void *handle) { - return CreateAudioDevice(name, SDL_FALSE, spec, handle, ¤t_audio.output_devices, ¤t_audio.output_device_count); + return CreatePhysicalAudioDevice(name, SDL_FALSE, spec, handle, ¤t_audio.output_devices, ¤t_audio.output_device_count); } /* The audio backends call this when a new device is plugged in. */ @@ -317,7 +349,7 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) } if (should_destroy) { - DestroyAudioDevice(device); + DestroyPhysicalAudioDevice(device); } } @@ -512,7 +544,6 @@ int SDL_InitAudio(const char *driver_name) void SDL_QuitAudio(void) { SDL_AudioDevice *devices = NULL; - SDL_AudioDevice *i; if (!current_audio.name) { /* not initialized?! */ return; @@ -521,7 +552,7 @@ void SDL_QuitAudio(void) /* merge device lists so we don't have to duplicate work below. */ SDL_LockRWLockForWriting(current_audio.device_list_lock); SDL_AtomicSet(¤t_audio.shutting_down, 1); - for (i = current_audio.output_devices; i != NULL; i = i->next) { + for (SDL_AudioDevice *i = current_audio.output_devices; i != NULL; i = i->next) { devices = i; } if (!devices) { @@ -536,12 +567,12 @@ void SDL_QuitAudio(void) SDL_UnlockRWLock(current_audio.device_list_lock); /* mark all devices for shutdown so all threads can begin to terminate. */ - for (i = devices; i != NULL; i = i->next) { + for (SDL_AudioDevice *i = devices; i != NULL; i = i->next) { SDL_AtomicSet(&i->shutdown, 1); } /* now wait on any audio threads. */ - for (i = devices; i != NULL; i = i->next) { + for (SDL_AudioDevice *i = devices; i != NULL; i = i->next) { if (i->thread) { SDL_assert(!SDL_AtomicGet(&i->condemned)); /* these shouldn't have been in the device list still, and thread should have detached. */ SDL_WaitThread(i->thread, NULL); @@ -550,9 +581,9 @@ void SDL_QuitAudio(void) } while (devices) { - i = devices->next; - DestroyAudioDevice(devices); - devices = i; + SDL_AudioDevice *next = devices->next; + DestroyPhysicalAudioDevice(devices); + devices = next; } /* Free the driver data */ @@ -586,7 +617,6 @@ void SDL_OutputAudioThreadSetup(SDL_AudioDevice *device) SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) { SDL_bool retval = SDL_TRUE; - SDL_AudioStream *stream; int buffer_size = device->buffer_size; Uint8 *mix_buffer; @@ -605,22 +635,26 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) } else { SDL_assert(buffer_size <= device->buffer_size); /* you can ask for less, but not more. */ SDL_memset(mix_buffer, device->silence_value, buffer_size); /* start with silence. */ - for (stream = device->bound_streams; stream != NULL; stream = stream->next_binding) { - /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. */ - /* (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind the same stream to different devices at the same time, though.) */ - const int br = SDL_GetAudioStreamData(stream, device->work_buffer, buffer_size); - if (br < 0) { - /* oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. */ - retval = SDL_FALSE; - break; - } else if (br > 0) { /* it's okay if we get less than requested, we mix what we have. */ - if (SDL_MixAudioFormat(mix_buffer, device->work_buffer, device->spec.format, br, SDL_MIX_MAXVOLUME) < 0) { /* !!! FIXME: allow streams to specify gain? */ - SDL_assert(!"We probably ended up with some totally unexpected audio format here"); - retval = SDL_FALSE; /* uh...? */ + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { + for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { + /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. */ + /* (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind the same stream to different devices at the same time, though.) */ + const int br = SDL_GetAudioStreamData(stream, device->work_buffer, buffer_size); + if (br < 0) { + /* oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. */ + retval = SDL_FALSE; break; + } else if (br > 0) { /* it's okay if we get less than requested, we mix what we have. */ + // !!! FIXME: this needs to mix to float32 or int32, so we don't clip. + if (SDL_MixAudioFormat(mix_buffer, device->work_buffer, device->spec.format, br, SDL_MIX_MAXVOLUME) < 0) { /* !!! FIXME: allow streams to specify gain? */ + SDL_assert(!"We probably ended up with some totally unexpected audio format here"); + retval = SDL_FALSE; /* uh...? */ + break; + } } } } + /* !!! FIXME: have PlayDevice return a value and do disconnects in here with it. */ current_audio.impl.PlayDevice(device, buffer_size); /* this SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice! */ } @@ -644,7 +678,7 @@ void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device) if (SDL_AtomicGet(&device->condemned)) { SDL_DetachThread(device->thread); /* no one is waiting for us, just detach ourselves. */ device->thread = NULL; - DestroyAudioDevice(device); + DestroyPhysicalAudioDevice(device); } } @@ -692,21 +726,22 @@ SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device) if (SDL_AtomicGet(&device->shutdown)) { retval = SDL_FALSE; /* we're done, shut it down. */ - } else if (device->bound_streams == NULL) { + } else if (device->logical_devices == NULL) { current_audio.impl.FlushCapture(device); /* nothing wants data, dump anything pending. */ } else { const int rc = current_audio.impl.CaptureFromDevice(device, device->work_buffer, device->buffer_size); if (rc < 0) { /* uhoh, device failed for some reason! */ retval = SDL_FALSE; } else if (rc > 0) { /* queue the new data to each bound stream. */ - SDL_AudioStream *stream; - for (stream = device->bound_streams; stream != NULL; stream = stream->next_binding) { - /* this will hold a lock on `stream` while putting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. */ - /* (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind the same stream to different devices at the same time, though.) */ - if (SDL_PutAudioStreamData(stream, device->work_buffer, rc) < 0) { - /* oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. */ - retval = SDL_FALSE; - break; + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { + for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { + /* this will hold a lock on `stream` while putting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. */ + /* (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind the same stream to different devices at the same time, though.) */ + if (SDL_PutAudioStreamData(stream, device->work_buffer, rc) < 0) { + /* oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. */ + retval = SDL_FALSE; + break; + } } } } @@ -727,7 +762,7 @@ void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device) current_audio.impl.FlushCapture(device); current_audio.impl.ThreadDeinit(device); if (SDL_AtomicGet(&device->condemned)) { - DestroyAudioDevice(device); + DestroyPhysicalAudioDevice(device); } } @@ -791,19 +826,69 @@ SDL_AudioDeviceID *SDL_GetAudioCaptureDevices(int *count) return GetAudioDevices(count, ¤t_audio.capture_devices, ¤t_audio.capture_device_count); } - -/* this finds the device object associated with `devid` and locks it for use. */ -static SDL_AudioDevice *ObtainAudioDevice(SDL_AudioDeviceID devid) +// If found, this locks _the physical device_ this logical device is associated with, before returning. +static SDL_LogicalAudioDevice *ObtainLogicalAudioDevice(SDL_AudioDeviceID devid) { - /* capture instance ids are even and output devices are odd, so we know which list to iterate from the devid. */ - const SDL_bool iscapture = (devid & 1) ? SDL_FALSE : SDL_TRUE; - SDL_AudioDevice *dev = NULL; + if (!SDL_GetCurrentAudioDriver()) { + SDL_SetError("Audio subsystem is not initialized"); + return NULL; + } + + SDL_LogicalAudioDevice *logdev = NULL; + + const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE; + if (islogical) { // don't bother looking if it's not a logical device id value. + const SDL_bool iscapture = (devid & (1<<0)) ? SDL_FALSE : SDL_TRUE; + + SDL_LockRWLockForReading(current_audio.device_list_lock); + + for (SDL_AudioDevice *device = iscapture ? current_audio.capture_devices : current_audio.output_devices; device != NULL; device = device->next) { + SDL_LockMutex(device->lock); // caller must unlock if we choose a logical device from this guy. + SDL_assert(!SDL_AtomicGet(&device->condemned)); // shouldn't be in the list if pending deletion. + for (logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { + if (logdev->instance_id == devid) { + break; // found it! + } + } + if (logdev != NULL) { + break; + } + SDL_UnlockMutex(device->lock); // give up this lock and try the next physical device. + } + + SDL_UnlockRWLock(current_audio.device_list_lock); + } + + if (!logdev) { + SDL_SetError("Invalid audio device instance ID"); + } + + return logdev; +} + +/* this finds the physical device associated with `devid` and locks it for use. + Note that a logical device instance id will return its associated physical device! */ +static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) +{ + // bit #1 of devid is set for physical devices and unset for logical. + const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE; + if (islogical) { + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); + if (logdev) { + return logdev->physical_device; + } + return NULL; + } if (!SDL_GetCurrentAudioDriver()) { SDL_SetError("Audio subsystem is not initialized"); return NULL; } + // bit #0 of devid is set for output devices and unset for capture. + const SDL_bool iscapture = (devid & (1<<0)) ? SDL_FALSE : SDL_TRUE; + SDL_AudioDevice *dev = NULL; + SDL_LockRWLockForReading(current_audio.device_list_lock); for (dev = iscapture ? current_audio.capture_devices : current_audio.output_devices; dev != NULL; dev = dev->next) { @@ -823,7 +908,7 @@ static SDL_AudioDevice *ObtainAudioDevice(SDL_AudioDeviceID devid) return dev; } -SDL_AudioDevice *SDL_ObtainAudioDeviceByHandle(void *handle) +SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle) { SDL_AudioDevice *dev = NULL; @@ -863,7 +948,7 @@ SDL_AudioDevice *SDL_ObtainAudioDeviceByHandle(void *handle) char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) { char *retval; - SDL_AudioDevice *device = ObtainAudioDevice(devid); + SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); if (!device) { return NULL; } @@ -884,7 +969,7 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec) return SDL_InvalidParamError("spec"); } - SDL_AudioDevice *device = ObtainAudioDevice(devid); + SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); if (!device) { return -1; } @@ -896,7 +981,7 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec) } /* this expects the device lock to be held. */ -static void CloseAudioDevice(SDL_AudioDevice *device) +static void ClosePhysicalAudioDevice(SDL_AudioDevice *device) { if (device->thread != NULL) { SDL_AtomicSet(&device->shutdown, 1); @@ -905,7 +990,10 @@ static void CloseAudioDevice(SDL_AudioDevice *device) SDL_AtomicSet(&device->shutdown, 0); } - current_audio.impl.CloseDevice(device); + if (device->is_opened) { + current_audio.impl.CloseDevice(device); + device->is_opened = SDL_FALSE; + } if (device->work_buffer) { SDL_aligned_free(device->work_buffer); @@ -919,16 +1007,17 @@ static void CloseAudioDevice(SDL_AudioDevice *device) void SDL_CloseAudioDevice(SDL_AudioDeviceID devid) { - SDL_AudioDevice *device = ObtainAudioDevice(devid); - if (device) { /* if NULL, maybe it was already lost? */ - if (SDL_AtomicDecRef(&device->refcount)) { + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); + if (logdev) { /* if NULL, maybe it was already lost? */ + SDL_AudioDevice *device = logdev->physical_device; + DestroyLogicalAudioDevice(logdev); + + if (device->logical_devices == NULL) { // no more logical devices? Close the physical device, too. + // !!! FIXME: we _need_ to release this lock, but doing so can cause a race condition if someone opens a device while we're closing it. SDL_UnlockMutex(device->lock); /* can't hold the lock or the audio thread will deadlock while we WaitThread it. */ - CloseAudioDevice(device); + ClosePhysicalAudioDevice(device); } else { - if (SDL_AtomicGet(&device->refcount) < 0) { - SDL_AtomicSet(&device->refcount, 0); /* something closed more than it should, force this back to zero. Best we can do. */ - } - SDL_UnlockMutex(device->lock); /* can't hold the lock or the audio thread will deadlock while we WaitThread it. */ + SDL_UnlockMutex(device->lock); // we're set, let everything go again. } } } @@ -1008,9 +1097,9 @@ void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device) } /* this expects the device lock to be held. */ -static int OpenAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec) +static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec) { - SDL_assert(SDL_AtomicGet(&device->refcount) == 1); + SDL_assert(device->logical_devices == NULL); SDL_AudioSpec spec; SDL_memcpy(&spec, inspec, sizeof (SDL_AudioSpec)); @@ -1026,8 +1115,9 @@ static int OpenAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec) device->sample_frames = GetDefaultSampleFramesFromFreq(device->spec.freq); SDL_UpdatedAudioDeviceFormat(device); /* start this off sane. */ + device->is_opened = SDL_TRUE; // mark this true even if impl.OpenDevice fails, so we know to clean up. if (current_audio.impl.OpenDevice(device) < 0) { - CloseAudioDevice(device); /* clean up anything the backend left half-initialized. */ + ClosePhysicalAudioDevice(device); /* clean up anything the backend left half-initialized. */ return -1; } @@ -1036,20 +1126,20 @@ static int OpenAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec) /* Allocate a scratch audio buffer */ device->work_buffer = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), device->buffer_size); if (device->work_buffer == NULL) { - CloseAudioDevice(device); + ClosePhysicalAudioDevice(device); return SDL_OutOfMemory(); } /* Start the audio thread if necessary */ if (!current_audio.impl.ProvidesOwnCallbackThread) { - const size_t stacksize = 64 * 1024; /* We only need a little space, so make the stack tiny. We may adjust this as necessary later. */ + const size_t stacksize = 0; /* just take the system default, since audio streams might have callbacks. */ char threadname[64]; (void)SDL_snprintf(threadname, sizeof(threadname), "SDLAudio%c%d", (device->iscapture) ? 'C' : 'P', (int) device->instance_id); device->thread = SDL_CreateThreadInternal(device->iscapture ? CaptureAudioThread : OutputAudioThread, threadname, stacksize, device); if (device->thread == NULL) { - CloseAudioDevice(device); + ClosePhysicalAudioDevice(device); return SDL_SetError("Couldn't create audio thread"); } } @@ -1059,14 +1149,43 @@ static int OpenAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec) SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec) { - SDL_AudioDevice *device = ObtainAudioDevice(devid); /* !!! FIXME: need to choose default device for devid==0 */ - int retval = 0; + SDL_bool is_default = SDL_FALSE; + if (devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) { + // !!! FIXME: needs to be hooked up. + //devid = current_audio.default_output_device_id; + is_default = SDL_TRUE; + } else if (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) { + // !!! FIXME: needs to be hooked up. + //devid = current_audio.default_capture_device_id; + is_default = SDL_TRUE; + } + SDL_AudioDeviceID retval = 0; + + // this will let you use a logical device to make a new logical device on the parent physical device. Could be useful? + SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); if (device) { - retval = device->instance_id; - if (SDL_AtomicIncRef(&device->refcount) == 0) { - if (OpenAudioDevice(device, spec) == -1) { - retval = 0; + SDL_LogicalAudioDevice *logdev = (SDL_LogicalAudioDevice *) SDL_calloc(1, sizeof (SDL_LogicalAudioDevice)); + if (!logdev) { + SDL_OutOfMemory(); + } else { + if (device->logical_devices == NULL) { // first thing using this physical device? Open at the OS level... + if (OpenPhysicalAudioDevice(device, spec) == -1) { + SDL_free(logdev); + logdev = NULL; + } + } + + if (logdev != NULL) { + SDL_AtomicSet(&logdev->paused, 0); + retval = logdev->instance_id = assign_audio_device_instance_id(device->iscapture, /*islogical=*/SDL_TRUE); + logdev->physical_device = device; + logdev->is_default = is_default; + logdev->next = device->logical_devices; + if (device->logical_devices) { + device->logical_devices->prev = logdev; + } + device->logical_devices = logdev; } } SDL_UnlockMutex(device->lock); @@ -1077,27 +1196,29 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int num_streams) { - SDL_AudioDevice *device; - int retval = 0; - int i; + const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE; + SDL_LogicalAudioDevice *logdev; if (num_streams == 0) { - return 0; /* nothing to do */ + return 0; // nothing to do } else if (num_streams < 0) { return SDL_InvalidParamError("num_streams"); } else if (streams == NULL) { return SDL_InvalidParamError("streams"); - } else if ((device = ObtainAudioDevice(devid)) == NULL) { - return -1; /* ObtainAudioDevice set the error message. */ - } else if (SDL_AtomicGet(&device->refcount) == 0) { - SDL_UnlockMutex(device->lock); - return SDL_SetError("Device is not opened"); + } else if (!islogical) { + return SDL_SetError("Audio streams are bound to device ids from SDL_OpenAudioDevice, not raw physical devices"); + } else if ((logdev = ObtainLogicalAudioDevice(devid)) == NULL) { + return -1; // ObtainLogicalAudioDevice set the error message. } - SDL_assert(!device->bound_streams || (device->bound_streams->prev_binding == NULL)); + // make sure start of list is sane. + SDL_assert(!logdev->bound_streams || (logdev->bound_streams->prev_binding == NULL)); - /* lock all the streams upfront, so we can verify they aren't bound elsewhere and add them all in one block, as this is intended to add everything or nothing. */ - for (i = 0; i < num_streams; i++) { + SDL_AudioDevice *device = logdev->physical_device; + int retval = 0; + + // lock all the streams upfront, so we can verify they aren't bound elsewhere and add them all in one block, as this is intended to add everything or nothing. + for (int i = 0; i < num_streams; i++) { SDL_AudioStream *stream = streams[i]; if (stream == NULL) { retval = SDL_SetError("Stream #%d is NULL", i); @@ -1119,26 +1240,27 @@ int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int } if (retval == 0) { - /* Now that everything is verified, chain everything together. */ - for (i = 0; i < num_streams; i++) { + // Now that everything is verified, chain everything together. + const SDL_bool iscapture = device->iscapture; + for (int i = 0; i < num_streams; i++) { SDL_AudioStream *stream = streams[i]; SDL_AudioSpec src_spec, dst_spec; - /* set the proper end of the stream to the device's format. */ + // set the proper end of the stream to the device's format. SDL_GetAudioStreamFormat(stream, &src_spec, &dst_spec); - if (device->iscapture) { + if (iscapture) { SDL_SetAudioStreamFormat(stream, &device->spec, &dst_spec); } else { SDL_SetAudioStreamFormat(stream, &src_spec, &device->spec); } - stream->bound_device = device; + stream->bound_device = logdev; stream->prev_binding = NULL; - stream->next_binding = device->bound_streams; - if (device->bound_streams) { - device->bound_streams->prev_binding = stream; + stream->next_binding = logdev->bound_streams; + if (logdev->bound_streams) { + logdev->bound_streams->prev_binding = stream; } - device->bound_streams = stream; + logdev->bound_streams = stream; SDL_UnlockMutex(stream->lock); } @@ -1168,15 +1290,13 @@ void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams) } while (SDL_TRUE) { - SDL_AudioDevice *bounddev; - SDL_LockMutex(stream->lock); /* lock to check this and then release it, in case the device isn't locked yet. */ - bounddev = stream->bound_device; + SDL_LogicalAudioDevice *bounddev = stream->bound_device; SDL_UnlockMutex(stream->lock); /* lock in correct order. */ if (bounddev) { - SDL_LockMutex(bounddev->lock); /* this requires recursive mutexes, since we're likely locking the same device multiple times. */ + SDL_LockMutex(bounddev->physical_device->lock); /* this requires recursive mutexes, since we're likely locking the same device multiple times. */ } SDL_LockMutex(stream->lock); @@ -1185,7 +1305,7 @@ void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams) } else { SDL_UnlockMutex(stream->lock); /* it changed bindings! Try again. */ if (bounddev) { - SDL_UnlockMutex(bounddev->lock); + SDL_UnlockMutex(bounddev->physical_device->lock); } } } @@ -1213,11 +1333,11 @@ void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams) for (i = 0; i < num_streams; i++) { SDL_AudioStream *stream = streams[i]; if (stream && stream->bound_device) { - SDL_AudioDevice *dev = stream->bound_device; + SDL_LogicalAudioDevice *logdev = stream->bound_device; stream->bound_device = NULL; SDL_UnlockMutex(stream->lock); - if (dev) { - SDL_UnlockMutex(dev->lock); + if (logdev) { + SDL_UnlockMutex(logdev->physical_device->lock); } } } @@ -1231,11 +1351,17 @@ void SDL_UnbindAudioStream(SDL_AudioStream *stream) SDL_AudioStream *SDL_CreateAndBindAudioStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec) { + const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE; + if (!islogical) { + SDL_SetError("Audio streams are bound to device ids from SDL_OpenAudioDevice, not raw physical devices"); + return NULL; + } + SDL_AudioStream *stream = NULL; - SDL_AudioDevice *device = ObtainAudioDevice(devid); - if (device) { - const SDL_bool iscapture = (devid & 1) ? SDL_FALSE : SDL_TRUE; /* capture instance ids are even and output devices are odd */ - if (iscapture) { + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); + if (logdev) { + SDL_AudioDevice *device = logdev->physical_device; + if (device->iscapture) { stream = SDL_CreateAudioStream(&device->spec, spec); } else { stream = SDL_CreateAudioStream(spec, &device->spec); diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 9674151d8a..ccc1af9745 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -55,8 +55,8 @@ extern void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_sam #define DEFAULT_AUDIO_FREQUENCY 44100 -/* The SDL audio driver */ typedef struct SDL_AudioDevice SDL_AudioDevice; +typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice; /* Used by src/SDL.c to initialize a particular audio driver. */ extern int SDL_InitAudio(const char *driver_name); @@ -80,7 +80,7 @@ extern SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device); /* Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. Locks the device! You must unlock!! */ -extern SDL_AudioDevice *SDL_ObtainAudioDeviceByHandle(void *handle); +extern SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle); /* Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct. */ extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device); @@ -169,11 +169,37 @@ struct SDL_AudioStream int pre_resample_channels; int packetlen; - SDL_AudioDevice *bound_device; + SDL_LogicalAudioDevice *bound_device; SDL_AudioStream *next_binding; SDL_AudioStream *prev_binding; }; +/* Logical devices are an abstraction in SDL3; you can open the same physical + device multiple times, and each will result in an object with its own set + of bound audio streams, etc, even though internally these are all processed + as a group when mixing the final output for the physical device. */ +struct SDL_LogicalAudioDevice +{ + /* the unique instance ID of this device. */ + SDL_AudioDeviceID instance_id; + + /* The physical device associated with this opened device. */ + SDL_AudioDevice *physical_device; + + /* If whole logical device is paused (process no streams bound to this device). */ + SDL_AtomicInt paused; + + /* double-linked list of all audio streams currently bound to this opened device. */ + SDL_AudioStream *bound_streams; + + /* SDL_TRUE if this was opened as a default device. */ + SDL_bool is_default; + + /* double-linked list of opened devices on the same physical device. */ + SDL_LogicalAudioDevice *next; + SDL_LogicalAudioDevice *prev; +}; + struct SDL_AudioDevice { /* A mutex for locking access to this struct */ @@ -216,16 +242,16 @@ struct SDL_AudioDevice /* A thread to feed the audio device */ SDL_Thread *thread; + /* SDL_TRUE if this physical device is currently opened by the backend. */ + SDL_bool is_opened; + /* Data private to this driver */ struct SDL_PrivateAudioData *hidden; - /* Each device open increases the refcount. We actually close the system device when this hits zero again. */ - SDL_AtomicInt refcount; + /* All logical devices associated with this physical device. */ + SDL_LogicalAudioDevice *logical_devices; - /* double-linked list of all audio streams currently bound to this device. */ - SDL_AudioStream *bound_streams; - - /* double-linked list of all devices. */ + /* double-linked list of all physical devices. */ struct SDL_AudioDevice *prev; struct SDL_AudioDevice *next; }; diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 514357e378..16ff3cb906 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -824,7 +824,7 @@ static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint3 PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, (void *)((intptr_t)added))); } else if (removed && (sink || source)) { /* removes we can handle just with the device index. */ - SDL_AudioDevice *device = SDL_ObtainAudioDeviceByHandle((void *)((intptr_t)idx + 1)); /* !!! FIXME: maybe just have a "disconnect by handle" function instead. */ + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)idx + 1)); /* !!! FIXME: maybe just have a "disconnect by handle" function instead. */ if (device) { SDL_UnlockMutex(device->lock); /* AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. */ SDL_AudioDeviceDisconnected(device); diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index e4da9c8d37..0745fd025f 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -884,6 +884,7 @@ SDL3_0.0.0 { SDL_MixAudioFormat; SDL_ConvertAudioSamples; SDL_GetSilenceValueForFormat; + SDL_LoadWAV; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 029c9b0f28..4964e77629 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -910,3 +910,4 @@ #define SDL_MixAudioFormat SDL_MixAudioFormat_REAL #define SDL_ConvertAudioSamples SDL_ConvertAudioSamples_REAL #define SDL_GetSilenceValueForFormat SDL_GetSilenceValueForFormat_REAL +#define SDL_LoadWAV SDL_LoadWAV_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 034a630e02..b51ac769c1 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -954,3 +954,4 @@ SDL_DYNAPI_PROC(int,SDL_LoadWAV_RW,(SDL_RWops *a, int b, SDL_AudioSpec *c, Uint8 SDL_DYNAPI_PROC(int,SDL_MixAudioFormat,(Uint8 *a, const Uint8 *b, SDL_AudioFormat c, Uint32 d, int e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(int,SDL_ConvertAudioSamples,(const SDL_AudioSpec *a, const Uint8 *b, int c, const SDL_AudioSpec *d, Uint8 **e, int *f),(a,b,c,d,e,f),return) SDL_DYNAPI_PROC(int,SDL_GetSilenceValueForFormat,(SDL_AudioFormat a),(a),return) +SDL_DYNAPI_PROC(int,SDL_LoadWAV,(const char *a, SDL_AudioSpec *b, Uint8 **c, Uint32 *d),(a,b,c,d),return) From d96a1db7d752ed99cba8c5da2f664f57b0981dcb Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 21 Jun 2023 00:05:10 -0400 Subject: [PATCH 008/138] audio: Opening via a logical device ID should also track default device. As these will change devices as the default device changes, in the future, we would want the original and new logical device to stay together. --- src/audio/SDL_audio.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 96fe7fa7de..2c4748ffbe 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -1160,10 +1160,21 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp is_default = SDL_TRUE; } + // this will let you use a logical device to make a new logical device on the parent physical device. Could be useful? + SDL_AudioDevice *device = NULL; + const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE; + if (!islogical) { + device = ObtainPhysicalAudioDevice(devid); + } else { + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); // this locks the physical device, too. + if (logdev) { + is_default = logdev->is_default; // was the original logical device meant to be a default? Make this one, too. + device = logdev->physical_device; + } + } + SDL_AudioDeviceID retval = 0; - // this will let you use a logical device to make a new logical device on the parent physical device. Could be useful? - SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); if (device) { SDL_LogicalAudioDevice *logdev = (SDL_LogicalAudioDevice *) SDL_calloc(1, sizeof (SDL_LogicalAudioDevice)); if (!logdev) { From 4b78b789a701c15a503990d62fed9c0a22dc7345 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 21 Jun 2023 01:02:47 -0400 Subject: [PATCH 009/138] audio: Switch SDL_audio.c and SDL_audiocvt.c to C99-ish syntax. These files are completely different from SDL2, and no clean merging is likely to happen there anyhow, so there's really no harm in just switching them over completely to SDL3's new policy of allowing `//` comments and mixed variable declarations. Feels deeply sacrilegious, though. --- src/audio/SDL_audio.c | 337 +++++++++++++++++---------------------- src/audio/SDL_audiocvt.c | 301 +++++++++++++++++----------------- 2 files changed, 291 insertions(+), 347 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 2c4748ffbe..f4033c0fd0 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -25,11 +25,9 @@ #include "../thread/SDL_systhread.h" #include "../SDL_utils_c.h" -extern void Android_JNI_AudioSetThreadPriority(int, int); /* we need this on Android in the audio device threads. */ +extern void Android_JNI_AudioSetThreadPriority(int, int); // we need this on Android in the audio device threads. -static SDL_AudioDriver current_audio; - -/* Available audio drivers */ +// Available audio drivers static const AudioBootStrap *const bootstrap[] = { #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO &PULSEAUDIO_bootstrap, @@ -100,6 +98,8 @@ static const AudioBootStrap *const bootstrap[] = { NULL }; +static SDL_AudioDriver current_audio; + int SDL_GetNumAudioDrivers(void) { return SDL_arraysize(bootstrap) - 1; @@ -118,7 +118,7 @@ const char *SDL_GetCurrentAudioDriver(void) return current_audio.name; } -/* device management and hotplug... */ +// device management and hotplug... /* SDL_AudioDevice, in SDL3, represents a piece of physical hardware, whether it is in use or not, so these objects exist as long as @@ -208,15 +208,13 @@ static void DestroyPhysicalAudioDevice(SDL_AudioDevice *device) static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, SDL_bool iscapture, const SDL_AudioSpec *spec, void *handle, SDL_AudioDevice **devices, SDL_AtomicInt *device_count) { - SDL_AudioDevice *device; - SDL_assert(name != NULL); if (SDL_AtomicGet(¤t_audio.shutting_down)) { - return NULL; /* we're shutting down, don't add any devices that are hotplugged at the last possible moment. */ + return NULL; // we're shutting down, don't add any devices that are hotplugged at the last possible moment. } - device = (SDL_AudioDevice *)SDL_calloc(1, sizeof(SDL_AudioDevice)); + SDL_AudioDevice *device = (SDL_AudioDevice *)SDL_calloc(1, sizeof(SDL_AudioDevice)); if (!device) { SDL_OutOfMemory(); return NULL; @@ -267,12 +265,10 @@ static SDL_AudioDevice *CreateAudioOutputDevice(const char *name, const SDL_Audi return CreatePhysicalAudioDevice(name, SDL_FALSE, spec, handle, ¤t_audio.output_devices, ¤t_audio.output_device_count); } -/* The audio backends call this when a new device is plugged in. */ +// The audio backends call this when a new device is plugged in. SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, const SDL_AudioSpec *inspec, void *handle) { - SDL_AudioDevice *device; SDL_AudioSpec spec; - if (!inspec) { spec.format = DEFAULT_AUDIO_FORMAT; spec.channels = DEFAULT_AUDIO_CHANNELS; @@ -283,9 +279,9 @@ SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, spec.freq = (inspec->freq != 0) ? inspec->freq : DEFAULT_AUDIO_FREQUENCY; } - device = iscapture ? CreateAudioCaptureDevice(name, &spec, handle) : CreateAudioOutputDevice(name, &spec, handle); + SDL_AudioDevice *device = iscapture ? CreateAudioCaptureDevice(name, &spec, handle) : CreateAudioOutputDevice(name, &spec, handle); if (device) { - /* Post the event, if desired */ + // Post the event, if desired if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_ADDED)) { SDL_Event event; event.type = SDL_EVENT_AUDIO_DEVICE_ADDED; @@ -298,19 +294,18 @@ SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, return device; } -/* Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread. */ +// Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread. void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) { - SDL_bool was_live = SDL_FALSE; - SDL_bool should_destroy = SDL_TRUE; - if (!device) { return; } - /* take it out of the device list. */ + SDL_bool was_live = SDL_FALSE; + + // take it out of the device list. SDL_LockRWLockForWriting(current_audio.device_list_lock); - SDL_LockMutex(device->lock); /* make sure nothing else is messing with the device before continuing. */ + SDL_LockMutex(device->lock); // make sure nothing else is messing with the device before continuing. if (device == current_audio.output_devices) { SDL_assert(device->prev == NULL); current_audio.output_devices = device->next; @@ -330,17 +325,18 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) } SDL_UnlockRWLock(current_audio.device_list_lock); - /* now device is not in the list, and we own it, so no one should be able to find it again, except the audio thread, which holds a pointer! */ + // now device is not in the list, and we own it, so no one should be able to find it again, except the audio thread, which holds a pointer! SDL_AtomicSet(&device->condemned, 1); - SDL_AtomicSet(&device->shutdown, 1); /* tell audio thread to terminate. */ - if (device->thread) { - should_destroy = SDL_FALSE; /* if there's an audio thread, don't free until thread is terminating. */ - } + SDL_AtomicSet(&device->shutdown, 1); // tell audio thread to terminate. + + // if there's an audio thread, don't free until thread is terminating, otherwise free stuff now. + const SDL_bool should_destroy = (device->thread != NULL); SDL_UnlockMutex(device->lock); - /* Post the event, if we haven't tried to before and if it's desired */ + // Post the event, if we haven't tried to before and if it's desired if (was_live && SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_REMOVED)) { SDL_Event event; + SDL_zero(event); event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED; event.common.timestamp = 0; event.adevice.which = device->instance_id; @@ -354,11 +350,20 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) } -/* stubs for audio drivers that don't need a specific entry point... */ +// stubs for audio drivers that don't need a specific entry point... + +static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device) { /* no-op. */ } +static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) { /* no-op. */ } +static void SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } +static void SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, int buffer_size) { /* no-op. */ } +static void SDL_AudioFlushCapture_Default(SDL_AudioDevice *device) { /* no-op. */ } +static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } +static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ } +static void SDL_AudioFreeDeviceHandle_Default(void *handle) { /* no-op. */ } static void SDL_AudioDetectDevices_Default(void) { - /* you have to write your own implementation if these assertions fail. */ + // you have to write your own implementation if these assertions fail. SDL_assert(current_audio.impl.OnlyHasDefaultOutputDevice); SDL_assert(current_audio.impl.OnlyHasDefaultCaptureDevice || !current_audio.impl.HasCaptureSupport); @@ -368,22 +373,6 @@ static void SDL_AudioDetectDevices_Default(void) } } -static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device) -{ /* no-op. */ -} - -static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) -{ /* no-op. */ -} - -static void SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) -{ /* no-op. */ -} - -static void SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, int buffer_size) -{ /* no-op. */ -} - static Uint8 *SDL_AudioGetDeviceBuf_Default(SDL_AudioDevice *device, int *buffer_size) { *buffer_size = 0; @@ -392,23 +381,7 @@ static Uint8 *SDL_AudioGetDeviceBuf_Default(SDL_AudioDevice *device, int *buffer static int SDL_AudioCaptureFromDevice_Default(SDL_AudioDevice *device, void *buffer, int buflen) { - return -1; /* just fail immediately. */ -} - -static void SDL_AudioFlushCapture_Default(SDL_AudioDevice *device) -{ /* no-op. */ -} - -static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) -{ /* no-op. */ -} - -static void SDL_AudioDeinitialize_Default(void) -{ /* no-op. */ -} - -static void SDL_AudioFreeDeviceHandle_Default(void *handle) -{ /* no-op. */ + return SDL_Unsupported(); } static int SDL_AudioOpenDevice_Default(SDL_AudioDevice *device) @@ -416,7 +389,7 @@ static int SDL_AudioOpenDevice_Default(SDL_AudioDevice *device) return SDL_Unsupported(); } -/* Fill in stub functions for unused driver entry points. This lets us blindly call them without having to check for validity first. */ +// Fill in stub functions for unused driver entry points. This lets us blindly call them without having to check for validity first. static void CompleteAudioEntryPoints(void) { #define FILL_STUB(x) if (!current_audio.impl.x) { current_audio.impl.x = SDL_Audio##x##_Default; } @@ -435,30 +408,28 @@ static void CompleteAudioEntryPoints(void) #undef FILL_STUB } -/* !!! FIXME: the video subsystem does SDL_VideoInit, not SDL_InitVideo. Make this match. */ +// !!! FIXME: the video subsystem does SDL_VideoInit, not SDL_InitVideo. Make this match. int SDL_InitAudio(const char *driver_name) { - SDL_RWLock *device_list_lock = NULL; - SDL_bool initialized = SDL_FALSE; - SDL_bool tried_to_init = SDL_FALSE; - int i; - if (SDL_GetCurrentAudioDriver()) { - SDL_QuitAudio(); /* shutdown driver if already running. */ + SDL_QuitAudio(); // shutdown driver if already running. } SDL_ChooseAudioConverters(); - device_list_lock = SDL_CreateRWLock(); /* create this early, so if it fails we don't have to tear down the whole audio subsystem. */ + SDL_RWLock *device_list_lock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole audio subsystem. if (!device_list_lock) { return -1; } - /* Select the proper audio driver */ + // Select the proper audio driver if (driver_name == NULL) { driver_name = SDL_GetHint(SDL_HINT_AUDIO_DRIVER); } + SDL_bool initialized = SDL_FALSE; + SDL_bool tried_to_init = SDL_FALSE; + if (driver_name != NULL && *driver_name != 0) { char *driver_name_copy = SDL_strdup(driver_name); const char *driver_attempt = driver_name_copy; @@ -474,18 +445,18 @@ int SDL_InitAudio(const char *driver_name) *driver_attempt_end = '\0'; } - /* SDL 1.2 uses the name "dsound", so we'll support both. */ + // SDL 1.2 uses the name "dsound", so we'll support both. if (SDL_strcmp(driver_attempt, "dsound") == 0) { driver_attempt = "directsound"; - } else if (SDL_strcmp(driver_attempt, "pulse") == 0) { /* likewise, "pulse" was renamed to "pulseaudio" */ + } else if (SDL_strcmp(driver_attempt, "pulse") == 0) { // likewise, "pulse" was renamed to "pulseaudio" driver_attempt = "pulseaudio"; } - for (i = 0; bootstrap[i]; ++i) { + for (int i = 0; bootstrap[i]; ++i) { if (SDL_strcasecmp(bootstrap[i]->name, driver_attempt) == 0) { tried_to_init = SDL_TRUE; SDL_zero(current_audio); - SDL_AtomicSet(¤t_audio.last_device_instance_id, 2); /* start past 1 because of SDL2's legacy interface. */ + SDL_AtomicSet(¤t_audio.last_device_instance_id, 2); // start past 1 because of SDL2's legacy interface. current_audio.device_list_lock = device_list_lock; if (bootstrap[i]->init(¤t_audio.impl)) { current_audio.name = bootstrap[i]->name; @@ -501,14 +472,14 @@ int SDL_InitAudio(const char *driver_name) SDL_free(driver_name_copy); } else { - for (i = 0; (!initialized) && (bootstrap[i]); ++i) { + for (int i = 0; (!initialized) && (bootstrap[i]); ++i) { if (bootstrap[i]->demand_only) { continue; } tried_to_init = SDL_TRUE; SDL_zero(current_audio); - SDL_AtomicSet(¤t_audio.last_device_instance_id, 2); /* start past 1 because of SDL2's legacy interface. */ + SDL_AtomicSet(¤t_audio.last_device_instance_id, 2); // start past 1 because of SDL2's legacy interface. current_audio.device_list_lock = device_list_lock; if (bootstrap[i]->init(¤t_audio.impl)) { current_audio.name = bootstrap[i]->name; @@ -519,7 +490,7 @@ int SDL_InitAudio(const char *driver_name) } if (!initialized) { - /* specific drivers will set the error message if they fail... */ + // specific drivers will set the error message if they fail, but otherwise we do it here. if (!tried_to_init) { if (driver_name) { SDL_SetError("Audio target '%s' not available", driver_name); @@ -530,12 +501,12 @@ int SDL_InitAudio(const char *driver_name) SDL_zero(current_audio); SDL_DestroyRWLock(device_list_lock); - return -1; /* No driver was available, so fail. */ + return -1; // No driver was available, so fail. } CompleteAudioEntryPoints(); - /* Make sure we have a list of devices available at startup. */ + // Make sure we have a list of devices available at startup... current_audio.impl.DetectDevices(); return 0; @@ -543,15 +514,14 @@ int SDL_InitAudio(const char *driver_name) void SDL_QuitAudio(void) { - SDL_AudioDevice *devices = NULL; - - if (!current_audio.name) { /* not initialized?! */ + if (!current_audio.name) { // not initialized?! return; } - /* merge device lists so we don't have to duplicate work below. */ + // merge device lists so we don't have to duplicate work below. SDL_LockRWLockForWriting(current_audio.device_list_lock); SDL_AtomicSet(¤t_audio.shutting_down, 1); + SDL_AudioDevice *devices = NULL; for (SDL_AudioDevice *i = current_audio.output_devices; i != NULL; i = i->next) { devices = i; } @@ -566,15 +536,15 @@ void SDL_QuitAudio(void) current_audio.capture_devices = NULL; SDL_UnlockRWLock(current_audio.device_list_lock); - /* mark all devices for shutdown so all threads can begin to terminate. */ + // mark all devices for shutdown so all threads can begin to terminate. for (SDL_AudioDevice *i = devices; i != NULL; i = i->next) { SDL_AtomicSet(&i->shutdown, 1); } - /* now wait on any audio threads. */ + // now wait on any audio threads... for (SDL_AudioDevice *i = devices; i != NULL; i = i->next) { if (i->thread) { - SDL_assert(!SDL_AtomicGet(&i->condemned)); /* these shouldn't have been in the device list still, and thread should have detached. */ + SDL_assert(!SDL_AtomicGet(&i->condemned)); // these shouldn't have been in the device list still, and thread should have detached. SDL_WaitThread(i->thread, NULL); i->thread = NULL; } @@ -586,7 +556,7 @@ void SDL_QuitAudio(void) devices = next; } - /* Free the driver data */ + // Free the driver data current_audio.impl.Deinitialize(); SDL_DestroyRWLock(current_audio.device_list_lock); @@ -597,72 +567,72 @@ void SDL_QuitAudio(void) -/* Output device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort. */ +// Output device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort. void SDL_OutputAudioThreadSetup(SDL_AudioDevice *device) { SDL_assert(!device->iscapture); - /* The audio mixing is always a high priority thread */ + // The audio mixing is always a high priority thread #ifdef SDL_AUDIO_DRIVER_ANDROID Android_JNI_AudioSetThreadPriority(SDL_FALSE, device->id); #else SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL); #endif - /* Perform any thread setup */ + // Perform any thread setup current_audio.impl.ThreadInit(device); } SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) { - SDL_bool retval = SDL_TRUE; - int buffer_size = device->buffer_size; - Uint8 *mix_buffer; - SDL_assert(!device->iscapture); SDL_LockMutex(device->lock); if (SDL_AtomicGet(&device->shutdown)) { SDL_UnlockMutex(device->lock); - return SDL_FALSE; /* we're done, shut it down. */ + return SDL_FALSE; // we're done, shut it down. } - mix_buffer = current_audio.impl.GetDeviceBuf(device, &buffer_size); + SDL_bool retval = SDL_TRUE; + int buffer_size = device->buffer_size; + Uint8 *mix_buffer = current_audio.impl.GetDeviceBuf(device, &buffer_size); if (!mix_buffer) { retval = SDL_FALSE; } else { - SDL_assert(buffer_size <= device->buffer_size); /* you can ask for less, but not more. */ - SDL_memset(mix_buffer, device->silence_value, buffer_size); /* start with silence. */ + SDL_assert(buffer_size <= device->buffer_size); // you can ask for less, but not more. + SDL_memset(mix_buffer, device->silence_value, buffer_size); // start with silence. for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { - /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. */ - /* (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind the same stream to different devices at the same time, though.) */ + /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams + for iterating here because the binding linked list can only change while the device lock is held. + (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind + the same stream to different devices at the same time, though.) */ const int br = SDL_GetAudioStreamData(stream, device->work_buffer, buffer_size); if (br < 0) { - /* oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. */ + // oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. retval = SDL_FALSE; break; - } else if (br > 0) { /* it's okay if we get less than requested, we mix what we have. */ + } else if (br > 0) { // it's okay if we get less than requested, we mix what we have. // !!! FIXME: this needs to mix to float32 or int32, so we don't clip. - if (SDL_MixAudioFormat(mix_buffer, device->work_buffer, device->spec.format, br, SDL_MIX_MAXVOLUME) < 0) { /* !!! FIXME: allow streams to specify gain? */ + if (SDL_MixAudioFormat(mix_buffer, device->work_buffer, device->spec.format, br, SDL_MIX_MAXVOLUME) < 0) { // !!! FIXME: allow streams to specify gain? SDL_assert(!"We probably ended up with some totally unexpected audio format here"); - retval = SDL_FALSE; /* uh...? */ + retval = SDL_FALSE; // uh...? break; } } } } - /* !!! FIXME: have PlayDevice return a value and do disconnects in here with it. */ - current_audio.impl.PlayDevice(device, buffer_size); /* this SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice! */ + // !!! FIXME: have PlayDevice return a value and do disconnects in here with it. + current_audio.impl.PlayDevice(device, buffer_size); // this SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice! } SDL_UnlockMutex(device->lock); if (!retval) { - SDL_AudioDeviceDisconnected(device); /* doh. */ + SDL_AudioDeviceDisconnected(device); // doh. } return retval; @@ -670,20 +640,19 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device) { - const int samples = (device->buffer_size / (SDL_AUDIO_BITSIZE(device->spec.format) / 8)) / device->spec.channels; SDL_assert(!device->iscapture); - /* Wait for the audio to drain. */ /* !!! FIXME: don't bother waiting if device is lost. */ + const int samples = (device->buffer_size / (SDL_AUDIO_BITSIZE(device->spec.format) / 8)) / device->spec.channels; + // Wait for the audio to drain. !!! FIXME: don't bother waiting if device is lost. SDL_Delay(((samples * 1000) / device->spec.freq) * 2); current_audio.impl.ThreadDeinit(device); if (SDL_AtomicGet(&device->condemned)) { - SDL_DetachThread(device->thread); /* no one is waiting for us, just detach ourselves. */ + SDL_DetachThread(device->thread); // no one is waiting for us, just detach ourselves. device->thread = NULL; DestroyPhysicalAudioDevice(device); } } -/* thread entry point */ -static int SDLCALL OutputAudioThread(void *devicep) +static int SDLCALL OutputAudioThread(void *devicep) // thread entry point { SDL_AudioDevice *device = (SDL_AudioDevice *)devicep; SDL_assert(device != NULL); @@ -699,46 +668,47 @@ static int SDLCALL OutputAudioThread(void *devicep) -/* Capture device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort. */ +// Capture device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort. void SDL_CaptureAudioThreadSetup(SDL_AudioDevice *device) { SDL_assert(device->iscapture); - /* The audio mixing is always a high priority thread */ + // Audio capture is always a high priority thread (!!! FIXME: _should_ it be?) #ifdef SDL_AUDIO_DRIVER_ANDROID Android_JNI_AudioSetThreadPriority(SDL_TRUE, device->id); #else SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); #endif - /* Perform any thread setup */ current_audio.impl.ThreadInit(device); } SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device) { - SDL_bool retval = SDL_TRUE; - SDL_assert(device->iscapture); SDL_LockMutex(device->lock); + SDL_bool retval = SDL_TRUE; + if (SDL_AtomicGet(&device->shutdown)) { - retval = SDL_FALSE; /* we're done, shut it down. */ + retval = SDL_FALSE; // we're done, shut it down. } else if (device->logical_devices == NULL) { - current_audio.impl.FlushCapture(device); /* nothing wants data, dump anything pending. */ + current_audio.impl.FlushCapture(device); // nothing wants data, dump anything pending. } else { const int rc = current_audio.impl.CaptureFromDevice(device, device->work_buffer, device->buffer_size); - if (rc < 0) { /* uhoh, device failed for some reason! */ + if (rc < 0) { // uhoh, device failed for some reason! retval = SDL_FALSE; - } else if (rc > 0) { /* queue the new data to each bound stream. */ + } else if (rc > 0) { // queue the new data to each bound stream. for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { - /* this will hold a lock on `stream` while putting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. */ - /* (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind the same stream to different devices at the same time, though.) */ + /* this will hold a lock on `stream` while putting. We don't explicitly lock the streams + for iterating here because the binding linked list can only change while the device lock is held. + (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind + the same stream to different devices at the same time, though.) */ if (SDL_PutAudioStreamData(stream, device->work_buffer, rc) < 0) { - /* oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. */ + // oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. retval = SDL_FALSE; break; } @@ -750,7 +720,7 @@ SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device) SDL_UnlockMutex(device->lock); if (!retval) { - SDL_AudioDeviceDisconnected(device); /* doh. */ + SDL_AudioDeviceDisconnected(device); // doh. } return retval; @@ -766,8 +736,7 @@ void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device) } } -/* thread entry point */ -static int SDLCALL CaptureAudioThread(void *devicep) +static int SDLCALL CaptureAudioThread(void *devicep) // thread entry point { SDL_AudioDevice *device = (SDL_AudioDevice *)devicep; SDL_assert(device != NULL); @@ -781,31 +750,27 @@ static int SDLCALL CaptureAudioThread(void *devicep) static SDL_AudioDeviceID *GetAudioDevices(int *reqcount, SDL_AudioDevice **devices, SDL_AtomicInt *device_count) { - SDL_AudioDeviceID *retval = NULL; - int num_devices = 0; - if (!SDL_GetCurrentAudioDriver()) { SDL_SetError("Audio subsystem is not initialized"); return NULL; } SDL_LockRWLockForReading(current_audio.device_list_lock); - num_devices = SDL_AtomicGet(device_count); - retval = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID)); + int num_devices = SDL_AtomicGet(device_count); + SDL_AudioDeviceID *retval = (SDL_AudioDeviceID *) SDL_malloc((num_devices + 1) * sizeof (SDL_AudioDeviceID)); if (retval == NULL) { num_devices = 0; SDL_OutOfMemory(); } else { const SDL_AudioDevice *dev = *devices; - int i; - for (i = 0; i < num_devices; i++) { + for (int i = 0; i < num_devices; i++) { SDL_assert(dev != NULL); - SDL_assert(!SDL_AtomicGet((SDL_AtomicInt *) &dev->condemned)); /* shouldn't be in the list if pending deletion. */ + SDL_assert(!SDL_AtomicGet((SDL_AtomicInt *) &dev->condemned)); // shouldn't be in the list if pending deletion. retval[i] = dev->instance_id; dev = dev->next; } - SDL_assert(dev == NULL); /* did the whole list? */ - retval[num_devices] = 0; /* null-terminated. */ + SDL_assert(dev == NULL); // did the whole list? + retval[num_devices] = 0; // null-terminated. } SDL_UnlockRWLock(current_audio.device_list_lock); @@ -892,9 +857,9 @@ static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) SDL_LockRWLockForReading(current_audio.device_list_lock); for (dev = iscapture ? current_audio.capture_devices : current_audio.output_devices; dev != NULL; dev = dev->next) { - if (dev->instance_id == devid) { /* found it? */ - SDL_LockMutex(dev->lock); /* caller must unlock. */ - SDL_assert(!SDL_AtomicGet(&dev->condemned)); /* shouldn't be in the list if pending deletion. */ + if (dev->instance_id == devid) { // found it? + SDL_LockMutex(dev->lock); // caller must unlock. + SDL_assert(!SDL_AtomicGet(&dev->condemned)); // shouldn't be in the list if pending deletion. break; } } @@ -910,8 +875,6 @@ static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle) { - SDL_AudioDevice *dev = NULL; - if (!SDL_GetCurrentAudioDriver()) { SDL_SetError("Audio subsystem is not initialized"); return NULL; @@ -919,18 +882,21 @@ SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle) SDL_LockRWLockForReading(current_audio.device_list_lock); + SDL_AudioDevice *dev = NULL; for (dev = current_audio.output_devices; dev != NULL; dev = dev->next) { - if (dev->handle == handle) { /* found it? */ - SDL_LockMutex(dev->lock); /* caller must unlock. */ - SDL_assert(!SDL_AtomicGet(&dev->condemned)); /* shouldn't be in the list if pending deletion. */ + if (dev->handle == handle) { // found it? + SDL_LockMutex(dev->lock); // caller must unlock. + SDL_assert(!SDL_AtomicGet(&dev->condemned)); // shouldn't be in the list if pending deletion. break; } } + if (!dev) { + // !!! FIXME: code duplication, from above. for (dev = current_audio.capture_devices; dev != NULL; dev = dev->next) { - if (dev->handle == handle) { /* found it? */ - SDL_LockMutex(dev->lock); /* caller must unlock. */ - SDL_assert(!SDL_AtomicGet(&dev->condemned)); /* shouldn't be in the list if pending deletion. */ + if (dev->handle == handle) { // found it? + SDL_LockMutex(dev->lock); // caller must unlock. + SDL_assert(!SDL_AtomicGet(&dev->condemned)); // shouldn't be in the list if pending deletion. break; } } @@ -947,13 +913,12 @@ SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle) char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) { - char *retval; SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); if (!device) { return NULL; } - retval = SDL_strdup(device->name); + char *retval = SDL_strdup(device->name); if (!retval) { SDL_OutOfMemory(); } @@ -980,7 +945,7 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec) return 0; } -/* this expects the device lock to be held. */ +// this expects the device lock to be held. static void ClosePhysicalAudioDevice(SDL_AudioDevice *device) { if (device->thread != NULL) { @@ -1008,13 +973,13 @@ static void ClosePhysicalAudioDevice(SDL_AudioDevice *device) void SDL_CloseAudioDevice(SDL_AudioDeviceID devid) { SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); - if (logdev) { /* if NULL, maybe it was already lost? */ + if (logdev) { // if NULL, maybe it was already lost? SDL_AudioDevice *device = logdev->physical_device; DestroyLogicalAudioDevice(logdev); if (device->logical_devices == NULL) { // no more logical devices? Close the physical device, too. // !!! FIXME: we _need_ to release this lock, but doing so can cause a race condition if someone opens a device while we're closing it. - SDL_UnlockMutex(device->lock); /* can't hold the lock or the audio thread will deadlock while we WaitThread it. */ + SDL_UnlockMutex(device->lock); // can't hold the lock or the audio thread will deadlock while we WaitThread it. ClosePhysicalAudioDevice(device); } else { SDL_UnlockMutex(device->lock); // we're set, let everything go again. @@ -1026,9 +991,7 @@ void SDL_CloseAudioDevice(SDL_AudioDeviceID devid) static SDL_AudioFormat ParseAudioFormatString(const char *string) { if (string) { -#define CHECK_FMT_STRING(x) \ - if (SDL_strcmp(string, #x) == 0) \ - return SDL_AUDIO_##x + #define CHECK_FMT_STRING(x) if (SDL_strcmp(string, #x) == 0) { return SDL_AUDIO_##x; } CHECK_FMT_STRING(U8); CHECK_FMT_STRING(S8); CHECK_FMT_STRING(S16LSB); @@ -1042,18 +1005,16 @@ static SDL_AudioFormat ParseAudioFormatString(const char *string) CHECK_FMT_STRING(F32MSB); CHECK_FMT_STRING(F32SYS); CHECK_FMT_STRING(F32); -#undef CHECK_FMT_STRING + #undef CHECK_FMT_STRING } return 0; } static void PrepareAudioFormat(SDL_AudioSpec *spec) { - const char *env; - if (spec->freq == 0) { spec->freq = DEFAULT_AUDIO_FREQUENCY; - env = SDL_getenv("SDL_AUDIO_FREQUENCY"); // !!! FIXME: should be a hint? + const char *env = SDL_getenv("SDL_AUDIO_FREQUENCY"); // !!! FIXME: should be a hint? if (env != NULL) { const int val = SDL_atoi(env); if (val > 0) { @@ -1064,7 +1025,7 @@ static void PrepareAudioFormat(SDL_AudioSpec *spec) if (spec->channels == 0) { spec->channels = DEFAULT_AUDIO_CHANNELS; - env = SDL_getenv("SDL_AUDIO_CHANNELS"); + const char *env = SDL_getenv("SDL_AUDIO_CHANNELS"); if (env != NULL) { const int val = SDL_atoi(env); if (val > 0) { @@ -1081,7 +1042,7 @@ static void PrepareAudioFormat(SDL_AudioSpec *spec) static int GetDefaultSampleFramesFromFreq(int freq) { - /* Pick the closest power-of-two to ~46 ms at desired frequency */ + // Pick the closest power-of-two to ~46 ms at desired frequency const int max_sample = ((freq / 1000) * 46); int current_sample = 1; while (current_sample < max_sample) { @@ -1096,7 +1057,7 @@ void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device) device->buffer_size = device->sample_frames * (SDL_AUDIO_BITSIZE(device->spec.format) / 8) * device->spec.channels; } -/* this expects the device lock to be held. */ +// this expects the device lock to be held. static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec) { SDL_assert(device->logical_devices == NULL); @@ -1105,36 +1066,35 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec SDL_memcpy(&spec, inspec, sizeof (SDL_AudioSpec)); PrepareAudioFormat(&spec); - /* we allow the device format to change if it's better than the current settings (by various definitions of "better"). This prevents + /* We allow the device format to change if it's better than the current settings (by various definitions of "better"). This prevents something low quality, like an old game using S8/8000Hz audio, from ruining a music thing playing at CD quality that tries to open later. - (or some VoIP library that opens for mono output ruining your surround-sound game because it got there first). */ - /* These are just requests! The backend may change any of these values during OpenDevice method! */ + (or some VoIP library that opens for mono output ruining your surround-sound game because it got there first). + These are just requests! The backend may change any of these values during OpenDevice method! */ device->spec.format = (SDL_AUDIO_BITSIZE(device->default_spec.format) >= SDL_AUDIO_BITSIZE(spec.format)) ? device->default_spec.format : spec.format; device->spec.freq = SDL_max(device->default_spec.freq, spec.freq); device->spec.channels = SDL_max(device->default_spec.channels, spec.channels); device->sample_frames = GetDefaultSampleFramesFromFreq(device->spec.freq); - SDL_UpdatedAudioDeviceFormat(device); /* start this off sane. */ + SDL_UpdatedAudioDeviceFormat(device); // start this off sane. device->is_opened = SDL_TRUE; // mark this true even if impl.OpenDevice fails, so we know to clean up. if (current_audio.impl.OpenDevice(device) < 0) { - ClosePhysicalAudioDevice(device); /* clean up anything the backend left half-initialized. */ + ClosePhysicalAudioDevice(device); // clean up anything the backend left half-initialized. return -1; } - SDL_UpdatedAudioDeviceFormat(device); /* in case the backend changed things and forgot to call this. */ + SDL_UpdatedAudioDeviceFormat(device); // in case the backend changed things and forgot to call this. - /* Allocate a scratch audio buffer */ + // Allocate a scratch audio buffer device->work_buffer = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), device->buffer_size); if (device->work_buffer == NULL) { ClosePhysicalAudioDevice(device); return SDL_OutOfMemory(); } - /* Start the audio thread if necessary */ + // Start the audio thread if necessary if (!current_audio.impl.ProvidesOwnCallbackThread) { - const size_t stacksize = 0; /* just take the system default, since audio streams might have callbacks. */ + const size_t stacksize = 0; // just take the system default, since audio streams might have callbacks. char threadname[64]; - (void)SDL_snprintf(threadname, sizeof(threadname), "SDLAudio%c%d", (device->iscapture) ? 'C' : 'P', (int) device->instance_id); device->thread = SDL_CreateThreadInternal(device->iscapture ? CaptureAudioThread : OutputAudioThread, threadname, stacksize, device); @@ -1289,32 +1249,30 @@ int SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_AudioStream *stream) void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams) { - int i; - /* to prevent deadlock when holding both locks, we _must_ lock the device first, and the stream second, as that is the order the audio thread will do it. But this means we have an unlikely, pathological case where a stream could change its binding between when we lookup its bound device and when we lock everything, so we double-check here. */ - for (i = 0; i < num_streams; i++) { + for (int i = 0; i < num_streams; i++) { SDL_AudioStream *stream = streams[i]; if (!stream) { - continue; /* nothing to do, it's a NULL stream. */ + continue; // nothing to do, it's a NULL stream. } while (SDL_TRUE) { - SDL_LockMutex(stream->lock); /* lock to check this and then release it, in case the device isn't locked yet. */ + SDL_LockMutex(stream->lock); // lock to check this and then release it, in case the device isn't locked yet. SDL_LogicalAudioDevice *bounddev = stream->bound_device; SDL_UnlockMutex(stream->lock); - /* lock in correct order. */ + // lock in correct order. if (bounddev) { - SDL_LockMutex(bounddev->physical_device->lock); /* this requires recursive mutexes, since we're likely locking the same device multiple times. */ + SDL_LockMutex(bounddev->physical_device->lock); // this requires recursive mutexes, since we're likely locking the same device multiple times. } SDL_LockMutex(stream->lock); if (bounddev == stream->bound_device) { - break; /* the binding didn't change in the small window where it could, so we're good. */ + break; // the binding didn't change in the small window where it could, so we're good. } else { - SDL_UnlockMutex(stream->lock); /* it changed bindings! Try again. */ + SDL_UnlockMutex(stream->lock); // it changed bindings! Try again. if (bounddev) { SDL_UnlockMutex(bounddev->physical_device->lock); } @@ -1322,8 +1280,8 @@ void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams) } } - /* everything is locked, start unbinding streams. */ - for (i = 0; i < num_streams; i++) { + // everything is locked, start unbinding streams. + for (int i = 0; i < num_streams; i++) { SDL_AudioStream *stream = streams[i]; if (stream && stream->bound_device) { if (stream->bound_device->bound_streams == stream) { @@ -1340,8 +1298,8 @@ void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams) } } - /* Finalize and unlock everything. */ - for (i = 0; i < num_streams; i++) { + // Finalize and unlock everything. + for (int i = 0; i < num_streams; i++) { SDL_AudioStream *stream = streams[i]; if (stream && stream->bound_device) { SDL_LogicalAudioDevice *logdev = stream->bound_device; @@ -1403,13 +1361,12 @@ static const SDL_AudioFormat format_list[NUM_FORMATS][NUM_FORMATS + 1] = { const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format) { - int i; - for (i = 0; i < NUM_FORMATS; i++) { + for (int i = 0; i < NUM_FORMATS; i++) { if (format_list[i][0] == format) { return &format_list[i][0]; } } - return &format_list[0][NUM_FORMATS]; /* not found; return what looks like a list with only a zero in it. */ + return &format_list[0][NUM_FORMATS]; // not found; return what looks like a list with only a zero in it. } int SDL_GetSilenceValueForFormat(SDL_AudioFormat format) diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index d61aa75ca2..f9f43ed444 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -20,7 +20,7 @@ */ #include "SDL_internal.h" -/* Functions for audio drivers to perform runtime conversion of audio format */ +// Functions for audio drivers to perform runtime conversion of audio format #include "SDL_audio_c.h" @@ -64,7 +64,7 @@ static int GetHistoryBufferSampleFrames(const Sint32 required_resampler_frames) return (int) SDL_max(required_resampler_frames, 5000); } -/* lpadding and rpadding are expected to be buffers of (GetResamplePadding(inrate, outrate) * chans * sizeof (float)) bytes. */ +// lpadding and rpadding are expected to be buffers of (GetResamplePadding(inrate, outrate) * chans * sizeof (float)) bytes. static void ResampleAudio(const int chans, const int inrate, const int outrate, const float *lpadding, const float *rpadding, const float *inbuf, const int inframes, @@ -97,7 +97,7 @@ static void ResampleAudio(const int chans, const int inrate, const int outrate, for (chan = 0; chan < chans; chan++) { float outsample = 0.0f; - /* do this twice to calculate the sample, once for the "left wing" and then same for the right. */ + // do this twice to calculate the sample, once for the "left wing" and then same for the right. for (j = 0; (filterindex1 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)) < RESAMPLER_FILTER_SIZE; j++) { const int filt_ind = filterindex1 + j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING; const int srcframe = srcindex - j; @@ -106,11 +106,11 @@ static void ResampleAudio(const int chans, const int inrate, const int outrate, outsample += (float) (insample * (ResamplerFilter[filt_ind] + (interpolation1 * ResamplerFilterDifference[filt_ind]))); } - /* Do the right wing! */ + // Do the right wing! for (j = 0; (filterindex2 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)) < RESAMPLER_FILTER_SIZE; j++) { const int filt_ind = filterindex2 + j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING; const int srcframe = srcindex + 1 + j; - /* !!! FIXME: we can bubble this conditional out of here by doing a post loop. */ + // !!! FIXME: we can bubble this conditional out of here by doing a post loop. const float insample = (srcframe >= inframes) ? rpadding[((srcframe - inframes) * chans) + chan] : inbuf[(srcframe * chans) + chan]; outsample += (float) (insample * (ResamplerFilter[filt_ind] + (interpolation2 * ResamplerFilterDifference[filt_ind]))); } @@ -154,25 +154,25 @@ static void ResampleAudio(const int chans, const int inrate, const int outrate, */ #ifdef SDL_SSE3_INTRINSICS -/* Convert from stereo to mono. Average left and right. */ +// Convert from stereo to mono. Average left and right. static void SDL_TARGETING("sse3") SDL_ConvertStereoToMono_SSE3(float *dst, const float *src, int num_frames) { + LOG_DEBUG_AUDIO_CONVERT("stereo", "mono (using SSE3)"); + const __m128 divby2 = _mm_set1_ps(0.5f); int i = num_frames; - LOG_DEBUG_AUDIO_CONVERT("stereo", "mono (using SSE3)"); - /* Do SSE blocks as long as we have 16 bytes available. Just use unaligned load/stores, if the memory at runtime is aligned it'll be just as fast on modern processors */ - while (i >= 4) { /* 4 * float32 */ + while (i >= 4) { // 4 * float32 _mm_storeu_ps(dst, _mm_mul_ps(_mm_hadd_ps(_mm_loadu_ps(src), _mm_loadu_ps(src + 4)), divby2)); i -= 4; src += 8; dst += 4; } - /* Finish off any leftovers with scalar operations. */ + // Finish off any leftovers with scalar operations. while (i) { *dst = (src[0] + src[1]) * 0.5f; dst++; @@ -183,34 +183,33 @@ static void SDL_TARGETING("sse3") SDL_ConvertStereoToMono_SSE3(float *dst, const #endif #ifdef SDL_SSE_INTRINSICS -/* Convert from mono to stereo. Duplicate to stereo left and right. */ +// Convert from mono to stereo. Duplicate to stereo left and right. static void SDL_TARGETING("sse") SDL_ConvertMonoToStereo_SSE(float *dst, const float *src, int num_frames) { - int i = num_frames; + LOG_DEBUG_AUDIO_CONVERT("mono", "stereo (using SSE)"); - /* convert backwards, since output is growing in-place. */ + // convert backwards, since output is growing in-place. src += (num_frames-4) * 1; dst += (num_frames-4) * 2; - LOG_DEBUG_AUDIO_CONVERT("mono", "stereo (using SSE)"); - /* Do SSE blocks as long as we have 16 bytes available. Just use unaligned load/stores, if the memory at runtime is aligned it'll be just as fast on modern processors */ - /* convert backwards, since output is growing in-place. */ - while (i >= 4) { /* 4 * float32 */ - const __m128 input = _mm_loadu_ps(src); /* A B C D */ - _mm_storeu_ps(dst, _mm_unpacklo_ps(input, input)); /* A A B B */ - _mm_storeu_ps(dst + 4, _mm_unpackhi_ps(input, input)); /* C C D D */ + // convert backwards, since output is growing in-place. + int i = num_frames; + while (i >= 4) { // 4 * float32 + const __m128 input = _mm_loadu_ps(src); // A B C D + _mm_storeu_ps(dst, _mm_unpacklo_ps(input, input)); // A A B B + _mm_storeu_ps(dst + 4, _mm_unpackhi_ps(input, input)); // C C D D i -= 4; src -= 4; dst -= 8; } - /* Finish off any leftovers with scalar operations. */ + // Finish off any leftovers with scalar operations. src += 3; - dst += 6; /* adjust for smaller buffers. */ - while (i) { /* convert backwards, since output is growing in-place. */ + dst += 6; // adjust for smaller buffers. + while (i) { // convert backwards, since output is growing in-place. const float srcFC = src[0]; dst[1] /* FR */ = srcFC; dst[0] /* FL */ = srcFC; @@ -221,14 +220,12 @@ static void SDL_TARGETING("sse") SDL_ConvertMonoToStereo_SSE(float *dst, const f } #endif -/* Include the autogenerated channel converters... */ +// Include the autogenerated channel converters... #include "SDL_audio_channel_converters.h" static void AudioConvertByteswap(void *dst, const void *src, int num_samples, int bitsize) { - int i; - #if DEBUG_AUDIO_CONVERT SDL_Log("SDL_AUDIO_CONVERT: Converting %d-bit byte order", bitsize); #endif @@ -238,7 +235,7 @@ static void AudioConvertByteswap(void *dst, const void *src, int num_samples, in case b: { \ const Uint##b *tsrc = (const Uint##b *)src; \ Uint##b *tdst = (Uint##b *)dst; \ - for (i = 0; i < num_samples; i++) { \ + for (int i = 0; i < num_samples; i++) { \ tdst[i] = SDL_Swap##b(tsrc[i]); \ } \ break; \ @@ -258,28 +255,28 @@ static void AudioConvertByteswap(void *dst, const void *src, int num_samples, in static void AudioConvertToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt) { - SDL_assert( (SDL_AUDIO_BITSIZE(src_fmt) <= 8) || ((SDL_AUDIO_ISBIGENDIAN(src_fmt) == 0) == (SDL_BYTEORDER == SDL_LIL_ENDIAN)) ); /* This only deals with native byte order. */ + SDL_assert( (SDL_AUDIO_BITSIZE(src_fmt) <= 8) || ((SDL_AUDIO_ISBIGENDIAN(src_fmt) == 0) == (SDL_BYTEORDER == SDL_LIL_ENDIAN)) ); // This only deals with native byte order. switch (src_fmt & ~SDL_AUDIO_MASK_ENDIAN) { case SDL_AUDIO_S8: SDL_Convert_S8_to_F32(dst, (const Sint8 *) src, num_samples); break; case SDL_AUDIO_U8: SDL_Convert_U8_to_F32(dst, (const Uint8 *) src, num_samples); break; case SDL_AUDIO_S16: SDL_Convert_S16_to_F32(dst, (const Sint16 *) src, num_samples); break; case SDL_AUDIO_S32: SDL_Convert_S32_to_F32(dst, (const Sint32 *) src, num_samples); break; - case SDL_AUDIO_F32: if (dst != src) { SDL_memcpy(dst, src, num_samples * sizeof (float)); } break; /* oh well, just pass it through. */ + case SDL_AUDIO_F32: if (dst != src) { SDL_memcpy(dst, src, num_samples * sizeof (float)); } break; // oh well, just pass it through. default: SDL_assert(!"Unexpected audio format!"); break; } } static void AudioConvertFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt) { - SDL_assert( (SDL_AUDIO_BITSIZE(dst_fmt) <= 8) || ((SDL_AUDIO_ISBIGENDIAN(dst_fmt) == 0) == (SDL_BYTEORDER == SDL_LIL_ENDIAN)) ); /* This only deals with native byte order. */ + SDL_assert( (SDL_AUDIO_BITSIZE(dst_fmt) <= 8) || ((SDL_AUDIO_ISBIGENDIAN(dst_fmt) == 0) == (SDL_BYTEORDER == SDL_LIL_ENDIAN)) ); // This only deals with native byte order. switch (dst_fmt & ~SDL_AUDIO_MASK_ENDIAN) { case SDL_AUDIO_S8: SDL_Convert_F32_to_S8((Sint8 *) dst, src, num_samples); break; case SDL_AUDIO_U8: SDL_Convert_F32_to_U8((Uint8 *) dst, src, num_samples); break; case SDL_AUDIO_S16: SDL_Convert_F32_to_S16((Sint16 *) dst, src, num_samples); break; case SDL_AUDIO_S32: SDL_Convert_F32_to_S32((Sint32 *) dst, src, num_samples); break; - case SDL_AUDIO_F32: if (dst != src) { SDL_memcpy(dst, src, num_samples * sizeof (float)); } break; /* oh well, just pass it through. */ + case SDL_AUDIO_F32: if (dst != src) { SDL_memcpy(dst, src, num_samples * sizeof (float)); } break; // oh well, just pass it through. default: SDL_assert(!"Unexpected audio format!"); break; } } @@ -295,13 +292,13 @@ static SDL_bool SDL_IsSupportedAudioFormat(const SDL_AudioFormat fmt) case SDL_AUDIO_S32MSB: case SDL_AUDIO_F32LSB: case SDL_AUDIO_F32MSB: - return SDL_TRUE; /* supported. */ + return SDL_TRUE; // supported. default: break; } - return SDL_FALSE; /* unsupported. */ + return SDL_FALSE; // unsupported. } static SDL_bool SDL_IsSupportedChannelCount(const int channels) @@ -320,8 +317,6 @@ static SDL_bool SDL_IsSupportedChannelCount(const int channels) static void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, int src_channels, void *dst, SDL_AudioFormat dst_format, int dst_channels) { - const int dst_bitsize = (int) SDL_AUDIO_BITSIZE(dst_format); - const int src_bitsize = (int) SDL_AUDIO_BITSIZE(src_format); SDL_assert(src != NULL); SDL_assert(dst != NULL); SDL_assert(SDL_IsSupportedAudioFormat(src_format)); @@ -329,13 +324,16 @@ static void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_fo SDL_assert(SDL_IsSupportedChannelCount(src_channels)); SDL_assert(SDL_IsSupportedChannelCount(dst_channels)); + if (!num_frames) { + return; // no data to convert, quit. + } + #if DEBUG_AUDIO_CONVERT SDL_Log("SDL_AUDIO_CONVERT: Convert format %04x->%04x, channels %u->%u", src_format, dst_format, src_channels, dst_channels); #endif - if (!num_frames) { - return; /* no data to convert, quit. */ - } + const int dst_bitsize = (int) SDL_AUDIO_BITSIZE(dst_format); + const int src_bitsize = (int) SDL_AUDIO_BITSIZE(src_format); /* Type conversion goes like this now: - byteswap to CPU native format first if necessary. @@ -351,55 +349,55 @@ static void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_fo (script-generated) custom converters for every data type and it was a bloat on SDL compile times and final library size. */ - /* see if we can skip float conversion entirely. */ + // see if we can skip float conversion entirely. if (src_channels == dst_channels) { if (src_format == dst_format) { - /* nothing to do, we're already in the right format, just copy it over if necessary. */ + // nothing to do, we're already in the right format, just copy it over if necessary. if (src != dst) { SDL_memcpy(dst, src, num_frames * src_channels * (dst_bitsize / 8)); } return; } - /* just a byteswap needed? */ + // just a byteswap needed? if ((src_format & ~SDL_AUDIO_MASK_ENDIAN) == (dst_format & ~SDL_AUDIO_MASK_ENDIAN)) { if (src_bitsize == 8) { if (src != dst) { SDL_memcpy(dst, src, num_frames * src_channels * (dst_bitsize / 8)); } - return; /* nothing to do, it's a 1-byte format. */ + return; // nothing to do, it's a 1-byte format. } AudioConvertByteswap(dst, src, num_frames * src_channels, src_bitsize); - return; /* all done. */ + return; // all done. } } - /* make sure we're in native byte order. */ + // make sure we're in native byte order. if ((SDL_AUDIO_ISBIGENDIAN(src_format) != 0) == (SDL_BYTEORDER == SDL_LIL_ENDIAN) && (src_bitsize > 8)) { AudioConvertByteswap(dst, src, num_frames * src_channels, src_bitsize); - src = dst; /* we've written to dst, future work will convert in-place. */ + src = dst; // we've written to dst, future work will convert in-place. } - /* get us to float format. */ + // get us to float format. if (!SDL_AUDIO_ISFLOAT(src_format)) { AudioConvertToFloat((float *) dst, src, num_frames * src_channels, src_format); - src = dst; /* we've written to dst, future work will convert in-place. */ + src = dst; // we've written to dst, future work will convert in-place. } - /* Channel conversion */ + // Channel conversion if (src_channels != dst_channels) { SDL_AudioChannelConverter channel_converter; SDL_AudioChannelConverter override = NULL; - /* SDL_IsSupportedChannelCount should have caught these asserts, or we added a new format and forgot to update the table. */ + // SDL_IsSupportedChannelCount should have caught these asserts, or we added a new format and forgot to update the table. SDL_assert(src_channels <= SDL_arraysize(channel_converters)); SDL_assert(dst_channels <= SDL_arraysize(channel_converters[0])); channel_converter = channel_converters[src_channels - 1][dst_channels - 1]; SDL_assert(channel_converter != NULL); - /* swap in some SIMD versions for a few of these. */ + // swap in some SIMD versions for a few of these. if (channel_converter == SDL_ConvertStereoToMono) { #ifdef SDL_SSE3_INTRINSICS if (!override && SDL_HasSSE3()) { override = SDL_ConvertStereoToMono_SSE3; } @@ -414,38 +412,38 @@ static void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_fo channel_converter = override; } channel_converter((float *) dst, (float *) src, num_frames); - src = dst; /* we've written to dst, future work will convert in-place. */ + src = dst; // we've written to dst, future work will convert in-place. } - /* Resampling is not done in here. SDL_AudioStream handles that. */ + // Resampling is not done in here. SDL_AudioStream handles that. - /* Move to final data type. */ + // Move to final data type. if (!SDL_AUDIO_ISFLOAT(dst_format)) { AudioConvertFromFloat(dst, (float *) src, num_frames * dst_channels, dst_format); - src = dst; /* we've written to dst, future work will convert in-place. */ + src = dst; // we've written to dst, future work will convert in-place. } - /* make sure we're in final byte order. */ + // make sure we're in final byte order. if ((SDL_AUDIO_ISBIGENDIAN(dst_format) != 0) == (SDL_BYTEORDER == SDL_LIL_ENDIAN) && (dst_bitsize > 8)) { AudioConvertByteswap(dst, src, num_frames * dst_channels, dst_bitsize); - src = dst; /* we've written to dst, future work will convert in-place. */ + src = dst; // we've written to dst, future work will convert in-place. } - SDL_assert(src == dst); /* if we got here, we _had_ to have done _something_. Otherwise, we should have memcpy'd! */ + SDL_assert(src == dst); // if we got here, we _had_ to have done _something_. Otherwise, we should have memcpy'd! } -/* figure out the largest thing we might need for ConvertAudio, which might grow data in-place. */ +// figure out the largest thing we might need for ConvertAudio, which might grow data in-place. static int CalculateMaxSampleFrameSize(SDL_AudioFormat src_format, int src_channels, SDL_AudioFormat dst_format, int dst_channels) { const int src_format_size = SDL_AUDIO_BITSIZE(src_format) / 8; const int dst_format_size = SDL_AUDIO_BITSIZE(dst_format) / 8; const int max_app_format_size = SDL_max(src_format_size, dst_format_size); - const int max_format_size = SDL_max(max_app_format_size, sizeof (float)); /* ConvertAudio converts to float internally. */ + const int max_format_size = SDL_max(max_app_format_size, sizeof (float)); // ConvertAudio converts to float internally. const int max_channels = SDL_max(src_channels, dst_channels); return max_format_size * max_channels; } -/* this assumes you're holding the stream's lock (or are still creating the stream). */ +// this assumes you're holding the stream's lock (or are still creating the stream). static int SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec) { /* If increasing channels, do it after resampling, since we'd just @@ -474,14 +472,15 @@ static int SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *sr Uint8 *future_buffer = stream->future_buffer; float *padding; - /* do all the things that can fail upfront, so we can just return an error without changing the stream if anything goes wrong. */ + // do all the things that can fail upfront, so we can just return an error without changing the stream if anything goes wrong. - /* set up for (possibly new) conversions */ + // set up for (possibly new) conversions - /* grow the padding buffers if necessary; these buffer sizes change if sample rate or source channel count is adjusted. */ - /* (we can replace these buffers in `stream` now even if we abandon this function when a later allocation fails, because it's safe for these buffers to be overallocated and their contents don't matter.) */ + /* grow the padding buffers if necessary; these buffer sizes change if sample rate or source channel count is adjusted. + (we can replace these buffers in `stream` now even if we abandon this function when a later allocation fails, because + it's safe for these buffers to be overallocated and their contents don't matter.) */ if (stream->resampler_padding_allocation < resampler_padding_allocation) { - /* left_padding and right_padding are just scratch buffers, so we don't need to preserve existing contents. */ + // left_padding and right_padding are just scratch buffers, so we don't need to preserve existing contents. padding = (float *) SDL_aligned_alloc(SDL_SIMDGetAlignment(), resampler_padding_allocation); if (!padding) { return SDL_OutOfMemory(); @@ -499,7 +498,7 @@ static int SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *sr stream->resampler_padding_allocation = resampler_padding_allocation; } - /* grow the history buffer if necessary; often times this won't be, as it already buffers more than immediately necessary in case of a dramatic downsample. */ + // grow the history buffer if necessary; often times this won't be, as it already buffers more than immediately necessary in case of a dramatic downsample. if (stream->history_buffer_allocation < history_buffer_allocation) { history_buffer = (Uint8 *) SDL_aligned_alloc(SDL_SIMDGetAlignment(), history_buffer_allocation); if (!history_buffer) { @@ -507,7 +506,7 @@ static int SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *sr } } - /* grow the future buffer if necessary; the buffer size changes if sample rate is adjusted. */ + // grow the future buffer if necessary; the buffer size changes if sample rate is adjusted. if (stream->future_buffer_allocation < future_buffer_allocation) { future_buffer = (Uint8 *) SDL_aligned_alloc(SDL_SIMDGetAlignment(), future_buffer_allocation); if (!future_buffer) { @@ -518,9 +517,9 @@ static int SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *sr } } - /* okay, we've done all the things that can fail, now we can change stream state. */ + // okay, we've done all the things that can fail, now we can change stream state. - /* copy to new buffers and/or convert data; ConvertAudio will do a simple memcpy if format matches, and nothing at all if the buffer hasn't changed */ + // copy to new buffers and/or convert data; ConvertAudio will do a simple memcpy if format matches, and nothing at all if the buffer hasn't changed if (stream->future_buffer) { ConvertAudio(stream->future_buffer_filled_frames, stream->future_buffer, stream->src_spec.format, stream->src_spec.channels, future_buffer, src_format, src_channels); } else if (future_buffer != NULL) { @@ -532,7 +531,7 @@ static int SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *sr ConvertAudio(history_buffer_frames, stream->history_buffer, stream->src_spec.format, stream->src_spec.channels, history_buffer, src_format, src_channels); } else { ConvertAudio(prev_history_buffer_frames, stream->history_buffer, stream->src_spec.format, stream->src_spec.channels, history_buffer + ((history_buffer_frames - prev_history_buffer_frames) * src_sample_frame_size), src_format, src_channels); - SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(src_format), (history_buffer_frames - prev_history_buffer_frames) * src_sample_frame_size); /* silence oldest history samples. */ + SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(src_format), (history_buffer_frames - prev_history_buffer_frames) * src_sample_frame_size); // silence oldest history samples. } } else if (history_buffer != NULL) { SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(src_format), history_buffer_allocation); @@ -570,10 +569,7 @@ static int SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *sr SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec) { - int packetlen = 4096; /* !!! FIXME: good enough for now. */ - SDL_AudioStream *retval; - - /* !!! FIXME: fail if audio isn't initialized? */ + // !!! FIXME: fail if audio isn't initialized if (!src_spec) { SDL_InvalidParamError("src_spec"); @@ -607,23 +603,24 @@ SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_ return NULL; } - retval = (SDL_AudioStream *)SDL_calloc(1, sizeof(SDL_AudioStream)); + SDL_AudioStream *retval = (SDL_AudioStream *)SDL_calloc(1, sizeof(SDL_AudioStream)); if (retval == NULL) { SDL_OutOfMemory(); return NULL; } + const int packetlen = 4096; // !!! FIXME: good enough for now. retval->queue = SDL_CreateDataQueue(packetlen, (size_t)packetlen * 2); if (!retval->queue) { SDL_DestroyAudioStream(retval); - return NULL; /* SDL_CreateDataQueue should have called SDL_SetError. */ + return NULL; // SDL_CreateDataQueue should have called SDL_SetError. } retval->lock = SDL_GetDataQueueMutex(retval->queue); SDL_assert(retval->lock != NULL); - /* Make sure we've chosen audio conversion functions (SIMD, scalar, etc.) */ - SDL_ChooseAudioConverters(); /* !!! FIXME: let's do this during SDL_Init? */ + // Make sure we've chosen audio conversion functions (SIMD, scalar, etc.) + SDL_ChooseAudioConverters(); // !!! FIXME: let's do this during SDL_Init retval->src_sample_frame_size = (SDL_AUDIO_BITSIZE(src_spec->format) / 8) * src_spec->channels; retval->packetlen = packetlen; @@ -689,8 +686,6 @@ int SDL_GetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioSpec *src_spec, S int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec) { - int retval; - if (!stream) { return SDL_InvalidParamError("stream"); } @@ -720,7 +715,7 @@ int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_s } SDL_LockMutex(stream->lock); - retval = SetAudioStreamFormat(stream, src_spec ? src_spec : &stream->src_spec, dst_spec ? dst_spec : &stream->dst_spec); + const int retval = SetAudioStreamFormat(stream, src_spec ? src_spec : &stream->src_spec, dst_spec ? dst_spec : &stream->dst_spec); SDL_UnlockMutex(stream->lock); return retval; @@ -728,8 +723,6 @@ int SDL_SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_s int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) { - int retval; - #if DEBUG_AUDIOSTREAM SDL_Log("AUDIOSTREAM: wants to put %d preconverted bytes", len); #endif @@ -739,7 +732,7 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) } else if (buf == NULL) { return SDL_InvalidParamError("buf"); } else if (len == 0) { - return 0; /* nothing to do. */ + return 0; // nothing to do. } SDL_LockMutex(stream->lock); @@ -751,8 +744,8 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) return SDL_SetError("Can't add partial sample frames"); } - /* just queue the data, we convert/resample when dequeueing. */ - retval = SDL_WriteToDataQueue(stream->queue, buf, len); + // just queue the data, we convert/resample when dequeueing. + const int retval = SDL_WriteToDataQueue(stream->queue, buf, len); stream->flushed = SDL_FALSE; if (stream->put_callback) { @@ -782,16 +775,14 @@ int SDL_FlushAudioStream(SDL_AudioStream *stream) The returned buffer is aligned/padded for use with SIMD instructions. */ static Uint8 *EnsureStreamWorkBufferSize(SDL_AudioStream *stream, size_t newlen) { - Uint8 *ptr; - if (stream->work_buffer_allocation >= newlen) { return stream->work_buffer; } - ptr = (Uint8 *) SDL_aligned_alloc(SDL_SIMDGetAlignment(), newlen); + Uint8 *ptr = (Uint8 *) SDL_aligned_alloc(SDL_SIMDGetAlignment(), newlen); if (ptr == NULL) { SDL_OutOfMemory(); - return NULL; /* previous work buffer is still valid! */ + return NULL; // previous work buffer is still valid! } SDL_aligned_free(stream->work_buffer); @@ -802,35 +793,34 @@ static Uint8 *EnsureStreamWorkBufferSize(SDL_AudioStream *stream, size_t newlen) static int CalculateAudioStreamWorkBufSize(const SDL_AudioStream *stream, int len) { - int workbuf_frames = len / stream->dst_sample_frame_size; /* start with requested sample frames */ int workbuflen = len; - int inputlen; + int workbuf_frames = len / stream->dst_sample_frame_size; // start with requested sample frames + int inputlen = workbuf_frames * stream->max_sample_frame_size; - inputlen = workbuf_frames * stream->max_sample_frame_size; if (inputlen > workbuflen) { workbuflen = inputlen; } if (stream->dst_spec.freq != stream->src_spec.freq) { - /* calculate requested sample frames needed before resampling. Use a Uint64 so the multiplication doesn't overflow. */ + // calculate requested sample frames needed before resampling. Use a Uint64 so the multiplication doesn't overflow. const int input_frames = ((int) ((((Uint64) workbuf_frames) * stream->src_spec.freq) / stream->dst_spec.freq)); inputlen = input_frames * stream->max_sample_frame_size; if (inputlen > workbuflen) { workbuflen = inputlen; } - /* Calculate space needed to move to format/channels used for resampling stage. */ + // Calculate space needed to move to format/channels used for resampling stage. inputlen = input_frames * stream->pre_resample_channels * sizeof (float); if (inputlen > workbuflen) { workbuflen = inputlen; } - /* Calculate space needed after resample (which lives in a second copy in the same buffer). */ + // Calculate space needed after resample (which lives in a second copy in the same buffer). workbuflen += workbuf_frames * stream->pre_resample_channels * sizeof (float); } return workbuflen; } -/* You must hold stream->lock and validate your parameters before calling this! */ +// You must hold stream->lock and validate your parameters before calling this! static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int len) { const int max_available = SDL_GetAudioStreamAvailable(stream); @@ -868,29 +858,29 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le output_frames = len / dst_sample_frame_size; if (output_frames == 0) { - return 0; /* nothing to do. */ + return 0; // nothing to do. } - /* !!! FIXME: this could be less aggressive about allocation, if we decide the necessary size at each stage and select the maximum required. */ + // !!! FIXME: this could be less aggressive about allocation, if we decide the necessary size at each stage and select the maximum required. workbuflen = CalculateAudioStreamWorkBufSize(stream, len); workbuf = EnsureStreamWorkBufferSize(stream, workbuflen); if (!workbuf) { return -1; } - /* figure out how much data we need to fulfill the request. */ - input_frames = len / dst_sample_frame_size; /* total sample frames caller wants */ + // figure out how much data we need to fulfill the request. + input_frames = len / dst_sample_frame_size; // total sample frames caller wants if (dst_rate != src_rate) { - /* calculate requested sample frames needed before resampling. Use a Uint64 so the multiplication doesn't overflow. */ + // calculate requested sample frames needed before resampling. Use a Uint64 so the multiplication doesn't overflow. input_frames = (int) ((((Uint64) input_frames) * src_rate) / dst_rate); if (input_frames == 0) { - return 0; /* 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. */ + return 0; // 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. } } - workbuf_frames = 0; /* no input has been moved to the workbuf yet. */ + workbuf_frames = 0; // no input has been moved to the workbuf yet. - /* move any previous right-padding to the start of the buffer to convert, as those would have been the next samples from the queue ("the future buffer"). */ + // move any previous right-padding to the start of the buffer to convert, as those would have been the next samples from the queue ("the future buffer"). if (future_buffer_filled_frames) { const int cpyframes = SDL_min(input_frames, future_buffer_filled_frames); const int cpy = cpyframes * src_sample_frame_size; @@ -898,18 +888,18 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le workbuf_frames = cpyframes; if (future_buffer_filled_frames == cpyframes) { stream->future_buffer_filled_frames = future_buffer_filled_frames = 0; - } else { /* slide any remaining bytes to the start of the padding buffer, if this was a small request. */ + } else { // slide any remaining bytes to the start of the padding buffer, if this was a small request. SDL_memmove(future_buffer, future_buffer + cpy, (future_buffer_filled_frames - cpyframes) * src_sample_frame_size); future_buffer_filled_frames -= cpyframes; stream->future_buffer_filled_frames = future_buffer_filled_frames; } } - /* we either consumed all the future buffer or we don't need to read more from the queue. If this assert fails, we will have data in the wrong order in the future buffer when we top it off. */ + // we either consumed all the future buffer or we don't need to read more from the queue. If this assert fails, we will have data in the wrong order in the future buffer when we top it off. SDL_assert((future_buffer_filled_frames == 0) || (workbuf_frames == input_frames)); - /* now read unconverted data from the queue into the work buffer to fulfill the request. */ - if (input_frames > workbuf_frames) { /* need more data? */ + // now read unconverted data from the queue into the work buffer to fulfill the request. + if (input_frames > workbuf_frames) { // need more data? const int workbufpos = workbuf_frames * src_sample_frame_size; const int request_bytes = (input_frames - workbuf_frames) * src_sample_frame_size; int read_frames; @@ -917,10 +907,10 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le br = (int) SDL_ReadFromDataQueue(stream->queue, workbuf + workbufpos, request_bytes); read_frames = br / src_sample_frame_size; workbuf_frames += read_frames; - input_frames = workbuf_frames; /* what we actually have to work with */ + input_frames = workbuf_frames; // what we actually have to work with } - /* for some resamples, we need to fill up the future buffer, too, to use as right padding. */ + // for some resamples, we need to fill up the future buffer, too, to use as right padding. if (future_buffer_filled_frames < resampler_padding_frames) { const int cpyframes = resampler_padding_frames - future_buffer_filled_frames; const int cpy = cpyframes * src_sample_frame_size; @@ -929,24 +919,24 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le brframes = br / src_sample_frame_size; future_buffer_filled_frames += brframes; stream->future_buffer_filled_frames = future_buffer_filled_frames; - if (br < cpy) { /* we couldn't fill the future buffer with enough padding! */ - if (stream->flushed) { /* that's okay, we're flushing, just silence the still-needed padding. */ + if (br < cpy) { // we couldn't fill the future buffer with enough padding! + if (stream->flushed) { // that's okay, we're flushing, just silence the still-needed padding. SDL_memset(future_buffer + (future_buffer_filled_frames * src_sample_frame_size), SDL_GetSilenceValueForFormat(src_format), cpy - br); - } else { /* Drastic measures: steal from the work buffer! */ + } else { // Drastic measures: steal from the work buffer! const int stealcpyframes = SDL_min(workbuf_frames, cpyframes - brframes); const int stealcpy = stealcpyframes * src_sample_frame_size; SDL_memcpy(future_buffer + (future_buffer_filled_frames * src_sample_frame_size), workbuf + ((workbuf_frames - stealcpyframes) * src_sample_frame_size), stealcpy); workbuf_frames -= stealcpyframes; - input_frames = workbuf_frames; /* what we actually have to work with, now */ + input_frames = workbuf_frames; // what we actually have to work with, now future_buffer_filled_frames += stealcpyframes; SDL_assert(future_buffer_filled_frames <= resampler_padding_frames); } } } - /* Now, the work buffer has enough sample frames to fulfill the request (or all the frames available if not), and the future buffer is loaded if necessary. */ + // Now, the work buffer has enough sample frames to fulfill the request (or all the frames available if not), and the future buffer is loaded if necessary. - /* If we have resampling padding buffers, convert the current history and future buffers to float32. */ + // If we have resampling padding buffers, convert the current history and future buffers to float32. if (resampler_padding_frames > 0) { const int history_buffer_bytes = history_buffer_frames * src_sample_frame_size; const int resampler_padding_bytes = resampler_padding_frames * src_sample_frame_size; @@ -956,7 +946,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le ConvertAudio(resampler_padding_frames, future_buffer, src_format, src_channels, stream->right_padding, SDL_AUDIO_F32, pre_resample_channels); } - /* slide in new data to the history buffer, shuffling out the oldest, for the next run, since we've already updated left_padding with current data. */ + // slide in new data to the history buffer, shuffling out the oldest, for the next run, since we've already updated left_padding with current data. { const int history_buffer_bytes = history_buffer_frames * src_sample_frame_size; const int request_bytes = input_frames * src_sample_frame_size; @@ -964,15 +954,15 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le const int preserve_bytes = history_buffer_bytes - request_bytes; SDL_memmove(history_buffer, history_buffer + request_bytes, preserve_bytes); SDL_memcpy(history_buffer + preserve_bytes, workbuf, request_bytes); - } else { /* are we just replacing the whole thing instead? */ + } else { // are we just replacing the whole thing instead? SDL_memcpy(history_buffer, (workbuf + request_bytes) - history_buffer_bytes, history_buffer_bytes); } } - /* Not resampling? It's an easy conversion (and maybe not even that!) */ + // Not resampling? It's an easy conversion (and maybe not even that!) if (src_rate == dst_rate) { SDL_assert(resampler_padding_frames == 0); - /* see if we can do the conversion in-place (will fit in `buf` while in-progress), or if we need to do it in the workbuf and copy it over */ + // see if we can do the conversion in-place (will fit in `buf` while in-progress), or if we need to do it in the workbuf and copy it over if (max_sample_frame_size <= dst_sample_frame_size) { ConvertAudio(input_frames, workbuf, src_format, src_channels, buf, dst_format, dst_channels); } else { @@ -982,14 +972,14 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le return input_frames * dst_sample_frame_size; } - /* Resampling! get the work buffer to float32 format, etc, in-place. */ + // Resampling! get the work buffer to float32 format, etc, in-place. ConvertAudio(input_frames, workbuf, src_format, src_channels, workbuf, SDL_AUDIO_F32, pre_resample_channels); if ((dst_format == SDL_AUDIO_F32) && (dst_channels == pre_resample_channels)) { resample_outbuf = (float *) buf; } else { const int output_bytes = output_frames * pre_resample_channels * sizeof (float); - resample_outbuf = (float *) ((workbuf + stream->work_buffer_allocation) - output_bytes); /* do at the end of the buffer so we have room for final convert at front. */ + resample_outbuf = (float *) ((workbuf + stream->work_buffer_allocation) - output_bytes); // do at the end of the buffer so we have room for final convert at front. } ResampleAudio(pre_resample_channels, src_rate, dst_rate, @@ -997,8 +987,8 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le (const float *) workbuf, input_frames, resample_outbuf, output_frames); - /* Get us to the final format! */ - /* see if we can do the conversion in-place (will fit in `buf` while in-progress), or if we need to do it in the workbuf and copy it over */ + // Get us to the final format! + // see if we can do the conversion in-place (will fit in `buf` while in-progress), or if we need to do it in the workbuf and copy it over if (max_sample_frame_size <= dst_sample_frame_size) { ConvertAudio(output_frames, resample_outbuf, SDL_AUDIO_F32, pre_resample_channels, buf, dst_format, dst_channels); } else { @@ -1009,11 +999,10 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le return (int) (output_frames * dst_sample_frame_size); } -/* get converted/resampled data from the stream */ +// get converted/resampled data from the stream int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) { Uint8 *buf = (Uint8 *) voidbuf; - int retval = 0; #if DEBUG_AUDIOSTREAM SDL_Log("AUDIOSTREAM: want to get %d converted bytes", len); @@ -1026,20 +1015,20 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) } else if (len < 0) { return SDL_InvalidParamError("len"); } else if (len == 0) { - return 0; /* nothing to do. */ + return 0; // nothing to do. } SDL_LockMutex(stream->lock); - len -= len % stream->dst_sample_frame_size; /* chop off any fractional sample frame. */ + len -= len % stream->dst_sample_frame_size; // chop off any fractional sample frame. // give the callback a chance to fill in more stream data if it wants. if (stream->get_callback) { - int approx_request = len / stream->dst_sample_frame_size; /* start with sample frames desired */ + int approx_request = len / stream->dst_sample_frame_size; // start with sample frames desired if (stream->src_spec.freq != stream->dst_spec.freq) { - /* calculate difference in dataset size after resampling. Use a Uint64 so the multiplication doesn't overflow. */ + // calculate difference in dataset size after resampling. Use a Uint64 so the multiplication doesn't overflow. approx_request = (size_t) ((((Uint64) approx_request) * stream->src_spec.freq) / stream->dst_spec.freq); - if (!stream->flushed) { /* do we need to fill the future buffer to accomodate this, too? */ + if (!stream->flushed) { // do we need to fill the future buffer to accomodate this, too? approx_request += stream->future_buffer_filled_frames - stream->resampler_padding_frames; } } @@ -1050,9 +1039,10 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) stream->get_callback(stream, approx_request, stream->get_callback_userdata); } - /* we convert in chunks, so we don't end up allocating a massive work buffer, etc. */ - while (len > 0) { /* didn't ask for a whole sample frame, nothing to do */ - const int chunk_size = 1024 * 1024; /* !!! FIXME: a megabyte might be overly-aggressive. */ + // we convert in chunks, so we don't end up allocating a massive work buffer, etc. + int retval = 0; + while (len > 0) { // didn't ask for a whole sample frame, nothing to do + const int chunk_size = 1024 * 1024; // !!! FIXME: a megabyte might be overly-aggressive. const int rc = GetAudioStreamDataInternal(stream, buf, SDL_min(len, chunk_size)); if (rc == -1) { @@ -1085,41 +1075,39 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) return retval; } -/* number of converted/resampled bytes available */ +// number of converted/resampled bytes available int SDL_GetAudioStreamAvailable(SDL_AudioStream *stream) { - const int max_int = 0x7FFFFFFF; /* !!! FIXME: This will blow up on weird processors. Is there an SDL_INT_MAX? */ - size_t count; - if (!stream) { return SDL_InvalidParamError("stream"); } SDL_LockMutex(stream->lock); - /* total bytes available in source format in data queue */ - count = SDL_GetDataQueueSize(stream->queue); + // total bytes available in source format in data queue + size_t count = SDL_GetDataQueueSize(stream->queue); - /* total sample frames available in data queue */ + // total sample frames available in data queue count /= stream->src_sample_frame_size; count += stream->future_buffer_filled_frames; - /* sample frames after resampling */ + // sample frames after resampling if (stream->src_spec.freq != stream->dst_spec.freq) { if (!stream->flushed) { - /* have to save some samples for padding. They aren't available until more data is added or the stream is flushed. */ + // have to save some samples for padding. They aren't available until more data is added or the stream is flushed. count = (count < ((size_t) stream->resampler_padding_frames)) ? 0 : (count - stream->resampler_padding_frames); } - /* calculate difference in dataset size after resampling. Use a Uint64 so the multiplication doesn't overflow. */ + // calculate difference in dataset size after resampling. Use a Uint64 so the multiplication doesn't overflow. count = (size_t) ((((Uint64) count) * stream->dst_spec.freq) / stream->src_spec.freq); } - /* convert from sample frames to bytes in destination format. */ + // convert from sample frames to bytes in destination format. count *= stream->dst_sample_frame_size; SDL_UnlockMutex(stream->lock); - /* if this overflows an int, just clamp it to a maximum. */ + // if this overflows an int, just clamp it to a maximum. + const int max_int = 0x7FFFFFFF; // !!! FIXME: This will blow up on weird processors. Is there an SDL_INT_MAX? return (count >= ((size_t) max_int)) ? max_int : ((int) count); } @@ -1142,7 +1130,7 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream) { if (stream) { SDL_UnbindAudioStream(stream); - /* do not destroy stream->lock! it's a copy of `stream->queue`'s mutex, so destroying the queue will handle it. */ + // do not destroy stream->lock! it's a copy of `stream->queue`'s mutex, so destroying the queue will handle it. SDL_DestroyDataQueue(stream->queue); SDL_aligned_free(stream->work_buffer); SDL_aligned_free(stream->history_buffer); @@ -1156,11 +1144,6 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream) int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len) { - int ret = -1; - SDL_AudioStream *stream = NULL; - Uint8 *dst = NULL; - int dstlen = 0; - if (dst_data) { *dst_data = NULL; } @@ -1179,7 +1162,11 @@ int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data return SDL_InvalidParamError("dst_len"); } - stream = SDL_CreateAudioStream(src_spec, dst_spec); + int retval = -1; + Uint8 *dst = NULL; + int dstlen = 0; + + SDL_AudioStream *stream = SDL_CreateAudioStream(src_spec, dst_spec); if (stream != NULL) { if ((SDL_PutAudioStreamData(stream, src_data, src_len) == 0) && (SDL_FlushAudioStream(stream) == 0)) { dstlen = SDL_GetAudioStreamAvailable(stream); @@ -1188,13 +1175,13 @@ int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data if (!dst) { SDL_OutOfMemory(); } else { - ret = (SDL_GetAudioStreamData(stream, dst, dstlen) >= 0) ? 0 : -1; + retval = (SDL_GetAudioStreamData(stream, dst, dstlen) >= 0) ? 0 : -1; } } } } - if (ret == -1) { + if (retval == -1) { SDL_free(dst); } else { *dst_data = dst; @@ -1202,6 +1189,6 @@ int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data } SDL_DestroyAudioStream(stream); - return ret; + return retval; } From fd4c9f4e1110a5ee8f374fd7b9a00c021a0aa045 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 21 Jun 2023 23:34:43 -0400 Subject: [PATCH 010/138] audio: documentation improvements. --- include/SDL3/SDL_audio.h | 104 ++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 35 deletions(-) diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 5490be5c27..7b5239643b 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -19,8 +19,6 @@ 3. This notice may not be removed or altered from any source distribution. */ -/* !!! FIXME: several functions in here need Doxygen comments. */ - /** * \file SDL_audio.h * @@ -164,6 +162,8 @@ typedef struct SDL_AudioSpec - You push data as you have it, and pull it when you need it - It can also function as a basic audio data queue even if you just have sound that needs to pass from one place to another. + - You can hook callbacks up to them when more data is added or + requested, to manage data on-the-fly. */ struct SDL_AudioStream; /* this is opaque to the outside world. */ typedef struct SDL_AudioStream SDL_AudioStream; @@ -194,10 +194,10 @@ typedef struct SDL_AudioStream SDL_AudioStream; * * \returns the number of built-in audio drivers. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_GetAudioDriver */ extern DECLSPEC int SDLCALL SDL_GetNumAudioDrivers(void); @@ -245,7 +245,6 @@ extern DECLSPEC const char *SDLCALL SDL_GetAudioDriver(int index); */ extern DECLSPEC const char *SDLCALL SDL_GetCurrentAudioDriver(void); - /** * Get a list of currently-connected audio output devices. * @@ -254,16 +253,20 @@ extern DECLSPEC const char *SDLCALL SDL_GetCurrentAudioDriver(void); * that record audio, like a microphone ("capture" devices), use * SDL_GetAudioCaptureDevices() instead. * + * This only returns a list of physical devices; it will not have any + * device IDs returned by SDL_OpenAudioDevice(). + * * \param count a pointer filled in with the number of devices returned * \returns a 0 terminated array of device instance IDs which should be * freed with SDL_free(), or NULL on error; call SDL_GetError() for * more details. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_OpenAudioDevice + * \sa SDL_GetAudioCaptureDevices */ extern DECLSPEC SDL_AudioDeviceID *SDLCALL SDL_GetAudioOutputDevices(int *count); @@ -275,16 +278,20 @@ extern DECLSPEC SDL_AudioDeviceID *SDLCALL SDL_GetAudioOutputDevices(int *count) * that play sound, perhaps to speakers or headphones ("output" devices), * use SDL_GetAudioOutputDevices() instead. * + * This only returns a list of physical devices; it will not have any + * device IDs returned by SDL_OpenAudioDevice(). + * * \param count a pointer filled in with the number of devices returned * \returns a 0 terminated array of device instance IDs which should be * freed with SDL_free(), or NULL on error; call SDL_GetError() for * more details. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_OpenAudioDevice + * \sa SDL_GetAudioOutputDevices */ extern DECLSPEC SDL_AudioDeviceID *SDLCALL SDL_GetAudioCaptureDevices(int *count); @@ -297,10 +304,10 @@ extern DECLSPEC SDL_AudioDeviceID *SDLCALL SDL_GetAudioCaptureDevices(int *count * \param devid the instance ID of the device to query. * \returns the name of the audio device, or NULL on error. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_GetNumAudioDevices * \sa SDL_GetDefaultAudioInfo */ @@ -351,8 +358,6 @@ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SD * unplugged so the system jumped to a new default, the user plugged * in headphones on a mobile device, etc). Unless you have a good * reason to choose a specific device, this is probably what you want. - * Requesting the default will also allow the user to specify - * preferences with hints/environment variables. * * You may request a specific format for the audio device, but there is * no promise the device will honor that request for several reasons. As @@ -368,6 +373,11 @@ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SD * libraries to open a device separately from the main app and bind its own * streams without conflicting. * + * It is also legal to open a device ID returned by a previous call to + * this function; doing so just creates another logical device on the same + * physical device. This may be useful for making logical groupings of + * audio streams. + * * This function returns the opened device ID on success. This is a new, * unique SDL_AudioDeviceID that represents a logical device. * @@ -388,10 +398,10 @@ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SD * \param spec the requested device configuration. Can be NULL to use reasonable defaults. * \returns The device ID on success, 0 on error; call SDL_GetError() for more information. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_CloseAudioDevice * \sa SDL_GetAudioDeviceFormat */ @@ -410,10 +420,10 @@ extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(SDL_AudioDeviceID * * \param devid an audio device id previously returned by SDL_OpenAudioDevice() * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_OpenAudioDevice */ extern DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID devid); @@ -444,10 +454,10 @@ extern DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID devid); * \param num_streams Number streams listed in the `streams` array. * \returns 0 on success, -1 on error; call SDL_GetError() for more information. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_BindAudioStreams * \sa SDL_UnbindAudioStreams * \sa SDL_UnbindAudioStream @@ -464,10 +474,10 @@ extern DECLSPEC int SDLCALL SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_Au * \param stream an audio stream to bind to a device. * \returns 0 on success, -1 on error; call SDL_GetError() for more information. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_BindAudioStreams * \sa SDL_UnbindAudioStreams * \sa SDL_UnbindAudioStream @@ -486,10 +496,10 @@ extern DECLSPEC int SDLCALL SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_Aud * \param streams an array of audio streams to unbind. * \param num_streams Number streams listed in the `streams` array. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_BindAudioStreams * \sa SDL_BindAudioStream * \sa SDL_UnbindAudioStream @@ -504,10 +514,10 @@ extern DECLSPEC void SDLCALL SDL_UnbindAudioStreams(SDL_AudioStream **streams, i * * \param stream an audio stream to unbind from a device. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_BindAudioStream * \sa SDL_BindAudioStreams * \sa SDL_UnbindAudioStreams @@ -597,6 +607,10 @@ extern DECLSPEC int SDLCALL SDL_SetAudioStreamFormat(SDL_AudioStream *stream, * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread, but if the + * stream has a callback set, the caller might need to manage + * extra locking. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_CreateAudioStream @@ -625,6 +639,10 @@ extern DECLSPEC int SDLCALL SDL_PutAudioStreamData(SDL_AudioStream *stream, cons * \param len The maximum number of bytes to fill * \returns the number of bytes read from the stream, or -1 on error * + * \threadsafety It is safe to call this function from any thread, but if the + * stream has a callback set, the caller might need to manage + * extra locking. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_CreateAudioStream @@ -653,6 +671,8 @@ extern DECLSPEC int SDLCALL SDL_GetAudioStreamData(SDL_AudioStream *stream, void * \param stream The audio stream to query * \returns the number of converted/resampled bytes available. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_CreateAudioStream @@ -676,6 +696,8 @@ extern DECLSPEC int SDLCALL SDL_GetAudioStreamAvailable(SDL_AudioStream *stream) * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_CreateAudioStream @@ -694,6 +716,8 @@ extern DECLSPEC int SDLCALL SDL_FlushAudioStream(SDL_AudioStream *stream); * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_CreateAudioStream @@ -728,10 +752,10 @@ extern DECLSPEC int SDLCALL SDL_ClearAudioStream(SDL_AudioStream *stream); * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_UnlockAudioStream * \sa SDL_SetAudioStreamPutCallback * \sa SDL_SetAudioStreamGetCallback @@ -748,11 +772,11 @@ extern DECLSPEC int SDLCALL SDL_LockAudioStream(SDL_AudioStream *stream); * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety You should only call this from the same thread that * previously called SDL_LockAudioStream. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_LockAudioStream * \sa SDL_SetAudioStreamPutCallback * \sa SDL_SetAudioStreamGetCallback @@ -814,10 +838,10 @@ typedef void (SDLCALL *SDL_AudioStreamRequestCallback)(SDL_AudioStream *stream, * \param userdata an opaque pointer provided to the callback for its own personal use. * \returns 0 on success, -1 on error. This only fails if `stream` is NULL. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_SetAudioStreamPutCallback */ extern DECLSPEC int SDLCALL SDL_SetAudioStreamGetCallback(SDL_AudioStream *stream, SDL_AudioStreamRequestCallback callback, void *userdata); @@ -861,10 +885,10 @@ extern DECLSPEC int SDLCALL SDL_SetAudioStreamGetCallback(SDL_AudioStream *strea * \param userdata an opaque pointer provided to the callback for its own personal use. * \returns 0 on success, -1 on error. This only fails if `stream` is NULL. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_SetAudioStreamGetCallback */ extern DECLSPEC int SDLCALL SDL_SetAudioStreamPutCallback(SDL_AudioStream *stream, SDL_AudioStreamRequestCallback callback, void *userdata); @@ -875,6 +899,8 @@ extern DECLSPEC int SDLCALL SDL_SetAudioStreamPutCallback(SDL_AudioStream *strea * * \param stream The audio stream to free * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_CreateAudioStream @@ -903,10 +929,10 @@ extern DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream); * \param spec the audio stream's input format * \returns a bound audio stream on success, ready to use. NULL on error; call SDL_GetError() for more information. * - * \since This function is available since SDL 3.0.0. - * * \threadsafety It is safe to call this function from any thread. * + * \since This function is available since SDL 3.0.0. + * * \sa SDL_BindAudioStreams * \sa SDL_UnbindAudioStreams * \sa SDL_UnbindAudioStream @@ -989,6 +1015,8 @@ extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAndBindAudioStream(SDL_AudioD * When the application is done with the data returned in * `audio_buf`, it should call SDL_free() to dispose of it. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_free @@ -1028,6 +1056,8 @@ extern DECLSPEC int SDLCALL SDL_LoadWAV_RW(SDL_RWops * src, int freesrc, * When the application is done with the data returned in * `audio_buf`, it should call SDL_free() to dispose of it. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_free @@ -1070,6 +1100,8 @@ extern DECLSPEC int SDLCALL SDL_LoadWAV(const char *path, SDL_AudioSpec * spec, * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. */ extern DECLSPEC int SDLCALL SDL_MixAudioFormat(Uint8 * dst, @@ -1101,6 +1133,8 @@ extern DECLSPEC int SDLCALL SDL_MixAudioFormat(Uint8 * dst, * \returns 0 on success or a negative error code on failure; call * SDL_GetError() for more information. * + * \threadsafety It is safe to call this function from any thread. + * * \since This function is available since SDL 3.0.0. * * \sa SDL_CreateAudioStream From 01f7b53865eb59c4e80c4f1cbd6e83743f127b85 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 22 Jun 2023 00:59:26 -0400 Subject: [PATCH 011/138] audio: Readded (logical) device pausing. --- docs/README-migration.md | 7 +-- include/SDL3/SDL_audio.h | 81 +++++++++++++++++++++++++++++++ src/audio/SDL_audio.c | 40 +++++++++++++++ src/dynapi/SDL_dynapi.sym | 3 ++ src/dynapi/SDL_dynapi_overrides.h | 3 ++ src/dynapi/SDL_dynapi_procs.h | 3 ++ 6 files changed, 134 insertions(+), 3 deletions(-) diff --git a/docs/README-migration.md b/docs/README-migration.md index ffa2a38547..c5d6131d74 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -144,9 +144,11 @@ Rather than iterating over audio devices using a device index, there is a new fu SDL_LockAudioDevice() and SDL_UnlockAudioDevice() have been removed, since there is no callback in another thread to protect. Internally, SDL's audio subsystem and SDL_AudioStream maintain their own locks internally, so audio streams are safe to use from any thread. -SDL_PauseAudioDevice() has been removed; unbinding an audio stream from a device with SDL_UnbindAudioStream() will leave the stream still containing any unconsumed data, effectively pausing it until rebound with SDL_BindAudioStream() again. Devices act like they are "paused" after open, like SDL2, until a stream is bound to it. +SDL_PauseAudioDevice() no longer takes a second argument; it always pauses the device. To unpause, use SDL_UnpauseAudioDevice(). -SDL_GetAudioDeviceStatus() has been removed; there is no more concept of "pausing" a device, just whether streams are bound, so please keep track of your audio streams! +Audio devices, opened by SDL_OpenAudioDevice(), no longer start in a paused state, as they don't begin processing audio until a stream is bound. + +SDL_GetAudioDeviceStatus() has been removed; there is now SDL_IsAudioDevicePaused(). SDL_QueueAudio(), SDL_DequeueAudio, and SDL_ClearQueuedAudio and SDL_GetQueuedAudioSize() have been removed; an SDL_AudioStream bound to a device provides the exact same functionality. @@ -230,7 +232,6 @@ The following functions have been removed: * SDL_OpenAudio() * SDL_CloseAudio() * SDL_PauseAudio() -* SDL_PauseAudioDevice * SDL_GetAudioStatus() * SDL_GetAudioDeviceStatus() * SDL_LockAudio() diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 7b5239643b..6962b8f3de 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -407,6 +407,87 @@ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SD */ extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec); +/** + * Use this function to pause audio playback on a specified device. + * + * This function pauses audio processing for a given device. Any bound + * audio streams will not progress, and no audio will be generated. + * Pausing one device does not prevent other unpaused devices from running. + * + * Unlike in SDL2, audio devices start in an _unpaused_ state, since an app + * has to bind a stream before any audio will flow. Pausing a paused + * device is a legal no-op. + * + * Pausing a device can be useful to halt all audio without unbinding all + * the audio streams. This might be useful while a game is paused, or + * a level is loading, etc. + * + * Physical devices can not be paused or unpaused, only logical devices + * created through SDL_OpenAudioDevice() can be. + * + * \param dev a device opened by SDL_OpenAudioDevice() + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_UnpauseAudioDevice + * \sa SDL_IsAudioDevicePaused + */ +extern DECLSPEC int SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID dev); + +/** + * Use this function to unpause audio playback on a specified device. + * + * This function unpauses audio processing for a given device that has + * previously been paused with SDL_PauseAudioDevice(). Once unpaused, any + * bound audio streams will begin to progress again, and audio can be + * generated. + * + * Unlike in SDL2, audio devices start in an _unpaused_ state, since an app + * has to bind a stream before any audio will flow. Unpausing an unpaused + * device is a legal no-op. + * + * Physical devices can not be paused or unpaused, only logical devices + * created through SDL_OpenAudioDevice() can be. + * + * \param dev a device opened by SDL_OpenAudioDevice() + * \returns 0 on success or a negative error code on failure; call + * SDL_GetError() for more information. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_UnpauseAudioDevice + * \sa SDL_IsAudioDevicePaused + */ +extern DECLSPEC int SDLCALL SDL_UnpauseAudioDevice(SDL_AudioDeviceID dev); + +/** + * Use this function to query if an audio device is paused. + * + * Unlike in SDL2, audio devices start in an _unpaused_ state, since an app + * has to bind a stream before any audio will flow. + * + * Physical devices can not be paused or unpaused, only logical devices + * created through SDL_OpenAudioDevice() can be. Physical and invalid + * device IDs will report themselves as unpaused here. + * + * \param dev a device opened by SDL_OpenAudioDevice() + * \returns SDL_TRUE if device is valid and paused, SDL_FALSE otherwise. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_PauseAudioDevice + * \sa SDL_UnpauseAudioDevice + * \sa SDL_IsAudioDevicePaused + */ +extern DECLSPEC SDL_bool SDLCALL SDL_IsAudioDevicePaused(SDL_AudioDeviceID dev); /** * Close a previously-opened audio device. diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index f4033c0fd0..74a36d5f7b 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -603,7 +603,12 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) } else { SDL_assert(buffer_size <= device->buffer_size); // you can ask for less, but not more. SDL_memset(mix_buffer, device->silence_value, buffer_size); // start with silence. + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { + if (SDL_AtomicGet(&logdev->paused)) { + continue; // paused? Skip this logical device. + } + for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. @@ -1165,6 +1170,41 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp return retval; } +static int SetLogicalAudioDevicePauseState(SDL_AudioDeviceID devid, int value) +{ + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); + if (!logdev) { + return -1; // ObtainLogicalAudioDevice will have set an error. + } + SDL_AtomicSet(&logdev->paused, value); + SDL_UnlockMutex(logdev->physical_device->lock); + return 0; +} + +int SDL_PauseAudioDevice(SDL_AudioDeviceID devid) +{ + return SetLogicalAudioDevicePauseState(devid, 1); +} + +int SDLCALL SDL_UnpauseAudioDevice(SDL_AudioDeviceID devid) +{ + return SetLogicalAudioDevicePauseState(devid, 0); +} + +SDL_bool SDL_IsAudioDevicePaused(SDL_AudioDeviceID devid) +{ + SDL_LogicalAudioDevice *logdev = ObtainLogicalAudioDevice(devid); + SDL_bool retval = SDL_FALSE; + if (logdev) { + if (SDL_AtomicGet(&logdev->paused)) { + retval = SDL_TRUE; + } + SDL_UnlockMutex(logdev->physical_device->lock); + } + return retval; +} + + int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int num_streams) { const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE; diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 0745fd025f..7346c2e57d 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -885,6 +885,9 @@ SDL3_0.0.0 { SDL_ConvertAudioSamples; SDL_GetSilenceValueForFormat; SDL_LoadWAV; + SDL_PauseAudioDevice; + SDL_UnpauseAudioDevice; + SDL_IsAudioDevicePaused; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 4964e77629..28066c561b 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -911,3 +911,6 @@ #define SDL_ConvertAudioSamples SDL_ConvertAudioSamples_REAL #define SDL_GetSilenceValueForFormat SDL_GetSilenceValueForFormat_REAL #define SDL_LoadWAV SDL_LoadWAV_REAL +#define SDL_PauseAudioDevice SDL_PauseAudioDevice_REAL +#define SDL_UnpauseAudioDevice SDL_UnpauseAudioDevice_REAL +#define SDL_IsAudioDevicePaused SDL_IsAudioDevicePaused_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index b51ac769c1..211dedbf5b 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -955,3 +955,6 @@ SDL_DYNAPI_PROC(int,SDL_MixAudioFormat,(Uint8 *a, const Uint8 *b, SDL_AudioForma SDL_DYNAPI_PROC(int,SDL_ConvertAudioSamples,(const SDL_AudioSpec *a, const Uint8 *b, int c, const SDL_AudioSpec *d, Uint8 **e, int *f),(a,b,c,d,e,f),return) SDL_DYNAPI_PROC(int,SDL_GetSilenceValueForFormat,(SDL_AudioFormat a),(a),return) SDL_DYNAPI_PROC(int,SDL_LoadWAV,(const char *a, SDL_AudioSpec *b, Uint8 **c, Uint32 *d),(a,b,c,d),return) +SDL_DYNAPI_PROC(int,SDL_PauseAudioDevice,(SDL_AudioDeviceID a),(a),return) +SDL_DYNAPI_PROC(int,SDL_UnpauseAudioDevice,(SDL_AudioDeviceID a),(a),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_IsAudioDevicePaused,(SDL_AudioDeviceID a),(a),return) From 464640440f75c3ff65c597c50cf37e397f5a4c0f Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 22 Jun 2023 01:00:12 -0400 Subject: [PATCH 012/138] audio: Added SDL_GetAudioStreamBinding. Now you can open a device, bind a stream, and forget about the device ID until you're ready to shutdown, where you can query the stream for it. --- include/SDL3/SDL_audio.h | 25 +++++++++++++++++++++++++ src/audio/SDL_audio.c | 12 ++++++++++++ src/dynapi/SDL_dynapi.sym | 1 + src/dynapi/SDL_dynapi_overrides.h | 1 + src/dynapi/SDL_dynapi_procs.h | 1 + 5 files changed, 40 insertions(+) diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 6962b8f3de..58888cd8be 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -542,6 +542,7 @@ extern DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID devid); * \sa SDL_BindAudioStreams * \sa SDL_UnbindAudioStreams * \sa SDL_UnbindAudioStream + * \sa SDL_GetAudioStreamBinding */ extern DECLSPEC int SDLCALL SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int num_streams); @@ -562,6 +563,7 @@ extern DECLSPEC int SDLCALL SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_Au * \sa SDL_BindAudioStreams * \sa SDL_UnbindAudioStreams * \sa SDL_UnbindAudioStream + * \sa SDL_GetAudioStreamBinding */ extern DECLSPEC int SDLCALL SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_AudioStream *stream); @@ -584,6 +586,7 @@ extern DECLSPEC int SDLCALL SDL_BindAudioStream(SDL_AudioDeviceID devid, SDL_Aud * \sa SDL_BindAudioStreams * \sa SDL_BindAudioStream * \sa SDL_UnbindAudioStream + * \sa SDL_GetAudioStreamBinding */ extern DECLSPEC void SDLCALL SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams); @@ -602,9 +605,31 @@ extern DECLSPEC void SDLCALL SDL_UnbindAudioStreams(SDL_AudioStream **streams, i * \sa SDL_BindAudioStream * \sa SDL_BindAudioStreams * \sa SDL_UnbindAudioStreams + * \sa SDL_GetAudioStreamBinding */ extern DECLSPEC void SDLCALL SDL_UnbindAudioStream(SDL_AudioStream *stream); +/** + * Query an audio stream for its currently-bound device. + * + * This reports the audio device that an audio stream is currently bound to. + * + * If not bound, or invalid, this returns zero, which is not a valid device ID. + * + * \param stream the audio stream to query. + * \returns The bound audio device, or 0 if not bound or invalid. + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_BindAudioStream + * \sa SDL_BindAudioStreams + * \sa SDL_UnbindAudioStream + * \sa SDL_UnbindAudioStreams + */ +extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_GetAudioStreamBinding(SDL_AudioStream *stream); + /** * Create a new audio stream. diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 74a36d5f7b..25e8ffac83 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -1357,6 +1357,18 @@ void SDL_UnbindAudioStream(SDL_AudioStream *stream) SDL_UnbindAudioStreams(&stream, 1); } +SDL_AudioDeviceID SDL_GetAudioStreamBinding(SDL_AudioStream *stream) +{ + SDL_AudioDeviceID retval = 0; + if (stream) { + SDL_LockMutex(stream->lock); + if (stream->bound_device) { + retval = stream->bound_device->instance_id; + } + SDL_UnlockMutex(stream->lock); + } + return retval; +} SDL_AudioStream *SDL_CreateAndBindAudioStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec) { diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 7346c2e57d..90da7b5fa2 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -888,6 +888,7 @@ SDL3_0.0.0 { SDL_PauseAudioDevice; SDL_UnpauseAudioDevice; SDL_IsAudioDevicePaused; + SDL_GetAudioStreamBinding; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 28066c561b..452819fcae 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -914,3 +914,4 @@ #define SDL_PauseAudioDevice SDL_PauseAudioDevice_REAL #define SDL_UnpauseAudioDevice SDL_UnpauseAudioDevice_REAL #define SDL_IsAudioDevicePaused SDL_IsAudioDevicePaused_REAL +#define SDL_GetAudioStreamBinding SDL_GetAudioStreamBinding_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 211dedbf5b..78eaa0409b 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -958,3 +958,4 @@ SDL_DYNAPI_PROC(int,SDL_LoadWAV,(const char *a, SDL_AudioSpec *b, Uint8 **c, Uin SDL_DYNAPI_PROC(int,SDL_PauseAudioDevice,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(int,SDL_UnpauseAudioDevice,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_IsAudioDevicePaused,(SDL_AudioDeviceID a),(a),return) +SDL_DYNAPI_PROC(SDL_AudioDeviceID,SDL_GetAudioStreamBinding,(SDL_AudioStream *a),(a),return) From 97b2f747d0a41ae7e4f48512fe1ddb9fb72cc2fa Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 22 Jun 2023 01:17:16 -0400 Subject: [PATCH 013/138] docs: Corrections to audio section of README-migration.md --- docs/README-migration.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/README-migration.md b/docs/README-migration.md index c5d6131d74..10366ced20 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -84,8 +84,9 @@ in SDL3: ```c /* ...somewhere near startup... */ - SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(0, SDL_AUDIO_S16, 2, 44100); - SDL_AudioSteam *stream = SDL_CreateAndBindAudioStream(my_audio_device, SDL_AUDIO_S16, 2, 44100); + SDL_AudioSpec spec = { SDL_AUDIO_S16, 2, 44100 }; + SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec); + SDL_AudioSteam *stream = SDL_CreateAndBindAudioStream(my_audio_device, &spec); /* ...in your main loop... */ /* calculate a little more audio into `buf`, add it to `stream` */ @@ -102,8 +103,9 @@ If you absolutely require the callback method, SDL_AudioStreams can use a callba } /* ...somewhere near startup... */ - SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(0, SDL_AUDIO_S16, 2, 44100); - SDL_AudioSteam *stream = SDL_CreateAndBindAudioStream(my_audio_device, SDL_AUDIO_S16, 2, 44100); + SDL_AudioSpec spec = { SDL_AUDIO_S16, 2, 44100 }; + SDL_AudioDeviceID my_audio_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec); + SDL_AudioSteam *stream = SDL_CreateAndBindAudioStream(my_audio_device, &spec); SDL_SetAudioStreamGetCallback(stream, MyAudioCallback); /* MyAudioCallback will be called whenever the device requests more audio data. */ @@ -113,11 +115,11 @@ SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_ The `SDL_AUDIO_ALLOW_*` symbols have been removed; now one may request the format they desire from the audio device, but ultimately SDL_AudioStream will manage the difference. One can use SDL_GetAudioDeviceFormat() to see what the final format is, if any "allowed" changes should be accomodated by the app. -SDL_AudioDeviceID no longer represents an open audio device's handle, it's now the device's instance ID that the device owns as long as it exists on the system. The separation between device instances and device indexes is gone. +SDL_AudioDeviceID now represents both an open audio device's handle (a "logical" device) and the instance ID that the hardware owns as long as it exists on the system (a "physical" device). The separation between device instances and device indexes is gone. -Devices are opened by device instance ID, and a new handle is not generated by the open operation; instead, opens of the same device instance are reference counted. This allows any device to be opened multiple times, possibly by unrelated pieces of code. +Devices are opened by physical device instance ID, and a new logical instance ID is generated by the open operation; This allows any device to be opened multiple times, possibly by unrelated pieces of code. SDL will manage the logical devices to provide a single stream of audio to the physical device behind the scenes. -Devices are not opened by an arbitrary string name anymore, but by device instance ID (or 0 to request a reasonable default, like a NULL string in SDL2). In SDL2, the string was used to open both a standard list of system devices, but also allowed for arbitrary devices, such as hostnames of network sound servers. In SDL3, many of the backends that supported arbitrary device names are obsolete and have been removed; of those that remain, arbitrary devices will be opened with a device ID of 0 and an SDL_hint, so specific end-users can set an environment variable to fit their needs and apps don't have to concern themselves with it. +Devices are not opened by an arbitrary string name anymore, but by device instance ID (or magic numbers to request a reasonable default, like a NULL string in SDL2). In SDL2, the string was used to open both a standard list of system devices, but also allowed for arbitrary devices, such as hostnames of network sound servers. In SDL3, many of the backends that supported arbitrary device names are obsolete and have been removed; of those that remain, arbitrary devices will be opened with a default device ID and an SDL_hint, so specific end-users can set an environment variable to fit their needs and apps don't have to concern themselves with it. Many functions that would accept a device index and an `iscapture` parameter now just take an SDL_AudioDeviceID, as they are unique across all devices, instead of separate indices into output and capture device lists. @@ -142,7 +144,7 @@ Rather than iterating over audio devices using a device index, there is a new fu } ``` -SDL_LockAudioDevice() and SDL_UnlockAudioDevice() have been removed, since there is no callback in another thread to protect. Internally, SDL's audio subsystem and SDL_AudioStream maintain their own locks internally, so audio streams are safe to use from any thread. +SDL_LockAudioDevice() and SDL_UnlockAudioDevice() have been removed, since there is no callback in another thread to protect. SDL's audio subsystem and SDL_AudioStream maintain their own locks internally, so audio streams are safe to use from any thread. If the app assigns a callback to a specific stream, it can use the stream's lock through SDL_LockAudioStream() if necessary. SDL_PauseAudioDevice() no longer takes a second argument; it always pauses the device. To unpause, use SDL_UnpauseAudioDevice(). @@ -164,6 +166,8 @@ SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_ SDL_FreeWAV has been removed and calls can be replaced with SDL_free. +SDL_LoadWAV() is a proper function now and no longer a macro (but offers the same functionality otherwise). + SDL_AudioCVT interface has been removed, the SDL_AudioStream interface (for audio supplied in pieces) or the new SDL_ConvertAudioSamples() function (for converting a complete audio buffer in one call) can be used instead. Code that used to look like this: @@ -180,8 +184,9 @@ should be changed to: ```c Uint8 *dst_data = NULL; int dst_len = 0; - if (SDL_ConvertAudioSamples(src_format, src_channels, src_rate, src_data, src_len - dst_format, dst_channels, dst_rate, &dst_data, &dst_len) < 0) { + const SDL_AudioSpec src_spec = { src_format, src_channels, src_rate }; + const SDL_AudioSpec dst_spec = { dst_format, dst_channels, dst_rate }; + if (SDL_ConvertAudioSamples(&src_spec, src_data, src_len, &dst_spec, &dst_data, &dst_len) < 0) { /* error */ } do_something(dst_data, dst_len); @@ -210,7 +215,7 @@ If you need to convert U16 audio data to a still-supported format at runtime, th All remaining `AUDIO_*` symbols have been renamed to `SDL_AUDIO_*` for API consistency, but othewise are identical in value and usage. -In SDL2, SDL_AudioStream would convert/resample audio data during input (via SDL_AudioStreamPut). In SDL3, it does this work when requesting audio (via SDL_GetAudioStreamData, which would have been SDL_AudioStreamPut in SDL2. The way you use an AudioStream is roughly the same, just be aware that the workload moved to a different phase. +In SDL2, SDL_AudioStream would convert/resample audio data during input (via SDL_AudioStreamPut). In SDL3, it does this work when requesting audio (via SDL_GetAudioStreamData, which would have been SDL_AudioStreamGet in SDL2). The way you use an AudioStream is roughly the same, just be aware that the workload moved to a different phase. In SDL2, SDL_AudioStreamAvailable() returns 0 if passed a NULL stream. In SDL3, the equivalent SDL_GetAudioStreamAvailable() call returns -1 and sets an error string, which matches other audiostream APIs' behavior. From e50cb72eb64698bb572a08d4e8c329f351912a94 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 22 Jun 2023 18:29:45 -0400 Subject: [PATCH 014/138] docs: Note that audio opening doesn't implicitly init SDL now. --- docs/README-migration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README-migration.md b/docs/README-migration.md index 10366ced20..cc7c75a590 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -57,6 +57,8 @@ The audio subsystem in SDL3 is dramatically different than SDL2. The primary way The SDL 1.2 audio compatibility API has also been removed, as it was a simplified version of the audio callback interface. +SDL3 will not implicitly initialize the audio subsystem on your behalf if you open a device without doing so. Please explicitly call SDL_Init(SDL_INIT_AUDIO) at some point. + If your app depends on the callback method, there is a similar approach you can take. But first, this is the new approach: In SDL2, you might have done something like this to play audio: From 089cd87cb53c09c36cc03eba8a47a3870af67388 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 22 Jun 2023 18:31:03 -0400 Subject: [PATCH 015/138] audio: Make sure device count stays correct as hardware disconnects. --- src/audio/SDL_audio.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 25e8ffac83..19624b8ba2 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -323,6 +323,11 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) device->next->prev = device->prev; was_live = SDL_TRUE; } + + if (was_live) { + SDL_AtomicDecRef(device->iscapture ? ¤t_audio.capture_device_count : ¤t_audio.output_device_count); + } + SDL_UnlockRWLock(current_audio.device_list_lock); // now device is not in the list, and we own it, so no one should be able to find it again, except the audio thread, which holds a pointer! @@ -534,6 +539,8 @@ void SDL_QuitAudio(void) } current_audio.output_devices = NULL; current_audio.capture_devices = NULL; + SDL_AtomicSet(¤t_audio.output_device_count, 0); + SDL_AtomicSet(¤t_audio.capture_device_count, 0); SDL_UnlockRWLock(current_audio.device_list_lock); // mark all devices for shutdown so all threads can begin to terminate. @@ -767,7 +774,7 @@ static SDL_AudioDeviceID *GetAudioDevices(int *reqcount, SDL_AudioDevice **devic num_devices = 0; SDL_OutOfMemory(); } else { - const SDL_AudioDevice *dev = *devices; + const SDL_AudioDevice *dev = *devices; // pointer to a pointer so we can dereference it after the lock is held. for (int i = 0; i < num_devices; i++) { SDL_assert(dev != NULL); SDL_assert(!SDL_AtomicGet((SDL_AtomicInt *) &dev->condemned)); // shouldn't be in the list if pending deletion. From c7a44eea8369c92c964172f8707758d10d577724 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 22 Jun 2023 18:31:33 -0400 Subject: [PATCH 016/138] audio: Fixed logic error. --- src/audio/SDL_audio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 19624b8ba2..76b36f8e47 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -335,7 +335,7 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) SDL_AtomicSet(&device->shutdown, 1); // tell audio thread to terminate. // if there's an audio thread, don't free until thread is terminating, otherwise free stuff now. - const SDL_bool should_destroy = (device->thread != NULL); + const SDL_bool should_destroy = (device->thread == NULL); SDL_UnlockMutex(device->lock); // Post the event, if we haven't tried to before and if it's desired From ee10bab3cd66e2c718079fd529fee7e22173d4b9 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 23 Jun 2023 02:37:48 -0400 Subject: [PATCH 017/138] audio: An enormous amount of work on managing default devices. --- src/audio/SDL_audio.c | 149 ++++++++++++++++- src/audio/SDL_sysaudio.h | 11 +- src/audio/disk/SDL_diskaudio.c | 6 +- src/audio/pulseaudio/SDL_pulseaudio.c | 231 ++++++++++++++++++-------- test/loopwave.c | 4 +- 5 files changed, 314 insertions(+), 87 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 76b36f8e47..b60ddb790b 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -301,6 +301,9 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) return; } + // !!! FIXME: if this was the default device, we should figure out how to migrate the appropriate logical devices instead of declaring them dead. + // !!! FIXME: (right now we rely on the backends to change the default device before disconnecting this one, but that's probably not practical.) + SDL_bool was_live = SDL_FALSE; // take it out of the device list. @@ -347,6 +350,16 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) event.adevice.which = device->instance_id; event.adevice.iscapture = device->iscapture ? 1 : 0; SDL_PushEvent(&event); + + // post an event for each logical device, too. + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { + SDL_zero(event); + event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED; + event.common.timestamp = 0; + event.adevice.which = logdev->instance_id; + event.adevice.iscapture = device->iscapture ? 1 : 0; + SDL_PushEvent(&event); + } } if (should_destroy) { @@ -366,15 +379,15 @@ static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ } static void SDL_AudioFreeDeviceHandle_Default(void *handle) { /* no-op. */ } -static void SDL_AudioDetectDevices_Default(void) +static void SDL_AudioDetectDevices_Default(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { // you have to write your own implementation if these assertions fail. SDL_assert(current_audio.impl.OnlyHasDefaultOutputDevice); SDL_assert(current_audio.impl.OnlyHasDefaultCaptureDevice || !current_audio.impl.HasCaptureSupport); - SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)((size_t)0x1)); + *default_output = SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)((size_t)0x1)); if (current_audio.impl.HasCaptureSupport) { - SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)((size_t)0x2)); + *default_capture = SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)((size_t)0x2)); } } @@ -512,7 +525,14 @@ int SDL_InitAudio(const char *driver_name) CompleteAudioEntryPoints(); // Make sure we have a list of devices available at startup... - current_audio.impl.DetectDevices(); + SDL_AudioDevice *default_output = NULL; + SDL_AudioDevice *default_capture = NULL; + current_audio.impl.DetectDevices(&default_output, &default_capture); + + current_audio.default_output_device_id = default_output ? default_output->instance_id : 0; + current_audio.default_capture_device_id = default_capture ? default_capture->instance_id : 0; + + // !!! FIXME: if a default is zero but there are devices available, should we just pick the first one? return 0; } @@ -1121,17 +1141,25 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec) { + if (!SDL_GetCurrentAudioDriver()) { + SDL_SetError("Audio subsystem is not initialized"); + return 0; + } + SDL_bool is_default = SDL_FALSE; if (devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) { - // !!! FIXME: needs to be hooked up. - //devid = current_audio.default_output_device_id; + devid = current_audio.default_output_device_id; is_default = SDL_TRUE; } else if (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) { - // !!! FIXME: needs to be hooked up. - //devid = current_audio.default_capture_device_id; + devid = current_audio.default_capture_device_id; is_default = SDL_TRUE; } + if ((devid == 0) && is_default) { + SDL_SetError("No default audio device available"); + return 0; + } + // this will let you use a logical device to make a new logical device on the parent physical device. Could be useful? SDL_AudioDevice *device = NULL; const SDL_bool islogical = (devid & (1<<1)) ? SDL_FALSE : SDL_TRUE; @@ -1433,3 +1461,108 @@ int SDL_GetSilenceValueForFormat(SDL_AudioFormat format) return (format == SDL_AUDIO_U8) ? 0x80 : 0x00; } +// called internally by backends when the system default device changes. +void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) +{ + if (new_default_device == NULL) { // !!! FIXME: what should we do in this case? Maybe all devices are lost, so there _isn't_ a default? + return; // uhoh. + } + + const SDL_bool iscapture = new_default_device->iscapture; + const SDL_AudioDeviceID current_devid = iscapture ? current_audio.default_capture_device_id : current_audio.default_output_device_id; + + if (new_default_device->instance_id == current_devid) { + return; // this is already the default. + } + + SDL_LockMutex(new_default_device->lock); + + SDL_AudioDevice *current_default_device = ObtainPhysicalAudioDevice(current_devid); + + /* change the official default ID over while we have locks on both devices, so if something raced to open the default during + this, it either gets the new device or is ready on the old and can be migrated. */ + if (iscapture) { + current_audio.default_capture_device_id = new_default_device->instance_id; + } else { + current_audio.default_output_device_id = new_default_device->instance_id; + } + + if (current_default_device) { + // migrate any logical devices that were opened as a default to the new physical device... + + SDL_assert(current_default_device->iscapture == iscapture); + + // See if we have to open the new physical device, and if so, find the best audiospec for it. + SDL_AudioSpec spec; + SDL_bool needs_migration = SDL_FALSE; + SDL_zero(spec); + for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev != NULL; logdev = logdev->next) { + if (logdev->is_default) { + needs_migration = SDL_TRUE; + for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { + const SDL_AudioSpec *streamspec = iscapture ? &stream->dst_spec : &stream->src_spec; + if (SDL_AUDIO_BITSIZE(streamspec->format) > SDL_AUDIO_BITSIZE(spec.format)) { + spec.format = streamspec->format; + } + if (streamspec->channels > spec.channels) { + spec.channels = streamspec->channels; + } + if (streamspec->freq > spec.freq) { + spec.freq = streamspec->freq; + } + } + } + } + + if (needs_migration) { + if (new_default_device->logical_devices == NULL) { // New default physical device not been opened yet? Open at the OS level... + if (OpenPhysicalAudioDevice(new_default_device, &spec) == -1) { + needs_migration = SDL_FALSE; // uhoh, just leave everything on the old default, nothing to be done. + } + } + } + + if (needs_migration) { + SDL_LogicalAudioDevice *next = NULL; + for (SDL_LogicalAudioDevice *logdev = current_default_device->logical_devices; logdev != NULL; logdev = next) { + next = logdev->next; + + if (!logdev->is_default) { + continue; // not opened as a default, leave it on the current physical device. + } + + // make sure all our streams are targeting the new device's format. + for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { + SDL_SetAudioStreamFormat(stream, iscapture ? &new_default_device->spec : NULL, iscapture ? NULL : &new_default_device->spec); + } + + // now migrate the logical device. + if (logdev->next) { + logdev->next->prev = logdev->prev; + } + if (logdev->prev) { + logdev->prev->next = logdev->next; + } + if (current_default_device->logical_devices == logdev) { + current_default_device->logical_devices = logdev->next; + } + + logdev->physical_device = new_default_device; + logdev->prev = NULL; + logdev->next = new_default_device->logical_devices; + new_default_device->logical_devices = logdev; + } + + if (current_default_device->logical_devices == NULL) { // nothing left on the current physical device, close it. + // !!! FIXME: we _need_ to release this lock, but doing so can cause a race condition if someone opens a device while we're closing it. + SDL_UnlockMutex(current_default_device->lock); // can't hold the lock or the audio thread will deadlock while we WaitThread it. + ClosePhysicalAudioDevice(current_default_device); + SDL_LockMutex(current_default_device->lock); // we're about to unlock this again, so make sure the locks match. + } + } + + SDL_UnlockMutex(current_default_device->lock); + } + + SDL_UnlockMutex(new_default_device->lock); +} diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index ccc1af9745..cc6a9713cc 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -70,15 +70,18 @@ const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format); /* Must be called at least once before using converters (SDL_CreateAudioStream will call it !!! FIXME but probably shouldn't). */ extern void SDL_ChooseAudioConverters(void); -/* Audio targets should call this as devices are added to the system (such as +/* Backends should call this as devices are added to the system (such as a USB headset being plugged in), and should also be called for for every device found during DetectDevices(). */ extern SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, const SDL_AudioSpec *spec, void *handle); -/* Audio targets should call this if an opened audio device is lost. +/* Backends should call this if an opened audio device is lost. This can happen due to i/o errors, or a device being unplugged, etc. */ extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device); +// Backends should call this if the system default device changes. +extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device); + /* Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. Locks the device! You must unlock!! */ extern SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle); @@ -96,7 +99,7 @@ extern void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device); typedef struct SDL_AudioDriverImpl { - void (*DetectDevices)(void); + void (*DetectDevices)(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture); int (*OpenDevice)(SDL_AudioDevice *device); void (*ThreadInit)(SDL_AudioDevice *device); /* Called by audio thread at start */ void (*ThreadDeinit)(SDL_AudioDevice *device); /* Called by audio thread at end */ @@ -126,6 +129,8 @@ typedef struct SDL_AudioDriver SDL_RWLock *device_list_lock; /* A mutex for device detection */ SDL_AudioDevice *output_devices; /* the list of currently-available audio output devices. */ SDL_AudioDevice *capture_devices; /* the list of currently-available audio capture devices. */ + SDL_AudioDeviceID default_output_device_id; + SDL_AudioDeviceID default_capture_device_id; SDL_AtomicInt output_device_count; SDL_AtomicInt capture_device_count; SDL_AtomicInt last_device_instance_id; /* increments on each device add to provide unique instance IDs */ diff --git a/src/audio/disk/SDL_diskaudio.c b/src/audio/disk/SDL_diskaudio.c index 6850e7b03c..517a4859ee 100644 --- a/src/audio/disk/SDL_diskaudio.c +++ b/src/audio/disk/SDL_diskaudio.c @@ -151,10 +151,10 @@ static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device) return 0; } -static void DISKAUDIO_DetectDevices(void) +static void DISKAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { - SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1); - SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2); + *default_output = SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1); + *default_capture = SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2); } static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl) diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 16ff3cb906..2ebb533c93 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -43,13 +43,15 @@ static pa_context *pulseaudio_context = NULL; static SDL_Thread *pulseaudio_hotplug_thread = NULL; static SDL_AtomicInt pulseaudio_hotplug_thread_active; -/* These are the OS identifiers (i.e. ALSA strings)... */ +// These are the OS identifiers (i.e. ALSA strings)... static char *default_sink_path = NULL; static char *default_source_path = NULL; -/* ... and these are the descriptions we use in GetDefaultAudioInfo. */ +// ... and these are the descriptions we use in GetDefaultAudioInfo... static char *default_sink_name = NULL; static char *default_source_name = NULL; - +// ... and these are the PulseAudio device indices of the default devices. +static uint32_t default_sink_index = 0; +static uint32_t default_source_index = 0; static const char *(*PULSEAUDIO_pa_get_library_version)(void); static pa_channel_map *(*PULSEAUDIO_pa_channel_map_init_auto)( @@ -555,12 +557,12 @@ static void SourceDeviceNameCallback(pa_context *c, const pa_source_info *i, int static SDL_bool FindDeviceName(struct SDL_PrivateAudioData *h, const SDL_bool iscapture, void *handle) { - const uint32_t idx = ((uint32_t)((intptr_t)handle)) - 1; - if (handle == NULL) { /* NULL == default device. */ return SDL_TRUE; } + const uint32_t idx = ((uint32_t)((intptr_t)handle)) - 1; + if (iscapture) { WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceDeviceNameCallback, &h->device_name)); } else { @@ -685,11 +687,8 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) PULSEAUDIO_pa_stream_set_state_callback(h->stream, PulseStreamStateChangeCallback, NULL); - /* now that we have multi-device support, don't move a stream from - a device that was unplugged to something else, unless we're default. */ - if (h->device_name != NULL) { - flags |= PA_STREAM_DONT_MOVE; - } + // SDL manages device moves if the default changes, so don't ever let Pulse automatically migrate this stream. + flags |= PA_STREAM_DONT_MOVE; if (iscapture) { PULSEAUDIO_pa_stream_set_read_callback(h->stream, ReadCallback, h); @@ -745,46 +744,50 @@ static SDL_AudioFormat PulseFormatToSDLFormat(pa_sample_format_t format) } } -/* This is called when PulseAudio adds an output ("sink") device. */ +// This is called when PulseAudio adds an output ("sink") device. +// !!! FIXME: this is almost identical to SourceInfoCallback, merge the two. static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, void *data) { if (i) { - const SDL_bool add = (SDL_bool)((intptr_t)data); - SDL_AudioSpec spec; - spec.format = PulseFormatToSDLFormat(i->sample_spec.format); - spec.channels = i->sample_spec.channels; - spec.freq = i->sample_spec.rate; + const SDL_bool add = (SDL_bool) ((intptr_t)data); if (add) { + SDL_AudioSpec spec; + spec.format = PulseFormatToSDLFormat(i->sample_spec.format); + spec.channels = i->sample_spec.channels; + spec.freq = i->sample_spec.rate; SDL_AddAudioDevice(SDL_FALSE, i->description, &spec, (void *)((intptr_t)i->index + 1)); } if (default_sink_path != NULL && SDL_strcmp(i->name, default_sink_path) == 0) { SDL_free(default_sink_name); default_sink_name = SDL_strdup(i->description); + default_sink_index = i->index; } } PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } /* This is called when PulseAudio adds a capture ("source") device. */ +// !!! FIXME: this is almost identical to SinkInfoCallback, merge the two. static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_last, void *data) { /* Maybe skip "monitor" sources. These are just output from other sinks. */ if (i && (include_monitors || (i->monitor_of_sink == PA_INVALID_INDEX))) { - const SDL_bool add = (SDL_bool)((intptr_t)data); - SDL_AudioSpec spec; - spec.format = PulseFormatToSDLFormat(i->sample_spec.format); - spec.channels = i->sample_spec.channels; - spec.freq = i->sample_spec.rate; + const SDL_bool add = (SDL_bool) ((intptr_t)data); if (add) { + SDL_AudioSpec spec; + spec.format = PulseFormatToSDLFormat(i->sample_spec.format); + spec.channels = i->sample_spec.channels; + spec.freq = i->sample_spec.rate; SDL_AddAudioDevice(SDL_TRUE, i->description, &spec, (void *)((intptr_t)i->index + 1)); } if (default_source_path != NULL && SDL_strcmp(i->name, default_source_path) == 0) { SDL_free(default_source_name); default_source_name = SDL_strdup(i->description); + default_source_index = i->index; } } PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); @@ -792,80 +795,152 @@ static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_la static void ServerInfoCallback(pa_context *c, const pa_server_info *i, void *data) { - SDL_free(default_sink_path); - SDL_free(default_source_path); - default_sink_path = SDL_strdup(i->default_sink_name); - default_source_path = SDL_strdup(i->default_source_name); + if (!default_sink_path || (SDL_strcmp(i->default_sink_name, default_sink_path) != 0)) { + printf("DEFAULT SINK PATH CHANGED TO '%s'\n", i->default_sink_name); + SDL_free(default_sink_path); + default_sink_path = SDL_strdup(i->default_sink_name); + } + + if (!default_source_path || (SDL_strcmp(i->default_source_name, default_source_path) != 0)) { + printf("DEFAULT SOURCE PATH CHANGED TO '%s'\n", i->default_source_name); + SDL_free(default_source_path); + default_source_path = SDL_strdup(i->default_source_name); + } + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } -/* This is called when PulseAudio has a device connected/removed/changed. */ +typedef struct PulseHotplugEvent +{ + pa_subscription_event_type_t t; + uint32_t idx; +} PulseHotplugEvent; + +typedef struct PulseHotplugEvents +{ + PulseHotplugEvent *events; + int num_events; + int allocated_events; + SDL_bool need_server_info_refresh; +} PulseHotplugEvents; + +// This is called when PulseAudio has a device connected/removed/changed. static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *data) { - const SDL_bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW); - const SDL_bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE); - const SDL_bool changed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE); - - if (added || removed || changed) { /* we only care about add/remove events. */ - const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK); - const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); - - /* adds need sink details from the PulseAudio server. Another callback... */ - /* (just unref all these operations right away, because we aren't going to wait on them and their callbacks will handle any work, so they can free as soon as that happens.) */ - if ((added || changed) && sink) { - if (changed) { - PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL)); - } - PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkInfoCallback, (void *)((intptr_t)added))); - } else if ((added || changed) && source) { - if (changed) { - PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL)); - } - PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, (void *)((intptr_t)added))); - } else if (removed && (sink || source)) { - /* removes we can handle just with the device index. */ - SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)idx + 1)); /* !!! FIXME: maybe just have a "disconnect by handle" function instead. */ - if (device) { - SDL_UnlockMutex(device->lock); /* AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. */ - SDL_AudioDeviceDisconnected(device); - } + // We can't block in here for new ServerInfo, but we need that info to resolve first, since we can + // migrate default devices during a removal, as long as we know the new default. So just queue up + // the data and let HotplugThread handle it, where we can call WaitForPulseOperation. + PulseHotplugEvents *events = (PulseHotplugEvents *) data; + if (events->allocated_events <= events->num_events) { + const int new_allocation = events->num_events + 16; + void *ptr = SDL_realloc(events->events, new_allocation * sizeof (PulseHotplugEvent)); + if (!ptr) { + return; // oh well. } + events->events = (PulseHotplugEvent *) ptr; + events->allocated_events = new_allocation; + } + events->events[events->num_events].t = t; + events->events[events->num_events].idx = idx; + events->num_events++; + + const SDL_bool changed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE); + if (changed) { + events->need_server_info_refresh = SDL_TRUE; } PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } -/* this runs as a thread while the Pulse target is initialized to catch hotplug events. */ +static void CheckDefaultDevice(int prev_default, int new_default) +{ + if (prev_default != new_default) { + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)new_default + 1)); + if (device) { + SDL_UnlockMutex(device->lock); + SDL_DefaultAudioDeviceChanged(device); + } + } +} + +// this runs as a thread while the Pulse target is initialized to catch hotplug events. static int SDLCALL HotplugThread(void *data) { pa_operation *op; SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); + + PulseHotplugEvents events; + SDL_zero(events); + PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); - PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, HotplugCallback, NULL); - - /* don't WaitForPulseOperation on the subscription; when it's done we'll be able to get hotplug events, but waiting doesn't changing anything. */ - op = PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, NULL, NULL); - - SDL_PostSemaphore((SDL_Semaphore *) data); - + PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, HotplugCallback, &events); + WaitForPulseOperation(PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL)); while (SDL_AtomicGet(&pulseaudio_hotplug_thread_active)) { PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); - if (op && PULSEAUDIO_pa_operation_get_state(op) != PA_OPERATION_RUNNING) { - PULSEAUDIO_pa_operation_unref(op); - op = NULL; + if (events.need_server_info_refresh) { + events.need_server_info_refresh = SDL_FALSE; + WaitForPulseOperation(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL)); + } else if (events.num_events) { // don't process events until we didn't get anything new that needs a server info update. + const uint32_t prev_default_sink_index = default_sink_index; + const uint32_t prev_default_source_index = default_source_index; + PulseHotplugEvents eventscpy; + SDL_memcpy(&eventscpy, &events, sizeof (PulseHotplugEvents)); + SDL_zero(events); // make sure the current array isn't touched in case new events fire. + + // process adds and changes first, so we definitely have default device changes processed. Then remove devices after. + for (int i = 0; i < eventscpy.num_events; i++) { + const pa_subscription_event_type_t t = eventscpy.events[i].t; + const uint32_t idx = eventscpy.events[i].idx; + const SDL_bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW); + const SDL_bool changed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE); + const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK); + const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); + if (added || changed) { + if (sink) { + WaitForPulseOperation(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkInfoCallback, (void *)((intptr_t)added))); + } else if (source) { + WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, (void *)((intptr_t)added))); + } + } + } + + // don't hold the pulse lock during this, since it could deadlock vs a playing device that we're about to lock here. + PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); + + CheckDefaultDevice(prev_default_sink_index, default_sink_index); + CheckDefaultDevice(prev_default_source_index, default_source_index); + + // okay, default devices are sane, migrations were made if necessary...now remove lost devices. + for (int i = 0; i < eventscpy.num_events; i++) { + const pa_subscription_event_type_t t = eventscpy.events[i].t; + const uint32_t idx = eventscpy.events[i].idx; + const SDL_bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE); + const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK); + const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); + if (removed && (sink || source)) { + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)idx + 1)); + if (device) { + SDL_UnlockMutex(device->lock); // AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. + SDL_AudioDeviceDisconnected(device); + } + } + } + + PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); + + SDL_free(eventscpy.events); } } - if (op) { - PULSEAUDIO_pa_operation_unref(op); - } - - PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, NULL, NULL); + PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, NULL, NULL); // Don't fire HotplugCallback again. PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); + + SDL_free(events.events); // should be NULL, but just in case. + return 0; } -static void PULSEAUDIO_DetectDevices(void) +static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { SDL_Semaphore *ready_sem = SDL_CreateSemaphore(0); @@ -875,6 +950,19 @@ static void PULSEAUDIO_DetectDevices(void) WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_list(pulseaudio_context, SourceInfoCallback, (void *)((intptr_t)SDL_TRUE))); PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); + SDL_AudioDevice *device; + device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)default_sink_index + 1)); + if (device) { + SDL_UnlockMutex(device->lock); + *default_output = device; + } + + device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)default_source_index + 1)); + if (device) { + SDL_UnlockMutex(device->lock); + *default_capture = device; + } + /* ok, we have a sane list, let's set up hotplug notifications now... */ SDL_AtomicSet(&pulseaudio_hotplug_thread_active, 1); pulseaudio_hotplug_thread = SDL_CreateThreadInternal(HotplugThread, "PulseHotplug", 256 * 1024, ready_sem); /* !!! FIXME: this can probably survive in significantly less stack space. */ @@ -937,6 +1025,9 @@ static void PULSEAUDIO_Deinitialize(void) SDL_free(default_source_name); default_source_name = NULL; + default_source_index = 0; + default_sink_index = 0; + UnloadPulseAudioLibrary(); } diff --git a/test/loopwave.c b/test/loopwave.c index 15a64bf341..997c14f798 100644 --- a/test/loopwave.c +++ b/test/loopwave.c @@ -86,9 +86,7 @@ close_audio(void) static void open_audio(void) { - SDL_AudioDeviceID *devices = SDL_GetAudioOutputDevices(NULL); - device = devices ? SDL_OpenAudioDevice(devices[0], &wave.spec) : 0; - SDL_free(devices); + device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &wave.spec); if (!device) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s\n", SDL_GetError()); SDL_free(wave.sound); From db39cbf208c7df8b79c95dc1b672c147741e8c94 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 23 Jun 2023 14:32:25 -0400 Subject: [PATCH 018/138] audio: Allow SDL_GetAudioDeviceFormat() to query the default devices. Officially removed SDL_GetDefaultAudioInfo(), as its functionality that isn't obsolete is now offered elsewhere. --- docs/README-migration.md | 3 +++ include/SDL3/SDL_audio.h | 5 +++++ src/audio/SDL_audio.c | 14 ++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/docs/README-migration.md b/docs/README-migration.md index cc7c75a590..1ce3bb34f1 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -162,6 +162,8 @@ SDL_AudioSpec has been reduced; now it only holds format, channel, and sample ra SDL_GetAudioDeviceSpec() is removed; use SDL_GetAudioDeviceFormat() instead. +SDL_GetDefaultAudioInfo() is removed; SDL_GetAudioDeviceFormat() with SDL_AUDIO_DEVICE_DEFAULT_OUTPUT or SDL_AUDIO_DEVICE_DEFAULT_CAPTURE. There is no replacement for querying the default device name; the string is no longer used to open devices, and SDL3 will migrate between physical devices on the fly if the system default changes, so if you must show this to the user, a generic name like "System default" is recommended. + SDL_MixAudio() has been removed, as it relied on legacy SDL 1.2 quirks; SDL_MixAudioFormat() remains and offers the same functionality. SDL_AudioInit() and SDL_AudioQuit() have been removed. Instead you can call SDL_InitSubSystem() and SDL_QuitSubSystem() with SDL_INIT_AUDIO, which will properly refcount the subsystems. You can choose a specific audio driver using SDL_AUDIO_DRIVER hint. @@ -241,6 +243,7 @@ The following functions have been removed: * SDL_PauseAudio() * SDL_GetAudioStatus() * SDL_GetAudioDeviceStatus() +* SDL_GetDefaultAudioInfo() * SDL_LockAudio() * SDL_LockAudioDevice() * SDL_UnlockAudio() diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index 58888cd8be..e8a1fc7cda 100644 --- a/include/SDL3/SDL_audio.h +++ b/include/SDL3/SDL_audio.h @@ -321,6 +321,11 @@ extern DECLSPEC char *SDLCALL SDL_GetAudioDeviceName(SDL_AudioDeviceID devid); * the device's preferred format (or a reasonable default if this * can't be determined). * + * You may also specify SDL_AUDIO_DEVICE_DEFAULT_OUTPUT or + * SDL_AUDIO_DEVICE_DEFAULT_CAPTURE here, which is useful for getting + * a reasonable recommendation before opening the system-recommended + * default device. + * * \param devid the instance ID of the device to query. * \param spec On return, will be filled with device details. * \returns 0 on success or a negative error code on failure; call diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index b60ddb790b..6cb683433f 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -966,6 +966,20 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec) return SDL_InvalidParamError("spec"); } + SDL_bool is_default = SDL_FALSE; + if (devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) { + devid = current_audio.default_output_device_id; + is_default = SDL_TRUE; + } else if (devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) { + devid = current_audio.default_capture_device_id; + is_default = SDL_TRUE; + } + + if ((devid == 0) && is_default) { + return SDL_SetError("No default audio device available"); + return 0; + } + SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); if (!device) { return -1; From cdd2ba81decd387e0015a4ba83e0a6d2ca18e23d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 23 Jun 2023 21:42:48 -0400 Subject: [PATCH 019/138] audio: Fixed adding new physical devices to a double-linked list. (Forgot to hook up existing node's `prev` field when adding the new device to the head of the list. Doh.) --- src/audio/SDL_audio.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 6cb683433f..6b8099c4e7 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -246,6 +246,11 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, SDL_bool isc device->instance_id = assign_audio_device_instance_id(iscapture, /*islogical=*/SDL_FALSE); SDL_LockRWLockForWriting(current_audio.device_list_lock); + + if (*devices) { + SDL_assert((*devices)->prev == NULL); + (*devices)->prev = device; + } device->next = *devices; *devices = device; SDL_AtomicIncRef(device_count); From fe1daf6fb563d15b74cd4780f443d3fa12067d8a Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 23 Jun 2023 21:50:24 -0400 Subject: [PATCH 020/138] audio: Mark disconnected default devices as "zombies". Zombie devices just sit there doing nothing until a new default device is chosen, and then they migrate all their logical devices before being destroyed. This lets the system deal with the likely outcome of a USB headset being the default audio device, and when its cable is yanked out, the backend will likely announce this _before_ it chooses a new default (or, perhaps, the only device in the system got yanked out and there _isn't_ a new default to be had until the user plugs the cable back in). This lets the audio device hold on without disturbing the app until it can seamlessly migrate audio, and it also means the backend does not have to be careful in how it announces device events, since SDL will manage the time between a device loss and its replacement. Note that this _only_ applies to things opened as the default device (SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, etc). If those USB headphones are the default, and one SDL_OpenAudioDevice() call asked for them specifically and the other just said "give me the system default," the explicitly requested open will get a device-lost notification immediately. The other open will live on as a zombie until it can migrate to the new default. This drops the complexity of the PulseAudio hotplug thread dramatically, back to what it was previously, since it no longer needs to fight against Pulse's asychronous nature, but just report device disconnects and new default choices as they arrive. loopwave has been updated to not check for device removals anymore; since it opens the default device, this is now managed for it; it no longer needs to close and reopen a device, and as far as it knows, the device is never lost in the first place. --- src/audio/SDL_audio.c | 100 +++++++++++++------ src/audio/SDL_sysaudio.h | 3 + src/audio/pulseaudio/SDL_pulseaudio.c | 137 ++++++++------------------ test/loopwave.c | 14 --- 4 files changed, 111 insertions(+), 143 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 6b8099c4e7..4daa15c3b7 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -236,6 +236,7 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, SDL_bool isc SDL_AtomicSet(&device->shutdown, 0); SDL_AtomicSet(&device->condemned, 0); + SDL_AtomicSet(&device->zombie, 0); device->iscapture = iscapture; SDL_memcpy(&device->spec, spec, sizeof (SDL_AudioSpec)); SDL_memcpy(&device->default_spec, spec, sizeof (SDL_AudioSpec)); @@ -296,9 +297,27 @@ SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, SDL_PushEvent(&event); } } + return device; } +// this _also_ destroys the logical device! +static void DisconnectLogicalAudioDevice(SDL_LogicalAudioDevice *logdev) +{ + if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_REMOVED)) { + SDL_Event event; + SDL_zero(event); +SDL_Log("Sending event about loss of logical device #%u", (unsigned int) logdev->instance_id); + event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED; + event.common.timestamp = 0; + event.adevice.which = logdev->instance_id; + event.adevice.iscapture = logdev->physical_device->iscapture ? 1 : 0; + SDL_PushEvent(&event); + } + + DestroyLogicalAudioDevice(logdev); +} + // Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread. void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) { @@ -306,8 +325,21 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) return; } - // !!! FIXME: if this was the default device, we should figure out how to migrate the appropriate logical devices instead of declaring them dead. - // !!! FIXME: (right now we rely on the backends to change the default device before disconnecting this one, but that's probably not practical.) + // if the current default device is going down, mark it as dead but keep it around until a replacement is decided upon, so we can migrate logical devices to it. + if ((device->instance_id == current_audio.default_output_device_id) || (device->instance_id == current_audio.default_capture_device_id)) { + SDL_AtomicSet(&device->zombie, 1); + SDL_AtomicSet(&device->shutdown, 1); // tell audio thread to terminate, but don't mark it condemned, so the thread won't destroy the device. We'll join on the audio thread later. + + // dump any logical devices that explicitly opened this device. Things that opened the system default can stay. + SDL_LogicalAudioDevice *next = NULL; + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = next) { + next = logdev->next; + if (!logdev->is_default) { // if opened as a default, leave it on the zombie device for later migration. + DisconnectLogicalAudioDevice(logdev); + } + } + return; // done for now. Come back when a new default device is chosen! + } SDL_bool was_live = SDL_FALSE; @@ -332,6 +364,9 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) was_live = SDL_TRUE; } + device->next = NULL; + device->prev = NULL; + if (was_live) { SDL_AtomicDecRef(device->iscapture ? ¤t_audio.capture_device_count : ¤t_audio.output_device_count); } @@ -342,6 +377,13 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) SDL_AtomicSet(&device->condemned, 1); SDL_AtomicSet(&device->shutdown, 1); // tell audio thread to terminate. + // disconnect each attached logical device, so apps won't find their streams still bound if they get the REMOVED event before the device thread cleans up. + SDL_LogicalAudioDevice *next; + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = next) { + next = logdev->next; + DisconnectLogicalAudioDevice(logdev); + } + // if there's an audio thread, don't free until thread is terminating, otherwise free stuff now. const SDL_bool should_destroy = (device->thread == NULL); SDL_UnlockMutex(device->lock); @@ -355,16 +397,6 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) event.adevice.which = device->instance_id; event.adevice.iscapture = device->iscapture ? 1 : 0; SDL_PushEvent(&event); - - // post an event for each logical device, too. - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { - SDL_zero(event); - event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED; - event.common.timestamp = 0; - event.adevice.which = logdev->instance_id; - event.adevice.iscapture = device->iscapture ? 1 : 0; - SDL_PushEvent(&event); - } } if (should_destroy) { @@ -1113,6 +1145,11 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec { SDL_assert(device->logical_devices == NULL); + // Just pretend to open a zombie device. It can still collect logical devices on the assumption they will all migrate when the default device is officially changed. + if (SDL_AtomicGet(&device->zombie)) { + return 0; // Braaaaaaaaains. + } + SDL_AudioSpec spec; SDL_memcpy(&spec, inspec, sizeof (SDL_AudioSpec)); PrepareAudioFormat(&spec); @@ -1195,28 +1232,24 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp SDL_AudioDeviceID retval = 0; if (device) { - SDL_LogicalAudioDevice *logdev = (SDL_LogicalAudioDevice *) SDL_calloc(1, sizeof (SDL_LogicalAudioDevice)); - if (!logdev) { + SDL_LogicalAudioDevice *logdev = NULL; + if (!is_default && SDL_AtomicGet(&device->zombie)) { + // uhoh, this device is undead, and just waiting for a new default device to be declared so it can hand off to it. Refuse explicit opens. + SDL_SetError("Device was already lost and can't accept new opens"); + } else if ((logdev = (SDL_LogicalAudioDevice *) SDL_calloc(1, sizeof (SDL_LogicalAudioDevice))) == NULL) { SDL_OutOfMemory(); + } else if (!device->is_opened && OpenPhysicalAudioDevice(device, spec) == -1) { // first thing using this physical device? Open at the OS level... + SDL_free(logdev); } else { - if (device->logical_devices == NULL) { // first thing using this physical device? Open at the OS level... - if (OpenPhysicalAudioDevice(device, spec) == -1) { - SDL_free(logdev); - logdev = NULL; - } - } - - if (logdev != NULL) { - SDL_AtomicSet(&logdev->paused, 0); - retval = logdev->instance_id = assign_audio_device_instance_id(device->iscapture, /*islogical=*/SDL_TRUE); - logdev->physical_device = device; - logdev->is_default = is_default; - logdev->next = device->logical_devices; - if (device->logical_devices) { - device->logical_devices->prev = logdev; - } - device->logical_devices = logdev; + SDL_AtomicSet(&logdev->paused, 0); + retval = logdev->instance_id = assign_audio_device_instance_id(device->iscapture, /*islogical=*/SDL_TRUE); + logdev->physical_device = device; + logdev->is_default = is_default; + logdev->next = device->logical_devices; + if (device->logical_devices) { + device->logical_devices->prev = logdev; } + device->logical_devices = logdev; } SDL_UnlockMutex(device->lock); } @@ -1584,4 +1617,9 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device) } SDL_UnlockMutex(new_default_device->lock); + + // was current device already dead and just kept around to migrate to a new default device? Now we can kill it. Aim for the brain. + if (current_default_device && SDL_AtomicGet(¤t_default_device->zombie)) { + 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. + } } diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index cc6a9713cc..f6f17ffa78 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -238,6 +238,9 @@ struct SDL_AudioDevice /* non-zero if we want the device to be destroyed (so audio thread knows to do it on termination). */ SDL_AtomicInt condemned; + /* non-zero if this was a disconnected default device and we're waiting for its replacement. */ + SDL_AtomicInt zombie; + /* SDL_TRUE if this is a capture device instead of an output device */ SDL_bool iscapture; diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 2ebb533c93..c9f66bb251 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -810,53 +810,47 @@ static void ServerInfoCallback(pa_context *c, const pa_server_info *i, void *dat PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } -typedef struct PulseHotplugEvent -{ - pa_subscription_event_type_t t; - uint32_t idx; -} PulseHotplugEvent; - -typedef struct PulseHotplugEvents -{ - PulseHotplugEvent *events; - int num_events; - int allocated_events; - SDL_bool need_server_info_refresh; -} PulseHotplugEvents; - -// This is called when PulseAudio has a device connected/removed/changed. +// This is called when PulseAudio has a device connected/removed/changed. */ static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *data) { - // We can't block in here for new ServerInfo, but we need that info to resolve first, since we can - // migrate default devices during a removal, as long as we know the new default. So just queue up - // the data and let HotplugThread handle it, where we can call WaitForPulseOperation. - PulseHotplugEvents *events = (PulseHotplugEvents *) data; - if (events->allocated_events <= events->num_events) { - const int new_allocation = events->num_events + 16; - void *ptr = SDL_realloc(events->events, new_allocation * sizeof (PulseHotplugEvent)); - if (!ptr) { - return; // oh well. - } - events->events = (PulseHotplugEvent *) ptr; - events->allocated_events = new_allocation; - } - events->events[events->num_events].t = t; - events->events[events->num_events].idx = idx; - events->num_events++; - + const SDL_bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW); + const SDL_bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE); const SDL_bool changed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE); - if (changed) { - events->need_server_info_refresh = SDL_TRUE; + + if (added || removed || changed) { /* we only care about add/remove events. */ + const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK); + const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); + + if (changed) { + PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL)); + } + + /* adds need sink details from the PulseAudio server. Another callback... + (just unref all these operations right away, because we aren't going to wait on them + and their callbacks will handle any work, so they can free as soon as that happens.) */ + if ((added || changed) && sink) { + PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkInfoCallback, (void *)((intptr_t)added))); + } else if ((added || changed) && source) { + PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, (void *)((intptr_t)added))); + } else if (removed && (sink || source)) { + // removes we can handle just with the device index. + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)idx + 1)); + if (device) { + SDL_UnlockMutex(device->lock); // AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. + SDL_AudioDeviceDisconnected(device); + } + } } PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } -static void CheckDefaultDevice(int prev_default, int new_default) +static void CheckDefaultDevice(uint32_t *prev_default, uint32_t new_default) { - if (prev_default != new_default) { + if (*prev_default != new_default) { SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)new_default + 1)); if (device) { SDL_UnlockMutex(device->lock); + *prev_default = new_default; SDL_DefaultAudioDeviceChanged(device); } } @@ -865,78 +859,25 @@ static void CheckDefaultDevice(int prev_default, int new_default) // this runs as a thread while the Pulse target is initialized to catch hotplug events. static int SDLCALL HotplugThread(void *data) { - pa_operation *op; + uint32_t prev_default_sink_index = default_sink_index; + uint32_t prev_default_source_index = default_source_index; SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); - - PulseHotplugEvents events; - SDL_zero(events); - PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); - PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, HotplugCallback, &events); - WaitForPulseOperation(PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL)); + PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, HotplugCallback, NULL); + WaitForPulseOperation(PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, NULL, NULL)); while (SDL_AtomicGet(&pulseaudio_hotplug_thread_active)) { PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); - if (events.need_server_info_refresh) { - events.need_server_info_refresh = SDL_FALSE; - WaitForPulseOperation(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL)); - } else if (events.num_events) { // don't process events until we didn't get anything new that needs a server info update. - const uint32_t prev_default_sink_index = default_sink_index; - const uint32_t prev_default_source_index = default_source_index; - PulseHotplugEvents eventscpy; - SDL_memcpy(&eventscpy, &events, sizeof (PulseHotplugEvents)); - SDL_zero(events); // make sure the current array isn't touched in case new events fire. - // process adds and changes first, so we definitely have default device changes processed. Then remove devices after. - for (int i = 0; i < eventscpy.num_events; i++) { - const pa_subscription_event_type_t t = eventscpy.events[i].t; - const uint32_t idx = eventscpy.events[i].idx; - const SDL_bool added = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW); - const SDL_bool changed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE); - const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK); - const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); - if (added || changed) { - if (sink) { - WaitForPulseOperation(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkInfoCallback, (void *)((intptr_t)added))); - } else if (source) { - WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, (void *)((intptr_t)added))); - } - } - } - - // don't hold the pulse lock during this, since it could deadlock vs a playing device that we're about to lock here. - PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); - - CheckDefaultDevice(prev_default_sink_index, default_sink_index); - CheckDefaultDevice(prev_default_source_index, default_source_index); - - // okay, default devices are sane, migrations were made if necessary...now remove lost devices. - for (int i = 0; i < eventscpy.num_events; i++) { - const pa_subscription_event_type_t t = eventscpy.events[i].t; - const uint32_t idx = eventscpy.events[i].idx; - const SDL_bool removed = ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE); - const SDL_bool sink = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK); - const SDL_bool source = ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); - if (removed && (sink || source)) { - SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)idx + 1)); - if (device) { - SDL_UnlockMutex(device->lock); // AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. - SDL_AudioDeviceDisconnected(device); - } - } - } - - PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); - - SDL_free(eventscpy.events); - } + // Update default devices; don't hold the pulse lock during this, since it could deadlock vs a playing device that we're about to lock here. + PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); + CheckDefaultDevice(&prev_default_sink_index, default_sink_index); + CheckDefaultDevice(&prev_default_source_index, default_source_index); + PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); } PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, NULL, NULL); // Don't fire HotplugCallback again. PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); - - SDL_free(events.events); // should be NULL, but just in case. - return 0; } diff --git a/test/loopwave.c b/test/loopwave.c index 997c14f798..6a07f49004 100644 --- a/test/loopwave.c +++ b/test/loopwave.c @@ -103,14 +103,6 @@ open_audio(void) SDL_SetAudioStreamGetCallback(stream, fillerup, NULL); } -#ifndef __EMSCRIPTEN__ -static void reopen_audio(void) -{ - close_audio(); - open_audio(); -} -#endif - static int done = 0; @@ -191,8 +183,6 @@ int main(int argc, char *argv[]) open_audio(); - SDL_FlushEvents(SDL_EVENT_AUDIO_DEVICE_ADDED, SDL_EVENT_AUDIO_DEVICE_REMOVED); - #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(loop, 0, 1); #else @@ -203,10 +193,6 @@ int main(int argc, char *argv[]) if (event.type == SDL_EVENT_QUIT) { done = 1; } - if ((event.type == SDL_EVENT_AUDIO_DEVICE_ADDED && !event.adevice.iscapture) || - (event.type == SDL_EVENT_AUDIO_DEVICE_REMOVED && !event.adevice.iscapture && event.adevice.which == device)) { - reopen_audio(); - } } SDL_Delay(100); From b03c493fc41fedbdaccd57b379ddbe3345d76d52 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 00:34:02 -0400 Subject: [PATCH 021/138] test: Updated testmultiaudio to new SDL3 audio API --- test/testmultiaudio.c | 176 ++++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 93 deletions(-) diff --git a/test/testmultiaudio.c b/test/testmultiaudio.c index d2ac1cdec2..8a26c6f55a 100644 --- a/test/testmultiaudio.c +++ b/test/testmultiaudio.c @@ -27,85 +27,57 @@ static SDL_AudioSpec spec; static Uint8 *sound = NULL; /* Pointer to wave data */ static Uint32 soundlen = 0; /* Length of wave data */ -typedef struct -{ - SDL_AudioDeviceID dev; - int soundpos; - SDL_AtomicInt done; -} callback_data; +/* these have to be in globals so the Emscripten port can see them in the mainloop. :/ */ +static SDL_AudioDeviceID device = 0; +static SDL_AudioStream *stream = NULL; -static callback_data cbd[64]; - -static void SDLCALL -play_through_once(void *arg, Uint8 *stream, int len) -{ - callback_data *cbdata = (callback_data *)arg; - Uint8 *waveptr = sound + cbdata->soundpos; - int waveleft = soundlen - cbdata->soundpos; - int cpy = len; - if (cpy > waveleft) { - cpy = waveleft; - } - - SDL_memcpy(stream, waveptr, cpy); - len -= cpy; - cbdata->soundpos += cpy; - if (len > 0) { - stream += cpy; - SDL_memset(stream, spec.silence, len); - SDL_AtomicSet(&cbdata->done, 1); - } -} #ifdef __EMSCRIPTEN__ static void loop(void) { - if (SDL_AtomicGet(&cbd[0].done)) { - emscripten_cancel_main_loop(); - SDL_PauseAudioDevice(cbd[0].dev); - SDL_CloseAudioDevice(cbd[0].dev); + if (SDL_GetAudioStreamAvailable(stream) == 0) { + SDL_Log("done."); + SDL_CloseAudioDevice(device); + SDL_DestroyAudioStream(stream); SDL_free(sound); SDL_Quit(); + emscripten_cancel_main_loop(); } } #endif static void -test_multi_audio(int devcount) +test_multi_audio(SDL_AudioDeviceID *devices, int devcount) { int keep_going = 1; + SDL_AudioStream **streams = NULL; int i; -#ifdef __ANDROID__ +#ifdef __ANDROID__ /* !!! FIXME: maybe always create a window, in the SDLTest layer, so these #ifdefs don't have to be here? */ SDL_Event event; /* Create a Window to get fully initialized event processing for testing pause on Android. */ SDL_CreateWindow("testmultiaudio", 320, 240, 0); #endif - if (devcount > 64) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Too many devices (%d), clamping to 64...\n", - devcount); - devcount = 64; - } - - spec.callback = play_through_once; - for (i = 0; i < devcount; i++) { - const char *devname = SDL_GetAudioDeviceName(i, 0); - SDL_Log("playing on device #%d: ('%s')...", i, devname); + char *devname = SDL_GetAudioDeviceName(devices[i]); - SDL_memset(&cbd[0], '\0', sizeof(callback_data)); - spec.userdata = &cbd[0]; - cbd[0].dev = SDL_OpenAudioDevice(devname, 0, &spec, NULL, 0); - if (cbd[0].dev == 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Open device failed: %s\n", SDL_GetError()); + SDL_Log("Playing on device #%d of %d: id=%u, name='%s'...", i, devcount, (unsigned int) devices[i], devname); + + device = SDL_OpenAudioDevice(devices[i], &spec); + if (device == 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Open device failed: %s", SDL_GetError()); + } else if ((stream = SDL_CreateAndBindAudioStream(device, &spec)) == NULL) { /* we can reuse these, but we'll just make one each time for now. */ + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Audio stream creation failed: %s", SDL_GetError()); + SDL_CloseAudioDevice(device); } else { - SDL_PlayAudioDevice(cbd[0].dev); + SDL_PutAudioStreamData(stream, sound, soundlen); + SDL_FlushAudioStream(stream); #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(loop, 0, 1); #else - while (!SDL_AtomicGet(&cbd[0].done)) { + while (SDL_GetAudioStreamAvailable(stream) > 0) { #ifdef __ANDROID__ /* Empty queue, some application events would prevent pause. */ while (SDL_PollEvent(&event)) { @@ -113,61 +85,77 @@ test_multi_audio(int devcount) #endif SDL_Delay(100); } - SDL_PauseAudioDevice(cbd[0].dev); #endif - SDL_Log("done.\n"); - SDL_CloseAudioDevice(cbd[0].dev); + SDL_Log("done."); + SDL_CloseAudioDevice(device); + SDL_DestroyAudioStream(stream); } + SDL_free(devname); + stream = NULL; } - SDL_memset(cbd, '\0', sizeof(cbd)); - - SDL_Log("playing on all devices...\n"); - for (i = 0; i < devcount; i++) { - const char *devname = SDL_GetAudioDeviceName(i, 0); - spec.userdata = &cbd[i]; - cbd[i].dev = SDL_OpenAudioDevice(devname, 0, &spec, NULL, 0); - if (cbd[i].dev == 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Open device %d failed: %s\n", i, SDL_GetError()); - } - } - - for (i = 0; i < devcount; i++) { - if (cbd[i].dev) { - SDL_PlayAudioDevice(cbd[i].dev); - } - } - - while (keep_going) { - keep_going = 0; + /* note that Emscripten currently doesn't run this part (but maybe only has a single audio device anyhow?) */ + SDL_Log("Playing on all devices...\n"); + streams = (SDL_AudioStream **) SDL_calloc(devcount, sizeof (SDL_AudioStream *)); + if (!streams) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory!"); + } else { for (i = 0; i < devcount; i++) { - if ((cbd[i].dev) && (!SDL_AtomicGet(&cbd[i].done))) { - keep_going = 1; + char *devname = SDL_GetAudioDeviceName(devices[i]); + device = SDL_OpenAudioDevice(devices[i], &spec); + if (device == 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Open device %d of %d (id=%u, name='%s') failed: %s\n", i, devcount, (unsigned int) devices[i], devname, SDL_GetError()); + } + SDL_free(devname); + devices[i] = device; /* just replace the physical device ID with the newly-opened logical device ID. */ + if (device) { + SDL_PauseAudioDevice(device); /* hold while we set up all the streams. */ + streams[i] = SDL_CreateAndBindAudioStream(device, &spec); + if (streams[i] == NULL) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Audio stream creation failed for device %d of %d: %s", i, devcount, SDL_GetError()); + } else { + SDL_PutAudioStreamData(streams[i], sound, soundlen); + SDL_FlushAudioStream(streams[i]); + } } } -#ifdef __ANDROID__ - /* Empty queue, some application events would prevent pause. */ - while (SDL_PollEvent(&event)) { + + /* try to start all the devices about the same time. SDL does not guarantee sync across physical devices. */ + for (i = 0; i < devcount; i++) { + if (devices[i]) { + SDL_UnpauseAudioDevice(devices[i]); + } } + + while (keep_going) { + keep_going = 0; + for (i = 0; i < devcount; i++) { + if (streams[i] && (SDL_GetAudioStreamAvailable(streams[i]) > 0)) { + keep_going = 1; + } + } +#ifdef __ANDROID__ + /* Empty queue, some application events would prevent pause. */ + while (SDL_PollEvent(&event)) {} #endif - SDL_Delay(100); - } - -#ifndef __EMSCRIPTEN__ - for (i = 0; i < devcount; i++) { - if (cbd[i].dev) { - SDL_PauseAudioDevice(cbd[i].dev); - SDL_CloseAudioDevice(cbd[i].dev); + SDL_Delay(100); } + + for (i = 0; i < devcount; i++) { + SDL_CloseAudioDevice(devices[i]); + SDL_DestroyAudioStream(streams[i]); + } + + SDL_free(streams); } SDL_Log("All done!\n"); -#endif } int main(int argc, char **argv) { + SDL_AudioDeviceID *devices = NULL; int devcount = 0; int i; char *filename = NULL; @@ -212,20 +200,21 @@ int main(int argc, char **argv) filename = GetResourceFilename(filename, "sample.wav"); - devcount = SDL_GetNumAudioDevices(0); - if (devcount < 1) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Don't see any specific audio devices!\n"); + devices = SDL_GetAudioOutputDevices(&devcount); + if (devices == NULL) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Don't see any specific audio devices!"); } else { /* Load the wave file into memory */ - if (SDL_LoadWAV(filename, &spec, &sound, &soundlen) == NULL) { + if (SDL_LoadWAV(filename, &spec, &sound, &soundlen) == -1) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError()); } else { - test_multi_audio(devcount); + test_multi_audio(devices, devcount); SDL_free(sound); } } + SDL_free(devices); SDL_free(filename); SDL_Quit(); @@ -233,3 +222,4 @@ int main(int argc, char **argv) return 0; } + From eee407caf89949287f4db355df60c2a1f7869c7e Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 00:36:19 -0400 Subject: [PATCH 022/138] docs: migration guide note that SDL_LoadWAV has a different return type. --- docs/README-migration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README-migration.md b/docs/README-migration.md index 1ce3bb34f1..81b5bcb21a 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -172,6 +172,8 @@ SDL_FreeWAV has been removed and calls can be replaced with SDL_free. SDL_LoadWAV() is a proper function now and no longer a macro (but offers the same functionality otherwise). +SDL_LoadWAV_RW() and SDL_LoadWAV() return an int now: zero on success, -1 on error, like most of SDL. They no longer return a pointer to an SDL_AudioSpec. + SDL_AudioCVT interface has been removed, the SDL_AudioStream interface (for audio supplied in pieces) or the new SDL_ConvertAudioSamples() function (for converting a complete audio buffer in one call) can be used instead. Code that used to look like this: From f598626e46e2ecc555e651c5b86598a33a30bafa Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 00:46:33 -0400 Subject: [PATCH 023/138] test: loopwave shouldn't use an audiostream callback. --- test/loopwave.c | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/test/loopwave.c b/test/loopwave.c index 6a07f49004..5b404a5cd4 100644 --- a/test/loopwave.c +++ b/test/loopwave.c @@ -37,28 +37,11 @@ static struct static SDL_AudioDeviceID device; static SDL_AudioStream *stream; -static void SDLCALL -fillerup(SDL_AudioStream *stream, int len, void *unused) +static void fillerup(void) { - Uint8 *waveptr; - int waveleft; - - /*SDL_Log("CALLBACK WANTS %d MORE BYTES!", len);*/ - - /* Set up the pointers */ - waveptr = wave.sound + wave.soundpos; - waveleft = wave.soundlen - wave.soundpos; - - /* Go! */ - while (waveleft <= len) { - SDL_PutAudioStreamData(stream, waveptr, waveleft); - len -= waveleft; - waveptr = wave.sound; - waveleft = wave.soundlen; - wave.soundpos = 0; + if (SDL_GetAudioStreamAvailable(stream) < (wave.soundlen / 2)) { + SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen); } - SDL_PutAudioStreamData(stream, waveptr, len); - wave.soundpos += len; } /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ @@ -99,8 +82,6 @@ open_audio(void) SDL_free(wave.sound); quit(2); } - - SDL_SetAudioStreamGetCallback(stream, fillerup, NULL); } @@ -111,7 +92,7 @@ static int done = 0; #ifdef __EMSCRIPTEN__ static void loop(void) { - if (done || (SDL_GetAudioDeviceStatus(device) != SDL_AUDIO_PLAYING)) { + if (done) { emscripten_cancel_main_loop(); } else { fillerup(); @@ -195,6 +176,7 @@ int main(int argc, char *argv[]) } } + fillerup(); SDL_Delay(100); } #endif From 0e5a1d4f294a2787c04d7d73285c6592ad154a6b Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 00:48:05 -0400 Subject: [PATCH 024/138] pulseaudio: Removed debug logging. --- src/audio/pulseaudio/SDL_pulseaudio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index c9f66bb251..94e5ef445f 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -796,13 +796,13 @@ static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_la static void ServerInfoCallback(pa_context *c, const pa_server_info *i, void *data) { if (!default_sink_path || (SDL_strcmp(i->default_sink_name, default_sink_path) != 0)) { - printf("DEFAULT SINK PATH CHANGED TO '%s'\n", i->default_sink_name); + /*printf("DEFAULT SINK PATH CHANGED TO '%s'\n", i->default_sink_name);*/ SDL_free(default_sink_path); default_sink_path = SDL_strdup(i->default_sink_name); } if (!default_source_path || (SDL_strcmp(i->default_source_name, default_source_path) != 0)) { - printf("DEFAULT SOURCE PATH CHANGED TO '%s'\n", i->default_source_name); + /*printf("DEFAULT SOURCE PATH CHANGED TO '%s'\n", i->default_source_name);*/ SDL_free(default_source_path); default_source_path = SDL_strdup(i->default_source_name); } From 47b0321ebfffe09a2a0205ffdab65bee3edaa800 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 00:59:33 -0400 Subject: [PATCH 025/138] test: Removed loopwavequeue.c; obsolete in SDL3. --- test/CMakeLists.txt | 1 - test/README | 1 - test/loopwavequeue.c | 183 ------------------------------------------- 3 files changed, 185 deletions(-) delete mode 100644 test/loopwavequeue.c diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 679a602aa5..418ed76254 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -119,7 +119,6 @@ endif() add_sdl_test_executable(checkkeys SOURCES checkkeys.c) add_sdl_test_executable(checkkeysthreads SOURCES checkkeysthreads.c) add_sdl_test_executable(loopwave NEEDS_RESOURCES TESTUTILS SOURCES loopwave.c) -add_sdl_test_executable(loopwavequeue NEEDS_RESOURCES TESTUTILS SOURCES loopwavequeue.c) add_sdl_test_executable(testsurround SOURCES testsurround.c) add_sdl_test_executable(testresample NEEDS_RESOURCES SOURCES testresample.c) add_sdl_test_executable(testaudioinfo SOURCES testaudioinfo.c) diff --git a/test/README b/test/README index 914c114304..4327b1d4f3 100644 --- a/test/README +++ b/test/README @@ -3,7 +3,6 @@ These are test programs for the SDL library: checkkeys Watch the key events to check the keyboard loopwave Audio test -- loop playing a WAV file - loopwavequeue Audio test -- loop playing a WAV file with SDL_QueueAudio testsurround Audio test -- play test tone on each audio channel testaudioinfo Lists audio device capabilities testerror Tests multi-threaded error handling diff --git a/test/loopwavequeue.c b/test/loopwavequeue.c deleted file mode 100644 index e8c4daaabd..0000000000 --- a/test/loopwavequeue.c +++ /dev/null @@ -1,183 +0,0 @@ -/* - Copyright (C) 1997-2023 Sam Lantinga - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely. -*/ - -/* Program to load a wave file and loop playing it using SDL sound queueing */ - -#include - -#ifdef __EMSCRIPTEN__ -#include -#endif - -#include -#include -#include - -#ifdef HAVE_SIGNAL_H -#include -#endif - -#include "testutils.h" - -static struct -{ - SDL_AudioSpec spec; - Uint8 *sound; /* Pointer to wave data */ - Uint32 soundlen; /* Length of wave data */ -} wave; - -/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ -static void -quit(int rc) -{ - SDL_Quit(); - /* Let 'main()' return normally */ - if (rc != 0) { - exit(rc); - } -} - -static int done = 0; -static void poked(int sig) -{ - done = 1; -} - -static SDL_AudioDeviceID g_audio_id = 0; - -static void loop(void) -{ -#ifdef __EMSCRIPTEN__ - if (done || (SDL_GetAudioDeviceStatus(g_audio_id) != SDL_AUDIO_PLAYING)) { - emscripten_cancel_main_loop(); - } else -#endif - { - /* The device from SDL_OpenAudio() is always device #1. */ - const Uint32 queued = SDL_GetQueuedAudioSize(g_audio_id); - SDL_Log("Device has %u bytes queued.\n", (unsigned int)queued); - if (queued <= 8192) { /* time to requeue the whole thing? */ - if (SDL_QueueAudio(g_audio_id, wave.sound, wave.soundlen) == 0) { - SDL_Log("Device queued %u more bytes.\n", (unsigned int)wave.soundlen); - } else { - SDL_Log("Device FAILED to queue %u more bytes: %s\n", (unsigned int)wave.soundlen, SDL_GetError()); - } - } - } -} - -int main(int argc, char *argv[]) -{ - int i; - char *filename = NULL; - SDLTest_CommonState *state; - - /* Initialize test framework */ - state = SDLTest_CommonCreateState(argv, 0); - if (state == NULL) { - return 1; - } - - /* Enable standard application logging */ - SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); - - /* Parse commandline */ - for (i = 1; i < argc;) { - int consumed; - - consumed = SDLTest_CommonArg(state, i); - if (!consumed) { - if (!filename) { - filename = argv[i]; - consumed = 1; - } - } - if (consumed <= 0) { - static const char *options[] = { "[sample.wav]", NULL }; - SDLTest_CommonLogUsage(state, argv[0], options); - exit(1); - } - - i += consumed; - } - - /* Load the SDL library */ - if (SDL_Init(SDL_INIT_AUDIO) < 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError()); - return 1; - } - - filename = GetResourceFilename(filename, "sample.wav"); - - if (filename == NULL) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s\n", SDL_GetError()); - quit(1); - } - - /* Load the wave file into memory */ - if (SDL_LoadWAV(filename, &wave.spec, &wave.sound, &wave.soundlen) == NULL) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError()); - quit(1); - } - - wave.spec.callback = NULL; /* we'll push audio. */ - -#ifdef HAVE_SIGNAL_H - /* Set the signals */ -#ifdef SIGHUP - (void)signal(SIGHUP, poked); -#endif - (void)signal(SIGINT, poked); -#ifdef SIGQUIT - (void)signal(SIGQUIT, poked); -#endif - (void)signal(SIGTERM, poked); -#endif /* HAVE_SIGNAL_H */ - - /* Initialize fillerup() variables */ - g_audio_id = SDL_OpenAudioDevice(NULL, 0, &wave.spec, NULL, 0); - - if (!g_audio_id) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s\n", SDL_GetError()); - SDL_free(wave.sound); - quit(2); - } - - /*static x[99999]; SDL_QueueAudio(1, x, sizeof (x));*/ - - /* Let the audio run */ - SDL_PlayAudioDevice(g_audio_id); - - done = 0; - - /* Note that we stuff the entire audio buffer into the queue in one - shot. Most apps would want to feed it a little at a time, as it - plays, but we're going for simplicity here. */ - -#ifdef __EMSCRIPTEN__ - emscripten_set_main_loop(loop, 0, 1); -#else - while (!done && (SDL_GetAudioDeviceStatus(g_audio_id) == SDL_AUDIO_PLAYING)) { - loop(); - - SDL_Delay(100); /* let it play for a while. */ - } -#endif - - /* Clean up on signal */ - SDL_CloseAudioDevice(g_audio_id); - SDL_free(wave.sound); - SDL_free(filename); - SDL_Quit(); - SDLTest_CommonDestroyState(state); - return 0; -} From 323ecce1234becdc0568979718fe32e2a1b1a91b Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 01:34:30 -0400 Subject: [PATCH 026/138] docs: Added migration note about SDL_AUDIODEVICEREMOVED. --- docs/README-migration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README-migration.md b/docs/README-migration.md index 81b5bcb21a..e2f42cd81d 100644 --- a/docs/README-migration.md +++ b/docs/README-migration.md @@ -222,8 +222,10 @@ If you need to convert U16 audio data to a still-supported format at runtime, th All remaining `AUDIO_*` symbols have been renamed to `SDL_AUDIO_*` for API consistency, but othewise are identical in value and usage. In SDL2, SDL_AudioStream would convert/resample audio data during input (via SDL_AudioStreamPut). In SDL3, it does this work when requesting audio (via SDL_GetAudioStreamData, which would have been SDL_AudioStreamGet in SDL2). The way you use an AudioStream is roughly the same, just be aware that the workload moved to a different phase. + In SDL2, SDL_AudioStreamAvailable() returns 0 if passed a NULL stream. In SDL3, the equivalent SDL_GetAudioStreamAvailable() call returns -1 and sets an error string, which matches other audiostream APIs' behavior. +In SDL2, SDL_AUDIODEVICEREMOVED events would fire for open devices with the `which` field set to the SDL_AudioDeviceID of the lost device, and in later SDL2 releases, would also fire this event with a `which` field of zero for unopened devices, to signify that the app might want to refresh the available device list. In SDL3, this event works the same, except it won't ever fire with a zero; in this case it'll return the physical device's SDL_AudioDeviceID. Any still-open SDL_AudioDeviceIDs generated from this device with SDL_OpenAudioDevice() will also fire a separate event. The following functions have been renamed: * SDL_AudioStreamAvailable() => SDL_GetAudioStreamAvailable() From 2be5f726d4cd7b0cd136c904cb4630ae8c30e48a Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 01:35:03 -0400 Subject: [PATCH 027/138] audio: Removed debug logging. --- src/audio/SDL_audio.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 4daa15c3b7..9dfae71f57 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -307,7 +307,6 @@ static void DisconnectLogicalAudioDevice(SDL_LogicalAudioDevice *logdev) if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_REMOVED)) { SDL_Event event; SDL_zero(event); -SDL_Log("Sending event about loss of logical device #%u", (unsigned int) logdev->instance_id); event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED; event.common.timestamp = 0; event.adevice.which = logdev->instance_id; From f883b9fc6400c08b1432412d413dd32a5a25e9ee Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 01:35:12 -0400 Subject: [PATCH 028/138] test: Updated testaudiohotplug to SDL3 audio API. --- test/testaudiohotplug.c | 59 +++++++++++++---------------------------- 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/test/testaudiohotplug.c b/test/testaudiohotplug.c index 61047a5620..b919aa371b 100644 --- a/test/testaudiohotplug.c +++ b/test/testaudiohotplug.c @@ -31,9 +31,6 @@ static SDL_AudioSpec spec; static Uint8 *sound = NULL; /* Pointer to wave data */ static Uint32 soundlen = 0; /* Length of wave data */ -static int posindex = 0; -static Uint32 positions[64]; - static SDLTest_CommonState *state; /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ @@ -48,31 +45,6 @@ quit(int rc) } } -static void SDLCALL -fillerup(void *_pos, Uint8 *stream, int len) -{ - Uint32 pos = *((Uint32 *)_pos); - Uint8 *waveptr; - int waveleft; - - /* Set up the pointers */ - waveptr = sound + pos; - waveleft = soundlen - pos; - - /* Go! */ - while (waveleft <= len) { - SDL_memcpy(stream, waveptr, waveleft); - stream += waveleft; - len -= waveleft; - waveptr = sound; - waveleft = soundlen; - pos = 0; - } - SDL_memcpy(stream, waveptr, len); - pos += len; - *((Uint32 *)_pos) = pos; -} - static int done = 0; static void poked(int sig) @@ -97,28 +69,35 @@ static void iteration(void) done = 1; } } else if (e.type == SDL_EVENT_AUDIO_DEVICE_ADDED) { - int index = e.adevice.which; - int iscapture = e.adevice.iscapture; - const char *name = SDL_GetAudioDeviceName(index, iscapture); + const SDL_AudioDeviceID which = (SDL_AudioDeviceID ) e.adevice.which; + const SDL_bool iscapture = e.adevice.iscapture ? SDL_TRUE : SDL_FALSE; + char *name = SDL_GetAudioDeviceName(which); if (name != NULL) { - SDL_Log("New %s audio device at index %u: %s\n", devtypestr(iscapture), (unsigned int)index, name); + SDL_Log("New %s audio device at id %u: %s", devtypestr(iscapture), (unsigned int)which, name); } else { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Got new %s device at index %u, but failed to get the name: %s\n", - devtypestr(iscapture), (unsigned int)index, SDL_GetError()); + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Got new %s device, id %u, but failed to get the name: %s", + devtypestr(iscapture), (unsigned int)which, SDL_GetError()); continue; } if (!iscapture) { - positions[posindex] = 0; - spec.userdata = &positions[posindex++]; - spec.callback = fillerup; - dev = SDL_OpenAudioDevice(name, 0, &spec, NULL, 0); + dev = SDL_OpenAudioDevice(which, &spec); if (!dev) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open '%s': %s\n", name, SDL_GetError()); } else { + SDL_AudioStream *stream; SDL_Log("Opened '%s' as %u\n", name, (unsigned int)dev); - SDL_PlayAudioDevice(dev); + stream = SDL_CreateAndBindAudioStream(dev, &spec); + if (!stream) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create/bind an audio stream to %u ('%s'): %s", (unsigned int) dev, name, SDL_GetError()); + SDL_CloseAudioDevice(dev); + } + /* !!! FIXME: laziness, this used to loop the audio, but we'll just play it once for now on each connect. */ + SDL_PutAudioStreamData(stream, sound, soundlen); + SDL_FlushAudioStream(stream); + /* !!! FIXME: this is leaking the stream for now. We'll wire it up to a dictionary or whatever later. */ } } + SDL_free(name); } else if (e.type == SDL_EVENT_AUDIO_DEVICE_REMOVED) { dev = (SDL_AudioDeviceID)e.adevice.which; SDL_Log("%s device %u removed.\n", devtypestr(e.adevice.iscapture), (unsigned int)dev); @@ -194,7 +173,7 @@ int main(int argc, char *argv[]) } /* Load the wave file into memory */ - if (SDL_LoadWAV(filename, &spec, &sound, &soundlen) == NULL) { + if (SDL_LoadWAV(filename, &spec, &sound, &soundlen) == -1) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError()); quit(1); } From 5d4e9e5f80dbb5f62d5da31017f6c482c6ebc021 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 01:44:12 -0400 Subject: [PATCH 029/138] test: Updated testaudiostreamdynamicresample to SDL3 audio API. --- test/testaudiostreamdynamicresample.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/test/testaudiostreamdynamicresample.c b/test/testaudiostreamdynamicresample.c index 6660018114..609bd646dd 100644 --- a/test/testaudiostreamdynamicresample.c +++ b/test/testaudiostreamdynamicresample.c @@ -16,13 +16,6 @@ #include #include -static void SDLCALL audio_callback(void *userdata, Uint8 * stream, int len) -{ - SDL_AudioStream *audiostream = (SDL_AudioStream *) userdata; - SDL_memset(stream, 0, len); - SDL_GetAudioStreamData(audiostream, stream, len); -} - int main(int argc, char *argv[]) { SDL_Window *window; @@ -42,18 +35,17 @@ int main(int argc, char *argv[]) renderer = SDL_CreateRenderer(window, NULL, 0); SDL_LoadWAV("sample.wav", &spec, &audio_buf, &audio_len); - stream = SDL_CreateAudioStream(spec.format, spec.channels, spec.freq, spec.format, spec.channels, spec.freq); + stream = SDL_CreateAudioStream(&spec, &spec); SDL_PutAudioStreamData(stream, audio_buf, audio_len); - spec.callback = audio_callback; - spec.userdata = stream; - device = SDL_OpenAudioDevice(NULL, SDL_FALSE, &spec, NULL, 0); - SDL_PlayAudioDevice(device); + device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec); + SDL_BindAudioStream(device, stream); slider_fill_area.w /= 2; while (!done) { SDL_Event e; int newmultiplier = multiplier; + while (SDL_PollEvent(&e)) { if (e.type == SDL_EVENT_QUIT) { done = 1; @@ -74,6 +66,7 @@ int main(int argc, char *argv[]) } if (multiplier != newmultiplier) { + SDL_AudioSpec newspec; char title[64]; int newfreq = spec.freq; @@ -94,13 +87,13 @@ int main(int argc, char *argv[]) newfreq = spec.freq + (int) (spec.freq * (multiplier / 100.0f)); } /* SDL_Log("newfreq=%d multiplier=%d\n", newfreq, multiplier); */ - SDL_LockAudioDevice(device); - SDL_SetAudioStreamFormat(stream, spec.format, spec.channels, newfreq, spec.format, spec.channels, spec.freq); - SDL_UnlockAudioDevice(device); + SDL_memcpy(&newspec, &spec, sizeof (spec)); + newspec.freq = newfreq; + SDL_SetAudioStreamFormat(stream, &newspec, NULL); } /* keep it looping. */ - if (SDL_GetAudioStreamAvailable(stream) < (1024 * 100)) { + if (SDL_GetAudioStreamAvailable(stream) < (audio_len / 2)) { SDL_PutAudioStreamData(stream, audio_buf, audio_len); } @@ -116,6 +109,7 @@ int main(int argc, char *argv[]) SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_CloseAudioDevice(device); + SDL_DestroyAudioStream(stream); SDL_free(audio_buf); SDL_Quit(); return 0; From 924f370bd7c2f849073ad43ad2320779c0ce28fc Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 11:13:23 -0400 Subject: [PATCH 030/138] pulseaudio: Fix deadlock in HotplugThread. If we wait for context subscription to finish, we might miss the signal telling us to terminate the thread...this can happen if an app initializes the audio subsystem and then quits immediately. So just go right into the main loop of the thread; the subscription will finish when it finishes and then events will flow. --- src/audio/pulseaudio/SDL_pulseaudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 94e5ef445f..85dc8b5977 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -865,7 +865,7 @@ static int SDLCALL HotplugThread(void *data) SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, HotplugCallback, NULL); - WaitForPulseOperation(PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, NULL, NULL)); + PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, NULL, NULL)); while (SDL_AtomicGet(&pulseaudio_hotplug_thread_active)) { PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); From 62cf24eeb902b9c48895061955ac05f5a4188fc1 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 11:21:08 -0400 Subject: [PATCH 031/138] pulseaudio: Listen for server events in addition to sources and sinks. This gets us default device change notifications more efficiently, presumably. --- src/audio/pulseaudio/SDL_pulseaudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 85dc8b5977..c5189b7652 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -865,7 +865,7 @@ static int SDLCALL HotplugThread(void *data) SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, HotplugCallback, NULL); - PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, NULL, NULL)); + PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL)); while (SDL_AtomicGet(&pulseaudio_hotplug_thread_active)) { PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); From 883aee32c5d508d586b2204e5619ff00e8034492 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 11:24:24 -0400 Subject: [PATCH 032/138] audio: Let default formats differ for output and capture devices. --- src/audio/SDL_audio.c | 28 +++++++++++++++++----------- src/audio/SDL_sysaudio.h | 9 ++++++--- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 9dfae71f57..cdd8dbb69b 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -274,15 +274,19 @@ static SDL_AudioDevice *CreateAudioOutputDevice(const char *name, const SDL_Audi // The audio backends call this when a new device is plugged in. SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, const SDL_AudioSpec *inspec, void *handle) { + const SDL_AudioFormat default_format = iscapture ? DEFAULT_AUDIO_CAPTURE_FORMAT : DEFAULT_AUDIO_OUTPUT_FORMAT; + const int default_channels = iscapture ? DEFAULT_AUDIO_CAPTURE_CHANNELS : DEFAULT_AUDIO_OUTPUT_CHANNELS; + const int default_freq = iscapture ? DEFAULT_AUDIO_CAPTURE_FREQUENCY : DEFAULT_AUDIO_OUTPUT_FREQUENCY; + SDL_AudioSpec spec; if (!inspec) { - spec.format = DEFAULT_AUDIO_FORMAT; - spec.channels = DEFAULT_AUDIO_CHANNELS; - spec.freq = DEFAULT_AUDIO_FREQUENCY; + spec.format = default_format; + spec.channels = default_channels; + spec.freq = default_freq; } else { - spec.format = (inspec->format != 0) ? inspec->format : DEFAULT_AUDIO_FORMAT; - spec.channels = (inspec->channels != 0) ? inspec->channels : DEFAULT_AUDIO_CHANNELS; - spec.freq = (inspec->freq != 0) ? inspec->freq : DEFAULT_AUDIO_FREQUENCY; + spec.format = (inspec->format != 0) ? inspec->format : default_format; + spec.channels = (inspec->channels != 0) ? inspec->channels : default_channels; + spec.freq = (inspec->freq != 0) ? inspec->freq : default_freq; } SDL_AudioDevice *device = iscapture ? CreateAudioCaptureDevice(name, &spec, handle) : CreateAudioOutputDevice(name, &spec, handle); @@ -1092,10 +1096,11 @@ static SDL_AudioFormat ParseAudioFormatString(const char *string) return 0; } -static void PrepareAudioFormat(SDL_AudioSpec *spec) +static void PrepareAudioFormat(SDL_bool iscapture, SDL_AudioSpec *spec) { if (spec->freq == 0) { - spec->freq = DEFAULT_AUDIO_FREQUENCY; + spec->freq = iscapture ? DEFAULT_AUDIO_CAPTURE_FREQUENCY : DEFAULT_AUDIO_OUTPUT_FREQUENCY; + const char *env = SDL_getenv("SDL_AUDIO_FREQUENCY"); // !!! FIXME: should be a hint? if (env != NULL) { const int val = SDL_atoi(env); @@ -1106,7 +1111,7 @@ static void PrepareAudioFormat(SDL_AudioSpec *spec) } if (spec->channels == 0) { - spec->channels = DEFAULT_AUDIO_CHANNELS; + spec->channels = iscapture ? DEFAULT_AUDIO_CAPTURE_CHANNELS : DEFAULT_AUDIO_OUTPUT_CHANNELS;; const char *env = SDL_getenv("SDL_AUDIO_CHANNELS"); if (env != NULL) { const int val = SDL_atoi(env); @@ -1118,7 +1123,7 @@ static void PrepareAudioFormat(SDL_AudioSpec *spec) if (spec->format == 0) { const SDL_AudioFormat val = ParseAudioFormatString(SDL_getenv("SDL_AUDIO_FORMAT")); - spec->format = (val != 0) ? val : DEFAULT_AUDIO_FORMAT; + spec->format = (val != 0) ? val : (iscapture ? DEFAULT_AUDIO_CAPTURE_FORMAT : DEFAULT_AUDIO_OUTPUT_FORMAT); } } @@ -1142,6 +1147,7 @@ void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device) // this expects the device lock to be held. static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec) { + SDL_assert(!device->is_opened); SDL_assert(device->logical_devices == NULL); // Just pretend to open a zombie device. It can still collect logical devices on the assumption they will all migrate when the default device is officially changed. @@ -1151,7 +1157,7 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec SDL_AudioSpec spec; SDL_memcpy(&spec, inspec, sizeof (SDL_AudioSpec)); - PrepareAudioFormat(&spec); + PrepareAudioFormat(device->iscapture, &spec); /* We allow the device format to change if it's better than the current settings (by various definitions of "better"). This prevents something low quality, like an old game using S8/8000Hz audio, from ruining a music thing playing at CD quality that tries to open later. diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index f6f17ffa78..38bfa2a3f0 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -50,10 +50,13 @@ extern void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_sam #define DEFAULT_INPUT_DEVNAME "System audio capture device" /* these are used when no better specifics are known. We default to CD audio quality. */ -#define DEFAULT_AUDIO_FORMAT SDL_AUDIO_S16 -#define DEFAULT_AUDIO_CHANNELS 2 -#define DEFAULT_AUDIO_FREQUENCY 44100 +#define DEFAULT_AUDIO_OUTPUT_FORMAT SDL_AUDIO_S16 +#define DEFAULT_AUDIO_OUTPUT_CHANNELS 2 +#define DEFAULT_AUDIO_OUTPUT_FREQUENCY 44100 +#define DEFAULT_AUDIO_CAPTURE_FORMAT SDL_AUDIO_S16 +#define DEFAULT_AUDIO_CAPTURE_CHANNELS 1 +#define DEFAULT_AUDIO_CAPTURE_FREQUENCY 44100 typedef struct SDL_AudioDevice SDL_AudioDevice; typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice; From bb1cbbd33a2fb6b865cdfd5af4e69de99191db6b Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 11:24:51 -0400 Subject: [PATCH 033/138] test: Update testaudioinfo for SDL3 audio API. --- test/testaudioinfo.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/test/testaudioinfo.c b/test/testaudioinfo.c index 54ad1b9635..636d4aaf7b 100644 --- a/test/testaudioinfo.c +++ b/test/testaudioinfo.c @@ -14,29 +14,30 @@ #include static void -print_devices(int iscapture) +print_devices(SDL_bool iscapture) { SDL_AudioSpec spec; const char *typestr = ((iscapture) ? "capture" : "output"); - int n = SDL_GetNumAudioDevices(iscapture); + int n = 0; + SDL_AudioDeviceID *devices = iscapture ? SDL_GetAudioCaptureDevices(&n) : SDL_GetAudioOutputDevices(&n); - SDL_Log("Found %d %s device%s:\n", n, typestr, n != 1 ? "s" : ""); - - if (n == -1) { - SDL_Log(" Driver can't detect specific %s devices.\n\n", typestr); + if (devices == NULL) { + SDL_Log(" Driver failed to report %s devices: %s\n\n", typestr, SDL_GetError()); } else if (n == 0) { SDL_Log(" No %s devices found.\n\n", typestr); } else { int i; + SDL_Log("Found %d %s device%s:\n", n, typestr, n != 1 ? "s" : ""); for (i = 0; i < n; i++) { - const char *name = SDL_GetAudioDeviceName(i, iscapture); + char *name = SDL_GetAudioDeviceName(devices[i]); if (name != NULL) { SDL_Log(" %d: %s\n", i, name); + SDL_free(name); } else { SDL_Log(" %d Error: %s\n", i, SDL_GetError()); } - if (SDL_GetAudioDeviceSpec(i, iscapture, &spec) == 0) { + if (SDL_GetAudioDeviceFormat(devices[i], &spec) == 0) { SDL_Log(" Sample Rate: %d\n", spec.freq); SDL_Log(" Channels: %d\n", spec.channels); SDL_Log(" SDL_AudioFormat: %X\n", spec.format); @@ -44,11 +45,11 @@ print_devices(int iscapture) } SDL_Log("\n"); } + SDL_free(devices); } int main(int argc, char **argv) { - char *deviceName = NULL; SDL_AudioSpec spec; int i; int n; @@ -88,24 +89,22 @@ int main(int argc, char **argv) SDL_Log("Using audio driver: %s\n\n", SDL_GetCurrentAudioDriver()); - print_devices(0); - print_devices(1); + print_devices(SDL_FALSE); + print_devices(SDL_TRUE); - if (SDL_GetDefaultAudioInfo(&deviceName, &spec, 0) < 0) { - SDL_Log("Error when calling SDL_GetDefaultAudioInfo: %s\n", SDL_GetError()); + if (SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec) < 0) { + SDL_Log("Error when calling SDL_GetAudioDeviceFormat(default output): %s\n", SDL_GetError()); } else { - SDL_Log("Default Output Name: %s\n", deviceName != NULL ? deviceName : "unknown"); - SDL_free(deviceName); + SDL_Log("Default Output Device:\n"); SDL_Log("Sample Rate: %d\n", spec.freq); SDL_Log("Channels: %d\n", spec.channels); SDL_Log("SDL_AudioFormat: %X\n", spec.format); } - if (SDL_GetDefaultAudioInfo(&deviceName, &spec, 1) < 0) { - SDL_Log("Error when calling SDL_GetDefaultAudioInfo: %s\n", SDL_GetError()); + if (SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_CAPTURE, &spec) < 0) { + SDL_Log("Error when calling SDL_GetAudioDeviceFormat(default capture): %s\n", SDL_GetError()); } else { - SDL_Log("Default Capture Name: %s\n", deviceName != NULL ? deviceName : "unknown"); - SDL_free(deviceName); + SDL_Log("Default Capture Device:\n"); SDL_Log("Sample Rate: %d\n", spec.freq); SDL_Log("Channels: %d\n", spec.channels); SDL_Log("SDL_AudioFormat: %X\n", spec.format); @@ -115,3 +114,4 @@ int main(int argc, char **argv) SDLTest_CommonDestroyState(state); return 0; } + From 7e700531c59bb83daccff9ed4d4afb98698d877e Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 14:54:05 -0400 Subject: [PATCH 034/138] audio: Allow SDL_OpenAudioDevice to accept a NULL spec. This means "I don't care what format I get at all" and will just use the device's current (and/or default) format. This can be useful, since audio streams cover the differences anyhow. --- src/audio/SDL_audio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index cdd8dbb69b..3d791677db 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -1156,7 +1156,7 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec } SDL_AudioSpec spec; - SDL_memcpy(&spec, inspec, sizeof (SDL_AudioSpec)); + SDL_memcpy(&spec, inspec ? inspec : &device->default_spec, sizeof (SDL_AudioSpec)); PrepareAudioFormat(device->iscapture, &spec); /* We allow the device format to change if it's better than the current settings (by various definitions of "better"). This prevents From 3e10c0005dec68084c15e755d43b73c9650ff12b Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 14:53:44 -0400 Subject: [PATCH 035/138] audio: Capture devices should respect logical device pausing. --- src/audio/SDL_audio.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 3d791677db..27b2633cfb 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -774,6 +774,10 @@ SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device) retval = SDL_FALSE; } else if (rc > 0) { // queue the new data to each bound stream. for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { + if (SDL_AtomicGet(&logdev->paused)) { + continue; // paused? Skip this logical device. + } + for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { /* this will hold a lock on `stream` while putting. We don't explicitly lock the streams for iterating here because the binding linked list can only change while the device lock is held. From dac25fe9eb8176e061d86559332360d36ffd3df2 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 14:51:39 -0400 Subject: [PATCH 036/138] audio: Seperate audio capture into Wait/Read operations. Before it would just block in read operations, but separating this out matches what output devices already do, and also lets us separate out the unlocked waiting part from the fast part that holds the device lock. --- src/audio/SDL_audio.c | 9 ++- src/audio/SDL_sysaudio.h | 1 + src/audio/disk/SDL_diskaudio.c | 1 + src/audio/pulseaudio/SDL_pulseaudio.c | 88 ++++++++++++++------------- 4 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 27b2633cfb..992cfde99e 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -414,6 +414,7 @@ static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device) { /* no-op. */ static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, int buffer_size) { /* no-op. */ } +static void SDL_AudioWaitCaptureDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioFlushCapture_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ } @@ -458,6 +459,7 @@ static void CompleteAudioEntryPoints(void) FILL_STUB(WaitDevice); FILL_STUB(PlayDevice); FILL_STUB(GetDeviceBuf); + FILL_STUB(WaitCaptureDevice); FILL_STUB(CaptureFromDevice); FILL_STUB(FlushCapture); FILL_STUB(CloseDevice); @@ -769,6 +771,7 @@ SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device) } else if (device->logical_devices == NULL) { current_audio.impl.FlushCapture(device); // nothing wants data, dump anything pending. } else { + // this SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitCaptureDevice! const int rc = current_audio.impl.CaptureFromDevice(device, device->work_buffer, device->buffer_size); if (rc < 0) { // uhoh, device failed for some reason! retval = SDL_FALSE; @@ -818,7 +821,11 @@ static int SDLCALL CaptureAudioThread(void *devicep) // thread entry point SDL_assert(device != NULL); SDL_assert(device->iscapture); SDL_CaptureAudioThreadSetup(device); - while (SDL_CaptureAudioThreadIterate(device)) { /* spin, CaptureAudioThreadIterate will block if necessary. !!! FIXME: maybe this is bad. */ } + + do { + current_audio.impl.WaitCaptureDevice(device); + } while (SDL_CaptureAudioThreadIterate(device)); + SDL_CaptureAudioThreadShutdown(device); return 0; } diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 38bfa2a3f0..b9c7da6cc4 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -109,6 +109,7 @@ typedef struct SDL_AudioDriverImpl void (*WaitDevice)(SDL_AudioDevice *device); void (*PlayDevice)(SDL_AudioDevice *device, int buffer_size); Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size); + void (*WaitCaptureDevice)(SDL_AudioDevice *device); int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen); void (*FlushCapture)(SDL_AudioDevice *device); void (*CloseDevice)(SDL_AudioDevice *device); diff --git a/src/audio/disk/SDL_diskaudio.c b/src/audio/disk/SDL_diskaudio.c index 517a4859ee..e96014ce95 100644 --- a/src/audio/disk/SDL_diskaudio.c +++ b/src/audio/disk/SDL_diskaudio.c @@ -162,6 +162,7 @@ static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl) /* Set the function pointers */ impl->OpenDevice = DISKAUDIO_OpenDevice; impl->WaitDevice = DISKAUDIO_WaitDevice; + impl->WaitCaptureDevice = DISKAUDIO_WaitDevice; impl->PlayDevice = DISKAUDIO_PlayDevice; impl->GetDeviceBuf = DISKAUDIO_GetDeviceBuf; impl->CaptureFromDevice = DISKAUDIO_CaptureFromDevice; diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index c5189b7652..fcd9d15ecf 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -429,62 +429,65 @@ static void ReadCallback(pa_stream *p, size_t nbytes, void *userdata) PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); /* the capture code queries what it needs, we just need to signal to end any wait */ } -static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) +static void PULSEAUDIO_WaitCaptureDevice(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *h = device->hidden; - const void *data = NULL; - size_t nbytes = 0; - int retval = 0; + + if (h->capturebuf != NULL) { + return; // there's still data available to read. + } PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); while (!SDL_AtomicGet(&device->shutdown)) { - if (h->capturebuf != NULL) { - const int cpy = SDL_min(buflen, h->capturelen); - SDL_memcpy(buffer, h->capturebuf, cpy); - /*printf("PULSEAUDIO: fed %d captured bytes\n", cpy);*/ - h->capturebuf += cpy; - h->capturelen -= cpy; - if (h->capturelen == 0) { - h->capturebuf = NULL; - PULSEAUDIO_pa_stream_drop(h->stream); /* done with this fragment. */ - } - retval = cpy; /* new data, return it. */ + PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); + if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) { + //printf("PULSEAUDIO DEVICE FAILURE IN WAITCAPTUREDEVICE!\n"); + SDL_AudioDeviceDisconnected(device); break; - } - - while (!SDL_AtomicGet(&device->shutdown) && (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0)) { - PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); - if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) { - /*printf("PULSEAUDIO DEVICE FAILURE IN CAPTUREFROMDEVICE!\n");*/ - SDL_AudioDeviceDisconnected(device); - retval = -1; + } else if (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0) { + // a new fragment is available! + const void *data = NULL; + size_t nbytes = 0; + PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes); + SDL_assert(nbytes > 0); + if (data == NULL) { // If NULL, then the buffer had a hole, ignore that + PULSEAUDIO_pa_stream_drop(h->stream); // drop this fragment. + } else { + // store this fragment's data for use with CaptureFromDevice + //printf("PULSEAUDIO: captured %d new bytes\n", (int) nbytes); + h->capturebuf = (const Uint8 *)data; + h->capturelen = nbytes; break; } } - - if ((retval == -1) || SDL_AtomicGet(&device->shutdown)) { /* in case this happened while we were blocking. */ - retval = -1; - break; - } - - /* a new fragment is available! */ - PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes); - SDL_assert(nbytes > 0); - /* If data == NULL, then the buffer had a hole, ignore that */ - if (data == NULL) { - PULSEAUDIO_pa_stream_drop(h->stream); /* drop this fragment. */ - } else { - /* store this fragment's data, start feeding it to SDL. */ - /*printf("PULSEAUDIO: captured %d new bytes\n", (int) nbytes);*/ - h->capturebuf = (const Uint8 *)data; - h->capturelen = nbytes; - } } PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); +} - return retval; +static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) +{ + struct SDL_PrivateAudioData *h = device->hidden; + + if (h->capturebuf != NULL) { + const int cpy = SDL_min(buflen, h->capturelen); + if (cpy > 0) { + //printf("PULSEAUDIO: fed %d captured bytes\n", cpy); + SDL_memcpy(buffer, h->capturebuf, cpy); + h->capturebuf += cpy; + h->capturelen -= cpy; + } + if (h->capturelen == 0) { + h->capturebuf = NULL; + PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); // don't know if you _have_ to lock for this, but just in case. + PULSEAUDIO_pa_stream_drop(h->stream); // done with this fragment. + PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); + } + return cpy; /* new data, return it. */ + } + + return 0; } static void PULSEAUDIO_FlushCapture(SDL_AudioDevice *device) @@ -991,6 +994,7 @@ static SDL_bool PULSEAUDIO_Init(SDL_AudioDriverImpl *impl) impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf; impl->CloseDevice = PULSEAUDIO_CloseDevice; impl->Deinitialize = PULSEAUDIO_Deinitialize; + impl->WaitCaptureDevice = PULSEAUDIO_WaitCaptureDevice; impl->CaptureFromDevice = PULSEAUDIO_CaptureFromDevice; impl->FlushCapture = PULSEAUDIO_FlushCapture; #if 0 From f48cb716c2ba22bf6866d9e774c39997d3c668b2 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 14:56:54 -0400 Subject: [PATCH 037/138] pulseaudio: a couple minor tweaks. --- src/audio/pulseaudio/SDL_pulseaudio.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index fcd9d15ecf..7585345fe7 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -533,6 +533,7 @@ static void PULSEAUDIO_CloseDevice(SDL_AudioDevice *device) PULSEAUDIO_pa_stream_disconnect(device->hidden->stream); PULSEAUDIO_pa_stream_unref(device->hidden->stream); } + PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); // in case the device thread is waiting somewhere, this will unblock it. PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); SDL_free(device->hidden->mixbuf); @@ -560,10 +561,7 @@ static void SourceDeviceNameCallback(pa_context *c, const pa_source_info *i, int static SDL_bool FindDeviceName(struct SDL_PrivateAudioData *h, const SDL_bool iscapture, void *handle) { - if (handle == NULL) { /* NULL == default device. */ - return SDL_TRUE; - } - + SDL_assert(handle != NULL); // this was a thing in SDL2, but shouldn't be in SDL3. const uint32_t idx = ((uint32_t)((intptr_t)handle)) - 1; if (iscapture) { From e1c78718d4b678111b745841757f7fe3e0e863eb Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 14:57:12 -0400 Subject: [PATCH 038/138] test: testaudiocapture is updated for the SDL3 audio API. --- test/testaudiocapture.c | 109 +++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 46 deletions(-) diff --git a/test/testaudiocapture.c b/test/testaudiocapture.c index 01c267608c..87ee24c4b0 100644 --- a/test/testaudiocapture.c +++ b/test/testaudiocapture.c @@ -22,13 +22,14 @@ static SDL_Window *window = NULL; static SDL_Renderer *renderer = NULL; -static SDL_AudioSpec spec; -static SDL_AudioDeviceID devid_in = 0; -static SDL_AudioDeviceID devid_out = 0; +static SDL_AudioStream *stream_in = NULL; +static SDL_AudioStream *stream_out = NULL; static int done = 0; static void loop(void) { + const SDL_AudioDeviceID devid_in = SDL_GetAudioStreamBinding(stream_in); + const SDL_AudioDeviceID devid_out = SDL_GetAudioStreamBinding(stream_out); SDL_bool please_quit = SDL_FALSE; SDL_Event e; @@ -42,17 +43,18 @@ static void loop(void) } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { if (e.button.button == 1) { SDL_PauseAudioDevice(devid_out); - SDL_PlayAudioDevice(devid_in); + SDL_UnpauseAudioDevice(devid_in); } } else if (e.type == SDL_EVENT_MOUSE_BUTTON_UP) { if (e.button.button == 1) { SDL_PauseAudioDevice(devid_in); - SDL_PlayAudioDevice(devid_out); + SDL_FlushAudioStream(stream_in); /* so no samples are held back for resampling purposes. */ + SDL_UnpauseAudioDevice(devid_out); } } } - if (SDL_GetAudioDeviceStatus(devid_in) == SDL_AUDIO_PLAYING) { + if (!SDL_IsAudioDevicePaused(devid_in)) { SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); } else { SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); @@ -60,13 +62,26 @@ static void loop(void) SDL_RenderClear(renderer); SDL_RenderPresent(renderer); + /* Feed any new data we captured to the output stream. It'll play when we unpause the device. */ + while (!please_quit && (SDL_GetAudioStreamAvailable(stream_in) > 0)) { + Uint8 buf[1024]; + const int br = SDL_GetAudioStreamData(stream_in, buf, sizeof(buf)); + if (br < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to read from input audio stream: %s\n", SDL_GetError()); + please_quit = 1; + } else if (SDL_PutAudioStreamData(stream_out, buf, br) < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to write to output audio stream: %s\n", SDL_GetError()); + please_quit = 1; + } + } + if (please_quit) { /* stop playing back, quit. */ SDL_Log("Shutting down.\n"); - SDL_PauseAudioDevice(devid_in); SDL_CloseAudioDevice(devid_in); - SDL_PauseAudioDevice(devid_out); SDL_CloseAudioDevice(devid_out); + SDL_DestroyAudioStream(stream_in); + SDL_DestroyAudioStream(stream_out); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); @@ -77,28 +92,17 @@ static void loop(void) done = 1; return; } - - /* Note that it would be easier to just have a one-line function that - calls SDL_QueueAudio() as a capture device callback, but we're - trying to test the API, so we use SDL_DequeueAudio() here. */ - while (SDL_TRUE) { - Uint8 buf[1024]; - const Uint32 br = SDL_DequeueAudio(devid_in, buf, sizeof(buf)); - SDL_QueueAudio(devid_out, buf, br); - if (br < sizeof(buf)) { - break; - } - } } int main(int argc, char **argv) { - /* (NULL means "open default device.") */ - const char *devname = NULL; - SDL_AudioSpec wanted; - int devcount; - int i; + SDL_AudioDeviceID *devices; SDLTest_CommonState *state; + SDL_AudioSpec spec; + SDL_AudioDeviceID device; + SDL_AudioDeviceID want_device = SDL_AUDIO_DEVICE_DEFAULT_CAPTURE; + const char *devname = NULL; + int i; /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); @@ -121,7 +125,7 @@ int main(int argc, char **argv) } } if (consumed <= 0) { - static const char *options[] = { "[driver_name]", NULL }; + static const char *options[] = { "[device_name]", NULL }; SDLTest_CommonLogUsage(state, argv[0], options); exit(1); } @@ -145,19 +149,20 @@ int main(int argc, char **argv) SDL_Log("Using audio driver: %s\n", SDL_GetCurrentAudioDriver()); - devcount = SDL_GetNumAudioDevices(SDL_TRUE); - for (i = 0; i < devcount; i++) { - SDL_Log(" Capture device #%d: '%s'\n", i, SDL_GetAudioDeviceName(i, SDL_TRUE)); + devices = SDL_GetAudioCaptureDevices(NULL); + for (i = 0; devices[i] != 0; i++) { + char *name = SDL_GetAudioDeviceName(devices[i]); + SDL_Log(" Capture device #%d: '%s'\n", i, name); + if (devname && (SDL_strcmp(devname, name) == 0)) { + want_device = devices[i]; + } + SDL_free(name); } - SDL_zero(wanted); - wanted.freq = 44100; - wanted.format = SDL_AUDIO_F32SYS; - wanted.channels = 1; - wanted.samples = 4096; - wanted.callback = NULL; - - SDL_zero(spec); + if (devname && (want_device == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE)) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Didn't see a capture device named '%s', using the system default instead.\n", devname); + devname = NULL; + } /* DirectSound can fail in some instances if you open the same hardware for both capture and output and didn't open the output end first, @@ -166,24 +171,40 @@ int main(int argc, char **argv) circumstances. */ SDL_Log("Opening default playback device...\n"); - devid_out = SDL_OpenAudioDevice(NULL, SDL_FALSE, &wanted, &spec, SDL_AUDIO_ALLOW_ANY_CHANGE); - if (!devid_out) { + device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, NULL); + if (!device) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open an audio device for playback: %s!\n", SDL_GetError()); SDL_Quit(); exit(1); } + SDL_PauseAudioDevice(device); + SDL_GetAudioDeviceFormat(device, &spec); + stream_out = SDL_CreateAndBindAudioStream(device, &spec); + if (!stream_out) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create an audio stream for playback: %s!\n", SDL_GetError()); + SDL_Quit(); + exit(1); + } SDL_Log("Opening capture device %s%s%s...\n", devname ? "'" : "", devname ? devname : "[[default]]", devname ? "'" : ""); - devid_in = SDL_OpenAudioDevice(devname, SDL_TRUE, &spec, &spec, 0); - if (!devid_in) { + device = SDL_OpenAudioDevice(want_device, NULL); + if (!device) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open an audio device for capture: %s!\n", SDL_GetError()); SDL_Quit(); exit(1); } + SDL_PauseAudioDevice(device); + SDL_GetAudioDeviceFormat(device, &spec); + stream_in = SDL_CreateAndBindAudioStream(device, &spec); + if (!stream_in) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create an audio stream for capture: %s!\n", SDL_GetError()); + SDL_Quit(); + exit(1); + } SDL_Log("Ready! Hold down mouse or finger to record!\n"); @@ -198,11 +219,7 @@ int main(int argc, char **argv) } #endif - /* SDL_DestroyRenderer(renderer); */ - /* SDL_DestroyWindow(window); */ - - /* SDL_Quit(); */ - /* SDLTest_CommonDestroyState(state); */ + SDLTest_CommonDestroyState(state); return 0; } From 3a02eecced148ee14b26447a152ef481185b93ac Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 15:41:20 -0400 Subject: [PATCH 039/138] test: Update testsurround for SDL3 audio API. --- test/testsurround.c | 59 +++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/test/testsurround.c b/test/testsurround.c index f4060b28f9..d9cb006e0c 100644 --- a/test/testsurround.c +++ b/test/testsurround.c @@ -96,20 +96,23 @@ static SDL_bool is_lfe_channel(int channel_index, int channel_count) return (channel_count == 3 && channel_index == 2) || (channel_count >= 6 && channel_index == 3); } -static void SDLCALL fill_buffer(void *unused, Uint8 *stream, int len) +static void SDLCALL fill_buffer(SDL_AudioStream *stream, int len, void *unused) { - Sint16 *buffer = (Sint16 *)stream; - int samples = len / sizeof(Sint16); + const int samples = len / sizeof(Sint16); + Sint16 *buffer = NULL; static int total_samples = 0; int i; - SDL_memset(stream, 0, len); - /* This can happen for a short time when switching devices */ if (active_channel == total_channels) { return; } + buffer = (Sint16 *) SDL_calloc(samples, sizeof(Sint16)); + if (!buffer) { + return; /* oh well. */ + } + /* Play a sine wave on the active channel only */ for (i = active_channel; i < samples; i += total_channels) { float time = (float)total_samples++ / SAMPLE_RATE_HZ; @@ -134,12 +137,18 @@ static void SDLCALL fill_buffer(void *unused, Uint8 *stream, int len) break; } } + + SDL_PutAudioStreamData(stream, buffer, samples * sizeof (Sint16)); + + SDL_free(buffer); } int main(int argc, char *argv[]) { - int i; + SDL_AudioDeviceID *devices = NULL; SDLTest_CommonState *state; + int devcount = 0; + int i; /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); @@ -168,38 +177,53 @@ int main(int argc, char *argv[]) SDL_Log("Using audio driver: %s\n", SDL_GetCurrentAudioDriver()); - for (i = 0; i < SDL_GetNumAudioDevices(0); i++) { - const char *devname = SDL_GetAudioDeviceName(i, 0); + devices = SDL_GetAudioOutputDevices(&devcount); + if (!devices) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_GetAudioOutputDevices() failed: %s\n", SDL_GetError()); + devcount = 0; + } + + for (i = 0; i < devcount; i++) { + SDL_AudioStream *stream = NULL; + char *devname = SDL_GetAudioDeviceName(devices[i]); int j; SDL_AudioSpec spec; SDL_AudioDeviceID dev; - if (SDL_GetAudioDeviceSpec(i, 0, &spec) != 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_GetAudioSpec() failed: %s\n", SDL_GetError()); + SDL_Log("Testing audio device: %s\n", devname); + SDL_free(devname); + + if (SDL_GetAudioDeviceFormat(devices[i], &spec) != 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_GetAudioDeviceFormat() failed: %s\n", SDL_GetError()); continue; } + SDL_Log(" (%d channels)\n", spec.channels); + spec.freq = SAMPLE_RATE_HZ; spec.format = SDL_AUDIO_S16SYS; - spec.samples = 4096; - spec.callback = fill_buffer; - dev = SDL_OpenAudioDevice(devname, 0, &spec, NULL, 0); + dev = SDL_OpenAudioDevice(devices[i], &spec); if (dev == 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_OpenAudioDevice() failed: %s\n", SDL_GetError()); continue; } - SDL_Log("Testing audio device: %s (%d channels)\n", devname, spec.channels); + stream = SDL_CreateAndBindAudioStream(dev, &spec); + if (stream == NULL) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateAndBindAudioStream() failed: %s\n", SDL_GetError()); + SDL_CloseAudioDevice(dev); + continue; + } /* These are used by the fill_buffer callback */ total_channels = spec.channels; active_channel = 0; - SDL_PlayAudioDevice(dev); + SDL_SetAudioStreamGetCallback(stream, fill_buffer, NULL); for (j = 0; j < total_channels; j++) { - int sine_freq = is_lfe_channel(j, total_channels) ? LFE_SINE_FREQ_HZ : SINE_FREQ_HZ; + const int sine_freq = is_lfe_channel(j, total_channels) ? LFE_SINE_FREQ_HZ : SINE_FREQ_HZ; SDL_Log("Playing %d Hz test tone on channel: %s\n", sine_freq, get_channel_name(j, total_channels)); @@ -212,8 +236,11 @@ int main(int argc, char *argv[]) } SDL_CloseAudioDevice(dev); + SDL_DestroyAudioStream(stream); } + SDL_free(devices); + SDL_Quit(); return 0; } From 29afc2e42bdbcb0540ebf524e34f853e7b8efd25 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 15:46:40 -0400 Subject: [PATCH 040/138] test: Update testresample for SDL3 audio API. --- test/testresample.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/test/testresample.c b/test/testresample.c index 75378110a2..da4e8dd959 100644 --- a/test/testresample.c +++ b/test/testresample.c @@ -22,12 +22,11 @@ static void log_usage(char *progname, SDLTest_CommonState *state) { int main(int argc, char **argv) { SDL_AudioSpec spec; + SDL_AudioSpec cvtspec; SDL_AudioStream *stream = NULL; Uint8 *dst_buf = NULL; Uint32 len = 0; Uint8 *data = NULL; - int cvtfreq = 0; - int cvtchans = 0; int bitsize = 0; int blockalign = 0; int avgbytes = 0; @@ -49,6 +48,8 @@ int main(int argc, char **argv) /* Enable standard application logging */ SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); + SDL_zero(cvtspec); + /* Parse commandline */ for (i = 1; i < argc;) { int consumed; @@ -65,14 +66,14 @@ int main(int argc, char **argv) consumed = 1; } else if (argpos == 2) { char *endp; - cvtfreq = (int)SDL_strtoul(argv[i], &endp, 0); + cvtspec.freq = (int)SDL_strtoul(argv[i], &endp, 0); if (endp != argv[i] && *endp == '\0') { argpos++; consumed = 1; } } else if (argpos == 3) { char *endp; - cvtchans = (int)SDL_strtoul(argv[i], &endp, 0); + cvtspec.channels = (int)SDL_strtoul(argv[i], &endp, 0); if (endp != argv[i] && *endp == '\0') { argpos++; consumed = 1; @@ -100,14 +101,14 @@ int main(int argc, char **argv) goto end; } - if (SDL_LoadWAV(file_in, &spec, &data, &len) == NULL) { + if (SDL_LoadWAV(file_in, &spec, &data, &len) == -1) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "failed to load %s: %s\n", file_in, SDL_GetError()); ret = 3; goto end; } - if (SDL_ConvertAudioSamples(spec.format, spec.channels, spec.freq, data, len, - spec.format, cvtchans, cvtfreq, &dst_buf, &dst_len) < 0) { + cvtspec.format = spec.format; + if (SDL_ConvertAudioSamples(&spec, data, len, &cvtspec, &dst_buf, &dst_len) < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "failed to convert samples: %s\n", SDL_GetError()); ret = 4; goto end; @@ -122,8 +123,8 @@ int main(int argc, char **argv) } bitsize = SDL_AUDIO_BITSIZE(spec.format); - blockalign = (bitsize / 8) * cvtchans; - avgbytes = cvtfreq * blockalign; + blockalign = (bitsize / 8) * cvtspec.channels; + avgbytes = cvtspec.freq * blockalign; SDL_WriteLE32(io, 0x46464952); /* RIFF */ SDL_WriteLE32(io, dst_len + 36); @@ -131,8 +132,8 @@ int main(int argc, char **argv) SDL_WriteLE32(io, 0x20746D66); /* fmt */ SDL_WriteLE32(io, 16); /* chunk size */ SDL_WriteLE16(io, SDL_AUDIO_ISFLOAT(spec.format) ? 3 : 1); /* uncompressed */ - SDL_WriteLE16(io, cvtchans); /* channels */ - SDL_WriteLE32(io, cvtfreq); /* sample rate */ + SDL_WriteLE16(io, cvtspec.channels); /* channels */ + SDL_WriteLE32(io, cvtspec.freq); /* sample rate */ SDL_WriteLE32(io, avgbytes); /* average bytes per second */ SDL_WriteLE16(io, blockalign); /* block align */ SDL_WriteLE16(io, bitsize); /* significant bits per sample */ From 11dfc4d737a6f61ad8ec0ed50414ae1075caf672 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 23:10:54 -0400 Subject: [PATCH 041/138] test: Update testautomation_audio for SDL3 audio API. --- test/testautomation_audio.c | 296 +++++------------------------------- 1 file changed, 36 insertions(+), 260 deletions(-) diff --git a/test/testautomation_audio.c b/test/testautomation_audio.c index 9cd98a8598..5d6dd4e835 100644 --- a/test/testautomation_audio.c +++ b/test/testautomation_audio.c @@ -176,28 +176,22 @@ static int audio_initOpenCloseQuitAudio(void *arg) desired.freq = 22050; desired.format = SDL_AUDIO_S16SYS; desired.channels = 2; - desired.samples = 4096; - desired.callback = audio_testCallback; - desired.userdata = NULL; case 1: /* Set custom desired spec */ desired.freq = 48000; desired.format = SDL_AUDIO_F32SYS; desired.channels = 2; - desired.samples = 2048; - desired.callback = audio_testCallback; - desired.userdata = NULL; break; } /* Call Open (maybe multiple times) */ for (k = 0; k <= j; k++) { - result = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, 0); + result = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &desired); if (k == 0) { g_audio_id = result; } - SDLTest_AssertPass("Call to SDL_OpenAudioDevice(NULL, 0, desired_spec_%d, NULL, 0), call %d", j, k + 1); + SDLTest_AssertPass("Call to SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, desired_spec_%d), call %d", j, k + 1); SDLTest_AssertCheck(result > 0, "Verify return value; expected: > 0, got: %d", result); } @@ -269,9 +263,6 @@ static int audio_pauseUnpauseAudio(void *arg) desired.freq = 22050; desired.format = SDL_AUDIO_S16SYS; desired.channels = 2; - desired.samples = 4096; - desired.callback = audio_testCallback; - desired.userdata = NULL; break; case 1: @@ -279,18 +270,16 @@ static int audio_pauseUnpauseAudio(void *arg) desired.freq = 48000; desired.format = SDL_AUDIO_F32SYS; desired.channels = 2; - desired.samples = 2048; - desired.callback = audio_testCallback; - desired.userdata = NULL; break; } /* Call Open */ - g_audio_id = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, 0); + g_audio_id = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &desired); result = g_audio_id; - SDLTest_AssertPass("Call to SDL_OpenAudioDevice(NULL, 0, desired_spec_%d, NULL, 0)", j); + SDLTest_AssertPass("Call to SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, desired_spec_%d)", j); SDLTest_AssertCheck(result > 0, "Verify return value; expected > 0 got: %d", result); +#if 0 /* !!! FIXME: maybe update this? */ /* Start and stop audio multiple times */ for (l = 0; l < 3; l++) { SDLTest_Log("Pause/Unpause iteration: %d", l + 1); @@ -332,6 +321,7 @@ static int audio_pauseUnpauseAudio(void *arg) SDL_Delay(totalDelay + 10); SDLTest_AssertCheck(originalCounter == g_audio_testCallbackCounter, "Verify callback counter; expected: %d, got: %d", originalCounter, g_audio_testCallbackCounter); } +#endif /* Call Close */ SDL_CloseAudioDevice(g_audio_id); @@ -360,49 +350,32 @@ static int audio_enumerateAndNameAudioDevices(void *arg) { int t, tt; int i, n, nn; - const char *name, *nameAgain; + char *name, *nameAgain; + SDL_AudioDeviceID *devices = NULL; /* Iterate over types: t=0 output device, t=1 input/capture device */ for (t = 0; t < 2; t++) { - /* Get number of devices. */ - n = SDL_GetNumAudioDevices(t); - SDLTest_AssertPass("Call to SDL_GetNumAudioDevices(%i)", t); + devices = (t) ? SDL_GetAudioCaptureDevices(&n) : SDL_GetAudioOutputDevices(&n); + SDLTest_AssertPass("Call to SDL_GetAudio%sDevices(%i)", (t) ? "Capture" : "Output", t); SDLTest_Log("Number of %s devices < 0, reported as %i", (t) ? "capture" : "output", n); SDLTest_AssertCheck(n >= 0, "Validate result is >= 0, got: %i", n); - /* Variation of non-zero type */ - if (t == 1) { - tt = t + SDLTest_RandomIntegerInRange(1, 10); - nn = SDL_GetNumAudioDevices(tt); - SDLTest_AssertCheck(n == nn, "Verify result from SDL_GetNumAudioDevices(%i), expected same number of audio devices %i, got %i", tt, n, nn); - nn = SDL_GetNumAudioDevices(-tt); - SDLTest_AssertCheck(n == nn, "Verify result from SDL_GetNumAudioDevices(%i), expected same number of audio devices %i, got %i", -tt, n, nn); - } - /* List devices. */ if (n > 0) { + SDLTest_AssertCheck(devices != NULL, "Validate devices is not NULL if n > 0"); for (i = 0; i < n; i++) { - name = SDL_GetAudioDeviceName(i, t); - SDLTest_AssertPass("Call to SDL_GetAudioDeviceName(%i, %i)", i, t); - SDLTest_AssertCheck(name != NULL, "Verify result from SDL_GetAudioDeviceName(%i, %i) is not NULL", i, t); + name = SDL_GetAudioDeviceName(devices[i]); + SDLTest_AssertPass("Call to SDL_GetAudioDeviceName(%i)", i); + SDLTest_AssertCheck(name != NULL, "Verify result from SDL_GetAudioDeviceName(%i) is not NULL", i); if (name != NULL) { - SDLTest_AssertCheck(name[0] != '\0', "verify result from SDL_GetAudioDeviceName(%i, %i) is not empty, got: '%s'", i, t, name); - if (t == 1) { - /* Also try non-zero type */ - tt = t + SDLTest_RandomIntegerInRange(1, 10); - nameAgain = SDL_GetAudioDeviceName(i, tt); - SDLTest_AssertCheck(nameAgain != NULL, "Verify result from SDL_GetAudioDeviceName(%i, %i) is not NULL", i, tt); - if (nameAgain != NULL) { - SDLTest_AssertCheck(nameAgain[0] != '\0', "Verify result from SDL_GetAudioDeviceName(%i, %i) is not empty, got: '%s'", i, tt, nameAgain); - SDLTest_AssertCheck(SDL_strcmp(name, nameAgain) == 0, - "Verify SDL_GetAudioDeviceName(%i, %i) and SDL_GetAudioDeviceName(%i %i) return the same string", - i, t, i, tt); - } - } + SDLTest_AssertCheck(name[0] != '\0', "verify result from SDL_GetAudioDeviceName(%i) is not empty, got: '%s'", i, name); + SDL_free(name); } } } + + SDL_free(devices); } return TEST_COMPLETED; @@ -416,42 +389,7 @@ static int audio_enumerateAndNameAudioDevices(void *arg) */ static int audio_enumerateAndNameAudioDevicesNegativeTests(void *arg) { - int t; - int i, j, no, nc; - const char *name; - - /* Get number of devices. */ - no = SDL_GetNumAudioDevices(0); - SDLTest_AssertPass("Call to SDL_GetNumAudioDevices(0)"); - nc = SDL_GetNumAudioDevices(1); - SDLTest_AssertPass("Call to SDL_GetNumAudioDevices(1)"); - - /* Invalid device index when getting name */ - for (t = 0; t < 2; t++) { - /* Negative device index */ - i = SDLTest_RandomIntegerInRange(-10, -1); - name = SDL_GetAudioDeviceName(i, t); - SDLTest_AssertPass("Call to SDL_GetAudioDeviceName(%i, %i)", i, t); - SDLTest_AssertCheck(name == NULL, "Check SDL_GetAudioDeviceName(%i, %i) result NULL, expected NULL, got: %s", i, t, (name == NULL) ? "NULL" : name); - - /* Device index past range */ - for (j = 0; j < 3; j++) { - i = (t) ? nc + j : no + j; - name = SDL_GetAudioDeviceName(i, t); - SDLTest_AssertPass("Call to SDL_GetAudioDeviceName(%i, %i)", i, t); - SDLTest_AssertCheck(name == NULL, "Check SDL_GetAudioDeviceName(%i, %i) result, expected: NULL, got: %s", i, t, (name == NULL) ? "NULL" : name); - } - - /* Capture index past capture range but within output range */ - if ((no > 0) && (no > nc) && (t == 1)) { - i = no - 1; - name = SDL_GetAudioDeviceName(i, t); - SDLTest_AssertPass("Call to SDL_GetAudioDeviceName(%i, %i)", i, t); - SDLTest_AssertCheck(name == NULL, "Check SDL_GetAudioDeviceName(%i, %i) result, expected: NULL, got: %s", i, t, (name == NULL) ? "NULL" : name); - } - } - - return TEST_COMPLETED; + return TEST_COMPLETED; /* nothing in here atm since these interfaces changed in SDL3. */ } /** @@ -532,8 +470,7 @@ static int audio_buildAudioStream(void *arg) spec1.format = SDL_AUDIO_S16LSB; spec1.channels = 2; spec1.freq = 22050; - stream = SDL_CreateAudioStream(spec1.format, spec1.channels, spec1.freq, - spec1.format, spec1.channels, spec1.freq); + stream = SDL_CreateAudioStream(&spec1, &spec1); SDLTest_AssertPass("Call to SDL_CreateAudioStream(spec1 ==> spec1)"); SDLTest_AssertCheck(stream != NULL, "Verify stream value; expected: != NULL, got: %p", (void *)stream); SDL_DestroyAudioStream(stream); @@ -545,8 +482,7 @@ static int audio_buildAudioStream(void *arg) spec2.format = SDL_AUDIO_S16LSB; spec2.channels = 2; spec2.freq = 44100; - stream = SDL_CreateAudioStream(spec1.format, spec1.channels, spec1.freq, - spec2.format, spec2.channels, spec2.freq); + stream = SDL_CreateAudioStream(&spec1, &spec2); SDLTest_AssertPass("Call to SDL_CreateAudioStream(spec1 ==> spec2)"); SDLTest_AssertCheck(stream != NULL, "Verify stream value; expected: != NULL, got: %p", (void *)stream); SDL_DestroyAudioStream(stream); @@ -564,8 +500,7 @@ static int audio_buildAudioStream(void *arg) spec2.format = g_audioFormats[ii]; spec2.channels = g_audioChannels[jj]; spec2.freq = g_audioFrequencies[kk]; - stream = SDL_CreateAudioStream(spec1.format, spec1.channels, spec1.freq, - spec2.format, spec2.channels, spec2.freq); + stream = SDL_CreateAudioStream(&spec1, &spec2); SDLTest_AssertPass("Call to SDL_CreateAudioStream(format[%i]=%s(%i),channels[%i]=%i,freq[%i]=%i ==> format[%i]=%s(%i),channels[%i]=%i,freq[%i]=%i)", i, g_audioFormatsVerbose[i], spec1.format, j, spec1.channels, k, spec1.freq, ii, g_audioFormatsVerbose[ii], spec2.format, jj, spec2.channels, kk, spec2.freq); @@ -646,8 +581,7 @@ static int audio_buildAudioStreamNegative(void *arg) spec2.freq = 0; } SDLTest_Log("%s", message); - stream = SDL_CreateAudioStream(spec1.format, spec1.channels, spec1.freq, - spec2.format, spec2.channels, spec2.freq); + stream = SDL_CreateAudioStream(&spec1, &spec2); SDLTest_AssertPass("Call to SDL_CreateAudioStream(spec1 ==> spec2)"); SDLTest_AssertCheck(stream == NULL, "Verify stream value; expected: NULL, got: %p", (void *)stream); error = SDL_GetError(); @@ -669,16 +603,7 @@ static int audio_buildAudioStreamNegative(void *arg) */ static int audio_getAudioStatus(void *arg) { - SDL_AudioStatus result; - - /* Check current audio status */ - result = SDL_GetAudioDeviceStatus(g_audio_id); - SDLTest_AssertPass("Call to SDL_GetAudioDeviceStatus(g_audio_id)"); - SDLTest_AssertCheck(result == SDL_AUDIO_STOPPED || result == SDL_AUDIO_PLAYING || result == SDL_AUDIO_PAUSED, - "Verify returned value; expected: STOPPED (%i) | PLAYING (%i) | PAUSED (%i), got: %i", - SDL_AUDIO_STOPPED, SDL_AUDIO_PLAYING, SDL_AUDIO_PAUSED, result); - - return TEST_COMPLETED; + return TEST_COMPLETED; /* no longer a thing in SDL3. */ } /** @@ -688,57 +613,7 @@ static int audio_getAudioStatus(void *arg) */ static int audio_openCloseAndGetAudioStatus(void *arg) { - SDL_AudioStatus result; - int i; - int count; - const char *device; - SDL_AudioDeviceID id; - SDL_AudioSpec desired, obtained; - - /* Get number of devices. */ - count = SDL_GetNumAudioDevices(0); - SDLTest_AssertPass("Call to SDL_GetNumAudioDevices(0)"); - if (count > 0) { - for (i = 0; i < count; i++) { - /* Get device name */ - device = SDL_GetAudioDeviceName(i, 0); - SDLTest_AssertPass("SDL_GetAudioDeviceName(%i,0)", i); - SDLTest_AssertCheck(device != NULL, "Validate device name is not NULL; got: %s", (device != NULL) ? device : "NULL"); - if (device == NULL) { - return TEST_ABORTED; - } - - /* Set standard desired spec */ - desired.freq = 22050; - desired.format = SDL_AUDIO_S16SYS; - desired.channels = 2; - desired.samples = 4096; - desired.callback = audio_testCallback; - desired.userdata = NULL; - - /* Open device */ - id = SDL_OpenAudioDevice(device, 0, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE); - SDLTest_AssertPass("SDL_OpenAudioDevice('%s',...)", device); - SDLTest_AssertCheck(id > 0, "Validate device ID; expected: > 0, got: %" SDL_PRIu32, id); - if (id > 0) { - - /* Check device audio status */ - result = SDL_GetAudioDeviceStatus(id); - SDLTest_AssertPass("Call to SDL_GetAudioDeviceStatus()"); - SDLTest_AssertCheck(result == SDL_AUDIO_STOPPED || result == SDL_AUDIO_PLAYING || result == SDL_AUDIO_PAUSED, - "Verify returned value; expected: STOPPED (%i) | PLAYING (%i) | PAUSED (%i), got: %i", - SDL_AUDIO_STOPPED, SDL_AUDIO_PLAYING, SDL_AUDIO_PAUSED, result); - - /* Close device again */ - SDL_CloseAudioDevice(id); - SDLTest_AssertPass("Call to SDL_CloseAudioDevice()"); - } - } - } else { - SDLTest_Log("No devices to test with"); - } - - return TEST_COMPLETED; + return TEST_COMPLETED; /* not a thing in SDL3. */ } /** @@ -749,60 +624,7 @@ static int audio_openCloseAndGetAudioStatus(void *arg) */ static int audio_lockUnlockOpenAudioDevice(void *arg) { - int i; - int count; - const char *device; - SDL_AudioDeviceID id; - SDL_AudioSpec desired, obtained; - - /* Get number of devices. */ - count = SDL_GetNumAudioDevices(0); - SDLTest_AssertPass("Call to SDL_GetNumAudioDevices(0)"); - if (count > 0) { - for (i = 0; i < count; i++) { - /* Get device name */ - device = SDL_GetAudioDeviceName(i, 0); - SDLTest_AssertPass("SDL_GetAudioDeviceName(%i,0)", i); - SDLTest_AssertCheck(device != NULL, "Validate device name is not NULL; got: %s", (device != NULL) ? device : "NULL"); - if (device == NULL) { - return TEST_ABORTED; - } - - /* Set standard desired spec */ - desired.freq = 22050; - desired.format = SDL_AUDIO_S16SYS; - desired.channels = 2; - desired.samples = 4096; - desired.callback = audio_testCallback; - desired.userdata = NULL; - - /* Open device */ - id = SDL_OpenAudioDevice(device, 0, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE); - SDLTest_AssertPass("SDL_OpenAudioDevice('%s',...)", device); - SDLTest_AssertCheck(id > 1, "Validate device ID; expected: > 0, got: %" SDL_PRIu32, id); - if (id > 0) { - /* Lock to protect callback */ - SDL_LockAudioDevice(id); - SDLTest_AssertPass("SDL_LockAudioDevice(%" SDL_PRIu32 ")", id); - - /* Simulate callback processing */ - SDL_Delay(10); - SDLTest_Log("Simulate callback processing - delay"); - - /* Unlock again */ - SDL_UnlockAudioDevice(id); - SDLTest_AssertPass("SDL_UnlockAudioDevice(%" SDL_PRIu32 ")", id); - - /* Close device again */ - SDL_CloseAudioDevice(id); - SDLTest_AssertPass("Call to SDL_CloseAudioDevice()"); - } - } - } else { - SDLTest_Log("No devices to test with"); - } - - return TEST_COMPLETED; + return TEST_COMPLETED; /* not a thing in SDL3 */ } /** @@ -862,8 +684,7 @@ static int audio_convertAudio(void *arg) spec2.channels = g_audioChannels[jj]; spec2.freq = g_audioFrequencies[kk]; - stream = SDL_CreateAudioStream(spec1.format, spec1.channels, spec1.freq, - spec2.format, spec2.channels, spec2.freq); + stream = SDL_CreateAudioStream(&spec1, &spec2); SDLTest_AssertPass("Call to SDL_CreateAudioStream(format[%i]=%s(%i),channels[%i]=%i,freq[%i]=%i ==> format[%i]=%s(%i),channels[%i]=%i,freq[%i]=%i)", i, g_audioFormatsVerbose[i], spec1.format, j, spec1.channels, k, spec1.freq, ii, g_audioFormatsVerbose[ii], spec2.format, jj, spec2.channels, kk, spec2.freq); SDLTest_AssertCheck(stream != NULL, "Verify stream value; expected: != NULL, got: %p", (void *)stream); @@ -936,59 +757,7 @@ static int audio_convertAudio(void *arg) */ static int audio_openCloseAudioDeviceConnected(void *arg) { - int result = -1; - int i; - int count; - const char *device; - SDL_AudioDeviceID id; - SDL_AudioSpec desired, obtained; - - /* Get number of devices. */ - count = SDL_GetNumAudioDevices(0); - SDLTest_AssertPass("Call to SDL_GetNumAudioDevices(0)"); - if (count > 0) { - for (i = 0; i < count; i++) { - /* Get device name */ - device = SDL_GetAudioDeviceName(i, 0); - SDLTest_AssertPass("SDL_GetAudioDeviceName(%i,0)", i); - SDLTest_AssertCheck(device != NULL, "Validate device name is not NULL; got: %s", (device != NULL) ? device : "NULL"); - if (device == NULL) { - return TEST_ABORTED; - } - - /* Set standard desired spec */ - desired.freq = 22050; - desired.format = SDL_AUDIO_S16SYS; - desired.channels = 2; - desired.samples = 4096; - desired.callback = audio_testCallback; - desired.userdata = NULL; - - /* Open device */ - id = SDL_OpenAudioDevice(device, 0, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE); - SDLTest_AssertPass("SDL_OpenAudioDevice('%s',...)", device); - SDLTest_AssertCheck(id > 0, "Validate device ID; expected: > 0, got: %" SDL_PRIu32, id); - if (id > 0) { - - /* TODO: enable test code when function is available in SDL3 */ - -#ifdef AUDIODEVICECONNECTED_DEFINED - /* Get connected status */ - result = SDL_AudioDeviceConnected(id); - SDLTest_AssertPass("Call to SDL_AudioDeviceConnected()"); -#endif - SDLTest_AssertCheck(result == 1, "Verify returned value; expected: 1; got: %i", result); - - /* Close device again */ - SDL_CloseAudioDevice(id); - SDLTest_AssertPass("Call to SDL_CloseAudioDevice()"); - } - } - } else { - SDLTest_Log("No devices to test with"); - } - - return TEST_COMPLETED; + return TEST_COMPLETED; /* not a thing in SDL3. */ } static double sine_wave_sample(const Sint64 idx, const Sint64 rate, const Sint64 freq, const double phase) @@ -1040,6 +809,7 @@ static int audio_resampleLoss(void *arg) const int len_in = frames_in * (int)sizeof(float); const int len_target = frames_target * (int)sizeof(float); + SDL_AudioSpec tmpspec1, tmpspec2; Uint64 tick_beg = 0; Uint64 tick_end = 0; int i = 0; @@ -1056,7 +826,13 @@ static int audio_resampleLoss(void *arg) SDLTest_AssertPass("Test resampling of %i s %i Hz %f phase sine wave from sampling rate of %i Hz to %i Hz", spec->time, spec->freq, spec->phase, spec->rate_in, spec->rate_out); - stream = SDL_CreateAudioStream(SDL_AUDIO_F32, 1, spec->rate_in, SDL_AUDIO_F32, 1, spec->rate_out); + tmpspec1.format = SDL_AUDIO_F32; + tmpspec1.channels = 1; + tmpspec1.freq = spec->rate_in; + tmpspec2.format = SDL_AUDIO_F32; + tmpspec2.channels = 1; + tmpspec2.freq = spec->rate_out; + stream = SDL_CreateAudioStream(&tmpspec1, &tmpspec2); SDLTest_AssertPass("Call to SDL_CreateAudioStream(SDL_AUDIO_F32, 1, %i, SDL_AUDIO_F32, 1, %i)", spec->rate_in, spec->rate_out); SDLTest_AssertCheck(stream != NULL, "Expected SDL_CreateAudioStream to succeed."); if (stream == NULL) { From 943351affb97097bcee214fe723ba04587069421 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jun 2023 23:27:56 -0400 Subject: [PATCH 042/138] pulseaudio: GetDefaultAudioInfo isn't a thing anymore. --- src/audio/pulseaudio/SDL_pulseaudio.c | 47 --------------------------- 1 file changed, 47 deletions(-) diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 7585345fe7..eeebe41662 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -46,9 +46,6 @@ static SDL_AtomicInt pulseaudio_hotplug_thread_active; // These are the OS identifiers (i.e. ALSA strings)... static char *default_sink_path = NULL; static char *default_source_path = NULL; -// ... and these are the descriptions we use in GetDefaultAudioInfo... -static char *default_sink_name = NULL; -static char *default_source_name = NULL; // ... and these are the PulseAudio device indices of the default devices. static uint32_t default_sink_index = 0; static uint32_t default_source_index = 0; @@ -761,8 +758,6 @@ static void SinkInfoCallback(pa_context *c, const pa_sink_info *i, int is_last, } if (default_sink_path != NULL && SDL_strcmp(i->name, default_sink_path) == 0) { - SDL_free(default_sink_name); - default_sink_name = SDL_strdup(i->description); default_sink_index = i->index; } } @@ -786,8 +781,6 @@ static void SourceInfoCallback(pa_context *c, const pa_source_info *i, int is_la } if (default_source_path != NULL && SDL_strcmp(i->name, default_source_path) == 0) { - SDL_free(default_source_name); - default_source_name = SDL_strdup(i->description); default_source_index = i->index; } } @@ -912,39 +905,6 @@ static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_Audio SDL_DestroySemaphore(ready_sem); } -#if 0 -static int PULSEAUDIO_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) -{ - int i; - int numdevices; - - char *target; - if (iscapture) { - if (default_source_name == NULL) { - return SDL_SetError("PulseAudio could not find a default source"); - } - target = default_source_name; - } else { - if (default_sink_name == NULL) { - return SDL_SetError("PulseAudio could not find a default sink"); - } - target = default_sink_name; - } - - numdevices = SDL_GetNumAudioDevices(iscapture); - for (i = 0; i < numdevices; i += 1) { - if (SDL_strcmp(SDL_GetAudioDeviceName(i, iscapture), target) == 0) { - if (name != NULL) { - *name = SDL_strdup(target); - } - SDL_GetAudioDeviceSpec(i, iscapture, spec); - return 0; - } - } - return SDL_SetError("Could not find default PulseAudio device"); -} -#endif - static void PULSEAUDIO_Deinitialize(void) { if (pulseaudio_hotplug_thread) { @@ -962,10 +922,6 @@ static void PULSEAUDIO_Deinitialize(void) default_sink_path = NULL; SDL_free(default_source_path); default_source_path = NULL; - SDL_free(default_sink_name); - default_sink_name = NULL; - SDL_free(default_source_name); - default_source_name = NULL; default_source_index = 0; default_sink_index = 0; @@ -995,9 +951,6 @@ static SDL_bool PULSEAUDIO_Init(SDL_AudioDriverImpl *impl) impl->WaitCaptureDevice = PULSEAUDIO_WaitCaptureDevice; impl->CaptureFromDevice = PULSEAUDIO_CaptureFromDevice; impl->FlushCapture = PULSEAUDIO_FlushCapture; - #if 0 - impl->GetDefaultAudioInfo = PULSEAUDIO_GetDefaultAudioInfo; - #endif impl->HasCaptureSupport = SDL_TRUE; impl->SupportsNonPow2Samples = SDL_TRUE; From 3e9991b535b0caa25e45879fda4c92bd160e6ac7 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 26 Jun 2023 21:26:48 -0400 Subject: [PATCH 043/138] audio: Make sure we don't write to a NULL pointer. (This _probably_ never happens in the current codebase, but just in case.) --- src/audio/SDL_audiocvt.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index f9f43ed444..c33daf6ac9 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -1119,7 +1119,9 @@ int SDL_ClearAudioStream(SDL_AudioStream *stream) SDL_LockMutex(stream->lock); SDL_ClearDataQueue(stream->queue, (size_t)stream->packetlen * 2); - SDL_memset(stream->history_buffer, SDL_GetSilenceValueForFormat(stream->src_spec.format), stream->history_buffer_frames * stream->src_spec.channels * sizeof (float)); + if (stream->history_buffer != NULL) { + SDL_memset(stream->history_buffer, SDL_GetSilenceValueForFormat(stream->src_spec.format), stream->history_buffer_frames * stream->src_spec.channels * sizeof (float)); + } stream->future_buffer_filled_frames = 0; stream->flushed = SDL_FALSE; SDL_UnlockMutex(stream->lock); From 13202642a3fec02438ca7124557feb313834f922 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 26 Jun 2023 21:43:53 -0400 Subject: [PATCH 044/138] aaudio: Fixed capitialization, plus some minor cleanups. --- src/audio/SDL_audio.c | 2 +- src/audio/SDL_sysaudio.h | 2 +- src/audio/aaudio/SDL_aaudio.c | 53 +++++++++++++-------------- src/audio/aaudio/SDL_aaudio.h | 12 +++--- src/audio/aaudio/SDL_aaudiofuncs.h | 3 ++ src/video/android/SDL_androidevents.c | 20 +++++----- 6 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 992cfde99e..e5faeafceb 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -54,7 +54,7 @@ static const AudioBootStrap *const bootstrap[] = { &COREAUDIO_bootstrap, #endif #ifdef SDL_AUDIO_DRIVER_AAUDIO - &aaudio_bootstrap, + &AAUDIO_bootstrap, #endif #ifdef SDL_AUDIO_DRIVER_OPENSLES &openslES_bootstrap, diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index b9c7da6cc4..c318f55ab5 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -291,7 +291,7 @@ extern AudioBootStrap HAIKUAUDIO_bootstrap; extern AudioBootStrap COREAUDIO_bootstrap; extern AudioBootStrap DISKAUDIO_bootstrap; extern AudioBootStrap DUMMYAUDIO_bootstrap; -extern AudioBootStrap aaudio_bootstrap; /* !!! FIXME: capitalize this to match the others */ +extern AudioBootStrap AAUDIO_bootstrap; extern AudioBootStrap openslES_bootstrap; /* !!! FIXME: capitalize this to match the others */ extern AudioBootStrap ANDROIDAUDIO_bootstrap; extern AudioBootStrap PS2AUDIO_bootstrap; diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c index 76fe4e710a..92bd00a104 100644 --- a/src/audio/aaudio/SDL_aaudio.c +++ b/src/audio/aaudio/SDL_aaudio.c @@ -56,11 +56,10 @@ typedef struct AAUDIO_Data void *handle; #define SDL_PROC(ret, func, params) ret (*func) params; #include "SDL_aaudiofuncs.h" -#undef SDL_PROC } AAUDIO_Data; static AAUDIO_Data ctx; -static int aaudio_LoadFunctions(AAUDIO_Data *data) +static int AAUDIO_LoadFunctions(AAUDIO_Data *data) { #define SDL_PROC(ret, func, params) \ do { \ @@ -70,19 +69,17 @@ static int aaudio_LoadFunctions(AAUDIO_Data *data) } \ } while (0); #include "SDL_aaudiofuncs.h" -#undef SDL_PROC return 0; } -void aaudio_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error); -void aaudio_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error) +static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error) { - LOGI("SDL aaudio_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error)); + LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error)); } #define LIB_AAUDIO_SO "libaaudio.so" -static int aaudio_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int AAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) { struct SDL_PrivateAudioData *private; SDL_bool iscapture = _this->iscapture; @@ -123,7 +120,7 @@ static int aaudio_OpenDevice(SDL_AudioDevice *_this, const char *devname) ctx.AAudioStreamBuilder_setFormat(ctx.builder, format); } - ctx.AAudioStreamBuilder_setErrorCallback(ctx.builder, aaudio_errorCallback, private); + ctx.AAudioStreamBuilder_setErrorCallback(ctx.builder, AAUDIO_errorCallback, private); LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u", _this->spec.freq, SDL_AUDIO_BITSIZE(_this->spec.format), @@ -174,7 +171,7 @@ static int aaudio_OpenDevice(SDL_AudioDevice *_this, const char *devname) return 0; } -static void aaudio_CloseDevice(SDL_AudioDevice *_this) +static void AAUDIO_CloseDevice(SDL_AudioDevice *_this) { struct SDL_PrivateAudioData *private = _this->hidden; aaudio_result_t res; @@ -200,13 +197,13 @@ static void aaudio_CloseDevice(SDL_AudioDevice *_this) SDL_free(_this->hidden); } -static Uint8 *aaudio_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *_this) { struct SDL_PrivateAudioData *private = _this->hidden; return private->mixbuf; } -static void aaudio_PlayDevice(SDL_AudioDevice *_this) +static void AAUDIO_PlayDevice(SDL_AudioDevice *_this) { struct SDL_PrivateAudioData *private = _this->hidden; aaudio_result_t res; @@ -231,7 +228,7 @@ static void aaudio_PlayDevice(SDL_AudioDevice *_this) #endif } -static int aaudio_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static int AAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) { struct SDL_PrivateAudioData *private = _this->hidden; aaudio_result_t res; @@ -245,7 +242,7 @@ static int aaudio_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int bu return res * private->frame_size; } -static void aaudio_Deinitialize(void) +static void AAUDIO_Deinitialize(void) { LOGI(__func__); if (ctx.handle) { @@ -263,7 +260,7 @@ static void aaudio_Deinitialize(void) LOGI("End AAUDIO %s", SDL_GetError()); } -static SDL_bool aaudio_Init(SDL_AudioDriverImpl *impl) +static SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl) { aaudio_result_t res; LOGI(__func__); @@ -285,7 +282,7 @@ static SDL_bool aaudio_Init(SDL_AudioDriverImpl *impl) goto failure; } - if (aaudio_LoadFunctions(&ctx) < 0) { + if (AAUDIO_LoadFunctions(&ctx) < 0) { goto failure; } @@ -301,12 +298,12 @@ static SDL_bool aaudio_Init(SDL_AudioDriverImpl *impl) } impl->DetectDevices = Android_DetectDevices; - impl->Deinitialize = aaudio_Deinitialize; - impl->OpenDevice = aaudio_OpenDevice; - impl->CloseDevice = aaudio_CloseDevice; - impl->PlayDevice = aaudio_PlayDevice; - impl->GetDeviceBuf = aaudio_GetDeviceBuf; - impl->CaptureFromDevice = aaudio_CaptureFromDevice; + impl->Deinitialize = AAUDIO_Deinitialize; + impl->OpenDevice = AAUDIO_OpenDevice; + impl->CloseDevice = AAUDIO_CloseDevice; + impl->PlayDevice = AAUDIO_PlayDevice; + impl->GetDeviceBuf = AAUDIO_GetDeviceBuf; + impl->CaptureFromDevice = AAUDIO_CaptureFromDevice; impl->AllowsArbitraryDeviceNames = SDL_TRUE; /* and the capabilities */ @@ -315,7 +312,7 @@ static SDL_bool aaudio_Init(SDL_AudioDriverImpl *impl) impl->OnlyHasDefaultCaptureDevice = SDL_FALSE; /* this audio target is available. */ - LOGI("SDL aaudio_Init OK"); + LOGI("SDL AAUDIO_Init OK"); return SDL_TRUE; failure: @@ -330,12 +327,12 @@ failure: return SDL_FALSE; } -AudioBootStrap aaudio_bootstrap = { - "AAudio", "AAudio audio driver", aaudio_Init, SDL_FALSE +AudioBootStrap AAUDIO_bootstrap = { + "AAudio", "AAudio audio driver", AAUDIO_Init, SDL_FALSE }; /* Pause (block) all non already paused audio devices by taking their mixer lock */ -void aaudio_PauseDevices(void) +void AAUDIO_PauseDevices(void) { int i; @@ -406,7 +403,7 @@ void aaudio_PauseDevices(void) } /* Resume (unblock) all non already paused audio devices by releasing their mixer lock */ -void aaudio_ResumeDevices(void) +void AAUDIO_ResumeDevices(void) { int i; @@ -473,7 +470,7 @@ void aaudio_ResumeDevices(void) None of the standard state queries indicate any problem in my testing. And the error callback doesn't actually get called. But, AAudioStream_getTimestamp() does return AAUDIO_ERROR_INVALID_STATE */ -SDL_bool aaudio_DetectBrokenPlayState(void) +SDL_bool AAUDIO_DetectBrokenPlayState(void) { int i; @@ -506,7 +503,7 @@ SDL_bool aaudio_DetectBrokenPlayState(void) aaudio_stream_state_t currentState = ctx.AAudioStream_getState(private->stream); /* AAudioStream_getTimestamp() will also return AAUDIO_ERROR_INVALID_STATE while the stream is still initially starting. But we only care if it silently went invalid while playing. */ if (currentState == AAUDIO_STREAM_STATE_STARTED) { - LOGI("SDL aaudio_DetectBrokenPlayState: detected invalid audio device state: AAudioStream_getTimestamp result=%d, framePosition=%lld, timeNanoseconds=%lld, getState=%d", (int)res, (long long)framePosition, (long long)timeNanoseconds, (int)currentState); + LOGI("SDL AAUDIO_DetectBrokenPlayState: detected invalid audio device state: AAudioStream_getTimestamp result=%d, framePosition=%lld, timeNanoseconds=%lld, getState=%d", (int)res, (long long)framePosition, (long long)timeNanoseconds, (int)currentState); return SDL_TRUE; } } diff --git a/src/audio/aaudio/SDL_aaudio.h b/src/audio/aaudio/SDL_aaudio.h index 6b20bce91f..137dd81577 100644 --- a/src/audio/aaudio/SDL_aaudio.h +++ b/src/audio/aaudio/SDL_aaudio.h @@ -25,15 +25,15 @@ #ifdef SDL_AUDIO_DRIVER_AAUDIO -void aaudio_ResumeDevices(void); -void aaudio_PauseDevices(void); -SDL_bool aaudio_DetectBrokenPlayState(void); +void AAUDIO_ResumeDevices(void); +void AAUDIO_PauseDevices(void); +SDL_bool AAUDIO_DetectBrokenPlayState(void); #else -static void aaudio_ResumeDevices(void) {} -static void aaudio_PauseDevices(void) {} -static SDL_bool aaudio_DetectBrokenPlayState(void) { return SDL_FALSE; } +#define AAUDIO_ResumeDevices() +#define AAUDIO_PauseDevices() +#define AAUDIO_DetectBrokenPlayState() (SDL_FALSE) #endif diff --git a/src/audio/aaudio/SDL_aaudiofuncs.h b/src/audio/aaudio/SDL_aaudiofuncs.h index 3f0749ca6c..1e4e989057 100644 --- a/src/audio/aaudio/SDL_aaudiofuncs.h +++ b/src/audio/aaudio/SDL_aaudiofuncs.h @@ -77,3 +77,6 @@ SDL_PROC_UNUSED(aaudio_content_type_t, AAudioStream_getContentType, (AAudioStrea SDL_PROC_UNUSED(aaudio_input_preset_t, AAudioStream_getInputPreset, (AAudioStream * stream)) /* API 28 */ SDL_PROC_UNUSED(aaudio_allowed_capture_policy_t, AAudioStream_getAllowedCapturePolicy, (AAudioStream * stream)) /* API 29 */ SDL_PROC_UNUSED(bool, AAudioStream_isPrivacySensitive, (AAudioStream * stream)) /* API 30 */ + +#undef SDL_PROC +#undef SDL_PROC_UNUSED diff --git a/src/video/android/SDL_androidevents.c b/src/video/android/SDL_androidevents.c index e667255219..8dbe13ddb9 100644 --- a/src/video/android/SDL_androidevents.c +++ b/src/video/android/SDL_androidevents.c @@ -108,7 +108,7 @@ void Android_PumpEvents_Blocking(SDL_VideoDevice *_this) ANDROIDAUDIO_PauseDevices(); openslES_PauseDevices(); - aaudio_PauseDevices(); + AAUDIO_PauseDevices(); if (SDL_WaitSemaphore(Android_ResumeSem) == 0) { @@ -119,7 +119,7 @@ void Android_PumpEvents_Blocking(SDL_VideoDevice *_this) ANDROIDAUDIO_ResumeDevices(); openslES_ResumeDevices(); - aaudio_ResumeDevices(); + AAUDIO_ResumeDevices(); /* Restore the GL Context from here, as this operation is thread dependent */ #ifdef SDL_VIDEO_OPENGL_EGL @@ -160,9 +160,9 @@ void Android_PumpEvents_Blocking(SDL_VideoDevice *_this) } } - if (aaudio_DetectBrokenPlayState()) { - aaudio_PauseDevices(); - aaudio_ResumeDevices(); + if (AAUDIO_DetectBrokenPlayState()) { + AAUDIO_PauseDevices(); + AAUDIO_ResumeDevices(); } } @@ -187,7 +187,7 @@ void Android_PumpEvents_NonBlocking(SDL_VideoDevice *_this) if (videodata->pauseAudio) { ANDROIDAUDIO_PauseDevices(); openslES_PauseDevices(); - aaudio_PauseDevices(); + AAUDIO_PauseDevices(); } backup_context = 0; @@ -203,7 +203,7 @@ void Android_PumpEvents_NonBlocking(SDL_VideoDevice *_this) if (videodata->pauseAudio) { ANDROIDAUDIO_ResumeDevices(); openslES_ResumeDevices(); - aaudio_ResumeDevices(); + AAUDIO_ResumeDevices(); } #ifdef SDL_VIDEO_OPENGL_EGL @@ -246,9 +246,9 @@ void Android_PumpEvents_NonBlocking(SDL_VideoDevice *_this) } } - if (aaudio_DetectBrokenPlayState()) { - aaudio_PauseDevices(); - aaudio_ResumeDevices(); + if (AAUDIO_DetectBrokenPlayState()) { + AAUDIO_PauseDevices(); + AAUDIO_ResumeDevices(); } } From cfc8a0d17d4191f690764341b02026a92908a221 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 28 Jun 2023 10:13:34 -0400 Subject: [PATCH 045/138] pipewire: First shot at moving to the new SDL3 audio interfaces. This needs a little work still, but it mostly works. --- CMakeLists.txt | 1 - src/audio/SDL_audio.c | 21 ++- src/audio/SDL_sysaudio.h | 1 + src/audio/pipewire/SDL_pipewire.c | 291 +++++++++++++----------------- src/audio/pipewire/SDL_pipewire.h | 5 +- 5 files changed, 142 insertions(+), 177 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de86f90c10..56646f9b73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -356,7 +356,6 @@ set(SDL_VENDOR_INFO "" CACHE STRING "Vendor name and/or version to add to SDL_RE set(SDL_OSS OFF) set(SDL_ALSA OFF) set(SDL_JACK OFF) -set(SDL_PIPEWIRE OFF) set(SDL_SNDIO OFF) cmake_dependent_option(SDL_SHARED "Build a shared version of the library" ${SDL_SHARED_DEFAULT} ${SDL_SHARED_AVAILABLE} OFF) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index e5faeafceb..2baa2660b1 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -634,7 +634,16 @@ void SDL_QuitAudio(void) } - +void SDL_AudioThreadFinalize(SDL_AudioDevice *device) +{ + if (SDL_AtomicGet(&device->condemned)) { + if (device->thread) { + SDL_DetachThread(device->thread); // no one is waiting for us, just detach ourselves. + device->thread = NULL; + } + DestroyPhysicalAudioDevice(device); + } +} // Output device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort. @@ -719,11 +728,7 @@ void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device) // Wait for the audio to drain. !!! FIXME: don't bother waiting if device is lost. SDL_Delay(((samples * 1000) / device->spec.freq) * 2); current_audio.impl.ThreadDeinit(device); - if (SDL_AtomicGet(&device->condemned)) { - SDL_DetachThread(device->thread); // no one is waiting for us, just detach ourselves. - device->thread = NULL; - DestroyPhysicalAudioDevice(device); - } + SDL_AudioThreadFinalize(device); } static int SDLCALL OutputAudioThread(void *devicep) // thread entry point @@ -810,9 +815,7 @@ void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device) SDL_assert(device->iscapture); current_audio.impl.FlushCapture(device); current_audio.impl.ThreadDeinit(device); - if (SDL_AtomicGet(&device->condemned)) { - DestroyPhysicalAudioDevice(device); - } + SDL_AudioThreadFinalize(device); } static int SDLCALL CaptureAudioThread(void *devicep) // thread entry point diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index c318f55ab5..a727805493 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -99,6 +99,7 @@ extern void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device); extern void SDL_CaptureAudioThreadSetup(SDL_AudioDevice *device); extern SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device); extern void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device); +extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device); typedef struct SDL_AudioDriverImpl { diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c index 1d1b156254..5309e21310 100644 --- a/src/audio/pipewire/SDL_pipewire.c +++ b/src/audio/pipewire/SDL_pipewire.c @@ -327,7 +327,11 @@ static void io_list_remove(Uint32 id) spa_list_remove(&n->link); if (hotplug_events_enabled) { - SDL_RemoveAudioDevice(n->is_capture, PW_ID_TO_HANDLE(id)); + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle(PW_ID_TO_HANDLE(id)); + if (device) { + SDL_UnlockMutex(device->lock); // AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. + SDL_AudioDeviceDisconnected(device); + } } SDL_free(n); @@ -383,6 +387,7 @@ static struct io_node *io_list_get_by_id(Uint32 id) return NULL; } +#if 0 static struct io_node *io_list_get_by_path(char *path) { struct io_node *n, *temp; @@ -393,6 +398,7 @@ static struct io_node *io_list_get_by_path(char *path) } return NULL; } +#endif static void node_object_destroy(struct node_object *node) { @@ -833,7 +839,7 @@ static void hotplug_loop_destroy(void) } } -static void PIPEWIRE_DetectDevices(void) +static void PIPEWIRE_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { struct io_node *io; @@ -848,7 +854,10 @@ static void PIPEWIRE_DetectDevices(void) io_list_sort(); spa_list_for_each (io, &hotplug_io_list, link) { - SDL_AddAudioDevice(io->is_capture, io->name, &io->spec, PW_ID_TO_HANDLE(io->id)); + SDL_AudioDevice *device = SDL_AddAudioDevice(io->is_capture, io->name, &io->spec, PW_ID_TO_HANDLE(io->id)); +// !!! FIXME: obviously no +if (!io->is_capture && !*default_output) { *default_output = device; } +if (io->is_capture && !*default_capture) { *default_capture = device; } } hotplug_events_enabled = SDL_TRUE; @@ -936,167 +945,115 @@ static void initialize_spa_info(const SDL_AudioSpec *spec, struct spa_audio_info } } -static void output_callback(void *data) +static Uint8 *PIPEWIRE_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - struct pw_buffer *pw_buf; - struct spa_buffer *spa_buf; - Uint8 *dst; + // See if a buffer is available. If this returns NULL, SDL_OutputAudioThreadIterate will return SDL_FALSE, but since we own the thread, it won't kill playback. + // !!! FIXME: It's not clear to me if this ever returns NULL or if this was just defensive coding. - SDL_AudioDevice *_this = (SDL_AudioDevice *)data; - struct pw_stream *stream = _this->hidden->stream; - - /* Shutting down, don't do anything */ - if (SDL_AtomicGet(&_this->shutdown)) { - return; - } - - /* See if a buffer is available */ - pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); + struct pw_stream *stream = device->hidden->stream; + struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); if (pw_buf == NULL) { - return; + return NULL; } - spa_buf = pw_buf->buffer; - + struct spa_buffer *spa_buf = pw_buf->buffer; if (spa_buf->datas[0].data == NULL) { - return; + PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + return NULL; } - /* - * If the device is disabled, write silence to the stream buffer - * and run the callback with the work buffer to keep the callback - * firing regularly in case the audio is being used as a timer. - */ - SDL_LockMutex(_this->mixer_lock); - if (!SDL_AtomicGet(&_this->paused)) { - if (SDL_AtomicGet(&_this->enabled)) { - dst = spa_buf->datas[0].data; - } else { - dst = _this->work_buffer; - SDL_memset(spa_buf->datas[0].data, _this->spec.silence, _this->spec.size); - } - - if (!_this->stream) { - _this->callbackspec.callback(_this->callbackspec.userdata, dst, _this->callbackspec.size); - } else { - int got; - - /* Fire the callback until we have enough to fill a buffer */ - while (SDL_GetAudioStreamAvailable(_this->stream) < _this->spec.size) { - _this->callbackspec.callback(_this->callbackspec.userdata, _this->work_buffer, _this->callbackspec.size); - SDL_PutAudioStreamData(_this->stream, _this->work_buffer, _this->callbackspec.size); - } - - got = SDL_GetAudioStreamData(_this->stream, dst, _this->spec.size); - SDL_assert(got == _this->spec.size); - } - } else { - SDL_memset(spa_buf->datas[0].data, _this->spec.silence, _this->spec.size); - } - SDL_UnlockMutex(_this->mixer_lock); + device->hidden->pw_buf = pw_buf; + return (Uint8 *) spa_buf->datas[0].data; +} +static void PIPEWIRE_PlayDevice(SDL_AudioDevice *device, int buffer_size) +{ + struct pw_stream *stream = device->hidden->stream; + struct pw_buffer *pw_buf = device->hidden->pw_buf; + struct spa_buffer *spa_buf = pw_buf->buffer; spa_buf->datas[0].chunk->offset = 0; - spa_buf->datas[0].chunk->stride = _this->hidden->stride; - spa_buf->datas[0].chunk->size = _this->spec.size; + spa_buf->datas[0].chunk->stride = device->hidden->stride; + spa_buf->datas[0].chunk->size = buffer_size; PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + device->hidden->pw_buf = NULL; +} + +static void output_callback(void *data) +{ + SDL_OutputAudioThreadIterate((SDL_AudioDevice *)data); +} + +static void PIPEWIRE_FlushCapture(SDL_AudioDevice *device) +{ + struct pw_stream *stream = device->hidden->stream; + struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); + if (pw_buf != NULL) { // just requeue it without any further thought. + PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + } +} + +static int PIPEWIRE_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) +{ + struct pw_stream *stream = device->hidden->stream; + struct pw_buffer *pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); + if (!pw_buf) { + return 0; + } + + struct spa_buffer *spa_buf = pw_buf->buffer; + if (!spa_buf) { + PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + return 0; + } + + const Uint8 *src = (const Uint8 *)spa_buf->datas[0].data; + const Uint32 offset = SPA_MIN(spa_buf->datas[0].chunk->offset, spa_buf->datas[0].maxsize); + const Uint32 size = SPA_MIN(spa_buf->datas[0].chunk->size, spa_buf->datas[0].maxsize - offset); + const int cpy = SDL_min(buflen, (int) size); + + SDL_assert(size <= buflen); // We'll have to reengineer some stuff if this turns out to not be true. + + SDL_memcpy(buffer, src + offset, cpy); + PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + + return cpy; } static void input_callback(void *data) { - struct pw_buffer *pw_buf; - struct spa_buffer *spa_buf; - Uint8 *src; - SDL_AudioDevice *_this = (SDL_AudioDevice *)data; - struct pw_stream *stream = _this->hidden->stream; - - /* Shutting down, don't do anything */ - if (SDL_AtomicGet(&_this->shutdown)) { - return; - } - - pw_buf = PIPEWIRE_pw_stream_dequeue_buffer(stream); - if (pw_buf == NULL) { - return; - } - - spa_buf = pw_buf->buffer; - (src = (Uint8 *)spa_buf->datas[0].data); - if (src == NULL) { - return; - } - - if (!SDL_AtomicGet(&_this->paused)) { - /* Calculate the offset and data size */ - const Uint32 offset = SPA_MIN(spa_buf->datas[0].chunk->offset, spa_buf->datas[0].maxsize); - const Uint32 size = SPA_MIN(spa_buf->datas[0].chunk->size, spa_buf->datas[0].maxsize - offset); - - src += offset; - - /* Fill the buffer with silence if the stream is disabled. */ - if (!SDL_AtomicGet(&_this->enabled)) { - SDL_memset(src, _this->callbackspec.silence, size); - } - - /* Pipewire can vary the latency, so buffer all incoming data */ - SDL_WriteToDataQueue(_this->hidden->buffer, src, size); - - while (SDL_GetDataQueueSize(_this->hidden->buffer) >= _this->callbackspec.size) { - SDL_ReadFromDataQueue(_this->hidden->buffer, _this->work_buffer, _this->callbackspec.size); - - SDL_LockMutex(_this->mixer_lock); - _this->callbackspec.callback(_this->callbackspec.userdata, _this->work_buffer, _this->callbackspec.size); - SDL_UnlockMutex(_this->mixer_lock); - } - } else if (_this->hidden->buffer) { /* Flush the buffer when paused */ - if (SDL_GetDataQueueSize(_this->hidden->buffer) != 0) { - SDL_ClearDataQueue(_this->hidden->buffer, _this->hidden->input_buffer_packet_size); - } - } - - PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf); + SDL_CaptureAudioThreadIterate((SDL_AudioDevice *)data); } static void stream_add_buffer_callback(void *data, struct pw_buffer *buffer) { - SDL_AudioDevice *_this = data; + SDL_AudioDevice *device = (SDL_AudioDevice *) data; - if (_this->iscapture == SDL_FALSE) { - /* - * Clamp the output spec samples and size to the max size of the Pipewire buffer. - * If they exceed the maximum size of the Pipewire buffer, double buffering will be used. - */ - if (_this->spec.size > buffer->buffer->datas[0].maxsize) { - _this->spec.samples = buffer->buffer->datas[0].maxsize / _this->hidden->stride; - _this->spec.size = buffer->buffer->datas[0].maxsize; + if (device->iscapture == SDL_FALSE) { + /* Clamp the output spec samples and size to the max size of the Pipewire buffer. + If they exceed the maximum size of the Pipewire buffer, double buffering will be used. */ + if (device->buffer_size > buffer->buffer->datas[0].maxsize) { + SDL_LockMutex(device->lock); + device->sample_frames = buffer->buffer->datas[0].maxsize / device->hidden->stride; + device->buffer_size = buffer->buffer->datas[0].maxsize; + SDL_UnlockMutex(device->lock); } - } else if (_this->hidden->buffer == NULL) { - /* - * The latency of source nodes can change, so buffering is always required. - * - * Ensure that the intermediate input buffer is large enough to hold the requested - * application packet size or a full buffer of data from Pipewire, whichever is larger. - * - * A packet size of 2 periods should be more than is ever needed. - */ - _this->hidden->input_buffer_packet_size = SPA_MAX(_this->spec.size, buffer->buffer->datas[0].maxsize) * 2; - _this->hidden->buffer = SDL_CreateDataQueue(_this->hidden->input_buffer_packet_size, _this->hidden->input_buffer_packet_size); } - _this->hidden->stream_init_status |= PW_READY_FLAG_BUFFER_ADDED; - PIPEWIRE_pw_thread_loop_signal(_this->hidden->loop, false); + device->hidden->stream_init_status |= PW_READY_FLAG_BUFFER_ADDED; + PIPEWIRE_pw_thread_loop_signal(device->hidden->loop, false); } static void stream_state_changed_callback(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { - SDL_AudioDevice *_this = data; + SDL_AudioDevice *device = (SDL_AudioDevice *) data; if (state == PW_STREAM_STATE_STREAMING) { - _this->hidden->stream_init_status |= PW_READY_FLAG_STREAM_READY; + device->hidden->stream_init_status |= PW_READY_FLAG_STREAM_READY; } if (state == PW_STREAM_STATE_STREAMING || state == PW_STREAM_STATE_ERROR) { - PIPEWIRE_pw_thread_loop_signal(_this->hidden->loop, false); + PIPEWIRE_pw_thread_loop_signal(device->hidden->loop, false); } } @@ -1109,7 +1066,7 @@ static const struct pw_stream_events stream_input_events = { PW_VERSION_STREAM_E .add_buffer = stream_add_buffer_callback, .process = input_callback }; -static int PIPEWIRE_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device) { /* * NOTE: The PW_STREAM_FLAG_RT_PROCESS flag can be set to call the stream @@ -1128,12 +1085,12 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *_this, const char *devname) struct SDL_PrivateAudioData *priv; struct pw_properties *props; const char *app_name, *app_id, *stream_name, *stream_role, *error; - Uint32 node_id = _this->handle == NULL ? PW_ID_ANY : PW_HANDLE_TO_ID(_this->handle); - SDL_bool iscapture = _this->iscapture; + Uint32 node_id = device->handle == NULL ? PW_ID_ANY : PW_HANDLE_TO_ID(device->handle); + const SDL_bool iscapture = device->iscapture; int res; /* Clamp the period size to sane values */ - const int min_period = PW_MIN_SAMPLES * SPA_MAX(_this->spec.freq / PW_BASE_CLOCK_RATE, 1); + const int min_period = PW_MIN_SAMPLES * SPA_MAX(device->spec.freq / PW_BASE_CLOCK_RATE, 1); /* Get the hints for the application name, stream name and role */ app_name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME); @@ -1162,27 +1119,28 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *_this, const char *devname) } /* Initialize the Pipewire stream info from the SDL audio spec */ - initialize_spa_info(&_this->spec, &spa_info); + initialize_spa_info(&device->spec, &spa_info); params = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &spa_info); if (params == NULL) { return SDL_SetError("Pipewire: Failed to set audio format parameters"); } priv = SDL_calloc(1, sizeof(struct SDL_PrivateAudioData)); - _this->hidden = priv; + device->hidden = priv; if (priv == NULL) { return SDL_OutOfMemory(); } /* Size of a single audio frame in bytes */ - priv->stride = (SDL_AUDIO_BITSIZE(_this->spec.format) >> 3) * _this->spec.channels; + priv->stride = (SDL_AUDIO_BITSIZE(device->spec.format) / 8) * device->spec.channels; - if (_this->spec.samples < min_period) { - _this->spec.samples = min_period; - _this->spec.size = _this->spec.samples * priv->stride; + if (device->sample_frames < min_period) { + device->sample_frames = min_period; } - (void)SDL_snprintf(thread_name, sizeof(thread_name), "SDLAudio%c%ld", (iscapture) ? 'C' : 'P', (long)_this->handle); + SDL_UpdatedAudioDeviceFormat(device); + + (void)SDL_snprintf(thread_name, sizeof(thread_name), "SDLAudio%c%ld", (iscapture) ? 'C' : 'P', (long)device->handle); priv->loop = PIPEWIRE_pw_thread_loop_new(thread_name, NULL); if (priv->loop == NULL) { return SDL_SetError("Pipewire: Failed to create stream loop (%i)", errno); @@ -1213,8 +1171,8 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *_this, const char *devname) } PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_NAME, stream_name); PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, stream_name); - PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%i", _this->spec.samples, _this->spec.freq); - PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", _this->spec.freq); + PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%i", device->sample_frames, device->spec.freq); + PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", device->spec.freq); PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); /* @@ -1240,7 +1198,7 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *_this, const char *devname) /* Create the new stream */ priv->stream = PIPEWIRE_pw_stream_new_simple(PIPEWIRE_pw_thread_loop_get_loop(priv->loop), stream_name, props, - iscapture ? &stream_input_events : &stream_output_events, _this); + iscapture ? &stream_input_events : &stream_output_events, device); if (priv->stream == NULL) { return SDL_SetError("Pipewire: Failed to create stream (%i)", errno); } @@ -1268,39 +1226,38 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *_this, const char *devname) return SDL_SetError("Pipewire: Stream error: %s", error); } - /* If this is a capture stream, make sure the intermediate buffer was successfully allocated. */ - if (iscapture && priv->buffer == NULL) { - return SDL_SetError("Pipewire: Failed to allocate source buffer"); - } - return 0; } -static void PIPEWIRE_CloseDevice(SDL_AudioDevice *_this) +static void PIPEWIRE_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden->loop) { - PIPEWIRE_pw_thread_loop_stop(_this->hidden->loop); + if (!device->hidden) { + return; } - if (_this->hidden->stream) { - PIPEWIRE_pw_stream_destroy(_this->hidden->stream); + if (device->hidden->loop) { + PIPEWIRE_pw_thread_loop_stop(device->hidden->loop); } - if (_this->hidden->context) { - PIPEWIRE_pw_context_destroy(_this->hidden->context); + if (device->hidden->stream) { + PIPEWIRE_pw_stream_destroy(device->hidden->stream); } - if (_this->hidden->loop) { - PIPEWIRE_pw_thread_loop_destroy(_this->hidden->loop); + if (device->hidden->context) { + PIPEWIRE_pw_context_destroy(device->hidden->context); } - if (_this->hidden->buffer) { - SDL_DestroyDataQueue(_this->hidden->buffer); + if (device->hidden->loop) { + PIPEWIRE_pw_thread_loop_destroy(device->hidden->loop); } - SDL_free(_this->hidden); + SDL_free(device->hidden); + device->hidden = NULL; + + SDL_AudioThreadFinalize(device); } +#if 0 static int PIPEWIRE_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) { struct io_node *node; @@ -1338,6 +1295,7 @@ failed: PIPEWIRE_pw_thread_loop_unlock(hotplug_loop); return ret; } +#endif static void PIPEWIRE_Deinitialize(void) { @@ -1366,13 +1324,16 @@ static SDL_bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl) /* Set the function pointers */ impl->DetectDevices = PIPEWIRE_DetectDevices; impl->OpenDevice = PIPEWIRE_OpenDevice; - impl->CloseDevice = PIPEWIRE_CloseDevice; impl->Deinitialize = PIPEWIRE_Deinitialize; - impl->GetDefaultAudioInfo = PIPEWIRE_GetDefaultAudioInfo; + //impl->GetDefaultAudioInfo = PIPEWIRE_GetDefaultAudioInfo; + impl->PlayDevice = PIPEWIRE_PlayDevice; + impl->GetDeviceBuf = PIPEWIRE_GetDeviceBuf; + impl->CaptureFromDevice = PIPEWIRE_CaptureFromDevice; + impl->FlushCapture = PIPEWIRE_FlushCapture; + impl->CloseDevice = PIPEWIRE_CloseDevice; impl->HasCaptureSupport = SDL_TRUE; impl->ProvidesOwnCallbackThread = SDL_TRUE; - impl->SupportsNonPow2Samples = SDL_TRUE; return SDL_TRUE; } diff --git a/src/audio/pipewire/SDL_pipewire.h b/src/audio/pipewire/SDL_pipewire.h index 4ca3315b4d..5a6772ab52 100644 --- a/src/audio/pipewire/SDL_pipewire.h +++ b/src/audio/pipewire/SDL_pipewire.h @@ -32,11 +32,12 @@ struct SDL_PrivateAudioData struct pw_thread_loop *loop; struct pw_stream *stream; struct pw_context *context; - struct SDL_DataQueue *buffer; - size_t input_buffer_packet_size; Sint32 stride; /* Bytes-per-frame */ int stream_init_status; + + // Set in GetDeviceBuf, filled in AudioThreadIterate, queued in PlayDevice + struct pw_buffer *pw_buf; }; #endif /* SDL_pipewire_h_ */ From ad6c1781fc28c35e2acc89d4408d4c28f465c7e9 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 28 Jun 2023 10:21:57 -0400 Subject: [PATCH 046/138] pulseaudio: Minor cleanups. --- src/audio/pulseaudio/SDL_pulseaudio.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index eeebe41662..0852b2cc4c 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -556,12 +556,13 @@ static void SourceDeviceNameCallback(pa_context *c, const pa_source_info *i, int PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); } -static SDL_bool FindDeviceName(struct SDL_PrivateAudioData *h, const SDL_bool iscapture, void *handle) +static SDL_bool FindDeviceName(SDL_AudioDevice *device) { - SDL_assert(handle != NULL); // this was a thing in SDL2, but shouldn't be in SDL3. - const uint32_t idx = ((uint32_t)((intptr_t)handle)) - 1; + struct SDL_PrivateAudioData *h = device->hidden; + SDL_assert(device->handle != NULL); // this was a thing in SDL2, but shouldn't be in SDL3. + const uint32_t idx = ((uint32_t)((intptr_t)device->handle)) - 1; - if (iscapture) { + if (device->iscapture) { WaitForPulseOperation(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceDeviceNameCallback, &h->device_name)); } else { WaitForPulseOperation(PULSEAUDIO_pa_context_get_sink_info_by_index(pulseaudio_context, idx, SinkDeviceNameCallback, &h->device_name)); @@ -592,11 +593,10 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) SDL_assert(pulseaudio_context != NULL); /* Initialize all variables that we clean on shutdown */ - h = device->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*device->hidden)); + h = device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(device->hidden); /* Try for a closest match on audio format */ closefmts = SDL_ClosestAudioFormats(device->spec.format); @@ -663,7 +663,7 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); - if (!FindDeviceName(h, iscapture, device->handle)) { + if (!FindDeviceName(device)) { retval = SDL_SetError("Requested PulseAudio sink/source missing?"); } else { const char *name = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME); @@ -953,7 +953,6 @@ static SDL_bool PULSEAUDIO_Init(SDL_AudioDriverImpl *impl) impl->FlushCapture = PULSEAUDIO_FlushCapture; impl->HasCaptureSupport = SDL_TRUE; - impl->SupportsNonPow2Samples = SDL_TRUE; return SDL_TRUE; /* this audio target is available. */ } From a93fcf2444d4709d62a5216181ad5f7b71ebe315 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 02:34:40 -0400 Subject: [PATCH 047/138] audio: fixed flushed stream reporting bytes but not being able to get them. This would happen when you had ~1 frame of audio left in the stream, and resampling needs would cause this to not be enough to produce audio. But since we're already flushed, we can just add silence padding to let the app extract these last bits. --- src/audio/SDL_audiocvt.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index c33daf6ac9..effc3af255 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -872,9 +872,18 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le input_frames = len / dst_sample_frame_size; // total sample frames caller wants if (dst_rate != src_rate) { // calculate requested sample frames needed before resampling. Use a Uint64 so the multiplication doesn't overflow. - input_frames = (int) ((((Uint64) input_frames) * src_rate) / dst_rate); - if (input_frames == 0) { - return 0; // 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. + const int resampled_input_frames = (int) ((((Uint64) input_frames) * src_rate) / dst_rate); + if (resampled_input_frames > 0) { + input_frames = resampled_input_frames; + } else { // uhoh, not enough input frames! + // 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. + if (stream->flushed) { + SDL_assert(((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. + } else { + return 0; + } } } From 1dffb72c1decb4911133e36f3b85dad578319c6f Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 03:05:51 -0400 Subject: [PATCH 048/138] pipewire: Hooked up default device change notifications. --- src/audio/SDL_audio.c | 9 ++- src/audio/pipewire/SDL_pipewire.c | 112 +++++++----------------------- 2 files changed, 34 insertions(+), 87 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 2baa2660b1..abcd2219fa 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -571,8 +571,13 @@ int SDL_InitAudio(const char *driver_name) SDL_AudioDevice *default_capture = NULL; current_audio.impl.DetectDevices(&default_output, &default_capture); - current_audio.default_output_device_id = default_output ? default_output->instance_id : 0; - current_audio.default_capture_device_id = default_capture ? default_capture->instance_id : 0; + // these are only set if default_* is non-NULL, in case the backend just called SDL_DefaultAudioDeviceChanged directly during DetectDevices. + if (default_output) { + current_audio.default_output_device_id = default_output->instance_id; + } + if (default_capture) { + current_audio.default_capture_device_id = default_capture->instance_id; + } // !!! FIXME: if a default is zero but there are devices available, should we just pick the first one? diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c index 5309e21310..fc8efc539a 100644 --- a/src/audio/pipewire/SDL_pipewire.c +++ b/src/audio/pipewire/SDL_pipewire.c @@ -341,31 +341,6 @@ static void io_list_remove(Uint32 id) } } -static void io_list_sort(void) -{ - struct io_node *default_sink = NULL, *default_source = NULL; - struct io_node *n, *temp; - - /* Find and move the default nodes to the beginning of the list */ - spa_list_for_each_safe (n, temp, &hotplug_io_list, link) { - if (pipewire_default_sink_id != NULL && SDL_strcmp(n->path, pipewire_default_sink_id) == 0) { - default_sink = n; - spa_list_remove(&n->link); - } else if (pipewire_default_source_id != NULL && SDL_strcmp(n->path, pipewire_default_source_id) == 0) { - default_source = n; - spa_list_remove(&n->link); - } - } - - if (default_source) { - spa_list_prepend(&hotplug_io_list, &default_source->link); - } - - if (default_sink) { - spa_list_prepend(&hotplug_io_list, &default_sink->link); - } -} - static void io_list_clear(void) { struct io_node *n, *temp; @@ -387,19 +362,6 @@ static struct io_node *io_list_get_by_id(Uint32 id) return NULL; } -#if 0 -static struct io_node *io_list_get_by_path(char *path) -{ - struct io_node *n, *temp; - spa_list_for_each_safe (n, temp, &hotplug_io_list, link) { - if (SDL_strcmp(n->path, path) == 0) { - return n; - } - } - return NULL; -} -#endif - static void node_object_destroy(struct node_object *node) { SDL_assert(node); @@ -646,6 +608,23 @@ static char *get_name_from_json(const char *json) return SDL_strdup(value); } +static void change_default_device(const char *path) +{ + if (hotplug_events_enabled) { + struct io_node *n, *temp; + spa_list_for_each_safe (n, temp, &hotplug_io_list, link) { + if (SDL_strcmp(n->path, path) == 0) { + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle(PW_ID_TO_HANDLE(n->id)); + if (device) { + SDL_UnlockMutex(device->lock); + SDL_DefaultAudioDeviceChanged(device); + } + return; // found it, we're done. + } + } + } +} + /* Metadata node callback */ static int metadata_property(void *object, Uint32 subject, const char *key, const char *type, const char *value) { @@ -658,12 +637,14 @@ static int metadata_property(void *object, Uint32 subject, const char *key, cons } pipewire_default_sink_id = get_name_from_json(value); node->persist = SDL_TRUE; + change_default_device(pipewire_default_sink_id); } else if (!SDL_strcmp(key, "default.audio.source")) { if (pipewire_default_source_id != NULL) { SDL_free(pipewire_default_source_id); } pipewire_default_source_id = get_name_from_json(value); node->persist = SDL_TRUE; + change_default_device(pipewire_default_source_id); } } @@ -850,14 +831,15 @@ static void PIPEWIRE_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDe PIPEWIRE_pw_thread_loop_wait(hotplug_loop); } - /* Sort the I/O list so the default source/sink are listed first */ - io_list_sort(); - spa_list_for_each (io, &hotplug_io_list, link) { SDL_AudioDevice *device = SDL_AddAudioDevice(io->is_capture, io->name, &io->spec, PW_ID_TO_HANDLE(io->id)); -// !!! FIXME: obviously no -if (!io->is_capture && !*default_output) { *default_output = device; } -if (io->is_capture && !*default_capture) { *default_capture = device; } + if (pipewire_default_sink_id != NULL && SDL_strcmp(io->path, pipewire_default_sink_id) == 0) { + SDL_assert(!io->is_capture); + *default_output = device; + } else if (pipewire_default_source_id != NULL && SDL_strcmp(io->path, pipewire_default_source_id) == 0) { + SDL_assert(io->is_capture); + *default_capture = device; + } } hotplug_events_enabled = SDL_TRUE; @@ -1174,6 +1156,7 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device) PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%i", device->sample_frames, device->spec.freq); PIPEWIRE_pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", device->spec.freq); PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); + PIPEWIRE_pw_properties_set(props, PW_KEY_NODE_DONT_RECONNECT, "true"); // Requesting a specific device, don't migrate to new default hardware. /* * Pipewire 0.3.44 introduced PW_KEY_TARGET_OBJECT that takes either a path @@ -1257,46 +1240,6 @@ static void PIPEWIRE_CloseDevice(SDL_AudioDevice *device) SDL_AudioThreadFinalize(device); } -#if 0 -static int PIPEWIRE_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) -{ - struct io_node *node; - char *target; - int ret = 0; - - PIPEWIRE_pw_thread_loop_lock(hotplug_loop); - - if (iscapture) { - if (pipewire_default_source_id == NULL) { - ret = SDL_SetError("PipeWire could not find a default source"); - goto failed; - } - target = pipewire_default_source_id; - } else { - if (pipewire_default_sink_id == NULL) { - ret = SDL_SetError("PipeWire could not find a default sink"); - goto failed; - } - target = pipewire_default_sink_id; - } - - node = io_list_get_by_path(target); - if (node == NULL) { - ret = SDL_SetError("PipeWire device list is out of sync with defaults"); - goto failed; - } - - if (name != NULL) { - *name = SDL_strdup(node->name); - } - SDL_copyp(spec, &node->spec); - -failed: - PIPEWIRE_pw_thread_loop_unlock(hotplug_loop); - return ret; -} -#endif - static void PIPEWIRE_Deinitialize(void) { if (pipewire_initialized) { @@ -1325,7 +1268,6 @@ static SDL_bool PIPEWIRE_Init(SDL_AudioDriverImpl *impl) impl->DetectDevices = PIPEWIRE_DetectDevices; impl->OpenDevice = PIPEWIRE_OpenDevice; impl->Deinitialize = PIPEWIRE_Deinitialize; - //impl->GetDefaultAudioInfo = PIPEWIRE_GetDefaultAudioInfo; impl->PlayDevice = PIPEWIRE_PlayDevice; impl->GetDeviceBuf = PIPEWIRE_GetDeviceBuf; impl->CaptureFromDevice = PIPEWIRE_CaptureFromDevice; From a8323ebe680e23ce585de33cc36e74d5617259e3 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 11:37:40 -0400 Subject: [PATCH 049/138] audio: Better handling of ProvidesOwnCallbackThread backends. --- src/audio/SDL_audio.c | 20 ++++++++++++++------ src/audio/SDL_sysaudio.h | 5 ++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index abcd2219fa..77f5c2d86c 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -388,7 +388,7 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) } // if there's an audio thread, don't free until thread is terminating, otherwise free stuff now. - const SDL_bool should_destroy = (device->thread == NULL); + const SDL_bool should_destroy = SDL_AtomicGet(&device->thread_alive) ? SDL_FALSE : SDL_TRUE; SDL_UnlockMutex(device->lock); // Post the event, if we haven't tried to before and if it's desired @@ -641,6 +641,7 @@ void SDL_QuitAudio(void) void SDL_AudioThreadFinalize(SDL_AudioDevice *device) { + SDL_assert(SDL_AtomicGet(&device->thread_alive)); if (SDL_AtomicGet(&device->condemned)) { if (device->thread) { SDL_DetachThread(device->thread); // no one is waiting for us, just detach ourselves. @@ -648,6 +649,7 @@ void SDL_AudioThreadFinalize(SDL_AudioDevice *device) } DestroyPhysicalAudioDevice(device); } + SDL_AtomicSet(&device->thread_alive, 0); } // Output device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort. @@ -1053,15 +1055,18 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec) // this expects the device lock to be held. static void ClosePhysicalAudioDevice(SDL_AudioDevice *device) { - if (device->thread != NULL) { + SDL_assert(current_audio.impl.ProvidesOwnCallbackThread || ((device->thread == NULL) == (SDL_AtomicGet(&device->thread_alive) == 0))); + + if (SDL_AtomicGet(&device->thread_alive)) { SDL_AtomicSet(&device->shutdown, 1); - SDL_WaitThread(device->thread, NULL); - device->thread = NULL; - SDL_AtomicSet(&device->shutdown, 0); + if (device->thread != NULL) { + SDL_WaitThread(device->thread, NULL); + device->thread = NULL; + } } if (device->is_opened) { - current_audio.impl.CloseDevice(device); + current_audio.impl.CloseDevice(device); // if ProvidesOwnCallbackThread, this must join on any existing device thread before returning! device->is_opened = SDL_FALSE; } @@ -1073,6 +1078,7 @@ static void ClosePhysicalAudioDevice(SDL_AudioDevice *device) SDL_memcpy(&device->spec, &device->default_spec, sizeof (SDL_AudioSpec)); device->sample_frames = 0; device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format); + SDL_AtomicSet(&device->shutdown, 0); // ready to go again. } void SDL_CloseAudioDevice(SDL_AudioDeviceID devid) @@ -1204,6 +1210,7 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec } // Start the audio thread if necessary + SDL_AtomicSet(&device->thread_alive, 1); if (!current_audio.impl.ProvidesOwnCallbackThread) { const size_t stacksize = 0; // just take the system default, since audio streams might have callbacks. char threadname[64]; @@ -1211,6 +1218,7 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec device->thread = SDL_CreateThreadInternal(device->iscapture ? CaptureAudioThread : OutputAudioThread, threadname, stacksize, device); if (device->thread == NULL) { + SDL_AtomicSet(&device->thread_alive, 0); ClosePhysicalAudioDevice(device); return SDL_SetError("Couldn't create audio thread"); } diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index a727805493..ca71097fb7 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -118,7 +118,7 @@ typedef struct SDL_AudioDriverImpl void (*Deinitialize)(void); /* Some flags to push duplicate code into the core and reduce #ifdefs. */ - SDL_bool ProvidesOwnCallbackThread; + SDL_bool ProvidesOwnCallbackThread; // !!! FIXME: rename this, it's not a callback thread anymore. SDL_bool HasCaptureSupport; SDL_bool OnlyHasDefaultOutputDevice; SDL_bool OnlyHasDefaultCaptureDevice; @@ -246,6 +246,9 @@ struct SDL_AudioDevice /* non-zero if this was a disconnected default device and we're waiting for its replacement. */ SDL_AtomicInt zombie; + /* non-zero if this has a thread running (which might be `thread` or something provided by the backend!) */ + SDL_AtomicInt thread_alive; + /* SDL_TRUE if this is a capture device instead of an output device */ SDL_bool iscapture; From b60a56d368afe4401740a522bcccca70ea13e0f4 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 11:38:17 -0400 Subject: [PATCH 050/138] audio: take first reported device if no default was specified. --- src/audio/SDL_audio.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 77f5c2d86c..56e7ac113b 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -579,7 +579,13 @@ int SDL_InitAudio(const char *driver_name) current_audio.default_capture_device_id = default_capture->instance_id; } - // !!! FIXME: if a default is zero but there are devices available, should we just pick the first one? + // If no default was _ever_ specified, just take the first device we see, if any. + if (!current_audio.default_output_device_id && (current_audio.output_devices != NULL)) { + current_audio.default_output_device_id = current_audio.output_devices->instance_id; + } + if (!current_audio.default_capture_device_id && (current_audio.capture_devices != NULL)) { + current_audio.default_capture_device_id = current_audio.capture_devices->instance_id; + } return 0; } From 1fc01b030024a9891cc884c11757654c7568c94d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 11:51:22 -0400 Subject: [PATCH 051/138] audio: Try to definitely have a default device set up. --- src/audio/SDL_audio.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 56e7ac113b..cc6f585214 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -468,6 +468,19 @@ static void CompleteAudioEntryPoints(void) #undef FILL_STUB } +static SDL_AudioDeviceID GetFirstAddedAudioDeviceID(const SDL_bool iscapture) +{ + // (these are pushed to the front of the linked list as added, so the first device added is last in the list.) + SDL_LockRWLockForReading(current_audio.device_list_lock); + SDL_AudioDevice *last = NULL; + for (SDL_AudioDevice *i = current_audio.output_devices; i != NULL; i = i->next) { + last = i; + } + const SDL_AudioDeviceID retval = last ? last->instance_id : 0; + SDL_UnlockRWLock(current_audio.device_list_lock); + return retval; +} + // !!! FIXME: the video subsystem does SDL_VideoInit, not SDL_InitVideo. Make this match. int SDL_InitAudio(const char *driver_name) { @@ -580,11 +593,11 @@ int SDL_InitAudio(const char *driver_name) } // If no default was _ever_ specified, just take the first device we see, if any. - if (!current_audio.default_output_device_id && (current_audio.output_devices != NULL)) { - current_audio.default_output_device_id = current_audio.output_devices->instance_id; + if (!current_audio.default_output_device_id) { + current_audio.default_output_device_id = GetFirstAddedAudioDeviceID(/*iscapture=*/SDL_FALSE); } if (!current_audio.default_capture_device_id && (current_audio.capture_devices != NULL)) { - current_audio.default_capture_device_id = current_audio.capture_devices->instance_id; + current_audio.default_output_device_id = GetFirstAddedAudioDeviceID(/*iscapture=*/SDL_TRUE); } return 0; From e969160de03e13246bcdab2ccf7e07a78f4421f1 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 11:52:01 -0400 Subject: [PATCH 052/138] audio: unset a freed variable to NULL --- src/audio/SDL_audio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index cc6f585214..8926b7e000 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -574,6 +574,7 @@ int SDL_InitAudio(const char *driver_name) SDL_zero(current_audio); SDL_DestroyRWLock(device_list_lock); + current_audio.device_list_lock = NULL; return -1; // No driver was available, so fail. } From 9e3c5f93e0b036f16cdc291c9df0d24a35f6e22e Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 13:42:25 -0400 Subject: [PATCH 053/138] coreaudio: Change `_this` to `device` --- src/audio/coreaudio/SDL_coreaudio.m | 324 ++++++++++++++-------------- 1 file changed, 162 insertions(+), 162 deletions(-) diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m index 08af17c41e..20ac3bf5df 100644 --- a/src/audio/coreaudio/SDL_coreaudio.m +++ b/src/audio/coreaudio/SDL_coreaudio.m @@ -323,18 +323,18 @@ static void resume_audio_devices(void) } } -static void interruption_begin(SDL_AudioDevice *_this) +static void interruption_begin(SDL_AudioDevice *device) { - if (_this != NULL && _this->hidden->audioQueue != NULL) { - _this->hidden->interrupted = SDL_TRUE; - AudioQueuePause(_this->hidden->audioQueue); + if (device != NULL && device->hidden->audioQueue != NULL) { + device->hidden->interrupted = SDL_TRUE; + AudioQueuePause(device->hidden->audioQueue); } } -static void interruption_end(SDL_AudioDevice *_this) +static void interruption_end(SDL_AudioDevice *device) { - if (_this != NULL && _this->hidden != NULL && _this->hidden->audioQueue != NULL && _this->hidden->interrupted && AudioQueueStart(_this->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) { - _this->hidden->interrupted = SDL_FALSE; + if (device != NULL && device->hidden != NULL && device->hidden->audioQueue != NULL && device->hidden->interrupted && AudioQueueStart(device->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) { + device->hidden->interrupted = SDL_FALSE; } } @@ -367,7 +367,7 @@ static void interruption_end(SDL_AudioDevice *_this) @end -static BOOL update_audio_session(SDL_AudioDevice *_this, SDL_bool open, SDL_bool allow_playandrecord) +static BOOL update_audio_session(SDL_AudioDevice *device, SDL_bool open, SDL_bool allow_playandrecord) { @autoreleasepool { AVAudioSession *session = [AVAudioSession sharedInstance]; @@ -455,7 +455,7 @@ static BOOL update_audio_session(SDL_AudioDevice *_this, SDL_bool open, SDL_bool if (![session setActive:YES error:&err]) { if ([err code] == AVAudioSessionErrorCodeResourceNotAvailable && category == AVAudioSessionCategoryPlayAndRecord) { - return update_audio_session(_this, open, SDL_FALSE); + return update_audio_session(device, open, SDL_FALSE); } NSString *desc = err.description; @@ -472,7 +472,7 @@ static BOOL update_audio_session(SDL_AudioDevice *_this, SDL_bool open, SDL_bool if (open) { SDLInterruptionListener *listener = [SDLInterruptionListener new]; - listener.device = _this; + listener.device = device; [center addObserver:listener selector:@selector(audioSessionInterruption:) @@ -493,10 +493,10 @@ static BOOL update_audio_session(SDL_AudioDevice *_this, SDL_bool open, SDL_bool name:UIApplicationWillEnterForegroundNotification object:nil]; - _this->hidden->interruption_listener = CFBridgingRetain(listener); + device->hidden->interruption_listener = CFBridgingRetain(listener); } else { SDLInterruptionListener *listener = nil; - listener = (SDLInterruptionListener *)CFBridgingRelease(_this->hidden->interruption_listener); + listener = (SDLInterruptionListener *)CFBridgingRelease(device->hidden->interruption_listener); [center removeObserver:listener]; @synchronized(listener) { listener.device = NULL; @@ -511,47 +511,47 @@ static BOOL update_audio_session(SDL_AudioDevice *_this, SDL_bool open, SDL_bool /* The AudioQueue callback */ static void outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) { - SDL_AudioDevice *_this = (SDL_AudioDevice *)inUserData; + SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; - /* This flag is set before _this->mixer_lock is destroyed during + /* This flag is set before device->mixer_lock is destroyed during shutdown, so check it before grabbing the mutex, and then check it again _after_ in case we blocked waiting on the lock. */ - if (SDL_AtomicGet(&_this->shutdown)) { + if (SDL_AtomicGet(&device->shutdown)) { return; /* don't do anything, since we don't even want to enqueue this buffer again. */ } - SDL_LockMutex(_this->mixer_lock); + SDL_LockMutex(device->mixer_lock); - if (SDL_AtomicGet(&_this->shutdown)) { - SDL_UnlockMutex(_this->mixer_lock); + if (SDL_AtomicGet(&device->shutdown)) { + SDL_UnlockMutex(device->mixer_lock); return; /* don't do anything, since we don't even want to enqueue this buffer again. */ } - if (!SDL_AtomicGet(&_this->enabled) || SDL_AtomicGet(&_this->paused)) { + if (!SDL_AtomicGet(&device->enabled) || SDL_AtomicGet(&device->paused)) { /* Supply silence if audio is not enabled or paused */ - SDL_memset(inBuffer->mAudioData, _this->spec.silence, inBuffer->mAudioDataBytesCapacity); - } else if (_this->stream) { + SDL_memset(inBuffer->mAudioData, device->spec.silence, inBuffer->mAudioDataBytesCapacity); + } else if (device->stream) { UInt32 remaining = inBuffer->mAudioDataBytesCapacity; Uint8 *ptr = (Uint8 *)inBuffer->mAudioData; while (remaining > 0) { - if (SDL_GetAudioStreamAvailable(_this->stream) == 0) { + if (SDL_GetAudioStreamAvailable(device->stream) == 0) { /* Generate the data */ - (*_this->callbackspec.callback)(_this->callbackspec.userdata, - _this->hidden->buffer, _this->hidden->bufferSize); - _this->hidden->bufferOffset = 0; - SDL_PutAudioStreamData(_this->stream, _this->hidden->buffer, _this->hidden->bufferSize); + (*device->callbackspec.callback)(device->callbackspec.userdata, + device->hidden->buffer, device->hidden->bufferSize); + device->hidden->bufferOffset = 0; + SDL_PutAudioStreamData(device->stream, device->hidden->buffer, device->hidden->bufferSize); } - if (SDL_GetAudioStreamAvailable(_this->stream) > 0) { + if (SDL_GetAudioStreamAvailable(device->stream) > 0) { int got; - UInt32 len = SDL_GetAudioStreamAvailable(_this->stream); + UInt32 len = SDL_GetAudioStreamAvailable(device->stream); if (len > remaining) { len = remaining; } - got = SDL_GetAudioStreamData(_this->stream, ptr, len); + got = SDL_GetAudioStreamData(device->stream, ptr, len); SDL_assert((got < 0) || (got == len)); if (got != len) { - SDL_memset(ptr, _this->spec.silence, len); + SDL_memset(ptr, device->spec.silence, len); } ptr = ptr + len; remaining -= len; @@ -563,66 +563,66 @@ static void outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBuffe while (remaining > 0) { UInt32 len; - if (_this->hidden->bufferOffset >= _this->hidden->bufferSize) { + if (device->hidden->bufferOffset >= device->hidden->bufferSize) { /* Generate the data */ - (*_this->callbackspec.callback)(_this->callbackspec.userdata, - _this->hidden->buffer, _this->hidden->bufferSize); - _this->hidden->bufferOffset = 0; + (*device->callbackspec.callback)(device->callbackspec.userdata, + device->hidden->buffer, device->hidden->bufferSize); + device->hidden->bufferOffset = 0; } - len = _this->hidden->bufferSize - _this->hidden->bufferOffset; + len = device->hidden->bufferSize - device->hidden->bufferOffset; if (len > remaining) { len = remaining; } - SDL_memcpy(ptr, (char *)_this->hidden->buffer + _this->hidden->bufferOffset, len); + SDL_memcpy(ptr, (char *)device->hidden->buffer + device->hidden->bufferOffset, len); ptr = ptr + len; remaining -= len; - _this->hidden->bufferOffset += len; + device->hidden->bufferOffset += len; } } - AudioQueueEnqueueBuffer(_this->hidden->audioQueue, inBuffer, 0, NULL); + AudioQueueEnqueueBuffer(device->hidden->audioQueue, inBuffer, 0, NULL); inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity; - SDL_UnlockMutex(_this->mixer_lock); + SDL_UnlockMutex(device->mixer_lock); } static void inputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions, const AudioStreamPacketDescription *inPacketDescs) { - SDL_AudioDevice *_this = (SDL_AudioDevice *)inUserData; + SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; - if (SDL_AtomicGet(&_this->shutdown)) { + if (SDL_AtomicGet(&device->shutdown)) { return; /* don't do anything. */ } /* ignore unless we're active. */ - if (!SDL_AtomicGet(&_this->paused) && SDL_AtomicGet(&_this->enabled)) { + if (!SDL_AtomicGet(&device->paused) && SDL_AtomicGet(&device->enabled)) { const Uint8 *ptr = (const Uint8 *)inBuffer->mAudioData; UInt32 remaining = inBuffer->mAudioDataByteSize; while (remaining > 0) { - UInt32 len = _this->hidden->bufferSize - _this->hidden->bufferOffset; + UInt32 len = device->hidden->bufferSize - device->hidden->bufferOffset; if (len > remaining) { len = remaining; } - SDL_memcpy((char *)_this->hidden->buffer + _this->hidden->bufferOffset, ptr, len); + SDL_memcpy((char *)device->hidden->buffer + device->hidden->bufferOffset, ptr, len); ptr += len; remaining -= len; - _this->hidden->bufferOffset += len; + device->hidden->bufferOffset += len; - if (_this->hidden->bufferOffset >= _this->hidden->bufferSize) { - SDL_LockMutex(_this->mixer_lock); - (*_this->callbackspec.callback)(_this->callbackspec.userdata, _this->hidden->buffer, _this->hidden->bufferSize); - SDL_UnlockMutex(_this->mixer_lock); - _this->hidden->bufferOffset = 0; + if (device->hidden->bufferOffset >= device->hidden->bufferSize) { + SDL_LockMutex(device->mixer_lock); + (*device->callbackspec.callback)(device->callbackspec.userdata, device->hidden->buffer, device->hidden->bufferSize); + SDL_UnlockMutex(device->mixer_lock); + device->hidden->bufferOffset = 0; } } } - AudioQueueEnqueueBuffer(_this->hidden->audioQueue, inBuffer, 0, NULL); + AudioQueueEnqueueBuffer(device->hidden->audioQueue, inBuffer, 0, NULL); } #ifdef MACOSX_COREAUDIO @@ -634,17 +634,17 @@ static const AudioObjectPropertyAddress alive_address = { static OSStatus device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) { - SDL_AudioDevice *_this = (SDL_AudioDevice *)data; + SDL_AudioDevice *device = (SDL_AudioDevice *)data; SDL_bool dead = SDL_FALSE; UInt32 isAlive = 1; UInt32 size = sizeof(isAlive); OSStatus error; - if (!SDL_AtomicGet(&_this->enabled)) { + if (!SDL_AtomicGet(&device->enabled)) { return 0; /* already known to be dead. */ } - error = AudioObjectGetPropertyData(_this->hidden->deviceID, &alive_address, + error = AudioObjectGetPropertyData(device->hidden->deviceID, &alive_address, 0, NULL, &size, &isAlive); if (error == kAudioHardwareBadDeviceError) { @@ -654,7 +654,7 @@ static OSStatus device_unplugged(AudioObjectID devid, UInt32 num_addr, const Aud } if (dead) { - SDL_OpenedAudioDeviceDisconnected(_this); + SDL_OpenedAudioDeviceDisconnected(device); } return 0; @@ -663,41 +663,41 @@ static OSStatus device_unplugged(AudioObjectID devid, UInt32 num_addr, const Aud /* macOS calls this when the default device changed (if we have a default device open). */ static OSStatus default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) { - SDL_AudioDevice *_this = (SDL_AudioDevice *)inUserData; + SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; #if DEBUG_COREAUDIO - printf("COREAUDIO: default device changed for SDL audio device %p!\n", _this); + printf("COREAUDIO: default device changed for SDL audio device %p!\n", device); #endif - SDL_AtomicSet(&_this->hidden->device_change_flag, 1); /* let the audioqueue thread pick up on this when safe to do so. */ + SDL_AtomicSet(&device->hidden->device_change_flag, 1); /* let the audioqueue thread pick up on this when safe to do so. */ return noErr; } #endif -static void COREAUDIO_CloseDevice(SDL_AudioDevice *_this) +static void COREAUDIO_CloseDevice(SDL_AudioDevice *device) { - const SDL_bool iscapture = _this->iscapture; + const SDL_bool iscapture = device->iscapture; int i; /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ #ifdef MACOSX_COREAUDIO - if (_this->handle != NULL) { /* we don't register this listener for default devices. */ - AudioObjectRemovePropertyListener(_this->hidden->deviceID, &alive_address, device_unplugged, _this); + if (device->handle != NULL) { /* we don't register this listener for default devices. */ + AudioObjectRemovePropertyListener(device->hidden->deviceID, &alive_address, device_unplugged, device); } #endif /* if callback fires again, feed silence; don't call into the app. */ - SDL_AtomicSet(&_this->paused, 1); + SDL_AtomicSet(&device->paused, 1); /* dispose of the audio queue before waiting on the thread, or it might stall for a long time! */ - if (_this->hidden->audioQueue) { - AudioQueueFlush(_this->hidden->audioQueue); - AudioQueueStop(_this->hidden->audioQueue, 0); - AudioQueueDispose(_this->hidden->audioQueue, 0); + if (device->hidden->audioQueue) { + AudioQueueFlush(device->hidden->audioQueue); + AudioQueueStop(device->hidden->audioQueue, 0); + AudioQueueDispose(device->hidden->audioQueue, 0); } - if (_this->hidden->thread) { - SDL_assert(SDL_AtomicGet(&_this->shutdown) != 0); /* should have been set by SDL_audio.c */ - SDL_WaitThread(_this->hidden->thread, NULL); + if (device->hidden->thread) { + SDL_assert(SDL_AtomicGet(&device->shutdown) != 0); /* should have been set by SDL_audio.c */ + SDL_WaitThread(device->hidden->thread, NULL); } if (iscapture) { @@ -707,11 +707,11 @@ static void COREAUDIO_CloseDevice(SDL_AudioDevice *_this) } #ifndef MACOSX_COREAUDIO - update_audio_session(_this, SDL_FALSE, SDL_TRUE); + update_audio_session(device, SDL_FALSE, SDL_TRUE); #endif for (i = 0; i < num_open_devices; ++i) { - if (open_devices[i] == _this) { + if (open_devices[i] == device) { --num_open_devices; if (i < num_open_devices) { SDL_memmove(&open_devices[i], &open_devices[i + 1], sizeof(open_devices[i]) * (num_open_devices - i)); @@ -724,22 +724,22 @@ static void COREAUDIO_CloseDevice(SDL_AudioDevice *_this) open_devices = NULL; } - if (_this->hidden->ready_semaphore) { - SDL_DestroySemaphore(_this->hidden->ready_semaphore); + if (device->hidden->ready_semaphore) { + SDL_DestroySemaphore(device->hidden->ready_semaphore); } /* AudioQueueDispose() frees the actual buffer objects. */ - SDL_free(_this->hidden->audioBuffer); - SDL_free(_this->hidden->thread_error); - SDL_free(_this->hidden->buffer); - SDL_free(_this->hidden); + SDL_free(device->hidden->audioBuffer); + SDL_free(device->hidden->thread_error); + SDL_free(device->hidden->buffer); + SDL_free(device->hidden); } #ifdef MACOSX_COREAUDIO -static int prepare_device(SDL_AudioDevice *_this) +static int prepare_device(SDL_AudioDevice *device) { - void *handle = _this->handle; - SDL_bool iscapture = _this->iscapture; + void *handle = device->handle; + SDL_bool iscapture = device->iscapture; AudioDeviceID devid = (AudioDeviceID)((size_t)handle); OSStatus result = noErr; UInt32 size = 0; @@ -783,73 +783,73 @@ static int prepare_device(SDL_AudioDevice *_this) return 0; } - _this->hidden->deviceID = devid; + device->hidden->deviceID = devid; return 1; } -static int assign_device_to_audioqueue(SDL_AudioDevice *_this) +static int assign_device_to_audioqueue(SDL_AudioDevice *device) { const AudioObjectPropertyAddress prop = { kAudioDevicePropertyDeviceUID, - _this->iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + device->iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMain }; OSStatus result; CFStringRef devuid; UInt32 devuidsize = sizeof(devuid); - result = AudioObjectGetPropertyData(_this->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid); + result = AudioObjectGetPropertyData(device->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid); CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)"); - result = AudioQueueSetProperty(_this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize); + result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize); CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)"); return 1; } #endif -static int prepare_audioqueue(SDL_AudioDevice *_this) +static int prepare_audioqueue(SDL_AudioDevice *device) { - const AudioStreamBasicDescription *strdesc = &_this->hidden->strdesc; - const int iscapture = _this->iscapture; + const AudioStreamBasicDescription *strdesc = &device->hidden->strdesc; + const int iscapture = device->iscapture; OSStatus result; int i, numAudioBuffers = 2; AudioChannelLayout layout; double MINIMUM_AUDIO_BUFFER_TIME_MS; - const double msecs = (_this->spec.samples / ((double)_this->spec.freq)) * 1000.0; + const double msecs = (device->spec.samples / ((double)device->spec.freq)) * 1000.0; ; SDL_assert(CFRunLoopGetCurrent() != NULL); if (iscapture) { - result = AudioQueueNewInput(strdesc, inputCallback, _this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &_this->hidden->audioQueue); + result = AudioQueueNewInput(strdesc, inputCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); CHECK_RESULT("AudioQueueNewInput"); } else { - result = AudioQueueNewOutput(strdesc, outputCallback, _this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &_this->hidden->audioQueue); + result = AudioQueueNewOutput(strdesc, outputCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); CHECK_RESULT("AudioQueueNewOutput"); } #ifdef MACOSX_COREAUDIO - if (!assign_device_to_audioqueue(_this)) { + if (!assign_device_to_audioqueue(device)) { return 0; } /* only listen for unplugging on specific devices, not the default device, as that should switch to a different device (or hang out silently if there _is_ no other device). */ - if (_this->handle != NULL) { + if (device->handle != NULL) { /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ /* Fire a callback if the device stops being "alive" (disconnected, etc). */ /* If this fails, oh well, we won't notice a device had an extraordinary event take place. */ - AudioObjectAddPropertyListener(_this->hidden->deviceID, &alive_address, device_unplugged, _this); + AudioObjectAddPropertyListener(device->hidden->deviceID, &alive_address, device_unplugged, device); } #endif /* Calculate the final parameters for this audio specification */ - SDL_CalculateAudioSpec(&_this->spec); + SDL_CalculateAudioSpec(&device->spec); /* Set the channel layout for the audio queue */ SDL_zero(layout); - switch (_this->spec.channels) { + switch (device->spec.channels) { case 1: layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; break; @@ -877,16 +877,16 @@ static int prepare_audioqueue(SDL_AudioDevice *_this) break; } if (layout.mChannelLayoutTag != 0) { - result = AudioQueueSetProperty(_this->hidden->audioQueue, kAudioQueueProperty_ChannelLayout, &layout, sizeof(layout)); + result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_ChannelLayout, &layout, sizeof(layout)); CHECK_RESULT("AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)"); } /* Allocate a sample buffer */ - _this->hidden->bufferSize = _this->spec.size; - _this->hidden->bufferOffset = iscapture ? 0 : _this->hidden->bufferSize; + device->hidden->bufferSize = device->spec.size; + device->hidden->bufferOffset = iscapture ? 0 : device->hidden->bufferSize; - _this->hidden->buffer = SDL_malloc(_this->hidden->bufferSize); - if (_this->hidden->buffer == NULL) { + device->hidden->buffer = SDL_malloc(device->hidden->bufferSize); + if (device->hidden->buffer == NULL) { SDL_OutOfMemory(); return 0; } @@ -903,9 +903,9 @@ static int prepare_audioqueue(SDL_AudioDevice *_this) numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2); } - _this->hidden->numAudioBuffers = numAudioBuffers; - _this->hidden->audioBuffer = SDL_calloc(1, sizeof(AudioQueueBufferRef) * numAudioBuffers); - if (_this->hidden->audioBuffer == NULL) { + device->hidden->numAudioBuffers = numAudioBuffers; + device->hidden->audioBuffer = SDL_calloc(1, sizeof(AudioQueueBufferRef) * numAudioBuffers); + if (device->hidden->audioBuffer == NULL) { SDL_OutOfMemory(); return 0; } @@ -915,16 +915,16 @@ static int prepare_audioqueue(SDL_AudioDevice *_this) #endif for (i = 0; i < numAudioBuffers; i++) { - result = AudioQueueAllocateBuffer(_this->hidden->audioQueue, _this->spec.size, &_this->hidden->audioBuffer[i]); + result = AudioQueueAllocateBuffer(device->hidden->audioQueue, device->spec.size, &device->hidden->audioBuffer[i]); CHECK_RESULT("AudioQueueAllocateBuffer"); - SDL_memset(_this->hidden->audioBuffer[i]->mAudioData, _this->spec.silence, _this->hidden->audioBuffer[i]->mAudioDataBytesCapacity); - _this->hidden->audioBuffer[i]->mAudioDataByteSize = _this->hidden->audioBuffer[i]->mAudioDataBytesCapacity; + SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->spec.silence, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity); + device->hidden->audioBuffer[i]->mAudioDataByteSize = device->hidden->audioBuffer[i]->mAudioDataBytesCapacity; /* !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? */ - result = AudioQueueEnqueueBuffer(_this->hidden->audioQueue, _this->hidden->audioBuffer[i], 0, NULL); + result = AudioQueueEnqueueBuffer(device->hidden->audioQueue, device->hidden->audioBuffer[i], 0, NULL); CHECK_RESULT("AudioQueueEnqueueBuffer"); } - result = AudioQueueStart(_this->hidden->audioQueue, NULL); + result = AudioQueueStart(device->hidden->audioQueue, NULL); CHECK_RESULT("AudioQueueStart"); /* We're running! */ @@ -933,41 +933,41 @@ static int prepare_audioqueue(SDL_AudioDevice *_this) static int audioqueue_thread(void *arg) { - SDL_AudioDevice *_this = (SDL_AudioDevice *)arg; + SDL_AudioDevice *device = (SDL_AudioDevice *)arg; int rc; #ifdef MACOSX_COREAUDIO const AudioObjectPropertyAddress default_device_address = { - _this->iscapture ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice, + device->iscapture ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain }; - if (_this->handle == NULL) { /* opened the default device? Register to know if the user picks a new default. */ + if (device->handle == NULL) { /* opened the default device? Register to know if the user picks a new default. */ /* we don't care if this fails; we just won't change to new default devices, but we still otherwise function in this case. */ - AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_device_address, default_device_changed, _this); + AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_device_address, default_device_changed, device); } #endif - rc = prepare_audioqueue(_this); + rc = prepare_audioqueue(device); if (!rc) { - _this->hidden->thread_error = SDL_strdup(SDL_GetError()); - SDL_PostSemaphore(_this->hidden->ready_semaphore); + device->hidden->thread_error = SDL_strdup(SDL_GetError()); + SDL_PostSemaphore(device->hidden->ready_semaphore); return 0; } SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); /* init was successful, alert parent thread and start running... */ - SDL_PostSemaphore(_this->hidden->ready_semaphore); + SDL_PostSemaphore(device->hidden->ready_semaphore); - while (!SDL_AtomicGet(&_this->shutdown)) { + while (!SDL_AtomicGet(&device->shutdown)) { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); #ifdef MACOSX_COREAUDIO - if ((_this->handle == NULL) && SDL_AtomicGet(&_this->hidden->device_change_flag)) { - const AudioDeviceID prev_devid = _this->hidden->deviceID; - SDL_AtomicSet(&_this->hidden->device_change_flag, 0); + if ((device->handle == NULL) && SDL_AtomicGet(&device->hidden->device_change_flag)) { + const AudioDeviceID prev_devid = device->hidden->deviceID; + SDL_AtomicSet(&device->hidden->device_change_flag, 0); #if DEBUG_COREAUDIO printf("COREAUDIO: audioqueue_thread is trying to switch to new default device!\n"); @@ -976,53 +976,53 @@ static int audioqueue_thread(void *arg) /* if any of this fails, there's not much to do but wait to see if the user gives up and quits (flagging the audioqueue for shutdown), or toggles to some other system output device (in which case we'll try again). */ - if (prepare_device(_this) && (prev_devid != _this->hidden->deviceID)) { - AudioQueueStop(_this->hidden->audioQueue, 1); - if (assign_device_to_audioqueue(_this)) { + if (prepare_device(device) && (prev_devid != device->hidden->deviceID)) { + AudioQueueStop(device->hidden->audioQueue, 1); + if (assign_device_to_audioqueue(device)) { int i; - for (i = 0; i < _this->hidden->numAudioBuffers; i++) { - SDL_memset(_this->hidden->audioBuffer[i]->mAudioData, _this->spec.silence, _this->hidden->audioBuffer[i]->mAudioDataBytesCapacity); + for (i = 0; i < device->hidden->numAudioBuffers; i++) { + SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->spec.silence, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity); /* !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? */ - AudioQueueEnqueueBuffer(_this->hidden->audioQueue, _this->hidden->audioBuffer[i], 0, NULL); + AudioQueueEnqueueBuffer(device->hidden->audioQueue, device->hidden->audioBuffer[i], 0, NULL); } - AudioQueueStart(_this->hidden->audioQueue, NULL); + AudioQueueStart(device->hidden->audioQueue, NULL); } } } #endif } - if (!_this->iscapture) { /* Drain off any pending playback. */ - const CFTimeInterval secs = (((_this->spec.size / (SDL_AUDIO_BITSIZE(_this->spec.format) / 8.0)) / _this->spec.channels) / ((CFTimeInterval)_this->spec.freq)) * 2.0; + if (!device->iscapture) { /* Drain off any pending playback. */ + const CFTimeInterval secs = (((device->spec.size / (SDL_AUDIO_BITSIZE(device->spec.format) / 8.0)) / device->spec.channels) / ((CFTimeInterval)device->spec.freq)) * 2.0; CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0); } #ifdef MACOSX_COREAUDIO - if (_this->handle == NULL) { + if (device->handle == NULL) { /* we don't care if this fails; we just won't change to new default devices, but we still otherwise function in this case. */ - AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_device_address, default_device_changed, _this); + AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_device_address, default_device_changed, device); } #endif return 0; } -static int COREAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int COREAUDIO_OpenDevice(SDL_AudioDevice *device, const char *devname) { AudioStreamBasicDescription *strdesc; const SDL_AudioFormat *closefmts; SDL_AudioFormat test_format; - SDL_bool iscapture = _this->iscapture; + SDL_bool iscapture = device->iscapture; SDL_AudioDevice **new_open_devices; /* Initialize all variables that we clean on shutdown */ - _this->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + device->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(_this->hidden); + SDL_zerop(device->hidden); - strdesc = &_this->hidden->strdesc; + strdesc = &device->hidden->strdesc; if (iscapture) { open_capture_devices++; @@ -1033,26 +1033,26 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) new_open_devices = (SDL_AudioDevice **)SDL_realloc(open_devices, sizeof(open_devices[0]) * (num_open_devices + 1)); if (new_open_devices) { open_devices = new_open_devices; - open_devices[num_open_devices++] = _this; + open_devices[num_open_devices++] = device; } #ifndef MACOSX_COREAUDIO - if (!update_audio_session(_this, SDL_TRUE, SDL_TRUE)) { + if (!update_audio_session(device, SDL_TRUE, SDL_TRUE)) { return -1; } /* Stop CoreAudio from doing expensive audio rate conversion */ @autoreleasepool { AVAudioSession *session = [AVAudioSession sharedInstance]; - [session setPreferredSampleRate:_this->spec.freq error:nil]; - _this->spec.freq = (int)session.sampleRate; + [session setPreferredSampleRate:device->spec.freq error:nil]; + device->spec.freq = (int)session.sampleRate; #if TARGET_OS_TV if (iscapture) { - [session setPreferredInputNumberOfChannels:_this->spec.channels error:nil]; - _this->spec.channels = session.preferredInputNumberOfChannels; + [session setPreferredInputNumberOfChannels:device->spec.channels error:nil]; + device->spec.channels = session.preferredInputNumberOfChannels; } else { - [session setPreferredOutputNumberOfChannels:_this->spec.channels error:nil]; - _this->spec.channels = session.preferredOutputNumberOfChannels; + [session setPreferredOutputNumberOfChannels:device->spec.channels error:nil]; + device->spec.channels = session.preferredOutputNumberOfChannels; } #else /* Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS */ @@ -1064,11 +1064,11 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) SDL_zerop(strdesc); strdesc->mFormatID = kAudioFormatLinearPCM; strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked; - strdesc->mChannelsPerFrame = _this->spec.channels; - strdesc->mSampleRate = _this->spec.freq; + strdesc->mChannelsPerFrame = device->spec.channels; + strdesc->mSampleRate = device->spec.freq; strdesc->mFramesPerPacket = 1; - closefmts = SDL_ClosestAudioFormats(_this->spec.format); + closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { /* CoreAudio handles most of SDL's formats natively. */ switch (test_format) { @@ -1091,7 +1091,7 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) if (!test_format) { /* shouldn't happen, but just in case... */ return SDL_SetError("%s: Unsupported audio format", "coreaudio"); } - _this->spec.format = test_format; + device->spec.format = test_format; strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(test_format); if (SDL_AUDIO_ISBIGENDIAN(test_format)) { strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; @@ -1107,31 +1107,31 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket; #ifdef MACOSX_COREAUDIO - if (!prepare_device(_this)) { + if (!prepare_device(device)) { return -1; } #endif /* This has to init in a new thread so it can get its own CFRunLoop. :/ */ - _this->hidden->ready_semaphore = SDL_CreateSemaphore(0); - if (!_this->hidden->ready_semaphore) { + device->hidden->ready_semaphore = SDL_CreateSemaphore(0); + if (!device->hidden->ready_semaphore) { return -1; /* oh well. */ } - _this->hidden->thread = SDL_CreateThreadInternal(audioqueue_thread, "AudioQueue thread", 512 * 1024, _this); - if (!_this->hidden->thread) { + device->hidden->thread = SDL_CreateThreadInternal(audioqueue_thread, "AudioQueue thread", 512 * 1024, device); + if (!device->hidden->thread) { return -1; } - SDL_WaitSemaphore(_this->hidden->ready_semaphore); - SDL_DestroySemaphore(_this->hidden->ready_semaphore); - _this->hidden->ready_semaphore = NULL; + SDL_WaitSemaphore(device->hidden->ready_semaphore); + SDL_DestroySemaphore(device->hidden->ready_semaphore); + device->hidden->ready_semaphore = NULL; - if ((_this->hidden->thread != NULL) && (_this->hidden->thread_error != NULL)) { - return SDL_SetError("%s", _this->hidden->thread_error); + if ((device->hidden->thread != NULL) && (device->hidden->thread_error != NULL)) { + return SDL_SetError("%s", device->hidden->thread_error); } - return (_this->hidden->thread != NULL) ? 0 : -1; + return (device->hidden->thread != NULL) ? 0 : -1; } #ifndef MACOSX_COREAUDIO From 22afa5735f97e8a797a2f7c35066e552a04054e2 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 20:32:31 -0400 Subject: [PATCH 054/138] audio: FreeDeviceHandle should pass the whole device, for convenience. --- src/audio/SDL_audio.c | 4 ++-- src/audio/SDL_sysaudio.h | 2 +- src/audio/directsound/SDL_directsound.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 8926b7e000..d60ccb5f2e 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -198,7 +198,7 @@ static void DestroyPhysicalAudioDevice(SDL_AudioDevice *device) // it's safe to not hold the lock for this (we can't anyhow, or the audio thread won't quit), because we shouldn't be in the device list at this point. ClosePhysicalAudioDevice(device); - current_audio.impl.FreeDeviceHandle(device->handle); + current_audio.impl.FreeDeviceHandle(device); SDL_DestroyMutex(device->lock); SDL_free(device->work_buffer); @@ -418,7 +418,7 @@ static void SDL_AudioWaitCaptureDevice_Default(SDL_AudioDevice *device) { /* no- static void SDL_AudioFlushCapture_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ } -static void SDL_AudioFreeDeviceHandle_Default(void *handle) { /* no-op. */ } +static void SDL_AudioFreeDeviceHandle_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioDetectDevices_Default(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index ca71097fb7..ff3d1755c8 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -114,7 +114,7 @@ typedef struct SDL_AudioDriverImpl int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen); void (*FlushCapture)(SDL_AudioDevice *device); void (*CloseDevice)(SDL_AudioDevice *device); - void (*FreeDeviceHandle)(void *handle); /**< SDL is done with handle from SDL_AddAudioDevice() */ + void (*FreeDeviceHandle)(SDL_AudioDevice *handle); // SDL is done with this device; free the handle from SDL_AddAudioDevice() void (*Deinitialize)(void); /* Some flags to push duplicate code into the core and reduce #ifdefs. */ diff --git a/src/audio/directsound/SDL_directsound.c b/src/audio/directsound/SDL_directsound.c index 33ad196451..17fef74bf7 100644 --- a/src/audio/directsound/SDL_directsound.c +++ b/src/audio/directsound/SDL_directsound.c @@ -149,9 +149,9 @@ static int SetDSerror(const char *function, int code) return SDL_SetError("%s: %s (0x%x)", function, error, code); } -static void DSOUND_FreeDeviceHandle(void *handle) +static void DSOUND_FreeDeviceHandle(SDL_AudioDevice *device) { - SDL_free(handle); + SDL_free(device->handle); } static int DSOUND_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) From e518149d144c5f391a29a8c88a8d7a807f88d6be Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 20:32:55 -0400 Subject: [PATCH 055/138] audio: Fixed locking in SDL_AudioDeviceDisconnected --- src/audio/SDL_audio.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index d60ccb5f2e..aa1d66d4bc 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -330,6 +330,7 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) // if the current default device is going down, mark it as dead but keep it around until a replacement is decided upon, so we can migrate logical devices to it. if ((device->instance_id == current_audio.default_output_device_id) || (device->instance_id == current_audio.default_capture_device_id)) { + SDL_LockMutex(device->lock); // make sure nothing else is messing with the device before continuing. SDL_AtomicSet(&device->zombie, 1); SDL_AtomicSet(&device->shutdown, 1); // tell audio thread to terminate, but don't mark it condemned, so the thread won't destroy the device. We'll join on the audio thread later. @@ -341,6 +342,7 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) DisconnectLogicalAudioDevice(logdev); } } + SDL_UnlockMutex(device->lock); // make sure nothing else is messing with the device before continuing. return; // done for now. Come back when a new default device is chosen! } From 258bc9efed5a75d8a51c6d60c43383ace1e67515 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 20:34:15 -0400 Subject: [PATCH 056/138] audio: PlayDevice now passes the buffer, too, for convenience. --- src/audio/SDL_audio.c | 4 ++-- src/audio/SDL_sysaudio.h | 2 +- src/audio/disk/SDL_diskaudio.c | 6 ++---- src/audio/pipewire/SDL_pipewire.c | 2 +- src/audio/pulseaudio/SDL_pulseaudio.c | 12 +++++------- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index aa1d66d4bc..a031686abd 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -415,7 +415,7 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } -static void SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, int buffer_size) { /* no-op. */ } +static void SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { /* no-op. */ } static void SDL_AudioWaitCaptureDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioFlushCapture_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } @@ -738,7 +738,7 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) } // !!! FIXME: have PlayDevice return a value and do disconnects in here with it. - current_audio.impl.PlayDevice(device, buffer_size); // this SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice! + current_audio.impl.PlayDevice(device, mix_buffer, buffer_size); // this SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice! } SDL_UnlockMutex(device->lock); diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index ff3d1755c8..ecf2fcb0ac 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -108,7 +108,7 @@ typedef struct SDL_AudioDriverImpl void (*ThreadInit)(SDL_AudioDevice *device); /* Called by audio thread at start */ void (*ThreadDeinit)(SDL_AudioDevice *device); /* Called by audio thread at end */ void (*WaitDevice)(SDL_AudioDevice *device); - void (*PlayDevice)(SDL_AudioDevice *device, int buffer_size); + void (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen); // buffer and buflen are always from GetDeviceBuf, passed here for convenience. Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size); void (*WaitCaptureDevice)(SDL_AudioDevice *device); int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen); diff --git a/src/audio/disk/SDL_diskaudio.c b/src/audio/disk/SDL_diskaudio.c index e96014ce95..0cfa3b7a27 100644 --- a/src/audio/disk/SDL_diskaudio.c +++ b/src/audio/disk/SDL_diskaudio.c @@ -41,11 +41,9 @@ static void DISKAUDIO_WaitDevice(SDL_AudioDevice *device) SDL_Delay(device->hidden->io_delay); } -static void DISKAUDIO_PlayDevice(SDL_AudioDevice *device, int buffer_size) +static void DISKAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { - const Sint64 written = SDL_RWwrite(device->hidden->io, - device->hidden->mixbuf, - buffer_size); + const Sint64 written = SDL_RWwrite(device->hidden->io, buffer, buffer_size); /* If we couldn't write, assume fatal error for now */ if (written != buffer_size) { diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c index fc8efc539a..eac531bf19 100644 --- a/src/audio/pipewire/SDL_pipewire.c +++ b/src/audio/pipewire/SDL_pipewire.c @@ -948,7 +948,7 @@ static Uint8 *PIPEWIRE_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) return (Uint8 *) spa_buf->datas[0].data; } -static void PIPEWIRE_PlayDevice(SDL_AudioDevice *device, int buffer_size) +static void PIPEWIRE_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { struct pw_stream *stream = device->hidden->stream; struct pw_buffer *pw_buf = device->hidden->pw_buf; diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 0852b2cc4c..c0462f786a 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -388,18 +388,16 @@ static void PULSEAUDIO_WaitDevice(SDL_AudioDevice *device) PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); } -static void PULSEAUDIO_PlayDevice(SDL_AudioDevice *device, int buffer_size) +static void PULSEAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { struct SDL_PrivateAudioData *h = device->hidden; - const int available = buffer_size; - int rc; /*printf("PULSEAUDIO PLAYDEVICE START! mixlen=%d\n", available);*/ - SDL_assert(h->bytes_requested >= available); + SDL_assert(h->bytes_requested >= buffer_size); PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); - rc = PULSEAUDIO_pa_stream_write(h->stream, h->mixbuf, available, NULL, 0LL, PA_SEEK_RELATIVE); + const int rc = PULSEAUDIO_pa_stream_write(h->stream, buffer, buffer_size, NULL, 0LL, PA_SEEK_RELATIVE); PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); if (rc < 0) { @@ -407,8 +405,8 @@ static void PULSEAUDIO_PlayDevice(SDL_AudioDevice *device, int buffer_size) return; } - /*printf("PULSEAUDIO FEED! nbytes=%u\n", (unsigned int) available);*/ - h->bytes_requested -= available; + /*printf("PULSEAUDIO FEED! nbytes=%d\n", buffer_size);*/ + h->bytes_requested -= buffer_size; /*printf("PULSEAUDIO PLAYDEVICE END! written=%d\n", written);*/ } From 8473e522e0cf02bf73096007f679b3582fb079bb Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 20:35:58 -0400 Subject: [PATCH 057/138] audio: unify device thread naming. --- src/audio/SDL_audio.c | 9 ++++++++- src/audio/SDL_sysaudio.h | 3 +++ src/audio/pipewire/SDL_pipewire.c | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index a031686abd..87c4df8665 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -1191,6 +1191,13 @@ void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device) device->buffer_size = device->sample_frames * (SDL_AUDIO_BITSIZE(device->spec.format) / 8) * device->spec.channels; } +char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen) +{ + (void)SDL_snprintf(buf, buflen, "SDLAudio%c%d", (device->iscapture) ? 'C' : 'P', (int) device->instance_id); + return buf; +} + + // this expects the device lock to be held. static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec) { @@ -1236,7 +1243,7 @@ static int OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec if (!current_audio.impl.ProvidesOwnCallbackThread) { const size_t stacksize = 0; // just take the system default, since audio streams might have callbacks. char threadname[64]; - (void)SDL_snprintf(threadname, sizeof(threadname), "SDLAudio%c%d", (device->iscapture) ? 'C' : 'P', (int) device->instance_id); + SDL_GetAudioThreadName(device, threadname, sizeof (threadname)); device->thread = SDL_CreateThreadInternal(device->iscapture ? CaptureAudioThread : OutputAudioThread, threadname, stacksize, device); if (device->thread == NULL) { diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index ecf2fcb0ac..1653ea44be 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -91,6 +91,9 @@ extern SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle); /* Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct. */ extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device); +// Backends can call this to get a standardized name for a thread to power a specific audio device. +char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen); + /* These functions are the heart of the audio threads. Backends can call them directly if they aren't using the SDL-provided thread. */ extern void SDL_OutputAudioThreadSetup(SDL_AudioDevice *device); diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c index eac531bf19..170b6d746c 100644 --- a/src/audio/pipewire/SDL_pipewire.c +++ b/src/audio/pipewire/SDL_pipewire.c @@ -1122,7 +1122,7 @@ static int PIPEWIRE_OpenDevice(SDL_AudioDevice *device) SDL_UpdatedAudioDeviceFormat(device); - (void)SDL_snprintf(thread_name, sizeof(thread_name), "SDLAudio%c%ld", (iscapture) ? 'C' : 'P', (long)device->handle); + SDL_GetAudioThreadName(device, thread_name, sizeof(thread_name)); priv->loop = PIPEWIRE_pw_thread_loop_new(thread_name, NULL); if (priv->loop == NULL) { return SDL_SetError("Pipewire: Failed to create stream loop (%i)", errno); From 533777eff5ac230d3510c8676aaddf5910e6f78d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 20:36:39 -0400 Subject: [PATCH 058/138] audio: SDL_sysaudio.h comment conversion. --- src/audio/SDL_sysaudio.h | 114 +++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 1653ea44be..6786c3afff 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -18,6 +18,7 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ + #include "SDL_internal.h" #ifndef SDL_sysaudio_h_ @@ -34,7 +35,7 @@ #define LOG_DEBUG_AUDIO_CONVERT(from, to) #endif -/* These pointers get set during SDL_ChooseAudioConverters() to various SIMD implementations. */ +// These pointers get set during SDL_ChooseAudioConverters() to various SIMD implementations. extern void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples); extern void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples); extern void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples); @@ -44,12 +45,11 @@ extern void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_sampl extern void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples); extern void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples); - -/* !!! FIXME: These are wordy and unlocalized... */ +// !!! FIXME: These are wordy and unlocalized... #define DEFAULT_OUTPUT_DEVNAME "System audio output device" #define DEFAULT_INPUT_DEVNAME "System audio capture device" -/* these are used when no better specifics are known. We default to CD audio quality. */ +// these are used when no better specifics are known. We default to CD audio quality. #define DEFAULT_AUDIO_OUTPUT_FORMAT SDL_AUDIO_S16 #define DEFAULT_AUDIO_OUTPUT_CHANNELS 2 #define DEFAULT_AUDIO_OUTPUT_FREQUENCY 44100 @@ -61,16 +61,16 @@ extern void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_sam typedef struct SDL_AudioDevice SDL_AudioDevice; typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice; -/* Used by src/SDL.c to initialize a particular audio driver. */ +// Used by src/SDL.c to initialize a particular audio driver. extern int SDL_InitAudio(const char *driver_name); -/* Used by src/SDL.c to shut down previously-initialized audio. */ +// Used by src/SDL.c to shut down previously-initialized audio. extern void SDL_QuitAudio(void); -/* Function to get a list of audio formats, ordered most similar to `format` to least, 0-terminated. Don't free results. */ +// Function to get a list of audio formats, ordered most similar to `format` to least, 0-terminated. Don't free results. const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format); -/* Must be called at least once before using converters (SDL_CreateAudioStream will call it !!! FIXME but probably shouldn't). */ +// Must be called at least once before using converters (SDL_CreateAudioStream will call it !!! FIXME but probably shouldn't). extern void SDL_ChooseAudioConverters(void); /* Backends should call this as devices are added to the system (such as @@ -85,17 +85,17 @@ extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device); // Backends should call this if the system default device changes. extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device); -/* Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. Locks the device! You must unlock!! */ +// Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. Locks the device! You must unlock!! extern SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle); -/* Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct. */ +// Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct. extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device); // Backends can call this to get a standardized name for a thread to power a specific audio device. char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen); -/* These functions are the heart of the audio threads. Backends can call them directly if they aren't using the SDL-provided thread. */ +// These functions are the heart of the audio threads. Backends can call them directly if they aren't using the SDL-provided thread. extern void SDL_OutputAudioThreadSetup(SDL_AudioDevice *device); extern SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device); extern void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device); @@ -108,8 +108,8 @@ typedef struct SDL_AudioDriverImpl { void (*DetectDevices)(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture); int (*OpenDevice)(SDL_AudioDevice *device); - void (*ThreadInit)(SDL_AudioDevice *device); /* Called by audio thread at start */ - void (*ThreadDeinit)(SDL_AudioDevice *device); /* Called by audio thread at end */ + void (*ThreadInit)(SDL_AudioDevice *device); // Called by audio thread at start + void (*ThreadDeinit)(SDL_AudioDevice *device); // Called by audio thread at end void (*WaitDevice)(SDL_AudioDevice *device); void (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen); // buffer and buflen are always from GetDeviceBuf, passed here for convenience. Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size); @@ -120,7 +120,7 @@ typedef struct SDL_AudioDriverImpl void (*FreeDeviceHandle)(SDL_AudioDevice *handle); // SDL is done with this device; free the handle from SDL_AddAudioDevice() void (*Deinitialize)(void); - /* Some flags to push duplicate code into the core and reduce #ifdefs. */ + // Some flags to push duplicate code into the core and reduce #ifdefs. SDL_bool ProvidesOwnCallbackThread; // !!! FIXME: rename this, it's not a callback thread anymore. SDL_bool HasCaptureSupport; SDL_bool OnlyHasDefaultOutputDevice; @@ -131,35 +131,35 @@ typedef struct SDL_AudioDriverImpl typedef struct SDL_AudioDriver { - const char *name; /* The name of this audio driver */ - const char *desc; /* The description of this audio driver */ - SDL_AudioDriverImpl impl; /* the backend's interface */ - SDL_RWLock *device_list_lock; /* A mutex for device detection */ - SDL_AudioDevice *output_devices; /* the list of currently-available audio output devices. */ - SDL_AudioDevice *capture_devices; /* the list of currently-available audio capture devices. */ + const char *name; // The name of this audio driver + const char *desc; // The description of this audio driver + SDL_AudioDriverImpl impl; // the backend's interface + SDL_RWLock *device_list_lock; // A mutex for device detection + SDL_AudioDevice *output_devices; // the list of currently-available audio output devices. + SDL_AudioDevice *capture_devices; // the list of currently-available audio capture devices. SDL_AudioDeviceID default_output_device_id; SDL_AudioDeviceID default_capture_device_id; SDL_AtomicInt output_device_count; SDL_AtomicInt capture_device_count; - SDL_AtomicInt last_device_instance_id; /* increments on each device add to provide unique instance IDs */ - SDL_AtomicInt shutting_down; /* non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs. */ + SDL_AtomicInt last_device_instance_id; // increments on each device add to provide unique instance IDs + SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs. } SDL_AudioDriver; struct SDL_AudioStream { SDL_DataQueue *queue; - SDL_Mutex *lock; /* this is just a copy of `queue`'s mutex. We share a lock. */ + SDL_Mutex *lock; // this is just a copy of `queue`'s mutex. We share a lock. SDL_AudioStreamRequestCallback get_callback; void *get_callback_userdata; SDL_AudioStreamRequestCallback put_callback; void *put_callback_userdata; - Uint8 *work_buffer; /* used for scratch space during data conversion/resampling. */ - Uint8 *history_buffer; /* history for left padding and future sample rate changes. */ - Uint8 *future_buffer; /* stuff that left the queue for the right padding and will be next read's data. */ - float *left_padding; /* left padding for resampling. */ - float *right_padding; /* right padding for resampling. */ + Uint8 *work_buffer; // used for scratch space during data conversion/resampling. + Uint8 *history_buffer; // history for left padding and future sample rate changes. + Uint8 *future_buffer; // stuff that left the queue for the right padding and will be next read's data. + float *left_padding; // left padding for resampling. + float *right_padding; // right padding for resampling. SDL_bool flushed; @@ -193,84 +193,84 @@ struct SDL_AudioStream as a group when mixing the final output for the physical device. */ struct SDL_LogicalAudioDevice { - /* the unique instance ID of this device. */ + // the unique instance ID of this device. SDL_AudioDeviceID instance_id; - /* The physical device associated with this opened device. */ + // The physical device associated with this opened device. SDL_AudioDevice *physical_device; - /* If whole logical device is paused (process no streams bound to this device). */ + // If whole logical device is paused (process no streams bound to this device). SDL_AtomicInt paused; - /* double-linked list of all audio streams currently bound to this opened device. */ + // double-linked list of all audio streams currently bound to this opened device. SDL_AudioStream *bound_streams; - /* SDL_TRUE if this was opened as a default device. */ + // SDL_TRUE if this was opened as a default device. SDL_bool is_default; - /* double-linked list of opened devices on the same physical device. */ + // double-linked list of opened devices on the same physical device. SDL_LogicalAudioDevice *next; SDL_LogicalAudioDevice *prev; }; struct SDL_AudioDevice { - /* A mutex for locking access to this struct */ + // A mutex for locking access to this struct SDL_Mutex *lock; - /* human-readable name of the device. ("SoundBlaster Pro 16") */ + // human-readable name of the device. ("SoundBlaster Pro 16") char *name; - /* the unique instance ID of this device. */ + // the unique instance ID of this device. SDL_AudioDeviceID instance_id; - /* a way for the backend to identify this device _when not opened_ */ + // a way for the backend to identify this device _when not opened_ void *handle; - /* The device's current audio specification */ + // The device's current audio specification SDL_AudioSpec spec; Uint32 buffer_size; - /* The device's default audio specification */ + // The device's default audio specification SDL_AudioSpec default_spec; - /* Number of sample frames the devices wants per-buffer. */ + // Number of sample frames the devices wants per-buffer. int sample_frames; - /* Value to use for SDL_memset to silence a buffer in this device's format */ + // Value to use for SDL_memset to silence a buffer in this device's format int silence_value; - /* non-zero if we are signaling the audio thread to end. */ + // non-zero if we are signaling the audio thread to end. SDL_AtomicInt shutdown; - /* non-zero if we want the device to be destroyed (so audio thread knows to do it on termination). */ + // non-zero if we want the device to be destroyed (so audio thread knows to do it on termination). SDL_AtomicInt condemned; - /* non-zero if this was a disconnected default device and we're waiting for its replacement. */ + // non-zero if this was a disconnected default device and we're waiting for its replacement. SDL_AtomicInt zombie; - /* non-zero if this has a thread running (which might be `thread` or something provided by the backend!) */ + // non-zero if this has a thread running (which might be `thread` or something provided by the backend!) SDL_AtomicInt thread_alive; - /* SDL_TRUE if this is a capture device instead of an output device */ + // SDL_TRUE if this is a capture device instead of an output device SDL_bool iscapture; - /* Scratch buffer used for mixing. */ + // Scratch buffer used for mixing. Uint8 *work_buffer; - /* A thread to feed the audio device */ + // A thread to feed the audio device SDL_Thread *thread; - /* SDL_TRUE if this physical device is currently opened by the backend. */ + // SDL_TRUE if this physical device is currently opened by the backend. SDL_bool is_opened; - /* Data private to this driver */ + // Data private to this driver struct SDL_PrivateAudioData *hidden; - /* All logical devices associated with this physical device. */ + // All logical devices associated with this physical device. SDL_LogicalAudioDevice *logical_devices; - /* double-linked list of all physical devices. */ + // double-linked list of all physical devices. struct SDL_AudioDevice *prev; struct SDL_AudioDevice *next; }; @@ -280,10 +280,10 @@ typedef struct AudioBootStrap const char *name; const char *desc; SDL_bool (*init)(SDL_AudioDriverImpl *impl); - SDL_bool demand_only; /* if SDL_TRUE: request explicitly, or it won't be available. */ + SDL_bool demand_only; // if SDL_TRUE: request explicitly, or it won't be available. } AudioBootStrap; -/* Not all of these are available in a given build. Use #ifdefs, etc. */ +// Not all of these are available in a given build. Use #ifdefs, etc. extern AudioBootStrap PIPEWIRE_bootstrap; extern AudioBootStrap PULSEAUDIO_bootstrap; extern AudioBootStrap ALSA_bootstrap; @@ -299,7 +299,7 @@ extern AudioBootStrap COREAUDIO_bootstrap; extern AudioBootStrap DISKAUDIO_bootstrap; extern AudioBootStrap DUMMYAUDIO_bootstrap; extern AudioBootStrap AAUDIO_bootstrap; -extern AudioBootStrap openslES_bootstrap; /* !!! FIXME: capitalize this to match the others */ +extern AudioBootStrap openslES_bootstrap; // !!! FIXME: capitalize this to match the others extern AudioBootStrap ANDROIDAUDIO_bootstrap; extern AudioBootStrap PS2AUDIO_bootstrap; extern AudioBootStrap PSPAUDIO_bootstrap; @@ -311,4 +311,4 @@ extern AudioBootStrap QSAAUDIO_bootstrap; extern SDL_AudioDevice *get_audio_dev(SDL_AudioDeviceID id); extern int get_max_num_audio_dev(void); -#endif /* SDL_sysaudio_h_ */ +#endif // SDL_sysaudio_h_ From c653e57768aa78ac93d58e380b98d1706dd0d355 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 22:00:58 -0400 Subject: [PATCH 059/138] coreaudio: rewritten for SDL3 audio redesign! --- src/audio/coreaudio/SDL_coreaudio.h | 5 +- src/audio/coreaudio/SDL_coreaudio.m | 1114 +++++++++------------------ 2 files changed, 383 insertions(+), 736 deletions(-) diff --git a/src/audio/coreaudio/SDL_coreaudio.h b/src/audio/coreaudio/SDL_coreaudio.h index 989ed61d23..4b7f81bf06 100644 --- a/src/audio/coreaudio/SDL_coreaudio.h +++ b/src/audio/coreaudio/SDL_coreaudio.h @@ -53,15 +53,12 @@ struct SDL_PrivateAudioData AudioQueueRef audioQueue; int numAudioBuffers; AudioQueueBufferRef *audioBuffer; - void *buffer; - UInt32 bufferOffset; - UInt32 bufferSize; + AudioQueueBufferRef current_buffer; AudioStreamBasicDescription strdesc; SDL_Semaphore *ready_semaphore; char *thread_error; #ifdef MACOSX_COREAUDIO AudioDeviceID deviceID; - SDL_AtomicInt device_change_flag; #else SDL_bool interrupted; CFTypeRef interruption_listener; diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m index 20ac3bf5df..bd05c1a10c 100644 --- a/src/audio/coreaudio/SDL_coreaudio.m +++ b/src/audio/coreaudio/SDL_coreaudio.m @@ -22,28 +22,24 @@ #ifdef SDL_AUDIO_DRIVER_COREAUDIO -/* !!! FIXME: clean out some of the macro salsa in here. */ - #include "../SDL_audio_c.h" #include "../SDL_sysaudio.h" #include "SDL_coreaudio.h" #include "../../thread/SDL_systhread.h" -#define DEBUG_COREAUDIO 0 +#define DEBUG_COREAUDIO 1 #if DEBUG_COREAUDIO -#define CHECK_RESULT(msg) \ - if (result != noErr) { \ - printf("COREAUDIO: Got error %d from '%s'!\n", (int)result, msg); \ - SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \ - return 0; \ - } + #define CHECK_RESULT(msg) \ + if (result != noErr) { \ + SDL_Log("COREAUDIO: Got error %d from '%s'!\n", (int)result, msg); \ + return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \ + } #else -#define CHECK_RESULT(msg) \ - if (result != noErr) { \ - SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \ - return 0; \ - } + #define CHECK_RESULT(msg) \ + if (result != noErr) { \ + return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \ + } #endif #ifdef MACOSX_COREAUDIO @@ -53,78 +49,84 @@ static const AudioObjectPropertyAddress devlist_address = { kAudioObjectPropertyElementMain }; -typedef void (*addDevFn)(const char *name, SDL_AudioSpec *spec, const int iscapture, AudioDeviceID devId, void *data); +static const AudioObjectPropertyAddress default_output_device_address = { + kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMain +}; -typedef struct AudioDeviceList +static const AudioObjectPropertyAddress default_input_device_address = { + kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMain +}; + +static const AudioObjectPropertyAddress alive_address = { + kAudioDevicePropertyDeviceIsAlive, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMain +}; + + +static OSStatus DeviceAliveNotification(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) { - AudioDeviceID devid; - SDL_bool alive; - struct AudioDeviceList *next; -} AudioDeviceList; + SDL_AudioDevice *device = (SDL_AudioDevice *)data; + SDL_assert(((AudioObjectID)(size_t)device->handle) == devid); -static AudioDeviceList *output_devs = NULL; -static AudioDeviceList *capture_devs = NULL; + UInt32 alive = 1; + UInt32 size = sizeof(alive); + const OSStatus error = AudioObjectGetPropertyData(devid, addrs, 0, NULL, &size, &alive); -static SDL_bool add_to_internal_dev_list(const int iscapture, AudioDeviceID devId) -{ - AudioDeviceList *item = (AudioDeviceList *)SDL_malloc(sizeof(AudioDeviceList)); - if (item == NULL) { - return SDL_FALSE; - } - item->devid = devId; - item->alive = SDL_TRUE; - item->next = iscapture ? capture_devs : output_devs; - if (iscapture) { - capture_devs = item; - } else { - output_devs = item; + SDL_bool dead = SDL_FALSE; + if (error == kAudioHardwareBadDeviceError) { + dead = SDL_TRUE; /* device was unplugged. */ + } else if ((error == kAudioHardwareNoError) && (!alive)) { + dead = SDL_TRUE; /* device died in some other way. */ } - return SDL_TRUE; + if (dead) { + #if DEBUG_COREAUDIO + SDL_Log("COREAUDIO: device '%s' is lost!", device->name); + #endif + SDL_AudioDeviceDisconnected(device); + } + + return noErr; } -static void addToDevList(const char *name, SDL_AudioSpec *spec, const int iscapture, AudioDeviceID devId, void *data) +static void COREAUDIO_FreeDeviceHandle(SDL_AudioDevice *device) { - if (add_to_internal_dev_list(iscapture, devId)) { - SDL_AddAudioDevice(iscapture, name, spec, (void *)((size_t)devId)); - } + const AudioDeviceID devid = (AudioDeviceID)(size_t)device->handle; + AudioObjectRemovePropertyListener(devid, &alive_address, DeviceAliveNotification, device); } -static void build_device_list(int iscapture, addDevFn addfn, void *addfndata) +// This only _adds_ new devices. Removal is handled by devices triggering kAudioDevicePropertyDeviceIsAlive property changes. +static void RefreshPhysicalDevices(void) { - OSStatus result = noErr; UInt32 size = 0; AudioDeviceID *devs = NULL; - UInt32 i = 0; - UInt32 max = 0; + SDL_bool isstack; - result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, - &devlist_address, 0, NULL, &size); - if (result != kAudioHardwareNoError) { + if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &devlist_address, 0, NULL, &size) != kAudioHardwareNoError) { + return; + } else if ((devs = (AudioDeviceID *) SDL_small_alloc(Uint8, size, &isstack)) == NULL) { + return; + } else if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &devlist_address, 0, NULL, &size, devs) != kAudioHardwareNoError) { + SDL_small_free(devs, isstack); return; } - devs = (AudioDeviceID *)alloca(size); - if (devs == NULL) { - return; + const UInt32 total_devices = (UInt32) (size / sizeof(AudioDeviceID)); + for (UInt32 i = 0; i < total_devices; i++) { + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((size_t)devs[i])); + if (device) { + SDL_UnlockMutex(device->lock); + devs[i] = 0; // The system and SDL both agree it's already here, don't check it again. + } } - result = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &devlist_address, 0, NULL, &size, devs); - if (result != kAudioHardwareNoError) { - return; - } - - max = size / sizeof(AudioDeviceID); - for (i = 0; i < max; i++) { - CFStringRef cfstr = NULL; - char *ptr = NULL; - AudioDeviceID dev = devs[i]; - AudioBufferList *buflist = NULL; - int usable = 0; - CFIndex len = 0; - double sampleRate = 0; - SDL_AudioSpec spec; + // any non-zero items remaining in `devs` are new devices to be added. + for (int iscapture = 0; iscapture < 2; iscapture++) { const AudioObjectPropertyAddress addr = { kAudioDevicePropertyStreamConfiguration, iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, @@ -141,165 +143,167 @@ static void build_device_list(int iscapture, addDevFn addfn, void *addfndata) kAudioObjectPropertyElementMain }; - result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size); - if (result != noErr) { - continue; - } - - buflist = (AudioBufferList *)SDL_malloc(size); - if (buflist == NULL) { - continue; - } - - result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, - &size, buflist); - - SDL_zero(spec); - if (result == noErr) { - UInt32 j; - for (j = 0; j < buflist->mNumberBuffers; j++) { - spec.channels += buflist->mBuffers[j].mNumberChannels; + for (UInt32 i = 0; i < total_devices; i++) { + const AudioDeviceID dev = devs[i]; + if (!dev) { + continue; // already added. } - } - SDL_free(buflist); + AudioBufferList *buflist = NULL; + double sampleRate = 0; - if (spec.channels == 0) { - continue; - } - - size = sizeof(sampleRate); - result = AudioObjectGetPropertyData(dev, &freqaddr, 0, NULL, &size, &sampleRate); - if (result == noErr) { - spec.freq = (int)sampleRate; - } - - size = sizeof(CFStringRef); - result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr); - if (result != kAudioHardwareNoError) { - continue; - } - - len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), - kCFStringEncodingUTF8); - - ptr = (char *)SDL_malloc(len + 1); - usable = ((ptr != NULL) && - (CFStringGetCString(cfstr, ptr, len + 1, kCFStringEncodingUTF8))); - - CFRelease(cfstr); - - if (usable) { - len = SDL_strlen(ptr); - /* Some devices have whitespace at the end...trim it. */ - while ((len > 0) && (ptr[len - 1] == ' ')) { - len--; + if (AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size) != noErr) { + continue; + } else if ((buflist = (AudioBufferList *)SDL_malloc(size)) == NULL) { + continue; } - usable = (len > 0); - } - if (usable) { - ptr[len] = '\0'; + OSStatus result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, buflist); -#if DEBUG_COREAUDIO - printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n", - ((iscapture) ? "capture" : "output"), - (int)i, ptr, (int)dev); -#endif - addfn(ptr, &spec, iscapture, dev, addfndata); - } - SDL_free(ptr); /* addfn() would have copied the string. */ - } -} - -static void free_audio_device_list(AudioDeviceList **list) -{ - AudioDeviceList *item = *list; - while (item) { - AudioDeviceList *next = item->next; - SDL_free(item); - item = next; - } - *list = NULL; -} - -static void COREAUDIO_DetectDevices(void) -{ - build_device_list(SDL_TRUE, addToDevList, NULL); - build_device_list(SDL_FALSE, addToDevList, NULL); -} - -static void build_device_change_list(const char *name, SDL_AudioSpec *spec, const int iscapture, AudioDeviceID devId, void *data) -{ - AudioDeviceList **list = (AudioDeviceList **)data; - AudioDeviceList *item; - for (item = *list; item != NULL; item = item->next) { - if (item->devid == devId) { - item->alive = SDL_TRUE; - return; - } - } - - add_to_internal_dev_list(iscapture, devId); /* new device, add it. */ - SDL_AddAudioDevice(iscapture, name, spec, (void *)((size_t)devId)); -} - -static void reprocess_device_list(const int iscapture, AudioDeviceList **list) -{ - AudioDeviceList *item; - AudioDeviceList *prev = NULL; - for (item = *list; item != NULL; item = item->next) { - item->alive = SDL_FALSE; - } - - build_device_list(iscapture, build_device_change_list, list); - - /* free items in the list that aren't still alive. */ - item = *list; - while (item != NULL) { - AudioDeviceList *next = item->next; - if (item->alive) { - prev = item; - } else { - SDL_RemoveAudioDevice(iscapture, (void *)((size_t)item->devid)); - if (prev) { - prev->next = item->next; - } else { - *list = item->next; + SDL_AudioSpec spec; + SDL_zero(spec); + if (result == noErr) { + for (Uint32 j = 0; j < buflist->mNumberBuffers; j++) { + spec.channels += buflist->mBuffers[j].mNumberChannels; + } } - SDL_free(item); + + SDL_free(buflist); + + if (spec.channels == 0) { + continue; + } + + size = sizeof(sampleRate); + if (AudioObjectGetPropertyData(dev, &freqaddr, 0, NULL, &size, &sampleRate) == noErr) { + spec.freq = (int)sampleRate; + } + + CFStringRef cfstr = NULL; + size = sizeof(CFStringRef); + if (AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr) != kAudioHardwareNoError) { + continue; + } + + CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), kCFStringEncodingUTF8); + char *name = (char *)SDL_malloc(len + 1); + SDL_bool usable = ((name != NULL) && (CFStringGetCString(cfstr, name, len + 1, kCFStringEncodingUTF8))) ? SDL_TRUE : SDL_FALSE; + + CFRelease(cfstr); + + if (usable) { + // Some devices have whitespace at the end...trim it. + len = (CFIndex) SDL_strlen(name); + while ((len > 0) && (name[len - 1] == ' ')) { + len--; + } + usable = (len > 0); + } + + if (usable) { + name[len] = '\0'; + + #if DEBUG_COREAUDIO + SDL_Log("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n", + ((iscapture) ? "capture" : "output"), + (int)i, name, (int)dev); + #endif + + devs[i] = 0; // don't bother checking this one on the next iscapture iteration of the loop + + SDL_AudioDevice *device = SDL_AddAudioDevice(iscapture ? SDL_TRUE : SDL_FALSE, name, &spec, (void *)((size_t)dev)); + if (device) { + AudioObjectAddPropertyListener(dev, &alive_address, DeviceAliveNotification, device); + } + } + SDL_free(name); // SDL_AddAudioDevice() would have copied the string. } - item = next; } + + SDL_small_free(devs, isstack); } -/* this is called when the system's list of available audio devices changes. */ -static OSStatus device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) +// this is called when the system's list of available audio devices changes. +static OSStatus DeviceListChangedNotification(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) { - reprocess_device_list(SDL_TRUE, &capture_devs); - reprocess_device_list(SDL_FALSE, &output_devs); - return 0; + RefreshPhysicalDevices(); + return noErr; } -#endif -static int open_playback_devices; -static int open_capture_devices; -static int num_open_devices; -static SDL_AudioDevice **open_devices; - -#ifndef MACOSX_COREAUDIO - -static BOOL session_active = NO; - -static void pause_audio_devices(void) +static OSStatus DefaultAudioDeviceChangedNotification(AudioObjectID inObjectID, const AudioObjectPropertyAddress *addr) { - int i; + AudioDeviceID devid; + Uint32 size = sizeof(devid); + if (AudioObjectGetPropertyData(inObjectID, addr, 0, NULL, &size, &devid) == noErr) { + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((size_t)devid)); + if (device) { + SDL_UnlockMutex(device->lock); + SDL_DefaultAudioDeviceChanged(device); + } + } + return noErr; +} +static OSStatus DefaultOutputDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) +{ + #if DEBUG_COREAUDIO + SDL_Log("COREAUDIO: default output device changed!"); + #endif + SDL_assert(inNumberAddresses == 1); + return DefaultAudioDeviceChangedNotification(inObjectID, inAddresses); +} + +static OSStatus DefaultInputDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) +{ + #if DEBUG_COREAUDIO + SDL_Log("COREAUDIO: default input device changed!"); + #endif + SDL_assert(inNumberAddresses == 1); + return DefaultAudioDeviceChangedNotification(inObjectID, inAddresses); +} + +static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) +{ + RefreshPhysicalDevices(); + + AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL); + + /* Get the Device ID */ + UInt32 size; + AudioDeviceID devid; + + size = sizeof(AudioDeviceID); + if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_output_device_address, 0, NULL, &size, &devid) == noErr) { + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((size_t)devid)); + if (device) { + SDL_UnlockMutex(device->lock); + *default_output = device; + } + } + AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_output_device_address, DefaultOutputDeviceChangedNotification, NULL); + + size = sizeof(AudioDeviceID); + if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_input_device_address, 0, NULL, &size, &devid) == noErr) { + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((size_t)devid)); + if (device) { + SDL_UnlockMutex(device->lock); + *default_capture = device; + } + } + AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_input_device_address, DefaultInputDeviceChangedNotification, NULL); +} + +#else // iOS-specific section follows. + +static SDL_bool session_active = SDL_FALSE; + +static void PauseAudioDevices(void) // !!! FIXME: this needs to be updated, and we need a method to access SDL_audio.c's device lists. +{ if (!open_devices) { return; } - for (i = 0; i < num_open_devices; ++i) { + for (int i = 0; i < num_open_devices; ++i) { SDL_AudioDevice *device = open_devices[i]; if (device->hidden->audioQueue && !device->hidden->interrupted) { AudioQueuePause(device->hidden->audioQueue); @@ -307,15 +311,13 @@ static void pause_audio_devices(void) } } -static void resume_audio_devices(void) +static void ResumeAudioDevices(void) // !!! FIXME: this needs to be updated, and we need a method to access SDL_audio.c's device lists. { - int i; - if (!open_devices) { return; } - for (i = 0; i < num_open_devices; ++i) { + for (int i = 0; i < num_open_devices; ++i) { SDL_AudioDevice *device = open_devices[i]; if (device->hidden->audioQueue && !device->hidden->interrupted) { AudioQueueStart(device->hidden->audioQueue, NULL); @@ -323,7 +325,7 @@ static void resume_audio_devices(void) } } -static void interruption_begin(SDL_AudioDevice *device) +static void InterruptionBegin(SDL_AudioDevice *device) { if (device != NULL && device->hidden->audioQueue != NULL) { device->hidden->interrupted = SDL_TRUE; @@ -331,7 +333,7 @@ static void interruption_begin(SDL_AudioDevice *device) } } -static void interruption_end(SDL_AudioDevice *device) +static void InterruptionEnd(SDL_AudioDevice *device) { if (device != NULL && device->hidden != NULL && device->hidden->audioQueue != NULL && device->hidden->interrupted && AudioQueueStart(device->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) { device->hidden->interrupted = SDL_FALSE; @@ -351,9 +353,9 @@ static void interruption_end(SDL_AudioDevice *device) @synchronized(self) { NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey]; if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) { - interruption_begin(self.device); + InterruptionBegin(self.device); } else { - interruption_end(self.device); + InterruptionEnd(self.device); } } } @@ -361,13 +363,13 @@ static void interruption_end(SDL_AudioDevice *device) - (void)applicationBecameActive:(NSNotification *)note { @synchronized(self) { - interruption_end(self.device); + InterruptionEnd(self.device); } } @end -static BOOL update_audio_session(SDL_AudioDevice *device, SDL_bool open, SDL_bool allow_playandrecord) +static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_bool allow_playandrecord) { @autoreleasepool { AVAudioSession *session = [AVAudioSession sharedInstance]; @@ -402,11 +404,11 @@ static BOOL update_audio_session(SDL_AudioDevice *device, SDL_bool open, SDL_boo category = AVAudioSessionCategoryRecord; } -#if !TARGET_OS_TV + #if !TARGET_OS_TV if (category == AVAudioSessionCategoryPlayAndRecord) { options |= AVAudioSessionCategoryOptionDefaultToSpeaker; } -#endif + #endif if (category == AVAudioSessionCategoryRecord || category == AVAudioSessionCategoryPlayAndRecord) { /* AVAudioSessionCategoryOptionAllowBluetooth isn't available in the SDK for @@ -426,27 +428,27 @@ static BOOL update_audio_session(SDL_AudioDevice *device, SDL_bool open, SDL_boo if ([session respondsToSelector:@selector(setCategory:mode:options:error:)]) { if (![session.category isEqualToString:category] || session.categoryOptions != options) { /* Stop the current session so we don't interrupt other application audio */ - pause_audio_devices(); + PauseAudioDevices(); [session setActive:NO error:nil]; - session_active = NO; + session_active = SDL_FALSE; if (![session setCategory:category mode:mode options:options error:&err]) { NSString *desc = err.description; SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); - return NO; + return SDL_FALSE; } } } else { if (![session.category isEqualToString:category]) { /* Stop the current session so we don't interrupt other application audio */ - pause_audio_devices(); + PauseAudioDevices(); [session setActive:NO error:nil]; - session_active = NO; + session_active = SDL_FALSE; if (![session setCategory:category error:&err]) { NSString *desc = err.description; SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); - return NO; + return SDL_FALSE; } } } @@ -455,19 +457,19 @@ static BOOL update_audio_session(SDL_AudioDevice *device, SDL_bool open, SDL_boo if (![session setActive:YES error:&err]) { if ([err code] == AVAudioSessionErrorCodeResourceNotAvailable && category == AVAudioSessionCategoryPlayAndRecord) { - return update_audio_session(device, open, SDL_FALSE); + return UpdateAudioSession(device, open, SDL_FALSE); } NSString *desc = err.description; SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String); - return NO; + return SDL_FALSE; } - session_active = YES; - resume_audio_devices(); + session_active = SDL_TRUE; + ResumeAudioDevices(); } else if (!open_playback_devices && !open_capture_devices && session_active) { - pause_audio_devices(); + PauseAudioDevices(); [session setActive:NO error:nil]; - session_active = NO; + session_active = SDL_FALSE; } if (open) { @@ -504,191 +506,83 @@ static BOOL update_audio_session(SDL_AudioDevice *device, SDL_bool open, SDL_boo } } - return YES; + return SDL_TRUE; } #endif -/* The AudioQueue callback */ -static void outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) + +static void COREAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { - SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; - - /* This flag is set before device->mixer_lock is destroyed during - shutdown, so check it before grabbing the mutex, and then check it - again _after_ in case we blocked waiting on the lock. */ - if (SDL_AtomicGet(&device->shutdown)) { - return; /* don't do anything, since we don't even want to enqueue this buffer again. */ - } - - SDL_LockMutex(device->mixer_lock); - - if (SDL_AtomicGet(&device->shutdown)) { - SDL_UnlockMutex(device->mixer_lock); - return; /* don't do anything, since we don't even want to enqueue this buffer again. */ - } - - if (!SDL_AtomicGet(&device->enabled) || SDL_AtomicGet(&device->paused)) { - /* Supply silence if audio is not enabled or paused */ - SDL_memset(inBuffer->mAudioData, device->spec.silence, inBuffer->mAudioDataBytesCapacity); - } else if (device->stream) { - UInt32 remaining = inBuffer->mAudioDataBytesCapacity; - Uint8 *ptr = (Uint8 *)inBuffer->mAudioData; - - while (remaining > 0) { - if (SDL_GetAudioStreamAvailable(device->stream) == 0) { - /* Generate the data */ - (*device->callbackspec.callback)(device->callbackspec.userdata, - device->hidden->buffer, device->hidden->bufferSize); - device->hidden->bufferOffset = 0; - SDL_PutAudioStreamData(device->stream, device->hidden->buffer, device->hidden->bufferSize); - } - if (SDL_GetAudioStreamAvailable(device->stream) > 0) { - int got; - UInt32 len = SDL_GetAudioStreamAvailable(device->stream); - if (len > remaining) { - len = remaining; - } - got = SDL_GetAudioStreamData(device->stream, ptr, len); - SDL_assert((got < 0) || (got == len)); - if (got != len) { - SDL_memset(ptr, device->spec.silence, len); - } - ptr = ptr + len; - remaining -= len; - } - } - } else { - UInt32 remaining = inBuffer->mAudioDataBytesCapacity; - Uint8 *ptr = (Uint8 *)inBuffer->mAudioData; - - while (remaining > 0) { - UInt32 len; - if (device->hidden->bufferOffset >= device->hidden->bufferSize) { - /* Generate the data */ - (*device->callbackspec.callback)(device->callbackspec.userdata, - device->hidden->buffer, device->hidden->bufferSize); - device->hidden->bufferOffset = 0; - } - - len = device->hidden->bufferSize - device->hidden->bufferOffset; - if (len > remaining) { - len = remaining; - } - SDL_memcpy(ptr, (char *)device->hidden->buffer + device->hidden->bufferOffset, len); - ptr = ptr + len; - remaining -= len; - device->hidden->bufferOffset += len; - } - } - - AudioQueueEnqueueBuffer(device->hidden->audioQueue, inBuffer, 0, NULL); - - inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity; - - SDL_UnlockMutex(device->mixer_lock); + AudioQueueBufferRef current_buffer = device->hidden->current_buffer; + SDL_assert(current_buffer != NULL); // should have been called from OutputBufferReadyCallback + SDL_assert(buffer == (Uint8 *) current_buffer->mAudioData); + current_buffer->mAudioDataByteSize = current_buffer->mAudioDataBytesCapacity; + device->hidden->current_buffer = NULL; + AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); } -static void inputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, +static Uint8 *COREAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) +{ + AudioQueueBufferRef current_buffer = device->hidden->current_buffer; + SDL_assert(current_buffer != NULL); // should have been called from OutputBufferReadyCallback + SDL_assert(current_buffer->mAudioData != NULL); + *buffer_size = (int) current_buffer->mAudioDataBytesCapacity; + return (Uint8 *) current_buffer->mAudioData; +} + +static void OutputBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) +{ + SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; + SDL_assert(inBuffer != NULL); // ...right? + SDL_assert(device->hidden->current_buffer == NULL); // shouldn't have anything pending + device->hidden->current_buffer = inBuffer; + SDL_OutputAudioThreadIterate(device); + SDL_assert(device->hidden->current_buffer == NULL); // PlayDevice should have enqueued and cleaned it out. +} + +static int COREAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) +{ + AudioQueueBufferRef current_buffer = device->hidden->current_buffer; + SDL_assert(current_buffer != NULL); // should have been called from InputBufferReadyCallback + SDL_assert(current_buffer->mAudioData != NULL); + SDL_assert(buflen >= (int) current_buffer->mAudioDataByteSize); // `cpy` makes sure this won't overflow a buffer, but we _will_ drop samples if this assertion fails! + const int cpy = SDL_min(buflen, (int) current_buffer->mAudioDataByteSize); + SDL_memcpy(buffer, current_buffer->mAudioData, cpy); + device->hidden->current_buffer = NULL; + AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); // requeue for capturing more data later. + return cpy; +} + +static void COREAUDIO_FlushCapture(SDL_AudioDevice *device) +{ + AudioQueueBufferRef current_buffer = device->hidden->current_buffer; + if (current_buffer != NULL) { // also gets called at shutdown, when no buffer is available. + // just requeue the current buffer without reading from it, so it can be refilled with new data later. + device->hidden->current_buffer = NULL; + AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); + } +} + +static void InputBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions, const AudioStreamPacketDescription *inPacketDescs) { SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; - - if (SDL_AtomicGet(&device->shutdown)) { - return; /* don't do anything. */ - } - - /* ignore unless we're active. */ - if (!SDL_AtomicGet(&device->paused) && SDL_AtomicGet(&device->enabled)) { - const Uint8 *ptr = (const Uint8 *)inBuffer->mAudioData; - UInt32 remaining = inBuffer->mAudioDataByteSize; - while (remaining > 0) { - UInt32 len = device->hidden->bufferSize - device->hidden->bufferOffset; - if (len > remaining) { - len = remaining; - } - - SDL_memcpy((char *)device->hidden->buffer + device->hidden->bufferOffset, ptr, len); - ptr += len; - remaining -= len; - device->hidden->bufferOffset += len; - - if (device->hidden->bufferOffset >= device->hidden->bufferSize) { - SDL_LockMutex(device->mixer_lock); - (*device->callbackspec.callback)(device->callbackspec.userdata, device->hidden->buffer, device->hidden->bufferSize); - SDL_UnlockMutex(device->mixer_lock); - device->hidden->bufferOffset = 0; - } - } - } - - AudioQueueEnqueueBuffer(device->hidden->audioQueue, inBuffer, 0, NULL); + SDL_assert(inAQ == device->hidden->audioQueue); + SDL_assert(inBuffer != NULL); // ...right? + SDL_assert(device->hidden->current_buffer == NULL); // shouldn't have anything pending + device->hidden->current_buffer = inBuffer; + SDL_CaptureAudioThreadIterate(device); + SDL_assert(device->hidden->current_buffer == NULL); // CaptureFromDevice/FlushCapture should have enqueued and cleaned it out. } -#ifdef MACOSX_COREAUDIO -static const AudioObjectPropertyAddress alive_address = { - kAudioDevicePropertyDeviceIsAlive, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMain -}; - -static OSStatus device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *)data; - SDL_bool dead = SDL_FALSE; - UInt32 isAlive = 1; - UInt32 size = sizeof(isAlive); - OSStatus error; - - if (!SDL_AtomicGet(&device->enabled)) { - return 0; /* already known to be dead. */ - } - - error = AudioObjectGetPropertyData(device->hidden->deviceID, &alive_address, - 0, NULL, &size, &isAlive); - - if (error == kAudioHardwareBadDeviceError) { - dead = SDL_TRUE; /* device was unplugged. */ - } else if ((error == kAudioHardwareNoError) && (!isAlive)) { - dead = SDL_TRUE; /* device died in some other way. */ - } - - if (dead) { - SDL_OpenedAudioDeviceDisconnected(device); - } - - return 0; -} - -/* macOS calls this when the default device changed (if we have a default device open). */ -static OSStatus default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData; -#if DEBUG_COREAUDIO - printf("COREAUDIO: default device changed for SDL audio device %p!\n", device); -#endif - SDL_AtomicSet(&device->hidden->device_change_flag, 1); /* let the audioqueue thread pick up on this when safe to do so. */ - return noErr; -} -#endif - static void COREAUDIO_CloseDevice(SDL_AudioDevice *device) { - const SDL_bool iscapture = device->iscapture; - int i; - -/* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ -/* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ -#ifdef MACOSX_COREAUDIO - if (device->handle != NULL) { /* we don't register this listener for default devices. */ - AudioObjectRemovePropertyListener(device->hidden->deviceID, &alive_address, device_unplugged, device); + if (!device->hidden) { + return; } -#endif - /* if callback fires again, feed silence; don't call into the app. */ - SDL_AtomicSet(&device->paused, 1); - - /* dispose of the audio queue before waiting on the thread, or it might stall for a long time! */ + // dispose of the audio queue before waiting on the thread, or it might stall for a long time! if (device->hidden->audioQueue) { AudioQueueFlush(device->hidden->audioQueue); AudioQueueStop(device->hidden->audioQueue, 0); @@ -696,55 +590,33 @@ static void COREAUDIO_CloseDevice(SDL_AudioDevice *device) } if (device->hidden->thread) { - SDL_assert(SDL_AtomicGet(&device->shutdown) != 0); /* should have been set by SDL_audio.c */ + SDL_assert(SDL_AtomicGet(&device->shutdown) != 0); // should have been set by SDL_audio.c SDL_WaitThread(device->hidden->thread, NULL); } - if (iscapture) { - open_capture_devices--; - } else { - open_playback_devices--; - } - -#ifndef MACOSX_COREAUDIO - update_audio_session(device, SDL_FALSE, SDL_TRUE); -#endif - - for (i = 0; i < num_open_devices; ++i) { - if (open_devices[i] == device) { - --num_open_devices; - if (i < num_open_devices) { - SDL_memmove(&open_devices[i], &open_devices[i + 1], sizeof(open_devices[i]) * (num_open_devices - i)); - } - break; - } - } - if (num_open_devices == 0) { - SDL_free(open_devices); - open_devices = NULL; - } + #ifndef MACOSX_COREAUDIO + UpdateAudioSession(device, SDL_FALSE, SDL_TRUE); + #endif if (device->hidden->ready_semaphore) { SDL_DestroySemaphore(device->hidden->ready_semaphore); } - /* AudioQueueDispose() frees the actual buffer objects. */ + // AudioQueueDispose() frees the actual buffer objects. SDL_free(device->hidden->audioBuffer); SDL_free(device->hidden->thread_error); - SDL_free(device->hidden->buffer); SDL_free(device->hidden); } #ifdef MACOSX_COREAUDIO -static int prepare_device(SDL_AudioDevice *device) +static int PrepareDevice(SDL_AudioDevice *device) { void *handle = device->handle; - SDL_bool iscapture = device->iscapture; - AudioDeviceID devid = (AudioDeviceID)((size_t)handle); + SDL_assert(handle != NULL); // this meant "system default" in SDL2, but doesn't anymore + + const AudioDeviceID devid = (AudioDeviceID)((size_t)handle); OSStatus result = noErr; UInt32 size = 0; - UInt32 alive = 0; - pid_t pid = 0; AudioObjectPropertyAddress addr = { 0, @@ -752,42 +624,31 @@ static int prepare_device(SDL_AudioDevice *device) kAudioObjectPropertyElementMain }; - if (handle == NULL) { - size = sizeof(AudioDeviceID); - addr.mSelector = - ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice); - result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, - 0, NULL, &size, &devid); - CHECK_RESULT("AudioHardwareGetProperty (default device)"); - } - - addr.mSelector = kAudioDevicePropertyDeviceIsAlive; - addr.mScope = iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; - + UInt32 alive = 0; size = sizeof(alive); + addr.mSelector = kAudioDevicePropertyDeviceIsAlive; + addr.mScope = device->iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive); CHECK_RESULT("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)"); - if (!alive) { - SDL_SetError("CoreAudio: requested device exists, but isn't alive."); - return 0; + return SDL_SetError("CoreAudio: requested device exists, but isn't alive."); } - addr.mSelector = kAudioDevicePropertyHogMode; + // some devices don't support this property, so errors are fine here. + pid_t pid = 0; size = sizeof(pid); + addr.mSelector = kAudioDevicePropertyHogMode; result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid); - - /* some devices don't support this property, so errors are fine here. */ if ((result == noErr) && (pid != -1)) { - SDL_SetError("CoreAudio: requested device is being hogged."); - return 0; + return SDL_SetError("CoreAudio: requested device is being hogged."); } device->hidden->deviceID = devid; - return 1; + + return 0; } -static int assign_device_to_audioqueue(SDL_AudioDevice *device) +static int AssignDeviceToAudioQueue(SDL_AudioDevice *device) { const AudioObjectPropertyAddress prop = { kAudioDevicePropertyDeviceUID, @@ -803,51 +664,38 @@ static int assign_device_to_audioqueue(SDL_AudioDevice *device) result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize); CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)"); - return 1; + // !!! FIXME: do we need to CFRelease(devuid)? + + return 0; } #endif -static int prepare_audioqueue(SDL_AudioDevice *device) +static int PrepareAudioQueue(SDL_AudioDevice *device) { const AudioStreamBasicDescription *strdesc = &device->hidden->strdesc; - const int iscapture = device->iscapture; + const SDL_bool iscapture = device->iscapture; OSStatus result; - int i, numAudioBuffers = 2; - AudioChannelLayout layout; - double MINIMUM_AUDIO_BUFFER_TIME_MS; - const double msecs = (device->spec.samples / ((double)device->spec.freq)) * 1000.0; - ; SDL_assert(CFRunLoopGetCurrent() != NULL); if (iscapture) { - result = AudioQueueNewInput(strdesc, inputCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); + result = AudioQueueNewInput(strdesc, InputBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); CHECK_RESULT("AudioQueueNewInput"); } else { - result = AudioQueueNewOutput(strdesc, outputCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); + result = AudioQueueNewOutput(strdesc, OutputBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue); CHECK_RESULT("AudioQueueNewOutput"); } -#ifdef MACOSX_COREAUDIO - if (!assign_device_to_audioqueue(device)) { - return 0; + #ifdef MACOSX_COREAUDIO + if (AssignDeviceToAudioQueue(device) < 0) { + return -1; } + #endif - /* only listen for unplugging on specific devices, not the default device, as that should - switch to a different device (or hang out silently if there _is_ no other device). */ - if (device->handle != NULL) { - /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ - /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ - /* Fire a callback if the device stops being "alive" (disconnected, etc). */ - /* If this fails, oh well, we won't notice a device had an extraordinary event take place. */ - AudioObjectAddPropertyListener(device->hidden->deviceID, &alive_address, device_unplugged, device); - } -#endif - - /* Calculate the final parameters for this audio specification */ - SDL_CalculateAudioSpec(&device->spec); + SDL_UpdatedAudioDeviceFormat(device); // make sure this is correct. /* Set the channel layout for the audio queue */ + AudioChannelLayout layout; SDL_zero(layout); switch (device->spec.channels) { case 1: @@ -881,43 +729,35 @@ static int prepare_audioqueue(SDL_AudioDevice *device) CHECK_RESULT("AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)"); } - /* Allocate a sample buffer */ - device->hidden->bufferSize = device->spec.size; - device->hidden->bufferOffset = iscapture ? 0 : device->hidden->bufferSize; - - device->hidden->buffer = SDL_malloc(device->hidden->bufferSize); - if (device->hidden->buffer == NULL) { - SDL_OutOfMemory(); - return 0; - } - - /* Make sure we can feed the device a minimum amount of time */ - MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0; -#ifdef __IOS__ + // Make sure we can feed the device a minimum amount of time + double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0; + #ifdef __IOS__ if (SDL_floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) { - /* Older iOS hardware, use 40 ms as a minimum time */ + // Older iOS hardware, use 40 ms as a minimum time MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0; } -#endif + #endif + + int numAudioBuffers = 2; + const double msecs = (device->sample_frames / ((double)device->spec.freq)) * 1000.0; if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { /* use more buffers if we have a VERY small sample set. */ numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2); } device->hidden->numAudioBuffers = numAudioBuffers; - device->hidden->audioBuffer = SDL_calloc(1, sizeof(AudioQueueBufferRef) * numAudioBuffers); + device->hidden->audioBuffer = SDL_calloc(numAudioBuffers, sizeof(AudioQueueBufferRef)); if (device->hidden->audioBuffer == NULL) { - SDL_OutOfMemory(); - return 0; + return SDL_OutOfMemory(); } -#if DEBUG_COREAUDIO - printf("COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers); -#endif + #if DEBUG_COREAUDIO + SDL_Log("COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers); + #endif - for (i = 0; i < numAudioBuffers; i++) { - result = AudioQueueAllocateBuffer(device->hidden->audioQueue, device->spec.size, &device->hidden->audioBuffer[i]); + for (int i = 0; i < numAudioBuffers; i++) { + result = AudioQueueAllocateBuffer(device->hidden->audioQueue, device->buffer_size, &device->hidden->audioBuffer[i]); CHECK_RESULT("AudioQueueAllocateBuffer"); - SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->spec.silence, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity); + SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->silence_value, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity); device->hidden->audioBuffer[i]->mAudioDataByteSize = device->hidden->audioBuffer[i]->mAudioDataBytesCapacity; /* !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? */ result = AudioQueueEnqueueBuffer(device->hidden->audioQueue, device->hidden->audioBuffer[i], 0, NULL); @@ -927,117 +767,55 @@ static int prepare_audioqueue(SDL_AudioDevice *device) result = AudioQueueStart(device->hidden->audioQueue, NULL); CHECK_RESULT("AudioQueueStart"); - /* We're running! */ - return 1; + return 0; // We're running! } -static int audioqueue_thread(void *arg) +static int AudioQueueThreadEntry(void *arg) { SDL_AudioDevice *device = (SDL_AudioDevice *)arg; - int rc; -#ifdef MACOSX_COREAUDIO - const AudioObjectPropertyAddress default_device_address = { - device->iscapture ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMain - }; - - if (device->handle == NULL) { /* opened the default device? Register to know if the user picks a new default. */ - /* we don't care if this fails; we just won't change to new default devices, but we still otherwise function in this case. */ - AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_device_address, default_device_changed, device); + if (device->iscapture) { + SDL_CaptureAudioThreadSetup(device); + } else { + SDL_OutputAudioThreadSetup(device); } -#endif - rc = prepare_audioqueue(device); - if (!rc) { + if (PrepareAudioQueue(device) < 0) { device->hidden->thread_error = SDL_strdup(SDL_GetError()); SDL_PostSemaphore(device->hidden->ready_semaphore); return 0; } - SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); - - /* init was successful, alert parent thread and start running... */ + // init was successful, alert parent thread and start running... SDL_PostSemaphore(device->hidden->ready_semaphore); + // This would be WaitDevice/WaitCaptureDevice in the normal SDL audio thread, but we get *BufferReadyCallback calls here to know when to iterate. while (!SDL_AtomicGet(&device->shutdown)) { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); - -#ifdef MACOSX_COREAUDIO - if ((device->handle == NULL) && SDL_AtomicGet(&device->hidden->device_change_flag)) { - const AudioDeviceID prev_devid = device->hidden->deviceID; - SDL_AtomicSet(&device->hidden->device_change_flag, 0); - -#if DEBUG_COREAUDIO - printf("COREAUDIO: audioqueue_thread is trying to switch to new default device!\n"); -#endif - - /* if any of this fails, there's not much to do but wait to see if the user gives up - and quits (flagging the audioqueue for shutdown), or toggles to some other system - output device (in which case we'll try again). */ - if (prepare_device(device) && (prev_devid != device->hidden->deviceID)) { - AudioQueueStop(device->hidden->audioQueue, 1); - if (assign_device_to_audioqueue(device)) { - int i; - for (i = 0; i < device->hidden->numAudioBuffers; i++) { - SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->spec.silence, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity); - /* !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? */ - AudioQueueEnqueueBuffer(device->hidden->audioQueue, device->hidden->audioBuffer[i], 0, NULL); - } - AudioQueueStart(device->hidden->audioQueue, NULL); - } - } - } -#endif } - if (!device->iscapture) { /* Drain off any pending playback. */ - const CFTimeInterval secs = (((device->spec.size / (SDL_AUDIO_BITSIZE(device->spec.format) / 8.0)) / device->spec.channels) / ((CFTimeInterval)device->spec.freq)) * 2.0; + if (device->iscapture) { + SDL_CaptureAudioThreadShutdown(device); + } else { + // Drain off any pending playback. + const CFTimeInterval secs = (((CFTimeInterval)device->sample_frames) / ((CFTimeInterval)device->spec.freq)) * 2.0; CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0); + SDL_OutputAudioThreadShutdown(device); } -#ifdef MACOSX_COREAUDIO - if (device->handle == NULL) { - /* we don't care if this fails; we just won't change to new default devices, but we still otherwise function in this case. */ - AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_device_address, default_device_changed, device); - } -#endif - return 0; } -static int COREAUDIO_OpenDevice(SDL_AudioDevice *device, const char *devname) +static int COREAUDIO_OpenDevice(SDL_AudioDevice *device) { - AudioStreamBasicDescription *strdesc; - const SDL_AudioFormat *closefmts; - SDL_AudioFormat test_format; - SDL_bool iscapture = device->iscapture; - SDL_AudioDevice **new_open_devices; - /* Initialize all variables that we clean on shutdown */ - device->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*device->hidden)); + device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(device->hidden); - strdesc = &device->hidden->strdesc; - - if (iscapture) { - open_capture_devices++; - } else { - open_playback_devices++; - } - - new_open_devices = (SDL_AudioDevice **)SDL_realloc(open_devices, sizeof(open_devices[0]) * (num_open_devices + 1)); - if (new_open_devices) { - open_devices = new_open_devices; - open_devices[num_open_devices++] = device; - } - -#ifndef MACOSX_COREAUDIO - if (!update_audio_session(device, SDL_TRUE, SDL_TRUE)) { + #ifndef MACOSX_COREAUDIO + if (!UpdateAudioSession(device, SDL_TRUE, SDL_TRUE)) { return -1; } @@ -1046,29 +824,30 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device, const char *devname) AVAudioSession *session = [AVAudioSession sharedInstance]; [session setPreferredSampleRate:device->spec.freq error:nil]; device->spec.freq = (int)session.sampleRate; -#if TARGET_OS_TV - if (iscapture) { + #if TARGET_OS_TV + if (device->iscapture) { [session setPreferredInputNumberOfChannels:device->spec.channels error:nil]; device->spec.channels = session.preferredInputNumberOfChannels; } else { [session setPreferredOutputNumberOfChannels:device->spec.channels error:nil]; device->spec.channels = session.preferredOutputNumberOfChannels; } -#else + #else /* Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS */ -#endif /* TARGET_OS_TV */ + #endif /* TARGET_OS_TV */ } -#endif + #endif /* Setup a AudioStreamBasicDescription with the requested format */ - SDL_zerop(strdesc); + AudioStreamBasicDescription *strdesc = &device->hidden->strdesc; strdesc->mFormatID = kAudioFormatLinearPCM; strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked; strdesc->mChannelsPerFrame = device->spec.channels; strdesc->mSampleRate = device->spec.freq; strdesc->mFramesPerPacket = 1; - closefmts = SDL_ClosestAudioFormats(device->spec.format); + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); + SDL_AudioFormat test_format; while ((test_format = *(closefmts++)) != 0) { /* CoreAudio handles most of SDL's formats natively. */ switch (test_format) { @@ -1107,7 +886,7 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device, const char *devname) strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket; #ifdef MACOSX_COREAUDIO - if (!prepare_device(device)) { + if (PrepareDevice(device) < 0) { return -1; } #endif @@ -1118,7 +897,9 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device, const char *devname) return -1; /* oh well. */ } - device->hidden->thread = SDL_CreateThreadInternal(audioqueue_thread, "AudioQueue thread", 512 * 1024, device); + char threadname[64]; + SDL_GetAudioThreadName(device, threadname, sizeof(threadname)); + device->hidden->thread = SDL_CreateThreadInternal(AudioQueueThreadEntry, threadname, 0, device); if (!device->hidden->thread) { return -1; } @@ -1128,154 +909,20 @@ static int COREAUDIO_OpenDevice(SDL_AudioDevice *device, const char *devname) device->hidden->ready_semaphore = NULL; if ((device->hidden->thread != NULL) && (device->hidden->thread_error != NULL)) { + SDL_WaitThread(device->hidden->thread, NULL); + device->hidden->thread = NULL; return SDL_SetError("%s", device->hidden->thread_error); } return (device->hidden->thread != NULL) ? 0 : -1; } -#ifndef MACOSX_COREAUDIO -static int COREAUDIO_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) -{ - AVAudioSession *session = [AVAudioSession sharedInstance]; - - if (name != NULL) { - *name = NULL; - } - SDL_zerop(spec); - spec->freq = [session sampleRate]; - spec->channels = [session outputNumberOfChannels]; - return 0; -} -#else /* MACOSX_COREAUDIO */ -static int COREAUDIO_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) -{ - AudioDeviceID devid; - AudioBufferList *buflist; - OSStatus result; - UInt32 size; - CFStringRef cfstr; - char *devname; - int usable; - double sampleRate; - CFIndex len; - - AudioObjectPropertyAddress addr = { - iscapture ? kAudioHardwarePropertyDefaultInputDevice - : kAudioHardwarePropertyDefaultOutputDevice, - iscapture ? kAudioDevicePropertyScopeInput - : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - AudioObjectPropertyAddress nameaddr = { - kAudioObjectPropertyName, - iscapture ? kAudioDevicePropertyScopeInput - : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - AudioObjectPropertyAddress freqaddr = { - kAudioDevicePropertyNominalSampleRate, - iscapture ? kAudioDevicePropertyScopeInput - : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - AudioObjectPropertyAddress bufaddr = { - kAudioDevicePropertyStreamConfiguration, - iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMain - }; - - /* Get the Device ID */ - cfstr = NULL; - size = sizeof(AudioDeviceID); - result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, - 0, NULL, &size, &devid); - - if (result != noErr) { - return SDL_SetError("%s: Default Device ID not found", "coreaudio"); - } - - if (name != NULL) { - /* Use the Device ID to get the name */ - size = sizeof(CFStringRef); - result = AudioObjectGetPropertyData(devid, &nameaddr, 0, NULL, &size, &cfstr); - - if (result != noErr) { - return SDL_SetError("%s: Default Device Name not found", "coreaudio"); - } - - len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), - kCFStringEncodingUTF8); - devname = (char *)SDL_malloc(len + 1); - usable = ((devname != NULL) && - (CFStringGetCString(cfstr, devname, len + 1, kCFStringEncodingUTF8))); - CFRelease(cfstr); - - if (usable) { - usable = 0; - len = SDL_strlen(devname); - /* Some devices have whitespace at the end...trim it. */ - while ((len > 0) && (devname[len - 1] == ' ')) { - len--; - usable = (int)len; - } - } - - if (usable) { - devname[len] = '\0'; - } - *name = devname; - } - - /* Uses the Device ID to get the spec */ - SDL_zerop(spec); - - sampleRate = 0; - size = sizeof(sampleRate); - result = AudioObjectGetPropertyData(devid, &freqaddr, 0, NULL, &size, &sampleRate); - - if (result != noErr) { - return SDL_SetError("%s: Default Device Sample Rate not found", "coreaudio"); - } - - spec->freq = (int)sampleRate; - - result = AudioObjectGetPropertyDataSize(devid, &bufaddr, 0, NULL, &size); - if (result != noErr) { - return SDL_SetError("%s: Default Device Data Size not found", "coreaudio"); - } - - buflist = (AudioBufferList *)SDL_malloc(size); - if (buflist == NULL) { - return SDL_SetError("%s: Default Device Buffer List not found", "coreaudio"); - } - - result = AudioObjectGetPropertyData(devid, &bufaddr, 0, NULL, - &size, buflist); - - if (result == noErr) { - UInt32 j; - for (j = 0; j < buflist->mNumberBuffers; j++) { - spec->channels += buflist->mBuffers[j].mNumberChannels; - } - } - - SDL_free(buflist); - - if (spec->channels == 0) { - return SDL_SetError("%s: Default Device has no channels!", "coreaudio"); - } - - return 0; -} -#endif /* MACOSX_COREAUDIO */ - static void COREAUDIO_Deinitialize(void) { #ifdef MACOSX_COREAUDIO - AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL); - free_audio_device_list(&capture_devs); - free_audio_device_list(&output_devs); + AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL); + AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_output_device_address, DefaultOutputDeviceChangedNotification, NULL); + AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_input_device_address, DefaultInputDeviceChangedNotification, NULL); #endif } @@ -1283,13 +930,16 @@ static SDL_bool COREAUDIO_Init(SDL_AudioDriverImpl *impl) { /* Set the function pointers */ impl->OpenDevice = COREAUDIO_OpenDevice; + impl->PlayDevice = COREAUDIO_PlayDevice; + impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf; + impl->CaptureFromDevice = COREAUDIO_CaptureFromDevice; + impl->FlushCapture = COREAUDIO_FlushCapture; impl->CloseDevice = COREAUDIO_CloseDevice; impl->Deinitialize = COREAUDIO_Deinitialize; - impl->GetDefaultAudioInfo = COREAUDIO_GetDefaultAudioInfo; #ifdef MACOSX_COREAUDIO impl->DetectDevices = COREAUDIO_DetectDevices; - AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL); + impl->FreeDeviceHandle = COREAUDIO_FreeDeviceHandle; #else impl->OnlyHasDefaultOutputDevice = SDL_TRUE; impl->OnlyHasDefaultCaptureDevice = SDL_TRUE; From 0fb9e4baae2ae20757fb31068b11849c891b502c Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 23:26:20 -0400 Subject: [PATCH 060/138] audio: Remove no-longer-used SupportsNonPow2Samples --- src/audio/SDL_sysaudio.h | 1 - src/audio/alsa/SDL_alsa_audio.c | 1 - src/audio/coreaudio/SDL_coreaudio.m | 1 - src/audio/directsound/SDL_directsound.c | 1 - src/audio/disk/SDL_diskaudio.c | 1 - src/audio/wasapi/SDL_wasapi.c | 1 - 6 files changed, 6 deletions(-) diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 6786c3afff..1f018887c6 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -126,7 +126,6 @@ typedef struct SDL_AudioDriverImpl SDL_bool OnlyHasDefaultOutputDevice; SDL_bool OnlyHasDefaultCaptureDevice; SDL_bool AllowsArbitraryDeviceNames; - SDL_bool SupportsNonPow2Samples; } SDL_AudioDriverImpl; typedef struct SDL_AudioDriver diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c index 7699c5d8cb..8ab532b4c1 100644 --- a/src/audio/alsa/SDL_alsa_audio.c +++ b/src/audio/alsa/SDL_alsa_audio.c @@ -970,7 +970,6 @@ static SDL_bool ALSA_Init(SDL_AudioDriverImpl *impl) impl->FlushCapture = ALSA_FlushCapture; impl->HasCaptureSupport = SDL_TRUE; - impl->SupportsNonPow2Samples = SDL_TRUE; return SDL_TRUE; /* this audio target is available. */ } diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m index bd05c1a10c..44ca6a846d 100644 --- a/src/audio/coreaudio/SDL_coreaudio.m +++ b/src/audio/coreaudio/SDL_coreaudio.m @@ -947,7 +947,6 @@ static SDL_bool COREAUDIO_Init(SDL_AudioDriverImpl *impl) impl->ProvidesOwnCallbackThread = SDL_TRUE; impl->HasCaptureSupport = SDL_TRUE; - impl->SupportsNonPow2Samples = SDL_TRUE; return SDL_TRUE; /* this audio target is available. */ } diff --git a/src/audio/directsound/SDL_directsound.c b/src/audio/directsound/SDL_directsound.c index 17fef74bf7..3c9bbbdc34 100644 --- a/src/audio/directsound/SDL_directsound.c +++ b/src/audio/directsound/SDL_directsound.c @@ -644,7 +644,6 @@ static SDL_bool DSOUND_Init(SDL_AudioDriverImpl *impl) impl->GetDefaultAudioInfo = DSOUND_GetDefaultAudioInfo; impl->HasCaptureSupport = SDL_TRUE; - impl->SupportsNonPow2Samples = SDL_TRUE; return SDL_TRUE; /* this audio target is available. */ } diff --git a/src/audio/disk/SDL_diskaudio.c b/src/audio/disk/SDL_diskaudio.c index 0cfa3b7a27..b4bd798654 100644 --- a/src/audio/disk/SDL_diskaudio.c +++ b/src/audio/disk/SDL_diskaudio.c @@ -171,7 +171,6 @@ static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl) impl->AllowsArbitraryDeviceNames = SDL_TRUE; impl->HasCaptureSupport = SDL_TRUE; - impl->SupportsNonPow2Samples = SDL_TRUE; return SDL_TRUE; /* this audio target is available. */ } diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c index ba3a9c5352..d91d423999 100644 --- a/src/audio/wasapi/SDL_wasapi.c +++ b/src/audio/wasapi/SDL_wasapi.c @@ -598,7 +598,6 @@ static SDL_bool WASAPI_Init(SDL_AudioDriverImpl *impl) impl->Deinitialize = WASAPI_Deinitialize; impl->GetDefaultAudioInfo = WASAPI_GetDefaultAudioInfo; impl->HasCaptureSupport = SDL_TRUE; - impl->SupportsNonPow2Samples = SDL_TRUE; return SDL_TRUE; /* this audio target is available. */ } From 4deb2970c926fff2986081cbfa6baae5c1f5ca55 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 3 Jul 2023 23:49:45 -0400 Subject: [PATCH 061/138] alsa: Renamed `_this` to `device` --- src/audio/alsa/SDL_alsa_audio.c | 140 ++++++++++++++++---------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c index 8ab532b4c1..53f392dbf0 100644 --- a/src/audio/alsa/SDL_alsa_audio.c +++ b/src/audio/alsa/SDL_alsa_audio.c @@ -225,20 +225,20 @@ static const char *get_audio_device(void *handle, const int channels) } /* This function waits until it is possible to write a full sound buffer */ -static void ALSA_WaitDevice(SDL_AudioDevice *_this) +static void ALSA_WaitDevice(SDL_AudioDevice *device) { #if SDL_ALSA_NON_BLOCKING - const snd_pcm_sframes_t needed = (snd_pcm_sframes_t)_this->spec.samples; - while (SDL_AtomicGet(&_this->enabled)) { - const snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(_this->hidden->pcm_handle); + const snd_pcm_sframes_t needed = (snd_pcm_sframes_t)device->spec.samples; + while (SDL_AtomicGet(&device->enabled)) { + const snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle); if ((rc < 0) && (rc != -EAGAIN)) { /* Hmm, not much we can do - abort */ fprintf(stderr, "ALSA snd_pcm_avail failed (unrecoverable): %s\n", ALSA_snd_strerror(rc)); - SDL_OpenedAudioDeviceDisconnected(_this); + SDL_OpenedAudioDeviceDisconnected(device); return; } else if (rc < needed) { - const Uint32 delay = ((needed - (SDL_max(rc, 0))) * 1000) / _this->spec.freq; + const Uint32 delay = ((needed - (SDL_max(rc, 0))) * 1000) / device->spec.freq; SDL_Delay(SDL_max(delay, 10)); } else { break; /* ready to go! */ @@ -311,15 +311,15 @@ CHANNEL_SWIZZLE(SWIZ8) #undef SWIZ8 /* - * Called right before feeding _this->hidden->mixbuf to the hardware. Swizzle + * Called right before feeding device->hidden->mixbuf to the hardware. Swizzle * channels from Windows/Mac order to the format alsalib will want. */ -static void swizzle_alsa_channels(SDL_AudioDevice *_this, void *buffer, Uint32 bufferlen) +static void swizzle_alsa_channels(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen) { - switch (_this->spec.channels) { + switch (device->spec.channels) { #define CHANSWIZ(chans) \ case chans: \ - switch ((_this->spec.format & (0xFF))) { \ + switch ((device->spec.format & (0xFF))) { \ case 8: \ swizzle_alsa_channels_##chans##_Uint8(buffer, bufferlen); \ break; \ @@ -348,22 +348,22 @@ static void swizzle_alsa_channels(SDL_AudioDevice *_this, void *buffer, Uint32 b #ifdef SND_CHMAP_API_VERSION /* Some devices have the right channel map, no swizzling necessary */ -static void no_swizzle(SDL_AudioDevice *_this, void *buffer, Uint32 bufferlen) +static void no_swizzle(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen) { } #endif /* SND_CHMAP_API_VERSION */ -static void ALSA_PlayDevice(SDL_AudioDevice *_this) +static void ALSA_PlayDevice(SDL_AudioDevice *device) { - const Uint8 *sample_buf = (const Uint8 *)_this->hidden->mixbuf; - const int frame_size = ((SDL_AUDIO_BITSIZE(_this->spec.format)) / 8) * - _this->spec.channels; - snd_pcm_uframes_t frames_left = ((snd_pcm_uframes_t)_this->spec.samples); + const Uint8 *sample_buf = (const Uint8 *)device->hidden->mixbuf; + const int frame_size = ((SDL_AUDIO_BITSIZE(device->spec.format)) / 8) * + device->spec.channels; + snd_pcm_uframes_t frames_left = ((snd_pcm_uframes_t)device->spec.samples); - _this->hidden->swizzle_func(_this, _this->hidden->mixbuf, frames_left); + device->hidden->swizzle_func(device, device->hidden->mixbuf, frames_left); - while (frames_left > 0 && SDL_AtomicGet(&_this->enabled)) { - int status = ALSA_snd_pcm_writei(_this->hidden->pcm_handle, + while (frames_left > 0 && SDL_AtomicGet(&device->enabled)) { + int status = ALSA_snd_pcm_writei(device->hidden->pcm_handle, sample_buf, frames_left); if (status < 0) { @@ -373,20 +373,20 @@ static void ALSA_PlayDevice(SDL_AudioDevice *_this) SDL_Delay(1); continue; } - status = ALSA_snd_pcm_recover(_this->hidden->pcm_handle, status, 0); + status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, status, 0); if (status < 0) { /* Hmm, not much we can do - abort */ SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s\n", ALSA_snd_strerror(status)); - SDL_OpenedAudioDeviceDisconnected(_this); + SDL_OpenedAudioDeviceDisconnected(device); return; } continue; } else if (status == 0) { /* No frames were written (no available space in pcm device). Allow other threads to catch up. */ - Uint32 delay = (frames_left / 2 * 1000) / _this->spec.freq; + Uint32 delay = (frames_left / 2 * 1000) / device->spec.freq; SDL_Delay(delay); } @@ -395,34 +395,34 @@ static void ALSA_PlayDevice(SDL_AudioDevice *_this) } } -static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device) { - return _this->hidden->mixbuf; + return device->hidden->mixbuf; } -static int ALSA_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static int ALSA_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { Uint8 *sample_buf = (Uint8 *)buffer; - const int frame_size = ((SDL_AUDIO_BITSIZE(_this->spec.format)) / 8) * - _this->spec.channels; + const int frame_size = ((SDL_AUDIO_BITSIZE(device->spec.format)) / 8) * + device->spec.channels; const int total_frames = buflen / frame_size; snd_pcm_uframes_t frames_left = total_frames; snd_pcm_uframes_t wait_time = frame_size / 2; SDL_assert((buflen % frame_size) == 0); - while (frames_left > 0 && SDL_AtomicGet(&_this->enabled)) { + while (frames_left > 0 && SDL_AtomicGet(&device->enabled)) { int status; - status = ALSA_snd_pcm_readi(_this->hidden->pcm_handle, + status = ALSA_snd_pcm_readi(device->hidden->pcm_handle, sample_buf, frames_left); if (status == -EAGAIN) { - ALSA_snd_pcm_wait(_this->hidden->pcm_handle, wait_time); + ALSA_snd_pcm_wait(device->hidden->pcm_handle, wait_time); status = 0; } else if (status < 0) { /*printf("ALSA: capture error %d\n", status);*/ - status = ALSA_snd_pcm_recover(_this->hidden->pcm_handle, status, 0); + status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, status, 0); if (status < 0) { /* Hmm, not much we can do - abort */ SDL_LogError(SDL_LOG_CATEGORY_AUDIO, @@ -438,32 +438,32 @@ static int ALSA_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int bufl frames_left -= status; } - _this->hidden->swizzle_func(_this, buffer, total_frames - frames_left); + device->hidden->swizzle_func(device, buffer, total_frames - frames_left); return (total_frames - frames_left) * frame_size; } -static void ALSA_FlushCapture(SDL_AudioDevice *_this) +static void ALSA_FlushCapture(SDL_AudioDevice *device) { - ALSA_snd_pcm_reset(_this->hidden->pcm_handle); + ALSA_snd_pcm_reset(device->hidden->pcm_handle); } -static void ALSA_CloseDevice(SDL_AudioDevice *_this) +static void ALSA_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden->pcm_handle) { + if (device->hidden->pcm_handle) { /* Wait for the submitted audio to drain ALSA_snd_pcm_drop() can hang, so don't use that. */ - Uint32 delay = ((_this->spec.samples * 1000) / _this->spec.freq) * 2; + Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq) * 2; SDL_Delay(delay); - ALSA_snd_pcm_close(_this->hidden->pcm_handle); + ALSA_snd_pcm_close(device->hidden->pcm_handle); } - SDL_free(_this->hidden->mixbuf); - SDL_free(_this->hidden); + SDL_free(device->hidden->mixbuf); + SDL_free(device->hidden); } -static int ALSA_set_buffer_size(SDL_AudioDevice *_this, snd_pcm_hw_params_t *params) +static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *params) { int status; snd_pcm_hw_params_t *hwparams; @@ -475,9 +475,9 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *_this, snd_pcm_hw_params_t *par ALSA_snd_pcm_hw_params_copy(hwparams, params); /* Attempt to match the period size to the requested buffer size */ - persize = _this->spec.samples; + persize = device->spec.samples; status = ALSA_snd_pcm_hw_params_set_period_size_near( - _this->hidden->pcm_handle, hwparams, &persize, NULL); + device->hidden->pcm_handle, hwparams, &persize, NULL); if (status < 0) { return -1; } @@ -485,24 +485,24 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *_this, snd_pcm_hw_params_t *par /* Need to at least double buffer */ periods = 2; status = ALSA_snd_pcm_hw_params_set_periods_min( - _this->hidden->pcm_handle, hwparams, &periods, NULL); + device->hidden->pcm_handle, hwparams, &periods, NULL); if (status < 0) { return -1; } status = ALSA_snd_pcm_hw_params_set_periods_first( - _this->hidden->pcm_handle, hwparams, &periods, NULL); + device->hidden->pcm_handle, hwparams, &periods, NULL); if (status < 0) { return -1; } /* "set" the hardware with the desired parameters */ - status = ALSA_snd_pcm_hw_params(_this->hidden->pcm_handle, hwparams); + status = ALSA_snd_pcm_hw_params(device->hidden->pcm_handle, hwparams); if (status < 0) { return -1; } - _this->spec.samples = persize; + device->spec.samples = persize; /* This is useful for debugging */ if (SDL_getenv("SDL_AUDIO_ALSA_DEBUG")) { @@ -518,10 +518,10 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *_this, snd_pcm_hw_params_t *par return 0; } -static int ALSA_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname) { int status = 0; - SDL_bool iscapture = _this->iscapture; + SDL_bool iscapture = device->iscapture; snd_pcm_t *pcm_handle = NULL; snd_pcm_hw_params_t *hwparams = NULL; snd_pcm_sw_params_t *swparams = NULL; @@ -536,16 +536,16 @@ static int ALSA_OpenDevice(SDL_AudioDevice *_this, const char *devname) #endif /* Initialize all variables that we clean on shutdown */ - _this->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + device->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(_this->hidden); + SDL_zerop(device->hidden); /* Open the audio device */ /* Name of device should depend on # channels in spec */ status = ALSA_snd_pcm_open(&pcm_handle, - get_audio_device(_this->handle, _this->spec.channels), + get_audio_device(device->handle, device->spec.channels), iscapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); @@ -553,7 +553,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *_this, const char *devname) return SDL_SetError("ALSA: Couldn't open audio device: %s", ALSA_snd_strerror(status)); } - _this->hidden->pcm_handle = pcm_handle; + device->hidden->pcm_handle = pcm_handle; /* Figure out what the hardware is capable of */ snd_pcm_hw_params_alloca(&hwparams); @@ -570,7 +570,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *_this, const char *devname) } /* Try for a closest match on audio format */ - closefmts = SDL_ClosestAudioFormats(_this->spec.format); + closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { switch (test_format) { case SDL_AUDIO_U8: @@ -607,19 +607,19 @@ static int ALSA_OpenDevice(SDL_AudioDevice *_this, const char *devname) if (!test_format) { return SDL_SetError("%s: Unsupported audio format", "alsa"); } - _this->spec.format = test_format; + device->spec.format = test_format; /* Validate number of channels and determine if swizzling is necessary * Assume original swizzling, until proven otherwise. */ - _this->hidden->swizzle_func = swizzle_alsa_channels; + device->hidden->swizzle_func = swizzle_alsa_channels; #ifdef SND_CHMAP_API_VERSION chmap = ALSA_snd_pcm_get_chmap(pcm_handle); if (chmap) { if (ALSA_snd_pcm_chmap_print(chmap, sizeof(chmap_str), chmap_str) > 0) { if (SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0 || SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0) { - _this->hidden->swizzle_func = no_swizzle; + device->hidden->swizzle_func = no_swizzle; } } free(chmap); /* This should NOT be SDL_free() */ @@ -628,27 +628,27 @@ static int ALSA_OpenDevice(SDL_AudioDevice *_this, const char *devname) /* Set the number of channels */ status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams, - _this->spec.channels); - channels = _this->spec.channels; + device->spec.channels); + channels = device->spec.channels; if (status < 0) { status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels); if (status < 0) { return SDL_SetError("ALSA: Couldn't set audio channels"); } - _this->spec.channels = channels; + device->spec.channels = channels; } /* Set the audio rate */ - rate = _this->spec.freq; + rate = device->spec.freq; status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &rate, NULL); if (status < 0) { return SDL_SetError("ALSA: Couldn't set audio frequency: %s", ALSA_snd_strerror(status)); } - _this->spec.freq = rate; + device->spec.freq = rate; /* Set the buffer size, in samples */ - status = ALSA_set_buffer_size(_this, hwparams); + status = ALSA_set_buffer_size(device, hwparams); if (status < 0) { return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status)); } @@ -659,7 +659,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *_this, const char *devname) if (status < 0) { return SDL_SetError("ALSA: Couldn't get software config: %s", ALSA_snd_strerror(status)); } - status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, _this->spec.samples); + status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, device->spec.samples); if (status < 0) { return SDL_SetError("Couldn't set minimum available samples: %s", ALSA_snd_strerror(status)); } @@ -674,16 +674,16 @@ static int ALSA_OpenDevice(SDL_AudioDevice *_this, const char *devname) } /* Calculate the final parameters for this audio specification */ - SDL_CalculateAudioSpec(&_this->spec); + SDL_CalculateAudioSpec(&device->spec); /* Allocate mixing buffer */ if (!iscapture) { - _this->hidden->mixlen = _this->spec.size; - _this->hidden->mixbuf = (Uint8 *)SDL_malloc(_this->hidden->mixlen); - if (_this->hidden->mixbuf == NULL) { + device->hidden->mixlen = device->spec.size; + device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->hidden->mixlen); + if (device->hidden->mixbuf == NULL) { return SDL_OutOfMemory(); } - SDL_memset(_this->hidden->mixbuf, _this->spec.silence, _this->hidden->mixlen); + SDL_memset(device->hidden->mixbuf, device->spec.silence, device->hidden->mixlen); } #if !SDL_ALSA_NON_BLOCKING From f94ffd609206c945440dddd9a7e3e2409049505e Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 4 Jul 2023 17:27:09 -0400 Subject: [PATCH 062/138] audio: Fixed logic error --- src/audio/SDL_audio.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 87c4df8665..342024b791 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -475,7 +475,7 @@ static SDL_AudioDeviceID GetFirstAddedAudioDeviceID(const SDL_bool iscapture) // (these are pushed to the front of the linked list as added, so the first device added is last in the list.) SDL_LockRWLockForReading(current_audio.device_list_lock); SDL_AudioDevice *last = NULL; - for (SDL_AudioDevice *i = current_audio.output_devices; i != NULL; i = i->next) { + for (SDL_AudioDevice *i = iscapture ? current_audio.capture_devices : current_audio.output_devices; i != NULL; i = i->next) { last = i; } const SDL_AudioDeviceID retval = last ? last->instance_id : 0; @@ -599,8 +599,8 @@ int SDL_InitAudio(const char *driver_name) if (!current_audio.default_output_device_id) { current_audio.default_output_device_id = GetFirstAddedAudioDeviceID(/*iscapture=*/SDL_FALSE); } - if (!current_audio.default_capture_device_id && (current_audio.capture_devices != NULL)) { - current_audio.default_output_device_id = GetFirstAddedAudioDeviceID(/*iscapture=*/SDL_TRUE); + if (!current_audio.default_capture_device_id) { + current_audio.default_capture_device_id = GetFirstAddedAudioDeviceID(/*iscapture=*/SDL_TRUE); } return 0; From 0999a090a7d5b51e2cf950f5819045f84a159cdf Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 4 Jul 2023 17:27:17 -0400 Subject: [PATCH 063/138] audio: More tweaking of `device->thread_alive` --- src/audio/SDL_audio.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 342024b791..aea09ff807 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -668,6 +668,7 @@ void SDL_AudioThreadFinalize(SDL_AudioDevice *device) if (device->thread) { SDL_DetachThread(device->thread); // no one is waiting for us, just detach ourselves. device->thread = NULL; + SDL_AtomicSet(&device->thread_alive, 0); } DestroyPhysicalAudioDevice(device); } @@ -1085,6 +1086,7 @@ static void ClosePhysicalAudioDevice(SDL_AudioDevice *device) SDL_WaitThread(device->thread, NULL); device->thread = NULL; } + SDL_AtomicSet(&device->thread_alive, 0); } if (device->is_opened) { From 409b544505eafa8444ff5328493299f36abf687c Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 4 Jul 2023 17:28:04 -0400 Subject: [PATCH 064/138] alsa: Updated for new SDL3 audio API --- CMakeLists.txt | 1 - src/audio/alsa/SDL_alsa_audio.c | 359 ++++++++++++++++---------------- src/audio/alsa/SDL_alsa_audio.h | 1 - 3 files changed, 185 insertions(+), 176 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 56646f9b73..b745b1bbaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -354,7 +354,6 @@ set_option(SDL_CLANG_TIDY "Run clang-tidy static analysis" OFF) set(SDL_VENDOR_INFO "" CACHE STRING "Vendor name and/or version to add to SDL_REVISION") set(SDL_OSS OFF) -set(SDL_ALSA OFF) set(SDL_JACK OFF) set(SDL_SNDIO OFF) diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c index 53f392dbf0..d5bb90cf19 100644 --- a/src/audio/alsa/SDL_alsa_audio.c +++ b/src/audio/alsa/SDL_alsa_audio.c @@ -20,6 +20,8 @@ */ #include "SDL_internal.h" +// !!! FIXME: Clean out the fprintf and printf calls, replace with SDL_Log + #ifdef SDL_AUDIO_DRIVER_ALSA #ifndef SDL_ALSA_NON_BLOCKING @@ -45,6 +47,7 @@ static int (*ALSA_snd_pcm_open)(snd_pcm_t **, const char *, snd_pcm_stream_t, int); static int (*ALSA_snd_pcm_close)(snd_pcm_t *pcm); +static int (*ALSA_snd_pcm_start)(snd_pcm_t *pcm); static snd_pcm_sframes_t (*ALSA_snd_pcm_writei)(snd_pcm_t *, const void *, snd_pcm_uframes_t); static snd_pcm_sframes_t (*ALSA_snd_pcm_readi)(snd_pcm_t *, void *, snd_pcm_uframes_t); static int (*ALSA_snd_pcm_recover)(snd_pcm_t *, int, int); @@ -115,6 +118,7 @@ static int load_alsa_syms(void) { SDL_ALSA_SYM(snd_pcm_open); SDL_ALSA_SYM(snd_pcm_close); + SDL_ALSA_SYM(snd_pcm_start); SDL_ALSA_SYM(snd_pcm_writei); SDL_ALSA_SYM(snd_pcm_readi); SDL_ALSA_SYM(snd_pcm_recover); @@ -203,48 +207,21 @@ static int LoadALSALibrary(void) static const char *get_audio_device(void *handle, const int channels) { - const char *device; + SDL_assert(handle != NULL); // SDL2 used NULL to mean "default" but that's not true in SDL3. - if (handle != NULL) { - return (const char *)handle; - } - - /* !!! FIXME: we also check "SDL_AUDIO_DEVICE_NAME" at the higher level. */ - device = SDL_getenv("AUDIODEV"); /* Is there a standard variable name? */ - if (device != NULL) { - return device; - } - - if (channels == 6) { - return "plug:surround51"; - } else if (channels == 4) { - return "plug:surround40"; - } - - return "default"; -} - -/* This function waits until it is possible to write a full sound buffer */ -static void ALSA_WaitDevice(SDL_AudioDevice *device) -{ -#if SDL_ALSA_NON_BLOCKING - const snd_pcm_sframes_t needed = (snd_pcm_sframes_t)device->spec.samples; - while (SDL_AtomicGet(&device->enabled)) { - const snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle); - if ((rc < 0) && (rc != -EAGAIN)) { - /* Hmm, not much we can do - abort */ - fprintf(stderr, "ALSA snd_pcm_avail failed (unrecoverable): %s\n", - ALSA_snd_strerror(rc)); - SDL_OpenedAudioDeviceDisconnected(device); - return; - } else if (rc < needed) { - const Uint32 delay = ((needed - (SDL_max(rc, 0))) * 1000) / device->spec.freq; - SDL_Delay(SDL_max(delay, 10)); - } else { - break; /* ready to go! */ + if (SDL_strcmp((const char *) handle, "default") == 0) { + const char *device = SDL_getenv("AUDIODEV"); /* Is there a standard variable name? */ + if (device != NULL) { + return device; + } else if (channels == 6) { + return "plug:surround51"; + } else if (channels == 4) { + return "plug:surround40"; } + return "default"; } -#endif + + return (const char *)handle; } /* !!! FIXME: is there a channel swizzler in alsalib instead? */ @@ -353,16 +330,38 @@ static void no_swizzle(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen) } #endif /* SND_CHMAP_API_VERSION */ -static void ALSA_PlayDevice(SDL_AudioDevice *device) +/* This function waits until it is possible to write a full sound buffer */ +static void ALSA_WaitDevice(SDL_AudioDevice *device) { - const Uint8 *sample_buf = (const Uint8 *)device->hidden->mixbuf; + const snd_pcm_sframes_t needed = (snd_pcm_sframes_t)device->sample_frames; + while (!SDL_AtomicGet(&device->shutdown)) { + const snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle); + if ((rc < 0) && (rc != -EAGAIN)) { + /* Hmm, not much we can do - abort */ + fprintf(stderr, "ALSA snd_pcm_avail failed (unrecoverable): %s\n", + ALSA_snd_strerror(rc)); + SDL_AudioDeviceDisconnected(device); + return; + } else if (rc < needed) { + const Uint32 delay = ((needed - (SDL_max(rc, 0))) * 1000) / device->spec.freq; + SDL_Delay(SDL_max(delay, 10)); + } else { + break; /* ready to go! */ + } + } +} + +static void ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) +{ + SDL_assert(buffer == device->hidden->mixbuf); + Uint8 *sample_buf = device->hidden->mixbuf; const int frame_size = ((SDL_AUDIO_BITSIZE(device->spec.format)) / 8) * device->spec.channels; - snd_pcm_uframes_t frames_left = ((snd_pcm_uframes_t)device->spec.samples); + snd_pcm_uframes_t frames_left = (snd_pcm_uframes_t) (buflen / frame_size); - device->hidden->swizzle_func(device, device->hidden->mixbuf, frames_left); + device->hidden->swizzle_func(device, sample_buf, frames_left); - while (frames_left > 0 && SDL_AtomicGet(&device->enabled)) { + while ((frames_left > 0) && !SDL_AtomicGet(&device->shutdown)) { int status = ALSA_snd_pcm_writei(device->hidden->pcm_handle, sample_buf, frames_left); @@ -377,17 +376,16 @@ static void ALSA_PlayDevice(SDL_AudioDevice *device) if (status < 0) { /* Hmm, not much we can do - abort */ SDL_LogError(SDL_LOG_CATEGORY_AUDIO, - "ALSA write failed (unrecoverable): %s\n", + "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(status)); - SDL_OpenedAudioDeviceDisconnected(device); + SDL_AudioDeviceDisconnected(device); return; } continue; } else if (status == 0) { /* No frames were written (no available space in pcm device). Allow other threads to catch up. */ - Uint32 delay = (frames_left / 2 * 1000) / device->spec.freq; - SDL_Delay(delay); + SDL_Delay((frames_left / 2 * 1000) / device->spec.freq); } sample_buf += status * frame_size; @@ -395,7 +393,7 @@ static void ALSA_PlayDevice(SDL_AudioDevice *device) } } -static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device) +static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { return device->hidden->mixbuf; } @@ -411,11 +409,9 @@ static int ALSA_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buf SDL_assert((buflen % frame_size) == 0); - while (frames_left > 0 && SDL_AtomicGet(&device->enabled)) { - int status; - - status = ALSA_snd_pcm_readi(device->hidden->pcm_handle, - sample_buf, frames_left); + while ((frames_left > 0) && !SDL_AtomicGet(&device->shutdown)) { + int status = ALSA_snd_pcm_readi(device->hidden->pcm_handle, + sample_buf, frames_left); if (status == -EAGAIN) { ALSA_snd_pcm_wait(device->hidden->pcm_handle, wait_time); @@ -450,17 +446,17 @@ static void ALSA_FlushCapture(SDL_AudioDevice *device) static void ALSA_CloseDevice(SDL_AudioDevice *device) { - if (device->hidden->pcm_handle) { - /* Wait for the submitted audio to drain - ALSA_snd_pcm_drop() can hang, so don't use that. - */ - Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq) * 2; - SDL_Delay(delay); - - ALSA_snd_pcm_close(device->hidden->pcm_handle); + if (device->hidden) { + if (device->hidden->pcm_handle) { + /* Wait for the submitted audio to drain + ALSA_snd_pcm_drop() can hang, so don't use that. + */ + SDL_Delay(((device->sample_frames * 1000) / device->spec.freq) * 2); + ALSA_snd_pcm_close(device->hidden->pcm_handle); + } + SDL_free(device->hidden->mixbuf); + SDL_free(device->hidden); } - SDL_free(device->hidden->mixbuf); - SDL_free(device->hidden); } static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *params) @@ -475,7 +471,7 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *pa ALSA_snd_pcm_hw_params_copy(hwparams, params); /* Attempt to match the period size to the requested buffer size */ - persize = device->spec.samples; + persize = device->sample_frames; status = ALSA_snd_pcm_hw_params_set_period_size_near( device->hidden->pcm_handle, hwparams, &persize, NULL); if (status < 0) { @@ -502,7 +498,7 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *pa return -1; } - device->spec.samples = persize; + device->sample_frames = persize; /* This is useful for debugging */ if (SDL_getenv("SDL_AUDIO_ALSA_DEBUG")) { @@ -518,32 +514,20 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *pa return 0; } -static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname) +static int ALSA_OpenDevice(SDL_AudioDevice *device) { + const SDL_bool iscapture = device->iscapture; int status = 0; - SDL_bool iscapture = device->iscapture; - snd_pcm_t *pcm_handle = NULL; - snd_pcm_hw_params_t *hwparams = NULL; - snd_pcm_sw_params_t *swparams = NULL; - snd_pcm_format_t format = 0; - SDL_AudioFormat test_format = 0; - const SDL_AudioFormat *closefmts; - unsigned int rate = 0; - unsigned int channels = 0; -#ifdef SND_CHMAP_API_VERSION - snd_pcm_chmap_t *chmap; - char chmap_str[64]; -#endif /* Initialize all variables that we clean on shutdown */ - device->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*device->hidden)); + device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(device->hidden); /* Open the audio device */ /* Name of device should depend on # channels in spec */ + snd_pcm_t *pcm_handle = NULL; status = ALSA_snd_pcm_open(&pcm_handle, get_audio_device(device->handle, device->spec.channels), iscapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, @@ -556,6 +540,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname) device->hidden->pcm_handle = pcm_handle; /* Figure out what the hardware is capable of */ + snd_pcm_hw_params_t *hwparams = NULL; snd_pcm_hw_params_alloca(&hwparams); status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams); if (status < 0) { @@ -570,7 +555,9 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname) } /* Try for a closest match on audio format */ - closefmts = SDL_ClosestAudioFormats(device->spec.format); + snd_pcm_format_t format = 0; + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); + SDL_AudioFormat test_format; while ((test_format = *(closefmts++)) != 0) { switch (test_format) { case SDL_AUDIO_U8: @@ -605,7 +592,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname) } } if (!test_format) { - return SDL_SetError("%s: Unsupported audio format", "alsa"); + return SDL_SetError("ALSA: Unsupported audio format"); } device->spec.format = test_format; @@ -614,8 +601,9 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname) */ device->hidden->swizzle_func = swizzle_alsa_channels; #ifdef SND_CHMAP_API_VERSION - chmap = ALSA_snd_pcm_get_chmap(pcm_handle); + snd_pcm_chmap_t *chmap = ALSA_snd_pcm_get_chmap(pcm_handle); if (chmap) { + char chmap_str[64]; if (ALSA_snd_pcm_chmap_print(chmap, sizeof(chmap_str), chmap_str) > 0) { if (SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0 || SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0) { @@ -629,7 +617,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname) /* Set the number of channels */ status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams, device->spec.channels); - channels = device->spec.channels; + unsigned int channels = device->spec.channels; if (status < 0) { status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels); if (status < 0) { @@ -639,7 +627,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname) } /* Set the audio rate */ - rate = device->spec.freq; + unsigned int rate = device->spec.freq; status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &rate, NULL); if (status < 0) { @@ -654,12 +642,13 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname) } /* Set the software parameters */ + snd_pcm_sw_params_t *swparams = NULL; snd_pcm_sw_params_alloca(&swparams); status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams); if (status < 0) { return SDL_SetError("ALSA: Couldn't get software config: %s", ALSA_snd_strerror(status)); } - status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, device->spec.samples); + status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, device->sample_frames); if (status < 0) { return SDL_SetError("Couldn't set minimum available samples: %s", ALSA_snd_strerror(status)); } @@ -673,17 +662,16 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname) return SDL_SetError("Couldn't set software audio parameters: %s", ALSA_snd_strerror(status)); } - /* Calculate the final parameters for this audio specification */ - SDL_CalculateAudioSpec(&device->spec); + // Calculate the final parameters for this audio specification + SDL_UpdatedAudioDeviceFormat(device); - /* Allocate mixing buffer */ + // Allocate mixing buffer if (!iscapture) { - device->hidden->mixlen = device->spec.size; - device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->hidden->mixlen); + device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); if (device->hidden->mixbuf == NULL) { return SDL_OutOfMemory(); } - SDL_memset(device->hidden->mixbuf, device->spec.silence, device->hidden->mixlen); + SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); } #if !SDL_ALSA_NON_BLOCKING @@ -692,6 +680,8 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname) } #endif + ALSA_snd_pcm_start(pcm_handle); + /* We're ready to rock and roll. :-) */ return 0; } @@ -703,7 +693,7 @@ typedef struct ALSA_Device struct ALSA_Device *next; } ALSA_Device; -static void add_device(const int iscapture, const char *name, void *hint, ALSA_Device **pSeen) +static void add_device(const SDL_bool iscapture, const char *name, void *hint, ALSA_Device **pSeen) { ALSA_Device *dev = SDL_malloc(sizeof(ALSA_Device)); char *desc; @@ -765,21 +755,17 @@ static void add_device(const int iscapture, const char *name, void *hint, ALSA_D static ALSA_Device *hotplug_devices = NULL; -static void ALSA_HotplugIteration(void) +static void ALSA_HotplugIteration(SDL_bool *has_default_output, SDL_bool *has_default_capture) { void **hints = NULL; - ALSA_Device *dev; - ALSA_Device *unseen; - ALSA_Device *seen; - ALSA_Device *next; - ALSA_Device *prev; + ALSA_Device *unseen = NULL; + ALSA_Device *seen = NULL; if (ALSA_snd_device_name_hint(-1, "pcm", &hints) == 0) { - int i, j; const char *match = NULL; int bestmatch = 0xFFFF; + int has_default = -1; size_t match_len = 0; - int defaultdev = -1; static const char *const prefixes[] = { "hw:", "sysdefault:", "default:", NULL }; @@ -791,92 +777,100 @@ static void ALSA_HotplugIteration(void) actual hardware. It could be prefixed with "hw:" or "default:" or "sysdefault:" and maybe others. Go through the list and see if we can find a preferred prefix for the system. */ - for (i = 0; hints[i]; i++) { + for (int i = 0; hints[i]; i++) { char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME"); if (name == NULL) { continue; } - /* full name, not a prefix */ - if ((defaultdev == -1) && (SDL_strcmp(name, "default") == 0)) { - defaultdev = i; - } - - for (j = 0; prefixes[j]; j++) { - const char *prefix = prefixes[j]; - const size_t prefixlen = SDL_strlen(prefix); - if (SDL_strncmp(name, prefix, prefixlen) == 0) { - if (j < bestmatch) { - bestmatch = j; - match = prefix; - match_len = prefixlen; + if (SDL_strcmp(name, "default") == 0) { + if (has_default < 0) { + has_default = i; + } + } else { + for (int j = 0; prefixes[j]; j++) { + const char *prefix = prefixes[j]; + const size_t prefixlen = SDL_strlen(prefix); + if (SDL_strncmp(name, prefix, prefixlen) == 0) { + if (j < bestmatch) { + bestmatch = j; + match = prefix; + match_len = prefixlen; + } } } - } - free(name); /* This should NOT be SDL_free() */ + free(name); /* This should NOT be SDL_free() */ + } } /* look through the list of device names to find matches */ - for (i = 0; hints[i]; i++) { - char *name; - - /* if we didn't find a device name prefix we like at all... */ - if ((match == NULL) && (defaultdev != i)) { - continue; /* ...skip anything that isn't the default device. */ - } - - name = ALSA_snd_device_name_get_hint(hints[i], "NAME"); - if (name == NULL) { - continue; - } - - /* only want physical hardware interfaces */ - if (match == NULL || (SDL_strncmp(name, match, match_len) == 0)) { - char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID"); - const SDL_bool isoutput = (ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0); - const SDL_bool isinput = (ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0); - SDL_bool have_output = SDL_FALSE; - SDL_bool have_input = SDL_FALSE; - - free(ioid); /* This should NOT be SDL_free() */ - - if (!isoutput && !isinput) { - free(name); /* This should NOT be SDL_free() */ + if (match || (has_default >= 0)) { // did we find a device name prefix we like at all...? + for (int i = 0; hints[i]; i++) { + char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME"); + if (name == NULL) { continue; } - prev = NULL; - for (dev = unseen; dev; dev = next) { - next = dev->next; - if ((SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->iscapture) || ((isoutput) && !dev->iscapture))) { - if (prev) { - prev->next = next; + // only want physical hardware interfaces + const SDL_bool is_default = (has_default == i) ? SDL_TRUE : SDL_FALSE; + if (is_default || SDL_strncmp(name, match, match_len) == 0) { + char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID"); + const SDL_bool isoutput = (ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0); + const SDL_bool isinput = (ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0); + SDL_bool have_output = SDL_FALSE; + SDL_bool have_input = SDL_FALSE; + + free(ioid); + + if (!isoutput && !isinput) { + free(name); + continue; + } + + if (is_default) { + if (has_default_output && isoutput) { + *has_default_output = SDL_TRUE; + } else if (has_default_capture && isinput) { + *has_default_capture = SDL_TRUE; + } + free(name); + continue; + } + + ALSA_Device *prev = NULL; + ALSA_Device *next; + for (ALSA_Device *dev = unseen; dev; dev = next) { + next = dev->next; + if ((SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->iscapture) || ((isoutput) && !dev->iscapture))) { + if (prev) { + prev->next = next; + } else { + unseen = next; + } + dev->next = seen; + seen = dev; + if (isinput) { + have_input = SDL_TRUE; + } + if (isoutput) { + have_output = SDL_TRUE; + } } else { - unseen = next; + prev = dev; } - dev->next = seen; - seen = dev; - if (isinput) { - have_input = SDL_TRUE; - } - if (isoutput) { - have_output = SDL_TRUE; - } - } else { - prev = dev; + } + + if (isinput && !have_input) { + add_device(SDL_TRUE, name, hints[i], &seen); + } + if (isoutput && !have_output) { + add_device(SDL_FALSE, name, hints[i], &seen); } } - if (isinput && !have_input) { - add_device(SDL_TRUE, name, hints[i], &seen); - } - if (isoutput && !have_output) { - add_device(SDL_FALSE, name, hints[i], &seen); - } + free(name); /* This should NOT be SDL_free() */ } - - free(name); /* This should NOT be SDL_free() */ } ALSA_snd_device_name_free_hint(hints); @@ -884,10 +878,17 @@ static void ALSA_HotplugIteration(void) hotplug_devices = seen; /* now we have a known-good list of attached devices. */ /* report anything still in unseen as removed. */ - for (dev = unseen; dev; dev = next) { + ALSA_Device *next = NULL; + for (ALSA_Device *dev = unseen; dev; dev = next) { /*printf("ALSA: removing usb %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/ next = dev->next; - SDL_RemoveAudioDevice(dev->iscapture, dev->name); + + SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle(dev->name); + if (device) { + SDL_UnlockMutex(device->lock); // AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. + SDL_AudioDeviceDisconnected(device); + } + SDL_free(dev->name); SDL_free(dev); } @@ -909,16 +910,25 @@ static int SDLCALL ALSA_HotplugThread(void *arg) SDL_Delay(100); } - ALSA_HotplugIteration(); /* run the check. */ + ALSA_HotplugIteration(NULL, NULL); /* run the check. */ } return 0; } #endif -static void ALSA_DetectDevices(void) +static void ALSA_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { - ALSA_HotplugIteration(); /* run once now before a thread continues to check. */ + // ALSA doesn't have a concept of a changeable default device, afaik, so we expose a generic default + // device here. It's the best we can do at this level. + SDL_bool has_default_output = SDL_FALSE, has_default_capture = SDL_FALSE; + ALSA_HotplugIteration(&has_default_output, &has_default_capture); // run once now before a thread continues to check. */ + if (has_default_output) { + *default_output = SDL_AddAudioDevice(/*iscapture=*/SDL_FALSE, "ALSA default output device", NULL, SDL_strdup("default")); + } + if (has_default_capture) { + *default_capture = SDL_AddAudioDevice(/*iscapture=*/SDL_TRUE, "ALSA default capture device", NULL, SDL_strdup("default")); + } #if SDL_ALSA_HOTPLUG_THREAD SDL_AtomicSet(&ALSA_hotplug_shutdown, 0); @@ -966,6 +976,7 @@ static SDL_bool ALSA_Init(SDL_AudioDriverImpl *impl) impl->PlayDevice = ALSA_PlayDevice; impl->CloseDevice = ALSA_CloseDevice; impl->Deinitialize = ALSA_Deinitialize; + impl->WaitCaptureDevice = ALSA_WaitDevice; impl->CaptureFromDevice = ALSA_CaptureFromDevice; impl->FlushCapture = ALSA_FlushCapture; diff --git a/src/audio/alsa/SDL_alsa_audio.h b/src/audio/alsa/SDL_alsa_audio.h index b66318d751..ef7ce1bb5c 100644 --- a/src/audio/alsa/SDL_alsa_audio.h +++ b/src/audio/alsa/SDL_alsa_audio.h @@ -34,7 +34,6 @@ struct SDL_PrivateAudioData /* Raw mixing buffer */ Uint8 *mixbuf; - int mixlen; /* swizzle function */ void (*swizzle_func)(SDL_AudioDevice *_this, void *buffer, Uint32 bufferlen); From 65d296ef1a21353f5f1d06f7c191e100c36229ca Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 4 Jul 2023 19:26:41 -0400 Subject: [PATCH 065/138] audio: Use SDL_powerof2 instead of reinventing it. --- src/audio/SDL_audio.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index aea09ff807..bca3513612 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -1178,13 +1178,7 @@ static void PrepareAudioFormat(SDL_bool iscapture, SDL_AudioSpec *spec) static int GetDefaultSampleFramesFromFreq(int freq) { - // Pick the closest power-of-two to ~46 ms at desired frequency - const int max_sample = ((freq / 1000) * 46); - int current_sample = 1; - while (current_sample < max_sample) { - current_sample *= 2; - } - return current_sample; + return SDL_powerof2((freq / 1000) * 46); // Pick the closest power-of-two to ~46 ms at desired frequency } void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device) From 3482d1215a699a6aee3fc734c036e1bd899ec05d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 4 Jul 2023 19:27:10 -0400 Subject: [PATCH 066/138] alsa: Don't ever block in CaptureFromDevice. --- src/audio/alsa/SDL_alsa_audio.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c index d5bb90cf19..4703eee4c5 100644 --- a/src/audio/alsa/SDL_alsa_audio.c +++ b/src/audio/alsa/SDL_alsa_audio.c @@ -405,7 +405,6 @@ static int ALSA_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buf device->spec.channels; const int total_frames = buflen / frame_size; snd_pcm_uframes_t frames_left = total_frames; - snd_pcm_uframes_t wait_time = frame_size / 2; SDL_assert((buflen % frame_size) == 0); @@ -414,8 +413,7 @@ static int ALSA_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buf sample_buf, frames_left); if (status == -EAGAIN) { - ALSA_snd_pcm_wait(device->hidden->pcm_handle, wait_time); - status = 0; + break; // Can this even happen? Go back to WaitCaptureDevice, where the device lock isn't held. } else if (status < 0) { /*printf("ALSA: capture error %d\n", status);*/ status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, status, 0); @@ -426,7 +424,7 @@ static int ALSA_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buf ALSA_snd_strerror(status)); return -1; } - continue; + break; // Go back to WaitCaptureDevice, where the device lock isn't held. } /*printf("ALSA: captured %d bytes\n", status * frame_size);*/ From 6fd71185cd6dc200203ab6c483e49791826f8bf1 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 4 Jul 2023 19:37:31 -0400 Subject: [PATCH 067/138] dsp: Updated for new SDL3 audio API. --- CMakeLists.txt | 1 - src/audio/SDL_audiodev.c | 14 +-- src/audio/SDL_audiodev_c.h | 2 +- src/audio/dsp/SDL_dspaudio.c | 212 ++++++++++++++++++----------------- src/audio/dsp/SDL_dspaudio.h | 2 - 5 files changed, 115 insertions(+), 116 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b745b1bbaa..0af3be5528 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -353,7 +353,6 @@ set_option(SDL_CLANG_TIDY "Run clang-tidy static analysis" OFF) set(SDL_VENDOR_INFO "" CACHE STRING "Vendor name and/or version to add to SDL_REVISION") -set(SDL_OSS OFF) set(SDL_JACK OFF) set(SDL_SNDIO OFF) diff --git a/src/audio/SDL_audiodev.c b/src/audio/SDL_audiodev.c index 50bcddf86c..9d73fbb55c 100644 --- a/src/audio/SDL_audiodev.c +++ b/src/audio/SDL_audiodev.c @@ -45,13 +45,13 @@ #define SDL_PATH_DEV_AUDIO "/dev/audio" #endif -static void test_device(const int iscapture, const char *fname, int flags, int (*test)(int fd)) +static void test_device(const SDL_bool iscapture, const char *fname, int flags, SDL_bool (*test)(int fd)) { struct stat sb; if ((stat(fname, &sb) == 0) && (S_ISCHR(sb.st_mode))) { const int audio_fd = open(fname, flags | O_CLOEXEC, 0); if (audio_fd >= 0) { - const int okay = test(audio_fd); + const SDL_bool okay = test(audio_fd); close(audio_fd); if (okay) { static size_t dummyhandle = 0; @@ -63,18 +63,18 @@ static void test_device(const int iscapture, const char *fname, int flags, int ( * information, making this information inaccessible at * enumeration time */ - SDL_AddAudioDevice(iscapture, fname, 0, 0, 0, (void *)(uintptr_t)dummyhandle, ); + SDL_AddAudioDevice(iscapture, fname, NULL, (void *)(uintptr_t)dummyhandle); } } } } -static int test_stub(int fd) +static SDL_bool test_stub(int fd) { - return 1; + return SDL_TRUE; } -static void SDL_EnumUnixAudioDevices_Internal(const int iscapture, const int classic, int (*test)(int)) +static void SDL_EnumUnixAudioDevices_Internal(const SDL_bool iscapture, const SDL_bool classic, SDL_bool (*test)(int)) { const int flags = iscapture ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT; const char *audiodev; @@ -116,7 +116,7 @@ static void SDL_EnumUnixAudioDevices_Internal(const int iscapture, const int cla } } -void SDL_EnumUnixAudioDevices(const int classic, int (*test)(int)) +void SDL_EnumUnixAudioDevices(const SDL_bool classic, SDL_bool (*test)(int)) { SDL_EnumUnixAudioDevices_Internal(SDL_TRUE, classic, test); SDL_EnumUnixAudioDevices_Internal(SDL_FALSE, classic, test); diff --git a/src/audio/SDL_audiodev_c.h b/src/audio/SDL_audiodev_c.h index 370172c9e1..6301d07c54 100644 --- a/src/audio/SDL_audiodev_c.h +++ b/src/audio/SDL_audiodev_c.h @@ -36,6 +36,6 @@ #define OPEN_FLAGS_INPUT (O_RDONLY | O_NONBLOCK) #endif -extern void SDL_EnumUnixAudioDevices(const int classic, int (*test)(int)); +extern void SDL_EnumUnixAudioDevices(const SDL_bool classic, SDL_bool (*test)(int)); #endif /* SDL_audiodev_c_h_ */ diff --git a/src/audio/dsp/SDL_dspaudio.c b/src/audio/dsp/SDL_dspaudio.c index 88c053e8d6..e45df1b04b 100644 --- a/src/audio/dsp/SDL_dspaudio.c +++ b/src/audio/dsp/SDL_dspaudio.c @@ -20,9 +20,9 @@ */ #include "SDL_internal.h" -#ifdef SDL_AUDIO_DRIVER_OSS +// !!! FIXME: clean out perror and fprintf calls in here. -/* Allow access to a raw mixing buffer */ +#ifdef SDL_AUDIO_DRIVER_OSS #include /* For perror() */ #include /* For strerror() */ @@ -38,82 +38,69 @@ #include "../SDL_audio_c.h" #include "../SDL_audiodev_c.h" +#include "../../SDL_utils_c.h" #include "SDL_dspaudio.h" -static void DSP_DetectDevices(void) +static void DSP_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { - SDL_EnumUnixAudioDevices(0, NULL); + SDL_EnumUnixAudioDevices(SDL_FALSE, NULL); } -static void DSP_CloseDevice(SDL_AudioDevice *_this) +static void DSP_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden->audio_fd >= 0) { - close(_this->hidden->audio_fd); - } - SDL_free(_this->hidden->mixbuf); - SDL_free(_this->hidden); -} - -static int DSP_OpenDevice(SDL_AudioDevice *_this, const char *devname) -{ - SDL_bool iscapture = _this->iscapture; - const int flags = ((iscapture) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT); - int format = 0; - int value; - int frag_spec; - SDL_AudioFormat test_format; - const SDL_AudioFormat *closefmts; - - /* We don't care what the devname is...we'll try to open anything. */ - /* ...but default to first name in the list... */ - if (devname == NULL) { - devname = SDL_GetAudioDeviceName(0, iscapture); - if (devname == NULL) { - return SDL_SetError("No such audio device"); + if (device->hidden) { + if (device->hidden->audio_fd >= 0) { + close(device->hidden->audio_fd); } + SDL_free(device->hidden->mixbuf); + SDL_free(device->hidden); + } +} + +static int DSP_OpenDevice(SDL_AudioDevice *device) +{ + // Make sure fragment size stays a power of 2, or OSS fails. + // (I don't know which of these are actually legal values, though...) + if (device->spec.channels > 8) { + device->spec.channels = 8; + } else if (device->spec.channels > 4) { + device->spec.channels = 4; + } else if (device->spec.channels > 2) { + device->spec.channels = 2; } - /* Make sure fragment size stays a power of 2, or OSS fails. */ - /* I don't know which of these are actually legal values, though... */ - if (_this->spec.channels > 8) { - _this->spec.channels = 8; - } else if (_this->spec.channels > 4) { - _this->spec.channels = 4; - } else if (_this->spec.channels > 2) { - _this->spec.channels = 2; - } - - /* Initialize all variables that we clean on shutdown */ - _this->hidden = (struct SDL_PrivateAudioData *) SDL_malloc(sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + // Initialize all variables that we clean on shutdown + device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(_this->hidden); - /* Open the audio device */ - _this->hidden->audio_fd = open(devname, flags | O_CLOEXEC, 0); - if (_this->hidden->audio_fd < 0) { - return SDL_SetError("Couldn't open %s: %s", devname, strerror(errno)); + // Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that. + const int flags = ((device->iscapture) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT); + device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC, 0); + if (device->hidden->audio_fd < 0) { + return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno)); } - /* Make the file descriptor use blocking i/o with fcntl() */ + // Make the file descriptor use blocking i/o with fcntl() { - long ctlflags; - ctlflags = fcntl(_this->hidden->audio_fd, F_GETFL); - ctlflags &= ~O_NONBLOCK; - if (fcntl(_this->hidden->audio_fd, F_SETFL, ctlflags) < 0) { + const long ctlflags = fcntl(device->hidden->audio_fd, F_GETFL) & ~O_NONBLOCK; + if (fcntl(device->hidden->audio_fd, F_SETFL, ctlflags) < 0) { return SDL_SetError("Couldn't set audio blocking mode"); } } - /* Get a list of supported hardware formats */ - if (ioctl(_this->hidden->audio_fd, SNDCTL_DSP_GETFMTS, &value) < 0) { + // Get a list of supported hardware formats + int value; + if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_GETFMTS, &value) < 0) { perror("SNDCTL_DSP_GETFMTS"); return SDL_SetError("Couldn't get audio format list"); } /* Try for a closest match on audio format */ - closefmts = SDL_ClosestAudioFormats(_this->spec.format); + int format = 0; + SDL_AudioFormat test_format; + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { #ifdef DEBUG_AUDIO fprintf(stderr, "Trying format 0x%4.4x\n", test_format); @@ -134,17 +121,7 @@ static int DSP_OpenDevice(SDL_AudioDevice *_this, const char *devname) format = AFMT_S16_BE; } break; -#if 0 -/* - * These formats are not used by any real life systems so they are not - * needed here. - */ - case SDL_AUDIO_S8: - if (value & AFMT_S8) { - format = AFMT_S8; - } - break; -#endif + default: continue; } @@ -153,40 +130,43 @@ static int DSP_OpenDevice(SDL_AudioDevice *_this, const char *devname) if (format == 0) { return SDL_SetError("Couldn't find any hardware audio formats"); } - _this->spec.format = test_format; + device->spec.format = test_format; - /* Set the audio format */ + // Set the audio format value = format; - if ((ioctl(_this->hidden->audio_fd, SNDCTL_DSP_SETFMT, &value) < 0) || + if ((ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFMT, &value) < 0) || (value != format)) { perror("SNDCTL_DSP_SETFMT"); return SDL_SetError("Couldn't set audio format"); } - /* Set the number of channels of output */ - value = _this->spec.channels; - if (ioctl(_this->hidden->audio_fd, SNDCTL_DSP_CHANNELS, &value) < 0) { + // Set the number of channels of output + value = device->spec.channels; + if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_CHANNELS, &value) < 0) { perror("SNDCTL_DSP_CHANNELS"); return SDL_SetError("Cannot set the number of channels"); } - _this->spec.channels = value; + device->spec.channels = value; - /* Set the DSP frequency */ - value = _this->spec.freq; - if (ioctl(_this->hidden->audio_fd, SNDCTL_DSP_SPEED, &value) < 0) { + // Set the DSP frequency + value = device->spec.freq; + if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SPEED, &value) < 0) { perror("SNDCTL_DSP_SPEED"); return SDL_SetError("Couldn't set audio frequency"); } - _this->spec.freq = value; + device->spec.freq = value; /* Calculate the final parameters for this audio specification */ - SDL_CalculateAudioSpec(&_this->spec); + SDL_UpdatedAudioDeviceFormat(device); - /* Determine the power of two of the fragment size */ - for (frag_spec = 0; (0x01U << frag_spec) < _this->spec.size; ++frag_spec) { - } - if ((0x01U << frag_spec) != _this->spec.size) { - return SDL_SetError("Fragment size must be a power of two"); + /* Determine the power of two of the fragment size + Since apps don't control this in SDL3, and this driver only accepts 8, 16 + bit formats and 1, 2, 4, 8 channels, this should always be a power of 2 already. */ + SDL_assert(SDL_powerof2(device->buffer_size) == device->buffer_size); + + int frag_spec = 0; + while ((0x01U << frag_spec) < device->buffer_size) { + frag_spec++; } frag_spec |= 0x00020000; /* two fragments, for low latency */ @@ -195,13 +175,13 @@ static int DSP_OpenDevice(SDL_AudioDevice *_this, const char *devname) fprintf(stderr, "Requesting %d fragments of size %d\n", (frag_spec >> 16), 1 << (frag_spec & 0xFFFF)); #endif - if (ioctl(_this->hidden->audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0) { + if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0) { perror("SNDCTL_DSP_SETFRAGMENT"); } #ifdef DEBUG_AUDIO { audio_buf_info info; - ioctl(_this->hidden->audio_fd, SNDCTL_DSP_GETOSPACE, &info); + ioctl(device->hidden->audio_fd, SNDCTL_DSP_GETOSPACE, &info); fprintf(stderr, "fragments = %d\n", info.fragments); fprintf(stderr, "fragstotal = %d\n", info.fragstotal); fprintf(stderr, "fragsize = %d\n", info.fragsize); @@ -210,44 +190,64 @@ static int DSP_OpenDevice(SDL_AudioDevice *_this, const char *devname) #endif /* Allocate mixing buffer */ - if (!iscapture) { - _this->hidden->mixlen = _this->spec.size; - _this->hidden->mixbuf = (Uint8 *)SDL_malloc(_this->hidden->mixlen); - if (_this->hidden->mixbuf == NULL) { + if (!device->iscapture) { + device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); + if (device->hidden->mixbuf == NULL) { return SDL_OutOfMemory(); } - SDL_memset(_this->hidden->mixbuf, _this->spec.silence, _this->spec.size); + SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); } - /* We're ready to rock and roll. :-) */ - return 0; + return 0; // We're ready to rock and roll. :-) } -static void DSP_PlayDevice(SDL_AudioDevice *_this) +static void DSP_WaitDevice(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *h = _this->hidden; - if (write(h->audio_fd, h->mixbuf, h->mixlen) == -1) { + const unsigned long ioctlreq = device->iscapture ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE; + struct SDL_PrivateAudioData *h = device->hidden; + + while (!SDL_AtomicGet(&device->shutdown)) { + audio_buf_info info; + const int rc = ioctl(h->audio_fd, ioctlreq, &info); + if ((rc < 0) && (errno != EAGAIN)) { // Hmm, not much we can do - abort + fprintf(stderr, "dsp WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno)); + SDL_AudioDeviceDisconnected(device); + return; + } else if (info.bytes < device->buffer_size) { +//SDL_Log("DSP NEED=%d HAVE=%d", (int) device->buffer_size, (int) info.bytes); + SDL_Delay(10); + } else { +//SDL_Log("DSP NEED=%d HAVE=%d", (int) device->buffer_size, (int) info.bytes); + break; /* ready to go! */ + } + } +} + +static void DSP_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) +{ + struct SDL_PrivateAudioData *h = device->hidden; + if (write(h->audio_fd, buffer, buflen) == -1) { perror("Audio write"); - SDL_OpenedAudioDeviceDisconnected(_this); + SDL_AudioDeviceDisconnected(device); } #ifdef DEBUG_AUDIO fprintf(stderr, "Wrote %d bytes of audio data\n", h->mixlen); #endif } -static Uint8 *DSP_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *DSP_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - return _this->hidden->mixbuf; + return device->hidden->mixbuf; } -static int DSP_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static int DSP_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { - return (int)read(_this->hidden->audio_fd, buffer, buflen); + return (int)read(device->hidden->audio_fd, buffer, buflen); } -static void DSP_FlushCapture(SDL_AudioDevice *_this) +static void DSP_FlushCapture(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *h = _this->hidden; + struct SDL_PrivateAudioData *h = device->hidden; audio_buf_info info; if (ioctl(h->audio_fd, SNDCTL_DSP_GETISPACE, &info) == 0) { while (info.bytes > 0) { @@ -263,17 +263,17 @@ static void DSP_FlushCapture(SDL_AudioDevice *_this) } static SDL_bool InitTimeDevicesExist = SDL_FALSE; -static int look_for_devices_test(int fd) +static SDL_bool look_for_devices_test(int fd) { InitTimeDevicesExist = SDL_TRUE; /* note that _something_ exists. */ /* Don't add to the device list, we're just seeing if any devices exist. */ - return 0; + return SDL_FALSE; } static SDL_bool DSP_Init(SDL_AudioDriverImpl *impl) { InitTimeDevicesExist = SDL_FALSE; - SDL_EnumUnixAudioDevices(0, look_for_devices_test); + SDL_EnumUnixAudioDevices(SDL_FALSE, look_for_devices_test); if (!InitTimeDevicesExist) { SDL_SetError("dsp: No such audio device"); return SDL_FALSE; /* maybe try a different backend. */ @@ -282,9 +282,11 @@ static SDL_bool DSP_Init(SDL_AudioDriverImpl *impl) /* Set the function pointers */ impl->DetectDevices = DSP_DetectDevices; impl->OpenDevice = DSP_OpenDevice; + impl->WaitDevice = DSP_WaitDevice; impl->PlayDevice = DSP_PlayDevice; impl->GetDeviceBuf = DSP_GetDeviceBuf; impl->CloseDevice = DSP_CloseDevice; + impl->WaitCaptureDevice = DSP_WaitDevice; impl->CaptureFromDevice = DSP_CaptureFromDevice; impl->FlushCapture = DSP_FlushCapture; @@ -295,7 +297,7 @@ static SDL_bool DSP_Init(SDL_AudioDriverImpl *impl) } AudioBootStrap DSP_bootstrap = { - "dsp", "OSS /dev/dsp standard audio", DSP_Init, SDL_FALSE + "dsp", "Open Sound System (/dev/dsp)", DSP_Init, SDL_FALSE }; #endif /* SDL_AUDIO_DRIVER_OSS */ diff --git a/src/audio/dsp/SDL_dspaudio.h b/src/audio/dsp/SDL_dspaudio.h index e5bf29eddc..abe0c347df 100644 --- a/src/audio/dsp/SDL_dspaudio.h +++ b/src/audio/dsp/SDL_dspaudio.h @@ -32,8 +32,6 @@ struct SDL_PrivateAudioData /* Raw mixing buffer */ Uint8 *mixbuf; - int mixlen; }; -#define FUDGE_TICKS 10 /* The scheduler overhead ticks per frame */ #endif /* SDL_dspaudio_h_ */ From a2b488359e087f9dcb30c32bb7bfaa3354226930 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 4 Jul 2023 19:38:01 -0400 Subject: [PATCH 068/138] dsp: Removed debug logging --- src/audio/dsp/SDL_dspaudio.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/audio/dsp/SDL_dspaudio.c b/src/audio/dsp/SDL_dspaudio.c index e45df1b04b..41df8d68f4 100644 --- a/src/audio/dsp/SDL_dspaudio.c +++ b/src/audio/dsp/SDL_dspaudio.c @@ -214,10 +214,8 @@ static void DSP_WaitDevice(SDL_AudioDevice *device) SDL_AudioDeviceDisconnected(device); return; } else if (info.bytes < device->buffer_size) { -//SDL_Log("DSP NEED=%d HAVE=%d", (int) device->buffer_size, (int) info.bytes); SDL_Delay(10); } else { -//SDL_Log("DSP NEED=%d HAVE=%d", (int) device->buffer_size, (int) info.bytes); break; /* ready to go! */ } } From 18906a32b88d213b49816b157ab72a9f14da1532 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 00:22:35 -0400 Subject: [PATCH 069/138] jack: First shot at updating for SDL3 audio API. --- CMakeLists.txt | 1 - src/audio/jack/SDL_jackaudio.c | 77 ++++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0af3be5528..c74b19ee5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -353,7 +353,6 @@ set_option(SDL_CLANG_TIDY "Run clang-tidy static analysis" OFF) set(SDL_VENDOR_INFO "" CACHE STRING "Vendor name and/or version to add to SDL_REVISION") -set(SDL_JACK OFF) set(SDL_SNDIO OFF) cmake_dependent_option(SDL_SHARED "Build a shared version of the library" ${SDL_SHARED_DEFAULT} ${SDL_SHARED_AVAILABLE} OFF) diff --git a/src/audio/jack/SDL_jackaudio.c b/src/audio/jack/SDL_jackaudio.c index 60deac3a7a..36bfbb8e73 100644 --- a/src/audio/jack/SDL_jackaudio.c +++ b/src/audio/jack/SDL_jackaudio.c @@ -136,7 +136,7 @@ static int load_jack_syms(void) static void jackShutdownCallback(void *arg) /* JACK went away; device is lost. */ { SDL_AudioDevice *_this = (SDL_AudioDevice *)arg; - SDL_OpenedAudioDeviceDisconnected(_this); + SDL_AudioDeviceDisconnected(_this); SDL_PostSemaphore(_this->hidden->iosem); /* unblock the SDL thread. */ } @@ -149,12 +149,12 @@ static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg) SDL_AudioDevice *_this = (SDL_AudioDevice *)arg; jack_port_t **ports = _this->hidden->sdlports; const int total_channels = _this->spec.channels; - const int total_frames = _this->spec.samples; + const int total_frames = _this->sample_frames; int channelsi; - if (!SDL_AtomicGet(&_this->enabled)) { + if (SDL_AtomicGet(&_this->shutdown)) { /* silence the buffer to avoid repeats and corruption. */ - SDL_memset(_this->hidden->iobuffer, '\0', _this->spec.size); + SDL_memset(_this->hidden->iobuffer, _this->silence_value, _this->buffer_size); } for (channelsi = 0; channelsi < total_channels; channelsi++) { @@ -176,14 +176,14 @@ static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg) /* This function waits until it is possible to write a full sound buffer */ static void JACK_WaitDevice(SDL_AudioDevice *_this) { - if (SDL_AtomicGet(&_this->enabled)) { + if (!SDL_AtomicGet(&_this->shutdown)) { if (SDL_WaitSemaphore(_this->hidden->iosem) == -1) { - SDL_OpenedAudioDeviceDisconnected(_this); + SDL_AudioDeviceDisconnected(_this); } } } -static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *_this, int *buffer_size) { return (Uint8 *)_this->hidden->iobuffer; } @@ -191,10 +191,10 @@ static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *_this) static int jackProcessCaptureCallback(jack_nframes_t nframes, void *arg) { SDL_AudioDevice *_this = (SDL_AudioDevice *)arg; - if (SDL_AtomicGet(&_this->enabled)) { + if (!SDL_AtomicGet(&_this->shutdown)) { jack_port_t **ports = _this->hidden->sdlports; const int total_channels = _this->spec.channels; - const int total_frames = _this->spec.samples; + const int total_frames = _this->sample_frames; int channelsi; for (channelsi = 0; channelsi < total_channels; channelsi++) { @@ -216,7 +216,7 @@ static int jackProcessCaptureCallback(jack_nframes_t nframes, void *arg) static int JACK_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) { - SDL_assert(buflen == _this->spec.size); /* we always fill a full buffer. */ + SDL_assert(buflen == _this->buffer_size); /* we always fill a full buffer. */ /* Wait for JACK to fill the iobuffer */ if (SDL_WaitSemaphore(_this->hidden->iosem) == -1) { @@ -234,30 +234,46 @@ static void JACK_FlushCapture(SDL_AudioDevice *_this) static void JACK_CloseDevice(SDL_AudioDevice *_this) { - if (_this->hidden->client) { - JACK_jack_deactivate(_this->hidden->client); + if (_this->hidden) { + if (_this->hidden->client) { + JACK_jack_deactivate(_this->hidden->client); - if (_this->hidden->sdlports) { - const int channels = _this->spec.channels; - int i; - for (i = 0; i < channels; i++) { - JACK_jack_port_unregister(_this->hidden->client, _this->hidden->sdlports[i]); + if (_this->hidden->sdlports) { + const int channels = _this->spec.channels; + int i; + for (i = 0; i < channels; i++) { + JACK_jack_port_unregister(_this->hidden->client, _this->hidden->sdlports[i]); + } + SDL_free(_this->hidden->sdlports); } - SDL_free(_this->hidden->sdlports); + + JACK_jack_client_close(_this->hidden->client); } - JACK_jack_client_close(_this->hidden->client); - } + if (_this->hidden->iosem) { + SDL_DestroySemaphore(_this->hidden->iosem); + } - if (_this->hidden->iosem) { - SDL_DestroySemaphore(_this->hidden->iosem); + SDL_free(_this->hidden->iobuffer); + SDL_free(_this->hidden); } - - SDL_free(_this->hidden->iobuffer); - SDL_free(_this->hidden); } -static int JACK_OpenDevice(SDL_AudioDevice *_this, const char *devname) +// !!! FIXME: unify this (PulseAudio has a getAppName, Pipewire has a thing, etc +static const char *GetJackAppName(void) +{ + const char *retval = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME); + if (retval && *retval) { + return retval; + } + retval = SDL_GetHint(SDL_HINT_APP_NAME); + if (retval && *retval) { + return retval; + } + return "SDL Application"; +} + +static int JACK_OpenDevice(SDL_AudioDevice *_this) { /* Note that JACK uses "output" for capture devices (they output audio data to us) and "input" for playback (we input audio data to them). @@ -282,8 +298,7 @@ static int JACK_OpenDevice(SDL_AudioDevice *_this, const char *devname) return SDL_OutOfMemory(); } - /* !!! FIXME: we _still_ need an API to specify an app name */ - client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL); + client = JACK_jack_client_open(GetJackAppName(), JackNoStartServer, &status, NULL); _this->hidden->client = client; if (client == NULL) { return SDL_SetError("Can't open JACK client"); @@ -320,9 +335,9 @@ static int JACK_OpenDevice(SDL_AudioDevice *_this, const char *devname) _this->spec.format = SDL_AUDIO_F32SYS; _this->spec.freq = JACK_jack_get_sample_rate(client); _this->spec.channels = channels; - _this->spec.samples = JACK_jack_get_buffer_size(client); + _this->sample_frames = JACK_jack_get_buffer_size(client); - SDL_CalculateAudioSpec(&_this->spec); + SDL_UpdatedAudioDeviceFormat(_this); _this->hidden->iosem = SDL_CreateSemaphore(0); if (!_this->hidden->iosem) { @@ -330,7 +345,7 @@ static int JACK_OpenDevice(SDL_AudioDevice *_this, const char *devname) return -1; /* error was set by SDL_CreateSemaphore */ } - _this->hidden->iobuffer = (float *)SDL_calloc(1, _this->spec.size); + _this->hidden->iobuffer = (float *)SDL_calloc(1, _this->buffer_size); if (!_this->hidden->iobuffer) { SDL_free(audio_ports); return SDL_OutOfMemory(); From 86243b25894c30a595be69b4d1869261b73d1db5 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 00:43:09 -0400 Subject: [PATCH 070/138] jack: Use ProvidesOwnCallbackThread. We were firing a semaphore from the JACK-provided thread to otherwise work within the standard SDL2 device thread, but there's no need for this in SDL3. --- src/audio/jack/SDL_jackaudio.c | 126 +++++++++++++-------------------- src/audio/jack/SDL_jackaudio.h | 3 +- 2 files changed, 50 insertions(+), 79 deletions(-) diff --git a/src/audio/jack/SDL_jackaudio.c b/src/audio/jack/SDL_jackaudio.c index 36bfbb8e73..53eb73a474 100644 --- a/src/audio/jack/SDL_jackaudio.c +++ b/src/audio/jack/SDL_jackaudio.c @@ -135,9 +135,7 @@ static int load_jack_syms(void) static void jackShutdownCallback(void *arg) /* JACK went away; device is lost. */ { - SDL_AudioDevice *_this = (SDL_AudioDevice *)arg; - SDL_AudioDeviceDisconnected(_this); - SDL_PostSemaphore(_this->hidden->iosem); /* unblock the SDL thread. */ + SDL_AudioDeviceDisconnected((SDL_AudioDevice *)arg); } // !!! FIXME: implement and register these! @@ -146,39 +144,27 @@ static void jackShutdownCallback(void *arg) /* JACK went away; device is lost. * static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg) { - SDL_AudioDevice *_this = (SDL_AudioDevice *)arg; - jack_port_t **ports = _this->hidden->sdlports; - const int total_channels = _this->spec.channels; - const int total_frames = _this->sample_frames; - int channelsi; - - if (SDL_AtomicGet(&_this->shutdown)) { - /* silence the buffer to avoid repeats and corruption. */ - SDL_memset(_this->hidden->iobuffer, _this->silence_value, _this->buffer_size); - } - - for (channelsi = 0; channelsi < total_channels; channelsi++) { - float *dst = (float *)JACK_jack_port_get_buffer(ports[channelsi], nframes); - if (dst) { - const float *src = _this->hidden->iobuffer + channelsi; - int framesi; - for (framesi = 0; framesi < total_frames; framesi++) { - *(dst++) = *src; - src += total_channels; - } - } - } - - SDL_PostSemaphore(_this->hidden->iosem); /* tell SDL thread we're done; refill the buffer. */ + SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames); + SDL_OutputAudioThreadIterate((SDL_AudioDevice *)arg); return 0; } -/* This function waits until it is possible to write a full sound buffer */ -static void JACK_WaitDevice(SDL_AudioDevice *_this) +static void JACK_PlayDevice(SDL_AudioDevice *device, const Uint8 *ui8buffer, int buflen) { - if (!SDL_AtomicGet(&_this->shutdown)) { - if (SDL_WaitSemaphore(_this->hidden->iosem) == -1) { - SDL_AudioDeviceDisconnected(_this); + const float *buffer = (float *) ui8buffer; + jack_port_t **ports = device->hidden->sdlports; + const int total_channels = device->spec.channels; + const int total_frames = device->sample_frames; + const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames; + + for (int channelsi = 0; channelsi < total_channels; channelsi++) { + float *dst = (float *)JACK_jack_port_get_buffer(ports[channelsi], nframes); + if (dst) { + const float *src = buffer + channelsi; + for (int framesi = 0; framesi < total_frames; framesi++) { + *(dst++) = *src; + src += total_channels; + } } } } @@ -190,46 +176,36 @@ static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *_this, int *buffer_size) static int jackProcessCaptureCallback(jack_nframes_t nframes, void *arg) { - SDL_AudioDevice *_this = (SDL_AudioDevice *)arg; - if (!SDL_AtomicGet(&_this->shutdown)) { - jack_port_t **ports = _this->hidden->sdlports; - const int total_channels = _this->spec.channels; - const int total_frames = _this->sample_frames; - int channelsi; + SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames); + SDL_CaptureAudioThreadIterate((SDL_AudioDevice *)arg); + return 0; +} - for (channelsi = 0; channelsi < total_channels; channelsi++) { - const float *src = (const float *)JACK_jack_port_get_buffer(ports[channelsi], nframes); - if (src) { - float *dst = _this->hidden->iobuffer + channelsi; - int framesi; - for (framesi = 0; framesi < total_frames; framesi++) { - *dst = *(src++); - dst += total_channels; - } +static int JACK_CaptureFromDevice(SDL_AudioDevice *_this, void *vbuffer, int buflen) +{ + float *buffer = (float *) vbuffer; + jack_port_t **ports = _this->hidden->sdlports; + const int total_channels = _this->spec.channels; + const int total_frames = _this->sample_frames; + const jack_nframes_t nframes = (jack_nframes_t) _this->sample_frames; + + for (int channelsi = 0; channelsi < total_channels; channelsi++) { + const float *src = (const float *)JACK_jack_port_get_buffer(ports[channelsi], nframes); + if (src) { + float *dst = buffer + channelsi; + for (int framesi = 0; framesi < total_frames; framesi++) { + *dst = *(src++); + dst += total_channels; } } } - SDL_PostSemaphore(_this->hidden->iosem); /* tell SDL thread we're done; new buffer is ready! */ - return 0; -} - -static int JACK_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) -{ - SDL_assert(buflen == _this->buffer_size); /* we always fill a full buffer. */ - - /* Wait for JACK to fill the iobuffer */ - if (SDL_WaitSemaphore(_this->hidden->iosem) == -1) { - return -1; - } - - SDL_memcpy(buffer, _this->hidden->iobuffer, buflen); return buflen; } static void JACK_FlushCapture(SDL_AudioDevice *_this) { - SDL_WaitSemaphore(_this->hidden->iosem); + // do nothing, the data will just be replaced next callback. } static void JACK_CloseDevice(SDL_AudioDevice *_this) @@ -250,12 +226,11 @@ static void JACK_CloseDevice(SDL_AudioDevice *_this) JACK_jack_client_close(_this->hidden->client); } - if (_this->hidden->iosem) { - SDL_DestroySemaphore(_this->hidden->iosem); - } - SDL_free(_this->hidden->iobuffer); SDL_free(_this->hidden); + _this->hidden = NULL; + + SDL_AudioThreadFinalize(_this); } } @@ -339,16 +314,12 @@ static int JACK_OpenDevice(SDL_AudioDevice *_this) SDL_UpdatedAudioDeviceFormat(_this); - _this->hidden->iosem = SDL_CreateSemaphore(0); - if (!_this->hidden->iosem) { - SDL_free(audio_ports); - return -1; /* error was set by SDL_CreateSemaphore */ - } - - _this->hidden->iobuffer = (float *)SDL_calloc(1, _this->buffer_size); - if (!_this->hidden->iobuffer) { - SDL_free(audio_ports); - return SDL_OutOfMemory(); + if (!_this->iscapture) { + _this->hidden->iobuffer = (float *)SDL_calloc(1, _this->buffer_size); + if (!_this->hidden->iobuffer) { + SDL_free(audio_ports); + return SDL_OutOfMemory(); + } } /* Build SDL's ports, which we will connect to the device ports. */ @@ -421,8 +392,8 @@ static SDL_bool JACK_Init(SDL_AudioDriverImpl *impl) /* Set the function pointers */ impl->OpenDevice = JACK_OpenDevice; - impl->WaitDevice = JACK_WaitDevice; impl->GetDeviceBuf = JACK_GetDeviceBuf; + impl->PlayDevice = JACK_PlayDevice; impl->CloseDevice = JACK_CloseDevice; impl->Deinitialize = JACK_Deinitialize; impl->CaptureFromDevice = JACK_CaptureFromDevice; @@ -430,6 +401,7 @@ static SDL_bool JACK_Init(SDL_AudioDriverImpl *impl) impl->OnlyHasDefaultOutputDevice = SDL_TRUE; impl->OnlyHasDefaultCaptureDevice = SDL_TRUE; impl->HasCaptureSupport = SDL_TRUE; + impl->ProvidesOwnCallbackThread = SDL_TRUE; return SDL_TRUE; /* this audio target is available. */ } diff --git a/src/audio/jack/SDL_jackaudio.h b/src/audio/jack/SDL_jackaudio.h index 91f5680d55..a8cf81d4fa 100644 --- a/src/audio/jack/SDL_jackaudio.h +++ b/src/audio/jack/SDL_jackaudio.h @@ -28,9 +28,8 @@ struct SDL_PrivateAudioData { jack_client_t *client; - SDL_Semaphore *iosem; - float *iobuffer; jack_port_t **sdlports; + float *iobuffer; }; #endif /* SDL_jackaudio_h_ */ From 3f4f004794528516a692a8f792ee756346595a29 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 00:52:28 -0400 Subject: [PATCH 071/138] audio: Remove an assertion that no longer makes sense. One may happen to call SDL_AudioThreadFinalize when thread_alive is not set. --- src/audio/SDL_audio.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index bca3513612..5ce08c2684 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -663,7 +663,6 @@ void SDL_QuitAudio(void) void SDL_AudioThreadFinalize(SDL_AudioDevice *device) { - SDL_assert(SDL_AtomicGet(&device->thread_alive)); if (SDL_AtomicGet(&device->condemned)) { if (device->thread) { SDL_DetachThread(device->thread); // no one is waiting for us, just detach ourselves. From c83b68ef26d62b993ee94d79c57c2c6fcb7ff73c Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 00:54:11 -0400 Subject: [PATCH 072/138] jack: renamed `_this` to `device`. --- src/audio/jack/SDL_jackaudio.c | 82 +++++++++++++++++----------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/audio/jack/SDL_jackaudio.c b/src/audio/jack/SDL_jackaudio.c index 53eb73a474..3e3ed20828 100644 --- a/src/audio/jack/SDL_jackaudio.c +++ b/src/audio/jack/SDL_jackaudio.c @@ -169,9 +169,9 @@ static void JACK_PlayDevice(SDL_AudioDevice *device, const Uint8 *ui8buffer, int } } -static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *_this, int *buffer_size) +static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - return (Uint8 *)_this->hidden->iobuffer; + return (Uint8 *)device->hidden->iobuffer; } static int jackProcessCaptureCallback(jack_nframes_t nframes, void *arg) @@ -181,13 +181,13 @@ static int jackProcessCaptureCallback(jack_nframes_t nframes, void *arg) return 0; } -static int JACK_CaptureFromDevice(SDL_AudioDevice *_this, void *vbuffer, int buflen) +static int JACK_CaptureFromDevice(SDL_AudioDevice *device, void *vbuffer, int buflen) { float *buffer = (float *) vbuffer; - jack_port_t **ports = _this->hidden->sdlports; - const int total_channels = _this->spec.channels; - const int total_frames = _this->sample_frames; - const jack_nframes_t nframes = (jack_nframes_t) _this->sample_frames; + jack_port_t **ports = device->hidden->sdlports; + const int total_channels = device->spec.channels; + const int total_frames = device->sample_frames; + const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames; for (int channelsi = 0; channelsi < total_channels; channelsi++) { const float *src = (const float *)JACK_jack_port_get_buffer(ports[channelsi], nframes); @@ -203,34 +203,34 @@ static int JACK_CaptureFromDevice(SDL_AudioDevice *_this, void *vbuffer, int buf return buflen; } -static void JACK_FlushCapture(SDL_AudioDevice *_this) +static void JACK_FlushCapture(SDL_AudioDevice *device) { // do nothing, the data will just be replaced next callback. } -static void JACK_CloseDevice(SDL_AudioDevice *_this) +static void JACK_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden) { - if (_this->hidden->client) { - JACK_jack_deactivate(_this->hidden->client); + if (device->hidden) { + if (device->hidden->client) { + JACK_jack_deactivate(device->hidden->client); - if (_this->hidden->sdlports) { - const int channels = _this->spec.channels; + if (device->hidden->sdlports) { + const int channels = device->spec.channels; int i; for (i = 0; i < channels; i++) { - JACK_jack_port_unregister(_this->hidden->client, _this->hidden->sdlports[i]); + JACK_jack_port_unregister(device->hidden->client, device->hidden->sdlports[i]); } - SDL_free(_this->hidden->sdlports); + SDL_free(device->hidden->sdlports); } - JACK_jack_client_close(_this->hidden->client); + JACK_jack_client_close(device->hidden->client); } - SDL_free(_this->hidden->iobuffer); - SDL_free(_this->hidden); - _this->hidden = NULL; + SDL_free(device->hidden->iobuffer); + SDL_free(device->hidden); + device->hidden = NULL; - SDL_AudioThreadFinalize(_this); + SDL_AudioThreadFinalize(device); } } @@ -248,13 +248,13 @@ static const char *GetJackAppName(void) return "SDL Application"; } -static int JACK_OpenDevice(SDL_AudioDevice *_this) +static int JACK_OpenDevice(SDL_AudioDevice *device) { /* Note that JACK uses "output" for capture devices (they output audio data to us) and "input" for playback (we input audio data to them). Likewise, SDL's playback port will be "output" (we write data out) and capture will be "input" (we read data in). */ - SDL_bool iscapture = _this->iscapture; + SDL_bool iscapture = device->iscapture; const unsigned long sysportflags = iscapture ? JackPortIsOutput : JackPortIsInput; const unsigned long sdlportflags = iscapture ? JackPortIsInput : JackPortIsOutput; const JackProcessCallback callback = iscapture ? jackProcessCaptureCallback : jackProcessPlaybackCallback; @@ -268,13 +268,13 @@ static int JACK_OpenDevice(SDL_AudioDevice *_this) int i; /* Initialize all variables that we clean on shutdown */ - _this->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } client = JACK_jack_client_open(GetJackAppName(), JackNoStartServer, &status, NULL); - _this->hidden->client = client; + device->hidden->client = client; if (client == NULL) { return SDL_SetError("Can't open JACK client"); } @@ -307,24 +307,24 @@ static int JACK_OpenDevice(SDL_AudioDevice *_this) /* !!! FIXME: docs say about buffer size: "This size may change, clients that depend on it must register a bufsize_callback so they will be notified if it does." */ /* Jack pretty much demands what it wants. */ - _this->spec.format = SDL_AUDIO_F32SYS; - _this->spec.freq = JACK_jack_get_sample_rate(client); - _this->spec.channels = channels; - _this->sample_frames = JACK_jack_get_buffer_size(client); + device->spec.format = SDL_AUDIO_F32SYS; + device->spec.freq = JACK_jack_get_sample_rate(client); + device->spec.channels = channels; + device->sample_frames = JACK_jack_get_buffer_size(client); - SDL_UpdatedAudioDeviceFormat(_this); + SDL_UpdatedAudioDeviceFormat(device); - if (!_this->iscapture) { - _this->hidden->iobuffer = (float *)SDL_calloc(1, _this->buffer_size); - if (!_this->hidden->iobuffer) { + if (!device->iscapture) { + device->hidden->iobuffer = (float *)SDL_calloc(1, device->buffer_size); + if (!device->hidden->iobuffer) { SDL_free(audio_ports); return SDL_OutOfMemory(); } } /* Build SDL's ports, which we will connect to the device ports. */ - _this->hidden->sdlports = (jack_port_t **)SDL_calloc(channels, sizeof(jack_port_t *)); - if (_this->hidden->sdlports == NULL) { + device->hidden->sdlports = (jack_port_t **)SDL_calloc(channels, sizeof(jack_port_t *)); + if (device->hidden->sdlports == NULL) { SDL_free(audio_ports); return SDL_OutOfMemory(); } @@ -332,19 +332,19 @@ static int JACK_OpenDevice(SDL_AudioDevice *_this) for (i = 0; i < channels; i++) { char portname[32]; (void)SDL_snprintf(portname, sizeof(portname), "sdl_jack_%s_%d", sdlportstr, i); - _this->hidden->sdlports[i] = JACK_jack_port_register(client, portname, JACK_DEFAULT_AUDIO_TYPE, sdlportflags, 0); - if (_this->hidden->sdlports[i] == NULL) { + device->hidden->sdlports[i] = JACK_jack_port_register(client, portname, JACK_DEFAULT_AUDIO_TYPE, sdlportflags, 0); + if (device->hidden->sdlports[i] == NULL) { SDL_free(audio_ports); return SDL_SetError("jack_port_register failed"); } } - if (JACK_jack_set_process_callback(client, callback, _this) != 0) { + if (JACK_jack_set_process_callback(client, callback, device) != 0) { SDL_free(audio_ports); return SDL_SetError("JACK: Couldn't set process callback"); } - JACK_jack_on_shutdown(client, jackShutdownCallback, _this); + JACK_jack_on_shutdown(client, jackShutdownCallback, device); if (JACK_jack_activate(client) != 0) { SDL_free(audio_ports); @@ -353,7 +353,7 @@ static int JACK_OpenDevice(SDL_AudioDevice *_this) /* once activated, we can connect all the ports. */ for (i = 0; i < channels; i++) { - const char *sdlport = JACK_jack_port_name(_this->hidden->sdlports[i]); + const char *sdlport = JACK_jack_port_name(device->hidden->sdlports[i]); const char *srcport = iscapture ? devports[audio_ports[i]] : sdlport; const char *dstport = iscapture ? sdlport : devports[audio_ports[i]]; if (JACK_jack_connect(client, srcport, dstport) != 0) { From 74568cdb2b86013387d3e124cdd0878aef664541 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 02:10:14 -0400 Subject: [PATCH 073/138] ps2audio: Updated (but untested) for SDL3 audio API. --- src/audio/ps2/SDL_ps2audio.c | 61 ++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/src/audio/ps2/SDL_ps2audio.c b/src/audio/ps2/SDL_ps2audio.c index f14eb6ee7b..a839742e1f 100644 --- a/src/audio/ps2/SDL_ps2audio.c +++ b/src/audio/ps2/SDL_ps2audio.c @@ -20,8 +20,6 @@ */ #include "SDL_internal.h" -/* Output audio to nowhere... */ - #include "../SDL_audio_c.h" #include "SDL_ps2audio.h" @@ -29,20 +27,16 @@ #include #include -/* The tag name used by PS2 audio */ -#define PS2AUDIO_DRIVER_NAME "ps2" - -static int PS2AUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int PS2AUDIO_OpenDevice(SDL_AudioDevice *_this) { int i, mixlen; struct audsrv_fmt_t format; _this->hidden = (struct SDL_PrivateAudioData *) - SDL_malloc(sizeof(*_this->hidden)); + SDL_calloc(1, sizeof(*_this->hidden)); if (_this->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(_this->hidden); /* These are the native supported audio PS2 configs */ switch (_this->spec.freq) { @@ -53,19 +47,16 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) case 32000: case 44100: case 48000: - _this->spec.freq = _this->spec.freq; - break; + break; // acceptable value, keep it default: _this->spec.freq = 48000; break; } - _this->spec.samples = 512; + _this->sample_frames = 512; _this->spec.channels = _this->spec.channels == 1 ? 1 : 2; _this->spec.format = _this->spec.format == SDL_AUDIO_S8 ? SDL_AUDIO_S8 : SDL_AUDIO_S16; - SDL_CalculateAudioSpec(&_this->spec); - format.bits = _this->spec.format == SDL_AUDIO_S8 ? 8 : 16; format.freq = _this->spec.freq; format.channels = _this->spec.channels; @@ -80,12 +71,12 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) } /* Update the fragment size as size in bytes. */ - SDL_CalculateAudioSpec(&_this->spec); + SDL_UpdatedAudioDeviceFormat(_this); /* Allocate the mixing buffer. Its size and starting address must be a multiple of 64 bytes. Our sample count is already a multiple of 64, so spec->size should be a multiple of 64 as well. */ - mixlen = _this->spec.size * NUM_BUFFERS; + mixlen = _this->buffer_size * NUM_BUFFERS; _this->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen); if (_this->hidden->rawbuf == NULL) { return SDL_SetError("Couldn't allocate mixing buffer"); @@ -93,42 +84,45 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) SDL_memset(_this->hidden->rawbuf, 0, mixlen); for (i = 0; i < NUM_BUFFERS; i++) { - _this->hidden->mixbufs[i] = &_this->hidden->rawbuf[i * _this->spec.size]; + _this->hidden->mixbufs[i] = &_this->hidden->rawbuf[i * _this->buffer_size]; } _this->hidden->next_buffer = 0; return 0; } -static void PS2AUDIO_PlayDevice(SDL_AudioDevice *_this) +static void PS2AUDIO_PlayDevice(SDL_AudioDevice *_this, const Uint8 *buffer, int buflen) { - uint8_t *mixbuf = _this->hidden->mixbufs[_this->hidden->next_buffer]; - audsrv_play_audio((char *)mixbuf, _this->spec.size); - - _this->hidden->next_buffer = (_this->hidden->next_buffer + 1) % NUM_BUFFERS; + audsrv_play_audio((char *)buffer, buflen); } /* This function waits until it is possible to write a full sound buffer */ static void PS2AUDIO_WaitDevice(SDL_AudioDevice *_this) { - audsrv_wait_audio(_this->spec.size); + audsrv_wait_audio(_this->buffer_size); } -static Uint8 *PS2AUDIO_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *PS2AUDIO_GetDeviceBuf(SDL_AudioDevice *_this, int *buffer_size) { - return _this->hidden->mixbufs[_this->hidden->next_buffer]; + Uint8 *buffer = _this->hidden->mixbufs[_this->hidden->next_buffer]; + _this->hidden->next_buffer = (_this->hidden->next_buffer + 1) % NUM_BUFFERS; + return buffer;; } static void PS2AUDIO_CloseDevice(SDL_AudioDevice *_this) { - if (_this->hidden->channel >= 0) { - audsrv_stop_audio(); - _this->hidden->channel = -1; - } + if (_this->hidden) { + if (_this->hidden->channel >= 0) { + audsrv_stop_audio(); + _this->hidden->channel = -1; + } - if (_this->hidden->rawbuf != NULL) { - SDL_aligned_free(_this->hidden->rawbuf); - _this->hidden->rawbuf = NULL; + if (_this->hidden->rawbuf != NULL) { + SDL_aligned_free(_this->hidden->rawbuf); + _this->hidden->rawbuf = NULL; + } + SDL_free(_this->hidden); + _this->hidden = NULL; } } @@ -136,10 +130,9 @@ static void PS2AUDIO_ThreadInit(SDL_AudioDevice *_this) { /* Increase the priority of this audio thread by 1 to put it ahead of other SDL threads. */ - int32_t thid; + const int32_t thid = GetThreadId(); ee_thread_status_t status; - thid = GetThreadId(); - if (ReferThreadStatus(GetThreadId(), &status) == 0) { + if (ReferThreadStatus(thid, &status) == 0) { ChangeThreadPriority(thid, status.current_priority - 1); } } From 4993743a026726983af35638ab7ea289687cc10e Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 02:10:48 -0400 Subject: [PATCH 074/138] ps2audio: Renamed `_this` to `device` --- src/audio/ps2/SDL_ps2audio.c | 66 ++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/audio/ps2/SDL_ps2audio.c b/src/audio/ps2/SDL_ps2audio.c index a839742e1f..11fedc53b3 100644 --- a/src/audio/ps2/SDL_ps2audio.c +++ b/src/audio/ps2/SDL_ps2audio.c @@ -27,19 +27,19 @@ #include #include -static int PS2AUDIO_OpenDevice(SDL_AudioDevice *_this) +static int PS2AUDIO_OpenDevice(SDL_AudioDevice *device) { int i, mixlen; struct audsrv_fmt_t format; - _this->hidden = (struct SDL_PrivateAudioData *) - SDL_calloc(1, sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + device->hidden = (struct SDL_PrivateAudioData *) + SDL_calloc(1, sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } /* These are the native supported audio PS2 configs */ - switch (_this->spec.freq) { + switch (device->spec.freq) { case 11025: case 12000: case 22050: @@ -49,19 +49,19 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *_this) case 48000: break; // acceptable value, keep it default: - _this->spec.freq = 48000; + device->spec.freq = 48000; break; } - _this->sample_frames = 512; - _this->spec.channels = _this->spec.channels == 1 ? 1 : 2; - _this->spec.format = _this->spec.format == SDL_AUDIO_S8 ? SDL_AUDIO_S8 : SDL_AUDIO_S16; + device->sample_frames = 512; + device->spec.channels = device->spec.channels == 1 ? 1 : 2; + device->spec.format = device->spec.format == SDL_AUDIO_S8 ? SDL_AUDIO_S8 : SDL_AUDIO_S16; - format.bits = _this->spec.format == SDL_AUDIO_S8 ? 8 : 16; - format.freq = _this->spec.freq; - format.channels = _this->spec.channels; + format.bits = device->spec.format == SDL_AUDIO_S8 ? 8 : 16; + format.freq = device->spec.freq; + format.channels = device->spec.channels; - _this->hidden->channel = audsrv_set_format(&format); + device->hidden->channel = audsrv_set_format(&format); audsrv_set_volume(MAX_VOLUME); if (_this->hidden->channel < 0) { @@ -71,62 +71,62 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *_this) } /* Update the fragment size as size in bytes. */ - SDL_UpdatedAudioDeviceFormat(_this); + SDL_UpdatedAudioDeviceFormat(device); /* Allocate the mixing buffer. Its size and starting address must be a multiple of 64 bytes. Our sample count is already a multiple of 64, so spec->size should be a multiple of 64 as well. */ - mixlen = _this->buffer_size * NUM_BUFFERS; - _this->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen); - if (_this->hidden->rawbuf == NULL) { + mixlen = device->buffer_size * NUM_BUFFERS; + device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen); + if (device->hidden->rawbuf == NULL) { return SDL_SetError("Couldn't allocate mixing buffer"); } - SDL_memset(_this->hidden->rawbuf, 0, mixlen); + SDL_memset(device->hidden->rawbuf, 0, mixlen); for (i = 0; i < NUM_BUFFERS; i++) { - _this->hidden->mixbufs[i] = &_this->hidden->rawbuf[i * _this->buffer_size]; + device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size]; } - _this->hidden->next_buffer = 0; + device->hidden->next_buffer = 0; return 0; } -static void PS2AUDIO_PlayDevice(SDL_AudioDevice *_this, const Uint8 *buffer, int buflen) +static void PS2AUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { audsrv_play_audio((char *)buffer, buflen); } /* This function waits until it is possible to write a full sound buffer */ -static void PS2AUDIO_WaitDevice(SDL_AudioDevice *_this) +static void PS2AUDIO_WaitDevice(SDL_AudioDevice *device) { - audsrv_wait_audio(_this->buffer_size); + audsrv_wait_audio(device->buffer_size); } -static Uint8 *PS2AUDIO_GetDeviceBuf(SDL_AudioDevice *_this, int *buffer_size) +static Uint8 *PS2AUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - Uint8 *buffer = _this->hidden->mixbufs[_this->hidden->next_buffer]; - _this->hidden->next_buffer = (_this->hidden->next_buffer + 1) % NUM_BUFFERS; + Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer]; + device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS; return buffer;; } -static void PS2AUDIO_CloseDevice(SDL_AudioDevice *_this) +static void PS2AUDIO_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden) { - if (_this->hidden->channel >= 0) { + if (device->hidden) { + if (device->hidden->channel >= 0) { audsrv_stop_audio(); - _this->hidden->channel = -1; + device->hidden->channel = -1; } if (_this->hidden->rawbuf != NULL) { SDL_aligned_free(_this->hidden->rawbuf); _this->hidden->rawbuf = NULL; } - SDL_free(_this->hidden); - _this->hidden = NULL; + SDL_free(device->hidden); + device->hidden = NULL; } } -static void PS2AUDIO_ThreadInit(SDL_AudioDevice *_this) +static void PS2AUDIO_ThreadInit(SDL_AudioDevice *device) { /* Increase the priority of this audio thread by 1 to put it ahead of other SDL threads. */ From 6f12f68ec91903fb88ecf84819fbce93779bf1d9 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 02:14:20 -0400 Subject: [PATCH 075/138] ps2audio: SDL3ified the style --- src/audio/ps2/SDL_ps2audio.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/audio/ps2/SDL_ps2audio.c b/src/audio/ps2/SDL_ps2audio.c index 11fedc53b3..6315678a2b 100644 --- a/src/audio/ps2/SDL_ps2audio.c +++ b/src/audio/ps2/SDL_ps2audio.c @@ -29,16 +29,12 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *device) { - int i, mixlen; - struct audsrv_fmt_t format; - - device->hidden = (struct SDL_PrivateAudioData *) - SDL_calloc(1, sizeof(*device->hidden)); + device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); if (device->hidden == NULL) { return SDL_OutOfMemory(); } - /* These are the native supported audio PS2 configs */ + // These are the native supported audio PS2 configs switch (device->spec.freq) { case 11025: case 12000: @@ -57,6 +53,7 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *device) device->spec.channels = device->spec.channels == 1 ? 1 : 2; device->spec.format = device->spec.format == SDL_AUDIO_S8 ? SDL_AUDIO_S8 : SDL_AUDIO_S16; + struct audsrv_fmt_t format; format.bits = device->spec.format == SDL_AUDIO_S8 ? 8 : 16; format.freq = device->spec.freq; format.channels = device->spec.channels; @@ -70,24 +67,23 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *device) return SDL_SetError("Couldn't reserve hardware channel"); } - /* Update the fragment size as size in bytes. */ + // Update the fragment size as size in bytes. SDL_UpdatedAudioDeviceFormat(device); /* Allocate the mixing buffer. Its size and starting address must be a multiple of 64 bytes. Our sample count is already a multiple of 64, so spec->size should be a multiple of 64 as well. */ - mixlen = device->buffer_size * NUM_BUFFERS; + const int mixlen = device->buffer_size * NUM_BUFFERS; device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen); if (device->hidden->rawbuf == NULL) { return SDL_SetError("Couldn't allocate mixing buffer"); } - SDL_memset(device->hidden->rawbuf, 0, mixlen); - for (i = 0; i < NUM_BUFFERS; i++) { + SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen); + for (int i = 0; i < NUM_BUFFERS; i++) { device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size]; } - device->hidden->next_buffer = 0; return 0; } @@ -96,7 +92,6 @@ static void PS2AUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, in audsrv_play_audio((char *)buffer, buflen); } -/* This function waits until it is possible to write a full sound buffer */ static void PS2AUDIO_WaitDevice(SDL_AudioDevice *device) { audsrv_wait_audio(device->buffer_size); @@ -148,7 +143,6 @@ static SDL_bool PS2AUDIO_Init(SDL_AudioDriverImpl *impl) return SDL_FALSE; } - /* Set the function pointers */ impl->OpenDevice = PS2AUDIO_OpenDevice; impl->PlayDevice = PS2AUDIO_PlayDevice; impl->WaitDevice = PS2AUDIO_WaitDevice; @@ -157,7 +151,7 @@ static SDL_bool PS2AUDIO_Init(SDL_AudioDriverImpl *impl) impl->ThreadInit = PS2AUDIO_ThreadInit; impl->Deinitialize = PS2AUDIO_Deinitialize; impl->OnlyHasDefaultOutputDevice = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; // this audio target is available. } AudioBootStrap PS2AUDIO_bootstrap = { From 00ed6f8827f7f2378bb603065a13ba8a537c3ea1 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 02:22:33 -0400 Subject: [PATCH 076/138] test: Fixed compiler warnings for unused vars. --- test/testautomation_audio.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/test/testautomation_audio.c b/test/testautomation_audio.c index 5d6dd4e835..2c07ab978a 100644 --- a/test/testautomation_audio.c +++ b/test/testautomation_audio.c @@ -38,6 +38,7 @@ static void audioTearDown(void *arg) SDLTest_AssertPass("Cleanup of test files completed"); } +#if 0 /* !!! FIXME: maybe update this? */ /* Global counter for callback invocation */ static int g_audio_testCallbackCounter; @@ -51,6 +52,7 @@ static void SDLCALL audio_testCallback(void *userdata, Uint8 *stream, int len) g_audio_testCallbackCounter++; g_audio_testCallbackLength += len; } +#endif static SDL_AudioDeviceID g_audio_id = -1; @@ -225,10 +227,6 @@ static int audio_initOpenCloseQuitAudio(void *arg) static int audio_pauseUnpauseAudio(void *arg) { int result; - int i, iMax, j, k, l; - int totalDelay; - int pause_on; - int originalCounter; const char *audioDriver; SDL_AudioSpec desired; @@ -237,17 +235,17 @@ static int audio_pauseUnpauseAudio(void *arg) SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_AUDIO)"); /* Loop over all available audio drivers */ - iMax = SDL_GetNumAudioDrivers(); + const int iMax = SDL_GetNumAudioDrivers(); SDLTest_AssertPass("Call to SDL_GetNumAudioDrivers()"); SDLTest_AssertCheck(iMax > 0, "Validate number of audio drivers; expected: >0 got: %d", iMax); - for (i = 0; i < iMax; i++) { + for (int i = 0; i < iMax; i++) { audioDriver = SDL_GetAudioDriver(i); SDLTest_AssertPass("Call to SDL_GetAudioDriver(%d)", i); SDLTest_Assert(audioDriver != NULL, "Audio driver name is not NULL"); SDLTest_AssertCheck(audioDriver[0] != '\0', "Audio driver name is not empty; got: %s", audioDriver); /* NOLINT(clang-analyzer-core.NullDereference): Checked for NULL above */ /* Change specs */ - for (j = 0; j < 2; j++) { + for (int j = 0; j < 2; j++) { /* Call Init */ SDL_SetHint("SDL_AUDIO_DRIVER", audioDriver); @@ -281,7 +279,7 @@ static int audio_pauseUnpauseAudio(void *arg) #if 0 /* !!! FIXME: maybe update this? */ /* Start and stop audio multiple times */ - for (l = 0; l < 3; l++) { + for (int l = 0; l < 3; l++) { SDLTest_Log("Pause/Unpause iteration: %d", l + 1); /* Reset callback counters */ @@ -289,14 +287,13 @@ static int audio_pauseUnpauseAudio(void *arg) g_audio_testCallbackLength = 0; /* Un-pause audio to start playing (maybe multiple times) */ - pause_on = 0; for (k = 0; k <= j; k++) { SDL_PlayAudioDevice(g_audio_id); SDLTest_AssertPass("Call to SDL_PlayAudioDevice(g_audio_id), call %d", k + 1); } /* Wait for callback */ - totalDelay = 0; + int totalDelay = 0; do { SDL_Delay(10); totalDelay += 10; @@ -305,8 +302,8 @@ static int audio_pauseUnpauseAudio(void *arg) SDLTest_AssertCheck(g_audio_testCallbackLength > 0, "Verify callback length; expected: >0 got: %d", g_audio_testCallbackLength); /* Pause audio to stop playing (maybe multiple times) */ - for (k = 0; k <= j; k++) { - pause_on = (k == 0) ? 1 : SDLTest_RandomIntegerInRange(99, 9999); + for (int k = 0; k <= j; k++) { + const int pause_on = (k == 0) ? 1 : SDLTest_RandomIntegerInRange(99, 9999); if (pause_on) { SDL_PauseAudioDevice(g_audio_id); SDLTest_AssertPass("Call to SDL_PauseAudioDevice(g_audio_id), call %d", k + 1); @@ -317,7 +314,7 @@ static int audio_pauseUnpauseAudio(void *arg) } /* Ensure callback is not called again */ - originalCounter = g_audio_testCallbackCounter; + const int originalCounter = g_audio_testCallbackCounter; SDL_Delay(totalDelay + 10); SDLTest_AssertCheck(originalCounter == g_audio_testCallbackCounter, "Verify callback counter; expected: %d, got: %d", originalCounter, g_audio_testCallbackCounter); } @@ -348,9 +345,9 @@ static int audio_pauseUnpauseAudio(void *arg) */ static int audio_enumerateAndNameAudioDevices(void *arg) { - int t, tt; - int i, n, nn; - char *name, *nameAgain; + int t; + int i, n; + char *name; SDL_AudioDeviceID *devices = NULL; /* Iterate over types: t=0 output device, t=1 input/capture device */ From 3d6ba0cafd30f1469a61be2fa14a6b08e646e653 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 02:43:54 -0400 Subject: [PATCH 077/138] ps2audio: Removed free of buffer that hasn't been allocated yet. --- src/audio/ps2/SDL_ps2audio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/audio/ps2/SDL_ps2audio.c b/src/audio/ps2/SDL_ps2audio.c index 6315678a2b..22d841d47f 100644 --- a/src/audio/ps2/SDL_ps2audio.c +++ b/src/audio/ps2/SDL_ps2audio.c @@ -62,8 +62,6 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *device) audsrv_set_volume(MAX_VOLUME); if (_this->hidden->channel < 0) { - SDL_aligned_free(_this->hidden->rawbuf); - _this->hidden->rawbuf = NULL; return SDL_SetError("Couldn't reserve hardware channel"); } @@ -101,7 +99,7 @@ static Uint8 *PS2AUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer]; device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS; - return buffer;; + return buffer; } static void PS2AUDIO_CloseDevice(SDL_AudioDevice *device) From 121a2dce1562a94eb18c90d92a433f51f8ebac82 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 02:44:52 -0400 Subject: [PATCH 078/138] audio: Make sure `device->hidden` is NULL after CloseDevice --- src/audio/SDL_audio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 5ce08c2684..6ddaafe76b 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -1091,6 +1091,7 @@ static void ClosePhysicalAudioDevice(SDL_AudioDevice *device) if (device->is_opened) { current_audio.impl.CloseDevice(device); // if ProvidesOwnCallbackThread, this must join on any existing device thread before returning! device->is_opened = SDL_FALSE; + device->hidden = NULL; // just in case. } if (device->work_buffer) { From 1bfe97c2350adc8cfd9543b5508c8f44c67fe116 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 02:44:59 -0400 Subject: [PATCH 079/138] pspaudio: Updated for SDL3 audio API. However, this still blocks in PlayDevice and leaves WaitDevice as a no-op, which isn't ideal, since the device lock is held during PlayDevice. Ideally, this should be fixed. --- src/audio/psp/SDL_pspaudio.c | 131 ++++++++++++++++------------------- 1 file changed, 59 insertions(+), 72 deletions(-) diff --git a/src/audio/psp/SDL_pspaudio.c b/src/audio/psp/SDL_pspaudio.c index e43f692e1a..d0a796ce73 100644 --- a/src/audio/psp/SDL_pspaudio.c +++ b/src/audio/psp/SDL_pspaudio.c @@ -34,41 +34,36 @@ #include #include -/* The tag name used by PSP audio */ -#define PSPAUDIO_DRIVER_NAME "psp" - static inline SDL_bool isBasicAudioConfig(const SDL_AudioSpec *spec) { return spec->freq == 44100; } -static int PSPAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int PSPAUDIO_OpenDevice(SDL_AudioDevice *device) { int format, mixlen, i; - _this->hidden = (struct SDL_PrivateAudioData *) - SDL_malloc(sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(_this->hidden); - /* device only natively supports S16LSB */ - _this->spec.format = SDL_AUDIO_S16LSB; + // device only natively supports S16LSB + device->spec.format = SDL_AUDIO_S16LSB; /* PSP has some limitations with the Audio. It fully supports 44.1KHz (Mono & Stereo), however with frequencies different than 44.1KHz, it just supports Stereo, so a resampler must be done for these scenarios */ - if (isBasicAudioConfig(&_this->spec)) { - /* The sample count must be a multiple of 64. */ - _this->spec.samples = PSP_AUDIO_SAMPLE_ALIGN(_this->spec.samples); - /* The number of channels (1 or 2). */ - _this->spec.channels = _this->spec.channels == 1 ? 1 : 2; - format = _this->spec.channels == 1 ? PSP_AUDIO_FORMAT_MONO : PSP_AUDIO_FORMAT_STEREO; - _this->hidden->channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, _this->spec.samples, format); + if (isBasicAudioConfig(&device->spec)) { + // The sample count must be a multiple of 64. + device->sample_frames = PSP_AUDIO_SAMPLE_ALIGN(device->sample_frames); + // The number of channels (1 or 2). + device->spec.channels = device->spec.channels == 1 ? 1 : 2; + format = (device->spec.channels == 1) ? PSP_AUDIO_FORMAT_MONO : PSP_AUDIO_FORMAT_STEREO; + device->hidden->channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, device->spec.samples, format); } else { - /* 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11050, 8000 */ - switch (_this->spec.freq) { + // 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11050, 8000 + switch (device->spec.freq) { case 8000: case 11025: case 12000: @@ -78,93 +73,90 @@ static int PSPAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) case 32000: case 44100: case 48000: - _this->spec.freq = _this->spec.freq; - break; + break; // acceptable, keep it default: - _this->spec.freq = 48000; + device->spec.freq = 48000; break; } - /* The number of samples to output in one output call (min 17, max 4111). */ - _this->spec.samples = _this->spec.samples < 17 ? 17 : (_this->spec.samples > 4111 ? 4111 : _this->spec.samples); - _this->spec.channels = 2; /* we're forcing the hardware to stereo. */ - _this->hidden->channel = sceAudioSRCChReserve(_this->spec.samples, _this->spec.freq, 2); + // The number of samples to output in one output call (min 17, max 4111). + device->sample_frames = device->sample_frames < 17 ? 17 : (device->sample_frames > 4111 ? 4111 : device->sample_frames); + device->spec.channels = 2; // we're forcing the hardware to stereo. + device->hidden->channel = sceAudioSRCChReserve(device->sample_frames, device->spec.freq, 2); } if (_this->hidden->channel < 0) { - SDL_aligned_free(_this->hidden->rawbuf); - _this->hidden->rawbuf = NULL; return SDL_SetError("Couldn't reserve hardware channel"); } - /* Update the fragment size as size in bytes. */ - SDL_CalculateAudioSpec(&_this->spec); + // Update the fragment size as size in bytes. + SDL_UpdatedAudioDeviceFormat(device); /* Allocate the mixing buffer. Its size and starting address must be a multiple of 64 bytes. Our sample count is already a multiple of 64, so spec->size should be a multiple of 64 as well. */ - mixlen = _this->spec.size * NUM_BUFFERS; - _this->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen); - if (_this->hidden->rawbuf == NULL) { + mixlen = device->buffer_size * NUM_BUFFERS; + device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen); + if (device->hidden->rawbuf == NULL) { return SDL_SetError("Couldn't allocate mixing buffer"); } - SDL_memset(_this->hidden->rawbuf, 0, mixlen); + SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen); for (i = 0; i < NUM_BUFFERS; i++) { - _this->hidden->mixbufs[i] = &_this->hidden->rawbuf[i * _this->spec.size]; + device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size]; } - _this->hidden->next_buffer = 0; return 0; } -static void PSPAUDIO_PlayDevice(SDL_AudioDevice *_this) +static void PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { - Uint8 *mixbuf = _this->hidden->mixbufs[_this->hidden->next_buffer]; - if (!isBasicAudioConfig(&_this->spec)) { - SDL_assert(_this->spec.channels == 2); - sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, mixbuf); + if (!isBasicAudioConfig(&device->spec)) { + SDL_assert(device->spec.channels == 2); + sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, buffer); } else { - sceAudioOutputPannedBlocking(_this->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, mixbuf); + sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, buffer); } - - _this->hidden->next_buffer = (_this->hidden->next_buffer + 1) % NUM_BUFFERS; } -/* This function waits until it is possible to write a full sound buffer */ -static void PSPAUDIO_WaitDevice(SDL_AudioDevice *_this) +static void PSPAUDIO_WaitDevice(SDL_AudioDevice *device) { - /* Because we block when sending audio, there's no need for this function to do anything. */ + // Because we block when sending audio, there's no need for this function to do anything. } -static Uint8 *PSPAUDIO_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *PSPAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - return _this->hidden->mixbufs[_this->hidden->next_buffer]; + Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer]; + device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS; + return buffer; } -static void PSPAUDIO_CloseDevice(SDL_AudioDevice *_this) +static void PSPAUDIO_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden->channel >= 0) { - if (!isBasicAudioConfig(&_this->spec)) { - sceAudioSRCChRelease(); - } else { - sceAudioChRelease(_this->hidden->channel); + if (device->hidden) { + if (device->hidden->channel >= 0) { + if (!isBasicAudioConfig(&device->spec)) { + sceAudioSRCChRelease(); + } else { + sceAudioChRelease(device->hidden->channel); + } + device->hidden->channel = -1; } - _this->hidden->channel = -1; - } - if (_this->hidden->rawbuf != NULL) { - SDL_aligned_free(_this->hidden->rawbuf); - _this->hidden->rawbuf = NULL; + if (device->hidden->rawbuf != NULL) { + SDL_aligned_free(_this->hidden->rawbuf); + device->hidden->rawbuf = NULL; + } + SDL_free(device->hidden); + device->hidden = NULL; } } -static void PSPAUDIO_ThreadInit(SDL_AudioDevice *_this) +static void PSPAUDIO_ThreadInit(SDL_AudioDevice *device) { /* Increase the priority of this audio thread by 1 to put it ahead of other SDL threads. */ - SceUID thid; + const SceUID thid = sceKernelGetThreadId() SceKernelThreadInfo status; - thid = sceKernelGetThreadId(); status.size = sizeof(SceKernelThreadInfo); if (sceKernelReferThreadStatus(thid, &status) == 0) { sceKernelChangeThreadPriority(thid, status.currentPriority - 1); @@ -173,25 +165,20 @@ static void PSPAUDIO_ThreadInit(SDL_AudioDevice *_this) static SDL_bool PSPAUDIO_Init(SDL_AudioDriverImpl *impl) { - /* Set the function pointers */ impl->OpenDevice = PSPAUDIO_OpenDevice; impl->PlayDevice = PSPAUDIO_PlayDevice; impl->WaitDevice = PSPAUDIO_WaitDevice; impl->GetDeviceBuf = PSPAUDIO_GetDeviceBuf; impl->CloseDevice = PSPAUDIO_CloseDevice; impl->ThreadInit = PSPAUDIO_ThreadInit; - - /* PSP audio device */ impl->OnlyHasDefaultOutputDevice = SDL_TRUE; - /* - impl->HasCaptureSupport = SDL_TRUE; - impl->OnlyHasDefaultCaptureDevice = SDL_TRUE; - */ - return SDL_TRUE; /* this audio target is available. */ + //impl->HasCaptureSupport = SDL_TRUE; + //impl->OnlyHasDefaultCaptureDevice = SDL_TRUE; + return SDL_TRUE; } AudioBootStrap PSPAUDIO_bootstrap = { "psp", "PSP audio driver", PSPAUDIO_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_PSP */ +#endif // SDL_AUDIO_DRIVER_PSP From 79cc29ba35db3be88e30654f59daae3b9549ef95 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 11:50:22 -0400 Subject: [PATCH 080/138] wave: Don't check if format->channels > INT_MAX, it's a Uint16. --- src/audio/SDL_wave.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/audio/SDL_wave.c b/src/audio/SDL_wave.c index 759d34d9e1..c1d50ebbc3 100644 --- a/src/audio/SDL_wave.c +++ b/src/audio/SDL_wave.c @@ -1667,8 +1667,6 @@ static int WaveCheckFormat(WaveFile *file, size_t datalength) if (format->channels == 0) { return SDL_SetError("Invalid number of channels"); - } else if (format->channels > INT_MAX) { - return SDL_SetError("Number of channels exceeds limit of %d", INT_MAX); } if (format->frequency == 0) { From a0528cd5eda0fb40e327f1f3ea6e4cc2c598e499 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 11:51:06 -0400 Subject: [PATCH 081/138] emscriptenaudio: Updated for SDL3 audio API. --- src/audio/emscripten/SDL_emscriptenaudio.c | 225 +++++++-------------- src/audio/emscripten/SDL_emscriptenaudio.h | 2 +- 2 files changed, 74 insertions(+), 153 deletions(-) diff --git a/src/audio/emscripten/SDL_emscriptenaudio.c b/src/audio/emscripten/SDL_emscriptenaudio.c index cdb4308f4c..f24df6dd8d 100644 --- a/src/audio/emscripten/SDL_emscriptenaudio.c +++ b/src/audio/emscripten/SDL_emscriptenaudio.c @@ -27,15 +27,18 @@ #include -/* !!! FIXME: this currently expects that the audio callback runs in the main thread, - !!! FIXME: in intervals when the application isn't running, but that may not be - !!! FIXME: true always once pthread support becomes widespread. Revisit this code - !!! FIXME: at some point and see what needs to be done for that! */ +// just turn off clang-format for this whole file, this INDENT_OFF stuff on +// each EM_ASM section is ugly. +/* *INDENT-OFF* */ /* clang-format off */ -static void FeedAudioDevice(SDL_AudioDevice *_this, const void *buf, const int buflen) +static Uint8 *EMSCRIPTENAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - const int framelen = (SDL_AUDIO_BITSIZE(_this->spec.format) / 8) * _this->spec.channels; - /* *INDENT-OFF* */ /* clang-format off */ + return device->hidden->mixbuf; +} + +static void EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) +{ + const int framelen = (SDL_AUDIO_BITSIZE(device->spec.format) / 8) * device->spec.channels; MAIN_THREAD_EM_ASM({ var SDL3 = Module['SDL3']; var numChannels = SDL3.audio.currentOutputBuffer['numberOfChannels']; @@ -46,65 +49,25 @@ static void FeedAudioDevice(SDL_AudioDevice *_this, const void *buf, const int b } for (var j = 0; j < $1; ++j) { - channelData[j] = HEAPF32[$0 + ((j*numChannels + c) << 2) >> 2]; /* !!! FIXME: why are these shifts here? */ + channelData[j] = HEAPF32[$0 + ((j*numChannels + c) << 2) >> 2]; // !!! FIXME: why are these shifts here? } } - }, buf, buflen / framelen); -/* *INDENT-ON* */ /* clang-format on */ + }, buffer, buffer_size / framelen); } -static void HandleAudioProcess(SDL_AudioDevice *_this) +static void HandleAudioProcess(SDL_AudioDevice *device) // this fires when the main thread is idle. { - SDL_AudioCallback callback = _this->callbackspec.callback; - const int stream_len = _this->callbackspec.size; - - /* Only do something if audio is enabled */ - if (!SDL_AtomicGet(&_this->enabled) || SDL_AtomicGet(&_this->paused)) { - if (_this->stream) { - SDL_ClearAudioStream(_this->stream); - } - - SDL_memset(_this->work_buffer, _this->spec.silence, _this->spec.size); - FeedAudioDevice(_this, _this->work_buffer, _this->spec.size); - return; - } - - if (_this->stream == NULL) { /* no conversion necessary. */ - SDL_assert(_this->spec.size == stream_len); - callback(_this->callbackspec.userdata, _this->work_buffer, stream_len); - } else { /* streaming/converting */ - int got; - while (SDL_GetAudioStreamAvailable(_this->stream) < ((int)_this->spec.size)) { - callback(_this->callbackspec.userdata, _this->work_buffer, stream_len); - if (SDL_PutAudioStreamData(_this->stream, _this->work_buffer, stream_len) == -1) { - SDL_ClearAudioStream(_this->stream); - SDL_AtomicSet(&_this->enabled, 0); - break; - } - } - - got = SDL_GetAudioStreamData(_this->stream, _this->work_buffer, _this->spec.size); - SDL_assert((got < 0) || (got == _this->spec.size)); - if (got != _this->spec.size) { - SDL_memset(_this->work_buffer, _this->spec.silence, _this->spec.size); - } - } - - FeedAudioDevice(_this, _this->work_buffer, _this->spec.size); + SDL_OutputAudioThreadIterate(device); } -static void HandleCaptureProcess(SDL_AudioDevice *_this) + +static void EMSCRIPTENAUDIO_FlushCapture(SDL_AudioDevice *device) { - SDL_AudioCallback callback = _this->callbackspec.callback; - const int stream_len = _this->callbackspec.size; + // Do nothing, the new data will just be dropped. +} - /* Only do something if audio is enabled */ - if (!SDL_AtomicGet(&_this->enabled) || SDL_AtomicGet(&_this->paused)) { - SDL_ClearAudioStream(_this->stream); - return; - } - - /* *INDENT-OFF* */ /* clang-format off */ +static int EMSCRIPTENAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) +{ MAIN_THREAD_EM_ASM({ var SDL3 = Module['SDL3']; var numChannels = SDL3.capture.currentCaptureBuffer.numberOfChannels; @@ -114,7 +77,7 @@ static void HandleCaptureProcess(SDL_AudioDevice *_this) throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!'; } - if (numChannels == 1) { /* fastpath this a little for the common (mono) case. */ + if (numChannels == 1) { // fastpath this a little for the common (mono) case. for (var j = 0; j < $1; ++j) { setValue($0 + (j * 4), channelData[j], 'float'); } @@ -124,33 +87,18 @@ static void HandleCaptureProcess(SDL_AudioDevice *_this) } } } - }, _this->work_buffer, (_this->spec.size / sizeof(float)) / _this->spec.channels); -/* *INDENT-ON* */ /* clang-format on */ + }, buffer, (buflen / sizeof(float)) / device->spec.channels); - /* okay, we've got an interleaved float32 array in C now. */ - - if (_this->stream == NULL) { /* no conversion necessary. */ - SDL_assert(_this->spec.size == stream_len); - callback(_this->callbackspec.userdata, _this->work_buffer, stream_len); - } else { /* streaming/converting */ - if (SDL_PutAudioStreamData(_this->stream, _this->work_buffer, _this->spec.size) == -1) { - SDL_AtomicSet(&_this->enabled, 0); - } - - while (SDL_GetAudioStreamAvailable(_this->stream) >= stream_len) { - const int got = SDL_GetAudioStreamData(_this->stream, _this->work_buffer, stream_len); - SDL_assert((got < 0) || (got == stream_len)); - if (got != stream_len) { - SDL_memset(_this->work_buffer, _this->callbackspec.silence, stream_len); - } - callback(_this->callbackspec.userdata, _this->work_buffer, stream_len); /* Send it to the app. */ - } - } + return buflen; } -static void EMSCRIPTENAUDIO_CloseDevice(SDL_AudioDevice *_this) +static void HandleCaptureProcess(SDL_AudioDevice *device) // this fires when the main thread is idle. +{ + SDL_CaptureAudioThreadIterate(device); +} + +static void EMSCRIPTENAUDIO_CloseDevice(SDL_AudioDevice *device) { - /* *INDENT-OFF* */ /* clang-format off */ MAIN_THREAD_EM_ASM({ var SDL3 = Module['SDL3']; if ($0) { @@ -188,29 +136,23 @@ static void EMSCRIPTENAUDIO_CloseDevice(SDL_AudioDevice *_this) SDL3.audioContext.close(); SDL3.audioContext = undefined; } - }, _this->iscapture); -/* *INDENT-ON* */ /* clang-format on */ + }, device->iscapture); -#if 0 /* !!! FIXME: currently not used. Can we move some stuff off the SDL3 namespace? --ryan. */ - SDL_free(_this->hidden); -#endif + if (!device->hidden) { + SDL_free(device->hidden->mixbuf); + SDL_free(device->hidden); + device->hidden = NULL; + } } - EM_JS_DEPS(sdlaudio, "$autoResumeAudioContext,$dynCall"); -static int EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device) { - SDL_AudioFormat test_format; - const SDL_AudioFormat *closefmts; - SDL_bool iscapture = _this->iscapture; - int result; + // based on parts of library_sdl.js - /* based on parts of library_sdl.js */ - - /* *INDENT-OFF* */ /* clang-format off */ - /* create context */ - result = MAIN_THREAD_EM_ASM_INT({ + // create context + const int result = MAIN_THREAD_EM_ASM_INT({ if (typeof(Module['SDL3']) === 'undefined') { Module['SDL3'] = {}; } @@ -232,57 +174,41 @@ static int EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devnam } } return SDL3.audioContext === undefined ? -1 : 0; - }, iscapture); -/* *INDENT-ON* */ /* clang-format on */ + }, device->iscapture); if (result < 0) { return SDL_SetError("Web Audio API is not available!"); } - closefmts = SDL_ClosestAudioFormats(_this->spec.format); - while ((test_format = *(closefmts++)) != 0) { - switch (test_format) { - case SDL_AUDIO_F32: /* web audio only supports floats */ - break; - default: - continue; - } - break; - } + device->spec.format = SDL_AUDIO_F32; // web audio only supports floats - if (!test_format) { - /* Didn't find a compatible format :( */ - return SDL_SetError("%s: Unsupported audio format", "emscripten"); - } - _this->spec.format = test_format; - - /* Initialize all variables that we clean on shutdown */ -#if 0 /* !!! FIXME: currently not used. Can we move some stuff off the SDL3 namespace? --ryan. */ - _this->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + // Initialize all variables that we clean on shutdown + device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(_this->hidden); -#endif - _this->hidden = (struct SDL_PrivateAudioData *)0x1; - /* limit to native freq */ - _this->spec.freq = EM_ASM_INT({ - var SDL3 = Module['SDL3']; - return SDL3.audioContext.sampleRate; - }); + // limit to native freq + device->spec.freq = EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; }); - SDL_CalculateAudioSpec(&_this->spec); + SDL_UpdatedAudioDeviceFormat(device); - /* *INDENT-OFF* */ /* clang-format off */ - if (iscapture) { + if (!device->iscapture) { + device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); + if (device->hidden->mixbuf == NULL) { + return SDL_OutOfMemory(); + } + SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); + } + + if (device->iscapture) { /* The idea is to take the capture media stream, hook it up to an audio graph where we can pass it through a ScriptProcessorNode to access the raw PCM samples and push them to the SDL app's callback. From there, we "process" the audio data into silence - and forget about it. */ + and forget about it. - /* This should, strictly speaking, use MediaRecorder for capture, but + This should, strictly speaking, use MediaRecorder for capture, but this API is cleaner to use and better supported, and fires a callback whenever there's enough data to fire down into the app. The downside is that we are spending CPU time silencing a buffer @@ -317,7 +243,7 @@ static int EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devnam //console.log('SDL audio capture: we DO NOT have a microphone! (' + error.name + ')...leaving silence callback running.'); }; - /* we write silence to the audio callback until the microphone is available (user approves use, etc). */ + // we write silence to the audio callback until the microphone is available (user approves use, etc). SDL3.capture.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate); SDL3.capture.silenceBuffer.getChannelData(0).fill(0.0); var silence_callback = function() { @@ -332,9 +258,9 @@ static int EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devnam } else if (navigator.webkitGetUserMedia !== undefined) { navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone); } - }, _this->spec.channels, _this->spec.samples, HandleCaptureProcess, _this); + }, device->spec.channels, device->sample_frames, HandleCaptureProcess, device); } else { - /* setup a ScriptProcessorNode */ + // setup a ScriptProcessorNode MAIN_THREAD_EM_ASM({ var SDL3 = Module['SDL3']; SDL3.audio.scriptProcessorNode = SDL3.audioContext['createScriptProcessor']($1, 0, $0); @@ -344,33 +270,29 @@ static int EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devnam dynCall('vi', $2, [$3]); }; SDL3.audio.scriptProcessorNode['connect'](SDL3.audioContext['destination']); - }, _this->spec.channels, _this->spec.samples, HandleAudioProcess, _this); + }, device->spec.channels, device->sample_frames, HandleAudioProcess, device); } -/* *INDENT-ON* */ /* clang-format on */ return 0; } -static void EMSCRIPTENAUDIO_LockOrUnlockDeviceWithNoMixerLock(SDL_AudioDevice *device) -{ -} - static SDL_bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl) { SDL_bool available, capture_available; - /* Set the function pointers */ impl->OpenDevice = EMSCRIPTENAUDIO_OpenDevice; impl->CloseDevice = EMSCRIPTENAUDIO_CloseDevice; + impl->GetDeviceBuf = EMSCRIPTENAUDIO_GetDeviceBuf; + impl->PlayDevice = EMSCRIPTENAUDIO_PlayDevice; + impl->FlushCapture = EMSCRIPTENAUDIO_FlushCapture; + impl->CaptureFromDevice = EMSCRIPTENAUDIO_CaptureFromDevice; impl->OnlyHasDefaultOutputDevice = SDL_TRUE; - /* no threads here */ - impl->LockDevice = impl->UnlockDevice = EMSCRIPTENAUDIO_LockOrUnlockDeviceWithNoMixerLock; + // technically, this is just runs in idle time in the main thread, but it's close enough to a "thread" for our purposes. impl->ProvidesOwnCallbackThread = SDL_TRUE; - /* *INDENT-OFF* */ /* clang-format off */ - /* check availability */ + // check availability available = MAIN_THREAD_EM_ASM_INT({ if (typeof(AudioContext) !== 'undefined') { return true; @@ -378,14 +300,12 @@ static SDL_bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl) return true; } return false; - }); -/* *INDENT-ON* */ /* clang-format on */ + }) ? SDL_TRUE : SDL_FALSE; if (!available) { SDL_SetError("No audio context available"); } - /* *INDENT-OFF* */ /* clang-format off */ capture_available = available && MAIN_THREAD_EM_ASM_INT({ if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) { return true; @@ -393,8 +313,7 @@ static SDL_bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl) return true; } return false; - }); -/* *INDENT-ON* */ /* clang-format on */ + }) ? SDL_TRUE : SDL_FALSE; impl->HasCaptureSupport = capture_available ? SDL_TRUE : SDL_FALSE; impl->OnlyHasDefaultCaptureDevice = capture_available ? SDL_TRUE : SDL_FALSE; @@ -406,4 +325,6 @@ AudioBootStrap EMSCRIPTENAUDIO_bootstrap = { "emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_EMSCRIPTEN */ +/* *INDENT-ON* */ /* clang-format on */ + +#endif // SDL_AUDIO_DRIVER_EMSCRIPTEN diff --git a/src/audio/emscripten/SDL_emscriptenaudio.h b/src/audio/emscripten/SDL_emscriptenaudio.h index db9c737f5a..cc2f49b273 100644 --- a/src/audio/emscripten/SDL_emscriptenaudio.h +++ b/src/audio/emscripten/SDL_emscriptenaudio.h @@ -27,7 +27,7 @@ struct SDL_PrivateAudioData { - int unused; + Uint8 *mixbuf; }; #endif /* SDL_emscriptenaudio_h_ */ From 4233c41ce2b35e5ff1dffca14adf7b42e751980c Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 11:51:38 -0400 Subject: [PATCH 082/138] pulseaudio: Removed unnecessary variable. --- src/audio/pulseaudio/SDL_pulseaudio.c | 5 ++--- src/audio/pulseaudio/SDL_pulseaudio.h | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index c0462f786a..50d2072e79 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -640,8 +640,7 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) /* Allocate mixing buffer */ if (!iscapture) { - h->mixlen = device->buffer_size; - h->mixbuf = (Uint8 *)SDL_malloc(h->mixlen); + h->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); if (h->mixbuf == NULL) { return SDL_OutOfMemory(); } @@ -653,7 +652,7 @@ static int PULSEAUDIO_OpenDevice(SDL_AudioDevice *device) /* Reduced prebuffering compared to the defaults. */ paattr.fragsize = device->buffer_size; - paattr.tlength = h->mixlen; + paattr.tlength = device->buffer_size; paattr.prebuf = -1; paattr.maxlength = -1; paattr.minreq = -1; diff --git a/src/audio/pulseaudio/SDL_pulseaudio.h b/src/audio/pulseaudio/SDL_pulseaudio.h index 709f00daaf..10a7d9d121 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.h +++ b/src/audio/pulseaudio/SDL_pulseaudio.h @@ -36,7 +36,6 @@ struct SDL_PrivateAudioData /* Raw mixing buffer */ Uint8 *mixbuf; - int mixlen; int bytes_requested; /* bytes of data the hardware wants _now_. */ From fc7ed18ca1ee78c56c89595f66a1b5c13c664e49 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 15:22:56 -0400 Subject: [PATCH 083/138] emscriptenaudio: don't forget to finalize the audio thread --- src/audio/emscripten/SDL_emscriptenaudio.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/audio/emscripten/SDL_emscriptenaudio.c b/src/audio/emscripten/SDL_emscriptenaudio.c index f24df6dd8d..142d9e5663 100644 --- a/src/audio/emscripten/SDL_emscriptenaudio.c +++ b/src/audio/emscripten/SDL_emscriptenaudio.c @@ -99,6 +99,10 @@ static void HandleCaptureProcess(SDL_AudioDevice *device) // this fires when th static void EMSCRIPTENAUDIO_CloseDevice(SDL_AudioDevice *device) { + if (!device->hidden) { + return; + } + MAIN_THREAD_EM_ASM({ var SDL3 = Module['SDL3']; if ($0) { @@ -138,11 +142,11 @@ static void EMSCRIPTENAUDIO_CloseDevice(SDL_AudioDevice *device) } }, device->iscapture); - if (!device->hidden) { - SDL_free(device->hidden->mixbuf); - SDL_free(device->hidden); - device->hidden = NULL; - } + SDL_free(device->hidden->mixbuf); + SDL_free(device->hidden); + device->hidden = NULL; + + SDL_AudioThreadFinalize(device); } EM_JS_DEPS(sdlaudio, "$autoResumeAudioContext,$dynCall"); From 51ae78c0af93089fb736e354a6b8bca16d4410fa Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 15:23:03 -0400 Subject: [PATCH 084/138] haikuaudio: Updated for SDL3 audio API. --- src/audio/haiku/SDL_haikuaudio.cc | 152 +++++++++++++----------------- src/audio/haiku/SDL_haikuaudio.h | 2 + 2 files changed, 70 insertions(+), 84 deletions(-) diff --git a/src/audio/haiku/SDL_haikuaudio.cc b/src/audio/haiku/SDL_haikuaudio.cc index c9915839e5..c34142f346 100644 --- a/src/audio/haiku/SDL_haikuaudio.cc +++ b/src/audio/haiku/SDL_haikuaudio.cc @@ -22,7 +22,7 @@ #ifdef SDL_AUDIO_DRIVER_HAIKU -/* Allow access to the audio stream on Haiku */ +// Allow access to the audio stream on Haiku #include #include @@ -38,58 +38,45 @@ extern "C" } - -/* !!! FIXME: have the callback call the higher level to avoid code dupe. */ -/* The Haiku callback for handling the audio buffer */ -static void FillSound(void *device, void *stream, size_t len, - const media_raw_audio_format & format) +static Uint8 *HAIKUAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - SDL_AudioDevice *audio = (SDL_AudioDevice *) device; - SDL_AudioCallback callback = audio->callbackspec.callback; - - SDL_LockMutex(audio->mixer_lock); - - /* Only do something if audio is enabled */ - if (!SDL_AtomicGet(&audio->enabled) || SDL_AtomicGet(&audio->paused)) { - if (audio->stream) { - SDL_ClearAudioStream(audio->stream); - } - SDL_memset(stream, audio->spec.silence, len); - } else { - SDL_assert(audio->spec.size == len); - - if (audio->stream == NULL) { /* no conversion necessary. */ - callback(audio->callbackspec.userdata, (Uint8 *) stream, len); - } else { /* streaming/converting */ - const int stream_len = audio->callbackspec.size; - const int ilen = (int) len; - while (SDL_GetAudioStreamAvailable(audio->stream) < ilen) { - callback(audio->callbackspec.userdata, audio->work_buffer, stream_len); - if (SDL_PutAudioStreamData(audio->stream, audio->work_buffer, stream_len) == -1) { - SDL_ClearAudioStream(audio->stream); - SDL_AtomicSet(&audio->enabled, 0); - break; - } - } - - const int got = SDL_GetAudioStreamData(audio->stream, stream, ilen); - SDL_assert((got < 0) || (got == ilen)); - if (got != ilen) { - SDL_memset(stream, audio->spec.silence, len); - } - } - } - - SDL_UnlockMutex(audio->mixer_lock); + SDL_assert(device->hidden->current_buffer != NULL); + SDL_assert(device->hidden->current_buffer_len > 0); + *buffer_size = device->hidden->current_buffer_len; + return device->hidden->current_buffer; } -static void HAIKUAUDIO_CloseDevice(SDL_AudioDevice *_this) +static void HAIKUAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { - if (_this->hidden->audio_obj) { - _this->hidden->audio_obj->Stop(); - delete _this->hidden->audio_obj; + // We already wrote our output right into the BSoundPlayer's callback's stream. Just clean up our stuff. + SDL_assert(device->hidden->current_buffer != NULL); + SDL_assert(device->hidden->current_buffer_len > 0); + device->hidden->current_buffer = NULL; + device->hidden->current_buffer_len = 0; +} + +// The Haiku callback for handling the audio buffer +static void FillSound(void *data, void *stream, size_t len, const media_raw_audio_format & format) +{ + SDL_AudioDevice *device = (SDL_AudioDevice *)data; + SDL_assert(device->hidden->current_buffer == NULL); + SDL_assert(device->hidden->current_buffer_len == 0); + device->hidden->current_buffer = (Uint8 *) stream; + device->hidden->current_buffer_len = (int) len; + SDL_OutputAudioThreadIterate(device); +} + +static void HAIKUAUDIO_CloseDevice(SDL_AudioDevice *device) +{ + if (device->hidden) { + if (device->hidden->audio_obj) { + device->hidden->audio_obj->Stop(); + delete device->hidden->audio_obj; + } + delete device->hidden; + device->hidden = NULL; + SDL_AudioThreadFinalize(device); } - delete _this->hidden; } @@ -115,26 +102,24 @@ static inline void UnmaskSignals(sigset_t * omask) } -static int HAIKUAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int HAIKUAUDIO_OpenDevice(SDL_AudioDevice *device) { - media_raw_audio_format format; - SDL_AudioFormat test_format; - const SDL_AudioFormat *closefmts; - - /* Initialize all variables that we clean on shutdown */ - _this->hidden = new SDL_PrivateAudioData; - if (_this->hidden == NULL) { + // Initialize all variables that we clean on shutdown + device->hidden = new SDL_PrivateAudioData; + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(_this->hidden); + SDL_zerop(device->hidden); - /* Parse the audio format and fill the Be raw audio format */ + // Parse the audio format and fill the Be raw audio format + media_raw_audio_format format; SDL_zero(format); format.byte_order = B_MEDIA_LITTLE_ENDIAN; - format.frame_rate = (float) _this->spec.freq; - format.channel_count = _this->spec.channels; /* !!! FIXME: support > 2? */ + format.frame_rate = (float) device->spec.freq; + format.channel_count = device->spec.channels; // !!! FIXME: support > 2? - closefmts = SDL_ClosestAudioFormats(_this->spec.format); + SDL_AudioFormat test_format; + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { switch (test_format) { case SDL_AUDIO_S8: @@ -178,31 +163,30 @@ static int HAIKUAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) break; } - if (!test_format) { /* shouldn't happen, but just in case... */ - return SDL_SetError("%s: Unsupported audio format", "haiku"); + if (!test_format) { // shouldn't happen, but just in case... + return SDL_SetError("HAIKU: Unsupported audio format"); } - _this->spec.format = test_format; + device->spec.format = test_format; - /* Calculate the final parameters for this audio specification */ - SDL_CalculateAudioSpec(&_this->spec); + // Calculate the final parameters for this audio specification + SDL_UpdatedAudioDeviceFormat(device); - format.buffer_size = _this->spec.size; + format.buffer_size = device->buffer_size; - /* Subscribe to the audio stream (creates a new thread) */ + // Subscribe to the audio stream (creates a new thread) sigset_t omask; MaskSignals(&omask); - _this->hidden->audio_obj = new BSoundPlayer(&format, "SDL Audio", - FillSound, NULL, _this); + device->hidden->audio_obj = new BSoundPlayer(&format, "SDL Audio", + FillSound, NULL, device); UnmaskSignals(&omask); - if (_this->hidden->audio_obj->Start() == B_NO_ERROR) { - _this->hidden->audio_obj->SetHasData(true); + if (device->hidden->audio_obj->Start() == B_NO_ERROR) { + device->hidden->audio_obj->SetHasData(true); } else { - return SDL_SetError("Unable to start Be audio"); + return SDL_SetError("Unable to start Haiku audio"); } - /* We're running! */ - return 0; + return 0; // We're running! } static void HAIKUAUDIO_Deinitialize(void) @@ -210,29 +194,29 @@ static void HAIKUAUDIO_Deinitialize(void) SDL_QuitBeApp(); } -static SDL_bool HAIKUAUDIO_Init(SDL_AudioDriverImpl * impl) +static SDL_bool HAIKUAUDIO_Init(SDL_AudioDriverImpl *impl) { - /* Initialize the Be Application, if it's not already started */ if (SDL_InitBeApp() < 0) { return SDL_FALSE; } - /* Set the function pointers */ + // Set the function pointers impl->OpenDevice = HAIKUAUDIO_OpenDevice; + impl->GetDeviceBuf = HAIKUAUDIO_GetDeviceBuf; + impl->PlayDevice = HAIKUAUDIO_PlayDevice; impl->CloseDevice = HAIKUAUDIO_CloseDevice; impl->Deinitialize = HAIKUAUDIO_Deinitialize; impl->ProvidesOwnCallbackThread = SDL_TRUE; impl->OnlyHasDefaultOutputDevice = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } -extern "C" -{ - extern AudioBootStrap HAIKUAUDIO_bootstrap; -} + +extern "C" { extern AudioBootStrap HAIKUAUDIO_bootstrap; } + AudioBootStrap HAIKUAUDIO_bootstrap = { "haiku", "Haiku BSoundPlayer", HAIKUAUDIO_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_HAIKU */ +#endif // SDL_AUDIO_DRIVER_HAIKU diff --git a/src/audio/haiku/SDL_haikuaudio.h b/src/audio/haiku/SDL_haikuaudio.h index c8ecd6545e..c78c6061dd 100644 --- a/src/audio/haiku/SDL_haikuaudio.h +++ b/src/audio/haiku/SDL_haikuaudio.h @@ -28,6 +28,8 @@ struct SDL_PrivateAudioData { BSoundPlayer *audio_obj; + Uint8 *current_buffer; + int current_buffer_len; }; #endif /* SDL_haikuaudio_h_ */ From 0f6e59312b75ac0eb89930ec828533cb95eef829 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 5 Jul 2023 15:29:30 -0400 Subject: [PATCH 085/138] netbsdaudio: Removed email address from source code. Not to diminish their contribution, but I'm about to tear this code up and would rather the bug reports go to the SDL project. --- src/audio/netbsd/SDL_netbsdaudio.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/audio/netbsd/SDL_netbsdaudio.c b/src/audio/netbsd/SDL_netbsdaudio.c index 6474aa86e5..a1ee1649f1 100644 --- a/src/audio/netbsd/SDL_netbsdaudio.c +++ b/src/audio/netbsd/SDL_netbsdaudio.c @@ -22,10 +22,7 @@ #ifdef SDL_AUDIO_DRIVER_NETBSD -/* - * Driver for native NetBSD audio(4). - * nia@NetBSD.org - */ +// Driver for native NetBSD audio(4). #include #include From 6bc85577d78fc0f1e70012a3f9eabe6ce561414a Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 6 Jul 2023 00:05:23 -0400 Subject: [PATCH 086/138] netbsdaudio: Updated for SDL3 audio API. --- src/audio/netbsd/SDL_netbsdaudio.c | 203 +++++++++++++++-------------- 1 file changed, 105 insertions(+), 98 deletions(-) diff --git a/src/audio/netbsd/SDL_netbsdaudio.c b/src/audio/netbsd/SDL_netbsdaudio.c index a1ee1649f1..86ebd09b0a 100644 --- a/src/audio/netbsd/SDL_netbsdaudio.c +++ b/src/audio/netbsd/SDL_netbsdaudio.c @@ -38,26 +38,26 @@ #include "../SDL_audiodev_c.h" #include "SDL_netbsdaudio.h" -/* #define DEBUG_AUDIO */ +//#define DEBUG_AUDIO -static void NETBSDAUDIO_DetectDevices(void) +static void NETBSDAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { - SDL_EnumUnixAudioDevices(0, NULL); + SDL_EnumUnixAudioDevices(SDL_FALSE, NULL); } -static void NETBSDAUDIO_Status(SDL_AudioDevice *_this) +static void NETBSDAUDIO_Status(SDL_AudioDevice *device) { #ifdef DEBUG_AUDIO /* *INDENT-OFF* */ /* clang-format off */ audio_info_t info; const struct audio_prinfo *prinfo; - if (ioctl(_this->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) { + if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) { fprintf(stderr, "AUDIO_GETINFO failed.\n"); return; } - prinfo = _this->iscapture ? &info.record : &info.play; + prinfo = device->iscapture ? &info.record : &info.play; fprintf(stderr, "\n" "[%s info]\n" @@ -74,7 +74,7 @@ static void NETBSDAUDIO_Status(SDL_AudioDevice *_this) "waiting : %s\n" "active : %s\n" "", - _this->iscapture ? "record" : "play", + device->iscapture ? "record" : "play", prinfo->buffer_size, prinfo->sample_rate, prinfo->channels, @@ -100,7 +100,7 @@ static void NETBSDAUDIO_Status(SDL_AudioDevice *_this) info.blocksize, info.hiwat, info.lowat, (info.mode == AUMODE_PLAY) ? "PLAY" - : (info.mode = AUMODE_RECORD) ? "RECORD" + : (info.mode == AUMODE_RECORD) ? "RECORD" : (info.mode == AUMODE_PLAY_ALL ? "PLAY_ALL" : "?")); fprintf(stderr, "\n" @@ -108,23 +108,46 @@ static void NETBSDAUDIO_Status(SDL_AudioDevice *_this) "format : 0x%x\n" "size : %u\n" "", - _this->spec.format, - _this->spec.size); + device->spec.format, + device->buffer_size); /* *INDENT-ON* */ /* clang-format on */ -#endif /* DEBUG_AUDIO */ +#endif // DEBUG_AUDIO } -static void NETBSDAUDIO_PlayDevice(SDL_AudioDevice *_this) +static void NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *h = _this->hidden; - int written; + const SDL_bool iscapture = device->iscapture; + while (!SDL_AtomicGet(&device->shutdown)) { + audio_info_t info; + const int rc = ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info); + if (rc < 0) { + if (errno == EAGAIN) { + continue; + } + // Hmm, not much we can do - abort + fprintf(stderr, "netbsdaudio WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno)); + SDL_AudioDeviceDisconnected(device); + return; + } + const size_t remain = (size_t)((iscapture ? info.record.seek : info.play.seek) * (SDL_AUDIO_BITSIZE(device->spec.format) / 8)); + if (!iscapture && (remain >= device->buffer_size)) { + SDL_Delay(10); + } else if (iscapture && (remain < device->buffer_size)) { + SDL_Delay(10); + } else { + break; /* ready to go! */ + } + } +} - /* Write the audio data */ - written = write(h->audio_fd, h->mixbuf, h->mixlen); +static void NETBSDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) +{ + struct SDL_PrivateAudioData *h = device->hidden; + const int written = write(h->audio_fd, buffer, buflen); if (written == -1) { - /* Non recoverable error has occurred. It should be reported!!! */ - SDL_OpenedAudioDeviceDisconnected(_this); + // Non recoverable error has occurred. It should be reported!!! + SDL_AudioDeviceDisconnected(device); perror("audio"); return; } @@ -134,19 +157,17 @@ static void NETBSDAUDIO_PlayDevice(SDL_AudioDevice *_this) #endif } -static Uint8 *NETBSDAUDIO_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *NETBSDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - return _this->hidden->mixbuf; + return device->hidden->mixbuf; } -static int NETBSDAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *_buffer, int buflen) +static int NETBSDAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *vbuffer, int buflen) { - Uint8 *buffer = (Uint8 *)_buffer; - int br; - - br = read(_this->hidden->audio_fd, buffer, buflen); + Uint8 *buffer = (Uint8 *)vbuffer; + const int br = read(device->hidden->audio_fd, buffer, buflen); if (br == -1) { - /* Non recoverable error has occurred. It should be reported!!! */ + // Non recoverable error has occurred. It should be reported!!! perror("audio"); return -1; } @@ -154,86 +175,72 @@ static int NETBSDAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *_buffer, #ifdef DEBUG_AUDIO fprintf(stderr, "Captured %d bytes of audio data\n", br); #endif - return 0; + return br; } -static void NETBSDAUDIO_FlushCapture(SDL_AudioDevice *_this) +static void NETBSDAUDIO_FlushCapture(SDL_AudioDevice *device) { + struct SDL_PrivateAudioData *h = device->hidden; audio_info_t info; - size_t remain; - Uint8 buf[512]; - - if (ioctl(_this->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) { - return; /* oh well. */ - } - - remain = (size_t)(info.record.samples * (SDL_AUDIO_BITSIZE(_this->spec.format) / 8)); - while (remain > 0) { - const size_t len = SDL_min(sizeof(buf), remain); - const int br = read(_this->hidden->audio_fd, buf, len); - if (br <= 0) { - return; /* oh well. */ + if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) == 0) { + size_t remain = (size_t)(info.record.seek * (SDL_AUDIO_BITSIZE(device->spec.format) / 8)); + while (remain > 0) { + char buf[512]; + const size_t len = SDL_min(sizeof(buf), remain); + const ssize_t br = read(h->audio_fd, buf, len); + if (br <= 0) { + break; + } + remain -= br; } - remain -= br; } } -static void NETBSDAUDIO_CloseDevice(SDL_AudioDevice *_this) +static void NETBSDAUDIO_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden->audio_fd >= 0) { - close(_this->hidden->audio_fd); + if (device->hidden) { + if (device->hidden->audio_fd >= 0) { + close(device->hidden->audio_fd); + } + SDL_free(device->hidden->mixbuf); + SDL_free(device->hidden); } - SDL_free(_this->hidden->mixbuf); - SDL_free(_this->hidden); } -static int NETBSDAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int NETBSDAUDIO_OpenDevice(SDL_AudioDevice *device) { - SDL_bool iscapture = _this->iscapture; - SDL_AudioFormat test_format; - const SDL_AudioFormat *closefmts; + const SDL_bool iscapture = device->iscapture; int encoding = AUDIO_ENCODING_NONE; audio_info_t info, hwinfo; struct audio_prinfo *prinfo = iscapture ? &info.record : &info.play; - /* We don't care what the devname is...we'll try to open anything. */ - /* ...but default to first name in the list... */ - if (devname == NULL) { - devname = SDL_GetAudioDeviceName(0, iscapture); - if (devname == NULL) { - return SDL_SetError("No such audio device"); - } - } - - /* Initialize all variables that we clean on shutdown */ - _this->hidden = (struct SDL_PrivateAudioData *) SDL_malloc(sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + // Initialize all variables that we clean on shutdown + device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(_this->hidden); - /* Open the audio device */ - _this->hidden->audio_fd = open(devname, (iscapture ? O_RDONLY : O_WRONLY) | O_CLOEXEC); - if (_this->hidden->audio_fd < 0) { - return SDL_SetError("Couldn't open %s: %s", devname, strerror(errno)); + // Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that. + const int flags = ((device->iscapture) ? O_RDONLY : O_WRONLY); + device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC); + if (device->hidden->audio_fd < 0) { + return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno)); } AUDIO_INITINFO(&info); -#ifdef AUDIO_GETFORMAT /* Introduced in NetBSD 9.0 */ - if (ioctl(_this->hidden->audio_fd, AUDIO_GETFORMAT, &hwinfo) != -1) { - /* - * Use the device's native sample rate so the kernel doesn't have to - * resample. - */ - _this->spec.freq = iscapture ? hwinfo.record.sample_rate : hwinfo.play.sample_rate; +#ifdef AUDIO_GETFORMAT // Introduced in NetBSD 9.0 + if (ioctl(device->hidden->audio_fd, AUDIO_GETFORMAT, &hwinfo) != -1) { + // Use the device's native sample rate so the kernel doesn't have to resample. + device->spec.freq = iscapture ? hwinfo.record.sample_rate : hwinfo.play.sample_rate; } #endif - prinfo->sample_rate = _this->spec.freq; - prinfo->channels = _this->spec.channels; + prinfo->sample_rate = device->spec.freq; + prinfo->channels = device->spec.channels; - closefmts = SDL_ClosestAudioFormats(_this->spec.format); + SDL_AudioFormat test_format; + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { switch (test_format) { case SDL_AUDIO_U8: @@ -268,56 +275,56 @@ static int NETBSDAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) info.hiwat = 5; info.lowat = 3; - if (ioctl(_this->hidden->audio_fd, AUDIO_SETINFO, &info) < 0) { - return SDL_SetError("AUDIO_SETINFO failed for %s: %s", devname, strerror(errno)); + if (ioctl(device->hidden->audio_fd, AUDIO_SETINFO, &info) < 0) { + return SDL_SetError("AUDIO_SETINFO failed for %s: %s", device->name, strerror(errno)); } - if (ioctl(_this->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) { - return SDL_SetError("AUDIO_GETINFO failed for %s: %s", devname, strerror(errno)); + if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) { + return SDL_SetError("AUDIO_GETINFO failed for %s: %s", device->name, strerror(errno)); } - /* Final spec used for the device. */ - _this->spec.format = test_format; - _this->spec.freq = prinfo->sample_rate; - _this->spec.channels = prinfo->channels; + // Final spec used for the device. + device->spec.format = test_format; + device->spec.freq = prinfo->sample_rate; + device->spec.channels = prinfo->channels; - SDL_CalculateAudioSpec(&_this->spec); + SDL_UpdatedAudioDeviceFormat(device); if (!iscapture) { - /* Allocate mixing buffer */ - _this->hidden->mixlen = _this->spec.size; - _this->hidden->mixbuf = (Uint8 *)SDL_malloc(_this->hidden->mixlen); - if (_this->hidden->mixbuf == NULL) { + // Allocate mixing buffer + device->hidden->mixlen = device->buffer_size; + device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->hidden->mixlen); + if (device->hidden->mixbuf == NULL) { return SDL_OutOfMemory(); } - SDL_memset(_this->hidden->mixbuf, _this->spec.silence, _this->spec.size); + SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); } - NETBSDAUDIO_Status(_this); + NETBSDAUDIO_Status(device); - /* We're ready to rock and roll. :-) */ - return 0; + return 0; // We're ready to rock and roll. :-) } static SDL_bool NETBSDAUDIO_Init(SDL_AudioDriverImpl *impl) { - /* Set the function pointers */ impl->DetectDevices = NETBSDAUDIO_DetectDevices; impl->OpenDevice = NETBSDAUDIO_OpenDevice; + impl->WaitDevice = NETBSDAUDIO_WaitDevice; impl->PlayDevice = NETBSDAUDIO_PlayDevice; impl->GetDeviceBuf = NETBSDAUDIO_GetDeviceBuf; impl->CloseDevice = NETBSDAUDIO_CloseDevice; + impl->WaitCaptureDevice = NETBSDAUDIO_WaitDevice; impl->CaptureFromDevice = NETBSDAUDIO_CaptureFromDevice; impl->FlushCapture = NETBSDAUDIO_FlushCapture; impl->HasCaptureSupport = SDL_TRUE; impl->AllowsArbitraryDeviceNames = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap NETBSDAUDIO_bootstrap = { "netbsd", "NetBSD audio", NETBSDAUDIO_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_NETBSD */ +#endif // SDL_AUDIO_DRIVER_NETBSD From 1a552820512a23d948bbc87e8f9b52d2f6795326 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 6 Jul 2023 00:26:27 -0400 Subject: [PATCH 087/138] dsp: Some minor logic fixes --- src/audio/dsp/SDL_dspaudio.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/audio/dsp/SDL_dspaudio.c b/src/audio/dsp/SDL_dspaudio.c index 41df8d68f4..004acef98c 100644 --- a/src/audio/dsp/SDL_dspaudio.c +++ b/src/audio/dsp/SDL_dspaudio.c @@ -209,14 +209,18 @@ static void DSP_WaitDevice(SDL_AudioDevice *device) while (!SDL_AtomicGet(&device->shutdown)) { audio_buf_info info; const int rc = ioctl(h->audio_fd, ioctlreq, &info); - if ((rc < 0) && (errno != EAGAIN)) { // Hmm, not much we can do - abort + if (rc < 0) { + if (errno == EAGAIN) { + continue; + } + // Hmm, not much we can do - abort fprintf(stderr, "dsp WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno)); SDL_AudioDeviceDisconnected(device); return; } else if (info.bytes < device->buffer_size) { SDL_Delay(10); } else { - break; /* ready to go! */ + break; // ready to go! } } } @@ -227,6 +231,7 @@ static void DSP_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buf if (write(h->audio_fd, buffer, buflen) == -1) { perror("Audio write"); SDL_AudioDeviceDisconnected(device); + return; } #ifdef DEBUG_AUDIO fprintf(stderr, "Wrote %d bytes of audio data\n", h->mixlen); From fb395d3ad72768195008a5de213f70df0f1461d9 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 6 Jul 2023 16:52:20 -0400 Subject: [PATCH 088/138] sndio: Updated to the SDL3 audio API. --- CMakeLists.txt | 2 - src/audio/sndio/SDL_sndioaudio.c | 207 +++++++++++++++---------------- src/audio/sndio/SDL_sndioaudio.h | 12 +- 3 files changed, 104 insertions(+), 117 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c74b19ee5d..7e5a28b55f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -353,8 +353,6 @@ set_option(SDL_CLANG_TIDY "Run clang-tidy static analysis" OFF) set(SDL_VENDOR_INFO "" CACHE STRING "Vendor name and/or version to add to SDL_REVISION") -set(SDL_SNDIO OFF) - cmake_dependent_option(SDL_SHARED "Build a shared version of the library" ${SDL_SHARED_DEFAULT} ${SDL_SHARED_AVAILABLE} OFF) option(SDL_STATIC "Build a static version of the library" ${SDL_STATIC_DEFAULT}) option(SDL_TEST_LIBRARY "Build the SDL3_test library" ON) diff --git a/src/audio/sndio/SDL_sndioaudio.c b/src/audio/sndio/SDL_sndioaudio.c index 583aeb0847..a7ac91ddf7 100644 --- a/src/audio/sndio/SDL_sndioaudio.c +++ b/src/audio/sndio/SDL_sndioaudio.c @@ -23,7 +23,7 @@ #ifdef SDL_AUDIO_DRIVER_SNDIO -/* OpenBSD sndio target */ +// OpenBSD sndio target #ifdef HAVE_STDIO_H #include @@ -72,14 +72,13 @@ static int load_sndio_sym(const char *fn, void **addr) { *addr = SDL_LoadFunction(sndio_handle, fn); if (*addr == NULL) { - /* Don't call SDL_SetError(): SDL_LoadFunction already did. */ - return 0; + return 0; // Don't call SDL_SetError(): SDL_LoadFunction already did. } return 1; } -/* cast funcs to char* first, to please GCC's strict aliasing rules. */ +// cast funcs to char* first, to please GCC's strict aliasing rules. #define SDL_SNDIO_SYM(x) \ if (!load_sndio_sym(#x, (void **)(char *)&SNDIO_##x)) \ return -1 @@ -123,8 +122,7 @@ static int LoadSNDIOLibrary(void) if (sndio_handle == NULL) { sndio_handle = SDL_LoadObject(sndio_library); if (sndio_handle == NULL) { - retval = -1; - /* Don't call SDL_SetError(): SDL_LoadObject already did. */ + retval = -1; // Don't call SDL_SetError(): SDL_LoadObject already did. } else { retval = load_sndio_syms(); if (retval < 0) { @@ -147,129 +145,128 @@ static int LoadSNDIOLibrary(void) return 0; } -#endif /* SDL_AUDIO_DRIVER_SNDIO_DYNAMIC */ +#endif // SDL_AUDIO_DRIVER_SNDIO_DYNAMIC -static void SNDIO_WaitDevice(SDL_AudioDevice *_this) +static void SNDIO_WaitDevice(SDL_AudioDevice *device) { - /* no-op; SNDIO_sio_write() blocks if necessary. */ + const SDL_bool iscapture = device->iscapture; + + while (!SDL_AtomicGet(&device->shutdown)) { + if (SNDIO_sio_eof(device->hidden->dev)) { + SDL_AudioDeviceDisconnected(device); + return; + } + + const int nfds = SNDIO_sio_pollfd(device->hidden->dev, device->hidden->pfd, iscapture ? POLLIN : POLLOUT); + if (nfds <= 0 || poll(device->hidden->pfd, nfds, 10) < 0) { + SDL_AudioDeviceDisconnected(device); + return; + } + + const int revents = SNDIO_sio_revents(device->hidden->dev, device->hidden->pfd); + if (iscapture && (revents & POLLIN)) { + return; + } else if (!iscapture && (revents & POLLOUT)) { + return; + } else if (revents & POLLHUP) { + SDL_AudioDeviceDisconnected(device); + return; + } + } } -static void SNDIO_PlayDevice(SDL_AudioDevice *_this) +static void SNDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { - const int written = SNDIO_sio_write(_this->hidden->dev, - _this->hidden->mixbuf, - _this->hidden->mixlen); - - /* If we couldn't write, assume fatal error for now */ - if (written == 0) { - SDL_OpenedAudioDeviceDisconnected(_this); + // !!! FIXME: this should be non-blocking so we can check device->shutdown. + // this is set to blocking, because we _have_ to send the entire buffer down, but hopefully WaitDevice took most of the delay time. + if (SNDIO_sio_write(device->hidden->dev, buffer, buflen) != buflen) { + SDL_AudioDeviceDisconnected(device); // If we couldn't write, assume fatal error for now } #ifdef DEBUG_AUDIO fprintf(stderr, "Wrote %d bytes of audio data\n", written); #endif } -static int SNDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static int SNDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { - size_t r; - int revents; - int nfds; - - /* Emulate a blocking read */ - r = SNDIO_sio_read(_this->hidden->dev, buffer, buflen); - while (r == 0 && !SNDIO_sio_eof(_this->hidden->dev)) { - nfds = SNDIO_sio_pollfd(_this->hidden->dev, _this->hidden->pfd, POLLIN); - if (nfds <= 0 || poll(_this->hidden->pfd, nfds, INFTIM) < 0) { - return -1; - } - revents = SNDIO_sio_revents(_this->hidden->dev, _this->hidden->pfd); - if (revents & POLLIN) { - r = SNDIO_sio_read(_this->hidden->dev, buffer, buflen); - } - if (revents & POLLHUP) { - break; - } + // We set capture devices non-blocking; this can safely return 0 in SDL3, but we'll check for EOF to cause a device disconnect. + const size_t br = SNDIO_sio_read(device->hidden->dev, buffer, buflen); + if ((br == 0) && SNDIO_sio_eof(device->hidden->dev)) { + return -1; } - return (int)r; + return (int) br; } -static void SNDIO_FlushCapture(SDL_AudioDevice *_this) +static void SNDIO_FlushCapture(SDL_AudioDevice *device) { char buf[512]; - - while (SNDIO_sio_read(_this->hidden->dev, buf, sizeof(buf)) != 0) { - /* do nothing */; + while (!SDL_AtomicGet(&device->shutdown) && (SNDIO_sio_read(device->hidden->dev, buf, sizeof(buf)) > 0)) { + // do nothing } } -static Uint8 *SNDIO_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *SNDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - return _this->hidden->mixbuf; + return device->hidden->mixbuf; } -static void SNDIO_CloseDevice(SDL_AudioDevice *_this) +static void SNDIO_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden->pfd != NULL) { - SDL_free(_this->hidden->pfd); + if (device->hidden) { + if (device->hidden->dev != NULL) { + SNDIO_sio_stop(device->hidden->dev); + SNDIO_sio_close(device->hidden->dev); + } + SDL_free(device->hidden->pfd); + SDL_free(device->hidden->mixbuf); + SDL_free(device->hidden); + device->hidden = NULL; } - if (_this->hidden->dev != NULL) { - SNDIO_sio_stop(_this->hidden->dev); - SNDIO_sio_close(_this->hidden->dev); - } - SDL_free(_this->hidden->mixbuf); - SDL_free(_this->hidden); } -static int SNDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int SNDIO_OpenDevice(SDL_AudioDevice *device) { - SDL_AudioFormat test_format; - const SDL_AudioFormat *closefmts; - struct sio_par par; - SDL_bool iscapture = _this->iscapture; - - _this->hidden = (struct SDL_PrivateAudioData *) - SDL_malloc(sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(_this->hidden); - _this->hidden->mixlen = _this->spec.size; + // !!! FIXME: we really should standardize this on a specific SDL hint. + const char *audiodev = SDL_getenv("AUDIODEV"); - /* Capture devices must be non-blocking for SNDIO_FlushCapture */ - _this->hidden->dev = SNDIO_sio_open(devname != NULL ? devname : SIO_DEVANY, - iscapture ? SIO_REC : SIO_PLAY, iscapture); - if (_this->hidden->dev == NULL) { + // Capture devices must be non-blocking for SNDIO_FlushCapture + device->hidden->dev = SNDIO_sio_open(audiodev != NULL ? audiodev : SIO_DEVANY, + device->iscapture ? SIO_REC : SIO_PLAY, device->iscapture); + if (device->hidden->dev == NULL) { return SDL_SetError("sio_open() failed"); } - /* Allocate the pollfd array for capture devices */ - if (iscapture) { - _this->hidden->pfd = SDL_malloc(sizeof(struct pollfd) * SNDIO_sio_nfds(_this->hidden->dev)); - if (_this->hidden->pfd == NULL) { - return SDL_OutOfMemory(); - } + device->hidden->pfd = SDL_malloc(sizeof(struct pollfd) * SNDIO_sio_nfds(device->hidden->dev)); + if (device->hidden->pfd == NULL) { + return SDL_OutOfMemory(); } + struct sio_par par; SNDIO_sio_initpar(&par); - par.rate = _this->spec.freq; - par.pchan = _this->spec.channels; - par.round = _this->spec.samples; + par.rate = device->spec.freq; + par.pchan = device->spec.channels; + par.round = device->sample_frames; par.appbufsz = par.round * 2; - /* Try for a closest match on audio format */ - closefmts = SDL_ClosestAudioFormats(_this->spec.format); + // Try for a closest match on audio format + SDL_AudioFormat test_format; + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { if (!SDL_AUDIO_ISFLOAT(test_format)) { par.le = SDL_AUDIO_ISLITTLEENDIAN(test_format) ? 1 : 0; par.sig = SDL_AUDIO_ISSIGNED(test_format) ? 1 : 0; par.bits = SDL_AUDIO_BITSIZE(test_format); - if (SNDIO_sio_setpar(_this->hidden->dev, &par) == 0) { + if (SNDIO_sio_setpar(device->hidden->dev, &par) == 0) { continue; } - if (SNDIO_sio_getpar(_this->hidden->dev, &par) == 0) { + if (SNDIO_sio_getpar(device->hidden->dev, &par) == 0) { return SDL_SetError("sio_getpar() failed"); } if (par.bps != SIO_BPS(par.bits)) { @@ -282,46 +279,44 @@ static int SNDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) } if (!test_format) { - return SDL_SetError("%s: Unsupported audio format", "sndio"); + return SDL_SetError("sndio: Unsupported audio format"); } if ((par.bps == 4) && (par.sig) && (par.le)) { - _this->spec.format = SDL_AUDIO_S32LSB; + device->spec.format = SDL_AUDIO_S32LSB; } else if ((par.bps == 4) && (par.sig) && (!par.le)) { - _this->spec.format = SDL_AUDIO_S32MSB; + device->spec.format = SDL_AUDIO_S32MSB; } else if ((par.bps == 2) && (par.sig) && (par.le)) { - _this->spec.format = SDL_AUDIO_S16LSB; + device->spec.format = SDL_AUDIO_S16LSB; } else if ((par.bps == 2) && (par.sig) && (!par.le)) { - _this->spec.format = SDL_AUDIO_S16MSB; + device->spec.format = SDL_AUDIO_S16MSB; } else if ((par.bps == 1) && (par.sig)) { - _this->spec.format = SDL_AUDIO_S8; + device->spec.format = SDL_AUDIO_S8; } else if ((par.bps == 1) && (!par.sig)) { - _this->spec.format = SDL_AUDIO_U8; + device->spec.format = SDL_AUDIO_U8; } else { return SDL_SetError("sndio: Got unsupported hardware audio format."); } - _this->spec.freq = par.rate; - _this->spec.channels = par.pchan; - _this->spec.samples = par.round; + device->spec.freq = par.rate; + device->spec.channels = par.pchan; + device->sample_frames = par.round; - /* Calculate the final parameters for this audio specification */ - SDL_CalculateAudioSpec(&_this->spec); + // Calculate the final parameters for this audio specification + SDL_UpdatedAudioDeviceFormat(device); - /* Allocate mixing buffer */ - _this->hidden->mixlen = _this->spec.size; - _this->hidden->mixbuf = (Uint8 *)SDL_malloc(_this->hidden->mixlen); - if (_this->hidden->mixbuf == NULL) { + // Allocate mixing buffer + device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); + if (device->hidden->mixbuf == NULL) { return SDL_OutOfMemory(); } - SDL_memset(_this->hidden->mixbuf, _this->spec.silence, _this->hidden->mixlen); + SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); - if (!SNDIO_sio_start(_this->hidden->dev)) { + if (!SNDIO_sio_start(device->hidden->dev)) { return SDL_SetError("sio_start() failed"); } - /* We're ready to rock and roll. :-) */ - return 0; + return 0; // We're ready to rock and roll. :-) } static void SNDIO_Deinitialize(void) @@ -329,10 +324,10 @@ static void SNDIO_Deinitialize(void) UnloadSNDIOLibrary(); } -static void SNDIO_DetectDevices(void) +static void SNDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { - SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1); - SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2); + *default_output = SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1); + *default_capture = SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2); } static SDL_bool SNDIO_Init(SDL_AudioDriverImpl *impl) @@ -341,12 +336,12 @@ static SDL_bool SNDIO_Init(SDL_AudioDriverImpl *impl) return SDL_FALSE; } - /* Set the function pointers */ impl->OpenDevice = SNDIO_OpenDevice; impl->WaitDevice = SNDIO_WaitDevice; impl->PlayDevice = SNDIO_PlayDevice; impl->GetDeviceBuf = SNDIO_GetDeviceBuf; impl->CloseDevice = SNDIO_CloseDevice; + impl->WaitCaptureDevice = SNDIO_WaitDevice; impl->CaptureFromDevice = SNDIO_CaptureFromDevice; impl->FlushCapture = SNDIO_FlushCapture; impl->Deinitialize = SNDIO_Deinitialize; @@ -355,11 +350,11 @@ static SDL_bool SNDIO_Init(SDL_AudioDriverImpl *impl) impl->AllowsArbitraryDeviceNames = SDL_TRUE; impl->HasCaptureSupport = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap SNDIO_bootstrap = { "sndio", "OpenBSD sndio", SNDIO_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_SNDIO */ +#endif // SDL_AUDIO_DRIVER_SNDIO diff --git a/src/audio/sndio/SDL_sndioaudio.h b/src/audio/sndio/SDL_sndioaudio.h index 1790097903..6a36b7df0e 100644 --- a/src/audio/sndio/SDL_sndioaudio.h +++ b/src/audio/sndio/SDL_sndioaudio.h @@ -30,15 +30,9 @@ struct SDL_PrivateAudioData { - /* The audio device handle */ - struct sio_hdl *dev; - - /* Raw mixing buffer */ - Uint8 *mixbuf; - int mixlen; - - /* Polling structures for non-blocking sndio devices */ - struct pollfd *pfd; + struct sio_hdl *dev; // The audio device handle + Uint8 *mixbuf; // Raw mixing buffer + struct pollfd *pfd; // Polling structures for non-blocking sndio devices }; #endif /* SDL_sndioaudio_h_ */ From 4ba9c2eaded5f75c78642e603676c9955312e80c Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 6 Jul 2023 17:18:37 -0400 Subject: [PATCH 089/138] dummyaudio: Configurable delay, other SDL3 API fixes. --- src/audio/dummy/SDL_dummyaudio.c | 41 +++++++++++++++++++++++--------- src/audio/dummy/SDL_dummyaudio.h | 6 +---- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/audio/dummy/SDL_dummyaudio.c b/src/audio/dummy/SDL_dummyaudio.c index c8d10ef8f5..b9b84c773a 100644 --- a/src/audio/dummy/SDL_dummyaudio.c +++ b/src/audio/dummy/SDL_dummyaudio.c @@ -25,33 +25,51 @@ #include "../SDL_audio_c.h" #include "SDL_dummyaudio.h" -/* !!! FIXME: add a dummy WaitDevice to simulate real audio better? */ +/* !!! FIXME: this should be an SDL hint, not an environment variable. */ +#define DUMMYENVR_IODELAY "SDL_DUMMYAUDIODELAY" + +static void DUMMYAUDIO_WaitDevice(SDL_AudioDevice *device) +{ + SDL_Delay(device->hidden->io_delay); +} static int DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device) { - device->hidden = (struct SDL_PrivateAudioData *) SDL_malloc(device->buffer_size); + const char *envr = SDL_getenv(DUMMYENVR_IODELAY); + + device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); if (!device->hidden) { return SDL_OutOfMemory(); } - return 0; /* don't change reported device format. */ + + if (!device->iscapture) { + device->hidden->mixbuf = (Uint8 *) SDL_malloc(device->buffer_size); + if (!device->hidden->mixbuf) { + return SDL_OutOfMemory(); + } + } + + device->hidden->io_delay = envr ? SDL_atoi(envr) : ((device->sample_frames * 1000) / device->spec.freq); + + return 0; /* we're good; don't change reported device format. */ } static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device) { - SDL_free(device->hidden); - device->hidden = NULL; + if (device->hidden) { + SDL_free(device->hidden->mixbuf); + SDL_free(device->hidden); + device->hidden = NULL; + } } static Uint8 *DUMMYAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - return (Uint8 *) device->hidden; + return device->hidden->mixbuf; } static int DUMMYAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { - /* Delay to make this sort of simulate real audio input. */ - SDL_Delay((device->sample_frames * 1000) / device->spec.freq); - /* always return a full buffer of silence. */ SDL_memset(buffer, device->silence_value, buflen); return buflen; @@ -59,17 +77,18 @@ static int DUMMYAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, i static SDL_bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl) { - /* Set the function pointers */ impl->OpenDevice = DUMMYAUDIO_OpenDevice; impl->CloseDevice = DUMMYAUDIO_CloseDevice; + impl->WaitDevice = DUMMYAUDIO_WaitDevice; impl->GetDeviceBuf = DUMMYAUDIO_GetDeviceBuf; + impl->WaitCaptureDevice = DUMMYAUDIO_WaitDevice; impl->CaptureFromDevice = DUMMYAUDIO_CaptureFromDevice; impl->OnlyHasDefaultOutputDevice = SDL_TRUE; impl->OnlyHasDefaultCaptureDevice = SDL_TRUE; impl->HasCaptureSupport = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap DUMMYAUDIO_bootstrap = { diff --git a/src/audio/dummy/SDL_dummyaudio.h b/src/audio/dummy/SDL_dummyaudio.h index 78709ac002..44323e2c32 100644 --- a/src/audio/dummy/SDL_dummyaudio.h +++ b/src/audio/dummy/SDL_dummyaudio.h @@ -25,15 +25,11 @@ #include "../SDL_sysaudio.h" -/* !!! FIXME: none of this is actually used. Dump this whole file. */ - struct SDL_PrivateAudioData { /* The file descriptor for the audio device */ Uint8 *mixbuf; - Uint32 mixlen; - Uint32 write_delay; - Uint32 initial_calls; + Uint32 io_delay; }; #endif /* SDL_dummyaudio_h_ */ From 7d65ff86e2fae0a4b4307fcdcb8be481e12902f3 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 6 Jul 2023 17:21:34 -0400 Subject: [PATCH 090/138] diskaudio: Adjusted for later SDL3 audio API redesign changes. --- src/audio/disk/SDL_diskaudio.c | 54 ++++++++++++++-------------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/src/audio/disk/SDL_diskaudio.c b/src/audio/disk/SDL_diskaudio.c index b4bd798654..3970599683 100644 --- a/src/audio/disk/SDL_diskaudio.c +++ b/src/audio/disk/SDL_diskaudio.c @@ -22,20 +22,19 @@ #ifdef SDL_AUDIO_DRIVER_DISK -/* Output raw audio data to a file. */ +// Output raw audio data to a file. #include "../SDL_audio_c.h" #include "SDL_diskaudio.h" -/* !!! FIXME: these should be SDL hints, not environment variables. */ -/* environment variables and defaults. */ +// !!! FIXME: these should be SDL hints, not environment variables. +// environment variables and defaults. #define DISKENVR_OUTFILE "SDL_DISKAUDIOFILE" #define DISKDEFAULT_OUTFILE "sdlaudio.raw" #define DISKENVR_INFILE "SDL_DISKAUDIOFILEIN" #define DISKDEFAULT_INFILE "sdlaudio-in.raw" #define DISKENVR_IODELAY "SDL_DISKAUDIODELAY" -/* This function waits until it is possible to write a full sound buffer */ static void DISKAUDIO_WaitDevice(SDL_AudioDevice *device) { SDL_Delay(device->hidden->io_delay); @@ -44,9 +43,7 @@ static void DISKAUDIO_WaitDevice(SDL_AudioDevice *device) static void DISKAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { const Sint64 written = SDL_RWwrite(device->hidden->io, buffer, buffer_size); - - /* If we couldn't write, assume fatal error for now */ - if (written != buffer_size) { + if (written != buffer_size) { // If we couldn't write, assume fatal error for now SDL_AudioDeviceDisconnected(device); } #ifdef DEBUG_AUDIO @@ -64,19 +61,17 @@ static int DISKAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, in struct SDL_PrivateAudioData *h = device->hidden; const int origbuflen = buflen; - SDL_Delay(h->io_delay); - if (h->io) { const int br = (int) SDL_RWread(h->io, buffer, (Sint64) buflen); buflen -= br; buffer = ((Uint8 *)buffer) + br; - if (buflen > 0) { /* EOF (or error, but whatever). */ + if (buflen > 0) { // EOF (or error, but whatever). SDL_RWclose(h->io); h->io = NULL; } } - /* if we ran out of file, just write silence. */ + // if we ran out of file, just write silence. SDL_memset(buffer, device->silence_value, buflen); return origbuflen; @@ -84,16 +79,19 @@ static int DISKAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, in static void DISKAUDIO_FlushCapture(SDL_AudioDevice *device) { - /* no op...we don't advance the file pointer or anything. */ + // no op...we don't advance the file pointer or anything. } static void DISKAUDIO_CloseDevice(SDL_AudioDevice *device) { - if (device->hidden->io != NULL) { - SDL_RWclose(device->hidden->io); + if (device->hidden) { + if (device->hidden->io != NULL) { + SDL_RWclose(device->hidden->io); + } + SDL_free(device->hidden->mixbuf); + SDL_free(device->hidden); + device->hidden = NULL; } - SDL_free(device->hidden->mixbuf); - SDL_free(device->hidden); } static const char *get_filename(const SDL_bool iscapture) @@ -111,12 +109,10 @@ static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device) const char *fname = get_filename(iscapture); const char *envr = SDL_getenv(DISKENVR_IODELAY); - device->hidden = (struct SDL_PrivateAudioData *) - SDL_malloc(sizeof(*device->hidden)); + device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(device->hidden); if (envr != NULL) { device->hidden->io_delay = SDL_atoi(envr); @@ -124,13 +120,13 @@ static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device) device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq); } - /* Open the "audio device" */ + // Open the "audio device" device->hidden->io = SDL_RWFromFile(fname, iscapture ? "rb" : "wb"); if (device->hidden->io == NULL) { return -1; } - /* Allocate mixing buffer */ + // Allocate mixing buffer if (!iscapture) { device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); if (device->hidden->mixbuf == NULL) { @@ -139,14 +135,10 @@ static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device) SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); } - SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, - "You are using the SDL disk i/o audio driver!\n"); - SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, - " %s file [%s].\n", iscapture ? "Reading from" : "Writing to", - fname); + SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, "You are using the SDL disk i/o audio driver!"); + SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s].\n", iscapture ? "Reading from" : "Writing to", fname); - /* We're ready to rock and roll. :-) */ - return 0; + return 0; // We're ready to rock and roll. :-) } static void DISKAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) @@ -157,7 +149,6 @@ static void DISKAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioD static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl) { - /* Set the function pointers */ impl->OpenDevice = DISKAUDIO_OpenDevice; impl->WaitDevice = DISKAUDIO_WaitDevice; impl->WaitCaptureDevice = DISKAUDIO_WaitDevice; @@ -165,18 +156,17 @@ static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl) impl->GetDeviceBuf = DISKAUDIO_GetDeviceBuf; impl->CaptureFromDevice = DISKAUDIO_CaptureFromDevice; impl->FlushCapture = DISKAUDIO_FlushCapture; - impl->CloseDevice = DISKAUDIO_CloseDevice; impl->DetectDevices = DISKAUDIO_DetectDevices; impl->AllowsArbitraryDeviceNames = SDL_TRUE; impl->HasCaptureSupport = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap DISKAUDIO_bootstrap = { "disk", "direct-to-disk audio", DISKAUDIO_Init, SDL_TRUE }; -#endif /* SDL_AUDIO_DRIVER_DISK */ +#endif // SDL_AUDIO_DRIVER_DISK From c3f5a5fc729bd9a728fde54d7353397c90ecd55f Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 6 Jul 2023 17:25:07 -0400 Subject: [PATCH 091/138] dummyaudio: SDL3ify style --- src/audio/dummy/SDL_dummyaudio.c | 10 +++++----- src/audio/dummy/SDL_dummyaudio.h | 7 +++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/audio/dummy/SDL_dummyaudio.c b/src/audio/dummy/SDL_dummyaudio.c index b9b84c773a..bd3002daa7 100644 --- a/src/audio/dummy/SDL_dummyaudio.c +++ b/src/audio/dummy/SDL_dummyaudio.c @@ -20,12 +20,12 @@ */ #include "SDL_internal.h" -/* Output audio to nowhere... */ +// Output audio to nowhere... #include "../SDL_audio_c.h" #include "SDL_dummyaudio.h" -/* !!! FIXME: this should be an SDL hint, not an environment variable. */ +// !!! FIXME: this should be an SDL hint, not an environment variable. #define DUMMYENVR_IODELAY "SDL_DUMMYAUDIODELAY" static void DUMMYAUDIO_WaitDevice(SDL_AudioDevice *device) @@ -49,9 +49,9 @@ static int DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device) } } - device->hidden->io_delay = envr ? SDL_atoi(envr) : ((device->sample_frames * 1000) / device->spec.freq); + device->hidden->io_delay = (Uint32) (envr ? SDL_atoi(envr) : ((device->sample_frames * 1000) / device->spec.freq)); - return 0; /* we're good; don't change reported device format. */ + return 0; // we're good; don't change reported device format. } static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device) @@ -70,7 +70,7 @@ static Uint8 *DUMMYAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) static int DUMMYAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { - /* always return a full buffer of silence. */ + // always return a full buffer of silence. SDL_memset(buffer, device->silence_value, buflen); return buflen; } diff --git a/src/audio/dummy/SDL_dummyaudio.h b/src/audio/dummy/SDL_dummyaudio.h index 44323e2c32..d629e0d8b1 100644 --- a/src/audio/dummy/SDL_dummyaudio.h +++ b/src/audio/dummy/SDL_dummyaudio.h @@ -27,9 +27,8 @@ struct SDL_PrivateAudioData { - /* The file descriptor for the audio device */ - Uint8 *mixbuf; - Uint32 io_delay; + Uint8 *mixbuf; // The file descriptor for the audio device + Uint32 io_delay; // miliseconds to sleep in WaitDevice. }; -#endif /* SDL_dummyaudio_h_ */ +#endif // SDL_dummyaudio_h_ From 2fb122fe4690a6cd3c6f8508d73b329760cd14f5 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 11 Jul 2023 13:59:54 -0400 Subject: [PATCH 092/138] audio: backends now "find" instead of "obtain" devices by handle. Every single case of this didn't want the device locked, so just looking it up without having to immediately unlock it afterwards is better here. Often these devices are passed on to other functions that want to lock them themselves anyhow (disconnects, default changes, etc). --- src/audio/SDL_audio.c | 8 +++----- src/audio/SDL_sysaudio.h | 4 ++-- src/audio/alsa/SDL_alsa_audio.c | 8 +------- src/audio/coreaudio/SDL_coreaudio.m | 15 ++++----------- src/audio/pipewire/SDL_pipewire.c | 12 ++---------- src/audio/pulseaudio/SDL_pulseaudio.c | 15 ++++----------- 6 files changed, 16 insertions(+), 46 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 6ddaafe76b..f08531eb0b 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -988,7 +988,7 @@ static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) return dev; } -SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle) +SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) { if (!SDL_GetCurrentAudioDriver()) { SDL_SetError("Audio subsystem is not initialized"); @@ -1000,8 +1000,6 @@ SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle) SDL_AudioDevice *dev = NULL; for (dev = current_audio.output_devices; dev != NULL; dev = dev->next) { if (dev->handle == handle) { // found it? - SDL_LockMutex(dev->lock); // caller must unlock. - SDL_assert(!SDL_AtomicGet(&dev->condemned)); // shouldn't be in the list if pending deletion. break; } } @@ -1010,8 +1008,6 @@ SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle) // !!! FIXME: code duplication, from above. for (dev = current_audio.capture_devices; dev != NULL; dev = dev->next) { if (dev->handle == handle) { // found it? - SDL_LockMutex(dev->lock); // caller must unlock. - SDL_assert(!SDL_AtomicGet(&dev->condemned)); // shouldn't be in the list if pending deletion. break; } } @@ -1023,6 +1019,8 @@ SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle) SDL_SetError("Device handle not found"); } + SDL_assert(!SDL_AtomicGet(&dev->condemned)); // shouldn't be in the list if pending deletion. + return dev; } diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 1f018887c6..9d413a6cd9 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -85,8 +85,8 @@ extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device); // Backends should call this if the system default device changes. extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device); -// Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. Locks the device! You must unlock!! -extern SDL_AudioDevice *SDL_ObtainPhysicalAudioDeviceByHandle(void *handle); +// 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); // Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct. extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device); diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c index 4703eee4c5..9acab94b81 100644 --- a/src/audio/alsa/SDL_alsa_audio.c +++ b/src/audio/alsa/SDL_alsa_audio.c @@ -880,13 +880,7 @@ static void ALSA_HotplugIteration(SDL_bool *has_default_output, SDL_bool *has_de for (ALSA_Device *dev = unseen; dev; dev = next) { /*printf("ALSA: removing usb %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/ next = dev->next; - - SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle(dev->name); - if (device) { - SDL_UnlockMutex(device->lock); // AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. - SDL_AudioDeviceDisconnected(device); - } - + SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(dev->name)); SDL_free(dev->name); SDL_free(dev); } diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m index 44ca6a846d..1a666af292 100644 --- a/src/audio/coreaudio/SDL_coreaudio.m +++ b/src/audio/coreaudio/SDL_coreaudio.m @@ -118,9 +118,8 @@ static void RefreshPhysicalDevices(void) const UInt32 total_devices = (UInt32) (size / sizeof(AudioDeviceID)); for (UInt32 i = 0; i < total_devices; i++) { - SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((size_t)devs[i])); + SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devs[i])); if (device) { - SDL_UnlockMutex(device->lock); devs[i] = 0; // The system and SDL both agree it's already here, don't check it again. } } @@ -235,11 +234,7 @@ static OSStatus DefaultAudioDeviceChangedNotification(AudioObjectID inObjectID, AudioDeviceID devid; Uint32 size = sizeof(devid); if (AudioObjectGetPropertyData(inObjectID, addr, 0, NULL, &size, &devid) == noErr) { - SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((size_t)devid)); - if (device) { - SDL_UnlockMutex(device->lock); - SDL_DefaultAudioDeviceChanged(device); - } + SDL_DefaultAudioDeviceChanged(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devid))); } return noErr; } @@ -274,9 +269,8 @@ static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioD size = sizeof(AudioDeviceID); if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_output_device_address, 0, NULL, &size, &devid) == noErr) { - SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((size_t)devid)); + SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devid)); if (device) { - SDL_UnlockMutex(device->lock); *default_output = device; } } @@ -284,9 +278,8 @@ static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioD size = sizeof(AudioDeviceID); if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_input_device_address, 0, NULL, &size, &devid) == noErr) { - SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((size_t)devid)); + SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devid)); if (device) { - SDL_UnlockMutex(device->lock); *default_capture = device; } } diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c index 170b6d746c..419fbfcb86 100644 --- a/src/audio/pipewire/SDL_pipewire.c +++ b/src/audio/pipewire/SDL_pipewire.c @@ -327,11 +327,7 @@ static void io_list_remove(Uint32 id) spa_list_remove(&n->link); if (hotplug_events_enabled) { - SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle(PW_ID_TO_HANDLE(id)); - if (device) { - SDL_UnlockMutex(device->lock); // AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. - SDL_AudioDeviceDisconnected(device); - } + SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(PW_ID_TO_HANDLE(id))); } SDL_free(n); @@ -614,11 +610,7 @@ static void change_default_device(const char *path) struct io_node *n, *temp; spa_list_for_each_safe (n, temp, &hotplug_io_list, link) { if (SDL_strcmp(n->path, path) == 0) { - SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle(PW_ID_TO_HANDLE(n->id)); - if (device) { - SDL_UnlockMutex(device->lock); - SDL_DefaultAudioDeviceChanged(device); - } + SDL_DefaultAudioDeviceChanged(SDL_FindPhysicalAudioDeviceByHandle(PW_ID_TO_HANDLE(n->id))); return; // found it, we're done. } } diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 50d2072e79..f0c33d03f2 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -825,11 +825,7 @@ static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint3 PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_get_source_info_by_index(pulseaudio_context, idx, SourceInfoCallback, (void *)((intptr_t)added))); } else if (removed && (sink || source)) { // removes we can handle just with the device index. - SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)idx + 1)); - if (device) { - SDL_UnlockMutex(device->lock); // AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now. - SDL_AudioDeviceDisconnected(device); - } + SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((intptr_t)idx + 1))); } } PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0); @@ -838,9 +834,8 @@ static void HotplugCallback(pa_context *c, pa_subscription_event_type_t t, uint3 static void CheckDefaultDevice(uint32_t *prev_default, uint32_t new_default) { if (*prev_default != new_default) { - SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)new_default + 1)); + SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByHandle((void *)((intptr_t)new_default + 1)); if (device) { - SDL_UnlockMutex(device->lock); *prev_default = new_default; SDL_DefaultAudioDeviceChanged(device); } @@ -883,15 +878,13 @@ static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_Audio PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); SDL_AudioDevice *device; - device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)default_sink_index + 1)); + device = SDL_FindPhysicalAudioDeviceByHandle((void *)((intptr_t)default_sink_index + 1)); if (device) { - SDL_UnlockMutex(device->lock); *default_output = device; } - device = SDL_ObtainPhysicalAudioDeviceByHandle((void *)((intptr_t)default_source_index + 1)); + device = SDL_FindPhysicalAudioDeviceByHandle((void *)((intptr_t)default_source_index + 1)); if (device) { - SDL_UnlockMutex(device->lock); *default_capture = device; } From 4399b7171543d13ce89739d74ceb397fea562a13 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 11 Jul 2023 17:31:02 -0400 Subject: [PATCH 093/138] audio: Generalize how backends can lookup an SDL_AudioDevice. --- src/audio/SDL_audio.c | 18 ++++++++++++++---- src/audio/SDL_sysaudio.h | 3 +++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index f08531eb0b..cdf7a164ac 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -988,7 +988,7 @@ static SDL_AudioDevice *ObtainPhysicalAudioDevice(SDL_AudioDeviceID devid) return dev; } -SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) +SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(SDL_bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata) { if (!SDL_GetCurrentAudioDriver()) { SDL_SetError("Audio subsystem is not initialized"); @@ -999,7 +999,7 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) SDL_AudioDevice *dev = NULL; for (dev = current_audio.output_devices; dev != NULL; dev = dev->next) { - if (dev->handle == handle) { // found it? + if (callback(dev, userdata)) { // found it? break; } } @@ -1007,7 +1007,7 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) if (!dev) { // !!! FIXME: code duplication, from above. for (dev = current_audio.capture_devices; dev != NULL; dev = dev->next) { - if (dev->handle == handle) { // found it? + if (callback(dev, userdata)) { // found it? break; } } @@ -1016,7 +1016,7 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) SDL_UnlockRWLock(current_audio.device_list_lock); if (!dev) { - SDL_SetError("Device handle not found"); + SDL_SetError("Device not found"); } SDL_assert(!SDL_AtomicGet(&dev->condemned)); // shouldn't be in the list if pending deletion. @@ -1024,6 +1024,16 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) return dev; } +static SDL_bool TestDeviceHandleCallback(SDL_AudioDevice *device, void *handle) +{ + return device->handle == handle; +} + +SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle) +{ + return SDL_FindPhysicalAudioDeviceByCallback(TestDeviceHandleCallback, handle); +} + char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid) { SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 9d413a6cd9..f2016fe753 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -88,6 +88,9 @@ extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_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); +// Find an SDL_AudioDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE. +extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(SDL_bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata); + // Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct. extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device); From 77b3fb06eeda73bc4eb12fa3cfebc0c8c5b7203f Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 11 Jul 2023 17:32:31 -0400 Subject: [PATCH 094/138] directsound: First shot at updating for SDL3 audio API. This does an enormous amount of work in SDL_immdevice.c to simplify and clean up that interface, while moving some of its responsibilities to the higher level SDL_audio.c. I hope I saw the whole picture here, and this wasn't foolhardy of me. WASAPI has not been updated for these changes, or for SDL3 at all, yet. As such, it continues to be broken for now. It will be updated soon. This code compiles with my cross compiler, but hasn't been built with Visual Studio, or tested in any form, so there might be obvious fixes following along shortly. --- src/audio/SDL_sysaudio.h | 2 +- src/audio/directsound/SDL_directsound.c | 400 ++++++++++++----------- src/audio/directsound/SDL_directsound.h | 5 +- src/core/windows/SDL_immdevice.c | 405 +++++++++--------------- src/core/windows/SDL_immdevice.h | 15 +- 5 files changed, 369 insertions(+), 458 deletions(-) diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index f2016fe753..6995fbe102 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -120,7 +120,7 @@ typedef struct SDL_AudioDriverImpl int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen); void (*FlushCapture)(SDL_AudioDevice *device); void (*CloseDevice)(SDL_AudioDevice *device); - void (*FreeDeviceHandle)(SDL_AudioDevice *handle); // SDL is done with this device; free the handle from SDL_AddAudioDevice() + void (*FreeDeviceHandle)(SDL_AudioDevice *device); // SDL is done with this device; free the handle from SDL_AddAudioDevice() void (*Deinitialize)(void); // Some flags to push duplicate code into the core and reduce #ifdefs. diff --git a/src/audio/directsound/SDL_directsound.c b/src/audio/directsound/SDL_directsound.c index 3c9bbbdc34..f1ab29706b 100644 --- a/src/audio/directsound/SDL_directsound.c +++ b/src/audio/directsound/SDL_directsound.c @@ -22,34 +22,34 @@ #ifdef SDL_AUDIO_DRIVER_DSOUND -/* Allow access to a raw mixing buffer */ - #include "../SDL_audio_c.h" #include "SDL_directsound.h" #include #ifdef HAVE_MMDEVICEAPI_H #include "../../core/windows/SDL_immdevice.h" -#endif /* HAVE_MMDEVICEAPI_H */ +#endif #ifndef WAVE_FORMAT_IEEE_FLOAT #define WAVE_FORMAT_IEEE_FLOAT 0x0003 #endif -/* For Vista+, we can enumerate DSound devices with IMMDevice */ +// For Vista+, we can enumerate DSound devices with IMMDevice #ifdef HAVE_MMDEVICEAPI_H static SDL_bool SupportsIMMDevice = SDL_FALSE; -#endif /* HAVE_MMDEVICEAPI_H */ +#endif -/* DirectX function pointers for audio */ +// DirectX function pointers for audio static void *DSoundDLL = NULL; typedef HRESULT(WINAPI *fnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN); typedef HRESULT(WINAPI *fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID); typedef HRESULT(WINAPI *fnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN); typedef HRESULT(WINAPI *fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID); +typedef HRESULT(WINAPI *fnGetDeviceID)(LPCGUID, LPGUID); static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL; static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL; static fnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL; static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL; +static fnGetDeviceID pGetDeviceID = NULL; 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 } }; @@ -60,6 +60,7 @@ static void DSOUND_Unload(void) pDirectSoundEnumerateW = NULL; pDirectSoundCaptureCreate8 = NULL; pDirectSoundCaptureEnumerateW = NULL; + pGetDeviceID = NULL; if (DSoundDLL != NULL) { SDL_UnloadObject(DSoundDLL); @@ -77,18 +78,19 @@ static int DSOUND_Load(void) if (DSoundDLL == NULL) { SDL_SetError("DirectSound: failed to load DSOUND.DLL"); } else { -/* Now make sure we have DirectX 8 or better... */ +// Now make sure we have DirectX 8 or better... #define DSOUNDLOAD(f) \ { \ p##f = (fn##f)SDL_LoadFunction(DSoundDLL, #f); \ if (!p##f) \ loaded = 0; \ } - loaded = 1; /* will reset if necessary. */ + loaded = 1; // will reset if necessary. DSOUNDLOAD(DirectSoundCreate8); DSOUNDLOAD(DirectSoundEnumerateW); DSOUNDLOAD(DirectSoundCaptureCreate8); DSOUNDLOAD(DirectSoundCaptureEnumerateW); + DSOUNDLOAD(GetDeviceID); #undef DSOUNDLOAD if (!loaded) { @@ -150,55 +152,80 @@ static int SetDSerror(const char *function, int code) } static void DSOUND_FreeDeviceHandle(SDL_AudioDevice *device) -{ - SDL_free(device->handle); -} - -static int DSOUND_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) { #ifdef HAVE_MMDEVICEAPI_H if (SupportsIMMDevice) { - return SDL_IMMDevice_GetDefaultAudioInfo(name, spec, iscapture); + SDL_IMMDevice_FreeDeviceHandle(device); + } else +#endif + { + SDL_free(device->handle); } -#endif /* HAVE_MMDEVICEAPI_H */ - return SDL_Unsupported(); } -static BOOL CALLBACK FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID data) +// FindAllDevs is presumably only used on WinXP; Vista and later can use IMMDevice for better results. +typedef struct FindAllDevsData { - const int iscapture = (int)((size_t)data); - if (guid != NULL) { /* skip default device */ + SDL_bool iscapture; + SDL_AudioDevice **default_device; + LPCGUID default_device_guid; +} FindAllDevsData; + +static BOOL CALLBACK FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID userdata) +{ + FindAllDevsData *data = (FindAllDevsData *) userdata; + if (guid != NULL) { // skip default device char *str = WIN_LookupAudioDeviceName(desc, guid); if (str != NULL) { LPGUID cpyguid = (LPGUID)SDL_malloc(sizeof(GUID)); - SDL_memcpy(cpyguid, guid, sizeof(GUID)); + if (cpyguid) { + SDL_memcpy(cpyguid, guid, sizeof(GUID)); - /* Note that spec is NULL, because we are required to connect to the - * device before getting the channel mask and output format, making - * this information inaccessible at enumeration time - */ - SDL_AddAudioDevice(iscapture, str, NULL, cpyguid); - SDL_free(str); /* addfn() makes a copy of this string. */ + /* Note that spec is NULL, because we are required to connect to the + * device before getting the channel mask and output format, making + * this information inaccessible at enumeration time + */ + SDL_AudioDevice *device = SDL_AddAudioDevice(data->iscapture, str, NULL, cpyguid); + if (device && data->default_device && data->default_device_guid) { + if (SDL_memcmp(cpyguid, data->default_device_guid, sizeof (GUID)) == 0) { + *data->default_device = device; + } + } + } + SDL_free(str); // SDL_AddAudioDevice() makes a copy of this string. } } - return TRUE; /* keep enumerating. */ + return TRUE; // keep enumerating. } -static void DSOUND_DetectDevices(void) +static void DSOUND_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { #ifdef HAVE_MMDEVICEAPI_H if (SupportsIMMDevice) { - SDL_IMMDevice_EnumerateEndpoints(SDL_TRUE); - } else { -#endif /* HAVE_MMDEVICEAPI_H */ - pDirectSoundCaptureEnumerateW(FindAllDevs, (void *)((size_t)1)); - pDirectSoundEnumerateW(FindAllDevs, (void *)((size_t)0)); -#ifdef HAVE_MMDEVICEAPI_H + SDL_IMMDevice_EnumerateEndpoints(default_output, default_capture); + } else +#endif + { + // Without IMMDevice, you can enumerate devices and figure out the default devices, + // but you won't get device hotplug or default device change notifications. But this is + // only for WinXP; Windows Vista and later should be using IMMDevice. + FindAllDevsData data; + GUID guid; + + data.iscapture = SDL_TRUE; + data.default_device = default_capture; + data.default_device_guid = (pGetDeviceID(&DSDEVID_DefaultCapture, &guid) == DS_OK) ? &guid : NULL; + pDirectSoundCaptureEnumerateW(FindAllDevs, &data); + + data.iscapture = SDL_FALSE; + data.default_device = default_output; + data.default_device_guid = (pGetDeviceID(&DSDEVID_DefaultPlayback, &guid) == DS_OK) ? &guid : NULL; + pDirectSoundEnumerateW(FindAllDevs, &data); } -#endif /* HAVE_MMDEVICEAPI_H*/ + } -static void DSOUND_WaitDevice(SDL_AudioDevice *_this) +static void DSOUND_WaitDevice(SDL_AudioDevice *device) { DWORD status = 0; DWORD cursor = 0; @@ -208,11 +235,11 @@ static void DSOUND_WaitDevice(SDL_AudioDevice *_this) /* Semi-busy wait, since we have no way of getting play notification on a primary mixing buffer located in hardware (DirectX 5.0) */ - result = IDirectSoundBuffer_GetCurrentPosition(_this->hidden->mixbuf, + result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, &junk, &cursor); if (result != DS_OK) { if (result == DSERR_BUFFERLOST) { - IDirectSoundBuffer_Restore(_this->hidden->mixbuf); + IDirectSoundBuffer_Restore(device->hidden->mixbuf); } #ifdef DEBUG_SOUND SetDSerror("DirectSound GetCurrentPosition", result); @@ -220,21 +247,24 @@ static void DSOUND_WaitDevice(SDL_AudioDevice *_this) return; } - while ((cursor / _this->spec.size) == _this->hidden->lastchunk) { - /* FIXME: find out how much time is left and sleep that long */ + while ((cursor / device->buffer_size) == device->hidden->lastchunk) { + if (SDL_AtomicGet(&device->shutdown)) { + return; + } + SDL_Delay(1); - /* Try to restore a lost sound buffer */ - IDirectSoundBuffer_GetStatus(_this->hidden->mixbuf, &status); + // Try to restore a lost sound buffer + IDirectSoundBuffer_GetStatus(device->hidden->mixbuf, &status); if (status & DSBSTATUS_BUFFERLOST) { - IDirectSoundBuffer_Restore(_this->hidden->mixbuf); - IDirectSoundBuffer_GetStatus(_this->hidden->mixbuf, &status); + IDirectSoundBuffer_Restore(device->hidden->mixbuf); + IDirectSoundBuffer_GetStatus(device->hidden->mixbuf, &status); if (status & DSBSTATUS_BUFFERLOST) { break; } } if (!(status & DSBSTATUS_PLAYING)) { - result = IDirectSoundBuffer_Play(_this->hidden->mixbuf, 0, 0, + result = IDirectSoundBuffer_Play(device->hidden->mixbuf, 0, 0, DSBPLAY_LOOPING); if (result == DS_OK) { continue; @@ -245,8 +275,8 @@ static void DSOUND_WaitDevice(SDL_AudioDevice *_this) return; } - /* Find out where we are playing */ - result = IDirectSoundBuffer_GetCurrentPosition(_this->hidden->mixbuf, + // Find out where we are playing + result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, &junk, &cursor); if (result != DS_OK) { SetDSerror("DirectSound GetCurrentPosition", result); @@ -255,102 +285,100 @@ static void DSOUND_WaitDevice(SDL_AudioDevice *_this) } } -static void DSOUND_PlayDevice(SDL_AudioDevice *_this) +static void DSOUND_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { - /* Unlock the buffer, allowing it to play */ - if (_this->hidden->locked_buf) { - IDirectSoundBuffer_Unlock(_this->hidden->mixbuf, - _this->hidden->locked_buf, - _this->spec.size, NULL, 0); - } + // Unlock the buffer, allowing it to play + SDL_assert(buflen == device->buffer_size); + IDirectSoundBuffer_Unlock(device->hidden->mixbuf, (LPVOID) buffer, buflen, NULL, 0); } -static Uint8 *DSOUND_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *DSOUND_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { DWORD cursor = 0; DWORD junk = 0; HRESULT result = DS_OK; - DWORD rawlen = 0; - /* Figure out which blocks to fill next */ - _this->hidden->locked_buf = NULL; - result = IDirectSoundBuffer_GetCurrentPosition(_this->hidden->mixbuf, + SDL_assert(*buffer_size == device->buffer_size); + + // Figure out which blocks to fill next + device->hidden->locked_buf = NULL; + result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, &junk, &cursor); if (result == DSERR_BUFFERLOST) { - IDirectSoundBuffer_Restore(_this->hidden->mixbuf); - result = IDirectSoundBuffer_GetCurrentPosition(_this->hidden->mixbuf, + IDirectSoundBuffer_Restore(device->hidden->mixbuf); + result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, &junk, &cursor); } if (result != DS_OK) { SetDSerror("DirectSound GetCurrentPosition", result); return NULL; } - cursor /= _this->spec.size; + cursor /= device->buffer_size; #ifdef DEBUG_SOUND - /* Detect audio dropouts */ + // Detect audio dropouts { DWORD spot = cursor; - if (spot < _this->hidden->lastchunk) { - spot += _this->hidden->num_buffers; + if (spot < device->hidden->lastchunk) { + spot += device->hidden->num_buffers; } - if (spot > _this->hidden->lastchunk + 1) { + if (spot > device->hidden->lastchunk + 1) { fprintf(stderr, "Audio dropout, missed %d fragments\n", - (spot - (_this->hidden->lastchunk + 1))); + (spot - (device->hidden->lastchunk + 1))); } } #endif - _this->hidden->lastchunk = cursor; - cursor = (cursor + 1) % _this->hidden->num_buffers; - cursor *= _this->spec.size; + device->hidden->lastchunk = cursor; + cursor = (cursor + 1) % device->hidden->num_buffers; + cursor *= device->buffer_size; - /* Lock the audio buffer */ - result = IDirectSoundBuffer_Lock(_this->hidden->mixbuf, cursor, - _this->spec.size, - (LPVOID *)&_this->hidden->locked_buf, + // Lock the audio buffer + DWORD rawlen = 0; + result = IDirectSoundBuffer_Lock(device->hidden->mixbuf, cursor, + device->buffer_size, + (LPVOID *)&device->hidden->locked_buf, &rawlen, NULL, &junk, 0); if (result == DSERR_BUFFERLOST) { - IDirectSoundBuffer_Restore(_this->hidden->mixbuf); - result = IDirectSoundBuffer_Lock(_this->hidden->mixbuf, cursor, - _this->spec.size, - (LPVOID *)&_this->hidden->locked_buf, &rawlen, NULL, + IDirectSoundBuffer_Restore(device->hidden->mixbuf); + result = IDirectSoundBuffer_Lock(device->hidden->mixbuf, cursor, + device->buffer_size, + (LPVOID *)&device->hidden->locked_buf, &rawlen, NULL, &junk, 0); } if (result != DS_OK) { SetDSerror("DirectSound Lock", result); return NULL; } - return _this->hidden->locked_buf; + return device->hidden->locked_buf; } -static int DSOUND_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static void DSOUND_WaitCaptureDevice(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *h = _this->hidden; - DWORD junk, cursor, ptr1len, ptr2len; + struct SDL_PrivateAudioData *h = device->hidden; + while (!SDL_AtomicGet(&device->shutdown)) { + DWORD junk, cursor; + if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) { + SDL_AudioDeviceDisconnected(device); + return; + } else if ((cursor / device->buffer_size) != h->lastchunk) { + return; + } + SDL_Delay(1); + } +} + +static int DSOUND_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) +{ + struct SDL_PrivateAudioData *h = device->hidden; + DWORD ptr1len, ptr2len; VOID *ptr1, *ptr2; - SDL_assert((Uint32)buflen == _this->spec.size); + SDL_assert(buflen == device->buffer_size); - while (SDL_TRUE) { - if (SDL_AtomicGet(&_this->shutdown)) { /* in case the buffer froze... */ - SDL_memset(buffer, _this->spec.silence, buflen); - return buflen; - } - - if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) { - return -1; - } - if ((cursor / _this->spec.size) == h->lastchunk) { - SDL_Delay(1); /* FIXME: find out how much time is left and sleep that long */ - } else { - break; - } - } - - if (IDirectSoundCaptureBuffer_Lock(h->capturebuf, h->lastchunk * _this->spec.size, _this->spec.size, &ptr1, &ptr1len, &ptr2, &ptr2len, 0) != DS_OK) { + if (IDirectSoundCaptureBuffer_Lock(h->capturebuf, h->lastchunk * buflen, buflen, &ptr1, &ptr1len, &ptr2, &ptr2len, 0) != DS_OK) { return -1; } - SDL_assert(ptr1len == _this->spec.size); + SDL_assert(ptr1len == buflen); SDL_assert(ptr2 == NULL); SDL_assert(ptr2len == 0); @@ -362,51 +390,54 @@ static int DSOUND_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int bu h->lastchunk = (h->lastchunk + 1) % h->num_buffers; - return ptr1len; + return (int) ptr1len; } -static void DSOUND_FlushCapture(SDL_AudioDevice *_this) +static void DSOUND_FlushCapture(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *h = _this->hidden; + struct SDL_PrivateAudioData *h = device->hidden; DWORD junk, cursor; if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) == DS_OK) { - h->lastchunk = cursor / _this->spec.size; + h->lastchunk = cursor / device->buffer_size; } } -static void DSOUND_CloseDevice(SDL_AudioDevice *_this) +static void DSOUND_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden->mixbuf != NULL) { - IDirectSoundBuffer_Stop(_this->hidden->mixbuf); - IDirectSoundBuffer_Release(_this->hidden->mixbuf); + if (device->hidden) { + if (device->hidden->mixbuf != NULL) { + IDirectSoundBuffer_Stop(device->hidden->mixbuf); + IDirectSoundBuffer_Release(device->hidden->mixbuf); + } + if (device->hidden->sound != NULL) { + IDirectSound_Release(device->hidden->sound); + } + if (device->hidden->capturebuf != NULL) { + IDirectSoundCaptureBuffer_Stop(device->hidden->capturebuf); + IDirectSoundCaptureBuffer_Release(device->hidden->capturebuf); + } + if (device->hidden->capture != NULL) { + IDirectSoundCapture_Release(device->hidden->capture); + } + SDL_free(device->hidden); + device->hidden = NULL; } - if (_this->hidden->sound != NULL) { - IDirectSound_Release(_this->hidden->sound); - } - if (_this->hidden->capturebuf != NULL) { - IDirectSoundCaptureBuffer_Stop(_this->hidden->capturebuf); - IDirectSoundCaptureBuffer_Release(_this->hidden->capturebuf); - } - if (_this->hidden->capture != NULL) { - IDirectSoundCapture_Release(_this->hidden->capture); - } - SDL_free(_this->hidden); } /* This function tries to create a secondary audio buffer, and returns the number of audio chunks available in the created buffer. This is for playback devices, not capture. */ -static int CreateSecondary(SDL_AudioDevice *_this, const DWORD bufsize, WAVEFORMATEX *wfmt) +static int CreateSecondary(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt) { - LPDIRECTSOUND sndObj = _this->hidden->sound; - LPDIRECTSOUNDBUFFER *sndbuf = &_this->hidden->mixbuf; + LPDIRECTSOUND sndObj = device->hidden->sound; + LPDIRECTSOUNDBUFFER *sndbuf = &device->hidden->mixbuf; HRESULT result = DS_OK; DSBUFFERDESC format; LPVOID pvAudioPtr1, pvAudioPtr2; DWORD dwAudioBytes1, dwAudioBytes2; - /* Try to create the secondary buffer */ + // Try to create the secondary buffer SDL_zero(format); format.dwSize = sizeof(format); format.dwFlags = DSBCAPS_GETCURRENTPOSITION2; @@ -419,30 +450,29 @@ static int CreateSecondary(SDL_AudioDevice *_this, const DWORD bufsize, WAVEFORM } IDirectSoundBuffer_SetFormat(*sndbuf, wfmt); - /* Silence the initial audio buffer */ + // Silence the initial audio buffer result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes, (LPVOID *)&pvAudioPtr1, &dwAudioBytes1, (LPVOID *)&pvAudioPtr2, &dwAudioBytes2, DSBLOCK_ENTIREBUFFER); if (result == DS_OK) { - SDL_memset(pvAudioPtr1, _this->spec.silence, dwAudioBytes1); + SDL_memset(pvAudioPtr1, device->silence_value, dwAudioBytes1); IDirectSoundBuffer_Unlock(*sndbuf, (LPVOID)pvAudioPtr1, dwAudioBytes1, (LPVOID)pvAudioPtr2, dwAudioBytes2); } - /* We're ready to go */ - return 0; + return 0; // We're ready to go } /* This function tries to create a capture buffer, and returns the number of audio chunks available in the created buffer. This is for capture devices, not playback. */ -static int CreateCaptureBuffer(SDL_AudioDevice *_this, const DWORD bufsize, WAVEFORMATEX *wfmt) +static int CreateCaptureBuffer(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt) { - LPDIRECTSOUNDCAPTURE capture = _this->hidden->capture; - LPDIRECTSOUNDCAPTUREBUFFER *capturebuf = &_this->hidden->capturebuf; + LPDIRECTSOUNDCAPTURE capture = device->hidden->capture; + LPDIRECTSOUNDCAPTUREBUFFER *capturebuf = &device->hidden->capturebuf; DSCBUFFERDESC format; HRESULT result; @@ -464,7 +494,7 @@ static int CreateCaptureBuffer(SDL_AudioDevice *_this, const DWORD bufsize, WAVE } #if 0 - /* presumably this starts at zero, but just in case... */ + // presumably this starts at zero, but just in case... result = IDirectSoundCaptureBuffer_GetCurrentPosition(*capturebuf, &junk, &cursor); if (result != DS_OK) { IDirectSoundCaptureBuffer_Stop(*capturebuf); @@ -472,42 +502,45 @@ static int CreateCaptureBuffer(SDL_AudioDevice *_this, const DWORD bufsize, WAVE return SetDSerror("DirectSound GetCurrentPosition", result); } - _this->hidden->lastchunk = cursor / _this->spec.size; + device->hidden->lastchunk = cursor / device->buffer_size; #endif return 0; } -static int DSOUND_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int DSOUND_OpenDevice(SDL_AudioDevice *device) { - const DWORD numchunks = 8; - HRESULT result; - SDL_bool tried_format = SDL_FALSE; - SDL_bool iscapture = _this->iscapture; - SDL_AudioFormat test_format; - const SDL_AudioFormat *closefmts; - LPGUID guid = (LPGUID)_this->handle; - DWORD bufsize; - - /* Initialize all variables that we clean on shutdown */ - _this->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + // Initialize all variables that we clean on shutdown + device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_zerop(_this->hidden); - /* Open the audio device */ - if (iscapture) { - result = pDirectSoundCaptureCreate8(guid, &_this->hidden->capture, NULL); + // Open the audio device + LPGUID guid; +#ifdef HAVE_MMDEVICEAPI_H + if (SupportsIMMDevice) { + guid = SDL_IMMDevice_GetDirectSoundGUID(device); + } else +#endif + { + guid = (LPGUID) device->handle; + } + + SDL_assert(guid != NULL); + + HRESULT result; + if (device->iscapture) { + result = pDirectSoundCaptureCreate8(guid, &device->hidden->capture, NULL); if (result != DS_OK) { return SetDSerror("DirectSoundCaptureCreate8", result); } } else { - result = pDirectSoundCreate8(guid, &_this->hidden->sound, NULL); + result = pDirectSoundCreate8(guid, &device->hidden->sound, NULL); if (result != DS_OK) { return SetDSerror("DirectSoundCreate8", result); } - result = IDirectSound_SetCooperativeLevel(_this->hidden->sound, + result = IDirectSound_SetCooperativeLevel(device->hidden->sound, GetDesktopWindow(), DSSCL_NORMAL); if (result != DS_OK) { @@ -515,7 +548,10 @@ static int DSOUND_OpenDevice(SDL_AudioDevice *_this, const char *devname) } } - closefmts = SDL_ClosestAudioFormats(_this->spec.format); + const DWORD numchunks = 8; + SDL_bool tried_format = SDL_FALSE; + SDL_AudioFormat test_format; + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { switch (test_format) { case SDL_AUDIO_U8: @@ -524,69 +560,68 @@ static int DSOUND_OpenDevice(SDL_AudioDevice *_this, const char *devname) case SDL_AUDIO_F32: tried_format = SDL_TRUE; - _this->spec.format = test_format; + device->spec.format = test_format; - /* Update the fragment size as size in bytes */ - SDL_CalculateAudioSpec(&_this->spec); + // Update the fragment size as size in bytes + SDL_UpdatedAudioDeviceFormat(device); - bufsize = numchunks * _this->spec.size; + const DWORD bufsize = numchunks * device->buffer_size; if ((bufsize < DSBSIZE_MIN) || (bufsize > DSBSIZE_MAX)) { SDL_SetError("Sound buffer size must be between %d and %d", (int)((DSBSIZE_MIN < numchunks) ? 1 : DSBSIZE_MIN / numchunks), (int)(DSBSIZE_MAX / numchunks)); } else { - int rc; WAVEFORMATEXTENSIBLE wfmt; SDL_zero(wfmt); - if (_this->spec.channels > 2) { + if (device->spec.channels > 2) { wfmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; wfmt.Format.cbSize = sizeof(wfmt) - sizeof(WAVEFORMATEX); - if (SDL_AUDIO_ISFLOAT(_this->spec.format)) { + if (SDL_AUDIO_ISFLOAT(device->spec.format)) { SDL_memcpy(&wfmt.SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID)); } else { SDL_memcpy(&wfmt.SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)); } - wfmt.Samples.wValidBitsPerSample = SDL_AUDIO_BITSIZE(_this->spec.format); + wfmt.Samples.wValidBitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format); - switch (_this->spec.channels) { - case 3: /* 3.0 (or 2.1) */ + switch (device->spec.channels) { + case 3: // 3.0 (or 2.1) wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER; break; - case 4: /* 4.0 */ + case 4: // 4.0 wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break; - case 5: /* 5.0 (or 4.1) */ + case 5: // 5.0 (or 4.1) wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break; - case 6: /* 5.1 */ + case 6: // 5.1 wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break; - case 7: /* 6.1 */ + case 7: // 6.1 wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_BACK_CENTER; break; - case 8: /* 7.1 */ + case 8: // 7.1 wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT; break; default: SDL_assert(0 && "Unsupported channel count!"); break; } - } else if (SDL_AUDIO_ISFLOAT(_this->spec.format)) { + } else if (SDL_AUDIO_ISFLOAT(device->spec.format)) { wfmt.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; } else { wfmt.Format.wFormatTag = WAVE_FORMAT_PCM; } - wfmt.Format.wBitsPerSample = SDL_AUDIO_BITSIZE(_this->spec.format); - wfmt.Format.nChannels = _this->spec.channels; - wfmt.Format.nSamplesPerSec = _this->spec.freq; + wfmt.Format.wBitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format); + wfmt.Format.nChannels = device->spec.channels; + wfmt.Format.nSamplesPerSec = device->spec.freq; wfmt.Format.nBlockAlign = wfmt.Format.nChannels * (wfmt.Format.wBitsPerSample / 8); wfmt.Format.nAvgBytesPerSec = wfmt.Format.nSamplesPerSec * wfmt.Format.nBlockAlign; - rc = iscapture ? CreateCaptureBuffer(_this, bufsize, (WAVEFORMATEX *)&wfmt) : CreateSecondary(_this, bufsize, (WAVEFORMATEX *)&wfmt); + const int rc = device->iscapture ? CreateCaptureBuffer(device, bufsize, (WAVEFORMATEX *)&wfmt) : CreateSecondary(device, bufsize, (WAVEFORMATEX *)&wfmt); if (rc == 0) { - _this->hidden->num_buffers = numchunks; + device->hidden->num_buffers = numchunks; break; } } @@ -599,14 +634,14 @@ static int DSOUND_OpenDevice(SDL_AudioDevice *_this, const char *devname) if (!test_format) { if (tried_format) { - return -1; /* CreateSecondary() should have called SDL_SetError(). */ + return -1; // CreateSecondary() should have called SDL_SetError(). } return SDL_SetError("%s: Unsupported audio format", "directsound"); } - /* Playback buffers will auto-start playing in DSOUND_WaitDevice() */ + // Playback buffers will auto-start playing in DSOUND_WaitDevice() - return 0; /* good to go. */ + return 0; // good to go. } static void DSOUND_Deinitialize(void) @@ -616,7 +651,7 @@ static void DSOUND_Deinitialize(void) SDL_IMMDevice_Quit(); SupportsIMMDevice = SDL_FALSE; } -#endif /* HAVE_MMDEVICEAPI_H */ +#endif DSOUND_Unload(); } @@ -628,28 +663,27 @@ static SDL_bool DSOUND_Init(SDL_AudioDriverImpl *impl) #ifdef HAVE_MMDEVICEAPI_H SupportsIMMDevice = !(SDL_IMMDevice_Init() < 0); -#endif /* HAVE_MMDEVICEAPI_H */ +#endif - /* Set the function pointers */ impl->DetectDevices = DSOUND_DetectDevices; impl->OpenDevice = DSOUND_OpenDevice; impl->PlayDevice = DSOUND_PlayDevice; impl->WaitDevice = DSOUND_WaitDevice; impl->GetDeviceBuf = DSOUND_GetDeviceBuf; + impl->WaitCaptureDevice = DSOUND_WaitCaptureDevice; impl->CaptureFromDevice = DSOUND_CaptureFromDevice; impl->FlushCapture = DSOUND_FlushCapture; impl->CloseDevice = DSOUND_CloseDevice; impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle; impl->Deinitialize = DSOUND_Deinitialize; - impl->GetDefaultAudioInfo = DSOUND_GetDefaultAudioInfo; impl->HasCaptureSupport = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap DSOUND_bootstrap = { "directsound", "DirectSound", DSOUND_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_DSOUND */ +#endif // SDL_AUDIO_DRIVER_DSOUND diff --git a/src/audio/directsound/SDL_directsound.h b/src/audio/directsound/SDL_directsound.h index d66c920aec..18b67dac27 100644 --- a/src/audio/directsound/SDL_directsound.h +++ b/src/audio/directsound/SDL_directsound.h @@ -27,9 +27,10 @@ #include "../SDL_sysaudio.h" -/* The DirectSound objects */ +// The DirectSound objects struct SDL_PrivateAudioData { + // !!! FIXME: make this a union with capture/playback sections? LPDIRECTSOUND sound; LPDIRECTSOUNDBUFFER mixbuf; LPDIRECTSOUNDCAPTURE capture; @@ -39,4 +40,4 @@ struct SDL_PrivateAudioData Uint8 *locked_buf; }; -#endif /* SDL_directsound_h_ */ +#endif // SDL_directsound_h_ diff --git a/src/core/windows/SDL_immdevice.c b/src/core/windows/SDL_immdevice.c index 8f91268fb3..c00916a417 100644 --- a/src/core/windows/SDL_immdevice.c +++ b/src/core/windows/SDL_immdevice.c @@ -27,6 +27,12 @@ #include "../../audio/SDL_sysaudio.h" #include /* For CLSIDFromString */ +typedef struct SDL_IMMDevice_HandleData +{ + LPWSTR immdevice_id; + GUID directsound_guid; +} SDL_IMMDevice_HandleData; + static const ERole SDL_IMMDevice_role = eConsole; /* !!! FIXME: should this be eMultimedia? Should be a hint? */ /* This is global to the WASAPI target, to handle hotplug and default device lookup. */ @@ -51,9 +57,26 @@ static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010,{ static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; /* *INDENT-ON* */ /* clang-format on */ -/* these increment as default devices change. Opened default devices pick up changes in their threads. */ -SDL_AtomicInt SDL_IMMDevice_DefaultPlaybackGeneration; -SDL_AtomicInt SDL_IMMDevice_DefaultCaptureGeneration; +static SDL_bool FindByDevIDCallback(SDL_AudioDevice *device, void *userdata) +{ + const SDL_IMMDevice_HandleData *handle = (const SDL_IMMDevice_HandleData *) device->handle; + return (SDL_wcscmp(handle->immdevice_id, (LPCWSTR) userdata) == 0) ? SDL_TRUE : SDL_FALSE; +} + +static SDL_AudioDevice *SDL_IMMDevice_FindByDevID(LPCWSTR devid) +{ + return SDL_FindPhysicalAudioDeviceByCallback(FindByDevIDCallback, (void *) devid); +} + +LPGUID SDL_IMMDevice_GetDirectSoundGUID(SDL_AudioDevice *device) +{ + return (device && device->handle) ? &(((SDL_IMMDevice_HandleData *) device->handle)->directsound_guid) : NULL; +} + +LPCWSTR SDL_IMMDevice_GetDevID(SDL_AudioDevice *device) +{ + return (device && device->handle) ? ((const SDL_IMMDevice_HandleData *) device->handle)->immdevice_id : NULL; +} static void GetMMDeviceInfo(IMMDevice *device, char **utf8dev, WAVEFORMATEXTENSIBLE *fmt, GUID *guid) { @@ -82,94 +105,54 @@ static void GetMMDeviceInfo(IMMDevice *device, char **utf8dev, WAVEFORMATEXTENSI } } -/* This is a list of device id strings we have inflight, so we have consistent pointers to the same device. */ -typedef struct DevIdList +void SDL_IMMDevice_FreeDeviceHandle(SDL_AudioDevice *device) { - LPWSTR str; - LPGUID guid; - struct DevIdList *next; -} DevIdList; - -static DevIdList *deviceid_list = NULL; - -static void SDL_IMMDevice_Remove(const SDL_bool iscapture, LPCWSTR devid, SDL_bool useguid) -{ - 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, useguid ? ((void *)i->guid) : ((void *)i->str)); - SDL_free(i->str); - SDL_free(i); - } else { - prev = i; - } + if (device && device->handle) { + SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *) device->handle; + SDL_free(handle->immdevice_id); + SDL_free(handle); + device->handle = NULL; } } -static void SDL_IMMDevice_Add(const SDL_bool iscapture, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid, GUID *dsoundguid, SDL_bool useguid) +static SDL_AudioDevice *SDL_IMMDevice_Add(const SDL_bool iscapture, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid, GUID *dsoundguid) { - DevIdList *devidlist; - SDL_AudioSpec spec; - LPWSTR devidcopy; - LPGUID cpyguid; - LPVOID driverdata; - /* 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. */ + if (!devname) { + return NULL; + } + + // see if we already have this one first. + SDL_AudioDevice *device = SDL_IMMDevice_FindByDevID(devid); + if (!device) { + // handle is freed by SDL_IMMDevice_FreeDeviceHandle! + SDL_IMMDevice_HandleData *handle = SDL_malloc(sizeof(SDL_IMMDevice_HandleData)); + if (!handle) { + SDL_OutOfMemory(); + return NULL; } - } - - devidlist = (DevIdList *)SDL_malloc(sizeof(*devidlist)); - if (devidlist == NULL) { - return; /* oh well. */ - } - - devidcopy = SDL_wcsdup(devid); - if (!devidcopy) { - SDL_free(devidlist); - return; /* oh well. */ - } - - if (useguid) { - /* This is freed by DSOUND_FreeDeviceData! */ - cpyguid = (LPGUID)SDL_malloc(sizeof(GUID)); - if (!cpyguid) { - SDL_free(devidlist); - SDL_free(devidcopy); - return; /* oh well. */ + handle->immdevice_id = SDL_wcsdup(devid); + if (!handle->immdevice_id) { + SDL_OutOfMemory(); + SDL_free(handle); + return NULL; } - SDL_memcpy(cpyguid, dsoundguid, sizeof(GUID)); - driverdata = cpyguid; - } else { - cpyguid = NULL; - driverdata = devidcopy; + SDL_memcpy(&handle->directsound_guid, dsoundguid, sizeof(GUID)); + + SDL_AudioSpec spec; + SDL_zero(spec); + spec.channels = (Uint8)fmt->Format.nChannels; + spec.freq = fmt->Format.nSamplesPerSec; + spec.format = WaveFormatToSDLFormat((WAVEFORMATEX *)fmt); + + device = SDL_AddAudioDevice(iscapture, devname, &spec, handle); } - devidlist->str = devidcopy; - devidlist->guid = cpyguid; - 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, driverdata); + return device; } /* We need a COM subclass of IMMNotificationClient for hotplug support, which is @@ -184,11 +167,11 @@ typedef struct SDLMMNotificationClient SDL_bool useguid; } SDLMMNotificationClient; -static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_QueryInterface(IMMNotificationClient *this, REFIID iid, void **ppv) +static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_QueryInterface(IMMNotificationClient *client, REFIID iid, void **ppv) { if ((WIN_IsEqualIID(iid, &IID_IUnknown)) || (WIN_IsEqualIID(iid, &SDL_IID_IMMNotificationClient))) { - *ppv = this; - this->lpVtbl->AddRef(this); + *ppv = client; + client->lpVtbl->AddRef(client); return S_OK; } @@ -196,55 +179,34 @@ static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_QueryInterface(IMMNotif return E_NOINTERFACE; } -static ULONG STDMETHODCALLTYPE SDLMMNotificationClient_AddRef(IMMNotificationClient *ithis) +static ULONG STDMETHODCALLTYPE SDLMMNotificationClient_AddRef(IMMNotificationClient *iclient) { - SDLMMNotificationClient *this = (SDLMMNotificationClient *)ithis; - return (ULONG)(SDL_AtomicIncRef(&this->refcount) + 1); + SDLMMNotificationClient *client = (SDLMMNotificationClient *)iclient; + return (ULONG)(SDL_AtomicIncRef(&client->refcount) + 1); } -static ULONG STDMETHODCALLTYPE SDLMMNotificationClient_Release(IMMNotificationClient *ithis) +static ULONG STDMETHODCALLTYPE SDLMMNotificationClient_Release(IMMNotificationClient *iclient) { - /* this is a static object; we don't ever free it. */ - SDLMMNotificationClient *this = (SDLMMNotificationClient *)ithis; - const ULONG retval = SDL_AtomicDecRef(&this->refcount); + /* client is a static object; we don't ever free it. */ + SDLMMNotificationClient *client = (SDLMMNotificationClient *)iclient; + const ULONG retval = SDL_AtomicDecRef(&client->refcount); if (retval == 0) { - SDL_AtomicSet(&this->refcount, 0); /* uhh... */ + SDL_AtomicSet(&client->refcount, 0); /* uhh... */ return 0; } return retval - 1; } -/* These are the entry points called when WASAPI device endpoints change. */ -static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDefaultDeviceChanged(IMMNotificationClient *ithis, EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) +// These are the entry points called when WASAPI device endpoints change. +static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDefaultDeviceChanged(IMMNotificationClient *iclient, EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) { - if (role != SDL_IMMDevice_role) { - return S_OK; /* ignore it. */ + if (role == SDL_IMMDevice_role) { + SDL_DefaultAudioDeviceChanged(SDL_IMMDevice_FindByDevID(pwstrDeviceId)); } - - /* Increment the "generation," so opened devices will pick this up in their threads. */ - switch (flow) { - case eRender: - SDL_AtomicAdd(&SDL_IMMDevice_DefaultPlaybackGeneration, 1); - break; - - case eCapture: - SDL_AtomicAdd(&SDL_IMMDevice_DefaultCaptureGeneration, 1); - break; - - case eAll: - SDL_AtomicAdd(&SDL_IMMDevice_DefaultPlaybackGeneration, 1); - SDL_AtomicAdd(&SDL_IMMDevice_DefaultCaptureGeneration, 1); - break; - - default: - SDL_assert(!"uhoh, unexpected OnDefaultDeviceChange flow!"); - break; - } - return S_OK; } -static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceAdded(IMMNotificationClient *ithis, LPCWSTR pwstrDeviceId) +static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceAdded(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId) { /* we ignore this; devices added here then progress to ACTIVE, if appropriate, in OnDeviceStateChange, making that a better place to deal with device adds. More @@ -254,13 +216,12 @@ static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceAdded(IMMNotifi return S_OK; } -static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceRemoved(IMMNotificationClient *ithis, LPCWSTR pwstrDeviceId) +static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceRemoved(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId) { - /* See notes in OnDeviceAdded handler about why we ignore this. */ - return S_OK; + return S_OK; // See notes in OnDeviceAdded handler about why we ignore this. } -static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *ithis, LPCWSTR pwstrDeviceId, DWORD dwNewState) +static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId, DWORD dwNewState) { IMMDevice *device = NULL; @@ -270,18 +231,17 @@ static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IM EDataFlow flow; if (SUCCEEDED(IMMEndpoint_GetDataFlow(endpoint, &flow))) { const SDL_bool iscapture = (flow == eCapture); - const SDLMMNotificationClient *client = (SDLMMNotificationClient *)ithis; if (dwNewState == DEVICE_STATE_ACTIVE) { char *utf8dev; WAVEFORMATEXTENSIBLE fmt; GUID dsoundguid; GetMMDeviceInfo(device, &utf8dev, &fmt, &dsoundguid); if (utf8dev) { - SDL_IMMDevice_Add(iscapture, utf8dev, &fmt, pwstrDeviceId, &dsoundguid, client->useguid); + SDL_IMMDevice_Add(iscapture, utf8dev, &fmt, pwstrDeviceId, &dsoundguid); SDL_free(utf8dev); } } else { - SDL_IMMDevice_Remove(iscapture, pwstrDeviceId, client->useguid); + SDL_AudioDeviceDisconnected(SDL_IMMDevice_FindByDevID(pwstrDeviceId)); } } IMMEndpoint_Release(endpoint); @@ -292,9 +252,9 @@ static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IM return S_OK; } -static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *this, LPCWSTR pwstrDeviceId, const PROPERTYKEY key) +static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *client, LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { - return S_OK; /* we don't care about these. */ + return S_OK; // we don't care about these. } static const IMMNotificationClientVtbl notification_client_vtbl = { @@ -314,31 +274,25 @@ int SDL_IMMDevice_Init(void) { HRESULT ret; - SDL_AtomicSet(&SDL_IMMDevice_DefaultPlaybackGeneration, 1); - SDL_AtomicSet(&SDL_IMMDevice_DefaultCaptureGeneration, 1); - /* just skip the discussion with COM here. */ if (!WIN_IsWindowsVistaOrGreater()) { - return SDL_SetError("WASAPI support requires Windows Vista or later"); + return SDL_SetError("IMMDevice support requires Windows Vista or later"); } if (FAILED(WIN_CoInitialize())) { - return SDL_SetError("WASAPI: CoInitialize() failed"); + return SDL_SetError("IMMDevice: CoInitialize() failed"); } ret = CoCreateInstance(&SDL_CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_IMMDeviceEnumerator, (LPVOID *)&enumerator); if (FAILED(ret)) { WIN_CoUninitialize(); - return WIN_SetErrorFromHRESULT("WASAPI CoCreateInstance(MMDeviceEnumerator)", ret); + return WIN_SetErrorFromHRESULT("IMMDevice CoCreateInstance(MMDeviceEnumerator)", ret); } return 0; } void SDL_IMMDevice_Quit(void) { - DevIdList *devidlist; - DevIdList *next; - if (enumerator) { IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)¬ification_client); IMMDeviceEnumerator_Release(enumerator); @@ -346,178 +300,101 @@ void SDL_IMMDevice_Quit(void) } WIN_CoUninitialize(); - - for (devidlist = deviceid_list; devidlist; devidlist = next) { - next = devidlist->next; - SDL_free(devidlist->str); - SDL_free(devidlist); - } - deviceid_list = NULL; } -int SDL_IMMDevice_Get(LPCWSTR devid, IMMDevice **device, SDL_bool iscapture) +int SDL_IMMDevice_Get(SDL_AudioDevice *device, IMMDevice **immdevice, SDL_bool iscapture) { const Uint64 timeout = SDL_GetTicks() + 8000; /* intel's audio drivers can fail for up to EIGHT SECONDS after a device is connected or we wake from sleep. */ - HRESULT ret; SDL_assert(device != NULL); + SDL_assert(immdevice != NULL); - while (SDL_TRUE) { - if (devid == NULL) { - const EDataFlow dataflow = iscapture ? eCapture : eRender; - ret = IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, dataflow, SDL_IMMDevice_role, device); - } else { - ret = IMMDeviceEnumerator_GetDevice(enumerator, devid, device); + LPCWSTR devid = SDL_IMMDevice_GetDevID(device); + SDL_assert(devid != NULL); + + HRESULT ret; + while ((ret = IMMDeviceEnumerator_GetDevice(enumerator, devid, immdevice)) == E_NOTFOUND) { + const Uint64 now = SDL_GetTicks(); + if (timeout > now) { + const Uint64 ticksleft = timeout - now; + SDL_Delay((Uint32)SDL_min(ticksleft, 300)); /* wait awhile and try again. */ + continue; } - - if (SUCCEEDED(ret)) { - break; - } - - if (ret == E_NOTFOUND) { - const Uint64 now = SDL_GetTicks(); - if (timeout > now) { - const Uint64 ticksleft = timeout - now; - SDL_Delay((Uint32)SDL_min(ticksleft, 300)); /* wait awhile and try again. */ - continue; - } - } - - return WIN_SetErrorFromHRESULT("WASAPI can't find requested audio endpoint", ret); + break; } - return 0; + + return SUCCEEDED(ret) ? 0 : WIN_SetErrorFromHRESULT("WASAPI can't find requested audio endpoint", ret); + } -typedef struct +static void EnumerateEndpointsForFlow(const SDL_bool iscapture, SDL_AudioDevice **default_device) { - LPWSTR devid; - char *devname; - WAVEFORMATEXTENSIBLE fmt; - GUID dsoundguid; -} EndpointItem; - -static int SDLCALL sort_endpoints(const void *_a, const void *_b) -{ - LPWSTR a = ((const EndpointItem *)_a)->devid; - LPWSTR b = ((const EndpointItem *)_b)->devid; - if (!a && !b) { - return 0; - } else if (!a && b) { - return -1; - } else if (a && !b) { - return 1; - } - - while (SDL_TRUE) { - if (*a < *b) { - return -1; - } else if (*a > *b) { - return 1; - } else if (*a == 0) { - break; - } - a++; - b++; - } - - return 0; -} - -static void EnumerateEndpointsForFlow(const SDL_bool iscapture) -{ - IMMDeviceCollection *collection = NULL; - EndpointItem *items; - UINT i, total; - /* Note that WASAPI separates "adapter devices" from "audio endpoint devices" ...one adapter device ("SoundBlaster Pro") might have multiple endpoint devices ("Speakers", "Line-Out"). */ + IMMDeviceCollection *collection = NULL; if (FAILED(IMMDeviceEnumerator_EnumAudioEndpoints(enumerator, iscapture ? eCapture : eRender, DEVICE_STATE_ACTIVE, &collection))) { return; } + UINT total = 0; if (FAILED(IMMDeviceCollection_GetCount(collection, &total))) { IMMDeviceCollection_Release(collection); return; } - items = (EndpointItem *)SDL_calloc(total, sizeof(EndpointItem)); - if (items == NULL) { - return; /* oh well. */ - } - - for (i = 0; i < total; i++) { - EndpointItem *item = items + i; - IMMDevice *device = NULL; - if (SUCCEEDED(IMMDeviceCollection_Item(collection, i, &device))) { - if (SUCCEEDED(IMMDevice_GetId(device, &item->devid))) { - GetMMDeviceInfo(device, &item->devname, &item->fmt, &item->dsoundguid); + LPWSTR default_devid = NULL; + if (default_device) { + IMMDevice *default_immdevice = NULL; + const EDataFlow dataflow = iscapture ? eCapture : eRender; + if (SUCCEEDED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, dataflow, SDL_IMMDevice_role, &default_immdevice))) { + LPWSTR devid = NULL; + if (SUCCEEDED(IMMDevice_GetId(default_immdevice, &devid))) { + default_devid = SDL_wcsdup(devid); // if this fails, oh well. + CoTaskMemFree(devid); } - IMMDevice_Release(device); + IMMDevice_Release(default_immdevice); } } - /* sort the list of devices by their guid so list is consistent between runs */ - SDL_qsort(items, total, sizeof(*items), sort_endpoints); - - /* Send the sorted list on to the SDL's higher level. */ - for (i = 0; i < total; i++) { - EndpointItem *item = items + i; - if ((item->devid) && (item->devname)) { - SDL_IMMDevice_Add(iscapture, item->devname, &item->fmt, item->devid, &item->dsoundguid, notification_client.useguid); + for (UINT i = 0; i < total; i++) { + IMMDevice *immdevice = NULL; + if (SUCCEEDED(IMMDeviceCollection_Item(collection, i, &immdevice))) { + LPWSTR devid = NULL; + if (SUCCEEDED(IMMDevice_GetId(immdevice, &devid))) { + char *devname = NULL; + WAVEFORMATEXTENSIBLE fmt; + GUID dsoundguid; + SDL_zero(fmt); + SDL_zero(dsoundguid); + GetMMDeviceInfo(immdevice, &devname, &fmt, &dsoundguid); + if (devname) { + SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(iscapture, devname, &fmt, devid, &dsoundguid); + if (default_device && default_devid && SDL_wcscmp(default_devid, devid)) { + *default_device = sdldevice; + } + SDL_free(devname); + } + CoTaskMemFree(devid); + } + IMMDevice_Release(immdevice); } - SDL_free(item->devname); - CoTaskMemFree(item->devid); } - SDL_free(items); + SDL_free(default_devid); + IMMDeviceCollection_Release(collection); } -void SDL_IMMDevice_EnumerateEndpoints(SDL_bool useguid) +void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { - notification_client.useguid = useguid; - - EnumerateEndpointsForFlow(SDL_FALSE); /* playback */ - EnumerateEndpointsForFlow(SDL_TRUE); /* capture */ + EnumerateEndpointsForFlow(SDL_FALSE, default_output); /* playback */ + EnumerateEndpointsForFlow(SDL_TRUE, default_capture); /* capture */ /* if this fails, we just won't get hotplug events. Carry on anyhow. */ IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)¬ification_client); } -int SDL_IMMDevice_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) -{ - WAVEFORMATEXTENSIBLE fmt; - IMMDevice *device = NULL; - char *filler; - GUID morefiller; - const EDataFlow dataflow = iscapture ? eCapture : eRender; - HRESULT ret = IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, dataflow, SDL_IMMDevice_role, &device); - - if (FAILED(ret)) { - SDL_assert(device == NULL); - return WIN_SetErrorFromHRESULT("WASAPI can't find default audio endpoint", ret); - } - - if (name == NULL) { - name = &filler; - } - - SDL_zero(fmt); - GetMMDeviceInfo(device, name, &fmt, &morefiller); - IMMDevice_Release(device); - - if (name == &filler) { - SDL_free(filler); - } - - SDL_zerop(spec); - spec->channels = (Uint8)fmt.Format.nChannels; - spec->freq = fmt.Format.nSamplesPerSec; - spec->format = WaveFormatToSDLFormat((WAVEFORMATEX *)&fmt); - return 0; -} - SDL_AudioFormat WaveFormatToSDLFormat(WAVEFORMATEX *waveformat) { if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) { diff --git a/src/core/windows/SDL_immdevice.h b/src/core/windows/SDL_immdevice.h index c97402fda1..a5a0a557bb 100644 --- a/src/core/windows/SDL_immdevice.h +++ b/src/core/windows/SDL_immdevice.h @@ -26,16 +26,15 @@ #include #include +typedef struct SDL_AudioDevice SDL_AudioDevice; // this is defined in src/audio/SDL_sysaudio.h + int SDL_IMMDevice_Init(void); void SDL_IMMDevice_Quit(void); -int SDL_IMMDevice_Get(LPCWSTR devid, IMMDevice **device, SDL_bool iscapture); -void SDL_IMMDevice_EnumerateEndpoints(SDL_bool useguid); -int SDL_IMMDevice_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture); - +int SDL_IMMDevice_Get(SDL_AudioDevice *device, IMMDevice **immdevice, SDL_bool iscapture); +void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture); +LPGUID SDL_IMMDevice_GetDirectSoundGUID(SDL_AudioDevice *device); +LPCWSTR SDL_IMMDevice_GetDevID(SDL_AudioDevice *device); +void SDL_IMMDevice_FreeDeviceHandle(SDL_AudioDevice *device); SDL_AudioFormat WaveFormatToSDLFormat(WAVEFORMATEX *waveformat); -/* these increment as default devices change. Opened default devices pick up changes in their threads. */ -extern SDL_AtomicInt SDL_IMMDevice_DefaultPlaybackGeneration; -extern SDL_AtomicInt SDL_IMMDevice_DefaultCaptureGeneration; - #endif /* SDL_IMMDEVICE_H */ From be0dc630b7eb80f9fb5d18686c68bcd5b949c273 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 11 Jul 2023 21:55:55 -0400 Subject: [PATCH 095/138] audio: Fixed incorrect assertion --- src/audio/SDL_audio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index cdf7a164ac..5224cb067e 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -1019,7 +1019,7 @@ SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(SDL_bool (*callback)(SDL_ SDL_SetError("Device not found"); } - SDL_assert(!SDL_AtomicGet(&dev->condemned)); // shouldn't be in the list if pending deletion. + SDL_assert(!dev || !SDL_AtomicGet(&dev->condemned)); // shouldn't be in the list if pending deletion. return dev; } From dc04f85646654fb359f62f0aabb1f3864dd3c4ca Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Tue, 11 Jul 2023 22:07:52 -0400 Subject: [PATCH 096/138] audio: whoops, that should be an int. --- src/audio/SDL_sysaudio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 6995fbe102..2a034b5c85 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -231,7 +231,7 @@ struct SDL_AudioDevice // The device's current audio specification SDL_AudioSpec spec; - Uint32 buffer_size; + int buffer_size; // The device's default audio specification SDL_AudioSpec default_spec; From c58d95c3431c028d59e2af3d0d4b6e045e3c4f22 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 14 Jul 2023 19:55:30 -0400 Subject: [PATCH 097/138] 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. --- src/audio/SDL_audio.c | 44 +- src/audio/SDL_audiocvt.c | 2 +- src/audio/SDL_sysaudio.h | 6 + src/audio/wasapi/SDL_wasapi.c | 802 +++++++++++++++----------- src/audio/wasapi/SDL_wasapi.h | 25 +- src/audio/wasapi/SDL_wasapi_win32.c | 58 +- src/audio/wasapi/SDL_wasapi_winrt.cpp | 262 +++------ src/core/windows/SDL_immdevice.c | 7 +- src/core/windows/SDL_immdevice.h | 2 +- test/loopwave.c | 2 +- 10 files changed, 654 insertions(+), 556 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 5224cb067e..9d770b8170 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -1082,7 +1082,7 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec) 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) { 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. } } + +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; +} + diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index effc3af255..d7e2c54f41 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -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. // however, if the stream is flushed, we would just be padding any remaining input with silence anyhow, so use it up. 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. } else { return 0; diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index 2a034b5c85..7e170330a2 100644 --- a/src/audio/SDL_sysaudio.h +++ b/src/audio/SDL_sysaudio.h @@ -85,6 +85,12 @@ extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device); // Backends should call this if the system default device changes. 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. extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle); diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c index d91d423999..2f2ad8ff3a 100644 --- a/src/audio/wasapi/SDL_wasapi.c +++ b/src/audio/wasapi/SDL_wasapi.c @@ -24,6 +24,7 @@ #include "../../core/windows/SDL_windows.h" #include "../../core/windows/SDL_immdevice.h" +#include "../../thread/SDL_systhread.h" #include "../SDL_audio_c.h" #include "../SDL_sysaudio.h" @@ -32,7 +33,7 @@ #include "SDL_wasapi.h" -/* These constants aren't available in older SDKs */ +// These constants aren't available in older SDKs #ifndef AUDCLNT_STREAMFLAGS_RATEADJUST #define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000 #endif @@ -43,140 +44,367 @@ #define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000 #endif -/* Some GUIDs we need to know without linking to libraries that aren't available before Vista. */ +// Some GUIDs we need to know without linking to libraries that aren't available before Vista. static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } }; static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } }; -static void WASAPI_DetectDevices(void) +// WASAPI is _really_ particular about various things happening on the same thread, for COM and such, +// so we proxy various stuff to a single background thread to manage. + +typedef struct ManagementThreadPendingTask { - WASAPI_EnumerateEndpoints(); + ManagementThreadTask fn; + void *userdata; + int result; + SDL_Semaphore *task_complete_sem; + char *errorstr; + struct ManagementThreadPendingTask *next; +} ManagementThreadPendingTask; + +static SDL_Thread *ManagementThread = NULL; +static ManagementThreadPendingTask *ManagementThreadPendingTasks = NULL; +static SDL_Mutex *ManagementThreadLock = NULL; +static SDL_Condition *ManagementThreadCondition = NULL; +static SDL_AtomicInt ManagementThreadShutdown; + +static void ManagementThreadMainloop(void) +{ + SDL_LockMutex(ManagementThreadLock); + ManagementThreadPendingTask *task; + while (((task = SDL_AtomicGetPtr(&ManagementThreadPendingTasks)) != NULL) || !SDL_AtomicGet(&ManagementThreadShutdown)) { + if (!task) { + SDL_WaitCondition(ManagementThreadCondition, ManagementThreadLock); // block until there's something to do. + } else { + SDL_AtomicSetPtr(&ManagementThreadPendingTasks, task->next); // take task off the pending list. + SDL_UnlockMutex(ManagementThreadLock); // let other things add to the list while we chew on this task. + task->result = task->fn(task->userdata); // run this task. + if (task->task_complete_sem) { // something waiting on result? + task->errorstr = SDL_strdup(SDL_GetError()); + SDL_PostSemaphore(task->task_complete_sem); + } else { // nothing waiting, we're done, free it. + SDL_free(task); + } + SDL_LockMutex(ManagementThreadLock); // regrab the lock so we can get the next task; if nothing to do, we'll release the lock in SDL_WaitCondition. + } + } + SDL_UnlockMutex(ManagementThreadLock); // told to shut down and out of tasks, let go of the lock and return. } -static SDL_INLINE SDL_bool WasapiFailed(SDL_AudioDevice *_this, const HRESULT err) +int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_on_result) +{ + // We want to block for a result, but we are already running from the management thread! Just run the task now so we don't deadlock. + if ((wait_on_result != NULL) && (SDL_ThreadID() == SDL_GetThreadID(ManagementThread))) { + *wait_on_result = task(userdata); + return 0; // completed! + } + + if (SDL_AtomicGet(&ManagementThreadShutdown)) { + return SDL_SetError("Can't add task, we're shutting down"); + } + + ManagementThreadPendingTask *pending = SDL_calloc(1, sizeof(ManagementThreadPendingTask)); + if (!pending) { + return SDL_OutOfMemory(); + } + + pending->fn = task; + pending->userdata = userdata; + + if (wait_on_result) { + pending->task_complete_sem = SDL_CreateSemaphore(0); + if (!pending->task_complete_sem) { + SDL_free(pending); + return -1; + } + } + + pending->next = NULL; + + SDL_LockMutex(ManagementThreadLock); + + // add to end of task list. + ManagementThreadPendingTask *prev = NULL; + for (ManagementThreadPendingTask *i = SDL_AtomicGetPtr(&ManagementThreadPendingTasks); i != NULL; i = i->next) { + prev = i; + } + + if (prev != NULL) { + prev->next = pending; + } else { + SDL_AtomicSetPtr(&ManagementThreadPendingTasks, pending); + } + + // task is added to the end of the pending list, let management thread rip! + SDL_SignalCondition(ManagementThreadCondition); + SDL_UnlockMutex(ManagementThreadLock); + + if (wait_on_result) { + SDL_WaitSemaphore(pending->task_complete_sem); + SDL_DestroySemaphore(pending->task_complete_sem); + *wait_on_result = pending->result; + if (pending->errorstr) { + SDL_SetError("%s", pending->errorstr); + SDL_free(pending->errorstr); + } + SDL_free(pending); + } + + return 0; // successfully added (and possibly executed)! +} + +static int ManagementThreadPrepare(void) +{ + if (WASAPI_PlatformInit() == -1) { + return -1; + } + + ManagementThreadLock = SDL_CreateMutex(); + if (!ManagementThreadLock) { + WASAPI_PlatformDeinit(); + return -1; + } + + ManagementThreadCondition = SDL_CreateCondition(); + if (!ManagementThreadCondition) { + SDL_DestroyMutex(ManagementThreadLock); + ManagementThreadLock = NULL; + WASAPI_PlatformDeinit(); + return -1; + } + + return 0; +} + +typedef struct +{ + char *errorstr; + SDL_Semaphore *ready_sem; +} ManagementThreadEntryData; + +static int ManagementThreadEntry(void *userdata) +{ + ManagementThreadEntryData *data = (ManagementThreadEntryData *)userdata; + + if (ManagementThreadPrepare() < 0) { + data->errorstr = SDL_strdup(SDL_GetError()); + SDL_PostSemaphore(data->ready_sem); // unblock calling thread. + return 0; + } + + SDL_PostSemaphore(data->ready_sem); // unblock calling thread. + ManagementThreadMainloop(); + + WASAPI_PlatformDeinit(); + return 0; +} + +static int InitManagementThread(void) +{ + ManagementThreadEntryData mgmtdata; + SDL_zero(mgmtdata); + mgmtdata.ready_sem = SDL_CreateSemaphore(0); + if (!mgmtdata.ready_sem) { + return -1; + } + + SDL_AtomicSetPtr(&ManagementThreadPendingTasks, NULL); + SDL_AtomicSet(&ManagementThreadShutdown, 0); + ManagementThread = SDL_CreateThreadInternal(ManagementThreadEntry, "SDLWASAPIMgmt", 256 * 1024, &mgmtdata); // !!! FIXME: maybe even smaller stack size? + if (!ManagementThread) { + return -1; + } + + SDL_WaitSemaphore(mgmtdata.ready_sem); + SDL_DestroySemaphore(mgmtdata.ready_sem); + + if (mgmtdata.errorstr) { + SDL_WaitThread(ManagementThread, NULL); + ManagementThread = NULL; + SDL_SetError("%s", mgmtdata.errorstr); + SDL_free(mgmtdata.errorstr); + return -1; + } + + return 0; +} + +static void DeinitManagementThread(void) +{ + if (ManagementThread) { + SDL_AtomicSet(&ManagementThreadShutdown, 1); + SDL_LockMutex(ManagementThreadLock); + SDL_SignalCondition(ManagementThreadCondition); + SDL_UnlockMutex(ManagementThreadLock); + SDL_WaitThread(ManagementThread, NULL); + ManagementThread = NULL; + } + + SDL_assert(SDL_AtomicGetPtr(&ManagementThreadPendingTasks) == NULL); + + SDL_DestroyCondition(ManagementThreadCondition); + SDL_DestroyMutex(ManagementThreadLock); + ManagementThreadCondition = NULL; + ManagementThreadLock = NULL; + SDL_AtomicSet(&ManagementThreadShutdown, 0); +} + +typedef struct +{ + SDL_AudioDevice **default_output; + SDL_AudioDevice **default_capture; +} mgmtthrtask_DetectDevicesData; + +static int mgmtthrtask_DetectDevices(void *userdata) +{ + mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata; + WASAPI_EnumerateEndpoints(data->default_output, data->default_capture); + return 0; +} + +static void WASAPI_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) +{ + int rc; + mgmtthrtask_DetectDevicesData data = { default_output, default_capture }; + WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc); +} + +static int mgmtthrtask_DisconnectDevice(void *userdata) +{ + SDL_AudioDeviceDisconnected((SDL_AudioDevice *)userdata); + return 0; +} + +void WASAPI_DisconnectDevice(SDL_AudioDevice *device) +{ + // this runs async, so it can hold the device lock from the management thread. + WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, NULL); +} + +static SDL_bool WasapiFailed(SDL_AudioDevice *device, const HRESULT err) { if (err == S_OK) { return SDL_FALSE; } if (err == AUDCLNT_E_DEVICE_INVALIDATED) { - _this->hidden->device_lost = SDL_TRUE; - } else if (SDL_AtomicGet(&_this->enabled)) { - IAudioClient_Stop(_this->hidden->client); - SDL_OpenedAudioDeviceDisconnected(_this); - SDL_assert(!SDL_AtomicGet(&_this->enabled)); + device->hidden->device_lost = SDL_TRUE; + } else { + IAudioClient_Stop(device->hidden->client); + WASAPI_DisconnectDevice(device); } return SDL_TRUE; } -static int UpdateAudioStream(SDL_AudioDevice *_this, const SDL_AudioSpec *oldspec) +static int mgmtthrtask_ReleaseCaptureClient(void *userdata) { - /* Since WASAPI requires us to handle all audio conversion, and our - device format might have changed, we might have to add/remove/change - the audio stream that the higher level uses to convert data, so - SDL keeps firing the callback as if nothing happened here. */ + IAudioCaptureClient_Release((IAudioCaptureClient *)userdata); + return 0; +} - if ((_this->callbackspec.channels == _this->spec.channels) && - (_this->callbackspec.format == _this->spec.format) && - (_this->callbackspec.freq == _this->spec.freq) && - (_this->callbackspec.samples == _this->spec.samples)) { - /* no need to buffer/convert in an AudioStream! */ - SDL_DestroyAudioStream(_this->stream); - _this->stream = NULL; - } else if ((oldspec->channels == _this->spec.channels) && - (oldspec->format == _this->spec.format) && - (oldspec->freq == _this->spec.freq)) { - /* The existing audio stream is okay to keep using. */ - } else { - /* replace the audiostream for new format */ - SDL_DestroyAudioStream(_this->stream); - if (_this->iscapture) { - _this->stream = SDL_CreateAudioStream(_this->spec.format, - _this->spec.channels, _this->spec.freq, - _this->callbackspec.format, - _this->callbackspec.channels, - _this->callbackspec.freq); - } else { - _this->stream = SDL_CreateAudioStream(_this->callbackspec.format, - _this->callbackspec.channels, - _this->callbackspec.freq, _this->spec.format, - _this->spec.channels, _this->spec.freq); - } +static int mgmtthrtask_ReleaseRenderClient(void *userdata) +{ + IAudioRenderClient_Release((IAudioRenderClient *)userdata); + return 0; +} - if (!_this->stream) { - return -1; /* SDL_CreateAudioStream should have called SDL_SetError. */ - } +static int mgmtthrtask_ResetWasapiDevice(void *userdata) +{ + SDL_AudioDevice *device = (SDL_AudioDevice *)userdata; + + if (!device || !device->hidden) { + return 0; } - /* make sure our scratch buffer can cover the new device spec. */ - if (_this->spec.size > _this->work_buffer_len) { - Uint8 *ptr = (Uint8 *)SDL_realloc(_this->work_buffer, _this->spec.size); - if (ptr == NULL) { - return SDL_OutOfMemory(); - } - _this->work_buffer = ptr; - _this->work_buffer_len = _this->spec.size; + if (device->hidden->client) { + IAudioClient_Stop(device->hidden->client); + IAudioClient_Release(device->hidden->client); + device->hidden->client = NULL; + } + + if (device->hidden->render) { + // this is silly, but this will block indefinitely if you call it from SDLMMNotificationClient_OnDefaultDeviceChanged, so + // proxy this to the management thread to be released later. + WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseRenderClient, device->hidden->render, NULL); + device->hidden->render = NULL; + } + + if (device->hidden->capture) { + // this is silly, but this will block indefinitely if you call it from SDLMMNotificationClient_OnDefaultDeviceChanged, so + // proxy this to the management thread to be released later. + WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseCaptureClient, device->hidden->capture, NULL); + device->hidden->capture = NULL; + } + + if (device->hidden->waveformat) { + CoTaskMemFree(device->hidden->waveformat); + device->hidden->waveformat = NULL; + } + + if (device->hidden->activation_handler) { + WASAPI_PlatformDeleteActivationHandler(device->hidden->activation_handler); + device->hidden->activation_handler = NULL; + } + + if (device->hidden->event) { + CloseHandle(device->hidden->event); + device->hidden->event = NULL; } return 0; } -static void ReleaseWasapiDevice(SDL_AudioDevice *_this); - -static SDL_bool RecoverWasapiDevice(SDL_AudioDevice *_this) +static void ResetWasapiDevice(SDL_AudioDevice *device) { - ReleaseWasapiDevice(_this); /* dump the lost device's handles. */ + int rc; + WASAPI_ProxyToManagementThread(mgmtthrtask_ResetWasapiDevice, device, &rc); +} - if (_this->hidden->default_device_generation) { - _this->hidden->default_device_generation = SDL_AtomicGet(_this->iscapture ? &SDL_IMMDevice_DefaultCaptureGeneration : &SDL_IMMDevice_DefaultPlaybackGeneration); - } +static int mgmtthrtask_ActivateDevice(void *userdata) +{ + return WASAPI_ActivateDevice((SDL_AudioDevice *)userdata); +} - /* this can fail for lots of reasons, but the most likely is we had a - non-default device that was disconnected, so we can't recover. Default - devices try to reinitialize whatever the new default is, so it's more - likely to carry on here, but this handles a non-default device that - simply had its format changed in the Windows Control Panel. */ - if (WASAPI_ActivateDevice(_this, SDL_TRUE) == -1) { - SDL_OpenedAudioDeviceDisconnected(_this); +static int ActivateWasapiDevice(SDL_AudioDevice *device) +{ + int rc; + return ((WASAPI_ProxyToManagementThread(mgmtthrtask_ActivateDevice, device, &rc) < 0) || (rc < 0)) ? -1 : 0; +} + +static SDL_bool RecoverWasapiDevice(SDL_AudioDevice *device) +{ + ResetWasapiDevice(device); // dump the lost device's handles. + + // This handles a non-default device that simply had its format changed in the Windows Control Panel. + if (ActivateWasapiDevice(device) == -1) { + WASAPI_DisconnectDevice(device); return SDL_FALSE; } - _this->hidden->device_lost = SDL_FALSE; + device->hidden->device_lost = SDL_FALSE; - return SDL_TRUE; /* okay, carry on with new device details! */ + return SDL_TRUE; // okay, carry on with new device details! } -static SDL_bool RecoverWasapiIfLost(SDL_AudioDevice *_this) +static SDL_bool RecoverWasapiIfLost(SDL_AudioDevice *device) { - const int generation = _this->hidden->default_device_generation; - SDL_bool lost = _this->hidden->device_lost; - - if (!SDL_AtomicGet(&_this->enabled)) { - return SDL_FALSE; /* already failed. */ + if (SDL_AtomicGet(&device->shutdown)) { + return SDL_FALSE; // already failed. + } else if (!device->hidden->client) { + return SDL_TRUE; // still waiting for activation. } - if (!_this->hidden->client) { - return SDL_TRUE; /* still waiting for activation. */ - } - - if (!lost && (generation > 0)) { /* is a default device? */ - const int newgen = SDL_AtomicGet(_this->iscapture ? &SDL_IMMDevice_DefaultCaptureGeneration : &SDL_IMMDevice_DefaultPlaybackGeneration); - if (generation != newgen) { /* the desired default device was changed, jump over to it. */ - lost = SDL_TRUE; - } - } - - return lost ? RecoverWasapiDevice(_this) : SDL_TRUE; + return device->hidden->device_lost ? RecoverWasapiDevice(device) : SDL_TRUE; } -static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - /* get an endpoint buffer from WASAPI. */ + // get an endpoint buffer from WASAPI. BYTE *buffer = NULL; - while (RecoverWasapiIfLost(_this) && _this->hidden->render) { - if (!WasapiFailed(_this, IAudioRenderClient_GetBuffer(_this->hidden->render, _this->spec.samples, &buffer))) { + while (RecoverWasapiIfLost(device) && device->hidden->render) { + if (!WasapiFailed(device, IAudioRenderClient_GetBuffer(device->hidden->render, device->sample_frames, &buffer))) { return (Uint8 *)buffer; } SDL_assert(buffer == NULL); @@ -185,197 +413,108 @@ static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *_this) return (Uint8 *)buffer; } -static void WASAPI_PlayDevice(SDL_AudioDevice *_this) +static void WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { - if (_this->hidden->render != NULL) { /* definitely activated? */ - /* WasapiFailed() will mark the device for reacquisition or removal elsewhere. */ - WasapiFailed(_this, IAudioRenderClient_ReleaseBuffer(_this->hidden->render, _this->spec.samples, 0)); + if (device->hidden->render != NULL) { // definitely activated? + // WasapiFailed() will mark the device for reacquisition or removal elsewhere. + WasapiFailed(device, IAudioRenderClient_ReleaseBuffer(device->hidden->render, device->sample_frames, 0)); } } -static void WASAPI_WaitDevice(SDL_AudioDevice *_this) +static void WASAPI_WaitDevice(SDL_AudioDevice *device) { - while (RecoverWasapiIfLost(_this) && _this->hidden->client && _this->hidden->event) { - DWORD waitResult = WaitForSingleObjectEx(_this->hidden->event, 200, FALSE); + while (RecoverWasapiIfLost(device) && device->hidden->client && device->hidden->event) { + DWORD waitResult = WaitForSingleObjectEx(device->hidden->event, 200, FALSE); if (waitResult == WAIT_OBJECT_0) { - const UINT32 maxpadding = _this->spec.samples; + const UINT32 maxpadding = device->sample_frames; UINT32 padding = 0; - if (!WasapiFailed(_this, IAudioClient_GetCurrentPadding(_this->hidden->client, &padding))) { - /*SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);*/ - if (_this->iscapture) { - if (padding > 0) { - break; - } - } else { - if (padding <= maxpadding) { - break; - } + if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) { + //SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);*/ + if (device->iscapture && (padding > 0)) { + break; + } else if (!device->iscapture && (padding <= maxpadding)) { + break; } } } else if (waitResult != WAIT_TIMEOUT) { - /*SDL_Log("WASAPI FAILED EVENT!");*/ - IAudioClient_Stop(_this->hidden->client); - SDL_OpenedAudioDeviceDisconnected(_this); + //SDL_Log("WASAPI FAILED EVENT!");*/ + IAudioClient_Stop(device->hidden->client); + WASAPI_DisconnectDevice(device); } } } -static int WASAPI_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) -{ - SDL_AudioStream *stream = _this->hidden->capturestream; - const int avail = SDL_GetAudioStreamAvailable(stream); - if (avail > 0) { - const int cpy = SDL_min(buflen, avail); - SDL_GetAudioStreamData(stream, buffer, cpy); - return cpy; - } - - while (RecoverWasapiIfLost(_this)) { - HRESULT ret; - BYTE *ptr = NULL; - UINT32 frames = 0; - DWORD flags = 0; - - /* uhoh, client isn't activated yet, just return silence. */ - if (!_this->hidden->capture) { - /* Delay so we run at about the speed that audio would be arriving. */ - SDL_Delay(((_this->spec.samples * 1000) / _this->spec.freq)); - SDL_memset(buffer, _this->spec.silence, buflen); - return buflen; - } - - ret = IAudioCaptureClient_GetBuffer(_this->hidden->capture, &ptr, &frames, &flags, NULL, NULL); - if (ret != AUDCLNT_S_BUFFER_EMPTY) { - WasapiFailed(_this, ret); /* mark device lost/failed if necessary. */ - } - - if ((ret == AUDCLNT_S_BUFFER_EMPTY) || !frames) { - WASAPI_WaitDevice(_this); - } else if (ret == S_OK) { - const int total = ((int)frames) * _this->hidden->framesize; - const int cpy = SDL_min(buflen, total); - const int leftover = total - cpy; - const SDL_bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? SDL_TRUE : SDL_FALSE; - - if (silent) { - SDL_memset(buffer, _this->spec.silence, cpy); - } else { - SDL_memcpy(buffer, ptr, cpy); - } - - if (leftover > 0) { - ptr += cpy; - if (silent) { - SDL_memset(ptr, _this->spec.silence, leftover); /* I guess this is safe? */ - } - - if (SDL_PutAudioStreamData(stream, ptr, leftover) == -1) { - return -1; /* uhoh, out of memory, etc. Kill device. :( */ - } - } - - ret = IAudioCaptureClient_ReleaseBuffer(_this->hidden->capture, frames); - WasapiFailed(_this, ret); /* mark device lost/failed if necessary. */ - - return cpy; - } - } - - return -1; /* unrecoverable error. */ -} - -static void WASAPI_FlushCapture(SDL_AudioDevice *_this) +static int WASAPI_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { BYTE *ptr = NULL; UINT32 frames = 0; DWORD flags = 0; - if (!_this->hidden->capture) { - return; /* not activated yet? */ - } - - /* just read until we stop getting packets, throwing them away. */ - while (SDL_TRUE) { - const HRESULT ret = IAudioCaptureClient_GetBuffer(_this->hidden->capture, &ptr, &frames, &flags, NULL, NULL); + while (RecoverWasapiIfLost(device) && device->hidden->capture) { + HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL); if (ret == AUDCLNT_S_BUFFER_EMPTY) { - break; /* no more buffered data; we're done. */ - } else if (WasapiFailed(_this, ret)) { - break; /* failed for some other reason, abort. */ - } else if (WasapiFailed(_this, IAudioCaptureClient_ReleaseBuffer(_this->hidden->capture, frames))) { - break; /* something broke. */ + return 0; // in theory we should have waited until there was data, but oh well, we'll go back to waiting. Returning 0 is safe in SDL + } + + WasapiFailed(device, ret); // mark device lost/failed if necessary. + + if (ret == S_OK) { + const int total = ((int)frames) * device->hidden->framesize; + const int cpy = SDL_min(buflen, total); + const int leftover = total - cpy; + const SDL_bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? SDL_TRUE : SDL_FALSE; + + SDL_assert(leftover == 0); // according to MSDN, this isn't everything available, just one "packet" of data per-GetBuffer call. + + if (silent) { + SDL_memset(buffer, device->silence_value, cpy); + } else { + SDL_memcpy(buffer, ptr, cpy); + } + + ret = IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames); + WasapiFailed(device, ret); // mark device lost/failed if necessary. + + return cpy; } } - SDL_ClearAudioStream(_this->hidden->capturestream); + + return -1; // unrecoverable error. } -static void ReleaseWasapiDevice(SDL_AudioDevice *_this) +static void WASAPI_FlushCapture(SDL_AudioDevice *device) { - if (_this->hidden->client) { - IAudioClient_Stop(_this->hidden->client); - IAudioClient_Release(_this->hidden->client); - _this->hidden->client = NULL; - } + BYTE *ptr = NULL; + UINT32 frames = 0; + DWORD flags = 0; - if (_this->hidden->render) { - IAudioRenderClient_Release(_this->hidden->render); - _this->hidden->render = NULL; - } - - if (_this->hidden->capture) { - IAudioCaptureClient_Release(_this->hidden->capture); - _this->hidden->capture = NULL; - } - - if (_this->hidden->waveformat) { - CoTaskMemFree(_this->hidden->waveformat); - _this->hidden->waveformat = NULL; - } - - if (_this->hidden->capturestream) { - SDL_DestroyAudioStream(_this->hidden->capturestream); - _this->hidden->capturestream = NULL; - } - - if (_this->hidden->activation_handler) { - WASAPI_PlatformDeleteActivationHandler(_this->hidden->activation_handler); - _this->hidden->activation_handler = NULL; - } - - if (_this->hidden->event) { - CloseHandle(_this->hidden->event); - _this->hidden->event = NULL; + // just read until we stop getting packets, throwing them away. + while (!SDL_AtomicGet(&device->shutdown) && device->hidden->capture) { + const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL); + if (ret == AUDCLNT_S_BUFFER_EMPTY) { + break; // no more buffered data; we're done. + } else if (WasapiFailed(device, ret)) { + break; // failed for some other reason, abort. + } else if (WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames))) { + break; // something broke. + } } } -static void WASAPI_CloseDevice(SDL_AudioDevice *_this) +static void WASAPI_CloseDevice(SDL_AudioDevice *device) { - WASAPI_UnrefDevice(_this); -} - -void WASAPI_RefDevice(SDL_AudioDevice *_this) -{ - SDL_AtomicIncRef(&_this->hidden->refcount); -} - -void WASAPI_UnrefDevice(SDL_AudioDevice *_this) -{ - if (!SDL_AtomicDecRef(&_this->hidden->refcount)) { - return; + if (device->hidden) { + ResetWasapiDevice(device); + SDL_free(device->hidden->devid); + SDL_free(device->hidden); + device->hidden = NULL; } - - /* actual closing happens here. */ - - /* don't touch _this->hidden->task in here; it has to be reverted from - our callback thread. We do that in WASAPI_ThreadDeinit(). - (likewise for _this->hidden->coinitialized). */ - ReleaseWasapiDevice(_this); - SDL_free(_this->hidden->devid); - SDL_free(_this->hidden); } -/* This is called once a device is activated, possibly asynchronously. */ -int WASAPI_PrepDevice(SDL_AudioDevice *_this, const SDL_bool updatestream) +static int mgmtthrtask_PrepDevice(void *userdata) { + SDL_AudioDevice *device = (SDL_AudioDevice *)userdata; + /* !!! FIXME: we could request an exclusive mode stream, which is lower latency; !!! it will write into the kernel's audio buffer directly instead of !!! shared memory that a user-mode mixer then writes to the kernel with @@ -387,49 +526,42 @@ int WASAPI_PrepDevice(SDL_AudioDevice *_this, const SDL_bool updatestream) !!! wins actually look like. Maybe add a hint to force exclusive mode at !!! some point. To be sure, defaulting to shared mode is the right thing to !!! do in any case. */ - const SDL_AudioSpec oldspec = _this->spec; const AUDCLNT_SHAREMODE sharemode = AUDCLNT_SHAREMODE_SHARED; - UINT32 bufsize = 0; /* this is in sample frames, not samples, not bytes. */ - REFERENCE_TIME default_period = 0; - IAudioClient *client = _this->hidden->client; - IAudioRenderClient *render = NULL; - IAudioCaptureClient *capture = NULL; - WAVEFORMATEX *waveformat = NULL; - SDL_AudioFormat test_format; - SDL_AudioFormat wasapi_format = 0; - const SDL_AudioFormat *closefmts; - HRESULT ret = S_OK; - DWORD streamflags = 0; + IAudioClient *client = device->hidden->client; SDL_assert(client != NULL); -#if defined(__WINRT__) || defined(__GDK__) /* CreateEventEx() arrived in Vista, so we need an #ifdef for XP. */ - _this->hidden->event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS); +#if defined(__WINRT__) || defined(__GDK__) // CreateEventEx() arrived in Vista, so we need an #ifdef for XP. + device->hidden->event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS); #else - _this->hidden->event = CreateEventW(NULL, 0, 0, NULL); + device->hidden->event = CreateEventW(NULL, 0, 0, NULL); #endif - if (_this->hidden->event == NULL) { + if (device->hidden->event == NULL) { return WIN_SetError("WASAPI can't create an event handle"); } + HRESULT ret; + + WAVEFORMATEX *waveformat = NULL; ret = IAudioClient_GetMixFormat(client, &waveformat); if (FAILED(ret)) { return WIN_SetErrorFromHRESULT("WASAPI can't determine mix format", ret); } - SDL_assert(waveformat != NULL); - _this->hidden->waveformat = waveformat; + device->hidden->waveformat = waveformat; - _this->spec.channels = (Uint8)waveformat->nChannels; + SDL_AudioSpec newspec; + newspec.channels = (Uint8)waveformat->nChannels; - /* Make sure we have a valid format that we can convert to whatever WASAPI wants. */ - wasapi_format = WaveFormatToSDLFormat(waveformat); + // Make sure we have a valid format that we can convert to whatever WASAPI wants. + const SDL_AudioFormat wasapi_format = SDL_WaveFormatExToSDLFormat(waveformat); - closefmts = SDL_ClosestAudioFormats(_this->spec.format); + SDL_AudioFormat test_format; + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { if (test_format == wasapi_format) { - _this->spec.format = test_format; + newspec.format = test_format; break; } } @@ -438,36 +570,40 @@ int WASAPI_PrepDevice(SDL_AudioDevice *_this, const SDL_bool updatestream) return SDL_SetError("%s: Unsupported audio format", "wasapi"); } + REFERENCE_TIME default_period = 0; ret = IAudioClient_GetDevicePeriod(client, &default_period, NULL); if (FAILED(ret)) { return WIN_SetErrorFromHRESULT("WASAPI can't determine minimum device period", ret); } + DWORD streamflags = 0; + /* we've gotten reports that WASAPI's resampler introduces distortions, but in the short term it fixes some other WASAPI-specific quirks we haven't quite tracked down. Refer to bug #6326 for the immediate concern. */ -#if 0 - _this->spec.freq = waveformat->nSamplesPerSec; /* force sampling rate so our resampler kicks in, if necessary. */ -#else - /* favor WASAPI's resampler over our own */ - if ((DWORD)_this->spec.freq != waveformat->nSamplesPerSec) { +#if 1 + // favor WASAPI's resampler over our own + if ((DWORD)device->spec.freq != waveformat->nSamplesPerSec) { streamflags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY); - waveformat->nSamplesPerSec = _this->spec.freq; + waveformat->nSamplesPerSec = device->spec.freq; waveformat->nAvgBytesPerSec = waveformat->nSamplesPerSec * waveformat->nChannels * (waveformat->wBitsPerSample / 8); } #endif + newspec.freq = waveformat->nSamplesPerSec; + streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; ret = IAudioClient_Initialize(client, sharemode, streamflags, 0, 0, waveformat, NULL); if (FAILED(ret)) { return WIN_SetErrorFromHRESULT("WASAPI can't initialize audio client", ret); } - ret = IAudioClient_SetEventHandle(client, _this->hidden->event); + ret = IAudioClient_SetEventHandle(client, device->hidden->event); if (FAILED(ret)) { return WIN_SetErrorFromHRESULT("WASAPI can't set event handle", ret); } + UINT32 bufsize = 0; // this is in sample frames, not samples, not bytes. ret = IAudioClient_GetBufferSize(client, &bufsize); if (FAILED(ret)) { return WIN_SetErrorFromHRESULT("WASAPI can't determine buffer size", ret); @@ -475,116 +611,108 @@ int WASAPI_PrepDevice(SDL_AudioDevice *_this, const SDL_bool updatestream) /* Match the callback size to the period size to cut down on the number of interrupts waited for in each call to WaitDevice */ - { - const float period_millis = default_period / 10000.0f; - const float period_frames = period_millis * _this->spec.freq / 1000.0f; - _this->spec.samples = (Uint16)SDL_ceilf(period_frames); + const float period_millis = default_period / 10000.0f; + const float period_frames = period_millis * newspec.freq / 1000.0f; + const int new_sample_frames = (int) SDL_ceilf(period_frames); + + // Update the fragment size as size in bytes + if (SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames) < 0) { + return -1; } - /* Update the fragment size as size in bytes */ - SDL_CalculateAudioSpec(&_this->spec); - - _this->hidden->framesize = (SDL_AUDIO_BITSIZE(_this->spec.format) / 8) * _this->spec.channels; - - if (_this->iscapture) { - _this->hidden->capturestream = SDL_CreateAudioStream(_this->spec.format, _this->spec.channels, _this->spec.freq, _this->spec.format, _this->spec.channels, _this->spec.freq); - if (!_this->hidden->capturestream) { - return -1; /* already set SDL_Error */ - } + device->hidden->framesize = (SDL_AUDIO_BITSIZE(device->spec.format) / 8) * device->spec.channels; + if (device->iscapture) { + IAudioCaptureClient *capture = NULL; ret = IAudioClient_GetService(client, &SDL_IID_IAudioCaptureClient, (void **)&capture); if (FAILED(ret)) { return WIN_SetErrorFromHRESULT("WASAPI can't get capture client service", ret); } SDL_assert(capture != NULL); - _this->hidden->capture = capture; + device->hidden->capture = capture; ret = IAudioClient_Start(client); if (FAILED(ret)) { return WIN_SetErrorFromHRESULT("WASAPI can't start capture", ret); } - WASAPI_FlushCapture(_this); /* MSDN says you should flush capture endpoint right after startup. */ + WASAPI_FlushCapture(device); // MSDN says you should flush capture endpoint right after startup. } else { + IAudioRenderClient *render = NULL; ret = IAudioClient_GetService(client, &SDL_IID_IAudioRenderClient, (void **)&render); if (FAILED(ret)) { return WIN_SetErrorFromHRESULT("WASAPI can't get render client service", ret); } SDL_assert(render != NULL); - _this->hidden->render = render; + device->hidden->render = render; ret = IAudioClient_Start(client); if (FAILED(ret)) { return WIN_SetErrorFromHRESULT("WASAPI can't start playback", ret); } } - if (updatestream) { - return UpdateAudioStream(_this, &oldspec); - } - - return 0; /* good to go. */ + return 0; // good to go. } -static int WASAPI_OpenDevice(SDL_AudioDevice *_this, const char *devname) +// This is called once a device is activated, possibly asynchronously. +int WASAPI_PrepDevice(SDL_AudioDevice *device) { - LPCWSTR devid = (LPCWSTR)_this->handle; + int rc; + return (WASAPI_ProxyToManagementThread(mgmtthrtask_PrepDevice, device, &rc) < 0) ? -1 : rc; +} - /* Initialize all variables that we clean on shutdown */ - _this->hidden = (struct SDL_PrivateAudioData *) SDL_malloc(sizeof(*_this->hidden)); - if (_this->hidden == NULL) { +static int WASAPI_OpenDevice(SDL_AudioDevice *device) +{ + // Initialize all variables that we clean on shutdown + device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); - } - SDL_zerop(_this->hidden); - - WASAPI_RefDevice(_this); /* so CloseDevice() will unref to zero. */ - - if (!devid) { /* is default device? */ - _this->hidden->default_device_generation = SDL_AtomicGet(_this->iscapture ? &SDL_IMMDevice_DefaultCaptureGeneration : &SDL_IMMDevice_DefaultPlaybackGeneration); - } else { - _this->hidden->devid = SDL_wcsdup(devid); - if (!_this->hidden->devid) { - return SDL_OutOfMemory(); - } + } else if (ActivateWasapiDevice(device) < 0) { + return -1; // already set error. } - if (WASAPI_ActivateDevice(_this, SDL_FALSE) == -1) { - return -1; /* already set error. */ - } - - /* Ready, but waiting for async device activation. + /* Ready, but possibly waiting for async device activation. Until activation is successful, we will report silence from capture - devices and ignore data on playback devices. - Also, since we don't know the _actual_ device format until after - activation, we let the app have whatever it asks for. We set up - an SDL_AudioStream to convert, if necessary, once the activation - completes. */ + devices and ignore data on playback devices. Upon activation, we'll make + sure any bound audio streams are adjusted for the final device format. */ return 0; } -static void WASAPI_ThreadInit(SDL_AudioDevice *_this) +static void WASAPI_ThreadInit(SDL_AudioDevice *device) { - WASAPI_PlatformThreadInit(_this); + WASAPI_PlatformThreadInit(device); } -static void WASAPI_ThreadDeinit(SDL_AudioDevice *_this) +static void WASAPI_ThreadDeinit(SDL_AudioDevice *device) { - WASAPI_PlatformThreadDeinit(_this); + WASAPI_PlatformThreadDeinit(device); +} + +static int mgmtthrtask_FreeDeviceHandle(void *userdata) +{ + WASAPI_PlatformFreeDeviceHandle((SDL_AudioDevice *)userdata); + return 0; +} + +static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device) +{ + int rc; + WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc); } static void WASAPI_Deinitialize(void) { - WASAPI_PlatformDeinit(); + DeinitManagementThread(); } static SDL_bool WASAPI_Init(SDL_AudioDriverImpl *impl) { - if (WASAPI_PlatformInit() == -1) { + if (InitManagementThread() < 0) { return SDL_FALSE; } - /* Set the function pointers */ impl->DetectDevices = WASAPI_DetectDevices; impl->ThreadInit = WASAPI_ThreadInit; impl->ThreadDeinit = WASAPI_ThreadDeinit; @@ -592,18 +720,20 @@ static SDL_bool WASAPI_Init(SDL_AudioDriverImpl *impl) impl->PlayDevice = WASAPI_PlayDevice; impl->WaitDevice = WASAPI_WaitDevice; impl->GetDeviceBuf = WASAPI_GetDeviceBuf; + impl->WaitCaptureDevice = WASAPI_WaitDevice; impl->CaptureFromDevice = WASAPI_CaptureFromDevice; impl->FlushCapture = WASAPI_FlushCapture; impl->CloseDevice = WASAPI_CloseDevice; impl->Deinitialize = WASAPI_Deinitialize; - impl->GetDefaultAudioInfo = WASAPI_GetDefaultAudioInfo; + impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle; + impl->HasCaptureSupport = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap WASAPI_bootstrap = { "wasapi", "WASAPI", WASAPI_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_WASAPI */ +#endif // SDL_AUDIO_DRIVER_WASAPI diff --git a/src/audio/wasapi/SDL_wasapi.h b/src/audio/wasapi/SDL_wasapi.h index 5fdd0bbdb6..7ea47ccbc3 100644 --- a/src/audio/wasapi/SDL_wasapi.h +++ b/src/audio/wasapi/SDL_wasapi.h @@ -31,37 +31,38 @@ extern "C" { struct SDL_PrivateAudioData { - SDL_AtomicInt refcount; WCHAR *devid; WAVEFORMATEX *waveformat; IAudioClient *client; IAudioRenderClient *render; IAudioCaptureClient *capture; - SDL_AudioStream *capturestream; HANDLE event; HANDLE task; SDL_bool coinitialized; int framesize; - int default_device_generation; SDL_bool device_lost; void *activation_handler; - SDL_AtomicInt just_activated; }; /* win32 and winrt implementations call into these. */ -int WASAPI_PrepDevice(SDL_AudioDevice *_this, const SDL_bool updatestream); -void WASAPI_RefDevice(SDL_AudioDevice *_this); -void WASAPI_UnrefDevice(SDL_AudioDevice *_this); +int WASAPI_PrepDevice(SDL_AudioDevice *device); +void WASAPI_DisconnectDevice(SDL_AudioDevice *device); + + +// 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. */ +// UNLESS OTHERWISE NOTED THESE ALL HAPPEN ON THE MANAGEMENT THREAD. int WASAPI_PlatformInit(void); void WASAPI_PlatformDeinit(void); -void WASAPI_EnumerateEndpoints(void); -int WASAPI_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture); -int WASAPI_ActivateDevice(SDL_AudioDevice *_this, const SDL_bool isrecovery); -void WASAPI_PlatformThreadInit(SDL_AudioDevice *_this); -void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *_this); +void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture); +int WASAPI_ActivateDevice(SDL_AudioDevice *device); +void WASAPI_PlatformThreadInit(SDL_AudioDevice *device); // this happens on the audio device thread, not the management thread. +void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device); // this happens on the audio device thread, not the management thread. void WASAPI_PlatformDeleteActivationHandler(void *handler); +void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device); #ifdef __cplusplus } diff --git a/src/audio/wasapi/SDL_wasapi_win32.c b/src/audio/wasapi/SDL_wasapi_win32.c index f991092c15..75862371c4 100644 --- a/src/audio/wasapi/SDL_wasapi_win32.c +++ b/src/audio/wasapi/SDL_wasapi_win32.c @@ -49,7 +49,7 @@ static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x 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 */ } @@ -72,72 +72,65 @@ void WASAPI_PlatformDeinit(void) pAvSetMmThreadCharacteristicsW = 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. */ 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. */ if (pAvSetMmThreadCharacteristicsW) { 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. */ - if (_this->hidden->task && pAvRevertMmThreadCharacteristics) { - pAvRevertMmThreadCharacteristics(_this->hidden->task); - _this->hidden->task = NULL; + if (device->hidden->task && pAvRevertMmThreadCharacteristics) { + pAvRevertMmThreadCharacteristics(device->hidden->task); + device->hidden->task = NULL; } - if (_this->hidden->coinitialized) { + if (device->hidden->coinitialized) { 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; - HRESULT ret; - - if (SDL_IMMDevice_Get(_this->hidden->devid, &device, _this->iscapture) < 0) { - _this->hidden->client = NULL; + IMMDevice *immdevice = NULL; + if (SDL_IMMDevice_Get(device, &immdevice, device->iscapture) < 0) { + device->hidden->client = NULL; return -1; /* This is already set by SDL_IMMDevice_Get */ } - /* this is not async in standard win32, yay! */ - ret = IMMDevice_Activate(device, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&_this->hidden->client); - IMMDevice_Release(device); + /* this is _not_ async in standard win32, yay! */ + HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client); + IMMDevice_Release(immdevice); 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); } - SDL_assert(_this->hidden->client != NULL); - if (WASAPI_PrepDevice(_this, isrecovery) == -1) { /* not async, fire it right away. */ + SDL_assert(device->hidden->client != NULL); + if (WASAPI_PrepDevice(device) == -1) { /* not async, fire it right away. */ return -1; } 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); -} - -int WASAPI_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) -{ - return SDL_IMMDevice_GetDefaultAudioInfo(name, spec, iscapture); + SDL_IMMDevice_EnumerateEndpoints(default_output, default_capture); } 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."); } +void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device) +{ + SDL_IMMDevice_FreeDeviceHandle(device); +} + #endif /* SDL_AUDIO_DRIVER_WASAPI && !defined(__WINRT__) */ diff --git a/src/audio/wasapi/SDL_wasapi_winrt.cpp b/src/audio/wasapi/SDL_wasapi_winrt.cpp index 795c29b73d..3280f13597 100644 --- a/src/audio/wasapi/SDL_wasapi_winrt.cpp +++ b/src/audio/wasapi/SDL_wasapi_winrt.cpp @@ -53,21 +53,16 @@ using namespace Microsoft::WRL; 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); -extern "C" { -SDL_AtomicInt SDL_IMMDevice_DefaultPlaybackGeneration; -SDL_AtomicInt SDL_IMMDevice_DefaultCaptureGeneration; + +static SDL_bool FindWinRTAudioDeviceCallback(SDL_AudioDevice *device, void *userdata) +{ + return (SDL_wcscmp((LPCWSTR) device->handle, (LPCWSTR) userdata) == 0) ? SDL_TRUE : SDL_FALSE; } -/* This is a list of device id strings we have inflight, so we have consistent pointers to the same device. */ -typedef struct DevIdList +static SDL_AudioDevice *FindWinRTAudioDevice(LPCWSTR devid) { - WCHAR *str; - struct DevIdList *next; -} DevIdList; - -static DevIdList *deviceid_list = NULL; + return SDL_FindPhysicalAudioDeviceByCallback(FindWinRTAudioDeviceCallback, (void *) devid); +} class SDL_WasapiDeviceEventHandler { @@ -80,9 +75,10 @@ class SDL_WasapiDeviceEventHandler void OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args); void OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args); void OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args); - SDL_Semaphore *completed; + void WaitForCompletion(); private: + SDL_Semaphore *completed_semaphore; const SDL_bool iscapture; DeviceWatcher ^ watcher; Windows::Foundation::EventRegistrationToken added_handler; @@ -93,10 +89,11 @@ class SDL_WasapiDeviceEventHandler }; 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. + } Platform::String ^ selector = _iscapture ? MediaDevice::GetAudioCaptureSelector() : MediaDevice::GetAudioRenderSelector(); Platform::Collections::Vector properties; @@ -128,9 +125,10 @@ SDL_WasapiDeviceEventHandler::~SDL_WasapiDeviceEventHandler() watcher->Stop(); watcher = nullptr; } - if (completed) { - SDL_DestroySemaphore(completed); - completed = nullptr; + + if (completed_semaphore) { + SDL_DestroySemaphore(completed_semaphore); + completed_semaphore = nullptr; } if (iscapture) { @@ -142,21 +140,34 @@ SDL_WasapiDeviceEventHandler::~SDL_WasapiDeviceEventHandler() 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); char *utf8dev = WIN_StringToUTF8(info->Name->Data()); if (utf8dev) { - WAVEFORMATEXTENSIBLE fmt; + SDL_AudioSpec spec; + SDL_zero(spec); + Platform::Object ^ obj = info->Properties->Lookup(SDL_PKEY_AudioEngine_DeviceFormat); if (obj) { IPropertyValue ^ property = (IPropertyValue ^) obj; Platform::Array ^ data; property->GetUInt8Array(&data); - SDL_memcpy(&fmt, data->Data, SDL_min(data->Length, sizeof(WAVEFORMATEXTENSIBLE))); - } else { + WAVEFORMATEXTENSIBLE 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); } } @@ -164,7 +175,7 @@ void SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher ^ sender, DeviceI void SDL_WasapiDeviceEventHandler::OnDeviceRemoved(DeviceWatcher ^ sender, DeviceInformationUpdate ^ info) { 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) @@ -175,19 +186,30 @@ void SDL_WasapiDeviceEventHandler::OnDeviceUpdated(DeviceWatcher ^ sender, Devic void SDL_WasapiDeviceEventHandler::OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args) { 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) { 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) { 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; @@ -195,54 +217,63 @@ static SDL_WasapiDeviceEventHandler *capture_device_event_handler; int WASAPI_PlatformInit(void) { - SDL_AtomicSet(&SDL_IMMDevice_DefaultPlaybackGeneration, 1); - SDL_AtomicSet(&SDL_IMMDevice_DefaultCaptureGeneration, 1); return 0; } void WASAPI_PlatformDeinit(void) { - DevIdList *devidlist; - DevIdList *next; - delete playback_device_event_handler; playback_device_event_handler = nullptr; delete capture_device_event_handler; 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 // startup, so we don't need to enumerate them separately before // listening for updates. 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); - SDL_WaitSemaphore(playback_device_event_handler->completed); - SDL_WaitSemaphore(capture_device_event_handler->completed); + capture_device_event_handler->WaitForCompletion(); + defdevid = MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default) + if (defdevid) { + *default_capture = FindWinRTAudioDevice(defdevid->Data()); + } } -struct SDL_WasapiActivationHandler : public RuntimeClass, FtmBase, IActivateAudioInterfaceCompletionHandler> +class SDL_WasapiActivationHandler : public RuntimeClass, FtmBase, IActivateAudioInterfaceCompletionHandler> { - SDL_WasapiActivationHandler() : device(nullptr) {} - STDMETHOD(ActivateCompleted) - (IActivateAudioInterfaceAsyncOperation *operation); - SDL_AudioDevice *device; +public: + SDL_WasapiActivationHandler() : completion_semaphore(SDL_CreateSemaphore(0)) { SDL_assert(completion_semaphore != NULL); } + STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation *operation); + 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 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. - SDL_AtomicSet(&device->hidden->just_activated, 1); - WASAPI_UnrefDevice(device); + SDL_PostSemaphore(completion_semaphore); return S_OK; } @@ -251,24 +282,10 @@ void WASAPI_PlatformDeleteActivationHandler(void *handler) ((SDL_WasapiActivationHandler *)handler)->Release(); } -int WASAPI_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture) +int WASAPI_ActivateDevice(SDL_AudioDevice *device) { - return SDL_Unsupported(); -} - -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); + LPCWSTR devid = (LPCWSTR) device->handle; + SDL_assert(devid != NULL); ComPtr handler = Make(); 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()->device = _this; - _this->hidden->activation_handler = handler.Get(); + device->hidden->activation_handler = handler.Get(); - WASAPI_RefDevice(_this); /* completion handler will unref it. */ IActivateAudioInterfaceAsyncOperation *async = nullptr; 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(); } handler.Get()->Release(); - WASAPI_UnrefDevice(_this); return WIN_SetErrorFromHRESULT("WASAPI can't activate requested audio endpoint", ret); } - /* Spin until the async operation is complete. - * If we don't PrepDevice before leaving this function, the bug list gets LONG: - * - 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); - } + // !!! 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. + handler.WaitForCompletion(); // block here until we have an answer, so this is synchronous to us after all. HRESULT activateRes = S_OK; 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); } - iunknown->QueryInterface(IID_PPV_ARGS(&_this->hidden->client)); - if (!_this->hidden->client) { + iunknown->QueryInterface(IID_PPV_ARGS(&device->hidden->client)); + if (!device->hidden->client) { return SDL_SetError("Failed to query WASAPI client interface"); } - if (WASAPI_PrepDevice(_this, isrecovery) == -1) { + if (WASAPI_PrepDevice(device) == -1) { return -1; } return 0; } -void WASAPI_PlatformThreadInit(SDL_AudioDevice *_this) +void WASAPI_PlatformThreadInit(SDL_AudioDevice *device) { // !!! 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. } -/* Everything below was copied from SDL_wasapi.c, before it got moved to SDL_immdevice.c! */ - -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) +void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device) { - if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) { - 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); + SDL_free(device->handle); } #endif // SDL_AUDIO_DRIVER_WASAPI && defined(__WINRT__) diff --git a/src/core/windows/SDL_immdevice.c b/src/core/windows/SDL_immdevice.c index c00916a417..b8c5793d15 100644 --- a/src/core/windows/SDL_immdevice.c +++ b/src/core/windows/SDL_immdevice.c @@ -147,7 +147,7 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const SDL_bool iscapture, const char * SDL_zero(spec); spec.channels = (Uint8)fmt->Format.nChannels; spec.freq = fmt->Format.nSamplesPerSec; - spec.format = WaveFormatToSDLFormat((WAVEFORMATEX *)fmt); + spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt); device = SDL_AddAudioDevice(iscapture, devname, &spec, handle); } @@ -164,7 +164,6 @@ typedef struct SDLMMNotificationClient { const IMMNotificationClientVtbl *lpVtbl; SDL_AtomicInt refcount; - SDL_bool useguid; } SDLMMNotificationClient; 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); if (devname) { 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; } SDL_free(devname); @@ -395,7 +394,7 @@ void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_Audi IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)¬ification_client); } -SDL_AudioFormat WaveFormatToSDLFormat(WAVEFORMATEX *waveformat) +SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat) { if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) { return SDL_AUDIO_F32SYS; diff --git a/src/core/windows/SDL_immdevice.h b/src/core/windows/SDL_immdevice.h index a5a0a557bb..d71dc053f5 100644 --- a/src/core/windows/SDL_immdevice.h +++ b/src/core/windows/SDL_immdevice.h @@ -35,6 +35,6 @@ void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_Audi LPGUID SDL_IMMDevice_GetDirectSoundGUID(SDL_AudioDevice *device); LPCWSTR SDL_IMMDevice_GetDevID(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 */ diff --git a/test/loopwave.c b/test/loopwave.c index 5b404a5cd4..e6cc4df1e5 100644 --- a/test/loopwave.c +++ b/test/loopwave.c @@ -39,7 +39,7 @@ static SDL_AudioStream *stream; 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); } } From d6b4f4848877a060d2dcb39052320dc7b920b240 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 14 Jul 2023 19:58:05 -0400 Subject: [PATCH 098/138] visualc: Turn on multiprocessor compilation. --- VisualC/SDL/SDL.vcxproj | 6 +++++- VisualC/SDL_test/SDL_test.vcxproj | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index 8b8f6d6a79..0cea506042 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -122,6 +122,7 @@ OnlyExplicitInline Use SDL_internal.h + true _DEBUG;%(PreprocessorDefinitions) @@ -153,6 +154,7 @@ OnlyExplicitInline Use SDL_internal.h + true _DEBUG;%(PreprocessorDefinitions) @@ -188,6 +190,7 @@ OnlyExplicitInline Use SDL_internal.h + true NDEBUG;%(PreprocessorDefinitions) @@ -220,6 +223,7 @@ OnlyExplicitInline Use SDL_internal.h + true NDEBUG;%(PreprocessorDefinitions) @@ -656,4 +660,4 @@ - + \ No newline at end of file diff --git a/VisualC/SDL_test/SDL_test.vcxproj b/VisualC/SDL_test/SDL_test.vcxproj index 8148024c92..f69b2e5f6b 100644 --- a/VisualC/SDL_test/SDL_test.vcxproj +++ b/VisualC/SDL_test/SDL_test.vcxproj @@ -100,6 +100,7 @@ Level3 OldStyle true + true @@ -116,6 +117,7 @@ Level3 OldStyle true + true @@ -134,6 +136,7 @@ Level3 OldStyle true + true @@ -150,6 +153,7 @@ Level3 OldStyle true + true @@ -168,4 +172,4 @@ - + \ No newline at end of file From 0b58e96d9e34dd76a853fe2dd67997c1ace378b2 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 14 Jul 2023 23:37:17 -0400 Subject: [PATCH 099/138] wasapi: Patched WinRT to compile. --- src/audio/wasapi/SDL_wasapi_winrt.cpp | 12 ++++++------ src/core/windows/SDL_immdevice.c | 23 ----------------------- src/core/windows/SDL_immdevice.h | 1 - src/core/windows/SDL_windows.c | 27 +++++++++++++++++++++++++++ src/core/windows/SDL_windows.h | 3 +++ 5 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/audio/wasapi/SDL_wasapi_winrt.cpp b/src/audio/wasapi/SDL_wasapi_winrt.cpp index 3280f13597..373244ac73 100644 --- a/src/audio/wasapi/SDL_wasapi_winrt.cpp +++ b/src/audio/wasapi/SDL_wasapi_winrt.cpp @@ -159,12 +159,12 @@ void SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher ^ sender, DeviceI WAVEFORMATEXTENSIBLE 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); + spec.channels = (Uint8)fmt.Format.nChannels; + spec.freq = fmt.Format.nSamplesPerSec; + spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)&fmt); } - LPWSTR devid = SDL_wcsdup(devid); + LPWSTR devid = SDL_wcsdup(info->Id->Data()); if (devid) { SDL_AddAudioDevice(this->iscapture, utf8dev, spec.channels ? &spec : NULL, devid); } @@ -244,7 +244,7 @@ void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice capture_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_TRUE); capture_device_event_handler->WaitForCompletion(); - defdevid = MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default) + defdevid = MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default); if (defdevid) { *default_capture = FindWinRTAudioDevice(defdevid->Data()); } @@ -307,7 +307,7 @@ int WASAPI_ActivateDevice(SDL_AudioDevice *device) } // !!! 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. - handler.WaitForCompletion(); // block here until we have an answer, so this is synchronous to us after all. + handler.Get()->WaitForCompletion(); // block here until we have an answer, so this is synchronous to us after all. HRESULT activateRes = S_OK; IUnknown *iunknown = nullptr; diff --git a/src/core/windows/SDL_immdevice.c b/src/core/windows/SDL_immdevice.c index b8c5793d15..160297e387 100644 --- a/src/core/windows/SDL_immdevice.c +++ b/src/core/windows/SDL_immdevice.c @@ -53,8 +53,6 @@ static const IID SDL_IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089,{ 0x85, 0x86 static const PROPERTYKEY SDL_PKEY_Device_FriendlyName = { { 0xa45c254e, 0xdf1c, 0x4efd,{ 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, } }, 14 }; static const PROPERTYKEY SDL_PKEY_AudioEngine_DeviceFormat = { { 0xf19f064d, 0x82c, 0x4e27,{ 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, } }, 0 }; static const PROPERTYKEY SDL_PKEY_AudioEndpoint_GUID = { { 0x1da5d803, 0xd492, 0x4edd,{ 0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e, } }, 4 }; -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 } }; /* *INDENT-ON* */ /* clang-format on */ static SDL_bool FindByDevIDCallback(SDL_AudioDevice *device, void *userdata) @@ -394,25 +392,4 @@ void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_Audi IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)¬ification_client); } -SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat) -{ - if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) { - 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; -} - #endif /* (defined(__WIN32__) || defined(__GDK__)) && defined(HAVE_MMDEVICEAPI_H) */ diff --git a/src/core/windows/SDL_immdevice.h b/src/core/windows/SDL_immdevice.h index d71dc053f5..a190a7cf84 100644 --- a/src/core/windows/SDL_immdevice.h +++ b/src/core/windows/SDL_immdevice.h @@ -35,6 +35,5 @@ void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_Audi LPGUID SDL_IMMDevice_GetDirectSoundGUID(SDL_AudioDevice *device); LPCWSTR SDL_IMMDevice_GetDevID(SDL_AudioDevice *device); void SDL_IMMDevice_FreeDeviceHandle(SDL_AudioDevice *device); -SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat); #endif /* SDL_IMMDEVICE_H */ diff --git a/src/core/windows/SDL_windows.c b/src/core/windows/SDL_windows.c index c18bd642cd..b842622e6b 100644 --- a/src/core/windows/SDL_windows.c +++ b/src/core/windows/SDL_windows.c @@ -335,6 +335,33 @@ BOOL WIN_IsRectEmpty(const RECT *rect) return (rect->right <= rect->left) || (rect->bottom <= rect->top); } +/* Some GUIDs we need to know without linking to libraries that aren't available before Vista. */ +/* *INDENT-OFF* */ /* clang-format off */ +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 } }; +/* *INDENT-ON* */ /* clang-format on */ + +SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat) +{ + if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) { + 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; +} + /* Win32-specific SDL_RunApp(), which does most of the SDL_main work, based on SDL_windows_main.c, placed in the public domain by Sam Lantinga 4/13/98 */ #ifdef __WIN32__ diff --git a/src/core/windows/SDL_windows.h b/src/core/windows/SDL_windows.h index f6d87b1828..e9eb1f0d6e 100644 --- a/src/core/windows/SDL_windows.h +++ b/src/core/windows/SDL_windows.h @@ -78,6 +78,7 @@ #include #include /* for REFIID with broken mingw.org headers */ +#include /* Older Visual C++ headers don't have the Win64-compatible typedefs... */ #if defined(_MSC_VER) && (_MSC_VER <= 1200) @@ -154,6 +155,8 @@ extern void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect); /* Returns SDL_TRUE if the rect is empty */ extern BOOL WIN_IsRectEmpty(const RECT *rect); +extern SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } From ba27176106682be6aaf266ee678ac8b0a2aa4935 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 20 Jul 2023 20:09:25 -0400 Subject: [PATCH 100/138] vitaaudio: Untested attempt to move Vita audio to SDL3's audio API. --- src/audio/vita/SDL_vitaaudio.c | 150 ++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 66 deletions(-) diff --git a/src/audio/vita/SDL_vitaaudio.c b/src/audio/vita/SDL_vitaaudio.c index 5cd40e6996..314f423027 100644 --- a/src/audio/vita/SDL_vitaaudio.c +++ b/src/audio/vita/SDL_vitaaudio.c @@ -38,41 +38,41 @@ #define SCE_AUDIO_SAMPLE_ALIGN(s) (((s) + 63) & ~63) #define SCE_AUDIO_MAX_VOLUME 0x8000 -static int VITAAUD_OpenCaptureDevice(SDL_AudioDevice *_this) +static int VITAAUD_OpenCaptureDevice(SDL_AudioDevice *device) { - _this->spec.freq = 16000; - _this->spec.samples = 512; - _this->spec.channels = 1; + device->spec.freq = 16000; + device->spec.channels = 1; + device->sample_frames = 512; - SDL_CalculateAudioSpec(&_this->spec); + SDL_UpdatedAudioDeviceFormat(device); - _this->hidden->port = sceAudioInOpenPort(SCE_AUDIO_IN_PORT_TYPE_VOICE, 512, 16000, SCE_AUDIO_IN_PARAM_FORMAT_S16_MONO); + device->hidden->port = sceAudioInOpenPort(SCE_AUDIO_IN_PORT_TYPE_VOICE, 512, 16000, SCE_AUDIO_IN_PARAM_FORMAT_S16_MONO); - if (_this->hidden->port < 0) { - return SDL_SetError("Couldn't open audio in port: %x", _this->hidden->port); + if (device->hidden->port < 0) { + return SDL_SetError("Couldn't open audio in port: %x", device->hidden->port); } return 0; } -static int VITAAUD_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int VITAAUD_OpenDevice(SDL_AudioDevice *device, const char *devname) { int format, mixlen, i, port = SCE_AUDIO_OUT_PORT_TYPE_MAIN; int vols[2] = { SCE_AUDIO_MAX_VOLUME, SCE_AUDIO_MAX_VOLUME }; SDL_AudioFormat test_format; const SDL_AudioFormat *closefmts; - _this->hidden = (struct SDL_PrivateAudioData *) - SDL_malloc(sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + device->hidden = (struct SDL_PrivateAudioData *) + SDL_malloc(sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - SDL_memset(_this->hidden, 0, sizeof(*_this->hidden)); + SDL_memset(device->hidden, 0, sizeof(*device->hidden)); - closefmts = SDL_ClosestAudioFormats(_this->spec.format); + closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { if (test_format == SDL_AUDIO_S16LSB) { - _this->spec.format = test_format; + device->spec.format = test_format; break; } } @@ -81,106 +81,125 @@ static int VITAAUD_OpenDevice(SDL_AudioDevice *_this, const char *devname) return SDL_SetError("Unsupported audio format"); } - if (_this->iscapture) { - return VITAAUD_OpenCaptureDevice(_this); + if (device->iscapture) { + return VITAAUD_OpenCaptureDevice(device); } - /* The sample count must be a multiple of 64. */ - _this->spec.samples = SCE_AUDIO_SAMPLE_ALIGN(_this->spec.samples); + // The sample count must be a multiple of 64. + device->sample_frames = SCE_AUDIO_SAMPLE_ALIGN(device->sample_frames); - /* Update the fragment size as size in bytes. */ - SDL_CalculateAudioSpec(&_this->spec); + // Update the fragment size as size in bytes. + SDL_UpdatedAudioDeviceFormat(device); /* Allocate the mixing buffer. Its size and starting address must be a multiple of 64 bytes. Our sample count is already a multiple of 64, so spec->size should be a multiple of 64 as well. */ - mixlen = _this->spec.size * NUM_BUFFERS; - _this->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen); - if (_this->hidden->rawbuf == NULL) { + mixlen = device->buffer_size * NUM_BUFFERS; + device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen); + if (device->hidden->rawbuf == NULL) { return SDL_SetError("Couldn't allocate mixing buffer"); } - /* Setup the hardware channel. */ - if (_this->spec.channels == 1) { + // Setup the hardware channel. + if (device->spec.channels == 1) { format = SCE_AUDIO_OUT_MODE_MONO; } else { format = SCE_AUDIO_OUT_MODE_STEREO; } - if (_this->spec.freq < 48000) { + // the main port requires 48000Hz audio, so this drops to the background music port if necessary + if (device->spec.freq < 48000) { port = SCE_AUDIO_OUT_PORT_TYPE_BGM; } - _this->hidden->port = sceAudioOutOpenPort(port, _this->spec.samples, _this->spec.freq, format); - if (_this->hidden->port < 0) { - SDL_aligned_free(_this->hidden->rawbuf); - _this->hidden->rawbuf = NULL; - return SDL_SetError("Couldn't open audio out port: %x", _this->hidden->port); + device->hidden->port = sceAudioOutOpenPort(port, device->sample_frames, device->spec.freq, format); + if (device->hidden->port < 0) { + SDL_aligned_free(device->hidden->rawbuf); + device->hidden->rawbuf = NULL; + return SDL_SetError("Couldn't open audio out port: %x", device->hidden->port); } - sceAudioOutSetVolume(_this->hidden->port, SCE_AUDIO_VOLUME_FLAG_L_CH | SCE_AUDIO_VOLUME_FLAG_R_CH, vols); + sceAudioOutSetVolume(device->hidden->port, SCE_AUDIO_VOLUME_FLAG_L_CH | SCE_AUDIO_VOLUME_FLAG_R_CH, vols); - SDL_memset(_this->hidden->rawbuf, 0, mixlen); + SDL_memset(device->hidden->rawbuf, 0, mixlen); for (i = 0; i < NUM_BUFFERS; i++) { - _this->hidden->mixbufs[i] = &_this->hidden->rawbuf[i * _this->spec.size]; + device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size]; } - _this->hidden->next_buffer = 0; + device->hidden->next_buffer = 0; return 0; } -static void VITAAUD_PlayDevice(SDL_AudioDevice *_this) +static void VITAAUD_PlayDevice(SDL_AudioDevice *device) { - Uint8 *mixbuf = _this->hidden->mixbufs[_this->hidden->next_buffer]; + Uint8 *mixbuf = device->hidden->mixbufs[device->hidden->next_buffer]; - sceAudioOutOutput(_this->hidden->port, mixbuf); + sceAudioOutOutput(device->hidden->port, mixbuf); - _this->hidden->next_buffer = (_this->hidden->next_buffer + 1) % NUM_BUFFERS; + device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS; } -/* This function waits until it is possible to write a full sound buffer */ -static void VITAAUD_WaitDevice(SDL_AudioDevice *_this) +// This function waits until it is possible to write a full sound buffer +static void VITAAUD_WaitDevice(SDL_AudioDevice *device) { - /* Because we block when sending audio, there's no need for this function to do anything. */ + // !!! FIXME: we might just need to sleep roughly as long as playback buffers take to process, based on sample rate, etc. + while (!SDL_AtomicGet(&device->shutdown) && (sceAudioOutGetRestSample(device->hidden->port) >= device->buffer_size)) { + SDL_Delay(1); + } } -static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *device) { - return _this->hidden->mixbufs[_this->hidden->next_buffer]; + return device->hidden->mixbufs[device->hidden->next_buffer]; } -static void VITAAUD_CloseDevice(SDL_AudioDevice *_this) +static void VITAAUD_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden->port >= 0) { - if (_this->iscapture) { - sceAudioInReleasePort(_this->hidden->port); + if (device->hidden->port >= 0) { + if (device->iscapture) { + sceAudioInReleasePort(device->hidden->port); } else { - sceAudioOutReleasePort(_this->hidden->port); + sceAudioOutReleasePort(device->hidden->port); } - _this->hidden->port = -1; + device->hidden->port = -1; } - if (!_this->iscapture && _this->hidden->rawbuf != NULL) { - SDL_aligned_free(_this->hidden->rawbuf); - _this->hidden->rawbuf = NULL; + if (!device->iscapture && device->hidden->rawbuf != NULL) { + SDL_aligned_free(device->hidden->rawbuf); + device->hidden->rawbuf = NULL; } } -static int VITAAUD_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static void VITAAUD_WaitCaptureDevice(SDL_AudioDevice *device) +{ + // there's only a blocking call to obtain more data, so we'll just sleep as + // long as a buffer would run. + const Uint64 endticks = SDL_GetTicks() + ((device->sample_frames * 1000) / device->spec.freq); + while (!SDL_AtomicGet(&device->shutdown) && (SDL_GetTicks() < endticks)) { + SDL_Delay(1); + } +} + +static int VITAAUD_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { int ret; - SDL_assert(buflen == _this->spec.size); - ret = sceAudioInInput(_this->hidden->port, buffer); + SDL_assert(buflen == device->buffer_size); + ret = sceAudioInInput(device->hidden->port, buffer); if (ret < 0) { return SDL_SetError("Failed to capture from device: %x", ret); } - return _this->spec.size; + return device->buffer_size; } -static void VITAAUD_ThreadInit(SDL_AudioDevice *_this) +static void PULSEAUDIO_FlushCapture(SDL_AudioDevice *device) { - /* Increase the priority of this audio thread by 1 to put it - ahead of other SDL threads. */ + // just grab the latest and dump it. + sceAudioInInput(device->hidden->port, device->work_buffer); +} + +static void VITAAUD_ThreadInit(SDL_AudioDevice *device) +{ + // Increase the priority of this audio thread by 1 to put it ahead of other SDL threads. SceUID thid; SceKernelThreadInfo info; thid = sceKernelGetThreadId(); @@ -192,26 +211,25 @@ static void VITAAUD_ThreadInit(SDL_AudioDevice *_this) static SDL_bool VITAAUD_Init(SDL_AudioDriverImpl *impl) { - /* Set the function pointers */ impl->OpenDevice = VITAAUD_OpenDevice; impl->PlayDevice = VITAAUD_PlayDevice; impl->WaitDevice = VITAAUD_WaitDevice; impl->GetDeviceBuf = VITAAUD_GetDeviceBuf; impl->CloseDevice = VITAAUD_CloseDevice; impl->ThreadInit = VITAAUD_ThreadInit; - + impl->WaitCaptureDevice = VITAAUD_WaitCaptureDevice; + impl->FlushCapture = VITAAUD_FlushCapture; impl->CaptureFromDevice = VITAAUD_CaptureFromDevice; - /* and the capabilities */ impl->HasCaptureSupport = SDL_TRUE; impl->OnlyHasDefaultOutputDevice = SDL_TRUE; impl->OnlyHasDefaultCaptureDevice = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap VITAAUD_bootstrap = { "vita", "VITA audio driver", VITAAUD_Init, SDL_FALSE }; -#endif /* SDL_AUDIO_DRIVER_VITA */ +#endif // SDL_AUDIO_DRIVER_VITA From b0d89868c6855f70ca94bc79f9abcb431b4c3350 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 21 Jul 2023 10:55:33 -0400 Subject: [PATCH 101/138] n3dsaudio: Updated (but untested!) for SDL3 audio API. --- src/audio/n3ds/SDL_n3dsaudio.c | 257 ++++++++++++++------------------- src/audio/n3ds/SDL_n3dsaudio.h | 6 - 2 files changed, 108 insertions(+), 155 deletions(-) diff --git a/src/audio/n3ds/SDL_n3dsaudio.c b/src/audio/n3ds/SDL_n3dsaudio.c index 1d0bff66ef..e43ad08771 100644 --- a/src/audio/n3ds/SDL_n3dsaudio.c +++ b/src/audio/n3ds/SDL_n3dsaudio.c @@ -22,7 +22,7 @@ #ifdef SDL_AUDIO_DRIVER_N3DS -/* N3DS Audio driver */ +// N3DS Audio driver #include "../SDL_sysaudio.h" #include "SDL_n3dsaudio.h" @@ -32,27 +32,24 @@ static dspHookCookie dsp_hook; static SDL_AudioDevice *audio_device; -static void FreePrivateData(SDL_AudioDevice *_this); -static int FindAudioFormat(SDL_AudioDevice *_this); - -static SDL_INLINE void contextLock(SDL_AudioDevice *_this) +static SDL_INLINE void contextLock(SDL_AudioDevice *device) { - LightLock_Lock(&_this->hidden->lock); + LightLock_Lock(&device->hidden->lock); } -static SDL_INLINE void contextUnlock(SDL_AudioDevice *_this) +static SDL_INLINE void contextUnlock(SDL_AudioDevice *device) { - LightLock_Unlock(&_this->hidden->lock); + LightLock_Unlock(&device->hidden->lock); } -static void N3DSAUD_LockAudio(SDL_AudioDevice *_this) +static void N3DSAUD_LockAudio(SDL_AudioDevice *device) { - contextLock(_this); + contextLock(device); } -static void N3DSAUD_UnlockAudio(SDL_AudioDevice *_this) +static void N3DSAUD_UnlockAudio(SDL_AudioDevice *device) { - contextUnlock(_this); + contextUnlock(device); } static void N3DSAUD_DspHook(DSP_HookType hook) @@ -70,36 +67,36 @@ static void AudioFrameFinished(void *device) { bool shouldBroadcast = false; unsigned i; - SDL_AudioDevice *_this = (SDL_AudioDevice *)device; + SDL_AudioDevice *device = (SDL_AudioDevice *)device; - contextLock(_this); + contextLock(device); for (i = 0; i < NUM_BUFFERS; i++) { - if (_this->hidden->waveBuf[i].status == NDSP_WBUF_DONE) { - _this->hidden->waveBuf[i].status = NDSP_WBUF_FREE; + if (device->hidden->waveBuf[i].status == NDSP_WBUF_DONE) { + device->hidden->waveBuf[i].status = NDSP_WBUF_FREE; shouldBroadcast = SDL_TRUE; } } if (shouldBroadcast) { - CondVar_Broadcast(&_this->hidden->cv); + CondVar_Broadcast(&device->hidden->cv); } - contextUnlock(_this); + contextUnlock(device); } -static int N3DSAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int N3DSAUDIO_OpenDevice(SDL_AudioDevice *device) { Result ndsp_init_res; Uint8 *data_vaddr; float mix[12]; - _this->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - /* Initialise the DSP service */ + // Initialise the DSP service ndsp_init_res = ndspInit(); if (R_FAILED(ndsp_init_res)) { if ((R_SUMMARY(ndsp_init_res) == RS_NOTFOUND) && (R_MODULE(ndsp_init_res) == RM_DSP)) { @@ -110,158 +107,168 @@ static int N3DSAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) return -1; } - /* Initialise internal state */ - LightLock_Init(&_this->hidden->lock); - CondVar_Init(&_this->hidden->cv); + // Initialise internal state + LightLock_Init(&device->hidden->lock); + CondVar_Init(&device->hidden->cv); - if (_this->spec.channels > 2) { - _this->spec.channels = 2; + if (device->spec.channels > 2) { + device->spec.channels = 2; } - /* Should not happen but better be safe. */ - if (FindAudioFormat(_this) < 0) { + Uint32 format = 0; + SDL_AudioFormat test_format; + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); + while ((test_format = *(closefmts++)) != 0) { + if (test_format == SDL_AUDIO_S8) { // Signed 8-bit audio supported + format = (device->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM8 : NDSP_FORMAT_MONO_PCM8; + break; + } else if (test_format == SDL_AUDIO_S16) { // Signed 16-bit audio supported + format = (device->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM16 : NDSP_FORMAT_MONO_PCM16; + break; + } + } + + if (!test_format) { // shouldn't happen, but just in case... return SDL_SetError("No supported audio format found."); } - /* Update the fragment size as size in bytes */ - SDL_CalculateAudioSpec(&_this->spec); + device->spec.format = test_format; - /* Allocate mixing buffer */ - if (_this->spec.size >= SDL_MAX_UINT32 / 2) { + // Update the fragment size as size in bytes + SDL_UpdatedAudioDeviceFormat(device); + + // Allocate mixing buffer + if (device->buffer_size >= SDL_MAX_UINT32 / 2) { return SDL_SetError("Mixing buffer is too large."); } - _this->hidden->mixlen = _this->spec.size; - _this->hidden->mixbuf = (Uint8 *)SDL_malloc(_this->spec.size); - if (_this->hidden->mixbuf == NULL) { + device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); + if (device->hidden->mixbuf == NULL) { return SDL_OutOfMemory(); } - SDL_memset(_this->hidden->mixbuf, _this->spec.silence, _this->spec.size); + SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size); - data_vaddr = (Uint8 *)linearAlloc(_this->hidden->mixlen * NUM_BUFFERS); + data_vaddr = (Uint8 *)linearAlloc(device->buffer_size * NUM_BUFFERS); if (data_vaddr == NULL) { return SDL_OutOfMemory(); } - SDL_memset(data_vaddr, 0, _this->hidden->mixlen * NUM_BUFFERS); - DSP_FlushDataCache(data_vaddr, _this->hidden->mixlen * NUM_BUFFERS); + SDL_memset(data_vaddr, 0, device->buffer_size * NUM_BUFFERS); + DSP_FlushDataCache(data_vaddr, device->buffer_size * NUM_BUFFERS); - _this->hidden->nextbuf = 0; - _this->hidden->channels = _this->spec.channels; - _this->hidden->samplerate = _this->spec.freq; + device->hidden->nextbuf = 0; ndspChnReset(0); ndspChnSetInterp(0, NDSP_INTERP_LINEAR); - ndspChnSetRate(0, _this->spec.freq); - ndspChnSetFormat(0, _this->hidden->format); + ndspChnSetRate(0, device->spec.freq); + ndspChnSetFormat(0, device->hidden->format); - SDL_memset(mix, 0, sizeof(mix)); - mix[0] = 1.0; - mix[1] = 1.0; + SDL_zeroa(mix); + mix[0] = mix[1] = 1.0f; ndspChnSetMix(0, mix); - SDL_memset(_this->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS); + SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS); + const int sample_frame_size = device->spec.channels * (SDL_AUDIO_BITSIZE(device->spec.format) / 8); for (unsigned i = 0; i < NUM_BUFFERS; i++) { - _this->hidden->waveBuf[i].data_vaddr = data_vaddr; - _this->hidden->waveBuf[i].nsamples = _this->hidden->mixlen / _this->hidden->bytePerSample; - data_vaddr += _this->hidden->mixlen; + device->hidden->waveBuf[i].data_vaddr = data_vaddr; + device->hidden->waveBuf[i].nsamples = device->buffer_size / sample_frame_size; + data_vaddr += device->buffer_size; } - /* Setup callback */ - audio_device = _this; - ndspSetCallback(AudioFrameFinished, _this); + // Setup callback + audio_device = device; + ndspSetCallback(AudioFrameFinished, device); dspHook(&dsp_hook, N3DSAUD_DspHook); return 0; } -static int N3DSAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static void N3DSAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { - /* Delay to make this sort of simulate real audio input. */ - SDL_Delay((_this->spec.samples * 1000) / _this->spec.freq); + contextLock(device); - /* always return a full buffer of silence. */ - SDL_memset(buffer, _this->spec.silence, buflen); - return buflen; -} + const size_t nextbuf = device->hidden->nextbuf; -static void N3DSAUDIO_PlayDevice(SDL_AudioDevice *_this) -{ - size_t nextbuf; - size_t sampleLen; - contextLock(_this); - - nextbuf = _this->hidden->nextbuf; - sampleLen = _this->hidden->mixlen; - - if (_this->hidden->isCancelled || - _this->hidden->waveBuf[nextbuf].status != NDSP_WBUF_FREE) { - contextUnlock(_this); + if (device->hidden->isCancelled || + device->hidden->waveBuf[nextbuf].status != NDSP_WBUF_FREE) { + contextUnlock(device); return; } - _this->hidden->nextbuf = (nextbuf + 1) % NUM_BUFFERS; + device->hidden->nextbuf = (nextbuf + 1) % NUM_BUFFERS; - contextUnlock(_this); + contextUnlock(device); - SDL_memcpy((void *)_this->hidden->waveBuf[nextbuf].data_vaddr, - _this->hidden->mixbuf, sampleLen); - DSP_FlushDataCache(_this->hidden->waveBuf[nextbuf].data_vaddr, sampleLen); + SDL_memcpy((void *)device->hidden->waveBuf[nextbuf].data_vaddr, buffer, buflen); + DSP_FlushDataCache(device->hidden->waveBuf[nextbuf].data_vaddr, sampleLen); - ndspChnWaveBufAdd(0, &_this->hidden->waveBuf[nextbuf]); + ndspChnWaveBufAdd(0, &device->hidden->waveBuf[nextbuf]); } -static void N3DSAUDIO_WaitDevice(SDL_AudioDevice *_this) +static void N3DSAUDIO_WaitDevice(SDL_AudioDevice *device) { - contextLock(_this); - while (!_this->hidden->isCancelled && - _this->hidden->waveBuf[_this->hidden->nextbuf].status != NDSP_WBUF_FREE) { - CondVar_Wait(&_this->hidden->cv, &_this->hidden->lock); + contextLock(device); + while (!device->hidden->isCancelled && !SDL_AtomicGet(&device->shutdown) && + device->hidden->waveBuf[device->hidden->nextbuf].status != NDSP_WBUF_FREE) { + CondVar_Wait(&device->hidden->cv, &device->hidden->lock); } - contextUnlock(_this); + contextUnlock(device); } -static Uint8 *N3DSAUDIO_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *N3DSAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - return _this->hidden->mixbuf; + return device->hidden->mixbuf; } -static void N3DSAUDIO_CloseDevice(SDL_AudioDevice *_this) +static void N3DSAUDIO_CloseDevice(SDL_AudioDevice *device) { - contextLock(_this); + if (!device->hidden) { + return; + } + + contextLock(device); dspUnhook(&dsp_hook); ndspSetCallback(NULL, NULL); - if (!_this->hidden->isCancelled) { + if (!device->hidden->isCancelled) { ndspChnReset(0); - SDL_memset(_this->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS); - CondVar_Broadcast(&_this->hidden->cv); + SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS); + CondVar_Broadcast(&device->hidden->cv); } - contextUnlock(_this); + contextUnlock(device); ndspExit(); - FreePrivateData(_this); + if (device->hidden->waveBuf[0].data_vaddr) { + linearFree((void *)device->hidden->waveBuf[0].data_vaddr); + } + + if (device->hidden->mixbuf) { + SDL_free(device->hidden->mixbuf); + device->hidden->mixbuf = NULL; + } + + SDL_free(device->hidden); + device->hidden = NULL; } -static void N3DSAUDIO_ThreadInit(SDL_AudioDevice *_this) +static void N3DSAUDIO_ThreadInit(SDL_AudioDevice *device) { s32 current_priority; svcGetThreadPriority(¤t_priority, CUR_THREAD_HANDLE); current_priority--; - /* 0x18 is reserved for video, 0x30 is the default for main thread */ + // 0x18 is reserved for video, 0x30 is the default for main thread current_priority = SDL_clamp(current_priority, 0x19, 0x2F); svcSetThreadPriority(CUR_THREAD_HANDLE, current_priority); } static SDL_bool N3DSAUDIO_Init(SDL_AudioDriverImpl *impl) { - /* Set the function pointers */ impl->OpenDevice = N3DSAUDIO_OpenDevice; impl->PlayDevice = N3DSAUDIO_PlayDevice; impl->WaitDevice = N3DSAUDIO_WaitDevice; @@ -272,11 +279,10 @@ static SDL_bool N3DSAUDIO_Init(SDL_AudioDriverImpl *impl) impl->UnlockDevice = N3DSAUD_UnlockAudio; impl->OnlyHasDefaultOutputDevice = SDL_TRUE; - /* Should be possible, but micInit would fail */ + // Should be possible, but micInit would fail impl->HasCaptureSupport = SDL_FALSE; - impl->CaptureFromDevice = N3DSAUDIO_CaptureFromDevice; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap N3DSAUDIO_bootstrap = { @@ -286,51 +292,4 @@ AudioBootStrap N3DSAUDIO_bootstrap = { 0 }; -/** - * Cleans up all allocated memory, safe to call with null pointers - */ -static void FreePrivateData(SDL_AudioDevice *_this) -{ - if (!_this->hidden) { - return; - } - - if (_this->hidden->waveBuf[0].data_vaddr) { - linearFree((void *)_this->hidden->waveBuf[0].data_vaddr); - } - - if (_this->hidden->mixbuf) { - SDL_free(_this->hidden->mixbuf); - _this->hidden->mixbuf = NULL; - } - - SDL_free(_this->hidden); - _this->hidden = NULL; -} - -static int FindAudioFormat(SDL_AudioDevice *_this) -{ - SDL_AudioFormat test_format; - const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(_this->spec.format); - while ((test_format = *(closefmts++)) != 0) { - _this->spec.format = test_format; - switch (test_format) { - case SDL_AUDIO_S8: - /* Signed 8-bit audio supported */ - _this->hidden->format = (_this->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM8 : NDSP_FORMAT_MONO_PCM8; - _this->hidden->isSigned = 1; - _this->hidden->bytePerSample = _this->spec.channels; - return 0; - case SDL_AUDIO_S16: - /* Signed 16-bit audio supported */ - _this->hidden->format = (_this->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM16 : NDSP_FORMAT_MONO_PCM16; - _this->hidden->isSigned = 1; - _this->hidden->bytePerSample = _this->spec.channels * 2; - return 0; - } - } - - return -1; -} - -#endif /* SDL_AUDIO_DRIVER_N3DS */ +#endif // SDL_AUDIO_DRIVER_N3DS diff --git a/src/audio/n3ds/SDL_n3dsaudio.h b/src/audio/n3ds/SDL_n3dsaudio.h index 88b9ffcb52..83f9ca83c9 100644 --- a/src/audio/n3ds/SDL_n3dsaudio.h +++ b/src/audio/n3ds/SDL_n3dsaudio.h @@ -30,12 +30,6 @@ struct SDL_PrivateAudioData { /* Speaker data */ Uint8 *mixbuf; - Uint32 mixlen; - Uint32 format; - Uint32 samplerate; - Uint32 channels; - Uint8 bytePerSample; - Uint32 isSigned; Uint32 nextbuf; ndspWaveBuf waveBuf[NUM_BUFFERS]; LightLock lock; From 9fa4a6ef8789ead45a670bce1910748e5d145b58 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 21 Jul 2023 10:56:15 -0400 Subject: [PATCH 102/138] netbsdaudio: Minor fix. --- src/audio/netbsd/SDL_netbsdaudio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/audio/netbsd/SDL_netbsdaudio.c b/src/audio/netbsd/SDL_netbsdaudio.c index 86ebd09b0a..4165b056df 100644 --- a/src/audio/netbsd/SDL_netbsdaudio.c +++ b/src/audio/netbsd/SDL_netbsdaudio.c @@ -204,6 +204,7 @@ static void NETBSDAUDIO_CloseDevice(SDL_AudioDevice *device) } SDL_free(device->hidden->mixbuf); SDL_free(device->hidden); + device->hidden = NULL; } } From 107fd941cd688593feaf6e4231abb467a7681040 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 21 Jul 2023 10:56:28 -0400 Subject: [PATCH 103/138] vitaaudio: Clean up correctly in CloseDevice. --- src/audio/vita/SDL_vitaaudio.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/audio/vita/SDL_vitaaudio.c b/src/audio/vita/SDL_vitaaudio.c index 314f423027..7e52ceef68 100644 --- a/src/audio/vita/SDL_vitaaudio.c +++ b/src/audio/vita/SDL_vitaaudio.c @@ -148,25 +148,29 @@ static void VITAAUD_WaitDevice(SDL_AudioDevice *device) } } -static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *device) +static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { return device->hidden->mixbufs[device->hidden->next_buffer]; } static void VITAAUD_CloseDevice(SDL_AudioDevice *device) { - if (device->hidden->port >= 0) { - if (device->iscapture) { - sceAudioInReleasePort(device->hidden->port); - } else { - sceAudioOutReleasePort(device->hidden->port); + if (device->hidden) { + if (device->hidden->port >= 0) { + if (device->iscapture) { + sceAudioInReleasePort(device->hidden->port); + } else { + sceAudioOutReleasePort(device->hidden->port); + } + device->hidden->port = -1; } - device->hidden->port = -1; - } - if (!device->iscapture && device->hidden->rawbuf != NULL) { - SDL_aligned_free(device->hidden->rawbuf); - device->hidden->rawbuf = NULL; + if (!device->iscapture && device->hidden->rawbuf != NULL) { + SDL_aligned_free(device->hidden->rawbuf); // this uses memalign(), not SDL_malloc(). + device->hidden->rawbuf = NULL; + } + SDL_free(device->hidden); + device->hidden = NULL; } } From 0b6255551ef84cfff8272956bbc83efe5e4e309d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 21 Jul 2023 11:02:52 -0400 Subject: [PATCH 104/138] test: Fixed incorrect SDL_OpenAudioDevice call in testautomation. --- src/test/SDL_test_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c index ec0b988272..05ae8d1baf 100644 --- a/src/test/SDL_test_common.c +++ b/src/test/SDL_test_common.c @@ -1444,7 +1444,7 @@ SDL_bool SDLTest_CommonInit(SDLTest_CommonState *state) } const SDL_AudioSpec spec = { state->audio_format, state->audio_channels, state->audio_freq }; - state->audio_id = SDL_OpenAudioDevice(0, &spec); + state->audio_id = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec); if (!state->audio_id) { SDL_Log("Couldn't open audio: %s\n", SDL_GetError()); return SDL_FALSE; From 6567285eae48d858215dabe066ef57f104afee6c Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 21 Jul 2023 19:45:08 -0400 Subject: [PATCH 105/138] SDL_migration.cocci: Fix up SDL_(Pause|Unpause)Audio. --- build-scripts/SDL_migration.cocci | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build-scripts/SDL_migration.cocci b/build-scripts/SDL_migration.cocci index 441112b8e2..e654d5ef80 100644 --- a/build-scripts/SDL_migration.cocci +++ b/build-scripts/SDL_migration.cocci @@ -308,10 +308,10 @@ expression e; + SDL_PauseAudioDevice(e) | - SDL_PauseAudioDevice(e, 0) -+ SDL_PlayAudioDevice(e) ++ SDL_UnpauseAudioDevice(e) | - SDL_PauseAudioDevice(e, SDL_FALSE) -+ SDL_PlayAudioDevice(e) ++ SDL_UnpauseAudioDevice(e) ) @@ @@ -321,7 +321,7 @@ expression e, pause_on; + if (pause_on) { + SDL_PauseAudioDevice(e); + } else { -+ SDL_PlayAudioDevice(e); ++ SDL_UnpauseAudioDevice(e); + } From 5707e14716824ab9bcb5f2848dfef536aacd7cca Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 21 Jul 2023 19:48:39 -0400 Subject: [PATCH 106/138] audio: Fix up some things that broke when rebasing the branch against main. --- src/audio/pulseaudio/SDL_pulseaudio.c | 6 +----- src/dynapi/SDL_dynapi.sym | 2 -- src/dynapi/SDL_dynapi_overrides.h | 3 --- src/dynapi/SDL_dynapi_procs.h | 3 --- 4 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index f0c33d03f2..5644802c9a 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -869,8 +869,6 @@ static int SDLCALL HotplugThread(void *data) static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { - SDL_Semaphore *ready_sem = SDL_CreateSemaphore(0); - PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); WaitForPulseOperation(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL)); WaitForPulseOperation(PULSEAUDIO_pa_context_get_sink_info_list(pulseaudio_context, SinkInfoCallback, (void *)((intptr_t)SDL_TRUE))); @@ -890,9 +888,7 @@ static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_Audio /* ok, we have a sane list, let's set up hotplug notifications now... */ SDL_AtomicSet(&pulseaudio_hotplug_thread_active, 1); - pulseaudio_hotplug_thread = SDL_CreateThreadInternal(HotplugThread, "PulseHotplug", 256 * 1024, ready_sem); /* !!! FIXME: this can probably survive in significantly less stack space. */ - SDL_WaitSemaphore(ready_sem); - SDL_DestroySemaphore(ready_sem); + pulseaudio_hotplug_thread = SDL_CreateThreadInternal(HotplugThread, "PulseHotplug", 256 * 1024, NULL); /* !!! FIXME: this can probably survive in significantly less stack space. */ } static void PULSEAUDIO_Deinitialize(void) diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 90da7b5fa2..4375e2c0a9 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -42,7 +42,6 @@ SDL3_0.0.0 { SDL_CloseJoystick; SDL_CloseSensor; SDL_ComposeCustomBlendMode; - SDL_ConvertAudioSamples; SDL_ConvertEventToRenderCoordinates; SDL_ConvertPixels; SDL_ConvertSurface; @@ -487,7 +486,6 @@ SDL3_0.0.0 { SDL_OpenSensor; SDL_OpenURL; SDL_PeepEvents; - SDL_PlayAudioDevice; SDL_PollEvent; SDL_PostSemaphore; SDL_PremultiplyAlpha; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 452819fcae..25f0443865 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -66,7 +66,6 @@ #define SDL_CloseJoystick SDL_CloseJoystick_REAL #define SDL_CloseSensor SDL_CloseSensor_REAL #define SDL_ComposeCustomBlendMode SDL_ComposeCustomBlendMode_REAL -#define SDL_ConvertAudioSamples SDL_ConvertAudioSamples_REAL #define SDL_ConvertEventToRenderCoordinates SDL_ConvertEventToRenderCoordinates_REAL #define SDL_ConvertPixels SDL_ConvertPixels_REAL #define SDL_ConvertSurface SDL_ConvertSurface_REAL @@ -511,7 +510,6 @@ #define SDL_OpenSensor SDL_OpenSensor_REAL #define SDL_OpenURL SDL_OpenURL_REAL #define SDL_PeepEvents SDL_PeepEvents_REAL -#define SDL_PlayAudioDevice SDL_PlayAudioDevice_REAL #define SDL_PollEvent SDL_PollEvent_REAL #define SDL_PostSemaphore SDL_PostSemaphore_REAL #define SDL_PremultiplyAlpha SDL_PremultiplyAlpha_REAL @@ -579,7 +577,6 @@ #define SDL_SendGamepadEffect SDL_SendGamepadEffect_REAL #define SDL_SendJoystickEffect SDL_SendJoystickEffect_REAL #define SDL_SetAssertionHandler SDL_SetAssertionHandler_REAL -#define SDL_SetAudioStreamFormat SDL_SetAudioStreamFormat_REAL #define SDL_SetClipboardData SDL_SetClipboardData_REAL #define SDL_SetClipboardText SDL_SetClipboardText_REAL #define SDL_SetCursor SDL_SetCursor_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 78eaa0409b..fccf824f24 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -148,7 +148,6 @@ SDL_DYNAPI_PROC(void,SDL_CloseGamepad,(SDL_Gamepad *a),(a),) SDL_DYNAPI_PROC(void,SDL_CloseJoystick,(SDL_Joystick *a),(a),) SDL_DYNAPI_PROC(void,SDL_CloseSensor,(SDL_Sensor *a),(a),) SDL_DYNAPI_PROC(SDL_BlendMode,SDL_ComposeCustomBlendMode,(SDL_BlendFactor a, SDL_BlendFactor b, SDL_BlendOperation c, SDL_BlendFactor d, SDL_BlendFactor e, SDL_BlendOperation f),(a,b,c,d,e,f),return) -SDL_DYNAPI_PROC(int,SDL_ConvertAudioSamples,(SDL_AudioFormat a, Uint8 b, int c, const Uint8 *d, int e, SDL_AudioFormat f, Uint8 g, int h, Uint8 **i, int *j),(a,b,c,d,e,f,g,h,i,j),return) SDL_DYNAPI_PROC(int,SDL_ConvertEventToRenderCoordinates,(SDL_Renderer *a, SDL_Event *b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_ConvertPixels,(int a, int b, Uint32 c, const void *d, int e, Uint32 f, void *g, int h),(a,b,c,d,e,f,g,h),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_ConvertSurface,(SDL_Surface *a, const SDL_PixelFormat *b),(a,b),return) @@ -570,7 +569,6 @@ SDL_DYNAPI_PROC(SDL_Joystick*,SDL_OpenJoystick,(SDL_JoystickID a),(a),return) SDL_DYNAPI_PROC(SDL_Sensor*,SDL_OpenSensor,(SDL_SensorID a),(a),return) SDL_DYNAPI_PROC(int,SDL_OpenURL,(const char *a),(a),return) SDL_DYNAPI_PROC(int,SDL_PeepEvents,(SDL_Event *a, int b, SDL_eventaction c, Uint32 d, Uint32 e),(a,b,c,d,e),return) -SDL_DYNAPI_PROC(int,SDL_PlayAudioDevice,(SDL_AudioDeviceID a),(a),return) SDL_DYNAPI_PROC(int,SDL_PollEvent,(SDL_Event *a),(a),return) SDL_DYNAPI_PROC(int,SDL_PostSemaphore,(SDL_Semaphore *a),(a),return) SDL_DYNAPI_PROC(int,SDL_PremultiplyAlpha,(int a, int b, Uint32 c, const void *d, int e, Uint32 f, void *g, int h),(a,b,c,d,e,f,g,h),return) @@ -636,7 +634,6 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_ScreenSaverEnabled,(void),(),return) SDL_DYNAPI_PROC(int,SDL_SendGamepadEffect,(SDL_Gamepad *a, const void *b, int c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_SendJoystickEffect,(SDL_Joystick *a, const void *b, int c),(a,b,c),return) SDL_DYNAPI_PROC(void,SDL_SetAssertionHandler,(SDL_AssertionHandler a, void *b),(a,b),) -SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat b, int c, int d, SDL_AudioFormat e, int f, int g),(a,b,c,d,e,f,g),return) SDL_DYNAPI_PROC(int,SDL_SetClipboardData,(SDL_ClipboardDataCallback a, SDL_ClipboardCleanupCallback b, void *c, const char **d, size_t e),(a,b,c,d,e),return) SDL_DYNAPI_PROC(int,SDL_SetClipboardText,(const char *a),(a),return) SDL_DYNAPI_PROC(int,SDL_SetCursor,(SDL_Cursor *a),(a),return) From dbf993d35833a022b8b4e2ac27f94fe29cb21e95 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 10:34:44 -0400 Subject: [PATCH 107/138] vitaaudio: patched to compile. --- src/audio/vita/SDL_vitaaudio.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/audio/vita/SDL_vitaaudio.c b/src/audio/vita/SDL_vitaaudio.c index 7e52ceef68..efe5195521 100644 --- a/src/audio/vita/SDL_vitaaudio.c +++ b/src/audio/vita/SDL_vitaaudio.c @@ -55,7 +55,7 @@ static int VITAAUD_OpenCaptureDevice(SDL_AudioDevice *device) return 0; } -static int VITAAUD_OpenDevice(SDL_AudioDevice *device, const char *devname) +static int VITAAUD_OpenDevice(SDL_AudioDevice *device) { int format, mixlen, i, port = SCE_AUDIO_OUT_PORT_TYPE_MAIN; int vols[2] = { SCE_AUDIO_MAX_VOLUME, SCE_AUDIO_MAX_VOLUME }; @@ -130,13 +130,9 @@ static int VITAAUD_OpenDevice(SDL_AudioDevice *device, const char *devname) return 0; } -static void VITAAUD_PlayDevice(SDL_AudioDevice *device) +static void VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { - Uint8 *mixbuf = device->hidden->mixbufs[device->hidden->next_buffer]; - - sceAudioOutOutput(device->hidden->port, mixbuf); - - device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS; + sceAudioOutOutput(device->hidden->port, buffer); } // This function waits until it is possible to write a full sound buffer @@ -150,7 +146,9 @@ static void VITAAUD_WaitDevice(SDL_AudioDevice *device) static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - return device->hidden->mixbufs[device->hidden->next_buffer]; + Uint8 *retval = device->hidden->mixbufs[device->hidden->next_buffer]; + device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS; + return retval; } static void VITAAUD_CloseDevice(SDL_AudioDevice *device) @@ -195,7 +193,7 @@ static int VITAAUD_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int return device->buffer_size; } -static void PULSEAUDIO_FlushCapture(SDL_AudioDevice *device) +static void VITAAUD_FlushCapture(SDL_AudioDevice *device) { // just grab the latest and dump it. sceAudioInInput(device->hidden->port, device->work_buffer); From 66bcee2ca93b4b0245a18e4f82b8cc2b10dc51b6 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 10:37:10 -0400 Subject: [PATCH 108/138] testaudiostreamdynamicresample.c: Fixed MSVC compiler warning. --- test/testaudiostreamdynamicresample.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testaudiostreamdynamicresample.c b/test/testaudiostreamdynamicresample.c index 609bd646dd..761effde68 100644 --- a/test/testaudiostreamdynamicresample.c +++ b/test/testaudiostreamdynamicresample.c @@ -93,7 +93,7 @@ int main(int argc, char *argv[]) } /* keep it looping. */ - if (SDL_GetAudioStreamAvailable(stream) < (audio_len / 2)) { + if (SDL_GetAudioStreamAvailable(stream) < ((int) (audio_len / 2))) { SDL_PutAudioStreamData(stream, audio_buf, audio_len); } From 86ca412436bc6340fd3b37d2a13a16d9a8fbb19b Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 11:07:30 -0400 Subject: [PATCH 109/138] n3dsaudio: Patched to compile. --- src/audio/n3ds/SDL_n3dsaudio.c | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/audio/n3ds/SDL_n3dsaudio.c b/src/audio/n3ds/SDL_n3dsaudio.c index e43ad08771..f17ac5601f 100644 --- a/src/audio/n3ds/SDL_n3dsaudio.c +++ b/src/audio/n3ds/SDL_n3dsaudio.c @@ -42,32 +42,22 @@ static SDL_INLINE void contextUnlock(SDL_AudioDevice *device) LightLock_Unlock(&device->hidden->lock); } -static void N3DSAUD_LockAudio(SDL_AudioDevice *device) -{ - contextLock(device); -} - -static void N3DSAUD_UnlockAudio(SDL_AudioDevice *device) -{ - contextUnlock(device); -} - static void N3DSAUD_DspHook(DSP_HookType hook) { if (hook == DSPHOOK_ONCANCEL) { contextLock(audio_device); audio_device->hidden->isCancelled = SDL_TRUE; - SDL_AtomicSet(&audio_device->enabled, SDL_FALSE); + SDL_AudioDeviceDisconnected(audio_device); CondVar_Broadcast(&audio_device->hidden->cv); contextUnlock(audio_device); } } -static void AudioFrameFinished(void *device) +static void AudioFrameFinished(void *vdevice) { bool shouldBroadcast = false; unsigned i; - SDL_AudioDevice *device = (SDL_AudioDevice *)device; + SDL_AudioDevice *device = (SDL_AudioDevice *)vdevice; contextLock(device); @@ -163,7 +153,7 @@ static int N3DSAUDIO_OpenDevice(SDL_AudioDevice *device) ndspChnSetInterp(0, NDSP_INTERP_LINEAR); ndspChnSetRate(0, device->spec.freq); - ndspChnSetFormat(0, device->hidden->format); + ndspChnSetFormat(0, format); SDL_zeroa(mix); mix[0] = mix[1] = 1.0f; @@ -203,7 +193,7 @@ static void N3DSAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, i contextUnlock(device); SDL_memcpy((void *)device->hidden->waveBuf[nextbuf].data_vaddr, buffer, buflen); - DSP_FlushDataCache(device->hidden->waveBuf[nextbuf].data_vaddr, sampleLen); + DSP_FlushDataCache(device->hidden->waveBuf[nextbuf].data_vaddr, buflen); ndspChnWaveBufAdd(0, &device->hidden->waveBuf[nextbuf]); } @@ -275,8 +265,6 @@ static SDL_bool N3DSAUDIO_Init(SDL_AudioDriverImpl *impl) impl->GetDeviceBuf = N3DSAUDIO_GetDeviceBuf; impl->CloseDevice = N3DSAUDIO_CloseDevice; impl->ThreadInit = N3DSAUDIO_ThreadInit; - impl->LockDevice = N3DSAUD_LockAudio; - impl->UnlockDevice = N3DSAUD_UnlockAudio; impl->OnlyHasDefaultOutputDevice = SDL_TRUE; // Should be possible, but micInit would fail From 4836c2db073223135f8cdc10ff6be655a1fdd1f2 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 11:13:13 -0400 Subject: [PATCH 110/138] pspaudio: Patched to compile. --- src/audio/psp/SDL_pspaudio.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/audio/psp/SDL_pspaudio.c b/src/audio/psp/SDL_pspaudio.c index d0a796ce73..6c9e0dcbf0 100644 --- a/src/audio/psp/SDL_pspaudio.c +++ b/src/audio/psp/SDL_pspaudio.c @@ -41,8 +41,6 @@ static inline SDL_bool isBasicAudioConfig(const SDL_AudioSpec *spec) static int PSPAUDIO_OpenDevice(SDL_AudioDevice *device) { - int format, mixlen, i; - device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden)); if (device->hidden == NULL) { return SDL_OutOfMemory(); @@ -59,8 +57,8 @@ static int PSPAUDIO_OpenDevice(SDL_AudioDevice *device) device->sample_frames = PSP_AUDIO_SAMPLE_ALIGN(device->sample_frames); // The number of channels (1 or 2). device->spec.channels = device->spec.channels == 1 ? 1 : 2; - format = (device->spec.channels == 1) ? PSP_AUDIO_FORMAT_MONO : PSP_AUDIO_FORMAT_STEREO; - device->hidden->channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, device->spec.samples, format); + const int format = (device->spec.channels == 1) ? PSP_AUDIO_FORMAT_MONO : PSP_AUDIO_FORMAT_STEREO; + device->hidden->channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, device->samples_frames, format); } else { // 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11050, 8000 switch (device->spec.freq) { @@ -94,14 +92,14 @@ static int PSPAUDIO_OpenDevice(SDL_AudioDevice *device) /* Allocate the mixing buffer. Its size and starting address must be a multiple of 64 bytes. Our sample count is already a multiple of 64, so spec->size should be a multiple of 64 as well. */ - mixlen = device->buffer_size * NUM_BUFFERS; + const int mixlen = device->buffer_size * NUM_BUFFERS; device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen); if (device->hidden->rawbuf == NULL) { return SDL_SetError("Couldn't allocate mixing buffer"); } SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen); - for (i = 0; i < NUM_BUFFERS; i++) { + for (int i = 0; i < NUM_BUFFERS; i++) { device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size]; } @@ -112,9 +110,9 @@ static void PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, in { if (!isBasicAudioConfig(&device->spec)) { SDL_assert(device->spec.channels == 2); - sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, buffer); + sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, (void *) buffer); } else { - sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, buffer); + sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, (void *) buffer); } } @@ -143,7 +141,7 @@ static void PSPAUDIO_CloseDevice(SDL_AudioDevice *device) } if (device->hidden->rawbuf != NULL) { - SDL_aligned_free(_this->hidden->rawbuf); + SDL_aligned_free(device->hidden->rawbuf); device->hidden->rawbuf = NULL; } SDL_free(device->hidden); @@ -155,7 +153,7 @@ static void PSPAUDIO_ThreadInit(SDL_AudioDevice *device) { /* Increase the priority of this audio thread by 1 to put it ahead of other SDL threads. */ - const SceUID thid = sceKernelGetThreadId() + const SceUID thid = sceKernelGetThreadId(); SceKernelThreadInfo status; status.size = sizeof(SceKernelThreadInfo); if (sceKernelReferThreadStatus(thid, &status) == 0) { From 027b9e8787755c9da6335aacf68c92b6d7d9701f Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 11:26:16 -0400 Subject: [PATCH 111/138] coreaudio: (maybe) patched to compile on iOS. --- src/audio/coreaudio/SDL_coreaudio.m | 67 +++++++++++++++++++---------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m index 1a666af292..5a54396a98 100644 --- a/src/audio/coreaudio/SDL_coreaudio.m +++ b/src/audio/coreaudio/SDL_coreaudio.m @@ -290,32 +290,30 @@ static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioD static SDL_bool session_active = SDL_FALSE; -static void PauseAudioDevices(void) // !!! FIXME: this needs to be updated, and we need a method to access SDL_audio.c's device lists. +static SDL_bool PauseOneAudioDevice(SDL_AudioDevice *device, void *userdata) { - if (!open_devices) { - return; - } - - for (int i = 0; i < num_open_devices; ++i) { - SDL_AudioDevice *device = open_devices[i]; - if (device->hidden->audioQueue && !device->hidden->interrupted) { - AudioQueuePause(device->hidden->audioQueue); - } + if (device->hidden && device->hidden->audioQueue && !device->hidden->interrupted) { + AudioQueuePause(device->hidden->audioQueue); } + return SDL_FALSE; // keep enumerating devices until we've paused them all. } -static void ResumeAudioDevices(void) // !!! FIXME: this needs to be updated, and we need a method to access SDL_audio.c's device lists. +static void PauseAudioDevices(void) { - if (!open_devices) { - return; - } + (void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneAudioDevice, NULL); +} - for (int i = 0; i < num_open_devices; ++i) { - SDL_AudioDevice *device = open_devices[i]; - if (device->hidden->audioQueue && !device->hidden->interrupted) { - AudioQueueStart(device->hidden->audioQueue, NULL); - } +static SDL_bool ResumeOneAudioDevice(SDL_AudioDevice *device, void *userdata) +{ + if (device->hidden && device->hidden->audioQueue && !device->hidden->interrupted) { + AudioQueueStart(device->hidden->audioQueue, NULL); } + return SDL_FALSE; // keep enumerating devices until we've resumed them all. +} + +static void ResumeAudioDevices(void) +{ + (void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneAudioDevice, NULL); } static void InterruptionBegin(SDL_AudioDevice *device) @@ -362,6 +360,25 @@ static void InterruptionEnd(SDL_AudioDevice *device) @end +typedef struct +{ + int output; + int capture; +} CountOpenAudioDevicesData; + +static SDL_bool CountOpenAudioDevices(SDL_AudioDevice *device, void *userdata) +{ + CountOpenAudioDevicesData *data = (CountOpenAudioDevicesData *) userdata; + if (device->hidden != NULL) { // assume it's open if hidden != NULL + if (device->iscapture) { + data->capture++; + } else { + data->output++; + } + } + return SDL_FALSE; // keep enumerating until all devices have been checked. +} + static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_bool allow_playandrecord) { @autoreleasepool { @@ -374,6 +391,10 @@ static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_b NSError *err = nil; const char *hint; + CountOpenAudioDevicesData data; + SDL_zero(data); + (void) SDL_FindPhysicalAudioDeviceByCallback(CountOpenAudioDevices, &data); + hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY); if (hint) { if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) { @@ -391,9 +412,9 @@ static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_b category = AVAudioSessionCategoryPlayAndRecord; } } - } else if (open_playback_devices && open_capture_devices) { + } else if (data.output && data.capture) { category = AVAudioSessionCategoryPlayAndRecord; - } else if (open_capture_devices) { + } else if (data.capture) { category = AVAudioSessionCategoryRecord; } @@ -446,7 +467,7 @@ static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_b } } - if ((open_playback_devices || open_capture_devices) && !session_active) { + if ((data.output || data.capture) && !session_active) { if (![session setActive:YES error:&err]) { if ([err code] == AVAudioSessionErrorCodeResourceNotAvailable && category == AVAudioSessionCategoryPlayAndRecord) { @@ -459,7 +480,7 @@ static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_b } session_active = SDL_TRUE; ResumeAudioDevices(); - } else if (!open_playback_devices && !open_capture_devices && session_active) { + } else if (!data.output && !data.capture && session_active) { PauseAudioDevices(); [session setActive:NO error:nil]; session_active = SDL_FALSE; From d7cf63db67b9bb61d73e0130a4da5ec5d7cb2e1a Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 11:45:11 -0400 Subject: [PATCH 112/138] ps2audio: Patched to compile. --- src/audio/ps2/SDL_ps2audio.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/audio/ps2/SDL_ps2audio.c b/src/audio/ps2/SDL_ps2audio.c index 22d841d47f..0f16344b73 100644 --- a/src/audio/ps2/SDL_ps2audio.c +++ b/src/audio/ps2/SDL_ps2audio.c @@ -61,7 +61,7 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *device) device->hidden->channel = audsrv_set_format(&format); audsrv_set_volume(MAX_VOLUME); - if (_this->hidden->channel < 0) { + if (device->hidden->channel < 0) { return SDL_SetError("Couldn't reserve hardware channel"); } @@ -110,9 +110,9 @@ static void PS2AUDIO_CloseDevice(SDL_AudioDevice *device) device->hidden->channel = -1; } - if (_this->hidden->rawbuf != NULL) { - SDL_aligned_free(_this->hidden->rawbuf); - _this->hidden->rawbuf = NULL; + if (device->hidden->rawbuf != NULL) { + SDL_aligned_free(device->hidden->rawbuf); + device->hidden->rawbuf =2 NULL; } SDL_free(device->hidden); device->hidden = NULL; From 095ea57f94922b43635b2999787fde3da92d4d06 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 11:46:00 -0400 Subject: [PATCH 113/138] pspaudio: Patched to compile. --- src/audio/psp/SDL_pspaudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/psp/SDL_pspaudio.c b/src/audio/psp/SDL_pspaudio.c index 6c9e0dcbf0..903bc9a504 100644 --- a/src/audio/psp/SDL_pspaudio.c +++ b/src/audio/psp/SDL_pspaudio.c @@ -82,7 +82,7 @@ static int PSPAUDIO_OpenDevice(SDL_AudioDevice *device) device->hidden->channel = sceAudioSRCChReserve(device->sample_frames, device->spec.freq, 2); } - if (_this->hidden->channel < 0) { + if (device->hidden->channel < 0) { return SDL_SetError("Couldn't reserve hardware channel"); } From 455eef4cd979c2ff5bf26c33168775be3f195957 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 11:48:49 -0400 Subject: [PATCH 114/138] audio: Use AtomicAdd for device counts, don't treat as a refcount. --- src/audio/SDL_audio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 9d770b8170..52dc724a86 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -254,7 +254,7 @@ static SDL_AudioDevice *CreatePhysicalAudioDevice(const char *name, SDL_bool isc } device->next = *devices; *devices = device; - SDL_AtomicIncRef(device_count); + SDL_AtomicAdd(device_count, 1); SDL_UnlockRWLock(current_audio.device_list_lock); return device; @@ -373,7 +373,7 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) device->prev = NULL; if (was_live) { - SDL_AtomicDecRef(device->iscapture ? ¤t_audio.capture_device_count : ¤t_audio.output_device_count); + SDL_AtomicAdd(device->iscapture ? ¤t_audio.capture_device_count : ¤t_audio.output_device_count, -1); } SDL_UnlockRWLock(current_audio.device_list_lock); From 2c578bd0d57134e610c6c11705957dc002b0276a Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 13:25:04 -0400 Subject: [PATCH 115/138] qnxaudio: Rewrite for SDL3 audio APIs. I have no way to compile or test this atm, so this will likely need further attention. I ended up cleaning this up a ton and adding missing features, so the code changes are pretty dramatic vs a simple conversion to SDL3...so tread carefully in here. --- src/audio/qnx/SDL_qsa_audio.c | 616 +++++++++++++--------------------- src/audio/qnx/SDL_qsa_audio.h | 21 +- 2 files changed, 232 insertions(+), 405 deletions(-) diff --git a/src/audio/qnx/SDL_qsa_audio.c b/src/audio/qnx/SDL_qsa_audio.c index 92f999fb32..5c60e555cd 100644 --- a/src/audio/qnx/SDL_qsa_audio.c +++ b/src/audio/qnx/SDL_qsa_audio.c @@ -19,13 +19,7 @@ 3. This notice may not be removed or altered from any source distribution. */ -/* - * !!! FIXME: streamline this a little by removing all the - * !!! FIXME: if (capture) {} else {} sections that are identical - * !!! FIXME: except for one flag. - */ - -/* !!! FIXME: can this target support hotplugging? */ +// !!! FIXME: can this target support hotplugging? #include "../../SDL_internal.h" @@ -48,7 +42,7 @@ #include "../SDL_audio_c.h" #include "SDL_qsa_audio.h" -/* default channel communication parameters */ +// default channel communication parameters #define DEFAULT_CPARAMS_RATE 44100 #define DEFAULT_CPARAMS_VOICES 1 @@ -56,32 +50,17 @@ #define DEFAULT_CPARAMS_FRAGS_MIN 1 #define DEFAULT_CPARAMS_FRAGS_MAX 1 -/* List of found devices */ -#define QSA_MAX_DEVICES 32 -#define QSA_MAX_NAME_LENGTH 81+16 /* Hardcoded in QSA, can't be changed */ - -typedef struct _QSA_Device -{ - char name[QSA_MAX_NAME_LENGTH]; /* Long audio device name for SDL */ - int cardno; - int deviceno; -} QSA_Device; - -QSA_Device qsa_playback_device[QSA_MAX_DEVICES]; -uint32_t qsa_playback_devices; - -QSA_Device qsa_capture_device[QSA_MAX_DEVICES]; -uint32_t qsa_capture_devices; +#define QSA_MAX_NAME_LENGTH 81+16 // Hardcoded in QSA, can't be changed static int QSA_SetError(const char *fn, int status) { return SDL_SetError("QSA: %s() failed: %s", fn, snd_strerror(status)); } -/* !!! FIXME: does this need to be here? Does the SDL version not work? */ -static void QSA_ThreadInit(SDL_AudioDevice *_this) +// !!! FIXME: does this need to be here? Does the SDL version not work? +static void QSA_ThreadInit(SDL_AudioDevice *device) { - /* Increase default 10 priority to 25 to avoid jerky sound */ + // Increase default 10 priority to 25 to avoid jerky sound struct sched_param param; if (SchedGet(0, 0, ¶m) != -1) { param.sched_priority = param.sched_curpriority + 15; @@ -89,7 +68,7 @@ static void QSA_ThreadInit(SDL_AudioDevice *_this) } } -/* PCM channel parameters initialize function */ +// PCM channel parameters initialize function static void QSA_InitAudioParams(snd_pcm_channel_params_t * cpars) { SDL_zerop(cpars); @@ -106,209 +85,150 @@ static void QSA_InitAudioParams(snd_pcm_channel_params_t * cpars) cpars->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX; } -/* This function waits until it is possible to write a full sound buffer */ -static void QSA_WaitDevice(SDL_AudioDevice *_this) +// This function waits until it is possible to write a full sound buffer +static void QSA_WaitDevice(SDL_AudioDevice *device) { int result; - /* Setup timeout for playing one fragment equal to 2 seconds */ - /* If timeout occurred than something wrong with hardware or driver */ - /* For example, Vortex 8820 audio driver stucks on second DAC because */ - /* it doesn't exist ! */ - result = SDL_IOReady(_this->hidden->audio_fd, - _this->hidden->iscapture ? SDL_IOR_READ : SDL_IOR_WRITE, + // Setup timeout for playing one fragment equal to 2 seconds + // If timeout occurred than something wrong with hardware or driver + // For example, Vortex 8820 audio driver stucks on second DAC because + // it doesn't exist ! + result = SDL_IOReady(device->hidden->audio_fd, + device->iscapture ? SDL_IOR_READ : SDL_IOR_WRITE, 2 * 1000); switch (result) { case -1: - SDL_SetError("QSA: SDL_IOReady() failed: %s", strerror(errno)); + SDL_SetError("QSA: SDL_IOReady() failed: %s", strerror(errno)); // !!! FIXME: Should we just disconnect the device in this case? break; case 0: - SDL_SetError("QSA: timeout on buffer waiting occurred"); - _this->hidden->timeout_on_wait = 1; + device->hidden->timeout_on_wait = SDL_TRUE; // !!! FIXME: Should we just disconnect the device in this case? break; default: - _this->hidden->timeout_on_wait = 0; + device->hidden->timeout_on_wait = SDL_FALSE; break; } } -static void QSA_PlayDevice(SDL_AudioDevice *_this) +static void QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { - snd_pcm_channel_status_t cstatus; - int written; - int status; - int towrite; - void *pcmbuffer; - - if (!SDL_AtomicGet(&_this->enabled) || !_this->hidden) { + if (SDL_AtomicGet(&device->shutdown) || !device->hidden) { return; } - towrite = _this->spec.size; - pcmbuffer = _this->hidden->pcm_buf; + int towrite = buflen; - /* Write the audio data, checking for EAGAIN (buffer full) and underrun */ - do { - written = - snd_pcm_plugin_write(_this->hidden->audio_handle, pcmbuffer, - towrite); - if (written != towrite) { - /* Check if samples playback got stuck somewhere in hardware or in */ - /* the audio device driver */ - if ((errno == EAGAIN) && (written == 0)) { - if (_this->hidden->timeout_on_wait != 0) { - SDL_SetError("QSA: buffer playback timeout"); - return; + // Write the audio data, checking for EAGAIN (buffer full) and underrun + while ((towrite > 0) && !SDL_AtomicGet(&device->shutdown)); + const int bw = snd_pcm_plugin_write(device->hidden->audio_handle, buffer, towrite); + if (bw != towrite) { + // Check if samples playback got stuck somewhere in hardware or in the audio device driver + if ((errno == EAGAIN) && (bw == 0)) { + if (device->hidden->timeout_on_wait) { + return; // oh well, try again next time. !!! FIXME: Should we just disconnect the device in this case? } } - /* Check for errors or conditions */ + // Check for errors or conditions if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { - /* Let a little CPU time go by and try to write again */ - SDL_Delay(1); + SDL_Delay(1); // Let a little CPU time go by and try to write again - /* if we wrote some data */ - towrite -= written; - pcmbuffer += written * _this->spec.channels; + // if we wrote some data + towrite -= bw; + buffer += bw * device->spec.channels; + continue; + } else if ((errno == EINVAL) || (errno == EIO)) { + snd_pcm_channel_status_t cstatus; + SDL_zero(cstatus); + cstatus.channel = device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK; + + int status = snd_pcm_plugin_status(device->hidden->audio_handle, &cstatus); + if (status < 0) { + QSA_SetError("snd_pcm_plugin_status", status); + return; // !!! FIXME: disconnect the device? + } else if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY)) { + status = snd_pcm_plugin_prepare(device->hidden->audio_handle, device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK); + if (status < 0) { + QSA_SetError("snd_pcm_plugin_prepare", status); + return; // !!! FIXME: disconnect the device? + } + } continue; } else { - if ((errno == EINVAL) || (errno == EIO)) { - SDL_zero(cstatus); - if (!_this->hidden->iscapture) { - cstatus.channel = SND_PCM_CHANNEL_PLAYBACK; - } else { - cstatus.channel = SND_PCM_CHANNEL_CAPTURE; - } - - status = - snd_pcm_plugin_status(_this->hidden->audio_handle, - &cstatus); - if (status < 0) { - QSA_SetError("snd_pcm_plugin_status", status); - return; - } - - if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || - (cstatus.status == SND_PCM_STATUS_READY)) { - if (!_this->hidden->iscapture) { - status = - snd_pcm_plugin_prepare(_this->hidden-> - audio_handle, - SND_PCM_CHANNEL_PLAYBACK); - } else { - status = - snd_pcm_plugin_prepare(_this->hidden-> - audio_handle, - SND_PCM_CHANNEL_CAPTURE); - } - if (status < 0) { - QSA_SetError("snd_pcm_plugin_prepare", status); - return; - } - } - continue; - } else { - return; - } + return; // !!! FIXME: disconnect the device? } } else { - /* we wrote all remaining data */ - towrite -= written; - pcmbuffer += written * _this->spec.channels; + // we wrote all remaining data + towrite -= bw; + buffer += bw * device->spec.channels; } - } while ((towrite > 0) && SDL_AtomicGet(&_this->enabled)); + } - /* If we couldn't write, assume fatal error for now */ + // If we couldn't write, assume fatal error for now if (towrite != 0) { - SDL_OpenedAudioDeviceDisconnected(_this); + SDL_AudioDeviceDisconnected(device); } } -static Uint8 *QSA_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *QSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { - return _this->hidden->pcm_buf; + return device->hidden->pcm_buf; } -static void QSA_CloseDevice(SDL_AudioDevice *_this) +static void QSA_CloseDevice(SDL_AudioDevice *device) { - if (_this->hidden->audio_handle != NULL) { -#if _NTO_VERSION < 710 - if (!_this->hidden->iscapture) { - /* Finish playing available samples */ - snd_pcm_plugin_flush(_this->hidden->audio_handle, - SND_PCM_CHANNEL_PLAYBACK); - } else { - /* Cancel unread samples during capture */ - snd_pcm_plugin_flush(_this->hidden->audio_handle, - SND_PCM_CHANNEL_CAPTURE); + if (device->hidden) { + if (device->hidden->audio_handle != NULL) { + #if _NTO_VERSION < 710 + // Finish playing available samples or cancel unread samples during capture + snd_pcm_plugin_flush(device->hidden->audio_handle, device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK); + #endif + snd_pcm_close(device->hidden->audio_handle); } -#endif - snd_pcm_close(_this->hidden->audio_handle); - } - SDL_free(_this->hidden->pcm_buf); - SDL_free(_this->hidden); + SDL_free(device->hidden->pcm_buf); + SDL_free(device->hidden); + device->hidden = NULL; + } } -static int QSA_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int QSA_OpenDevice(SDL_AudioDevice *device) { -#if 0 - /* !!! FIXME: SDL2 used to pass this handle. What's the alternative? */ - const QSA_Device *device = (const QSA_Device *) handle; -#else - const QSA_Device *device = NULL; -#endif - int status = 0; - int format = 0; - SDL_AudioFormat test_format = 0; - const SDL_AudioFormat *closefmts; - snd_pcm_channel_setup_t csetup; - snd_pcm_channel_params_t cparams; - SDL_bool iscapture = _this->iscapture; + if (device->iscapture) { + return SDL_SetError("SDL capture support isn't available on QNX atm"); // !!! FIXME: most of this code has support for capture devices, but there's no CaptureFromDevice, etc functions. Fill them in! + } - /* Initialize all variables that we clean on shutdown */ - _this->hidden = - (struct SDL_PrivateAudioData *) SDL_calloc(1, - (sizeof - (struct - SDL_PrivateAudioData))); - if (_this->hidden == NULL) { + SDL_assert(device->handle != NULL); // NULL used to mean "system default device" in SDL2; it does not mean that in SDL3. + const Uint32 sdlhandle = (Uint32) ((size_t) device->handle); + const uint32_t cardno = (uint32_t) (sdlhandle & 0xFFFF); + const uint32_t deviceno = (uint32_t) ((sdlhandle >> 16) & 0xFFFF); + const SDL_bool iscapture = device->iscapture; + int status = 0; + + // Initialize all variables that we clean on shutdown + device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, (sizeof (struct SDL_PrivateAudioData))); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - /* Initialize channel transfer parameters to default */ + // Initialize channel transfer parameters to default + snd_pcm_channel_params_t cparams; QSA_InitAudioParams(&cparams); - /* Initialize channel direction: capture or playback */ - _this->hidden->iscapture = iscapture ? SDL_TRUE : SDL_FALSE; - - if (device != NULL) { - /* Open requested audio device */ - _this->hidden->deviceno = device->deviceno; - _this->hidden->cardno = device->cardno; - status = snd_pcm_open(&_this->hidden->audio_handle, - device->cardno, device->deviceno, - iscapture ? SND_PCM_OPEN_CAPTURE : SND_PCM_OPEN_PLAYBACK); - } else { - /* Open system default audio device */ - status = snd_pcm_open_preferred(&_this->hidden->audio_handle, - &_this->hidden->cardno, - &_this->hidden->deviceno, - iscapture ? SND_PCM_OPEN_CAPTURE : SND_PCM_OPEN_PLAYBACK); - } - - /* Check if requested device is opened */ + // Open requested audio device + status = snd_pcm_open(&device->hidden->audio_handle, cardno, deviceno, iscapture ? SND_PCM_OPEN_CAPTURE : SND_PCM_OPEN_PLAYBACK); if (status < 0) { - _this->hidden->audio_handle = NULL; + device->hidden->audio_handle = NULL; return QSA_SetError("snd_pcm_open", status); } - /* Try for a closest match on audio format */ - closefmts = SDL_ClosestAudioFormats(_this->spec.format); + // Try for a closest match on audio format + SDL_AudioFormat test_format = 0; + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { - /* if match found set format to equivalent QSA format */ + // if match found set format to equivalent QSA format switch (test_format) { - #define CHECKFMT(sdlfmt, qsafmt) case SDL_AUDIO_##sdlfmt: format = SND_PCM_SFMT_##qsafmt; break + #define CHECKFMT(sdlfmt, qsafmt) case SDL_AUDIO_##sdlfmt: cparams.format.format = SND_PCM_SFMT_##qsafmt; break CHECKFMT(U8, U8); CHECKFMT(S8, S8); CHECKFMT(S16LSB, S16_LE); @@ -323,267 +243,192 @@ static int QSA_OpenDevice(SDL_AudioDevice *_this, const char *devname) break; } - /* assumes test_format not 0 on success */ + // assumes test_format not 0 on success if (test_format == 0) { return SDL_SetError("QSA: Couldn't find any hardware audio formats"); } - _this->spec.format = test_format; + device->spec.format = test_format; - /* Set the audio format */ - cparams.format.format = format; + // Set mono/stereo/4ch/6ch/8ch audio + cparams.format.voices = device->spec.channels; - /* Set mono/stereo/4ch/6ch/8ch audio */ - cparams.format.voices = _this->spec.channels; + // Set rate + cparams.format.rate = device->spec.freq; - /* Set rate */ - cparams.format.rate = _this->spec.freq; - - /* Setup the transfer parameters according to cparams */ - status = snd_pcm_plugin_params(_this->hidden->audio_handle, &cparams); + // Setup the transfer parameters according to cparams + status = snd_pcm_plugin_params(device->hidden->audio_handle, &cparams); if (status < 0) { return QSA_SetError("snd_pcm_plugin_params", status); } - /* Make sure channel is setup right one last time */ + // Make sure channel is setup right one last time + snd_pcm_channel_setup_t csetup; SDL_zero(csetup); - if (!_this->hidden->iscapture) { - csetup.channel = SND_PCM_CHANNEL_PLAYBACK; - } else { - csetup.channel = SND_PCM_CHANNEL_CAPTURE; - } - - /* Setup an audio channel */ - if (snd_pcm_plugin_setup(_this->hidden->audio_handle, &csetup) < 0) { + csetup.channel = iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK; + if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) { return SDL_SetError("QSA: Unable to setup channel"); } - /* Calculate the final parameters for this audio specification */ - SDL_CalculateAudioSpec(&_this->spec); + device->sample_frames = csetup.buf.block.frag_size; - _this->hidden->pcm_len = _this->spec.size; + // Calculate the final parameters for this audio specification + SDL_UpdatedAudioDeviceFormat(device); - if (_this->hidden->pcm_len == 0) { - _this->hidden->pcm_len = - csetup.buf.block.frag_size * _this->spec.channels * - (snd_pcm_format_width(format) / 8); - } - - /* - * Allocate memory to the audio buffer and initialize with silence - * (Note that buffer size must be a multiple of fragment size, so find - * closest multiple) - */ - _this->hidden->pcm_buf = - (Uint8 *) SDL_malloc(_this->hidden->pcm_len); - if (_this->hidden->pcm_buf == NULL) { + device->hidden->pcm_buf = (Uint8 *) SDL_malloc(device->buffer_size); + if (device->hidden->pcm_buf == NULL) { return SDL_OutOfMemory(); } - SDL_memset(_this->hidden->pcm_buf, _this->spec.silence, - _this->hidden->pcm_len); + SDL_memset(device->hidden->pcm_buf, device->silence_value, device->buffer_size); - /* get the file descriptor */ - if (!_this->hidden->iscapture) { - _this->hidden->audio_fd = - snd_pcm_file_descriptor(_this->hidden->audio_handle, - SND_PCM_CHANNEL_PLAYBACK); - } else { - _this->hidden->audio_fd = - snd_pcm_file_descriptor(_this->hidden->audio_handle, - SND_PCM_CHANNEL_CAPTURE); - } - - if (_this->hidden->audio_fd < 0) { - return QSA_SetError("snd_pcm_file_descriptor", status); - } - - /* Prepare an audio channel */ - if (!_this->hidden->iscapture) { - /* Prepare audio playback */ - status = - snd_pcm_plugin_prepare(_this->hidden->audio_handle, - SND_PCM_CHANNEL_PLAYBACK); - } else { - /* Prepare audio capture */ - status = - snd_pcm_plugin_prepare(_this->hidden->audio_handle, - SND_PCM_CHANNEL_CAPTURE); + // get the file descriptor + device->hidden->audio_fd = snd_pcm_file_descriptor(device->hidden->audio_handle, csetup.channel); + if (device->hidden->audio_fd < 0) { + return QSA_SetError("snd_pcm_file_descriptor", device->hidden->audio_fd); } + // Prepare an audio channel + status = snd_pcm_plugin_prepare(device->hidden->audio_handle, csetup.channel) if (status < 0) { return QSA_SetError("snd_pcm_plugin_prepare", status); } - /* We're really ready to rock and roll. :-) */ - return 0; + return 0; // We're really ready to rock and roll. :-) } -static void QSA_DetectDevices(void) +static SDL_AudioFormat QnxFormatToSDLFormat(const int32_t qnxfmt) { - uint32_t it; - uint32_t cards; - uint32_t devices; - int32_t status; + switch (qnxfmt) { + #define CHECKFMT(sdlfmt, qsafmt) case SND_PCM_SFMT_##qsafmt: return SDL_AUDIO_##sdlfmt + CHECKFMT(U8, U8); + CHECKFMT(S8, S8); + CHECKFMT(S16LSB, S16_LE); + CHECKFMT(S16MSB, S16_BE); + CHECKFMT(S32LSB, S32_LE); + CHECKFMT(S32MSB, S32_BE); + CHECKFMT(F32LSB, FLOAT_LE); + CHECKFMT(F32MSB, FLOAT_BE); + #undef CHECKFMT + default: break; + } + return SDL_AUDIO_S16SYS; // oh well. +} - /* Detect amount of available devices */ - /* this value can be changed in the runtime */ - cards = snd_cards(); +static void QSA_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) +{ + // Detect amount of available devices + // this value can be changed in the runtime + int num_cards = 0; + (void) snd_cards_list(NULL, 0, &alloc_num_cards); + SDL_bool isstack = SDL_FALSE; + int *cards = SDL_small_alloc(int, num_cards, &isstack); + if (!cards) { + return; // we're in trouble. + } + int overflow_cards = 0; + const int total_num_cards = snd_cards_list(cards, num_cards, &overflow_cards); + // if overflow_cards > 0 or total_num_cards > num_cards, it changed at the last moment; oh well, we lost some. + num_cards = SDL_min(num_cards, total_num_cards); // ...but make sure it didn't _shrink_. - /* If io-audio manager is not running we will get 0 as number */ - /* of available audio devices */ - if (cards == 0) { - /* We have no any available audio devices */ + // If io-audio manager is not running we will get 0 as number of available audio devices + if (num_cards == 0) { // not any available audio devices? + SDL_small_free(cards, isstack); return; } - /* !!! FIXME: code duplication */ - /* Find requested devices by type */ - { /* output devices */ - /* Playback devices enumeration requested */ - for (it = 0; it < cards; it++) { - devices = 0; - do { - status = - snd_card_get_longname(it, - qsa_playback_device - [qsa_playback_devices].name, - QSA_MAX_NAME_LENGTH); - if (status == EOK) { - snd_pcm_t *handle; + // Find requested devices by type + for (int it = 0; it < num_cards; it++) { + const int card = cards[it]; + for (uint32_t deviceno = 0; ; deviceno++) { + int32_t status; + char name[QSA_MAX_NAME_LENGTH]; - /* Add device number to device name */ - sprintf(qsa_playback_device[qsa_playback_devices].name + - SDL_strlen(qsa_playback_device - [qsa_playback_devices].name), " d%d", - devices); + status = snd_card_get_longname(card, name, sizeof (name)); + if (status == EOK) { + snd_pcm_t *handle; - /* Store associated card number id */ - qsa_playback_device[qsa_playback_devices].cardno = it; + // Add device number to device name + char fullname[QSA_MAX_NAME_LENGTH + 32]; + SDL_snprintf(fullname, sizeof (fullname), "%s d%d", name, (int) deviceno); - /* Check if this device id could play anything */ - status = - snd_pcm_open(&handle, it, devices, - SND_PCM_OPEN_PLAYBACK); + // Check if this device id could play anything + SDL_bool iscapture = SDL_FALSE; + status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_PLAYBACK); + if (status != EOK) { // no? See if it's a capture device instead. + #if 0 // !!! FIXME: most of this code has support for capture devices, but there's no CaptureFromDevice, etc functions. Fill them in! + status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_CAPTURE); if (status == EOK) { - qsa_playback_device[qsa_playback_devices].deviceno = - devices; - status = snd_pcm_close(handle); - if (status == EOK) { - /* Note that spec is NULL, because we are required to open the device before - * acquiring the mix format, making this information inaccessible at - * enumeration time - */ - SDL_AddAudioDevice(SDL_FALSE, qsa_playback_device[qsa_playback_devices].name, NULL, &qsa_playback_device[qsa_playback_devices]); - qsa_playback_devices++; - } + iscapture = SDL_TRUE; + } + #endif + } + + if (status == EOK) { + SDL_AudioSpec spec; + SDL_AudioSpec *pspec = &spec; + snd_pcm_channel_setup_t csetup; + SDL_zero(csetup); + csetup.channel = iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK; + + if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) { + pspec = NULL; // go on without spec info. } else { - /* Check if we got end of devices list */ - if (status == -ENOENT) { - break; - } + spec.format = QnxFormatToSDLFormat(csetup.format.format); + spec.channels = csetup.format.channels; + spec.freq = csetup.format.rate; + } + + status = snd_pcm_close(handle); + if (status == EOK) { + // !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not. + SDL_assert(card <= 0xFFFF); + SDL_assert(deviceno <= 0xFFFF); + const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16); + SDL_AddAudioDevice(iscapture, fullname, pspec, (void *) ((size_t) sdlhandle)); } } else { - break; + // Check if we got end of devices list + if (status == -ENOENT) { + break; + } } - - /* Check if we reached maximum devices count */ - if (qsa_playback_devices >= QSA_MAX_DEVICES) { - break; - } - devices++; - } while (1); - - /* Check if we reached maximum devices count */ - if (qsa_playback_devices >= QSA_MAX_DEVICES) { + } else { break; } } } - { /* capture devices */ - /* Capture devices enumeration requested */ - for (it = 0; it < cards; it++) { - devices = 0; - do { - status = - snd_card_get_longname(it, - qsa_capture_device - [qsa_capture_devices].name, - QSA_MAX_NAME_LENGTH); - if (status == EOK) { - snd_pcm_t *handle; + SDL_small_free(cards, isstack); - /* Add device number to device name */ - sprintf(qsa_capture_device[qsa_capture_devices].name + - SDL_strlen(qsa_capture_device - [qsa_capture_devices].name), " d%d", - devices); + // Try to open the "preferred" devices, which will tell us the card/device pairs for the default devices. + snd_pcm_t handle; + int cardno, deviceno; + if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_PLAYBACK) == 0) { + snd_pcm_close(handle); + // !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not. + SDL_assert(cardno <= 0xFFFF); + SDL_assert(deviceno <= 0xFFFF); + const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16); + *default_output = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle)); + } - /* Store associated card number id */ - qsa_capture_device[qsa_capture_devices].cardno = it; - - /* Check if this device id could play anything */ - status = - snd_pcm_open(&handle, it, devices, - SND_PCM_OPEN_CAPTURE); - if (status == EOK) { - qsa_capture_device[qsa_capture_devices].deviceno = - devices; - status = snd_pcm_close(handle); - if (status == EOK) { - /* Note that spec is NULL, because we are required to open the device before - * acquiring the mix format, making this information inaccessible at - * enumeration time - */ - SDL_AddAudioDevice(SDL_TRUE, qsa_capture_device[qsa_capture_devices].name, NULL, &qsa_capture_device[qsa_capture_devices]); - qsa_capture_devices++; - } - } else { - /* Check if we got end of devices list */ - if (status == -ENOENT) { - break; - } - } - - /* Check if we reached maximum devices count */ - if (qsa_capture_devices >= QSA_MAX_DEVICES) { - break; - } - } else { - break; - } - devices++; - } while (1); - - /* Check if we reached maximum devices count */ - if (qsa_capture_devices >= QSA_MAX_DEVICES) { - break; - } - } + if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_CAPTURE) == 0) { + snd_pcm_close(handle); + // !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not. + SDL_assert(cardno <= 0xFFFF); + SDL_assert(deviceno <= 0xFFFF); + const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16); + *default_capture = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle)); } } static void QSA_Deinitialize(void) { - /* Clear devices array on shutdown */ - /* !!! FIXME: we zero these on init...any reason to do it here? */ - SDL_zeroa(qsa_playback_device); - SDL_zeroa(qsa_capture_device); - qsa_playback_devices = 0; - qsa_capture_devices = 0; + // nothing to do here atm. } static SDL_bool QSA_Init(SDL_AudioDriverImpl * impl) { - /* Clear devices array */ - SDL_zeroa(qsa_playback_device); - SDL_zeroa(qsa_capture_device); - qsa_playback_devices = 0; - qsa_capture_devices = 0; - - /* Set function pointers */ - /* DeviceLock and DeviceUnlock functions are used default, */ - /* provided by SDL, which uses pthread_mutex for lock/unlock */ impl->DetectDevices = QSA_DetectDevices; impl->OpenDevice = QSA_OpenDevice; impl->ThreadInit = QSA_ThreadInit; @@ -592,21 +437,16 @@ static SDL_bool QSA_Init(SDL_AudioDriverImpl * impl) impl->GetDeviceBuf = QSA_GetDeviceBuf; impl->CloseDevice = QSA_CloseDevice; impl->Deinitialize = QSA_Deinitialize; - impl->LockDevice = NULL; - impl->UnlockDevice = NULL; - impl->ProvidesOwnCallbackThread = 0; - impl->HasCaptureSupport = 1; - impl->OnlyHasDefaultOutputDevice = 0; - impl->OnlyHasDefaultCaptureDevice = 0; + // !!! FIXME: most of this code has support for capture devices, but there's no CaptureFromDevice, etc functions. Fill them in! + //impl->HasCaptureSupport = SDL_TRUE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap QSAAUDIO_bootstrap = { "qsa", "QNX QSA Audio", QSA_Init, 0 }; -#endif /* SDL_AUDIO_DRIVER_QNX */ +#endif // SDL_AUDIO_DRIVER_QNX -/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/audio/qnx/SDL_qsa_audio.h b/src/audio/qnx/SDL_qsa_audio.h index da74db9c5c..29dba24fd4 100644 --- a/src/audio/qnx/SDL_qsa_audio.h +++ b/src/audio/qnx/SDL_qsa_audio.h @@ -30,23 +30,10 @@ struct SDL_PrivateAudioData { - /* SDL capture state */ - SDL_bool iscapture; - - /* The audio device handle */ - int cardno; - int deviceno; - snd_pcm_t *audio_handle; - - /* The audio file descriptor */ - int audio_fd; - - /* Select timeout status */ - uint32_t timeout_on_wait; - - /* Raw mixing buffer */ - Uint8 *pcm_buf; - Uint32 pcm_len; + snd_pcm_t *audio_handle; // The audio device handle + int audio_fd; // The audio file descriptor, for selecting on + SDL_bool timeout_on_wait; // Select timeout status + Uint8 *pcm_buf; // Raw mixing buffer }; #endif /* __SDL_QSA_AUDIO_H__ */ From 9a2a0a146311e93a5dcbfe1b36ce82783cd0a72f Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 13:27:51 -0400 Subject: [PATCH 116/138] ps2audio: Delete errant character that got inserted before previous commit. --- src/audio/ps2/SDL_ps2audio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/ps2/SDL_ps2audio.c b/src/audio/ps2/SDL_ps2audio.c index 0f16344b73..e50a497dff 100644 --- a/src/audio/ps2/SDL_ps2audio.c +++ b/src/audio/ps2/SDL_ps2audio.c @@ -112,7 +112,7 @@ static void PS2AUDIO_CloseDevice(SDL_AudioDevice *device) if (device->hidden->rawbuf != NULL) { SDL_aligned_free(device->hidden->rawbuf); - device->hidden->rawbuf =2 NULL; + device->hidden->rawbuf = NULL; } SDL_free(device->hidden); device->hidden = NULL; From 4aa95c21bcf576dc4403d3982830f788aacb348d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 13:32:44 -0400 Subject: [PATCH 117/138] pspaudio: Patched to compile. --- src/audio/psp/SDL_pspaudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/psp/SDL_pspaudio.c b/src/audio/psp/SDL_pspaudio.c index 903bc9a504..b5af4e8813 100644 --- a/src/audio/psp/SDL_pspaudio.c +++ b/src/audio/psp/SDL_pspaudio.c @@ -58,7 +58,7 @@ static int PSPAUDIO_OpenDevice(SDL_AudioDevice *device) // The number of channels (1 or 2). device->spec.channels = device->spec.channels == 1 ? 1 : 2; const int format = (device->spec.channels == 1) ? PSP_AUDIO_FORMAT_MONO : PSP_AUDIO_FORMAT_STEREO; - device->hidden->channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, device->samples_frames, format); + device->hidden->channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, device->sample_frames, format); } else { // 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11050, 8000 switch (device->spec.freq) { From 29a0c689c9ca9f8f17c14d44dac5bdcbd817dd11 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 13:37:27 -0400 Subject: [PATCH 118/138] wasapi: Patched to compile with Clang. --- src/audio/wasapi/SDL_wasapi.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c index 2f2ad8ff3a..c76bc93805 100644 --- a/src/audio/wasapi/SDL_wasapi.c +++ b/src/audio/wasapi/SDL_wasapi.c @@ -72,11 +72,11 @@ static void ManagementThreadMainloop(void) { SDL_LockMutex(ManagementThreadLock); ManagementThreadPendingTask *task; - while (((task = SDL_AtomicGetPtr(&ManagementThreadPendingTasks)) != NULL) || !SDL_AtomicGet(&ManagementThreadShutdown)) { + while (((task = SDL_AtomicGetPtr((void **) &ManagementThreadPendingTasks)) != NULL) || !SDL_AtomicGet(&ManagementThreadShutdown)) { if (!task) { SDL_WaitCondition(ManagementThreadCondition, ManagementThreadLock); // block until there's something to do. } else { - SDL_AtomicSetPtr(&ManagementThreadPendingTasks, task->next); // take task off the pending list. + SDL_AtomicSetPtr((void **) &ManagementThreadPendingTasks, task->next); // take task off the pending list. SDL_UnlockMutex(ManagementThreadLock); // let other things add to the list while we chew on this task. task->result = task->fn(task->userdata); // run this task. if (task->task_complete_sem) { // something waiting on result? @@ -132,7 +132,7 @@ int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, in if (prev != NULL) { prev->next = pending; } else { - SDL_AtomicSetPtr(&ManagementThreadPendingTasks, pending); + SDL_AtomicSetPtr((void **) &ManagementThreadPendingTasks, pending); } // task is added to the end of the pending list, let management thread rip! @@ -208,7 +208,7 @@ static int InitManagementThread(void) return -1; } - SDL_AtomicSetPtr(&ManagementThreadPendingTasks, NULL); + SDL_AtomicSetPtr((void **) &ManagementThreadPendingTasks, NULL); SDL_AtomicSet(&ManagementThreadShutdown, 0); ManagementThread = SDL_CreateThreadInternal(ManagementThreadEntry, "SDLWASAPIMgmt", 256 * 1024, &mgmtdata); // !!! FIXME: maybe even smaller stack size? if (!ManagementThread) { @@ -240,7 +240,7 @@ static void DeinitManagementThread(void) ManagementThread = NULL; } - SDL_assert(SDL_AtomicGetPtr(&ManagementThreadPendingTasks) == NULL); + SDL_assert(SDL_AtomicGetPtr((void **) &ManagementThreadPendingTasks) == NULL); SDL_DestroyCondition(ManagementThreadCondition); SDL_DestroyMutex(ManagementThreadLock); From 7f4488f625a5e0defd16bc45a37ea5fb19449de9 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 14:49:55 -0400 Subject: [PATCH 119/138] wasapi: More fixes for Clang warnings. --- src/audio/wasapi/SDL_wasapi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c index c76bc93805..2041bc88b4 100644 --- a/src/audio/wasapi/SDL_wasapi.c +++ b/src/audio/wasapi/SDL_wasapi.c @@ -125,7 +125,7 @@ int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, in // add to end of task list. ManagementThreadPendingTask *prev = NULL; - for (ManagementThreadPendingTask *i = SDL_AtomicGetPtr(&ManagementThreadPendingTasks); i != NULL; i = i->next) { + for (ManagementThreadPendingTask *i = SDL_AtomicGetPtr((void **) &ManagementThreadPendingTasks); i != NULL; i = i->next) { prev = i; } @@ -658,7 +658,7 @@ static int mgmtthrtask_PrepDevice(void *userdata) // This is called once a device is activated, possibly asynchronously. int WASAPI_PrepDevice(SDL_AudioDevice *device) { - int rc; + int rc = 0; return (WASAPI_ProxyToManagementThread(mgmtthrtask_PrepDevice, device, &rc) < 0) ? -1 : rc; } From 5e820906623f7cd377fcf261c388fe418e7fbf43 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 16:08:55 -0400 Subject: [PATCH 120/138] testautomation_audio.c: Apparently we aren't updating test code for C99 atm. --- test/testautomation_audio.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/testautomation_audio.c b/test/testautomation_audio.c index 2c07ab978a..a59bbb206d 100644 --- a/test/testautomation_audio.c +++ b/test/testautomation_audio.c @@ -226,6 +226,8 @@ static int audio_initOpenCloseQuitAudio(void *arg) */ static int audio_pauseUnpauseAudio(void *arg) { + int iMax; + int i, k, j; int result; const char *audioDriver; SDL_AudioSpec desired; @@ -235,17 +237,17 @@ static int audio_pauseUnpauseAudio(void *arg) SDLTest_AssertPass("Call to SDL_QuitSubSystem(SDL_INIT_AUDIO)"); /* Loop over all available audio drivers */ - const int iMax = SDL_GetNumAudioDrivers(); + iMax = SDL_GetNumAudioDrivers(); SDLTest_AssertPass("Call to SDL_GetNumAudioDrivers()"); SDLTest_AssertCheck(iMax > 0, "Validate number of audio drivers; expected: >0 got: %d", iMax); - for (int i = 0; i < iMax; i++) { + for (i = 0; i < iMax; i++) { audioDriver = SDL_GetAudioDriver(i); SDLTest_AssertPass("Call to SDL_GetAudioDriver(%d)", i); SDLTest_Assert(audioDriver != NULL, "Audio driver name is not NULL"); SDLTest_AssertCheck(audioDriver[0] != '\0', "Audio driver name is not empty; got: %s", audioDriver); /* NOLINT(clang-analyzer-core.NullDereference): Checked for NULL above */ /* Change specs */ - for (int j = 0; j < 2; j++) { + for (j = 0; j < 2; j++) { /* Call Init */ SDL_SetHint("SDL_AUDIO_DRIVER", audioDriver); @@ -302,7 +304,7 @@ static int audio_pauseUnpauseAudio(void *arg) SDLTest_AssertCheck(g_audio_testCallbackLength > 0, "Verify callback length; expected: >0 got: %d", g_audio_testCallbackLength); /* Pause audio to stop playing (maybe multiple times) */ - for (int k = 0; k <= j; k++) { + for (k = 0; k <= j; k++) { const int pause_on = (k == 0) ? 1 : SDLTest_RandomIntegerInRange(99, 9999); if (pause_on) { SDL_PauseAudioDevice(g_audio_id); From 54af687210cf08b7b81b0888f9eca61034f232a1 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 22 Jul 2023 16:49:06 -0400 Subject: [PATCH 121/138] testautomation_audio.c: Patched to compile. :/ --- test/testautomation_audio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/testautomation_audio.c b/test/testautomation_audio.c index a59bbb206d..d7a7c65982 100644 --- a/test/testautomation_audio.c +++ b/test/testautomation_audio.c @@ -227,7 +227,7 @@ static int audio_initOpenCloseQuitAudio(void *arg) static int audio_pauseUnpauseAudio(void *arg) { int iMax; - int i, k, j; + int i, j /*, k, l*/; int result; const char *audioDriver; SDL_AudioSpec desired; @@ -281,7 +281,7 @@ static int audio_pauseUnpauseAudio(void *arg) #if 0 /* !!! FIXME: maybe update this? */ /* Start and stop audio multiple times */ - for (int l = 0; l < 3; l++) { + for (l = 0; l < 3; l++) { SDLTest_Log("Pause/Unpause iteration: %d", l + 1); /* Reset callback counters */ From 5ff87c6d4ae89b32b691e3a7083e9b834774c013 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 26 Jul 2023 15:45:40 -0400 Subject: [PATCH 122/138] android: Reworked audio backends for SDL3 audio API. This involved moving an `#ifdef` out of SDL_audio.c for thread priority, so the default ThreadInit now does the usual stuff for non-Android platforms, the Android platforms provide an implementatin of ThreadInit with their side of the `#ifdef` and other platforms that implement ThreadInit incorporated the appropriate code...which is why WASAPI is touched in here. The Android bits compile, but have not been tested, and there was some reworkings in the Java bits, so this might need some further fixes still. --- .../java/org/libsdl/app/SDLAudioManager.java | 62 +-- src/audio/SDL_audio.c | 26 +- src/audio/aaudio/SDL_aaudio.c | 479 +++++++----------- src/audio/aaudio/SDL_aaudiofuncs.h | 4 +- src/audio/android/SDL_androidaudio.c | 152 +++--- src/audio/openslES/SDL_openslES.c | 352 +++++++------ src/audio/wasapi/SDL_wasapi_win32.c | 3 + src/audio/wasapi/SDL_wasapi_winrt.cpp | 1 + src/core/android/SDL_android.c | 117 ++--- src/core/android/SDL_android.h | 9 +- 10 files changed, 485 insertions(+), 720 deletions(-) diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java b/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java index 2a6751e47c..e64a0790d4 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java @@ -21,8 +21,6 @@ public class SDLAudioManager { protected static AudioRecord mAudioRecord; protected static Context mContext; - private static final int[] NO_DEVICES = {}; - private static AudioDeviceCallback mAudioDeviceCallback; public static void initialize() { @@ -36,7 +34,7 @@ public class SDLAudioManager { @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { for (AudioDeviceInfo deviceInfo : addedDevices) { - addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()); + addAudioDevice(deviceInfo.isSink(), deviceInfo.getProductName().toString(), deviceInfo.getId()); } } @@ -52,13 +50,10 @@ public class SDLAudioManager { public static void setContext(Context context) { mContext = context; - if (context != null) { - registerAudioDeviceCallback(); - } } public static void release(Context context) { - unregisterAudioDeviceCallback(context); + // no-op atm } // Audio @@ -311,65 +306,20 @@ public class SDLAudioManager { return null; } - private static void registerAudioDeviceCallback() { + public static void registerAudioDeviceCallback() { if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null); } } - private static void unregisterAudioDeviceCallback(Context context) { + public static void unregisterAudioDeviceCallback() { if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { - AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback); } } - private static int[] ArrayListToArray(ArrayList integers) - { - int[] ret = new int[integers.size()]; - for (int i=0; i < ret.length; i++) { - ret[i] = integers.get(i).intValue(); - } - return ret; - } - - /** - * This method is called by SDL using JNI. - */ - public static int[] getAudioOutputDevices() { - if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { - AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - ArrayList arrlist = new ArrayList(); - for (AudioDeviceInfo dev : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) { - /* Device cannot be opened */ - if (dev.getType() == AudioDeviceInfo.TYPE_TELEPHONY) { - continue; - } - arrlist.add(dev.getId()); - } - return ArrayListToArray(arrlist); - } else { - return NO_DEVICES; - } - } - - /** - * This method is called by SDL using JNI. - */ - public static int[] getAudioInputDevices() { - if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { - AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - ArrayList arrlist = new ArrayList(); - for (AudioDeviceInfo dev : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) { - arrlist.add(dev.getId()); - } - return ArrayListToArray(arrlist); - } else { - return NO_DEVICES; - } - } - /** * This method is called by SDL using JNI. */ @@ -535,6 +485,6 @@ public class SDLAudioManager { public static native void removeAudioDevice(boolean isCapture, int deviceId); - public static native void addAudioDevice(boolean isCapture, int deviceId); + public static native void addAudioDevice(boolean isCapture, String name, int deviceId); } diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 52dc724a86..dd1d2af1b4 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -25,8 +25,6 @@ #include "../thread/SDL_systhread.h" #include "../SDL_utils_c.h" -extern void Android_JNI_AudioSetThreadPriority(int, int); // we need this on Android in the audio device threads. - // Available audio drivers static const AudioBootStrap *const bootstrap[] = { #ifdef SDL_AUDIO_DRIVER_PULSEAUDIO @@ -412,7 +410,6 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device) // stubs for audio drivers that don't need a specific entry point... -static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { /* no-op. */ } static void SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { /* no-op. */ } @@ -422,6 +419,11 @@ static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ } static void SDL_AudioFreeDeviceHandle_Default(SDL_AudioDevice *device) { /* no-op. */ } +static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device) +{ + SDL_SetThreadPriority(device->iscapture ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL); +} + static void SDL_AudioDetectDevices_Default(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { // you have to write your own implementation if these assertions fail. @@ -679,15 +681,6 @@ void SDL_AudioThreadFinalize(SDL_AudioDevice *device) void SDL_OutputAudioThreadSetup(SDL_AudioDevice *device) { SDL_assert(!device->iscapture); - - // The audio mixing is always a high priority thread -#ifdef SDL_AUDIO_DRIVER_ANDROID - Android_JNI_AudioSetThreadPriority(SDL_FALSE, device->id); -#else - SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL); -#endif - - // Perform any thread setup current_audio.impl.ThreadInit(device); } @@ -781,14 +774,6 @@ static int SDLCALL OutputAudioThread(void *devicep) // thread entry point void SDL_CaptureAudioThreadSetup(SDL_AudioDevice *device) { SDL_assert(device->iscapture); - - // Audio capture is always a high priority thread (!!! FIXME: _should_ it be?) -#ifdef SDL_AUDIO_DRIVER_ANDROID - Android_JNI_AudioSetThreadPriority(SDL_TRUE, device->id); -#else - SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); -#endif - current_audio.impl.ThreadInit(device); } @@ -1068,7 +1053,6 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec) if ((devid == 0) && is_default) { return SDL_SetError("No default audio device available"); - return 0; } SDL_AudioDevice *device = ObtainPhysicalAudioDevice(devid); diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c index 92bd00a104..777286bf38 100644 --- a/src/audio/aaudio/SDL_aaudio.c +++ b/src/audio/aaudio/SDL_aaudio.c @@ -34,16 +34,13 @@ struct SDL_PrivateAudioData { AAudioStream *stream; - /* Raw mixing buffer */ - Uint8 *mixbuf; - int mixlen; + Uint8 *mixbuf; // Raw mixing buffer int frame_size; - /* Resume device if it was paused automatically */ - int resume; + int resume; // Resume device if it was paused automatically }; -/* Debug */ +// Debug #if 0 #define LOGI(...) SDL_Log(__VA_ARGS__); #else @@ -52,7 +49,6 @@ struct SDL_PrivateAudioData typedef struct AAUDIO_Data { - AAudioStreamBuilder *builder; void *handle; #define SDL_PROC(ret, func, params) ret (*func) params; #include "SDL_aaudiofuncs.h" @@ -79,11 +75,14 @@ static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_re #define LIB_AAUDIO_SO "libaaudio.so" -static int AAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int AAUDIO_OpenDevice(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *private; - SDL_bool iscapture = _this->iscapture; + struct SDL_PrivateAudioData *hidden; + const SDL_bool iscapture = device->iscapture; aaudio_result_t res; + + SDL_assert(device->handle != NULL); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero. + LOGI(__func__); if (iscapture) { @@ -93,75 +92,91 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) } } - _this->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + hidden = device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); + if (hidden == NULL) { return SDL_OutOfMemory(); } - private = _this->hidden; - ctx.AAudioStreamBuilder_setSampleRate(ctx.builder, _this->spec.freq); - ctx.AAudioStreamBuilder_setChannelCount(ctx.builder, _this->spec.channels); - if(devname != NULL) { - int aaudio_device_id = SDL_atoi(devname); - LOGI("Opening device id %d", aaudio_device_id); - ctx.AAudioStreamBuilder_setDeviceId(ctx.builder, aaudio_device_id); - } - { - aaudio_direction_t direction = (iscapture ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); - ctx.AAudioStreamBuilder_setDirection(ctx.builder, direction); - } - { - aaudio_format_t format = AAUDIO_FORMAT_PCM_FLOAT; - if (_this->spec.format == SDL_AUDIO_S16SYS) { - format = AAUDIO_FORMAT_PCM_I16; - } else if (_this->spec.format == SDL_AUDIO_S16SYS) { - format = AAUDIO_FORMAT_PCM_FLOAT; - } - ctx.AAudioStreamBuilder_setFormat(ctx.builder, format); + AAudioStreamBuilder *builder = NULL; + res = ctx.AAudio_createStreamBuilder(&builder); + if (res != AAUDIO_OK) { + LOGI("SDL Failed AAudio_createStreamBuilder %d", res); + return SDL_SetError("SDL Failed AAudio_createStreamBuilder %d", res); + } else if (builder == NULL) { + LOGI("SDL Failed AAudio_createStreamBuilder - builder NULL"); + return SDL_SetError("SDL Failed AAudio_createStreamBuilder - builder NULL"); } - ctx.AAudioStreamBuilder_setErrorCallback(ctx.builder, AAUDIO_errorCallback, private); + // !!! FIXME: call AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); ? + + ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq); + ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels); + + const int aaudio_device_id = (int) ((size_t) device->handle); + LOGI("Opening device id %d", aaudio_device_id); + ctx.AAudioStreamBuilder_setDeviceId(builder, aaudio_device_id); + + const aaudio_direction_t direction = (iscapture ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); + ctx.AAudioStreamBuilder_setDirection(builder, direction); + aaudio_format_t format; + if (device->spec.format == SDL_AUDIO_S32SYS) { + format = AAUDIO_FORMAT_PCM_I32; + } else if (device->spec.format == SDL_AUDIO_F32SYS) { + format = AAUDIO_FORMAT_PCM_FLOAT; + } else { + format = AAUDIO_FORMAT_PCM_I16; // sint16 is a safe bet for everything else. + } + + ctx.AAudioStreamBuilder_setFormat(builder, format); + + ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, hidden); LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u", - _this->spec.freq, SDL_AUDIO_BITSIZE(_this->spec.format), - _this->spec.channels, (_this->spec.format & 0x1000) ? "BE" : "LE", _this->spec.samples); + device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format), + device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames); + + res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream); + ctx.AAudioStreamBuilder_delete(builder); - res = ctx.AAudioStreamBuilder_openStream(ctx.builder, &private->stream); if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res); return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); } - _this->spec.freq = ctx.AAudioStream_getSampleRate(private->stream); - _this->spec.channels = ctx.AAudioStream_getChannelCount(private->stream); - { - aaudio_format_t fmt = ctx.AAudioStream_getFormat(private->stream); - if (fmt == AAUDIO_FORMAT_PCM_I16) { - _this->spec.format = SDL_AUDIO_S16SYS; - } else if (fmt == AAUDIO_FORMAT_PCM_FLOAT) { - _this->spec.format = SDL_AUDIO_F32SYS; - } + device->spec.freq = ctx.AAudioStream_getSampleRate(hidden->stream); + device->spec.channels = ctx.AAudioStream_getChannelCount(hidden->stream); + + format = ctx.AAudioStream_getFormat(hidden->stream); + if (format == AAUDIO_FORMAT_PCM_I16) { + device->spec.format = SDL_AUDIO_S16SYS; + } else if (format == AAUDIO_FORMAT_PCM_I32) { + device->spec.format = SDL_AUDIO_S32SYS; + } else if (format == AAUDIO_FORMAT_PCM_FLOAT) { + device->spec.format = SDL_AUDIO_F32SYS; + } else { + return SDL_SetError("Got unexpected audio format %d from AAudioStream_getFormat", (int) format); } + device->sample_frames = ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 2; + LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u", - _this->spec.freq, SDL_AUDIO_BITSIZE(_this->spec.format), - _this->spec.channels, (_this->spec.format & 0x1000) ? "BE" : "LE", _this->spec.samples); + device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format), + device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames); - SDL_CalculateAudioSpec(&_this->spec); + SDL_UpdatedAudioDeviceFormat(device); - /* Allocate mixing buffer */ + // Allocate mixing buffer if (!iscapture) { - private->mixlen = _this->spec.size; - private->mixbuf = (Uint8 *)SDL_malloc(private->mixlen); - if (private->mixbuf == NULL) { + hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); + if (hidden->mixbuf == NULL) { return SDL_OutOfMemory(); } - SDL_memset(private->mixbuf, _this->spec.silence, _this->spec.size); + SDL_memset(hidden->mixbuf, device->silence_value, device->buffer_size); } - private->frame_size = _this->spec.channels * (SDL_AUDIO_BITSIZE(_this->spec.format) / 8); + hidden->frame_size = device->spec.channels * (SDL_AUDIO_BITSIZE(device->spec.format) / 8); - res = ctx.AAudioStream_requestStart(private->stream); + res = ctx.AAudioStream_requestStart(hidden->stream); if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStream_requestStart %d iscapture:%d", res, iscapture); return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); @@ -171,55 +186,51 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) return 0; } -static void AAUDIO_CloseDevice(SDL_AudioDevice *_this) +static void AAUDIO_CloseDevice(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *private = _this->hidden; - aaudio_result_t res; - LOGI(__func__); + struct SDL_PrivateAudioData *hidden = device->hidden; + if (hidden) { + LOGI(__func__); - if (private->stream) { - res = ctx.AAudioStream_requestStop(private->stream); - if (res != AAUDIO_OK) { - LOGI("SDL Failed AAudioStream_requestStop %d", res); - SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); - return; + if (hidden->stream) { + ctx.AAudioStream_requestStop(hidden->stream); + ctx.AAudioStream_close(hidden->stream); } - res = ctx.AAudioStream_close(private->stream); - if (res != AAUDIO_OK) { - LOGI("SDL Failed AAudioStreamBuilder_delete %d", res); - SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); - return; - } + SDL_free(hidden->mixbuf); + SDL_free(hidden); + device->hidden = NULL; } - - SDL_free(_this->hidden->mixbuf); - SDL_free(_this->hidden); } -static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize) { - struct SDL_PrivateAudioData *private = _this->hidden; - return private->mixbuf; + return device->hidden->mixbuf; } -static void AAUDIO_PlayDevice(SDL_AudioDevice *_this) +static void AAUDIO_WaitDevice(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *private = _this->hidden; - aaudio_result_t res; - int64_t timeoutNanoseconds = 1 * 1000 * 1000; /* 8 ms */ - res = ctx.AAudioStream_write(private->stream, private->mixbuf, private->mixlen / private->frame_size, timeoutNanoseconds); + AAudioStream *stream = device->hidden->stream; + while (!SDL_AtomicGet(&device->shutdown) && ((int) ctx.AAudioStream_getBufferSizeInFrames(stream)) < device->sample_frames) { + SDL_Delay(1); + } +} + +static void AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) +{ + AAudioStream *stream = device->hidden->stream; + const aaudio_result_t res = ctx.AAudioStream_write(stream, buffer, device->sample_frames, 0); if (res < 0) { LOGI("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); } else { - LOGI("SDL AAudio play: %d frames, wanted:%d frames", (int)res, private->mixlen / private->frame_size); + LOGI("SDL AAudio play: %d frames, wanted:%d frames", (int)res, sample_frames); } #if 0 - /* Log under-run count */ + // Log under-run count { static int prev = 0; - int32_t cnt = ctx.AAudioStream_getXRunCount(private->stream); + int32_t cnt = ctx.AAudioStream_getXRunCount(hidden->stream); if (cnt != prev) { SDL_Log("AAudio underrun: %d - total: %d", cnt - prev, cnt); prev = cnt; @@ -228,41 +239,31 @@ static void AAUDIO_PlayDevice(SDL_AudioDevice *_this) #endif } -static int AAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static int AAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { - struct SDL_PrivateAudioData *private = _this->hidden; - aaudio_result_t res; - int64_t timeoutNanoseconds = 8 * 1000 * 1000; /* 8 ms */ - res = ctx.AAudioStream_read(private->stream, buffer, buflen / private->frame_size, timeoutNanoseconds); + const aaudio_result_t res = ctx.AAudioStream_read(device->hidden->stream, buffer, device->sample_frames, 0); if (res < 0) { LOGI("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); return -1; } - LOGI("SDL AAudio capture:%d frames, wanted:%d frames", (int)res, buflen / private->frame_size); - return res * private->frame_size; + LOGI("SDL AAudio capture:%d frames, wanted:%d frames", (int)res, buflen / device->hidden->frame_size); + return res * device->hidden->frame_size; } static void AAUDIO_Deinitialize(void) { + Android_StopAudioHotplug(); + LOGI(__func__); if (ctx.handle) { - if (ctx.builder) { - aaudio_result_t res; - res = ctx.AAudioStreamBuilder_delete(ctx.builder); - if (res != AAUDIO_OK) { - SDL_SetError("Failed AAudioStreamBuilder_delete %s", ctx.AAudio_convertResultToText(res)); - } - } SDL_UnloadObject(ctx.handle); } - ctx.handle = NULL; - ctx.builder = NULL; + SDL_zero(ctx); LOGI("End AAUDIO %s", SDL_GetError()); } static SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl) { - aaudio_result_t res; LOGI(__func__); /* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release, @@ -279,189 +280,96 @@ static SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl) ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO); if (ctx.handle == NULL) { LOGI("SDL couldn't find " LIB_AAUDIO_SO); - goto failure; + return SDL_FALSE; } if (AAUDIO_LoadFunctions(&ctx) < 0) { - goto failure; + SDL_UnloadObject(ctx.handle); + SDL_zero(ctx); + return SDL_FALSE; } - res = ctx.AAudio_createStreamBuilder(&ctx.builder); - if (res != AAUDIO_OK) { - LOGI("SDL Failed AAudio_createStreamBuilder %d", res); - goto failure; - } - - if (ctx.builder == NULL) { - LOGI("SDL Failed AAudio_createStreamBuilder - builder NULL"); - goto failure; - } - - impl->DetectDevices = Android_DetectDevices; + impl->ThreadInit = Android_AudioThreadInit; + impl->DetectDevices = Android_StartAudioHotplug; impl->Deinitialize = AAUDIO_Deinitialize; impl->OpenDevice = AAUDIO_OpenDevice; impl->CloseDevice = AAUDIO_CloseDevice; + impl->WaitDevice = AAUDIO_WaitDevice; impl->PlayDevice = AAUDIO_PlayDevice; impl->GetDeviceBuf = AAUDIO_GetDeviceBuf; + impl->WaitCaptureDevice = AAUDIO_WaitDevice; impl->CaptureFromDevice = AAUDIO_CaptureFromDevice; - impl->AllowsArbitraryDeviceNames = SDL_TRUE; - /* and the capabilities */ impl->HasCaptureSupport = SDL_TRUE; - impl->OnlyHasDefaultOutputDevice = SDL_FALSE; - impl->OnlyHasDefaultCaptureDevice = SDL_FALSE; - /* this audio target is available. */ LOGI("SDL AAUDIO_Init OK"); return SDL_TRUE; - -failure: - if (ctx.handle) { - if (ctx.builder) { - ctx.AAudioStreamBuilder_delete(ctx.builder); - } - SDL_UnloadObject(ctx.handle); - } - ctx.handle = NULL; - ctx.builder = NULL; - return SDL_FALSE; } AudioBootStrap AAUDIO_bootstrap = { "AAudio", "AAudio audio driver", AAUDIO_Init, SDL_FALSE }; -/* Pause (block) all non already paused audio devices by taking their mixer lock */ + +static SDL_bool PauseOneDevice(SDL_AudioDevice *device, void *userdata) +{ + struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden; + if (hidden != NULL) { + if (hidden->stream) { + aaudio_result_t res; + + if (device->iscapture) { + // Pause() isn't implemented for 'capture', use Stop() + res = ctx.AAudioStream_requestStop(hidden->stream); + } else { + res = ctx.AAudioStream_requestPause(hidden->stream); + } + + if (res != AAUDIO_OK) { + LOGI("SDL Failed AAudioStream_requestPause %d", res); + SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); + } + + SDL_LockMutex(device->lock); + hidden->resume = SDL_TRUE; + } + } + return SDL_FALSE; // keep enumerating. +} + +// Pause (block) all non already paused audio devices by taking their mixer lock void AAUDIO_PauseDevices(void) { - int i; - - /* AAUDIO driver is not used */ - if (ctx.handle == NULL) { - return; - } - - for (i = 0; i < get_max_num_audio_dev(); i++) { - SDL_AudioDevice *_this = get_audio_dev(i); - SDL_AudioDevice *audioDevice = NULL; - SDL_AudioDevice *captureDevice = NULL; - - if (_this == NULL) { - continue; - } - - if (_this->iscapture) { - captureDevice = _this; - } else { - audioDevice = _this; - } - - if (audioDevice != NULL && audioDevice->hidden != NULL) { - struct SDL_PrivateAudioData *private = (struct SDL_PrivateAudioData *)audioDevice->hidden; - - if (private->stream) { - aaudio_result_t res = ctx.AAudioStream_requestPause(private->stream); - if (res != AAUDIO_OK) { - LOGI("SDL Failed AAudioStream_requestPause %d", res); - SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); - } - } - - if (SDL_AtomicGet(&audioDevice->paused)) { - /* The device is already paused, leave it alone */ - private->resume = SDL_FALSE; - } else { - SDL_LockMutex(audioDevice->mixer_lock); - SDL_AtomicSet(&audioDevice->paused, 1); - private->resume = SDL_TRUE; - } - } - - if (captureDevice != NULL && captureDevice->hidden != NULL) { - struct SDL_PrivateAudioData *private = (struct SDL_PrivateAudioData *)audioDevice->hidden; - - if (private->stream) { - /* Pause() isn't implemented for 'capture', use Stop() */ - aaudio_result_t res = ctx.AAudioStream_requestStop(private->stream); - if (res != AAUDIO_OK) { - LOGI("SDL Failed AAudioStream_requestStop %d", res); - SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); - } - } - - if (SDL_AtomicGet(&captureDevice->paused)) { - /* The device is already paused, leave it alone */ - private->resume = SDL_FALSE; - } else { - SDL_LockMutex(captureDevice->mixer_lock); - SDL_AtomicSet(&captureDevice->paused, 1); - private->resume = SDL_TRUE; - } - } - + if (ctx.handle != NULL) { // AAUDIO driver is used? + (void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneDevice, NULL); } } -/* Resume (unblock) all non already paused audio devices by releasing their mixer lock */ +// Resume (unblock) all non already paused audio devices by releasing their mixer lock +static SDL_bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata) +{ + struct SDL_PrivateAudioData *hidden = device->hidden; + if (hidden != NULL) { + if (hidden->resume) { + hidden->resume = SDL_FALSE; + SDL_UnlockMutex(device->lock); + } + + if (hidden->stream) { + aaudio_result_t res = ctx.AAudioStream_requestStart(hidden->stream); + if (res != AAUDIO_OK) { + LOGI("SDL Failed AAudioStream_requestStart %d", res); + SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); + } + } + } + return SDL_FALSE; // keep enumerating. +} + void AAUDIO_ResumeDevices(void) { - int i; - - /* AAUDIO driver is not used */ - if (ctx.handle == NULL) { - return; - } - - for (i = 0; i < get_max_num_audio_dev(); i++) { - SDL_AudioDevice *_this = get_audio_dev(i); - SDL_AudioDevice *audioDevice = NULL; - SDL_AudioDevice *captureDevice = NULL; - - if (_this == NULL) { - continue; - } - - if (_this->iscapture) { - captureDevice = _this; - } else { - audioDevice = _this; - } - - if (audioDevice != NULL && audioDevice->hidden != NULL) { - struct SDL_PrivateAudioData *private = audioDevice->hidden; - - if (private->resume) { - SDL_AtomicSet(&audioDevice->paused, 0); - private->resume = SDL_FALSE; - SDL_UnlockMutex(audioDevice->mixer_lock); - } - - if (private->stream) { - aaudio_result_t res = ctx.AAudioStream_requestStart(private->stream); - if (res != AAUDIO_OK) { - LOGI("SDL Failed AAudioStream_requestStart %d", res); - SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); - } - } - } - - if (captureDevice != NULL && captureDevice->hidden != NULL) { - struct SDL_PrivateAudioData *private = audioDevice->hidden; - - if (private->resume) { - SDL_AtomicSet(&captureDevice->paused, 0); - private->resume = SDL_FALSE; - SDL_UnlockMutex(captureDevice->mixer_lock); - } - - if (private->stream) { - aaudio_result_t res = ctx.AAudioStream_requestStart(private->stream); - if (res != AAUDIO_OK) { - LOGI("SDL Failed AAudioStream_requestStart %d", res); - SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); - } - } - } + if (ctx.handle != NULL) { // AAUDIO driver is used? + (void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneDevice, NULL); } } @@ -470,50 +378,29 @@ void AAUDIO_ResumeDevices(void) None of the standard state queries indicate any problem in my testing. And the error callback doesn't actually get called. But, AAudioStream_getTimestamp() does return AAUDIO_ERROR_INVALID_STATE */ -SDL_bool AAUDIO_DetectBrokenPlayState(void) +static SDL_bool DetectBrokenPlayStatePerDevice(SDL_AudioDevice *device, void *userdata) { - int i; - - /* AAUDIO driver is not used */ - if (ctx.handle == NULL) { - return SDL_FALSE; - } - - for (i = 0; i < get_max_num_audio_dev(); i++) { - SDL_AudioDevice *_this = get_audio_dev(i); - SDL_AudioDevice *audioDevice = NULL; - SDL_AudioDevice *captureDevice = NULL; - - if (_this == NULL) { - continue; - } - - if (_this->iscapture) { - captureDevice = _this; - } else { - audioDevice = _this; - } - - if (audioDevice != NULL && audioDevice->hidden != NULL) { - struct SDL_PrivateAudioData *private = audioDevice->hidden; - int64_t framePosition, timeNanoseconds; - - aaudio_result_t res = ctx.AAudioStream_getTimestamp(private->stream, CLOCK_MONOTONIC, &framePosition, &timeNanoseconds); - if (res == AAUDIO_ERROR_INVALID_STATE) { - aaudio_stream_state_t currentState = ctx.AAudioStream_getState(private->stream); - /* AAudioStream_getTimestamp() will also return AAUDIO_ERROR_INVALID_STATE while the stream is still initially starting. But we only care if it silently went invalid while playing. */ - if (currentState == AAUDIO_STREAM_STATE_STARTED) { - LOGI("SDL AAUDIO_DetectBrokenPlayState: detected invalid audio device state: AAudioStream_getTimestamp result=%d, framePosition=%lld, timeNanoseconds=%lld, getState=%d", (int)res, (long long)framePosition, (long long)timeNanoseconds, (int)currentState); - return SDL_TRUE; - } + SDL_assert(device != NULL); + if (!device->iscapture && device->hidden != NULL) { + struct SDL_PrivateAudioData *hidden = device->hidden; + int64_t framePosition, timeNanoseconds; + aaudio_result_t res = ctx.AAudioStream_getTimestamp(hidden->stream, CLOCK_MONOTONIC, &framePosition, &timeNanoseconds); + if (res == AAUDIO_ERROR_INVALID_STATE) { + aaudio_stream_state_t currentState = ctx.AAudioStream_getState(hidden->stream); + // AAudioStream_getTimestamp() will also return AAUDIO_ERROR_INVALID_STATE while the stream is still initially starting. But we only care if it silently went invalid while playing. + if (currentState == AAUDIO_STREAM_STATE_STARTED) { + LOGI("SDL AAUDIO_DetectBrokenPlayState: detected invalid audio device state: AAudioStream_getTimestamp result=%d, framePosition=%lld, timeNanoseconds=%lld, getState=%d", (int)res, (long long)framePosition, (long long)timeNanoseconds, (int)currentState); + return SDL_TRUE; // this guy. } - } - - (void) captureDevice; } - return SDL_FALSE; + return SDL_FALSE; // enumerate more devices. } -#endif /* SDL_AUDIO_DRIVER_AAUDIO */ +SDL_bool AAUDIO_DetectBrokenPlayState(void) +{ + return (ctx.handle && SDL_FindPhysicalAudioDeviceByCallback(DetectBrokenPlayStatePerDevice, NULL) != NULL) ? SDL_TRUE : SDL_FALSE; +} + +#endif // SDL_AUDIO_DRIVER_AAUDIO diff --git a/src/audio/aaudio/SDL_aaudiofuncs.h b/src/audio/aaudio/SDL_aaudiofuncs.h index 1e4e989057..f6e860d6a1 100644 --- a/src/audio/aaudio/SDL_aaudiofuncs.h +++ b/src/audio/aaudio/SDL_aaudiofuncs.h @@ -55,9 +55,9 @@ SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_waitForStateChange, (AAudioStream SDL_PROC(aaudio_result_t, AAudioStream_read, (AAudioStream * stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds)) SDL_PROC(aaudio_result_t, AAudioStream_write, (AAudioStream * stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds)) SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_setBufferSizeInFrames, (AAudioStream * stream, int32_t numFrames)) -SDL_PROC_UNUSED(int32_t, AAudioStream_getBufferSizeInFrames, (AAudioStream * stream)) +SDL_PROC(int32_t, AAudioStream_getBufferSizeInFrames, (AAudioStream * stream)) SDL_PROC_UNUSED(int32_t, AAudioStream_getFramesPerBurst, (AAudioStream * stream)) -SDL_PROC_UNUSED(int32_t, AAudioStream_getBufferCapacityInFrames, (AAudioStream * stream)) +SDL_PROC(int32_t, AAudioStream_getBufferCapacityInFrames, (AAudioStream * stream)) SDL_PROC_UNUSED(int32_t, AAudioStream_getFramesPerDataCallback, (AAudioStream * stream)) SDL_PROC(int32_t, AAudioStream_getXRunCount, (AAudioStream * stream)) SDL_PROC(int32_t, AAudioStream_getSampleRate, (AAudioStream * stream)) diff --git a/src/audio/android/SDL_androidaudio.c b/src/audio/android/SDL_androidaudio.c index 5f666a278a..7dbb8541af 100644 --- a/src/audio/android/SDL_androidaudio.c +++ b/src/audio/android/SDL_androidaudio.c @@ -22,7 +22,7 @@ #ifdef SDL_AUDIO_DRIVER_ANDROID -/* Output audio to Android */ +// Output audio to Android (legacy interface) #include "../SDL_sysaudio.h" #include "../SDL_audio_c.h" @@ -34,185 +34,157 @@ struct SDL_PrivateAudioData { - /* Resume device if it was paused automatically */ - int resume; + int resume; // Resume device if it was paused automatically }; static SDL_AudioDevice *audioDevice = NULL; static SDL_AudioDevice *captureDevice = NULL; - -static int ANDROIDAUDIO_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int ANDROIDAUDIO_OpenDevice(SDL_AudioDevice *device) { - SDL_AudioFormat test_format; - const SDL_AudioFormat *closefmts; - SDL_bool iscapture = _this->iscapture; + device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); + if (device->hidden == NULL) { + return SDL_OutOfMemory(); + } + + const SDL_bool iscapture = device->iscapture; if (iscapture) { if (captureDevice) { return SDL_SetError("An audio capture device is already opened"); } - } - - if (!iscapture) { + captureDevice = device; + } else { if (audioDevice) { return SDL_SetError("An audio playback device is already opened"); } + audioDevice = device; } - if (iscapture) { - captureDevice = _this; - } else { - audioDevice = _this; - } - - _this->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*_this->hidden)); - if (_this->hidden == NULL) { - return SDL_OutOfMemory(); - } - - closefmts = SDL_ClosestAudioFormats(_this->spec.format); + SDL_AudioFormat test_format; + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); while ((test_format = *(closefmts++)) != 0) { if ((test_format == SDL_AUDIO_U8) || (test_format == SDL_AUDIO_S16) || (test_format == SDL_AUDIO_F32)) { - _this->spec.format = test_format; + device->spec.format = test_format; break; } } if (!test_format) { - /* Didn't find a compatible format :( */ - return SDL_SetError("%s: Unsupported audio format", "android"); + return SDL_SetError("android: Unsupported audio format"); } - { - int audio_device_id = 0; - if (devname != NULL) { - audio_device_id = SDL_atoi(devname); - } - if (Android_JNI_OpenAudioDevice(iscapture, audio_device_id, &_this->spec) < 0) { - return -1; - } + if (Android_JNI_OpenAudioDevice(device) < 0) { + return -1; } - SDL_CalculateAudioSpec(&_this->spec); + SDL_UpdatedAudioDeviceFormat(device); return 0; } -static void ANDROIDAUDIO_PlayDevice(SDL_AudioDevice *_this) +// !!! FIXME: this needs a WaitDevice implementation. + +static void ANDROIDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { Android_JNI_WriteAudioBuffer(); } -static Uint8 *ANDROIDAUDIO_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *ANDROIDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size) { return Android_JNI_GetAudioBuffer(); } -static int ANDROIDAUDIO_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static int ANDROIDAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { return Android_JNI_CaptureAudioBuffer(buffer, buflen); } -static void ANDROIDAUDIO_FlushCapture(SDL_AudioDevice *_this) +static void ANDROIDAUDIO_FlushCapture(SDL_AudioDevice *device) { Android_JNI_FlushCapturedAudio(); } -static void ANDROIDAUDIO_CloseDevice(SDL_AudioDevice *_this) +static void ANDROIDAUDIO_CloseDevice(SDL_AudioDevice *device) { /* At this point SDL_CloseAudioDevice via close_audio_device took care of terminating the audio thread so it's safe to terminate the Java side buffer and AudioTrack */ - Android_JNI_CloseAudioDevice(_this->iscapture); - if (_this->iscapture) { - SDL_assert(captureDevice == _this); - captureDevice = NULL; - } else { - SDL_assert(audioDevice == _this); - audioDevice = NULL; + if (device->hidden) { + Android_JNI_CloseAudioDevice(device->iscapture); + if (device->iscapture) { + SDL_assert(captureDevice == device); + captureDevice = NULL; + } else { + SDL_assert(audioDevice == device); + audioDevice = NULL; + } + SDL_free(device->hidden); + device->hidden = NULL; } - SDL_free(_this->hidden); } static SDL_bool ANDROIDAUDIO_Init(SDL_AudioDriverImpl *impl) { - /* Set the function pointers */ - impl->DetectDevices = Android_DetectDevices; + impl->ThreadInit = Android_AudioThreadInit; + impl->DetectDevices = Android_StartAudioHotplug; + impl->Deinitialize = Android_StopAudioHotplug; impl->OpenDevice = ANDROIDAUDIO_OpenDevice; impl->PlayDevice = ANDROIDAUDIO_PlayDevice; impl->GetDeviceBuf = ANDROIDAUDIO_GetDeviceBuf; impl->CloseDevice = ANDROIDAUDIO_CloseDevice; impl->CaptureFromDevice = ANDROIDAUDIO_CaptureFromDevice; impl->FlushCapture = ANDROIDAUDIO_FlushCapture; - impl->AllowsArbitraryDeviceNames = SDL_TRUE; - /* and the capabilities */ impl->HasCaptureSupport = SDL_TRUE; - impl->OnlyHasDefaultOutputDevice = SDL_FALSE; - impl->OnlyHasDefaultCaptureDevice = SDL_FALSE; - return SDL_TRUE; /* this audio target is available. */ + return SDL_TRUE; } AudioBootStrap ANDROIDAUDIO_bootstrap = { "android", "SDL Android audio driver", ANDROIDAUDIO_Init, SDL_FALSE }; -/* Pause (block) all non already paused audio devices by taking their mixer lock */ +// Pause (block) all non already paused audio devices by taking their mixer lock void ANDROIDAUDIO_PauseDevices(void) { - /* TODO: Handle multiple devices? */ - struct SDL_PrivateAudioData *private; + // TODO: Handle multiple devices? + struct SDL_PrivateAudioData *hidden; if (audioDevice != NULL && audioDevice->hidden != NULL) { - private = (struct SDL_PrivateAudioData *)audioDevice->hidden; - if (SDL_AtomicGet(&audioDevice->paused)) { - /* The device is already paused, leave it alone */ - private->resume = SDL_FALSE; - } else { - SDL_LockMutex(audioDevice->mixer_lock); - SDL_AtomicSet(&audioDevice->paused, 1); - private->resume = SDL_TRUE; - } + hidden = (struct SDL_PrivateAudioData *)audioDevice->hidden; + SDL_LockMutex(audioDevice->lock); + hidden->resume = SDL_TRUE; } if (captureDevice != NULL && captureDevice->hidden != NULL) { - private = (struct SDL_PrivateAudioData *)captureDevice->hidden; - if (SDL_AtomicGet(&captureDevice->paused)) { - /* The device is already paused, leave it alone */ - private->resume = SDL_FALSE; - } else { - SDL_LockMutex(captureDevice->mixer_lock); - SDL_AtomicSet(&captureDevice->paused, 1); - private->resume = SDL_TRUE; - } + hidden = (struct SDL_PrivateAudioData *)captureDevice->hidden; + SDL_LockMutex(captureDevice->lock); + hidden->resume = SDL_TRUE; } } -/* Resume (unblock) all non already paused audio devices by releasing their mixer lock */ +// Resume (unblock) all non already paused audio devices by releasing their mixer lock void ANDROIDAUDIO_ResumeDevices(void) { - /* TODO: Handle multiple devices? */ - struct SDL_PrivateAudioData *private; + // TODO: Handle multiple devices? + struct SDL_PrivateAudioData *hidden; if (audioDevice != NULL && audioDevice->hidden != NULL) { - private = (struct SDL_PrivateAudioData *)audioDevice->hidden; - if (private->resume) { - SDL_AtomicSet(&audioDevice->paused, 0); - private->resume = SDL_FALSE; - SDL_UnlockMutex(audioDevice->mixer_lock); + hidden = (struct SDL_PrivateAudioData *)audioDevice->hidden; + if (hidden->resume) { + hidden->resume = SDL_FALSE; + SDL_UnlockMutex(audioDevice->lock); } } if (captureDevice != NULL && captureDevice->hidden != NULL) { - private = (struct SDL_PrivateAudioData *)captureDevice->hidden; - if (private->resume) { - SDL_AtomicSet(&captureDevice->paused, 0); - private->resume = SDL_FALSE; - SDL_UnlockMutex(captureDevice->mixer_lock); + hidden = (struct SDL_PrivateAudioData *)captureDevice->hidden; + if (hidden->resume) { + hidden->resume = SDL_FALSE; + SDL_UnlockMutex(captureDevice->lock); } } } -#endif /* SDL_AUDIO_DRIVER_ANDROID */ +#endif // SDL_AUDIO_DRIVER_ANDROID diff --git a/src/audio/openslES/SDL_openslES.c b/src/audio/openslES/SDL_openslES.c index 128fef61e9..37c7dc8cb3 100644 --- a/src/audio/openslES/SDL_openslES.c +++ b/src/audio/openslES/SDL_openslES.c @@ -22,9 +22,8 @@ #ifdef SDL_AUDIO_DRIVER_OPENSLES -/* For more discussion of low latency audio on Android, see this: - https://googlesamples.github.io/android-audio-high-performance/guides/opensl_es.html -*/ +// For more discussion of low latency audio on Android, see this: +// https://googlesamples.github.io/android-audio-high-performance/guides/opensl_es.html #include "../SDL_sysaudio.h" #include "../SDL_audio_c.h" @@ -36,7 +35,7 @@ #include -#define NUM_BUFFERS 2 /* -- Don't lower this! */ +#define NUM_BUFFERS 2 // -- Don't lower this! struct SDL_PrivateAudioData { @@ -83,14 +82,14 @@ struct SDL_PrivateAudioData #define SL_ANDROID_SPEAKER_5DOT1 (SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY) #define SL_ANDROID_SPEAKER_7DOT1 (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT) -/* engine interfaces */ +// engine interfaces static SLObjectItf engineObject = NULL; static SLEngineItf engineEngine = NULL; -/* output mix interfaces */ +// output mix interfaces static SLObjectItf outputMixObject = NULL; -/* buffer queue player interfaces */ +// buffer queue player interfaces static SLObjectItf bqPlayerObject = NULL; static SLPlayItf bqPlayerPlay = NULL; static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL; @@ -98,7 +97,7 @@ static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL; static SLVolumeItf bqPlayerVolume; #endif -/* recorder interfaces */ +// recorder interfaces static SLObjectItf recorderObject = NULL; static SLRecordItf recorderRecord = NULL; static SLAndroidSimpleBufferQueueItf recorderBufferQueue = NULL; @@ -123,13 +122,13 @@ static void openslES_DestroyEngine(void) { LOGI("openslES_DestroyEngine()"); - /* destroy output mix object, and invalidate all associated interfaces */ + // destroy output mix object, and invalidate all associated interfaces if (outputMixObject != NULL) { (*outputMixObject)->Destroy(outputMixObject); outputMixObject = NULL; } - /* destroy engine object, and invalidate all associated interfaces */ + // destroy engine object, and invalidate all associated interfaces if (engineObject != NULL) { (*engineObject)->Destroy(engineObject); engineObject = NULL; @@ -145,7 +144,7 @@ static int openslES_CreateEngine(void) LOGI("openSLES_CreateEngine()"); - /* create engine */ + // create engine result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); if (SL_RESULT_SUCCESS != result) { LOGE("slCreateEngine failed: %d", result); @@ -153,7 +152,7 @@ static int openslES_CreateEngine(void) } LOGI("slCreateEngine OK"); - /* realize the engine */ + // realize the engine result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); if (SL_RESULT_SUCCESS != result) { LOGE("RealizeEngine failed: %d", result); @@ -161,7 +160,7 @@ static int openslES_CreateEngine(void) } LOGI("RealizeEngine OK"); - /* get the engine interface, which is needed in order to create other objects */ + // get the engine interface, which is needed in order to create other objects result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); if (SL_RESULT_SUCCESS != result) { LOGE("EngineGetInterface failed: %d", result); @@ -169,7 +168,7 @@ static int openslES_CreateEngine(void) } LOGI("EngineGetInterface OK"); - /* create output mix */ + // create output mix result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req); if (SL_RESULT_SUCCESS != result) { LOGE("CreateOutputMix failed: %d", result); @@ -177,7 +176,7 @@ static int openslES_CreateEngine(void) } LOGI("CreateOutputMix OK"); - /* realize the output mix */ + // realize the output mix result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); if (SL_RESULT_SUCCESS != result) { LOGE("RealizeOutputMix failed: %d", result); @@ -190,7 +189,7 @@ error: return 0; } -/* this callback handler is called every time a buffer finishes recording */ +// this callback handler is called every time a buffer finishes recording static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) { struct SDL_PrivateAudioData *audiodata = (struct SDL_PrivateAudioData *)context; @@ -199,12 +198,12 @@ static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) SDL_PostSemaphore(audiodata->playsem); } -static void openslES_DestroyPCMRecorder(SDL_AudioDevice *_this) +static void openslES_DestroyPCMRecorder(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *audiodata = _this->hidden; + struct SDL_PrivateAudioData *audiodata = device->hidden; SLresult result; - /* stop recording */ + // stop recording if (recorderRecord != NULL) { result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); if (SL_RESULT_SUCCESS != result) { @@ -212,7 +211,7 @@ static void openslES_DestroyPCMRecorder(SDL_AudioDevice *_this) } } - /* destroy audio recorder object, and invalidate all associated interfaces */ + // destroy audio recorder object, and invalidate all associated interfaces if (recorderObject != NULL) { (*recorderObject)->Destroy(recorderObject); recorderObject = NULL; @@ -230,9 +229,9 @@ static void openslES_DestroyPCMRecorder(SDL_AudioDevice *_this) } } -static int openslES_CreatePCMRecorder(SDL_AudioDevice *_this) +static int openslES_CreatePCMRecorder(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *audiodata = _this->hidden; + struct SDL_PrivateAudioData *audiodata = device->hidden; SLDataFormat_PCM format_pcm; SLDataLocator_AndroidSimpleBufferQueue loc_bufq; SLDataSink audioSnk; @@ -248,19 +247,19 @@ static int openslES_CreatePCMRecorder(SDL_AudioDevice *_this) return SDL_SetError("This app doesn't have RECORD_AUDIO permission"); } - /* Just go with signed 16-bit audio as it's the most compatible */ - _this->spec.format = SDL_AUDIO_S16SYS; - _this->spec.channels = 1; - /*_this->spec.freq = SL_SAMPLINGRATE_16 / 1000;*/ + // Just go with signed 16-bit audio as it's the most compatible + device->spec.format = SDL_AUDIO_S16SYS; + device->spec.channels = 1; + //device->spec.freq = SL_SAMPLINGRATE_16 / 1000;*/ - /* Update the fragment size as size in bytes */ - SDL_CalculateAudioSpec(&_this->spec); + // Update the fragment size as size in bytes + SDL_UpdatedAudioDeviceFormat(device); LOGI("Try to open %u hz %u bit chan %u %s samples %u", - _this->spec.freq, SDL_AUDIO_BITSIZE(_this->spec.format), - _this->spec.channels, (_this->spec.format & 0x1000) ? "BE" : "LE", _this->spec.samples); + device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format), + device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames); - /* configure audio source */ + // configure audio source loc_dev.locatorType = SL_DATALOCATOR_IODEVICE; loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT; loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; @@ -268,93 +267,93 @@ static int openslES_CreatePCMRecorder(SDL_AudioDevice *_this) audioSrc.pLocator = &loc_dev; audioSrc.pFormat = NULL; - /* configure audio sink */ + // configure audio sink loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; loc_bufq.numBuffers = NUM_BUFFERS; format_pcm.formatType = SL_DATAFORMAT_PCM; - format_pcm.numChannels = _this->spec.channels; - format_pcm.samplesPerSec = _this->spec.freq * 1000; /* / kilo Hz to milli Hz */ - format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(_this->spec.format); - format_pcm.containerSize = SDL_AUDIO_BITSIZE(_this->spec.format); + format_pcm.numChannels = device->spec.channels; + format_pcm.samplesPerSec = device->spec.freq * 1000; // / kilo Hz to milli Hz + format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format); + format_pcm.containerSize = SDL_AUDIO_BITSIZE(device->spec.format); format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER; audioSnk.pLocator = &loc_bufq; audioSnk.pFormat = &format_pcm; - /* create audio recorder */ - /* (requires the RECORD_AUDIO permission) */ + // create audio recorder + // (requires the RECORD_AUDIO permission) result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, &audioSnk, 1, ids, req); if (SL_RESULT_SUCCESS != result) { LOGE("CreateAudioRecorder failed: %d", result); goto failed; } - /* realize the recorder */ + // realize the recorder result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE); if (SL_RESULT_SUCCESS != result) { LOGE("RealizeAudioPlayer failed: %d", result); goto failed; } - /* get the record interface */ + // get the record interface result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord); if (SL_RESULT_SUCCESS != result) { LOGE("SL_IID_RECORD interface get failed: %d", result); goto failed; } - /* get the buffer queue interface */ + // get the buffer queue interface result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue); if (SL_RESULT_SUCCESS != result) { LOGE("SL_IID_BUFFERQUEUE interface get failed: %d", result); goto failed; } - /* register callback on the buffer queue */ - /* context is '(SDL_PrivateAudioData *)_this->hidden' */ - result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, _this->hidden); + // register callback on the buffer queue + // context is '(SDL_PrivateAudioData *)device->hidden' + result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, device->hidden); if (SL_RESULT_SUCCESS != result) { LOGE("RegisterCallback failed: %d", result); goto failed; } - /* Create the audio buffer semaphore */ + // Create the audio buffer semaphore audiodata->playsem = SDL_CreateSemaphore(0); if (!audiodata->playsem) { LOGE("cannot create Semaphore!"); goto failed; } - /* Create the sound buffers */ - audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * _this->spec.size); + // Create the sound buffers + audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size); if (audiodata->mixbuff == NULL) { LOGE("mixbuffer allocate - out of memory"); goto failed; } for (i = 0; i < NUM_BUFFERS; i++) { - audiodata->pmixbuff[i] = audiodata->mixbuff + i * _this->spec.size; + audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size; } - /* in case already recording, stop recording and clear buffer queue */ + // in case already recording, stop recording and clear buffer queue result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); if (SL_RESULT_SUCCESS != result) { LOGE("Record set state failed: %d", result); goto failed; } - /* enqueue empty buffers to be filled by the recorder */ + // enqueue empty buffers to be filled by the recorder for (i = 0; i < NUM_BUFFERS; i++) { - result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[i], _this->spec.size); + result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[i], device->buffer_size); if (SL_RESULT_SUCCESS != result) { LOGE("Record enqueue buffers failed: %d", result); goto failed; } } - /* start recording */ + // start recording result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING); if (SL_RESULT_SUCCESS != result) { LOGE("Record set state failed: %d", result); @@ -367,7 +366,7 @@ failed: return SDL_SetError("Open device failed!"); } -/* this callback handler is called every time a buffer finishes playing */ +// this callback handler is called every time a buffer finishes playing static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) { struct SDL_PrivateAudioData *audiodata = (struct SDL_PrivateAudioData *)context; @@ -376,20 +375,19 @@ static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) SDL_PostSemaphore(audiodata->playsem); } -static void openslES_DestroyPCMPlayer(SDL_AudioDevice *_this) +static void openslES_DestroyPCMPlayer(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *audiodata = _this->hidden; - SLresult result; + struct SDL_PrivateAudioData *audiodata = device->hidden; - /* set the player's state to 'stopped' */ + // set the player's state to 'stopped' if (bqPlayerPlay != NULL) { - result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); + const SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); if (SL_RESULT_SUCCESS != result) { LOGE("SetPlayState stopped failed: %d", result); } } - /* destroy buffer queue audio player object, and invalidate all associated interfaces */ + // destroy buffer queue audio player object, and invalidate all associated interfaces if (bqPlayerObject != NULL) { (*bqPlayerObject)->Destroy(bqPlayerObject); @@ -408,26 +406,14 @@ static void openslES_DestroyPCMPlayer(SDL_AudioDevice *_this) } } -static int openslES_CreatePCMPlayer(SDL_AudioDevice *_this) +static int openslES_CreatePCMPlayer(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *audiodata = _this->hidden; - SLDataLocator_AndroidSimpleBufferQueue loc_bufq; - SLDataFormat_PCM format_pcm; - SLAndroidDataFormat_PCM_EX format_pcm_ex; - SLDataSource audioSrc; - SLDataSink audioSnk; - SLDataLocator_OutputMix loc_outmix; - const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME }; - const SLboolean req[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }; - SLresult result; - int i; - /* If we want to add floating point audio support (requires API level 21) it can be done as described here: https://developer.android.com/ndk/guides/audio/opensl/android-extensions.html#floating-point */ if (SDL_GetAndroidSDKVersion() >= 21) { - const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(_this->spec.format); + const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format); SDL_AudioFormat test_format; while ((test_format = *(closefmts++)) != 0) { if (SDL_AUDIO_ISSIGNED(test_format)) { @@ -436,40 +422,42 @@ static int openslES_CreatePCMPlayer(SDL_AudioDevice *_this) } if (!test_format) { - /* Didn't find a compatible format : */ + // Didn't find a compatible format : LOGI("No compatible audio format, using signed 16-bit audio"); test_format = SDL_AUDIO_S16SYS; } - _this->spec.format = test_format; + device->spec.format = test_format; } else { - /* Just go with signed 16-bit audio as it's the most compatible */ - _this->spec.format = SDL_AUDIO_S16SYS; + // Just go with signed 16-bit audio as it's the most compatible + device->spec.format = SDL_AUDIO_S16SYS; } - /* Update the fragment size as size in bytes */ - SDL_CalculateAudioSpec(&_this->spec); + // Update the fragment size as size in bytes + SDL_UpdatedAudioDeviceFormat(device); LOGI("Try to open %u hz %s %u bit chan %u %s samples %u", - _this->spec.freq, SDL_AUDIO_ISFLOAT(_this->spec.format) ? "float" : "pcm", SDL_AUDIO_BITSIZE(_this->spec.format), - _this->spec.channels, (_this->spec.format & 0x1000) ? "BE" : "LE", _this->spec.samples); + device->spec.freq, SDL_AUDIO_ISFLOAT(device->spec.format) ? "float" : "pcm", SDL_AUDIO_BITSIZE(device->spec.format), + device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames); - /* configure audio source */ + // configure audio source + SLDataLocator_AndroidSimpleBufferQueue loc_bufq; loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; loc_bufq.numBuffers = NUM_BUFFERS; + SLDataFormat_PCM format_pcm; format_pcm.formatType = SL_DATAFORMAT_PCM; - format_pcm.numChannels = _this->spec.channels; - format_pcm.samplesPerSec = _this->spec.freq * 1000; /* / kilo Hz to milli Hz */ - format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(_this->spec.format); - format_pcm.containerSize = SDL_AUDIO_BITSIZE(_this->spec.format); + format_pcm.numChannels = device->spec.channels; + format_pcm.samplesPerSec = device->spec.freq * 1000; // / kilo Hz to milli Hz + format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format); + format_pcm.containerSize = SDL_AUDIO_BITSIZE(device->spec.format); - if (SDL_AUDIO_ISBIGENDIAN(_this->spec.format)) { + if (SDL_AUDIO_ISBIGENDIAN(device->spec.format)) { format_pcm.endianness = SL_BYTEORDER_BIGENDIAN; } else { format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; } - switch (_this->spec.channels) { + switch (device->spec.channels) { case 1: format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT; break; @@ -495,14 +483,19 @@ static int openslES_CreatePCMPlayer(SDL_AudioDevice *_this) format_pcm.channelMask = SL_ANDROID_SPEAKER_7DOT1; break; default: - /* Unknown number of channels, fall back to stereo */ - _this->spec.channels = 2; + // Unknown number of channels, fall back to stereo + device->spec.channels = 2; format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; break; } - if (SDL_AUDIO_ISFLOAT(_this->spec.format)) { - /* Copy all setup into PCM EX structure */ + SLDataSink audioSnk; + SLDataSource audioSrc; + audioSrc.pFormat = (void *)&format_pcm; + + SLAndroidDataFormat_PCM_EX format_pcm_ex; + if (SDL_AUDIO_ISFLOAT(device->spec.format)) { + // Copy all setup into PCM EX structure format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; format_pcm_ex.endianness = format_pcm.endianness; format_pcm_ex.channelMask = format_pcm.channelMask; @@ -511,81 +504,87 @@ static int openslES_CreatePCMPlayer(SDL_AudioDevice *_this) format_pcm_ex.bitsPerSample = format_pcm.bitsPerSample; format_pcm_ex.containerSize = format_pcm.containerSize; format_pcm_ex.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; + audioSrc.pFormat = (void *)&format_pcm_ex; } audioSrc.pLocator = &loc_bufq; - audioSrc.pFormat = SDL_AUDIO_ISFLOAT(_this->spec.format) ? (void *)&format_pcm_ex : (void *)&format_pcm; - /* configure audio sink */ + // configure audio sink + SLDataLocator_OutputMix loc_outmix; loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; loc_outmix.outputMix = outputMixObject; audioSnk.pLocator = &loc_outmix; audioSnk.pFormat = NULL; - /* create audio player */ + // create audio player + const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME }; + const SLboolean req[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }; + SLresult result; result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req); if (SL_RESULT_SUCCESS != result) { LOGE("CreateAudioPlayer failed: %d", result); goto failed; } - /* realize the player */ + // realize the player result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); if (SL_RESULT_SUCCESS != result) { LOGE("RealizeAudioPlayer failed: %d", result); goto failed; } - /* get the play interface */ + // get the play interface result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); if (SL_RESULT_SUCCESS != result) { LOGE("SL_IID_PLAY interface get failed: %d", result); goto failed; } - /* get the buffer queue interface */ + // get the buffer queue interface result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bqPlayerBufferQueue); if (SL_RESULT_SUCCESS != result) { LOGE("SL_IID_BUFFERQUEUE interface get failed: %d", result); goto failed; } - /* register callback on the buffer queue */ - /* context is '(SDL_PrivateAudioData *)_this->hidden' */ - result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, _this->hidden); + // register callback on the buffer queue + // context is '(SDL_PrivateAudioData *)device->hidden' + result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, device->hidden); if (SL_RESULT_SUCCESS != result) { LOGE("RegisterCallback failed: %d", result); goto failed; } #if 0 - /* get the volume interface */ + // get the volume interface result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); if (SL_RESULT_SUCCESS != result) { LOGE("SL_IID_VOLUME interface get failed: %d", result); - /* goto failed; */ + // goto failed; } #endif - /* Create the audio buffer semaphore */ + struct SDL_PrivateAudioData *audiodata = device->hidden; + + // Create the audio buffer semaphore audiodata->playsem = SDL_CreateSemaphore(NUM_BUFFERS - 1); if (!audiodata->playsem) { LOGE("cannot create Semaphore!"); goto failed; } - /* Create the sound buffers */ - audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * _this->spec.size); + // Create the sound buffers + audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size); if (audiodata->mixbuff == NULL) { LOGE("mixbuffer allocate - out of memory"); goto failed; } - for (i = 0; i < NUM_BUFFERS; i++) { - audiodata->pmixbuff[i] = audiodata->mixbuff + i * _this->spec.size; + for (int i = 0; i < NUM_BUFFERS; i++) { + audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size; } - /* set the player's state to playing */ + // set the player's state to playing result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); if (SL_RESULT_SUCCESS != result) { LOGE("Play set state failed: %d", result); @@ -598,103 +597,98 @@ failed: return -1; } -static int openslES_OpenDevice(SDL_AudioDevice *_this, const char *devname) +static int openslES_OpenDevice(SDL_AudioDevice *device) { - _this->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*_this->hidden)); - if (_this->hidden == NULL) { + device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden)); + if (device->hidden == NULL) { return SDL_OutOfMemory(); } - if (_this->iscapture) { - LOGI("openslES_OpenDevice() %s for capture", devname); - return openslES_CreatePCMRecorder(_this); + if (device->iscapture) { + LOGI("openslES_OpenDevice() for capture"); + return openslES_CreatePCMRecorder(device); } else { int ret; - LOGI("openslES_OpenDevice() %s for playing", devname); - ret = openslES_CreatePCMPlayer(_this); + LOGI("openslES_OpenDevice() for playing"); + ret = openslES_CreatePCMPlayer(device); if (ret < 0) { - /* Another attempt to open the device with a lower frequency */ - if (_this->spec.freq > 48000) { - openslES_DestroyPCMPlayer(_this); - _this->spec.freq = 48000; - ret = openslES_CreatePCMPlayer(_this); + // Another attempt to open the device with a lower frequency + if (device->spec.freq > 48000) { + openslES_DestroyPCMPlayer(device); + device->spec.freq = 48000; + ret = openslES_CreatePCMPlayer(device); } } - if (ret == 0) { - return 0; - } else { + if (ret != 0) { return SDL_SetError("Open device failed!"); } } + + return 0; } -static void openslES_WaitDevice(SDL_AudioDevice *_this) +static void openslES_WaitDevice(SDL_AudioDevice *device) { - struct SDL_PrivateAudioData *audiodata = _this->hidden; + struct SDL_PrivateAudioData *audiodata = device->hidden; LOGV("openslES_WaitDevice()"); - /* Wait for an audio chunk to finish */ + // Wait for an audio chunk to finish SDL_WaitSemaphore(audiodata->playsem); } -static void openslES_PlayDevice(SDL_AudioDevice *_this) +static void openslES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { - struct SDL_PrivateAudioData *audiodata = _this->hidden; - SLresult result; + struct SDL_PrivateAudioData *audiodata = device->hidden; LOGV("======openslES_PlayDevice()======"); - /* Queue it up */ - result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, audiodata->pmixbuff[audiodata->next_buffer], _this->spec.size); + // Queue it up + const SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, buflen); audiodata->next_buffer++; if (audiodata->next_buffer >= NUM_BUFFERS) { audiodata->next_buffer = 0; } - /* If Enqueue fails, callback won't be called. - * Post the semaphore, not to run out of buffer */ + // If Enqueue fails, callback won't be called. + // Post the semaphore, not to run out of buffer if (SL_RESULT_SUCCESS != result) { SDL_PostSemaphore(audiodata->playsem); } } -/*/ n playn sem */ -/* getbuf 0 - 1 */ -/* fill buff 0 - 1 */ -/* play 0 - 0 1 */ -/* wait 1 0 0 */ -/* getbuf 1 0 0 */ -/* fill buff 1 0 0 */ -/* play 0 0 0 */ -/* wait */ -/* */ -/* okay.. */ +/// n playn sem +// getbuf 0 - 1 +// fill buff 0 - 1 +// play 0 - 0 1 +// wait 1 0 0 +// getbuf 1 0 0 +// fill buff 1 0 0 +// play 0 0 0 +// wait +// +// okay.. -static Uint8 *openslES_GetDeviceBuf(SDL_AudioDevice *_this) +static Uint8 *openslES_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize) { - struct SDL_PrivateAudioData *audiodata = _this->hidden; + struct SDL_PrivateAudioData *audiodata = device->hidden; LOGV("openslES_GetDeviceBuf()"); return audiodata->pmixbuff[audiodata->next_buffer]; } -static int openslES_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen) +static int openslES_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { - struct SDL_PrivateAudioData *audiodata = _this->hidden; - SLresult result; + struct SDL_PrivateAudioData *audiodata = device->hidden; - /* Wait for new recorded data */ - SDL_WaitSemaphore(audiodata->playsem); + // Copy it to the output buffer + SDL_assert(buflen == device->buffer_size); + SDL_memcpy(buffer, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_size); - /* Copy it to the output buffer */ - SDL_assert(buflen == _this->spec.size); - SDL_memcpy(buffer, audiodata->pmixbuff[audiodata->next_buffer], _this->spec.size); - - /* Re-enqueue the buffer */ - result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[audiodata->next_buffer], _this->spec.size); + // Re-enqueue the buffer + const SLresult result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_size); if (SL_RESULT_SUCCESS != result) { LOGE("Record enqueue buffers failed: %d", result); return -1; @@ -705,22 +699,24 @@ static int openslES_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int audiodata->next_buffer = 0; } - return _this->spec.size; + return device->buffer_size; } -static void openslES_CloseDevice(SDL_AudioDevice *_this) +static void openslES_CloseDevice(SDL_AudioDevice *device) { - /* struct SDL_PrivateAudioData *audiodata = _this->hidden; */ + // struct SDL_PrivateAudioData *audiodata = device->hidden; + if (device->hidden) { + if (device->iscapture) { + LOGI("openslES_CloseDevice() for capture"); + openslES_DestroyPCMRecorder(device); + } else { + LOGI("openslES_CloseDevice() for playing"); + openslES_DestroyPCMPlayer(device); + } - if (_this->iscapture) { - LOGI("openslES_CloseDevice() for capture"); - openslES_DestroyPCMRecorder(_this); - } else { - LOGI("openslES_CloseDevice() for playing"); - openslES_DestroyPCMPlayer(_this); + SDL_free(device->hidden); + device->hidden = NULL; } - - SDL_free(_this->hidden); } static SDL_bool openslES_Init(SDL_AudioDriverImpl *impl) @@ -733,24 +729,26 @@ static SDL_bool openslES_Init(SDL_AudioDriverImpl *impl) LOGI("openslES_Init() - set pointers"); - /* Set the function pointers */ - /* impl->DetectDevices = openslES_DetectDevices; */ + // Set the function pointers + // impl->DetectDevices = openslES_DetectDevices; + impl->ThreadInit = Android_AudioThreadInit; impl->OpenDevice = openslES_OpenDevice; impl->WaitDevice = openslES_WaitDevice; impl->PlayDevice = openslES_PlayDevice; impl->GetDeviceBuf = openslES_GetDeviceBuf; + impl->WaitCaptureDevice = openslES_WaitDevice; impl->CaptureFromDevice = openslES_CaptureFromDevice; impl->CloseDevice = openslES_CloseDevice; impl->Deinitialize = openslES_DestroyEngine; - /* and the capabilities */ + // and the capabilities impl->HasCaptureSupport = SDL_TRUE; impl->OnlyHasDefaultOutputDevice = SDL_TRUE; impl->OnlyHasDefaultCaptureDevice = SDL_TRUE; LOGI("openslES_Init() - success"); - /* this audio target is available. */ + // this audio target is available. return SDL_TRUE; } @@ -761,7 +759,7 @@ AudioBootStrap openslES_bootstrap = { void openslES_ResumeDevices(void) { if (bqPlayerPlay != NULL) { - /* set the player's state to 'playing' */ + // set the player's state to 'playing' SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); if (SL_RESULT_SUCCESS != result) { LOGE("openslES_ResumeDevices failed: %d", result); @@ -772,7 +770,7 @@ void openslES_ResumeDevices(void) void openslES_PauseDevices(void) { if (bqPlayerPlay != NULL) { - /* set the player's state to 'paused' */ + // set the player's state to 'paused' SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED); if (SL_RESULT_SUCCESS != result) { LOGE("openslES_PauseDevices failed: %d", result); @@ -780,4 +778,4 @@ void openslES_PauseDevices(void) } } -#endif /* SDL_AUDIO_DRIVER_OPENSLES */ +#endif // SDL_AUDIO_DRIVER_OPENSLES diff --git a/src/audio/wasapi/SDL_wasapi_win32.c b/src/audio/wasapi/SDL_wasapi_win32.c index 75862371c4..719ac94a3b 100644 --- a/src/audio/wasapi/SDL_wasapi_win32.c +++ b/src/audio/wasapi/SDL_wasapi_win32.c @@ -86,7 +86,10 @@ void WASAPI_PlatformThreadInit(SDL_AudioDevice *device) if (pAvSetMmThreadCharacteristicsW) { DWORD idx = 0; device->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx); + } else { + SDL_SetThreadPriority(device->iscapture ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL); } + } void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device) diff --git a/src/audio/wasapi/SDL_wasapi_winrt.cpp b/src/audio/wasapi/SDL_wasapi_winrt.cpp index 373244ac73..a4030f39c2 100644 --- a/src/audio/wasapi/SDL_wasapi_winrt.cpp +++ b/src/audio/wasapi/SDL_wasapi_winrt.cpp @@ -334,6 +334,7 @@ int WASAPI_ActivateDevice(SDL_AudioDevice *device) void WASAPI_PlatformThreadInit(SDL_AudioDevice *device) { // !!! FIXME: set this thread to "Pro Audio" priority. + SDL_SetThreadPriority(device->iscapture ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL); } void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device) diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index 1d3909b693..ad71d3d644 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -233,7 +233,7 @@ JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)( JNIEnv *env, jclass jcls); JNIEXPORT void JNICALL - SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture, + SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture, jstring name, jint device_id); JNIEXPORT void JNICALL @@ -242,7 +242,7 @@ JNIEXPORT void JNICALL static JNINativeMethod SDLAudioManager_tab[] = { { "nativeSetupJNI", "()I", SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI) }, - { "addAudioDevice", "(ZI)V", SDL_JAVA_AUDIO_INTERFACE(addAudioDevice) }, + { "addAudioDevice", "(ZLjava/lang/String;I)V", SDL_JAVA_AUDIO_INTERFACE(addAudioDevice) }, { "removeAudioDevice", "(ZI)V", SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice) } }; @@ -350,8 +350,8 @@ static jmethodID midSupportsRelativeMouse; static jclass mAudioManagerClass; /* method signatures */ -static jmethodID midGetAudioOutputDevices; -static jmethodID midGetAudioInputDevices; +static jmethodID midRegisterAudioDeviceCallback; +static jmethodID midUnregisterAudioDeviceCallback; static jmethodID midAudioOpen; static jmethodID midAudioWriteByteBuffer; static jmethodID midAudioWriteShortBuffer; @@ -681,12 +681,12 @@ JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv *env, jcl mAudioManagerClass = (jclass)((*env)->NewGlobalRef(env, cls)); - midGetAudioOutputDevices = (*env)->GetStaticMethodID(env, mAudioManagerClass, - "getAudioOutputDevices", - "()[I"); - midGetAudioInputDevices = (*env)->GetStaticMethodID(env, mAudioManagerClass, - "getAudioInputDevices", - "()[I"); + midRegisterAudioDeviceCallback = (*env)->GetStaticMethodID(env, mAudioManagerClass, + "registerAudioDeviceCallback", + "()V"); + midUnregisterAudioDeviceCallback = (*env)->GetStaticMethodID(env, mAudioManagerClass, + "unregisterAudioDeviceCallback", + "()V"); midAudioOpen = (*env)->GetStaticMethodID(env, mAudioManagerClass, "audioOpen", "(IIIII)[I"); midAudioWriteByteBuffer = (*env)->GetStaticMethodID(env, mAudioManagerClass, @@ -710,7 +710,7 @@ JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv *env, jcl midAudioSetThreadPriority = (*env)->GetStaticMethodID(env, mAudioManagerClass, "audioSetThreadPriority", "(ZI)V"); - if (!midGetAudioOutputDevices || !midGetAudioInputDevices || !midAudioOpen || + if (!midRegisterAudioDeviceCallback || !midUnregisterAudioDeviceCallback || !midAudioOpen || !midAudioWriteByteBuffer || !midAudioWriteShortBuffer || !midAudioWriteFloatBuffer || !midAudioClose || !midCaptureOpen || !midCaptureReadByteBuffer || !midCaptureReadShortBuffer || @@ -1002,19 +1002,14 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)( SDL_AtomicSet(&bPermissionRequestPending, SDL_FALSE); } -extern void SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, SDL_AudioSpec *spec, void *handle); -extern void SDL_RemoveAudioDevice(const SDL_bool iscapture, void *handle); - JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture, - jint device_id) + jstring name, jint device_id) { - if (SDL_GetCurrentAudioDriver() != NULL) { - char device_name[64]; - SDL_snprintf(device_name, sizeof(device_name), "%d", device_id); - SDL_Log("Adding device with name %s, capture %d", device_name, is_capture); - SDL_AddAudioDevice(is_capture, SDL_strdup(device_name), NULL, (void *)((size_t)device_id + 1)); - } + SDL_assert(SDL_GetCurrentAudioDriver() != NULL); // should have been started by Android_StartAudioHotplug inside an audio driver. + const char *utf8name = (*env)->GetStringUTFChars(env, name, NULL); + SDL_AddAudioDevice(is_capture ? SDL_TRUE : SDL_FALSE, SDL_strdup(utf8name), NULL, (void *)((size_t)device_id)); + (*env)->ReleaseStringUTFChars(env, name, utf8name); } JNIEXPORT void JNICALL @@ -1022,8 +1017,8 @@ SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice)(JNIEnv *env, jclass jcls, jboolean i jint device_id) { if (SDL_GetCurrentAudioDriver() != NULL) { - SDL_Log("Removing device with handle %d, capture %d", device_id + 1, is_capture); - SDL_RemoveAudioDevice(is_capture, (void *)((size_t)device_id + 1)); + SDL_Log("Removing device with handle %d, capture %d", device_id, is_capture); + SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)device_id))); } } @@ -1564,58 +1559,25 @@ static void *audioBufferPinned = NULL; static int captureBufferFormat = 0; static jobject captureBuffer = NULL; -static void Android_JNI_GetAudioDevices(int *devices, int *length, int max_len, int is_input) +void Android_StartAudioHotplug(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { JNIEnv *env = Android_JNI_GetEnv(); - jintArray result; - - if (is_input) { - result = (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midGetAudioInputDevices); - } else { - result = (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midGetAudioOutputDevices); - } - - *length = (*env)->GetArrayLength(env, result); - - *length = SDL_min(*length, max_len); - - (*env)->GetIntArrayRegion(env, result, 0, *length, devices); + // this will fire the callback for each existing device right away (which will eventually SDL_AddAudioDevice), and again later when things change. + (void) (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midRegisterAudioDeviceCallback); + *default_output = *default_capture = NULL; // !!! FIXME: how do you decide the default device id? } -void Android_DetectDevices(void) +void Android_StopAudioHotplug(void) { - int inputs[100]; - int outputs[100]; - int inputs_length = 0; - int outputs_length = 0; - - SDL_zeroa(inputs); - - Android_JNI_GetAudioDevices(inputs, &inputs_length, 100, 1 /* input devices */); - - for (int i = 0; i < inputs_length; ++i) { - int device_id = inputs[i]; - char device_name[64]; - SDL_snprintf(device_name, sizeof(device_name), "%d", device_id); - SDL_Log("Adding input device with name %s", device_name); - SDL_AddAudioDevice(SDL_TRUE, SDL_strdup(device_name), NULL, (void *)((size_t)device_id + 1)); - } - - SDL_zeroa(outputs); - - Android_JNI_GetAudioDevices(outputs, &outputs_length, 100, 0 /* output devices */); - - for (int i = 0; i < outputs_length; ++i) { - int device_id = outputs[i]; - char device_name[64]; - SDL_snprintf(device_name, sizeof(device_name), "%d", device_id); - SDL_Log("Adding output device with name %s", device_name); - SDL_AddAudioDevice(SDL_FALSE, SDL_strdup(device_name), NULL, (void *)((size_t)device_id + 1)); - } + JNIEnv *env = Android_JNI_GetEnv(); + (void) (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midUnregisterAudioDeviceCallback); } -int Android_JNI_OpenAudioDevice(int iscapture, int device_id, SDL_AudioSpec *spec) +int Android_JNI_OpenAudioDevice(SDL_AudioDevice *device) { + const SDL_bool iscapture = device->iscapture; + SDL_AudioSpec *spec = &device->spec; + const int device_id = (int) ((size_t) device->handle); int audioformat; jobject jbufobj = NULL; jobject result; @@ -1640,10 +1602,10 @@ int Android_JNI_OpenAudioDevice(int iscapture, int device_id, SDL_AudioSpec *spe if (iscapture) { __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for capture"); - result = (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midCaptureOpen, spec->freq, audioformat, spec->channels, spec->samples, device_id); + result = (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midCaptureOpen, spec->freq, audioformat, spec->channels, device->sample_frames, device_id); } else { __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for output"); - result = (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midAudioOpen, spec->freq, audioformat, spec->channels, spec->samples, device_id); + result = (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midAudioOpen, spec->freq, audioformat, spec->channels, device->sample_frames, device_id); } if (result == NULL) { /* Error during audio initialization, error printed from Java */ @@ -1668,10 +1630,10 @@ int Android_JNI_OpenAudioDevice(int iscapture, int device_id, SDL_AudioSpec *spe spec->format = SDL_AUDIO_F32; break; default: - return SDL_SetError("Unexpected audio format from Java: %d\n", audioformat); + return SDL_SetError("Unexpected audio format from Java: %d", audioformat); } spec->channels = resultElements[2]; - spec->samples = resultElements[3]; + device->sample_frames = resultElements[3]; (*env)->ReleaseIntArrayElements(env, (jintArray)result, resultElements, JNI_ABORT); (*env)->DeleteLocalRef(env, result); @@ -1680,7 +1642,7 @@ int Android_JNI_OpenAudioDevice(int iscapture, int device_id, SDL_AudioSpec *spe switch (audioformat) { case ENCODING_PCM_8BIT: { - jbyteArray audioBufferLocal = (*env)->NewByteArray(env, spec->samples * spec->channels); + jbyteArray audioBufferLocal = (*env)->NewByteArray(env, device->sample_frames * spec->channels); if (audioBufferLocal) { jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal); (*env)->DeleteLocalRef(env, audioBufferLocal); @@ -1688,7 +1650,7 @@ int Android_JNI_OpenAudioDevice(int iscapture, int device_id, SDL_AudioSpec *spe } break; case ENCODING_PCM_16BIT: { - jshortArray audioBufferLocal = (*env)->NewShortArray(env, spec->samples * spec->channels); + jshortArray audioBufferLocal = (*env)->NewShortArray(env, device->sample_frames * spec->channels); if (audioBufferLocal) { jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal); (*env)->DeleteLocalRef(env, audioBufferLocal); @@ -1696,7 +1658,7 @@ int Android_JNI_OpenAudioDevice(int iscapture, int device_id, SDL_AudioSpec *spe } break; case ENCODING_PCM_FLOAT: { - jfloatArray audioBufferLocal = (*env)->NewFloatArray(env, spec->samples * spec->channels); + jfloatArray audioBufferLocal = (*env)->NewFloatArray(env, device->sample_frames * spec->channels); if (audioBufferLocal) { jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal); (*env)->DeleteLocalRef(env, audioBufferLocal); @@ -1887,12 +1849,17 @@ void Android_JNI_CloseAudioDevice(const int iscapture) } } -void Android_JNI_AudioSetThreadPriority(int iscapture, int device_id) +static void Android_JNI_AudioSetThreadPriority(int iscapture, int device_id) { JNIEnv *env = Android_JNI_GetEnv(); (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioSetThreadPriority, iscapture, device_id); } +void Android_AudioThreadInit(SDL_AudioDevice *device) +{ + Android_JNI_AudioSetThreadPriority((int) device->iscapture, (int)device->instance_id); +} + /* Test for an exception and call SDL_SetError with its detail if one occurs */ /* If the parameter silent is truthy then SDL_SetError() will not be called. */ static SDL_bool Android_JNI_ExceptionOccurred(SDL_bool silent) diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h index a245af9800..be9b3d2bd5 100644 --- a/src/core/android/SDL_android.h +++ b/src/core/android/SDL_android.h @@ -30,6 +30,8 @@ extern "C" { #include #include +#include "../../audio/SDL_sysaudio.h" + /* Interface from the SDL library into the Android Java activity */ extern void Android_JNI_SetActivityTitle(const char *title); extern void Android_JNI_SetWindowStyle(SDL_bool fullscreen); @@ -47,14 +49,15 @@ extern SDL_DisplayOrientation Android_JNI_GetDisplayNaturalOrientation(void); extern SDL_DisplayOrientation Android_JNI_GetDisplayCurrentOrientation(void); /* Audio support */ -extern void Android_DetectDevices(void); -extern int Android_JNI_OpenAudioDevice(int iscapture, int device_id, SDL_AudioSpec *spec); +void Android_StartAudioHotplug(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture); +void Android_StopAudioHotplug(void); +extern void Android_AudioThreadInit(SDL_AudioDevice *device); +extern int Android_JNI_OpenAudioDevice(SDL_AudioDevice *device); extern void *Android_JNI_GetAudioBuffer(void); extern void Android_JNI_WriteAudioBuffer(void); extern int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen); extern void Android_JNI_FlushCapturedAudio(void); extern void Android_JNI_CloseAudioDevice(const int iscapture); -extern void Android_JNI_AudioSetThreadPriority(int iscapture, int device_id); /* Detecting device type */ extern SDL_bool Android_IsDeXMode(void); From ab68428a64a845fa07d8926c358cba5d61b444d2 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 27 Jul 2023 12:28:53 -0400 Subject: [PATCH 123/138] aaudio: Fixed for older SDKs and Android releases. --- src/audio/aaudio/SDL_aaudio.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c index 777286bf38..e6e0ef29a0 100644 --- a/src/audio/aaudio/SDL_aaudio.c +++ b/src/audio/aaudio/SDL_aaudio.c @@ -30,6 +30,10 @@ #include #include +#if __ANDROID_API__ < 31 +#define AAUDIO_FORMAT_PCM_I32 4 +#endif + struct SDL_PrivateAudioData { AAudioStream *stream; @@ -119,7 +123,7 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device) const aaudio_direction_t direction = (iscapture ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT); ctx.AAudioStreamBuilder_setDirection(builder, direction); aaudio_format_t format; - if (device->spec.format == SDL_AUDIO_S32SYS) { + if ((device->spec.format == SDL_AUDIO_S32SYS) && (SDL_GetAndroidSDKVersion() >= 31)) { format = AAUDIO_FORMAT_PCM_I32; } else if (device->spec.format == SDL_AUDIO_F32SYS) { format = AAUDIO_FORMAT_PCM_FLOAT; From 660054f3dc80fe1b9354d9d2ea3e99d50eb4be1d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 27 Jul 2023 18:12:34 -0400 Subject: [PATCH 124/138] include: Correct comment about audio device hotplug events. --- include/SDL3/SDL_events.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index cc3ee292c4..082305765d 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -493,7 +493,7 @@ typedef struct SDL_AudioDeviceEvent { Uint32 type; /**< ::SDL_EVENT_AUDIO_DEVICE_ADDED, or ::SDL_EVENT_AUDIO_DEVICE_REMOVED */ Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ - SDL_AudioDeviceID which; /**< The audio device index for the ADDED event (valid until next SDL_GetNumAudioDevices() call), SDL_AudioDeviceID for the REMOVED event */ + SDL_AudioDeviceID which; /**< SDL_AudioDeviceID for the device being added or removed */ Uint8 iscapture; /**< zero if an output device, non-zero if a capture device. */ Uint8 padding1; Uint8 padding2; From 5cbdf1168e05fbb652900458a00ceba81e2d1896 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 27 Jul 2023 18:13:01 -0400 Subject: [PATCH 125/138] androidaudio: Fixed incorrect JNI call (thanks, @madebr!) --- src/core/android/SDL_android.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index ad71d3d644..e00d99988f 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -1563,14 +1563,14 @@ void Android_StartAudioHotplug(SDL_AudioDevice **default_output, SDL_AudioDevice { JNIEnv *env = Android_JNI_GetEnv(); // this will fire the callback for each existing device right away (which will eventually SDL_AddAudioDevice), and again later when things change. - (void) (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midRegisterAudioDeviceCallback); + (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midRegisterAudioDeviceCallback); *default_output = *default_capture = NULL; // !!! FIXME: how do you decide the default device id? } void Android_StopAudioHotplug(void) { JNIEnv *env = Android_JNI_GetEnv(); - (void) (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midUnregisterAudioDeviceCallback); + (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midUnregisterAudioDeviceCallback); } int Android_JNI_OpenAudioDevice(SDL_AudioDevice *device) From 82ce05ad017e8b074bbe9d67178293839b74f70a Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 27 Jul 2023 18:22:20 -0400 Subject: [PATCH 126/138] pulseaudio: Be more aggressive with hotplug thread synchronization. (Borrowed from the SDL2 branch.) --- src/audio/pulseaudio/SDL_pulseaudio.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c index 5644802c9a..fb6c351827 100644 --- a/src/audio/pulseaudio/SDL_pulseaudio.c +++ b/src/audio/pulseaudio/SDL_pulseaudio.c @@ -847,28 +847,47 @@ static int SDLCALL HotplugThread(void *data) { uint32_t prev_default_sink_index = default_sink_index; uint32_t prev_default_source_index = default_source_index; + pa_operation *op; SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, HotplugCallback, NULL); - PULSEAUDIO_pa_operation_unref(PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL)); + + /* don't WaitForPulseOperation on the subscription; when it's done we'll be able to get hotplug events, but waiting doesn't changing anything. */ + op = PULSEAUDIO_pa_context_subscribe(pulseaudio_context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL); + + SDL_PostSemaphore((SDL_Semaphore *) data); + while (SDL_AtomicGet(&pulseaudio_hotplug_thread_active)) { PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop); + if (op && PULSEAUDIO_pa_operation_get_state(op) != PA_OPERATION_RUNNING) { + PULSEAUDIO_pa_operation_unref(op); + op = NULL; + } // Update default devices; don't hold the pulse lock during this, since it could deadlock vs a playing device that we're about to lock here. PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); CheckDefaultDevice(&prev_default_sink_index, default_sink_index); CheckDefaultDevice(&prev_default_source_index, default_source_index); PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); + } - PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, NULL, NULL); // Don't fire HotplugCallback again. + if (op) { + PULSEAUDIO_pa_operation_unref(op); + } + + PULSEAUDIO_pa_context_set_subscribe_callback(pulseaudio_context, NULL, NULL); PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop); return 0; + + } static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture) { + SDL_Semaphore *ready_sem = SDL_CreateSemaphore(0); + PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop); WaitForPulseOperation(PULSEAUDIO_pa_context_get_server_info(pulseaudio_context, ServerInfoCallback, NULL)); WaitForPulseOperation(PULSEAUDIO_pa_context_get_sink_info_list(pulseaudio_context, SinkInfoCallback, (void *)((intptr_t)SDL_TRUE))); @@ -888,7 +907,9 @@ static void PULSEAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_Audio /* ok, we have a sane list, let's set up hotplug notifications now... */ SDL_AtomicSet(&pulseaudio_hotplug_thread_active, 1); - pulseaudio_hotplug_thread = SDL_CreateThreadInternal(HotplugThread, "PulseHotplug", 256 * 1024, NULL); /* !!! FIXME: this can probably survive in significantly less stack space. */ + pulseaudio_hotplug_thread = SDL_CreateThreadInternal(HotplugThread, "PulseHotplug", 256 * 1024, ready_sem); // !!! FIXME: this can probably survive in significantly less stack space. + SDL_WaitSemaphore(ready_sem); + SDL_DestroySemaphore(ready_sem); } static void PULSEAUDIO_Deinitialize(void) From 1c074e8d9785d55fc720c06801a8010cf8a6cc6a Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 29 Jul 2023 19:43:24 -0400 Subject: [PATCH 127/138] android: Fixed audio device detection. --- .../main/java/org/libsdl/app/SDLAudioManager.java | 10 ++++++++++ src/audio/android/SDL_androidaudio.c | 1 + src/core/android/SDL_android.c | 12 ++++++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java b/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java index e64a0790d4..8c84b1c295 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLAudioManager.java @@ -309,6 +309,16 @@ public class SDLAudioManager { public static void registerAudioDeviceCallback() { if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + // get an initial list now, before hotplug callbacks fire. + for (AudioDeviceInfo dev : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) { + if (dev.getType() == AudioDeviceInfo.TYPE_TELEPHONY) { + continue; // Device cannot be opened + } + addAudioDevice(dev.isSink(), dev.getProductName().toString(), dev.getId()); + } + for (AudioDeviceInfo dev : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) { + addAudioDevice(dev.isSink(), dev.getProductName().toString(), dev.getId()); + } audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null); } } diff --git a/src/audio/android/SDL_androidaudio.c b/src/audio/android/SDL_androidaudio.c index 7dbb8541af..c87f2b3f32 100644 --- a/src/audio/android/SDL_androidaudio.c +++ b/src/audio/android/SDL_androidaudio.c @@ -128,6 +128,7 @@ static void ANDROIDAUDIO_CloseDevice(SDL_AudioDevice *device) static SDL_bool ANDROIDAUDIO_Init(SDL_AudioDriverImpl *impl) { + // !!! FIXME: if on Android API < 24, DetectDevices and Deinitialize should be NULL and OnlyHasDefaultOutputDevice and OnlyHasDefaultCaptureDevice should be SDL_TRUE, since audio device enum and hotplug appears to require Android 7.0+. impl->ThreadInit = Android_AudioThreadInit; impl->DetectDevices = Android_StartAudioHotplug; impl->Deinitialize = Android_StopAudioHotplug; diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index e00d99988f..28a9b04735 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -1006,10 +1006,14 @@ JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean is_capture, jstring name, jint device_id) { - SDL_assert(SDL_GetCurrentAudioDriver() != NULL); // should have been started by Android_StartAudioHotplug inside an audio driver. - const char *utf8name = (*env)->GetStringUTFChars(env, name, NULL); - SDL_AddAudioDevice(is_capture ? SDL_TRUE : SDL_FALSE, SDL_strdup(utf8name), NULL, (void *)((size_t)device_id)); - (*env)->ReleaseStringUTFChars(env, name, utf8name); + if (SDL_GetCurrentAudioDriver() != NULL) { + void *handle = (void *)((size_t)device_id); + if (!SDL_FindPhysicalAudioDeviceByHandle(handle)) { + const char *utf8name = (*env)->GetStringUTFChars(env, name, NULL); + SDL_AddAudioDevice(is_capture ? SDL_TRUE : SDL_FALSE, SDL_strdup(utf8name), NULL, handle); + (*env)->ReleaseStringUTFChars(env, name, utf8name); + } + } } JNIEXPORT void JNICALL From b49ce86765f5ced6935a33ce5a08604ea973b3c6 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 29 Jul 2023 19:43:41 -0400 Subject: [PATCH 128/138] audio: Fixed compiler warning on Android NDK. --- src/audio/SDL_audiocvt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index d7e2c54f41..0098504e22 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -1036,7 +1036,7 @@ int SDL_GetAudioStreamData(SDL_AudioStream *stream, void *voidbuf, int len) int approx_request = len / stream->dst_sample_frame_size; // start with sample frames desired if (stream->src_spec.freq != stream->dst_spec.freq) { // calculate difference in dataset size after resampling. Use a Uint64 so the multiplication doesn't overflow. - approx_request = (size_t) ((((Uint64) approx_request) * stream->src_spec.freq) / stream->dst_spec.freq); + approx_request = (int) (size_t) ((((Uint64) approx_request) * stream->src_spec.freq) / stream->dst_spec.freq); if (!stream->flushed) { // do we need to fill the future buffer to accomodate this, too? approx_request += stream->future_buffer_filled_frames - stream->resampler_padding_frames; } From 32a3fc3783a89fed481eaa4d487d56b7d6d5438d Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 29 Jul 2023 19:44:23 -0400 Subject: [PATCH 129/138] aaudio: Use the callback interface. This is allegedly lower-latency than the AAudioStream_write interface, but more importantly, it let me set this up to block in WaitDevice. Also turned on the low-latency performance mode, which trades battery life for a more efficient audio thread to some unspecified degree. --- src/audio/aaudio/SDL_aaudio.c | 108 +++++++++++++++++------------ src/audio/aaudio/SDL_aaudiofuncs.h | 16 ++--- 2 files changed, 73 insertions(+), 51 deletions(-) diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c index e6e0ef29a0..1ac0b0b347 100644 --- a/src/audio/aaudio/SDL_aaudio.c +++ b/src/audio/aaudio/SDL_aaudio.c @@ -37,10 +37,8 @@ struct SDL_PrivateAudioData { AAudioStream *stream; - Uint8 *mixbuf; // Raw mixing buffer - int frame_size; - + SDL_Semaphore *semaphore; int resume; // Resume device if it was paused automatically }; @@ -75,8 +73,13 @@ static int AAUDIO_LoadFunctions(AAUDIO_Data *data) static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error) { LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error)); + // !!! FIXME: you MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here. + // !!! FIXME: but we should flag the device and kill it in WaitDevice/PlayDevice. } +static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames); + + #define LIB_AAUDIO_SO "libaaudio.so" static int AAUDIO_OpenDevice(SDL_AudioDevice *device) @@ -132,21 +135,39 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device) } ctx.AAudioStreamBuilder_setFormat(builder, format); - - ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, hidden); + ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, device); + ctx.AAudioStreamBuilder_setDataCallback(builder, AAUDIO_dataCallback, device); + ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u", device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format), - device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames); + device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames); res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream); - ctx.AAudioStreamBuilder_delete(builder); if (res != AAUDIO_OK) { LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res); + ctx.AAudioStreamBuilder_delete(builder); return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); } + device->sample_frames = (int) ctx.AAudioStream_getFramesPerDataCallback(hidden->stream); + if (device->sample_frames == AAUDIO_UNSPECIFIED) { + // if this happens, figure out a reasonable sample frame count, tear down this stream and force it in a new stream. + device->sample_frames = (int) (ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 4); + LOGI("AAUDIO: Got a stream with unspecified sample frames per data callback! Retrying with %d frames...", device->sample_frames); + ctx.AAudioStream_close(hidden->stream); + ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, device->sample_frames); + res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream); + if (res != AAUDIO_OK) { // oh well, we tried. + LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res); + ctx.AAudioStreamBuilder_delete(builder); + return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); + } + } + + ctx.AAudioStreamBuilder_delete(builder); + device->spec.freq = ctx.AAudioStream_getSampleRate(hidden->stream); device->spec.channels = ctx.AAudioStream_getChannelCount(hidden->stream); @@ -161,9 +182,7 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device) return SDL_SetError("Got unexpected audio format %d from AAudioStream_getFormat", (int) format); } - device->sample_frames = ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 2; - - LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u", + LOGI("AAudio Actually opened %u hz %u bit chan %u %s samples %u", device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format), device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames); @@ -178,7 +197,11 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device) SDL_memset(hidden->mixbuf, device->silence_value, device->buffer_size); } - hidden->frame_size = device->spec.channels * (SDL_AUDIO_BITSIZE(device->spec.format) / 8); + hidden->semaphore = SDL_CreateSemaphore(0); + if (!hidden->semaphore) { + LOGI("SDL Failed SDL_CreateSemaphore %s iscapture:%d", SDL_GetError(), iscapture); + return -1; + } res = ctx.AAudioStream_requestStart(hidden->stream); if (res != AAUDIO_OK) { @@ -193,20 +216,42 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device) static void AAUDIO_CloseDevice(SDL_AudioDevice *device) { struct SDL_PrivateAudioData *hidden = device->hidden; - if (hidden) { - LOGI(__func__); + LOGI(__func__); + if (hidden) { if (hidden->stream) { ctx.AAudioStream_requestStop(hidden->stream); + // !!! FIXME: do we have to wait for the state to change to make sure all buffered audio has played, or will close do this (or will the system do this after the close)? + // !!! FIXME: also, will this definitely wait for a running data callback to finish, and then stop the callback from firing again? ctx.AAudioStream_close(hidden->stream); } + if (hidden->semaphore) { + SDL_DestroySemaphore(hidden->semaphore); + } + SDL_free(hidden->mixbuf); SDL_free(hidden); device->hidden = NULL; } } +// due to the way the aaudio data callback works, PlayDevice is a no-op. The callback collects audio while SDL camps in WaitDevice and +// fires a semaphore that will unblock WaitDevice and start a new iteration, so when the callback runs again, WaitDevice is ready +// to hand it more data. +static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames) +{ + SDL_AudioDevice *device = (SDL_AudioDevice *) userData; + SDL_assert(numFrames == device->sample_frames); + if (device->iscapture) { + SDL_memcpy(device->hidden->mixbuf, audioData, device->buffer_size); + } else { + SDL_memcpy(audioData, device->hidden->mixbuf, device->buffer_size); + } + SDL_PostSemaphore(device->hidden->semaphore); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} + static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize) { return device->hidden->mixbuf; @@ -214,44 +259,20 @@ static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize) static void AAUDIO_WaitDevice(SDL_AudioDevice *device) { - AAudioStream *stream = device->hidden->stream; - while (!SDL_AtomicGet(&device->shutdown) && ((int) ctx.AAudioStream_getBufferSizeInFrames(stream)) < device->sample_frames) { - SDL_Delay(1); - } + SDL_WaitSemaphore(device->hidden->semaphore); } static void AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { - AAudioStream *stream = device->hidden->stream; - const aaudio_result_t res = ctx.AAudioStream_write(stream, buffer, device->sample_frames, 0); - if (res < 0) { - LOGI("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); - } else { - LOGI("SDL AAudio play: %d frames, wanted:%d frames", (int)res, sample_frames); - } - -#if 0 - // Log under-run count - { - static int prev = 0; - int32_t cnt = ctx.AAudioStream_getXRunCount(hidden->stream); - if (cnt != prev) { - SDL_Log("AAudio underrun: %d - total: %d", cnt - prev, cnt); - prev = cnt; - } - } -#endif + // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. } +// no need for a FlushCapture implementation, just don't read mixbuf until the next iteration. static int AAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) { - const aaudio_result_t res = ctx.AAudioStream_read(device->hidden->stream, buffer, device->sample_frames, 0); - if (res < 0) { - LOGI("%s : %s", __func__, ctx.AAudio_convertResultToText(res)); - return -1; - } - LOGI("SDL AAudio capture:%d frames, wanted:%d frames", (int)res, buflen / device->hidden->frame_size); - return res * device->hidden->frame_size; + const int cpy = SDL_min(buflen, device->buffer_size); + SDL_memcpy(buffer, device->hidden->mixbuf, cpy); + return cpy; } static void AAUDIO_Deinitialize(void) @@ -377,6 +398,7 @@ void AAUDIO_ResumeDevices(void) } } +// !!! FIXME: do we need this now that we use the callback? /* We can sometimes get into a state where AAudioStream_write() will just block forever until we pause and unpause. None of the standard state queries indicate any problem in my testing. And the error callback doesn't actually get called. diff --git a/src/audio/aaudio/SDL_aaudiofuncs.h b/src/audio/aaudio/SDL_aaudiofuncs.h index f6e860d6a1..febbb89da8 100644 --- a/src/audio/aaudio/SDL_aaudiofuncs.h +++ b/src/audio/aaudio/SDL_aaudiofuncs.h @@ -32,15 +32,15 @@ SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aa SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode)) SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction)) SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames)) -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode)) +SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode)) SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) /* API 28 */ SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) /* API 28 */ SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) /* API 28 */ SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) /* API 29 */ SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) /* API 28 */ SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) /* API 30 */ -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setDataCallback, (AAudioStreamBuilder * builder, AAudioStream_dataCallback callback, void *userData)) -SDL_PROC_UNUSED(void, AAudioStreamBuilder_setFramesPerDataCallback, (AAudioStreamBuilder * builder, int32_t numFrames)) +SDL_PROC(void, AAudioStreamBuilder_setDataCallback, (AAudioStreamBuilder * builder, AAudioStream_dataCallback callback, void *userData)) +SDL_PROC(void, AAudioStreamBuilder_setFramesPerDataCallback, (AAudioStreamBuilder * builder, int32_t numFrames)) SDL_PROC(void, AAudioStreamBuilder_setErrorCallback, (AAudioStreamBuilder * builder, AAudioStream_errorCallback callback, void *userData)) SDL_PROC(aaudio_result_t, AAudioStreamBuilder_openStream, (AAudioStreamBuilder * builder, AAudioStream **stream)) SDL_PROC(aaudio_result_t, AAudioStreamBuilder_delete, (AAudioStreamBuilder * builder)) @@ -52,14 +52,14 @@ SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_requestFlush, (AAudioStream * stre SDL_PROC(aaudio_result_t, AAudioStream_requestStop, (AAudioStream * stream)) SDL_PROC(aaudio_stream_state_t, AAudioStream_getState, (AAudioStream * stream)) SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_waitForStateChange, (AAudioStream * stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds)) -SDL_PROC(aaudio_result_t, AAudioStream_read, (AAudioStream * stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds)) -SDL_PROC(aaudio_result_t, AAudioStream_write, (AAudioStream * stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds)) +SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_read, (AAudioStream * stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds)) +SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_write, (AAudioStream * stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds)) SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_setBufferSizeInFrames, (AAudioStream * stream, int32_t numFrames)) -SDL_PROC(int32_t, AAudioStream_getBufferSizeInFrames, (AAudioStream * stream)) +SDL_PROC_UNUSED(int32_t, AAudioStream_getBufferSizeInFrames, (AAudioStream * stream)) SDL_PROC_UNUSED(int32_t, AAudioStream_getFramesPerBurst, (AAudioStream * stream)) SDL_PROC(int32_t, AAudioStream_getBufferCapacityInFrames, (AAudioStream * stream)) -SDL_PROC_UNUSED(int32_t, AAudioStream_getFramesPerDataCallback, (AAudioStream * stream)) -SDL_PROC(int32_t, AAudioStream_getXRunCount, (AAudioStream * stream)) +SDL_PROC(int32_t, AAudioStream_getFramesPerDataCallback, (AAudioStream * stream)) +SDL_PROC_UNUSED(int32_t, AAudioStream_getXRunCount, (AAudioStream * stream)) SDL_PROC(int32_t, AAudioStream_getSampleRate, (AAudioStream * stream)) SDL_PROC(int32_t, AAudioStream_getChannelCount, (AAudioStream * stream)) SDL_PROC_UNUSED(int32_t, AAudioStream_getSamplesPerFrame, (AAudioStream * stream)) From 2507c1d68b5ab234417cd45d0f065229aa61e06a Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 29 Jul 2023 19:53:38 -0400 Subject: [PATCH 130/138] aaudio: Disconnect playing devices if error callback fires. --- src/audio/aaudio/SDL_aaudio.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c index 1ac0b0b347..f49599eb39 100644 --- a/src/audio/aaudio/SDL_aaudio.c +++ b/src/audio/aaudio/SDL_aaudio.c @@ -39,6 +39,7 @@ struct SDL_PrivateAudioData AAudioStream *stream; Uint8 *mixbuf; // Raw mixing buffer SDL_Semaphore *semaphore; + SDL_AtomicInt error_callback_triggered; int resume; // Resume device if it was paused automatically }; @@ -73,8 +74,12 @@ static int AAUDIO_LoadFunctions(AAUDIO_Data *data) static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error) { LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error)); - // !!! FIXME: you MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here. - // !!! FIXME: but we should flag the device and kill it in WaitDevice/PlayDevice. + + // You MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here. + // Just flag the device so we can kill it in WaitDevice/PlayDevice instead. + SDL_AudioDevice *device = (SDL_AudioDevice *) userData; + SDL_AtomicSet(&device->hidden->error_callback_triggered, 1); + SDL_PostSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice. } static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames); @@ -104,6 +109,8 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device) return SDL_OutOfMemory(); } + SDL_AtomicSet(&hidden->error_callback_triggered, 0); + AAudioStreamBuilder *builder = NULL; res = ctx.AAudio_createStreamBuilder(&builder); if (res != AAUDIO_OK) { @@ -264,7 +271,11 @@ static void AAUDIO_WaitDevice(SDL_AudioDevice *device) static void AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) { - // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. + // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here. + if (SDL_AtomicGet(&device->hidden->error_callback_triggered)) { + SDL_AtomicSet(&device->hidden->error_callback_triggered, 0); + SDL_AudioDeviceDisconnected(device); + } } // no need for a FlushCapture implementation, just don't read mixbuf until the next iteration. From 18fc0db9e51058060f19dc22fe801edc0427fc40 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 29 Jul 2023 19:56:51 -0400 Subject: [PATCH 131/138] aaudio: Rearranged source code to match other backends. --- src/audio/aaudio/SDL_aaudio.c | 256 +++++++++++++++++----------------- 1 file changed, 127 insertions(+), 129 deletions(-) diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c index f49599eb39..aeaf58a868 100644 --- a/src/audio/aaudio/SDL_aaudio.c +++ b/src/audio/aaudio/SDL_aaudio.c @@ -50,6 +50,8 @@ struct SDL_PrivateAudioData #define LOGI(...) #endif +#define LIB_AAUDIO_SO "libaaudio.so" + typedef struct AAUDIO_Data { void *handle; @@ -71,6 +73,7 @@ static int AAUDIO_LoadFunctions(AAUDIO_Data *data) return 0; } + static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error) { LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error)); @@ -82,10 +85,71 @@ static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_re SDL_PostSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice. } -static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames); +// due to the way the aaudio data callback works, PlayDevice is a no-op. The callback collects audio while SDL camps in WaitDevice and +// fires a semaphore that will unblock WaitDevice and start a new iteration, so when the callback runs again, WaitDevice is ready +// to hand it more data. +static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames) +{ + SDL_AudioDevice *device = (SDL_AudioDevice *) userData; + SDL_assert(numFrames == device->sample_frames); + if (device->iscapture) { + SDL_memcpy(device->hidden->mixbuf, audioData, device->buffer_size); + } else { + SDL_memcpy(audioData, device->hidden->mixbuf, device->buffer_size); + } + SDL_PostSemaphore(device->hidden->semaphore); + return AAUDIO_CALLBACK_RESULT_CONTINUE; +} +static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize) +{ + return device->hidden->mixbuf; +} -#define LIB_AAUDIO_SO "libaaudio.so" +static void AAUDIO_WaitDevice(SDL_AudioDevice *device) +{ + SDL_WaitSemaphore(device->hidden->semaphore); +} + +static void AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) +{ + // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here. + if (SDL_AtomicGet(&device->hidden->error_callback_triggered)) { + SDL_AtomicSet(&device->hidden->error_callback_triggered, 0); + SDL_AudioDeviceDisconnected(device); + } +} + +// no need for a FlushCapture implementation, just don't read mixbuf until the next iteration. +static int AAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) +{ + const int cpy = SDL_min(buflen, device->buffer_size); + SDL_memcpy(buffer, device->hidden->mixbuf, cpy); + return cpy; +} + +static void AAUDIO_CloseDevice(SDL_AudioDevice *device) +{ + struct SDL_PrivateAudioData *hidden = device->hidden; + LOGI(__func__); + + if (hidden) { + if (hidden->stream) { + ctx.AAudioStream_requestStop(hidden->stream); + // !!! FIXME: do we have to wait for the state to change to make sure all buffered audio has played, or will close do this (or will the system do this after the close)? + // !!! FIXME: also, will this definitely wait for a running data callback to finish, and then stop the callback from firing again? + ctx.AAudioStream_close(hidden->stream); + } + + if (hidden->semaphore) { + SDL_DestroySemaphore(hidden->semaphore); + } + + SDL_free(hidden->mixbuf); + SDL_free(hidden); + device->hidden = NULL; + } +} static int AAUDIO_OpenDevice(SDL_AudioDevice *device) { @@ -220,133 +284,6 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device) return 0; } -static void AAUDIO_CloseDevice(SDL_AudioDevice *device) -{ - struct SDL_PrivateAudioData *hidden = device->hidden; - LOGI(__func__); - - if (hidden) { - if (hidden->stream) { - ctx.AAudioStream_requestStop(hidden->stream); - // !!! FIXME: do we have to wait for the state to change to make sure all buffered audio has played, or will close do this (or will the system do this after the close)? - // !!! FIXME: also, will this definitely wait for a running data callback to finish, and then stop the callback from firing again? - ctx.AAudioStream_close(hidden->stream); - } - - if (hidden->semaphore) { - SDL_DestroySemaphore(hidden->semaphore); - } - - SDL_free(hidden->mixbuf); - SDL_free(hidden); - device->hidden = NULL; - } -} - -// due to the way the aaudio data callback works, PlayDevice is a no-op. The callback collects audio while SDL camps in WaitDevice and -// fires a semaphore that will unblock WaitDevice and start a new iteration, so when the callback runs again, WaitDevice is ready -// to hand it more data. -static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames) -{ - SDL_AudioDevice *device = (SDL_AudioDevice *) userData; - SDL_assert(numFrames == device->sample_frames); - if (device->iscapture) { - SDL_memcpy(device->hidden->mixbuf, audioData, device->buffer_size); - } else { - SDL_memcpy(audioData, device->hidden->mixbuf, device->buffer_size); - } - SDL_PostSemaphore(device->hidden->semaphore); - return AAUDIO_CALLBACK_RESULT_CONTINUE; -} - -static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize) -{ - return device->hidden->mixbuf; -} - -static void AAUDIO_WaitDevice(SDL_AudioDevice *device) -{ - SDL_WaitSemaphore(device->hidden->semaphore); -} - -static void AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen) -{ - // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here. - if (SDL_AtomicGet(&device->hidden->error_callback_triggered)) { - SDL_AtomicSet(&device->hidden->error_callback_triggered, 0); - SDL_AudioDeviceDisconnected(device); - } -} - -// no need for a FlushCapture implementation, just don't read mixbuf until the next iteration. -static int AAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen) -{ - const int cpy = SDL_min(buflen, device->buffer_size); - SDL_memcpy(buffer, device->hidden->mixbuf, cpy); - return cpy; -} - -static void AAUDIO_Deinitialize(void) -{ - Android_StopAudioHotplug(); - - LOGI(__func__); - if (ctx.handle) { - SDL_UnloadObject(ctx.handle); - } - SDL_zero(ctx); - LOGI("End AAUDIO %s", SDL_GetError()); -} - -static SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl) -{ - LOGI(__func__); - - /* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release, - * so don't use it until 8.1. - * - * See https://github.com/google/oboe/issues/40 for more information. - */ - if (SDL_GetAndroidSDKVersion() < 27) { - return SDL_FALSE; - } - - SDL_zero(ctx); - - ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO); - if (ctx.handle == NULL) { - LOGI("SDL couldn't find " LIB_AAUDIO_SO); - return SDL_FALSE; - } - - if (AAUDIO_LoadFunctions(&ctx) < 0) { - SDL_UnloadObject(ctx.handle); - SDL_zero(ctx); - return SDL_FALSE; - } - - impl->ThreadInit = Android_AudioThreadInit; - impl->DetectDevices = Android_StartAudioHotplug; - impl->Deinitialize = AAUDIO_Deinitialize; - impl->OpenDevice = AAUDIO_OpenDevice; - impl->CloseDevice = AAUDIO_CloseDevice; - impl->WaitDevice = AAUDIO_WaitDevice; - impl->PlayDevice = AAUDIO_PlayDevice; - impl->GetDeviceBuf = AAUDIO_GetDeviceBuf; - impl->WaitCaptureDevice = AAUDIO_WaitDevice; - impl->CaptureFromDevice = AAUDIO_CaptureFromDevice; - - impl->HasCaptureSupport = SDL_TRUE; - - LOGI("SDL AAUDIO_Init OK"); - return SDL_TRUE; -} - -AudioBootStrap AAUDIO_bootstrap = { - "AAudio", "AAudio audio driver", AAUDIO_Init, SDL_FALSE -}; - - static SDL_bool PauseOneDevice(SDL_AudioDevice *device, void *userdata) { struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden; @@ -440,4 +377,65 @@ SDL_bool AAUDIO_DetectBrokenPlayState(void) return (ctx.handle && SDL_FindPhysicalAudioDeviceByCallback(DetectBrokenPlayStatePerDevice, NULL) != NULL) ? SDL_TRUE : SDL_FALSE; } +static void AAUDIO_Deinitialize(void) +{ + Android_StopAudioHotplug(); + + LOGI(__func__); + if (ctx.handle) { + SDL_UnloadObject(ctx.handle); + } + SDL_zero(ctx); + LOGI("End AAUDIO %s", SDL_GetError()); +} + + +static SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl) +{ + LOGI(__func__); + + /* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release, + * so don't use it until 8.1. + * + * See https://github.com/google/oboe/issues/40 for more information. + */ + if (SDL_GetAndroidSDKVersion() < 27) { + return SDL_FALSE; + } + + SDL_zero(ctx); + + ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO); + if (ctx.handle == NULL) { + LOGI("SDL couldn't find " LIB_AAUDIO_SO); + return SDL_FALSE; + } + + if (AAUDIO_LoadFunctions(&ctx) < 0) { + SDL_UnloadObject(ctx.handle); + SDL_zero(ctx); + return SDL_FALSE; + } + + impl->ThreadInit = Android_AudioThreadInit; + impl->DetectDevices = Android_StartAudioHotplug; + impl->Deinitialize = AAUDIO_Deinitialize; + impl->OpenDevice = AAUDIO_OpenDevice; + impl->CloseDevice = AAUDIO_CloseDevice; + impl->WaitDevice = AAUDIO_WaitDevice; + impl->PlayDevice = AAUDIO_PlayDevice; + impl->GetDeviceBuf = AAUDIO_GetDeviceBuf; + impl->WaitCaptureDevice = AAUDIO_WaitDevice; + impl->CaptureFromDevice = AAUDIO_CaptureFromDevice; + + impl->HasCaptureSupport = SDL_TRUE; + + LOGI("SDL AAUDIO_Init OK"); + return SDL_TRUE; +} + +AudioBootStrap AAUDIO_bootstrap = { + "AAudio", "AAudio audio driver", AAUDIO_Init, SDL_FALSE +}; + #endif // SDL_AUDIO_DRIVER_AAUDIO From ae3090c387f6b56e44a334aa7c593bedbefdb194 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 29 Jul 2023 21:17:05 -0400 Subject: [PATCH 132/138] androidaudio: Move Init/bootstrap code to bottom of source code. I can't ever find this when it's in the middle! It's a "me" problem. :) --- src/audio/android/SDL_androidaudio.c | 44 ++++++++++++++-------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/audio/android/SDL_androidaudio.c b/src/audio/android/SDL_androidaudio.c index c87f2b3f32..761ec5be37 100644 --- a/src/audio/android/SDL_androidaudio.c +++ b/src/audio/android/SDL_androidaudio.c @@ -126,28 +126,6 @@ static void ANDROIDAUDIO_CloseDevice(SDL_AudioDevice *device) } } -static SDL_bool ANDROIDAUDIO_Init(SDL_AudioDriverImpl *impl) -{ - // !!! FIXME: if on Android API < 24, DetectDevices and Deinitialize should be NULL and OnlyHasDefaultOutputDevice and OnlyHasDefaultCaptureDevice should be SDL_TRUE, since audio device enum and hotplug appears to require Android 7.0+. - impl->ThreadInit = Android_AudioThreadInit; - impl->DetectDevices = Android_StartAudioHotplug; - impl->Deinitialize = Android_StopAudioHotplug; - impl->OpenDevice = ANDROIDAUDIO_OpenDevice; - impl->PlayDevice = ANDROIDAUDIO_PlayDevice; - impl->GetDeviceBuf = ANDROIDAUDIO_GetDeviceBuf; - impl->CloseDevice = ANDROIDAUDIO_CloseDevice; - impl->CaptureFromDevice = ANDROIDAUDIO_CaptureFromDevice; - impl->FlushCapture = ANDROIDAUDIO_FlushCapture; - - impl->HasCaptureSupport = SDL_TRUE; - - return SDL_TRUE; -} - -AudioBootStrap ANDROIDAUDIO_bootstrap = { - "android", "SDL Android audio driver", ANDROIDAUDIO_Init, SDL_FALSE -}; - // Pause (block) all non already paused audio devices by taking their mixer lock void ANDROIDAUDIO_PauseDevices(void) { @@ -188,4 +166,26 @@ void ANDROIDAUDIO_ResumeDevices(void) } } +static SDL_bool ANDROIDAUDIO_Init(SDL_AudioDriverImpl *impl) +{ + // !!! FIXME: if on Android API < 24, DetectDevices and Deinitialize should be NULL and OnlyHasDefaultOutputDevice and OnlyHasDefaultCaptureDevice should be SDL_TRUE, since audio device enum and hotplug appears to require Android 7.0+. + impl->ThreadInit = Android_AudioThreadInit; + impl->DetectDevices = Android_StartAudioHotplug; + impl->Deinitialize = Android_StopAudioHotplug; + impl->OpenDevice = ANDROIDAUDIO_OpenDevice; + impl->PlayDevice = ANDROIDAUDIO_PlayDevice; + impl->GetDeviceBuf = ANDROIDAUDIO_GetDeviceBuf; + impl->CloseDevice = ANDROIDAUDIO_CloseDevice; + impl->CaptureFromDevice = ANDROIDAUDIO_CaptureFromDevice; + impl->FlushCapture = ANDROIDAUDIO_FlushCapture; + + impl->HasCaptureSupport = SDL_TRUE; + + return SDL_TRUE; +} + +AudioBootStrap ANDROIDAUDIO_bootstrap = { + "android", "SDL Android audio driver", ANDROIDAUDIO_Init, SDL_FALSE +}; + #endif // SDL_AUDIO_DRIVER_ANDROID From b0edd23c00b7ce76ba2075976bfb283d29c79891 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sun, 30 Jul 2023 11:57:30 -0400 Subject: [PATCH 133/138] testsurround: Log available audio output devices at the start. --- test/testsurround.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/testsurround.c b/test/testsurround.c index d9cb006e0c..c759d073e4 100644 --- a/test/testsurround.c +++ b/test/testsurround.c @@ -183,6 +183,11 @@ int main(int argc, char *argv[]) devcount = 0; } + SDL_Log("Available audio devices:"); + for (i = 0; i < devcount; i++) { + SDL_Log("%s", SDL_GetAudioDeviceName(devices[i])); + } + for (i = 0; i < devcount; i++) { SDL_AudioStream *stream = NULL; char *devname = SDL_GetAudioDeviceName(devices[i]); From 87eae9a0a133c4a5c0c0f1e05e4b52b53962c963 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sun, 30 Jul 2023 20:24:27 -0400 Subject: [PATCH 134/138] aaudio: We need a mixbuf on capture devices, too. --- src/audio/aaudio/SDL_aaudio.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c index aeaf58a868..b843ba6c2d 100644 --- a/src/audio/aaudio/SDL_aaudio.c +++ b/src/audio/aaudio/SDL_aaudio.c @@ -260,13 +260,11 @@ static int AAUDIO_OpenDevice(SDL_AudioDevice *device) SDL_UpdatedAudioDeviceFormat(device); // Allocate mixing buffer - if (!iscapture) { - hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); - if (hidden->mixbuf == NULL) { - return SDL_OutOfMemory(); - } - SDL_memset(hidden->mixbuf, device->silence_value, device->buffer_size); + hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size); + if (hidden->mixbuf == NULL) { + return SDL_OutOfMemory(); } + SDL_memset(hidden->mixbuf, device->silence_value, device->buffer_size); hidden->semaphore = SDL_CreateSemaphore(0); if (!hidden->semaphore) { From 0eda582160d6c0552846fd99fb3e6f9a055a5d07 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sun, 30 Jul 2023 23:00:52 -0400 Subject: [PATCH 135/138] testaudiostreamdynamicresample: Load sample.wav correctly. --- test/CMakeLists.txt | 2 +- test/testaudiostreamdynamicresample.c | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 418ed76254..ee8b871692 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -122,7 +122,7 @@ add_sdl_test_executable(loopwave NEEDS_RESOURCES TESTUTILS SOURCES loopwave.c) add_sdl_test_executable(testsurround SOURCES testsurround.c) add_sdl_test_executable(testresample NEEDS_RESOURCES SOURCES testresample.c) add_sdl_test_executable(testaudioinfo SOURCES testaudioinfo.c) -add_sdl_test_executable(testaudiostreamdynamicresample SOURCES testaudiostreamdynamicresample.c) +add_sdl_test_executable(testaudiostreamdynamicresample NEEDS_RESOURCES TESTUTILS SOURCES testaudiostreamdynamicresample.c) file(GLOB TESTAUTOMATION_SOURCE_FILES testautomation*.c) add_sdl_test_executable(testautomation NEEDS_RESOURCES NO_C90 SOURCES ${TESTAUTOMATION_SOURCE_FILES}) diff --git a/test/testaudiostreamdynamicresample.c b/test/testaudiostreamdynamicresample.c index 761effde68..8745ddf10b 100644 --- a/test/testaudiostreamdynamicresample.c +++ b/test/testaudiostreamdynamicresample.c @@ -15,6 +15,7 @@ #include #include #include +#include "testutils.h" int main(int argc, char *argv[]) { @@ -29,12 +30,24 @@ int main(int argc, char *argv[]) Uint32 audio_len = 0; SDL_AudioStream *stream; SDL_AudioDeviceID device; + const char *fname = "sample.wav"; + char *path; + int rc; SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); window = SDL_CreateWindow("Drag the slider: Normal speed", 640, 480, 0); renderer = SDL_CreateRenderer(window, NULL, 0); - SDL_LoadWAV("sample.wav", &spec, &audio_buf, &audio_len); + path = GetNearbyFilename(fname); + rc = SDL_LoadWAV(path ? path : fname, &spec, &audio_buf, &audio_len); + SDL_free(path); + + if (rc < 0) { + SDL_Log("Failed to load '%s': %s", fname, SDL_GetError()); + SDL_Quit(); + return 1; + } + stream = SDL_CreateAudioStream(&spec, &spec); SDL_PutAudioStreamData(stream, audio_buf, audio_len); device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec); From 2de9253b6c1e67281c4c4ef2a5e89d54a61d8e34 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 2 Aug 2023 12:13:35 -0400 Subject: [PATCH 136/138] test: Added testaudio --- test/CMakeLists.txt | 1 + test/audiofile.bmp | Bin 0 -> 65674 bytes test/logaudiodev.bmp | Bin 0 -> 65674 bytes test/physaudiodev.bmp | Bin 0 -> 65674 bytes test/soundboard.bmp | Bin 0 -> 65674 bytes test/soundboard_levels.bmp | Bin 0 -> 2698 bytes test/speaker.bmp | Bin 0 -> 65674 bytes test/testaudio-art.txt | 8 + test/testaudio.c | 1064 ++++++++++++++++++++++++++++++++++++ test/trashcan.bmp | Bin 0 -> 65674 bytes 10 files changed, 1073 insertions(+) create mode 100644 test/audiofile.bmp create mode 100644 test/logaudiodev.bmp create mode 100644 test/physaudiodev.bmp create mode 100644 test/soundboard.bmp create mode 100644 test/soundboard_levels.bmp create mode 100644 test/speaker.bmp create mode 100644 test/testaudio-art.txt create mode 100644 test/testaudio.c create mode 100644 test/trashcan.bmp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ee8b871692..d207a9afd2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -202,6 +202,7 @@ if(Python3_FOUND AND Python3_VERSION VERSION_GREATER_EQUAL "3.2") endforeach() endif() +add_sdl_test_executable(testaudio NEEDS_RESOURCES TESTUTILS SOURCES testaudio.c) add_sdl_test_executable(testfile NONINTERACTIVE SOURCES testfile.c) add_sdl_test_executable(testcontroller TESTUTILS SOURCES testcontroller.c gamepadutils.c ${gamepad_image_headers}) add_sdl_test_executable(testgeometry TESTUTILS SOURCES testgeometry.c) diff --git a/test/audiofile.bmp b/test/audiofile.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f8b8106d6ecf70326ee677a9b3896cdfd8b59723 GIT binary patch literal 65674 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 F{{w+htyKU3 literal 0 HcmV?d00001 diff --git a/test/physaudiodev.bmp b/test/physaudiodev.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1d4da24643f61a7c4c6e16d93144ab003b513328 GIT binary patch literal 65674 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 W(82>PJkY`eEj-Y|1OGL6;Qs?9LpXT= literal 0 HcmV?d00001 diff --git a/test/soundboard.bmp b/test/soundboard.bmp new file mode 100644 index 0000000000000000000000000000000000000000..032b8984366d87ad2eb09096807af0226c3d369f GIT binary patch literal 65674 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 literal 0 HcmV?d00001 diff --git a/test/soundboard_levels.bmp b/test/soundboard_levels.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ce4cd5ba77342a16d7feaf17ed34f93d77294ed9 GIT binary patch literal 2698 zcmc)IYfO_@7zc2+eOt0kx49{(pcN>zMJUXu2pCG~{cQm)n4xU}l|r0mMnOO)^8z$Z zL0!~MsM9E3K)j%A0~Ds*qzDDU3CN|i)CuLXWSd~%**R}psHmH|4{Q4C|JxADJMUh&g=8H z4{hhp|NOqtMb;s3mOT_=F7ggC$uXYG9*WQ!;i~8)s8L-3_Mro7{SvkQvqIXA#GW>k zWK_X~^s4ca^r|tl$}zlNN~(lX6`of-i4RN~-4vYK8sr$!SgNXkp1eoT%SUVZMw0%b z5%d>(L0@1XWvo63$swvXwqr!ojeWPwdwAcfVDhZ~k{%4$=a)1*y_~k8u6X z{pIF;>p3zlXK@WWkM&;dM!KN4unRI|nUJ=6|6gSNfimd5(mipGJd5VG=X(n~!FUbp z0OR$CppqYey^-JizI2AD=hz0d;b!Iyn5r!629#x$LfJvA1j-H-LpgT17&daYLIRO$ z^qv+x~p>@9~46~`~bi(NwNNVZi08W+Hq#22uEy+s@BB-U1{^TAZ1`Dc}A zzp@qvEDpq_LV`F&!*^@BbPzJ;G<*zJj<2Kk9wxuq>!B=;3=N^6Sc{N~nCY(Uy zmrGcwfXzgF%hH)%gDfY%Rw0#rPr~4~O6Z&i5<0gvgu!bI_29LKEfjRD@Dy}Py@k4P zACX?l6zZf*k&d&#e@MxSsq=TB{l=c}^B~MCpf!%qd#dE~MwNX2sFF7YS1=mO<&UiA z2u74V;nOhYvhh^8Vwg?w`ZYHrp{L&U%=t4=lh|;1HWoH2#Nm9bMiOQz}Dzi@e(>@sKId5?RC8D#HhvwG) zIP>9X@_Lexx=_@8$;7nk`AN2XOP@y z8PMF?7pJ8!?s;rSG$cu7x7n0=omY=Cjl5VM7I5!}&weej!E6 z9cP-jE(=;j?(Q0lpwa6v5l*_tU4rIuS4{J)$0T&G1__-1{hYCZ9GGB2W0Tsf{{&?3 z2?r&Mcb1^dscyNw1I7oBzX0kvXo31q_EJO6-j%36IG3Q$slH#a9ma>ge*x5UXlh^F zCmV9oRzNH(=nPq3nF8a(-%at7ewKTx)50J&Fz__T<)ii=PsPA})Z<^Fd*ZudyW{*o z;kP(Hi1L0v-|f>T5p8xAzKfNZF}KFd@s5}=N#fyDi*a3N=+-Rtxy)YhK`)Q`Nh7Ye zvi86^*$4NV?89~axO?>yW&y4(%G(7k+EcG(*CqmFzKbph+-Ftb+A*koE$YlThzVGB z;{U9V_V-o)m+z1AUy>8-JiYZzf4|7ZUb!sFdwu9xYVkd632F9~R@UcgP5fNZ|E_D* IBF2Tk0c-))uK)l5 literal 0 HcmV?d00001 diff --git a/test/speaker.bmp b/test/speaker.bmp new file mode 100644 index 0000000000000000000000000000000000000000..56f3dc1ae6699c9fc89daa9b8925c2ebacfab52b GIT binary patch literal 65674 zcmeI5d7MOLic5?dHERCwpL*w8-RI8fn%?OiLwC=I;@+;VTg&;K ztxnagKJ4f#s#;X>cZH;jCC!be7FE5fTFHHtd|%V9$|wKP|L3ZzV*mK$4m)D}xsBgt zch@pWu|r0UY9XmLSN*z@S5;LGG-yt|d-UwNUEScpJ9h5cwT-mfn$)FRw>Fi$1R8kp zZ${oLy38LuawKhxle9(B_bpnq_@1jxmAr^9&uZJqIk=_`ZcQGqR)I+DVLE3@Ib2qW2CmF7;}sSf8XL?!8?-g z1R2Oc7jywMRm=+MvlX%~Z%0CZzh%pouCJNz>%6~`*CQoetWK2sqQ!%Xp;JPCADlmY z;TiY$wJY6cFTUUt_n)kOu9BC82G9zcp%db%sPUje^FSEyMc%>t(={vI=P#{tU;JgY zPuy4Xn$iGTK{JbgvEo6}hW{%t47;qjMSGXy8oy6 zBlnqjfbSVUGVr(65+mNm^#x;5JEvpGL<7ZN1XFonoA&>}A3W3P{nsA)qgy<8mYX!@ zNVjM2UOr76eT2_j_wWO0pUp(ibnO=7*}uu!#%s$Ta7!}a3Ic+#~S z>i<&k|BwO39Afd&3(vYG^JcqF?b~~MXLMd<|7ztsc;k~*&!zVpgvUOANqmc!*ZB0g z=$lW;!{2D8JjO|1J9UVS2N~9+W2e;0IqmFX$|G@qi(@%-$6VgdPbA@*2 zpE@m(2Sm4i`Nk{mpKrbCzL7P=Z)F|r>-XP@tt-iz(!V5q_tE?A>knjYNv?b@X5K$G zZ*nuwIXfDU;vJ4dKhHSlXO3sW{{3^yX1^Y!<>Xql^+2iH{}tG`$jbjiPNZ?Qaq;^q z*STG5*LkNllHVvhT5j>YS?$f_r-I@$S|$$GWlm_KeCv z&rUTR-6qlJ|8DEV^ohP1%-qrO@jNx>ct0M-#u$~CW86!%*pTYxR4-YdI z#(PFDpX$0* z`#ki3>P~2K{Dkq2HPc*p;Ip?kxRooGyQNoM;m(*o)eRpu)a}u)uTT5$H`JXez6JW5 zGL*IQn29gw%kdMAvv|4N&KesVkBouwbg$mtkBu=hRviyCII&?uG#1)rU7R&R#b2%^ z75`&?%Y2COG5#*?+IXFhtUxQ}3+~B7X7C4;9s>vT_j|*v&9w4k`C{{He{oB$yu$4> zs4jY5+tvE?s_hQiYfpFG)xUK-`*jxkl91QEq)%E8_MSc?^Wd)e+>__WBV{z7XQ@N^ zER=sKS5p2T^IOFq8gvnCpPm=fm0(UDSSbGZ1JG3Q0e|#$IvM?k%m?ncVX5oWE9O7w zvr2Pq!+s9)diChxZo7U-qz@FIuwD8BhS~;wrO(K^j)%IrCl7ysGTJuJBJ;|()Ypvr z!J0k(M_jMr$RnchLpOB1*Z*;S39YaN+33qq54`vK%kK1Pr#Kl{)JBNy&TU$`ZacS; z)Yk1J_liYWj?e#n%73|cUVSOTC)73Ic0&DlUk78s=SRn*yVRa}{V!u9kMHb$5A^>x zg1L@E@i%=b3;z$f0B>kS;vWA`{Uya{SsMTE?U&cOF^7+eu+V3)x1;L1yUULMvAg^1 zW88|1C%WfM*xq?F%V+;HGQlM?A!{ z#|-Tq*)+ys{Xcv|gRS=+El-8|U+OC<|Bo0T@loPnN?)$vj2^%ipg->vIU&zt!5nL& z_>U?pRwutJGOE~Xy*xW|;BM~b(?`1(E!uXKcIF zcckz@Dm}2_#Z|7hW5;MbJPQvX>pGvsCsgdUf3fi-&Sy*A|I--owR0|RLSyKLKM=F` zjrv=$VD4vSMQtWD!>`wJ(6^O;>wl2kQhsBJWch=vISSg;(s4E&SyjUm%2*I|I>8<;@`w{EoMr;WO)~! z{ZofuXJuaXz+E?9AJLv?(c!-p#H5ksG~FPuHtvM@jAc{oIBhxj$VnLG;9w1p5FFoF{rf z`$FG(OIyqhll282AdXNgb}Af)er7yNBo2{^mlc1?$J;GA|IhCGA)dcc*6q-RlgE$q zb1CLT_|(f5Tfm;RNU|A+i553rV_ zv?G?r_>lqVn~KItyU*VKtJ_=RH7YCUef&48&%ty0p@Sma6>r5~pViOP53O?{4^XF_ z7YrV_m-|fKUCh)!zGp0qDchK3x%hvc2P77#?*S?gD7Gr!>1h1iBY%o;1;3Fp$9q9y zY>j$z2=);9kA@WVdeqDgM5G zc;5tUc_!u$G|m@)bHb0Kw%`G5Ksp>#^*IxNtNm9#1Aj@ydLOjesK+E&2okJ2UX1zear)~O>NuTG+-{s@~t&Z*2%Ts=!lierL zP1bXsmbfu`A0A-t&KzIu1Nfu+t=_jhfIpCk|BgOJr1dd}qz$bP{Lw-A+OJf65z4>t zTWR`#FR$^wf4=eqo%SC(B$7w;|D!V}M*bhVTlGJfq5skGq25;>;5!@ZL;u4=>Q7=@ zXao7DKK78P{VyaBsQp(y1Aj@S=l==*em}0re?B|_&27w|_1;YW-^3C9lKOw>1$;xr zmMd{W^(Eke(Ep=t#XprS=ff9@zv)Y9`hVj8`#sqb^UjA((45$xp_98@)^IIm51v0> z=6^An)pGCvvJU3>1NelBJ!MGM5+@{9tiA*`fVE@&o-2FF^5NS|yb|hv(E}yx|M`9S zf@3C{z#o(e%RT?-LlGwE%flr`L#)Q~Kp6j_9JT;|fMnx(iZAgW)%(e-G>y|TAHMivri>{z}~4 zUH|2tysX!MrL9!!zxm3%0YI-a zH)L+7d-HUTuQmg{&v%!y|3mf>DHhBzZeO;vD0*LcAk_cD0~P--gO9*o3if7M9Sskl z2SQt*Hr?v@Fb{jdv(#bhq>WVjHY`7GjUK4@fBCHkzW@3wP4<6+-N=F6y-yg<3zEq^ zYsdI{>>*K^SNz8uKGLzbd~2}H=GjpGrTvQkC-R>S?cS1@5;!SldPRR@=CQ%l$v!jnJG1GF>T|BS=mxd_75^`PJ;3`PioIS9J$)RP zSU&4H8q-Zyj=a;Sot$#tcdpoG!#|2YZO8ju@&7XTf1x~LquHk$?hCa%kP2_5doMYI z?amvnlk@3g?`DN<Jc`RDpF_<$e%b)EMMehAwV%74ZG%YA>)-{kkD z9{IoDyR%N4=DKvM)i_Et2Q;=dX6v{J->i zVDoD)`S^|9yRFzE%iw#`fqS~&o^-H3kL3Y*C-BMHll^<5toL!o5P7%D`7D$<>A*ew zGoc=!PS%F7DeV6!*8HL3|E0%U=}ug4)#Hyu?|^}iV#YoV_G~RY`7pn}$6k+g>-~1G z-x}HP`NXVA{@KGg2T1Bte$qBZ4BOAGdE(Kc@IWa4(q<+8C-FI}L%$Pyu;RXZ#JB5| zfDQNz@6*{Woqo7~cgODMNw(gfDQ~6h16?9@?cXP{9r&zk=T7di`|fr0rI5Bj?Z5Ie z_)Dt1|6zFGvHS0J9jbTn*ejj36C3W&dy;eH!u>p0{iC z<|9k8DsqUtf}}1y~6oq+AjDoS1hD02=%|TQCa^L z*+-u6^?2_(E*Bn$zP0WAS%JwgTYgc!$F+awof9xq{PY^uH&giJA$eB$$?68q^y?#S z+Gp_x3gr`4{J-S>AHNw*Ovch3S-k6KM>o;k*lqT6F~76e+Gq7%uVJ~avN!0`$p`s& zLBQYg0B2K%dVqOBA?Ajm{7e5T{-4PIeYf3`fF*Nwe6vtSIj3RLh{p4{!PjDoKhJw0 z-Q|qo?(N(9d-d^cIpvFn;r-nT*)JIK0Bz%QXgTJF#30a9`PzP-Q~PiFQuh8IalN7Y z?45w)d9pv-zMl;~4_!FH9n`-|LOJjOn=wOrx@)J8bdSwxa4%jl#jRg7Ro(%e>ekBr z7-5MTH{HZU_17+|3-Lhm+L}zF+ zqHkyKr&*o+=*$MezH34`XgFry-QE2c9P2h*J3ZyuADjunIRG1_%>5UP^UqjZxj#_q z4)p-@g5km3@Fs~B=ezdD{2|o;GTyTG|5$$+Z1?X87NOqfygu}_#SEFBRX@VLv2d!# zHr2Tg$<71enK=^=XtEc4qO`%gp;iwpo_2UbjEKH&&6-ig|4a1$9=`W3)q@^i_UMCw z_4nD=5B6#^d1q_k)I;4SIo~VQc|R)isrZ6t=bv(D6CS`Pv_1!YAx>y{Z}~lUCd8|9 z@drZrmwLt9 zCT)dtL;9q=M?aXSV8`=`2dVux^_8vvw`Kine*Iptt(D&jdR$`jR{zczH$1|ga@hOV z+v(Nd@Ofh+L=tAAK8sFigl#f+E`dr0z$3oHJ#6R)pq{l7oU?*v-9 zvyNx!{*3%qMBh$3DV;=DS9$*}8ID_%hfiqd1^uK=e#c4sLj0I@LxY)nUiJ~?qW6_o zLj5m1P`3UbdpT65pe^gOHnvN=R{4W+%-`_gQ`z)?!U?~4m zXIc7x-+cI<-yMI3a6BcHNLYBz7MW zdrI@!SFHA5`3(FemEHO;deF}MYj$WYzTf0%?gjpXc55HWBQag#dYNzp*AUxm^445F zCE@%a)(!3XuFKIMZ2uF7Rg!SLwuW z2EMsyT3*<~Gnsf`oy3Y+FVOzbHvQ390DH;kqslFP2TS$qvb6=F{+Ig7(*J{Y&`{|K zZK1LHT*#i{4<_KhK8}Ar@|}qXSTA7B$l^~Qlphs;T@A`b-T8Y@Ie2s`b|v#XOiW7VuIKf+E9K(|D%JHAHDvUeZ|Tjs{eDH1BCJ~ z^_8jrw>gRbS0BZGerW%XeMIT}zhg%qS@HiwZh8Nc-~8tHKJ4#4eIaM{q&xqa{k^l! zJ2#RiMCNG4^8Z*nR@vstUNYMk=BBnUyla-L?WcUj|7+Yo z!FxBB=2x6}aAa@6U*kVY_oIf!;y;Dv0oIP$KVbX9enPeV^hM)8+82%gH~_*rJo9Z^|E( zqwl%eet4zg|24+{K74as!uy-}fKSYx6ww{}V*@PRiPNr+ulW~353v5jnv!kfVmT)m znJ3W)&XBWnf8I;ry<_mql?Oukmv+n2|ATfU&g+5pDtk@Wf9sp9|IV9K-1T4f60f181|I`)5T%^m=r zr4Fn2=>u)4-sj3ZG(0c#k$Zoi*V-TauK4SCv<7nPwLJitDXd~5;x2j=9L|0p1DGbphI?uk<-ry|3d9^}n=R zw*DV9VIIhyO2wb+0kZ#-bNQ_eW*^XxWbdyn2WDf2u>W(MUl-2g4{TiAxc}4WUti~e zQa5c_y-y$5i)!2a-)k4$|>h|5c@i&~IH|KNXcUvBKK+gA1 z#slc^qr@*HKEN6waYEv`tQQd1V{Pvmncq`J&wHm0BZ8`R+v;SLV znQN|kAe4WpzijbN4OVQar9Wpv&3;(FCh7tuH;!9xz|*`e?K`d zx*>tj@Hx=?;rkzdyyJH{;~nyf+JDpMviAR!2YCPMjOmHzI(L@$ziyYfo7KtO;}77c zv1X(=8hU1x$NOKrLl*uPJo@HNdFQN)`7Kb|V4o`IkmMutmIoAn>Wuffto=Vr&v#$t z{m;hVe1t~4i+TH5O?dzreRzhx|G862`40Jl`Yw_^rPz9FTQt_k`-V2=L?3u3^<8;4 z)#9jpn`>UE|D}Ir?f>aKQ0ci^&fQ>tEp*q!yO_Ka68Zy*8T&+u6%#AMhiBbT-~XCS)o>Vw9! zMj2Z+JfQa9@L5Uwf8PpS-dwZN&+$)enBevr(BG#M>c{)MO`H=PpBslVg)IpE0mX+n z;Ual2hxGzvJR7`K=6T12m@e;{5!*3Xcs{`1uUxS_smwDT`b^A%F)*Hik{BC#8(&(L z_IEE8e;tQhOV#GtT*06B;X>^r|?{IOpt?JitC{&W9K>c<+q$ zv5&k~_7Zb8h(G^P_H2=-?det9)z3pD{`7}iZfkw>fJGbLqx<>g^+Mx$h*#dL}al)IfUEubWz1NDHKC7RTVUaG6 z^Iv%nDP3HT-!Wdc;Hs!itFk;ty`~J4&RPvm)~<&R`2p- z(`!YqYdu`)`>kSc7!UC%>_K9Be6tS!K&hDNEd2zkJkW1h}?Wjs&Pxh8qGoR!CP^-=B{sfRx4cvx#f*5PHw z)4e*j&QP~&wa<&~6K&)b<^|xdJP_)C;i;1H|G+?+lYQdMp%zgI&x1QwtzC4 z&wL6WDV;oK!mNNuN`Vhq3B-&=&*u7!dI@{VH|;5B&EK{Gk`)?;>+8 zXo9?ex$02v$wO!26Doa~OCuw>$Y>$)4%?+KA^!9keTM97zqu#Rk0;<~>QMa?%D+5c zQvM(KPaJb(M3+!sf;$P_3H~vC*%)^X^<^=!rS1^#FptmZf8|?j6@5nj(fN`7m+_EC z3_{Do8$3&#P{*P6-;^mi|Btz!$_3Y>hxT&Mh|GXD2|B6&hcBgNp-C?OpUPB-S)uL2 zP1nr#eN@bj8QMFtX^eyMs4gpa|BrWoUViXCng7Mk?^bN#0ptc-K)OSGLgoefJy&99 z#EMl`3XNmF^=*1-wX5wIoBvrJU_9u1`~mJmUS>Sud^XhoQeR2=fBGKSqS-SOumxXi zK^L)&_($sVD?Rz1_z&^*OgcGV9CGf9QYD z1M>doKQ?djGN8IsX=7@t1qKEztCkQKMul za_ow%wM}OhjF5SAdk@{MbI(O~brf%@y9V>a+)1XJsp4$}yk2Lre1n)HU7eq%v LgGw8Pr@{XNkWPDZ literal 0 HcmV?d00001 diff --git a/test/testaudio-art.txt b/test/testaudio-art.txt new file mode 100644 index 0000000000..f30a3d24ee --- /dev/null +++ b/test/testaudio-art.txt @@ -0,0 +1,8 @@ +The .bmp files used by testaudio.c were made by AlDraw: + +https://linktr.ee/AlexDraw + +They may be distributed as public domain files! + + + diff --git a/test/testaudio.c b/test/testaudio.c new file mode 100644 index 0000000000..d8ca140567 --- /dev/null +++ b/test/testaudio.c @@ -0,0 +1,1064 @@ +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +#include +#include +#include +#include "testutils.h" + +#define POOF_LIFETIME 250 + +typedef struct Texture +{ + SDL_Texture *texture; + float w; + float h; +} Texture; + +typedef enum ThingType +{ + THING_NULL, + THING_PHYSDEV, + THING_PHYSDEV_CAPTURE, + THING_LOGDEV, + THING_LOGDEV_CAPTURE, + THING_TRASHCAN, + THING_STREAM, + THING_POOF, + THING_WAV +} ThingType; + + +typedef struct Thing Thing; + +struct Thing +{ + ThingType what; + + union { + struct { + SDL_AudioDeviceID devid; + SDL_bool iscapture; + SDL_AudioSpec spec; + char *name; + } physdev; + struct { + SDL_AudioDeviceID devid; + SDL_bool iscapture; + SDL_AudioSpec spec; + Thing *physdev; + } logdev; + struct { + SDL_AudioSpec spec; + Uint8 *buf; + Uint32 buflen; + } wav; + struct { + float startw; + float starth; + float centerx; + float centery; + } poof; + struct { + SDL_AudioStream *stream; + int total_ticks; + Uint64 next_level_update; + Uint8 levels[5]; + } stream; + } data; + + Thing *line_connected_to; + char *titlebar; + SDL_FRect rect; + float z; + Uint8 r, g, b, a; + float progress; + float scale; + Uint64 createticks; + Texture *texture; + const ThingType *can_be_dropped_onto; + + void (*ontick)(Thing *thing, Uint64 now); + void (*ondrag)(Thing *thing, int button, float x, float y); + void (*ondrop)(Thing *thing, int button, float x, float y); + void (*ondraw)(Thing *thing, SDL_Renderer *renderer); + + Thing *prev; + Thing *next; +}; + + +static Uint64 app_ready_ticks = 0; +static int done = 0; +static SDLTest_CommonState *state = NULL; + +static Thing *things = NULL; +static char *current_titlebar = NULL; + +static Thing *droppable_highlighted_thing = NULL; +static Thing *dragging_thing = NULL; +static int dragging_button = -1; + +static Texture *physdev_texture = NULL; +static Texture *logdev_texture = NULL; +static Texture *audio_texture = NULL; +static Texture *trashcan_texture = NULL; +static Texture *soundboard_texture = NULL; +static Texture *soundboard_levels_texture = NULL; + +static void DestroyTexture(Texture *tex); + +/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ +static void Quit(int rc) +{ + DestroyTexture(physdev_texture); + DestroyTexture(logdev_texture); + DestroyTexture(audio_texture); + DestroyTexture(trashcan_texture); + DestroyTexture(soundboard_texture); + DestroyTexture(soundboard_levels_texture); + SDLTest_CommonQuit(state); + /* Let 'main()' return normally */ + if (rc != 0) { + exit(rc); + } +} + +static char *xstrdup(const char *str) +{ + char *ptr = SDL_strdup(str); + if (!ptr) { + SDL_Log("Out of memory!"); + Quit(1); + } + return ptr; +} + +static void *xalloc(const size_t len) +{ + void *ptr = SDL_calloc(1, len); + if (!ptr) { + SDL_Log("Out of memory!"); + Quit(1); + } + return ptr; +} + + +static void SetTitleBar(const char *fmt, ...) +{ + char *newstr = NULL; + va_list ap; + va_start(ap, fmt); + SDL_vasprintf(&newstr, fmt, ap); + va_end(ap); + + if (newstr && (!current_titlebar || (SDL_strcmp(current_titlebar, newstr) != 0))) { + SDL_SetWindowTitle(state->windows[0], newstr); + SDL_free(current_titlebar); + current_titlebar = newstr; + } else { + SDL_free(newstr); + } +} + +static void SetDefaultTitleBar(void) +{ + SetTitleBar("testaudio: %s", SDL_GetCurrentAudioDriver()); +} + +static Thing *FindThingAtPoint(const float x, const float y) +{ + const SDL_FPoint pt = { x, y }; + Thing *retval = NULL; + Thing *i; + for (i = things; i != NULL; i = i->next) { + if ((i != dragging_thing) && SDL_PointInRectFloat(&pt, &i->rect)) { + retval = i; /* keep going, though, because things drawn on top are later in the list. */ + } + } + return retval; +} + +static Thing *UpdateMouseOver(const float x, const float y) +{ + Thing *thing; + + if (dragging_thing) { + thing = dragging_thing; + } else { + thing = FindThingAtPoint(x, y); + } + + if (!thing) { + SetDefaultTitleBar(); + } else if (thing->titlebar) { + SetTitleBar("%s", thing->titlebar); + } + + return thing; +} + +static Thing *CreateThing(ThingType what, float x, float y, float z, float w, float h, Texture *texture, char *titlebar) +{ + Thing *last = NULL; + Thing *i; + Thing *thing; + + thing = (Thing *) xalloc(sizeof (Thing)); + if ((w < 0) || (h < 0)) { + SDL_assert(texture != NULL); + if (w < 0) { + w = texture->w; + } + if (h < 0) { + h = texture->h; + } + } + + thing->what = what; + thing->rect.x = x; + thing->rect.y = y; + thing->rect.w = w; + thing->rect.h = h; + thing->z = z; + thing->r = 255; + thing->g = 255; + thing->b = 255; + thing->a = 255; + thing->scale = 1.0f; + thing->createticks = SDL_GetTicks(); + thing->texture = texture; + thing->titlebar = titlebar; + + /* insert in list by Z order (furthest from the "camera" first, so they get drawn over; negative Z is not drawn at all). */ + if (things == NULL) { + things = thing; + return thing; + } + + for (i = things; i != NULL; i = i->next) { + if (z > i->z) { /* insert here. */ + thing->next = i; + thing->prev = i->prev; + + SDL_assert(i->prev == last); + + if (i->prev) { + i->prev->next = thing; + } else { + SDL_assert(i == things); + things = thing; + } + i->prev = thing; + return thing; + } + last = i; + } + + if (last) { + last->next = thing; + thing->prev = last; + } + return thing; +} + +static void DestroyThing(Thing *thing) +{ + if (!thing) { + return; + } + + switch (thing->what) { + case THING_POOF: break; + case THING_NULL: break; + case THING_TRASHCAN: break; + case THING_LOGDEV: + case THING_LOGDEV_CAPTURE: + SDL_CloseAudioDevice(thing->data.logdev.devid); + break; + case THING_PHYSDEV: + case THING_PHYSDEV_CAPTURE: + SDL_free(thing->data.physdev.name); + break; + case THING_WAV: + SDL_free(thing->data.wav.buf); + break; + case THING_STREAM: + SDL_DestroyAudioStream(thing->data.stream.stream); + break; + } + + if (thing->prev) { + thing->prev->next = thing->next; + } else { + SDL_assert(thing == things); + things = thing->next; + } + + if (thing->next) { + thing->next->prev = thing->prev; + } + + SDL_free(thing->titlebar); + SDL_free(thing); +} + +static void DrawOneThing(SDL_Renderer *renderer, Thing *thing) +{ + SDL_FRect dst; + SDL_memcpy(&dst, &thing->rect, sizeof (SDL_FRect)); + if (thing->scale != 1.0f) { + const float centerx = thing->rect.x + (thing->rect.w / 2); + const float centery = thing->rect.y + (thing->rect.h / 2); + SDL_assert(thing->texture != NULL); + dst.w = thing->texture->w * thing->scale; + dst.h = thing->texture->h * thing->scale; + dst.x = centerx - (dst.w / 2); + dst.y = centery - (dst.h / 2); + } + + if (thing->texture) { + if (droppable_highlighted_thing == thing) { + SDL_SetRenderDrawColor(renderer, 255, 0, 255, 100); + SDL_RenderFillRect(renderer, &dst); + } + SDL_SetRenderDrawColor(renderer, thing->r, thing->g, thing->b, thing->a); + SDL_RenderTexture(renderer, thing->texture->texture, NULL, &dst); + } else { + SDL_SetRenderDrawColor(renderer, thing->r, thing->g, thing->b, thing->a); + SDL_RenderFillRect(renderer, &dst); + } + + if (thing->ondraw) { + thing->ondraw(thing, renderer); + } + + if (thing->progress > 0.0f) { + SDL_FRect r = { thing->rect.x, thing->rect.y + (thing->rect.h + 2.0f), 0.0f, 10.0f }; + r.w = thing->rect.w * ((thing->progress > 1.0f) ? 1.0f : thing->progress); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 128); + SDL_RenderFillRect(renderer, &r); + } +} + +static void DrawThings(SDL_Renderer *renderer) +{ + Thing *i; + + /* draw connecting lines first, so they're behind everything else. */ + for (i = things; i && (i->z >= 0.0f); i = i->next) { + Thing *dst = i->line_connected_to; + if (dst) { + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + SDL_RenderLine(renderer, i->rect.x + (i->rect.w / 2), i->rect.y + (i->rect.h / 2), dst->rect.x + (dst->rect.w / 2), dst->rect.y + (dst->rect.h / 2)); + } + } + + /* Draw the actual things. */ + for (i = things; i && (i->z >= 0.0f); i = i->next) { + if (i != dragging_thing) { + DrawOneThing(renderer, i); + } + } + + if (dragging_thing) { + DrawOneThing(renderer, dragging_thing); /* draw last so it's always on top. */ + } +} + +static void Draw(void) +{ + SDL_Renderer *renderer = state->renderers[0]; + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, 64, 0, 64, 255); + SDL_RenderClear(renderer); + DrawThings(renderer); + SDL_RenderPresent(renderer); +} + +static void RepositionRowOfThings(const ThingType what, const float y) +{ + int total_things = 0; + float texw = 0.0f; + float texh = 0.0f; + Thing *i; + + for (i = things; i != NULL; i = i->next) { + if (i->what == what) { + texw = i->rect.w; + texh = i->rect.h; + total_things++; + } + } + + if (total_things > 0) { + int w, h; + SDL_GetWindowSize(state->windows[0], &w, &h); + const float spacing = w / ((float) total_things); + float x = (spacing - texw) / 2.0f; + for (i = things; i != NULL; i = i->next) { + if (i->what == what) { + i->rect.x = x; + i->rect.y = (y >= 0.0f) ? y : ((h + y) - texh); + x += spacing; + } + } + } +} + +static const char *AudioFmtToString(const SDL_AudioFormat fmt) +{ + switch (fmt) { + #define FMTCASE(x) case SDL_AUDIO_##x: return #x + FMTCASE(U8); + FMTCASE(S8); + FMTCASE(S16LSB); + FMTCASE(S16MSB); + FMTCASE(S32LSB); + FMTCASE(S32MSB); + FMTCASE(F32LSB); + FMTCASE(F32MSB); + #undef FMTCASE + } + return "?"; +} + +static const char *AudioChansToStr(const int channels) +{ + switch (channels) { + case 1: return "mono"; + case 2: return "stereo"; + case 3: return "2.1"; + case 4: return "quad"; + case 5: return "4.1"; + case 6: return "5.1"; + case 7: return "6.1"; + case 8: return "7.1"; + default: break; + } + return "?"; +} + +static void PoofThing_ondrag(Thing *thing, int button, float x, float y) +{ + dragging_thing = NULL; /* refuse to be dragged. */ +} + +static void PoofThing_ontick(Thing *thing, Uint64 now) +{ + const int lifetime = POOF_LIFETIME; + const int elasped = (int) (now - thing->createticks); + if (elasped > lifetime) { + DestroyThing(thing); + } else { + const float pct = ((float) elasped) / ((float) lifetime); + thing->a = (Uint8) (int) (255.0f - (pct * 255.0f)); + thing->scale = 1.0f - pct; /* shrink to nothing! */ + } +} + +static Thing *CreatePoofThing(Thing *poofing_thing) +{ + const float centerx = poofing_thing->rect.x + (poofing_thing->rect.w / 2); + const float centery = poofing_thing->rect.y + (poofing_thing->rect.h / 2); + const float z = poofing_thing->z; + Thing *thing = CreateThing(THING_POOF, poofing_thing->rect.x, poofing_thing->rect.y, z, poofing_thing->rect.w, poofing_thing->rect.h, poofing_thing->texture, NULL); + thing->data.poof.startw = poofing_thing->rect.w; + thing->data.poof.starth = poofing_thing->rect.h; + thing->data.poof.centerx = centerx; + thing->data.poof.centery = centery; + thing->ontick = PoofThing_ontick; + thing->ondrag = PoofThing_ondrag; + return thing; +} + +static void DestroyThingInPoof(Thing *thing) +{ + if (thing) { + if (thing->what != THING_POOF) { + CreatePoofThing(thing); + } + DestroyThing(thing); + } +} + +/* this poofs a thing and additionally poofs all things connected to the thing. */ +static void TrashThing(Thing *thing) +{ + Thing *i, *next; + for (i = things; i != NULL; i = next) { + next = i->next; + if (i->line_connected_to == thing) { + TrashThing(i); + next = things; /* start over in case this blew up the list. */ + } + } + DestroyThingInPoof(thing); +} + +static void StreamThing_ontick(Thing *thing, Uint64 now) +{ + if (!thing->line_connected_to) { + return; + } + + /* are we playing? See if we're done, or update state. */ + if (thing->line_connected_to->what == THING_LOGDEV) { + const int available = SDL_GetAudioStreamAvailable(thing->data.stream.stream); + SDL_AudioSpec spec; + if (!available || (SDL_GetAudioStreamFormat(thing->data.stream.stream, NULL, &spec) < 0)) { + DestroyThingInPoof(thing); + } else { + const int ticksleft = (int) ((((Uint64) ((available / (SDL_AUDIO_BITSIZE(spec.format) / 8)) / spec.channels)) * 1000) / spec.freq); + const float pct = thing->data.stream.total_ticks ? (((float) (ticksleft)) / ((float) thing->data.stream.total_ticks)) : 0.0f; + thing->progress = 1.0f - pct; + } + } + + if (thing->data.stream.next_level_update <= now) { + Uint64 perf = SDL_GetPerformanceCounter(); + int i; + for (i = 0; i < SDL_arraysize(thing->data.stream.levels); i++) { + thing->data.stream.levels[i] = (Uint8) (perf % 6); + perf >>= 3; + } + thing->data.stream.next_level_update += 150; + } +} + +static void StreamThing_ondrag(Thing *thing, int button, float x, float y) +{ + if (button == SDL_BUTTON_RIGHT) { /* this is kinda hacky, but use this to disconnect from a playing source. */ + if (thing->line_connected_to) { + SDL_UnbindAudioStream(thing->data.stream.stream); /* unbind from current device */ + thing->line_connected_to = NULL; + } + } +} + +static void StreamThing_ondrop(Thing *thing, int button, float x, float y) +{ + if (droppable_highlighted_thing) { + if (droppable_highlighted_thing->what == THING_TRASHCAN) { + TrashThing(thing); + } else if (((droppable_highlighted_thing->what == THING_LOGDEV) || (droppable_highlighted_thing->what == THING_LOGDEV_CAPTURE)) && (droppable_highlighted_thing != thing->line_connected_to)) { + /* connect to a logical device! */ + SDL_Log("Binding audio stream ('%s') to logical device %u", thing->titlebar, droppable_highlighted_thing->data.logdev.devid); + if (thing->line_connected_to) { + const SDL_AudioSpec *spec = &droppable_highlighted_thing->data.logdev.spec; + SDL_UnbindAudioStream(thing->data.stream.stream); /* unbind from current device */ + if (thing->line_connected_to->what == THING_LOGDEV_CAPTURE) { + SDL_FlushAudioStream(thing->data.stream.stream); + thing->data.stream.total_ticks = (int) (((((Uint64) (SDL_GetAudioStreamAvailable(thing->data.stream.stream) / (SDL_AUDIO_BITSIZE(spec->format) / 8))) / spec->channels) * 1000) / spec->freq); + } + } + + SDL_BindAudioStream(droppable_highlighted_thing->data.logdev.devid, thing->data.stream.stream); /* bind to new device! */ + + thing->progress = 0.0f; /* ontick will adjust this if we're on an output device.*/ + thing->data.stream.next_level_update = SDL_GetTicks() + 100; + thing->line_connected_to = droppable_highlighted_thing; + } + } +} + +static void StreamThing_ondraw(Thing *thing, SDL_Renderer *renderer) +{ + if (thing->line_connected_to) { /* are we playing? Update progress bar, and bounce the levels a little. */ + static const float xlocs[5] = { 18, 39, 59, 79, 99 }; + static const float ylocs[5] = { 49, 39, 29, 19, 10 }; + const float blockw = soundboard_levels_texture->w; + const float blockh = soundboard_levels_texture->h / 5.0f; + int i, j; + SDL_SetRenderDrawColor(renderer, thing->r, thing->g, thing->b, thing->a); + for (i = 0; i < SDL_arraysize(thing->data.stream.levels); i++) { + const int level = (int) thing->data.stream.levels[i]; + const float x = xlocs[i]; + for (j = 0; j < level; j++) { + const SDL_FRect src = { 0, soundboard_levels_texture->h - ((j+1) * blockh), blockw, blockh }; + const SDL_FRect dst = { thing->rect.x + x, thing->rect.y + ylocs[j], blockw, blockh }; + SDL_RenderTexture(renderer, soundboard_levels_texture->texture, &src, &dst); + } + } + } +} + +static Thing *CreateStreamThing(const SDL_AudioSpec *spec, const Uint8 *buf, const Uint32 buflen, const char *fname, const float x, const float y) +{ + static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_LOGDEV, THING_LOGDEV_CAPTURE, THING_NULL }; + Thing *thing = CreateThing(THING_STREAM, x, y, 0, -1, -1, soundboard_texture, fname ? xstrdup(fname) : NULL); + SDL_Log("Adding audio stream for %s", fname); + thing->data.stream.stream = SDL_CreateAudioStream(spec, spec); + if (buf && buflen) { + SDL_PutAudioStreamData(thing->data.stream.stream, buf, (int) buflen); + SDL_FlushAudioStream(thing->data.stream.stream); + thing->data.stream.total_ticks = (int) (((((Uint64) (SDL_GetAudioStreamAvailable(thing->data.stream.stream) / (SDL_AUDIO_BITSIZE(spec->format) / 8))) / spec->channels) * 1000) / spec->freq); + } + thing->ontick = StreamThing_ontick; + thing->ondrag = StreamThing_ondrag; + thing->ondrop = StreamThing_ondrop; + thing->ondraw = StreamThing_ondraw; + thing->can_be_dropped_onto = can_be_dropped_onto; + return thing; +} + +static void WavThing_ondrag(Thing *thing, int button, float x, float y) +{ + if (button == SDL_BUTTON_RIGHT) { /* drag out a new audio stream. */ + dragging_thing = CreateStreamThing(&thing->data.wav.spec, thing->data.wav.buf, thing->data.wav.buflen, thing->titlebar, x - (thing->rect.w / 2), y - (thing->rect.h / 2)); + } +} + +static void WavThing_ondrop(Thing *thing, int button, float x, float y) +{ + if (droppable_highlighted_thing) { + if (droppable_highlighted_thing->what == THING_TRASHCAN) { + TrashThing(thing); + } + } +} + +static Thing *LoadWavThing(const char *fname, float x, float y) +{ + Thing *thing = NULL; + char *path; + SDL_AudioSpec spec; + Uint8 *buf = NULL; + Uint32 buflen = 0; + + path = GetNearbyFilename(fname); + if (path) { + fname = path; + } + + if (SDL_LoadWAV(fname, &spec, &buf, &buflen) == 0) { + static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_NULL }; + char *titlebar = NULL; + const char *nodirs = SDL_strrchr(fname, '/'); + #ifdef __WINDOWS__ + const char *nodirs2 = SDL_strrchr(nodirs ? nodirs : fname, '\\'); + if (nodirs2) { + nodirs = nodirs2; + } + #endif + + SDL_Log("Adding WAV file '%s'", fname); + + if (nodirs) { + nodirs++; + } else { + nodirs = fname; + } + + SDL_asprintf(&titlebar, "WAV file (\"%s\", %s, %s, %uHz)", nodirs, AudioFmtToString(spec.format), AudioChansToStr(spec.channels), (unsigned int) spec.freq); + thing = CreateThing(THING_WAV, x - (audio_texture->w / 2), y - (audio_texture->h / 2), 5, -1, -1, audio_texture, titlebar); + SDL_memcpy(&thing->data.wav.spec, &spec, sizeof (SDL_AudioSpec)); + thing->data.wav.buf = buf; + thing->data.wav.buflen = buflen; + thing->can_be_dropped_onto = can_be_dropped_onto; + thing->ondrag = WavThing_ondrag; + thing->ondrop = WavThing_ondrop; + } + + SDL_free(path); + + return thing; +} + +static Thing *LoadStockWavThing(const char *fname) +{ + char *path = GetNearbyFilename(fname); + Thing *thing = LoadWavThing(path ? path : fname, 0.0f, 0.0f); /* will reposition in a moment. */ + SDL_free(path); + return thing; +} + +static void LoadStockWavThings(void) +{ + LoadStockWavThing("sample.wav"); + RepositionRowOfThings(THING_WAV, -10.0f); +} + +static void DestroyTexture(Texture *tex) +{ + if (tex) { + SDL_DestroyTexture(tex->texture); + SDL_free(tex); + } +} + +static Texture *CreateTexture(const char *fname) +{ + Texture *tex = (Texture *) xalloc(sizeof (Texture)); + int texw, texh; + tex->texture = LoadTexture(state->renderers[0], fname, SDL_TRUE, &texw, &texh); + if (!tex->texture) { + SDL_Log("Failed to load '%s': %s", fname, SDL_GetError()); + SDL_free(tex); + Quit(1); + } + SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_BLEND); + tex->w = (float) texw; + tex->h = (float) texh; + return tex; +} + +static Thing *CreateLogicalDeviceThing(Thing *parent, const SDL_AudioDeviceID which, const float x, const float y); + +static void DeviceThing_ondrag(Thing *thing, int button, float x, float y) +{ + if ((button == SDL_BUTTON_MIDDLE) && (thing->what == THING_LOGDEV_CAPTURE)) { /* drag out a new stream. This is a UX mess. :/ */ + dragging_thing = CreateStreamThing(&thing->data.logdev.spec, NULL, 0, NULL, x, y); + dragging_thing->data.stream.next_level_update = SDL_GetTicks() + 100; + SDL_BindAudioStream(thing->data.logdev.devid, dragging_thing->data.stream.stream); /* bind to new device! */ + dragging_thing->line_connected_to = thing; + } else if (button == SDL_BUTTON_RIGHT) { /* drag out a new logical device. */ + const SDL_AudioDeviceID which = ((thing->what == THING_LOGDEV) || (thing->what == THING_LOGDEV_CAPTURE)) ? thing->data.logdev.devid : thing->data.physdev.devid; + const SDL_AudioDeviceID devid = SDL_OpenAudioDevice(which, NULL); + dragging_thing = devid ? CreateLogicalDeviceThing(thing, devid, x - (thing->rect.w / 2), y - (thing->rect.h / 2)) : NULL; + } +} + +static void SetLogicalDeviceTitlebar(Thing *thing) +{ + SDL_AudioSpec *spec = &thing->data.logdev.spec; + SDL_GetAudioDeviceFormat(thing->data.logdev.devid, spec); + SDL_asprintf(&thing->titlebar, "Logical device #%u (%s, %s, %s, %uHz)", (unsigned int) thing->data.logdev.devid, thing->data.logdev.iscapture ? "CAPTURE" : "OUTPUT", AudioFmtToString(spec->format), AudioChansToStr(spec->channels), (unsigned int) spec->freq); +} + +static void LogicalDeviceThing_ondrop(Thing *thing, int button, float x, float y) +{ + if (droppable_highlighted_thing) { + if (droppable_highlighted_thing->what == THING_TRASHCAN) { + TrashThing(thing); + } + } +} + +static Thing *CreateLogicalDeviceThing(Thing *parent, const SDL_AudioDeviceID which, const float x, const float y) +{ + static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_NULL }; + Thing *physthing = ((parent->what == THING_LOGDEV) || (parent->what == THING_LOGDEV_CAPTURE)) ? parent->data.logdev.physdev : parent; + const SDL_bool iscapture = physthing->data.physdev.iscapture; + Thing *thing; + + SDL_Log("Adding logical audio device %u", (unsigned int) which); + thing = CreateThing(iscapture ? THING_LOGDEV_CAPTURE : THING_LOGDEV, x, y, 5, -1, -1, logdev_texture, NULL); + thing->data.logdev.devid = which; + thing->data.logdev.iscapture = iscapture; + thing->data.logdev.physdev = physthing; + thing->line_connected_to = physthing; + thing->ondrag = DeviceThing_ondrag; + thing->ondrop = LogicalDeviceThing_ondrop; + thing->can_be_dropped_onto = can_be_dropped_onto; + + SetLogicalDeviceTitlebar(thing); + return thing; +} + +static void SetPhysicalDeviceTitlebar(Thing *thing) +{ + SDL_AudioSpec *spec = &thing->data.physdev.spec; + SDL_GetAudioDeviceFormat(thing->data.physdev.devid, spec); + if (thing->data.physdev.devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) { + SDL_asprintf(&thing->titlebar, "Default system device (CAPTURE, %s, %s, %uHz)", AudioFmtToString(spec->format), AudioChansToStr(spec->channels), (unsigned int) spec->freq); + } else if (thing->data.physdev.devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) { + SDL_asprintf(&thing->titlebar, "Default system device (OUTPUT, %s, %s, %uHz)", AudioFmtToString(spec->format), AudioChansToStr(spec->channels), (unsigned int) spec->freq); + } else { + SDL_asprintf(&thing->titlebar, "Physical device #%u (%s, \"%s\", %s, %s, %uHz)", (unsigned int) thing->data.physdev.devid, thing->data.physdev.iscapture ? "CAPTURE" : "OUTPUT", thing->data.physdev.name, AudioFmtToString(spec->format), AudioChansToStr(spec->channels), (unsigned int) spec->freq); + } +} + +static void PhysicalDeviceThing_ondrop(Thing *thing, int button, float x, float y) +{ + if (droppable_highlighted_thing) { + if (droppable_highlighted_thing->what == THING_TRASHCAN) { + TrashThing(thing); + } + } +} + +static void PhysicalDeviceThing_ontick(Thing *thing, Uint64 now) +{ + const int lifetime = POOF_LIFETIME; + const int elasped = (int) (now - thing->createticks); + if (elasped > lifetime) { + thing->scale = 1.0f; + thing->a = 255; + thing->ontick = NULL; /* no more ticking. */ + } else { + const float pct = ((float) elasped) / ((float) lifetime); + thing->a = (Uint8) (int) (pct * 255.0f); + thing->scale = pct; /* grow to normal size */ + } +} + + +static Thing *CreatePhysicalDeviceThing(const SDL_AudioDeviceID which, const SDL_bool iscapture) +{ + static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_NULL }; + static float next_physdev_x = 0; + Thing *thing; + int winw, winh; + + SDL_GetWindowSize(state->windows[0], &winw, &winh); + if (next_physdev_x > (winw-physdev_texture->w)) { + next_physdev_x = 0; + } + + SDL_Log("Adding physical audio device %u", (unsigned int) which); + thing = CreateThing(iscapture ? THING_PHYSDEV_CAPTURE : THING_PHYSDEV, next_physdev_x, 170, 5, -1, -1, physdev_texture, NULL); + thing->data.physdev.devid = which; + thing->data.physdev.iscapture = iscapture; + thing->data.physdev.name = SDL_GetAudioDeviceName(which); + thing->ondrag = DeviceThing_ondrag; + thing->ondrop = PhysicalDeviceThing_ondrop; + thing->ontick = PhysicalDeviceThing_ontick; + thing->can_be_dropped_onto = can_be_dropped_onto; + + SetPhysicalDeviceTitlebar(thing); + if (SDL_GetTicks() <= (app_ready_ticks + 2000)) { /* assume this is the initial batch if it happens in the first two seconds. */ + RepositionRowOfThings(THING_PHYSDEV, 10.0f); /* don't rearrange them after the initial add. */ + RepositionRowOfThings(THING_PHYSDEV_CAPTURE, 170.0f); /* don't rearrange them after the initial add. */ + next_physdev_x = 0.0f; + } else { + next_physdev_x += physdev_texture->w * 1.5f; + } + + return thing; +} + +static Thing *CreateTrashcanThing(void) +{ + int winw, winh; + SDL_GetWindowSize(state->windows[0], &winw, &winh); + return CreateThing(THING_TRASHCAN, winw - trashcan_texture->w, winh - trashcan_texture->h, 10, -1, -1, trashcan_texture, "Drag things here to remove them."); +} + +static Thing *CreateDefaultPhysicalDevice(const SDL_bool iscapture) +{ + return CreatePhysicalDeviceThing(iscapture ? SDL_AUDIO_DEVICE_DEFAULT_CAPTURE : SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, iscapture); +} + +static void TickThings(void) +{ + Thing *i; + Thing *next; + const Uint64 now = SDL_GetTicks(); + for (i = things; i != NULL; i = next) { + next = i->next; /* in case this deletes itself. */ + if (i->ontick) { + i->ontick(i, now); + } + } +} + +static void WindowResized(const int newwinw, const int newwinh) +{ + Thing *i; + const float neww = (float) newwinw; + const float newh = (float) newwinh; + const float oldw = (float) state->window_w; + const float oldh = (float) state->window_h; + for (i = things; i != NULL; i = i->next) { + const float halfw = i->rect.w / 2.0f; + const float halfh = i->rect.h / 2.0f; + const float x = (i->rect.x + halfw) / oldw; + const float y = (i->rect.y + halfh) / oldh; + i->rect.x = (x * neww) - halfw; + i->rect.y = (y * newh) - halfh; + } + state->window_w = newwinw; + state->window_h = newwinh; +} + + +static void Loop(void) +{ + SDL_Event event; + SDL_bool saw_event = SDL_FALSE; + + if (app_ready_ticks == 0) { + app_ready_ticks = SDL_GetTicks(); + } + + while (SDL_PollEvent(&event)) { + Thing *thing = NULL; + + saw_event = SDL_TRUE; + + switch (event.type) { + case SDL_EVENT_MOUSE_MOTION: + thing = UpdateMouseOver(event.motion.x, event.motion.y); + if ((dragging_button == -1) && event.motion.state) { + if (event.motion.state & SDL_BUTTON_LMASK) { + dragging_button = SDL_BUTTON_LEFT; + } else if (event.motion.state & SDL_BUTTON_RMASK) { + dragging_button = SDL_BUTTON_RIGHT; + } else if (event.motion.state & SDL_BUTTON_MMASK) { + dragging_button = SDL_BUTTON_MIDDLE; + } + + + if (dragging_button != -1) { + dragging_thing = thing; + if (thing && thing->ondrag) { + thing->ondrag(thing, dragging_button, event.motion.x, event.motion.y); + } + } + } + + droppable_highlighted_thing = NULL; + if (dragging_thing) { + dragging_thing->rect.x = event.motion.x - (dragging_thing->rect.w / 2); + dragging_thing->rect.y = event.motion.y - (dragging_thing->rect.h / 2); + if (dragging_thing->can_be_dropped_onto) { + thing = FindThingAtPoint(event.motion.x, event.motion.y); + if (thing) { + int i; + for (i = 0; dragging_thing->can_be_dropped_onto[i]; i++) { + if (dragging_thing->can_be_dropped_onto[i] == thing->what) { + droppable_highlighted_thing = thing; + break; + } + } + } + } + } + break; + + case SDL_EVENT_MOUSE_BUTTON_DOWN: + thing = UpdateMouseOver(event.button.x, event.button.y); + break; + + case SDL_EVENT_MOUSE_BUTTON_UP: + if (dragging_button == event.button.button) { + Thing *dropped_thing = dragging_thing; + dragging_thing = NULL; + dragging_button = -1; + if (dropped_thing && dropped_thing->ondrop) { + dropped_thing->ondrop(dropped_thing, event.button.button, event.button.x, event.button.y); + } + droppable_highlighted_thing = NULL; + } + thing = UpdateMouseOver(event.button.x, event.button.y); + break; + + case SDL_EVENT_MOUSE_WHEEL: + UpdateMouseOver(event.wheel.mouseX, event.wheel.mouseY); + break; + + case SDL_EVENT_DROP_FILE: + SDL_Log("Drop file! '%s'", event.drop.file); + LoadWavThing(event.drop.file, event.drop.x, event.drop.y); + /* SDLTest_CommonEvent will free the string, below. */ + break; + + case SDL_EVENT_WINDOW_RESIZED: + WindowResized(event.window.data1, event.window.data2); + break; + + case SDL_EVENT_AUDIO_DEVICE_ADDED: + CreatePhysicalDeviceThing(event.adevice.which, event.adevice.iscapture); + break; + + case SDL_EVENT_AUDIO_DEVICE_REMOVED: { + const SDL_AudioDeviceID which = event.adevice.which; + Thing *i, *next; + SDL_Log("Removing audio device %u", (unsigned int) which); + for (i = things; i != NULL; i = next) { + next = i->next; + if (((i->what == THING_PHYSDEV) || (i->what == THING_PHYSDEV_CAPTURE)) && (i->data.physdev.devid == which)) { + TrashThing(i); + next = things; /* in case we mangled the list. */ + } else if (((i->what == THING_LOGDEV) || (i->what == THING_LOGDEV_CAPTURE)) && (i->data.logdev.devid == which)) { + TrashThing(i); + next = things; /* in case we mangled the list. */ + } + } + break; + } + + default: break; + } + + SDLTest_CommonEvent(state, &event, &done); + } + + TickThings(); + Draw(); + + if (!saw_event) { + SDL_Delay(10); + } + + #ifdef __EMSCRIPTEN__ + if (done) { + emscripten_cancel_main_loop(); + } + #endif +} + +int main(int argc, char *argv[]) +{ + int i; + + state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO | SDL_INIT_AUDIO); + if (state == NULL) { + Quit(1); + } + + state->window_flags |= SDL_WINDOW_RESIZABLE; + + for (i = 1; i < argc;) { + int consumed = SDLTest_CommonArg(state, i); + if (consumed == 0) { + consumed = -1; + /* add our own command lines here. */ + } + if (consumed < 0) { + static const char *options[] = { + /* add our own command lines here. */ + /*"[--blend none|blend|add|mod|mul|sub]",*/ + NULL + }; + SDLTest_CommonLogUsage(state, argv[0], options); + Quit(1); + } + i += consumed; + } + + if (!SDLTest_CommonInit(state)) { + Quit(2); + } + + SetDefaultTitleBar(); + + physdev_texture = CreateTexture("physaudiodev.bmp"); + logdev_texture = CreateTexture("logaudiodev.bmp"); + audio_texture = CreateTexture("audiofile.bmp"); + trashcan_texture = CreateTexture("trashcan.bmp"); + soundboard_texture = CreateTexture("soundboard.bmp"); + soundboard_levels_texture = CreateTexture("soundboard_levels.bmp"); + + LoadStockWavThings(); + CreateTrashcanThing(); + CreateDefaultPhysicalDevice(SDL_FALSE); + CreateDefaultPhysicalDevice(SDL_TRUE); + +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(Loop, 0, 1); +#else + while (!done) { + Loop(); + } +#endif + + Quit(0); + return 0; +} + diff --git a/test/trashcan.bmp b/test/trashcan.bmp new file mode 100644 index 0000000000000000000000000000000000000000..64a60b22ebcc0da79d0922fe468fe920cfd15cf6 GIT binary patch literal 65674 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 F{x=oGqjCTM literal 0 HcmV?d00001 From 1b1f02c5aaec5631cf98262ee70f0217c010ae7c Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 2 Aug 2023 15:07:40 -0400 Subject: [PATCH 137/138] testaudio: Apparently compilers don't like this possibly being NULL now...? --- test/testaudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testaudio.c b/test/testaudio.c index d8ca140567..ff0fb27a83 100644 --- a/test/testaudio.c +++ b/test/testaudio.c @@ -591,7 +591,7 @@ static Thing *CreateStreamThing(const SDL_AudioSpec *spec, const Uint8 *buf, con { static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_LOGDEV, THING_LOGDEV_CAPTURE, THING_NULL }; Thing *thing = CreateThing(THING_STREAM, x, y, 0, -1, -1, soundboard_texture, fname ? xstrdup(fname) : NULL); - SDL_Log("Adding audio stream for %s", fname); + SDL_Log("Adding audio stream for %s", fname ? fname : "(null)"); thing->data.stream.stream = SDL_CreateAudioStream(spec, spec); if (buf && buflen) { SDL_PutAudioStreamData(thing->data.stream.stream, buf, (int) buflen); From 5ca3c50bf0477562b17647773e1f65307d86d3bc Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 2 Aug 2023 15:23:37 -0400 Subject: [PATCH 138/138] testaudio: Fix compiler warning. --- test/testaudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testaudio.c b/test/testaudio.c index ff0fb27a83..7087ca2111 100644 --- a/test/testaudio.c +++ b/test/testaudio.c @@ -547,7 +547,7 @@ static void StreamThing_ondrop(Thing *thing, int button, float x, float y) TrashThing(thing); } else if (((droppable_highlighted_thing->what == THING_LOGDEV) || (droppable_highlighted_thing->what == THING_LOGDEV_CAPTURE)) && (droppable_highlighted_thing != thing->line_connected_to)) { /* connect to a logical device! */ - SDL_Log("Binding audio stream ('%s') to logical device %u", thing->titlebar, droppable_highlighted_thing->data.logdev.devid); + SDL_Log("Binding audio stream ('%s') to logical device %u", thing->titlebar, (unsigned int) droppable_highlighted_thing->data.logdev.devid); if (thing->line_connected_to) { const SDL_AudioSpec *spec = &droppable_highlighted_thing->data.logdev.spec; SDL_UnbindAudioStream(thing->data.stream.stream); /* unbind from current device */