chore(build): use SDL3

This commit is contained in:
phaneron 2025-04-12 04:38:19 -04:00
parent 9d04a35d87
commit b3c0734a9e
3286 changed files with 866354 additions and 554996 deletions

850
vendor/sdl-3.2.10/src/SDL.c vendored Normal file
View file

@ -0,0 +1,850 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL3/SDL_revision.h"
#if defined(SDL_PLATFORM_WINDOWS)
#include "core/windows/SDL_windows.h"
#else
#include <unistd.h> // _exit(), etc.
#endif
// this checks for HAVE_DBUS_DBUS_H internally.
#include "core/linux/SDL_dbus.h"
#ifdef SDL_PLATFORM_EMSCRIPTEN
#include <emscripten.h>
#endif
// Initialization code for SDL
#include "SDL_assert_c.h"
#include "SDL_hints_c.h"
#include "SDL_log_c.h"
#include "SDL_properties_c.h"
#include "audio/SDL_sysaudio.h"
#include "camera/SDL_camera_c.h"
#include "cpuinfo/SDL_cpuinfo_c.h"
#include "events/SDL_events_c.h"
#include "haptic/SDL_haptic_c.h"
#include "joystick/SDL_gamepad_c.h"
#include "joystick/SDL_joystick_c.h"
#include "render/SDL_sysrender.h"
#include "sensor/SDL_sensor_c.h"
#include "stdlib/SDL_getenv_c.h"
#include "thread/SDL_thread_c.h"
#include "tray/SDL_tray_utils.h"
#include "video/SDL_pixels_c.h"
#include "video/SDL_surface_c.h"
#include "video/SDL_video_c.h"
#include "filesystem/SDL_filesystem_c.h"
#include "io/SDL_asyncio_c.h"
#ifdef SDL_PLATFORM_ANDROID
#include "core/android/SDL_android.h"
#endif
#define SDL_INIT_EVERYTHING ~0U
// Initialization/Cleanup routines
#include "timer/SDL_timer_c.h"
#ifdef SDL_VIDEO_DRIVER_WINDOWS
extern bool SDL_HelperWindowCreate(void);
extern void SDL_HelperWindowDestroy(void);
#endif
#ifdef SDL_BUILD_MAJOR_VERSION
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MAJOR_VERSION,
SDL_MAJOR_VERSION == SDL_BUILD_MAJOR_VERSION);
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MINOR_VERSION,
SDL_MINOR_VERSION == SDL_BUILD_MINOR_VERSION);
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MICRO_VERSION,
SDL_MICRO_VERSION == SDL_BUILD_MICRO_VERSION);
#endif
// Limited by its encoding in SDL_VERSIONNUM
SDL_COMPILE_TIME_ASSERT(SDL_MAJOR_VERSION_min, SDL_MAJOR_VERSION >= 0);
SDL_COMPILE_TIME_ASSERT(SDL_MAJOR_VERSION_max, SDL_MAJOR_VERSION <= 10);
SDL_COMPILE_TIME_ASSERT(SDL_MINOR_VERSION_min, SDL_MINOR_VERSION >= 0);
SDL_COMPILE_TIME_ASSERT(SDL_MINOR_VERSION_max, SDL_MINOR_VERSION <= 999);
SDL_COMPILE_TIME_ASSERT(SDL_MICRO_VERSION_min, SDL_MICRO_VERSION >= 0);
SDL_COMPILE_TIME_ASSERT(SDL_MICRO_VERSION_max, SDL_MICRO_VERSION <= 999);
/* This is not declared in any header, although it is shared between some
parts of SDL, because we don't want anything calling it without an
extremely good reason. */
extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
SDL_NORETURN void SDL_ExitProcess(int exitcode)
{
#if defined(SDL_PLATFORM_WINDOWS)
/* "if you do not know the state of all threads in your process, it is
better to call TerminateProcess than ExitProcess"
https://msdn.microsoft.com/en-us/library/windows/desktop/ms682658(v=vs.85).aspx */
TerminateProcess(GetCurrentProcess(), exitcode);
/* MingW doesn't have TerminateProcess marked as noreturn, so add an
ExitProcess here that will never be reached but make MingW happy. */
ExitProcess(exitcode);
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
emscripten_cancel_main_loop(); // this should "kill" the app.
emscripten_force_exit(exitcode); // this should "kill" the app.
exit(exitcode);
#elif defined(SDL_PLATFORM_HAIKU) // Haiku has _Exit, but it's not marked noreturn.
_exit(exitcode);
#elif defined(HAVE__EXIT) // Upper case _Exit()
_Exit(exitcode);
#else
_exit(exitcode);
#endif
}
// App metadata
bool SDL_SetAppMetadata(const char *appname, const char *appversion, const char *appidentifier)
{
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING, appname);
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING, appversion);
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, appidentifier);
return true;
}
static bool SDL_ValidMetadataProperty(const char *name)
{
if (!name || !*name) {
return false;
}
if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0 ||
SDL_strcmp(name, SDL_PROP_APP_METADATA_VERSION_STRING) == 0 ||
SDL_strcmp(name, SDL_PROP_APP_METADATA_IDENTIFIER_STRING) == 0 ||
SDL_strcmp(name, SDL_PROP_APP_METADATA_CREATOR_STRING) == 0 ||
SDL_strcmp(name, SDL_PROP_APP_METADATA_COPYRIGHT_STRING) == 0 ||
SDL_strcmp(name, SDL_PROP_APP_METADATA_URL_STRING) == 0 ||
SDL_strcmp(name, SDL_PROP_APP_METADATA_TYPE_STRING) == 0) {
return true;
}
return false;
}
bool SDL_SetAppMetadataProperty(const char *name, const char *value)
{
if (!SDL_ValidMetadataProperty(name)) {
return SDL_InvalidParamError("name");
}
return SDL_SetStringProperty(SDL_GetGlobalProperties(), name, value);
}
const char *SDL_GetAppMetadataProperty(const char *name)
{
if (!SDL_ValidMetadataProperty(name)) {
SDL_InvalidParamError("name");
return NULL;
}
const char *value = NULL;
if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0) {
value = SDL_GetHint(SDL_HINT_APP_NAME);
} else if (SDL_strcmp(name, SDL_PROP_APP_METADATA_IDENTIFIER_STRING) == 0) {
value = SDL_GetHint(SDL_HINT_APP_ID);
}
if (!value || !*value) {
value = SDL_GetStringProperty(SDL_GetGlobalProperties(), name, NULL);
}
if (!value || !*value) {
if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0) {
value = "SDL Application";
} else if (SDL_strcmp(name, SDL_PROP_APP_METADATA_TYPE_STRING) == 0) {
value = "application";
}
}
return value;
}
// The initialized subsystems
#ifdef SDL_MAIN_NEEDED
static bool SDL_MainIsReady = false;
#else
static bool SDL_MainIsReady = true;
#endif
static SDL_ThreadID SDL_MainThreadID = 0;
static bool SDL_bInMainQuit = false;
static Uint8 SDL_SubsystemRefCount[32];
// Private helper to increment a subsystem's ref counter.
static void SDL_IncrementSubsystemRefCount(Uint32 subsystem)
{
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255));
if (subsystem_index >= 0) {
++SDL_SubsystemRefCount[subsystem_index];
}
}
// Private helper to decrement a subsystem's ref counter.
static void SDL_DecrementSubsystemRefCount(Uint32 subsystem)
{
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
if ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] > 0)) {
if (SDL_bInMainQuit) {
SDL_SubsystemRefCount[subsystem_index] = 0;
} else {
--SDL_SubsystemRefCount[subsystem_index];
}
}
}
// Private helper to check if a system needs init.
static bool SDL_ShouldInitSubsystem(Uint32 subsystem)
{
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255));
return ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 0));
}
// Private helper to check if a system needs to be quit.
static bool SDL_ShouldQuitSubsystem(Uint32 subsystem)
{
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
if ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 0)) {
return false;
}
/* If we're in SDL_Quit, we shut down every subsystem, even if refcount
* isn't zero.
*/
return (((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 1)) || SDL_bInMainQuit);
}
/* Private helper to either increment's existing ref counter,
* or fully init a new subsystem. */
static bool SDL_InitOrIncrementSubsystem(Uint32 subsystem)
{
int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255));
if (subsystem_index < 0) {
return false;
}
if (SDL_SubsystemRefCount[subsystem_index] > 0) {
++SDL_SubsystemRefCount[subsystem_index];
return true;
}
return SDL_InitSubSystem(subsystem);
}
void SDL_SetMainReady(void)
{
SDL_MainIsReady = true;
if (SDL_MainThreadID == 0) {
SDL_MainThreadID = SDL_GetCurrentThreadID();
}
}
bool SDL_IsMainThread(void)
{
if (SDL_MainThreadID == 0) {
// Not initialized yet?
return true;
}
if (SDL_MainThreadID == SDL_GetCurrentThreadID()) {
return true;
}
return false;
}
// Initialize all the subsystems that require initialization before threads start
void SDL_InitMainThread(void)
{
static bool done_info = false;
SDL_InitTLSData();
SDL_InitEnvironment();
SDL_InitTicks();
SDL_InitFilesystem();
if (!done_info) {
const char *value;
value = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "App name: %s", value ? value : "<unspecified>");
value = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING);
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "App version: %s", value ? value : "<unspecified>");
value = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING);
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "App ID: %s", value ? value : "<unspecified>");
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "SDL revision: %s", SDL_REVISION);
done_info = true;
}
}
static void SDL_QuitMainThread(void)
{
SDL_QuitFilesystem();
SDL_QuitTicks();
SDL_QuitEnvironment();
SDL_QuitTLSData();
}
bool SDL_InitSubSystem(SDL_InitFlags flags)
{
Uint32 flags_initialized = 0;
if (!SDL_MainIsReady) {
return SDL_SetError("Application didn't initialize properly, did you include SDL_main.h in the file containing your main() function?");
}
SDL_InitMainThread();
#ifdef SDL_USE_LIBDBUS
SDL_DBus_Init();
#endif
#ifdef SDL_VIDEO_DRIVER_WINDOWS
if (flags & (SDL_INIT_HAPTIC | SDL_INIT_JOYSTICK)) {
if (!SDL_HelperWindowCreate()) {
goto quit_and_error;
}
}
#endif
// Initialize the event subsystem
if (flags & SDL_INIT_EVENTS) {
if (SDL_ShouldInitSubsystem(SDL_INIT_EVENTS)) {
SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS);
if (!SDL_InitEvents()) {
SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS);
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS);
}
flags_initialized |= SDL_INIT_EVENTS;
}
// Initialize the video subsystem
if (flags & SDL_INIT_VIDEO) {
#ifndef SDL_VIDEO_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_VIDEO)) {
// video implies events
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
goto quit_and_error;
}
// We initialize video on the main thread
// On Apple platforms this is a requirement.
// On other platforms, this is the definition.
SDL_MainThreadID = SDL_GetCurrentThreadID();
SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO);
if (!SDL_VideoInit(NULL)) {
SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);
SDL_PushError();
SDL_QuitSubSystem(SDL_INIT_EVENTS);
SDL_PopError();
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO);
}
flags_initialized |= SDL_INIT_VIDEO;
#else
SDL_SetError("SDL not built with video support");
goto quit_and_error;
#endif
}
// Initialize the audio subsystem
if (flags & SDL_INIT_AUDIO) {
#ifndef SDL_AUDIO_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_AUDIO)) {
// audio implies events
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
goto quit_and_error;
}
SDL_IncrementSubsystemRefCount(SDL_INIT_AUDIO);
if (!SDL_InitAudio(NULL)) {
SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO);
SDL_PushError();
SDL_QuitSubSystem(SDL_INIT_EVENTS);
SDL_PopError();
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_AUDIO);
}
flags_initialized |= SDL_INIT_AUDIO;
#else
SDL_SetError("SDL not built with audio support");
goto quit_and_error;
#endif
}
// Initialize the joystick subsystem
if (flags & SDL_INIT_JOYSTICK) {
#ifndef SDL_JOYSTICK_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_JOYSTICK)) {
// joystick implies events
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
goto quit_and_error;
}
SDL_IncrementSubsystemRefCount(SDL_INIT_JOYSTICK);
if (!SDL_InitJoysticks()) {
SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK);
SDL_PushError();
SDL_QuitSubSystem(SDL_INIT_EVENTS);
SDL_PopError();
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_JOYSTICK);
}
flags_initialized |= SDL_INIT_JOYSTICK;
#else
SDL_SetError("SDL not built with joystick support");
goto quit_and_error;
#endif
}
if (flags & SDL_INIT_GAMEPAD) {
#ifndef SDL_JOYSTICK_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_GAMEPAD)) {
// game controller implies joystick
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_JOYSTICK)) {
goto quit_and_error;
}
SDL_IncrementSubsystemRefCount(SDL_INIT_GAMEPAD);
if (!SDL_InitGamepads()) {
SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD);
SDL_PushError();
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
SDL_PopError();
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_GAMEPAD);
}
flags_initialized |= SDL_INIT_GAMEPAD;
#else
SDL_SetError("SDL not built with joystick support");
goto quit_and_error;
#endif
}
// Initialize the haptic subsystem
if (flags & SDL_INIT_HAPTIC) {
#ifndef SDL_HAPTIC_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_HAPTIC)) {
SDL_IncrementSubsystemRefCount(SDL_INIT_HAPTIC);
if (!SDL_InitHaptics()) {
SDL_DecrementSubsystemRefCount(SDL_INIT_HAPTIC);
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_HAPTIC);
}
flags_initialized |= SDL_INIT_HAPTIC;
#else
SDL_SetError("SDL not built with haptic (force feedback) support");
goto quit_and_error;
#endif
}
// Initialize the sensor subsystem
if (flags & SDL_INIT_SENSOR) {
#ifndef SDL_SENSOR_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_SENSOR)) {
SDL_IncrementSubsystemRefCount(SDL_INIT_SENSOR);
if (!SDL_InitSensors()) {
SDL_DecrementSubsystemRefCount(SDL_INIT_SENSOR);
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_SENSOR);
}
flags_initialized |= SDL_INIT_SENSOR;
#else
SDL_SetError("SDL not built with sensor support");
goto quit_and_error;
#endif
}
// Initialize the camera subsystem
if (flags & SDL_INIT_CAMERA) {
#ifndef SDL_CAMERA_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_CAMERA)) {
// camera implies events
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
goto quit_and_error;
}
SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA);
if (!SDL_CameraInit(NULL)) {
SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
SDL_PushError();
SDL_QuitSubSystem(SDL_INIT_EVENTS);
SDL_PopError();
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA);
}
flags_initialized |= SDL_INIT_CAMERA;
#else
SDL_SetError("SDL not built with camera support");
goto quit_and_error;
#endif
}
(void)flags_initialized; // make static analysis happy, since this only gets used in error cases.
return SDL_ClearError();
quit_and_error:
{
SDL_PushError();
SDL_QuitSubSystem(flags_initialized);
SDL_PopError();
}
return false;
}
bool SDL_Init(SDL_InitFlags flags)
{
return SDL_InitSubSystem(flags);
}
void SDL_QuitSubSystem(SDL_InitFlags flags)
{
// Shut down requested initialized subsystems
#ifndef SDL_CAMERA_DISABLED
if (flags & SDL_INIT_CAMERA) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_CAMERA)) {
SDL_QuitCamera();
// camera implies events
SDL_QuitSubSystem(SDL_INIT_EVENTS);
}
SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
}
#endif
#ifndef SDL_SENSOR_DISABLED
if (flags & SDL_INIT_SENSOR) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_SENSOR)) {
SDL_QuitSensors();
}
SDL_DecrementSubsystemRefCount(SDL_INIT_SENSOR);
}
#endif
#ifndef SDL_JOYSTICK_DISABLED
if (flags & SDL_INIT_GAMEPAD) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_GAMEPAD)) {
SDL_QuitGamepads();
// game controller implies joystick
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}
SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD);
}
if (flags & SDL_INIT_JOYSTICK) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_JOYSTICK)) {
SDL_QuitJoysticks();
// joystick implies events
SDL_QuitSubSystem(SDL_INIT_EVENTS);
}
SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK);
}
#endif
#ifndef SDL_HAPTIC_DISABLED
if (flags & SDL_INIT_HAPTIC) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_HAPTIC)) {
SDL_QuitHaptics();
}
SDL_DecrementSubsystemRefCount(SDL_INIT_HAPTIC);
}
#endif
#ifndef SDL_AUDIO_DISABLED
if (flags & SDL_INIT_AUDIO) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_AUDIO)) {
SDL_QuitAudio();
// audio implies events
SDL_QuitSubSystem(SDL_INIT_EVENTS);
}
SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO);
}
#endif
#ifndef SDL_VIDEO_DISABLED
if (flags & SDL_INIT_VIDEO) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_VIDEO)) {
SDL_QuitRender();
SDL_VideoQuit();
// video implies events
SDL_QuitSubSystem(SDL_INIT_EVENTS);
}
SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);
}
#endif
if (flags & SDL_INIT_EVENTS) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_EVENTS)) {
SDL_QuitEvents();
}
SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS);
}
}
Uint32 SDL_WasInit(SDL_InitFlags flags)
{
int i;
int num_subsystems = SDL_arraysize(SDL_SubsystemRefCount);
Uint32 initialized = 0;
// Fast path for checking one flag
if (SDL_HasExactlyOneBitSet32(flags)) {
int subsystem_index = SDL_MostSignificantBitIndex32(flags);
return SDL_SubsystemRefCount[subsystem_index] ? flags : 0;
}
if (!flags) {
flags = SDL_INIT_EVERYTHING;
}
num_subsystems = SDL_min(num_subsystems, SDL_MostSignificantBitIndex32(flags) + 1);
// Iterate over each bit in flags, and check the matching subsystem.
for (i = 0; i < num_subsystems; ++i) {
if ((flags & 1) && SDL_SubsystemRefCount[i] > 0) {
initialized |= (1 << i);
}
flags >>= 1;
}
return initialized;
}
void SDL_Quit(void)
{
SDL_bInMainQuit = true;
// Quit all subsystems
#ifdef SDL_VIDEO_DRIVER_WINDOWS
SDL_HelperWindowDestroy();
#endif
SDL_QuitSubSystem(SDL_INIT_EVERYTHING);
SDL_CleanupTrays();
#ifdef SDL_USE_LIBDBUS
SDL_DBus_Quit();
#endif
SDL_QuitTimers();
SDL_QuitAsyncIO();
SDL_SetObjectsInvalid();
SDL_AssertionsQuit();
SDL_QuitPixelFormatDetails();
SDL_QuitCPUInfo();
/* Now that every subsystem has been quit, we reset the subsystem refcount
* and the list of initialized subsystems.
*/
SDL_memset(SDL_SubsystemRefCount, 0x0, sizeof(SDL_SubsystemRefCount));
SDL_QuitLog();
SDL_QuitHints();
SDL_QuitProperties();
SDL_QuitMainThread();
SDL_bInMainQuit = false;
}
// Get the library version number
int SDL_GetVersion(void)
{
return SDL_VERSION;
}
// Get the library source revision
const char *SDL_GetRevision(void)
{
return SDL_REVISION;
}
// Get the name of the platform
const char *SDL_GetPlatform(void)
{
#if defined(SDL_PLATFORM_PRIVATE)
return SDL_PLATFORM_PRIVATE_NAME;
#elif defined(SDL_PLATFORM_AIX)
return "AIX";
#elif defined(SDL_PLATFORM_ANDROID)
return "Android";
#elif defined(SDL_PLATFORM_BSDI)
return "BSDI";
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
return "Emscripten";
#elif defined(SDL_PLATFORM_FREEBSD)
return "FreeBSD";
#elif defined(SDL_PLATFORM_HAIKU)
return "Haiku";
#elif defined(SDL_PLATFORM_HPUX)
return "HP-UX";
#elif defined(SDL_PLATFORM_IRIX)
return "Irix";
#elif defined(SDL_PLATFORM_LINUX)
return "Linux";
#elif defined(__MINT__)
return "Atari MiNT";
#elif defined(SDL_PLATFORM_MACOS)
return "macOS";
#elif defined(SDL_PLATFORM_NETBSD)
return "NetBSD";
#elif defined(SDL_PLATFORM_OPENBSD)
return "OpenBSD";
#elif defined(SDL_PLATFORM_OS2)
return "OS/2";
#elif defined(SDL_PLATFORM_OSF)
return "OSF/1";
#elif defined(SDL_PLATFORM_QNXNTO)
return "QNX Neutrino";
#elif defined(SDL_PLATFORM_RISCOS)
return "RISC OS";
#elif defined(SDL_PLATFORM_SOLARIS)
return "Solaris";
#elif defined(SDL_PLATFORM_WIN32)
return "Windows";
#elif defined(SDL_PLATFORM_WINGDK)
return "WinGDK";
#elif defined(SDL_PLATFORM_XBOXONE)
return "Xbox One";
#elif defined(SDL_PLATFORM_XBOXSERIES)
return "Xbox Series X|S";
#elif defined(SDL_PLATFORM_IOS)
return "iOS";
#elif defined(SDL_PLATFORM_TVOS)
return "tvOS";
#elif defined(SDL_PLATFORM_PS2)
return "PlayStation 2";
#elif defined(SDL_PLATFORM_PSP)
return "PlayStation Portable";
#elif defined(SDL_PLATFORM_VITA)
return "PlayStation Vita";
#elif defined(SDL_PLATFORM_3DS)
return "Nintendo 3DS";
#elif defined(__managarm__)
return "Managarm";
#else
return "Unknown (see SDL_platform.h)";
#endif
}
bool SDL_IsTablet(void)
{
#ifdef SDL_PLATFORM_ANDROID
return SDL_IsAndroidTablet();
#elif defined(SDL_PLATFORM_IOS)
extern bool SDL_IsIPad(void);
return SDL_IsIPad();
#else
return false;
#endif
}
bool SDL_IsTV(void)
{
#ifdef SDL_PLATFORM_ANDROID
return SDL_IsAndroidTV();
#elif defined(SDL_PLATFORM_IOS)
extern bool SDL_IsAppleTV(void);
return SDL_IsAppleTV();
#else
return false;
#endif
}
static SDL_Sandbox SDL_DetectSandbox(void)
{
#if defined(SDL_PLATFORM_LINUX)
if (access("/.flatpak-info", F_OK) == 0) {
return SDL_SANDBOX_FLATPAK;
}
/* For Snap, we check multiple variables because they might be set for
* unrelated reasons. This is the same thing WebKitGTK does. */
if (SDL_getenv("SNAP") && SDL_getenv("SNAP_NAME") && SDL_getenv("SNAP_REVISION")) {
return SDL_SANDBOX_SNAP;
}
if (access("/run/host/container-manager", F_OK) == 0) {
return SDL_SANDBOX_UNKNOWN_CONTAINER;
}
#elif defined(SDL_PLATFORM_MACOS)
if (SDL_getenv("APP_SANDBOX_CONTAINER_ID")) {
return SDL_SANDBOX_MACOS;
}
#endif
return SDL_SANDBOX_NONE;
}
SDL_Sandbox SDL_GetSandbox(void)
{
static SDL_Sandbox sandbox;
static bool sandbox_initialized;
if (!sandbox_initialized) {
sandbox = SDL_DetectSandbox();
sandbox_initialized = true;
}
return sandbox;
}
#ifdef SDL_PLATFORM_WIN32
#if (!defined(HAVE_LIBC) || defined(__WATCOMC__)) && !defined(SDL_STATIC_LIB)
// FIXME: Still need to include DllMain() on Watcom C ?
BOOL APIENTRY MINGW32_FORCEALIGN _DllMainCRTStartup(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
#endif // Building DLL
#endif // defined(SDL_PLATFORM_WIN32)

454
vendor/sdl-3.2.10/src/SDL_assert.c vendored Normal file
View file

@ -0,0 +1,454 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(SDL_PLATFORM_WINDOWS)
#include "core/windows/SDL_windows.h"
#endif
#include "SDL_assert_c.h"
#include "video/SDL_sysvideo.h"
#if defined(SDL_PLATFORM_WINDOWS)
#ifndef WS_OVERLAPPEDWINDOW
#define WS_OVERLAPPEDWINDOW 0
#endif
#endif
#ifdef SDL_PLATFORM_EMSCRIPTEN
#include <emscripten.h>
#endif
// The size of the stack buffer to use for rendering assert messages.
#define SDL_MAX_ASSERT_MESSAGE_STACK 256
static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata);
/*
* We keep all triggered assertions in a singly-linked list so we can
* generate a report later.
*/
static SDL_AssertData *triggered_assertions = NULL;
#ifndef SDL_THREADS_DISABLED
static SDL_Mutex *assertion_mutex = NULL;
#endif
static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion;
static void *assertion_userdata = NULL;
#ifdef __GNUC__
static void debug_print(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
#endif
static void debug_print(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_WARN, fmt, ap);
va_end(ap);
}
static void SDL_AddAssertionToReport(SDL_AssertData *data)
{
/* (data) is always a static struct defined with the assert macros, so
we don't have to worry about copying or allocating them. */
data->trigger_count++;
if (data->trigger_count == 1) { // not yet added?
data->next = triggered_assertions;
triggered_assertions = data;
}
}
#if defined(SDL_PLATFORM_WINDOWS)
#define ENDLINE "\r\n"
#else
#define ENDLINE "\n"
#endif
static int SDL_RenderAssertMessage(char *buf, size_t buf_len, const SDL_AssertData *data)
{
return SDL_snprintf(buf, buf_len,
"Assertion failure at %s (%s:%d), triggered %u %s:" ENDLINE " '%s'",
data->function, data->filename, data->linenum,
data->trigger_count, (data->trigger_count == 1) ? "time" : "times",
data->condition);
}
static void SDL_GenerateAssertionReport(void)
{
const SDL_AssertData *item = triggered_assertions;
// only do this if the app hasn't assigned an assertion handler.
if ((item) && (assertion_handler != SDL_PromptAssertion)) {
debug_print("\n\nSDL assertion report.\n");
debug_print("All SDL assertions between last init/quit:\n\n");
while (item) {
debug_print(
"'%s'\n"
" * %s (%s:%d)\n"
" * triggered %u time%s.\n"
" * always ignore: %s.\n",
item->condition, item->function, item->filename,
item->linenum, item->trigger_count,
(item->trigger_count == 1) ? "" : "s",
item->always_ignore ? "yes" : "no");
item = item->next;
}
debug_print("\n");
SDL_ResetAssertionReport();
}
}
/* This is not declared in any header, although it is shared between some
parts of SDL, because we don't want anything calling it without an
extremely good reason. */
#ifdef __WATCOMC__
extern void SDL_ExitProcess(int exitcode);
#pragma aux SDL_ExitProcess aborts;
#endif
extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
#ifdef __WATCOMC__
static void SDL_AbortAssertion(void);
#pragma aux SDL_AbortAssertion aborts;
#endif
static SDL_NORETURN void SDL_AbortAssertion(void)
{
SDL_Quit();
SDL_ExitProcess(42);
}
static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata)
{
SDL_AssertState state = SDL_ASSERTION_ABORT;
SDL_Window *window;
SDL_MessageBoxData messagebox;
SDL_MessageBoxButtonData buttons[] = {
{ 0, SDL_ASSERTION_RETRY, "Retry" },
{ 0, SDL_ASSERTION_BREAK, "Break" },
{ 0, SDL_ASSERTION_ABORT, "Abort" },
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
SDL_ASSERTION_IGNORE, "Ignore" },
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
SDL_ASSERTION_ALWAYS_IGNORE, "Always Ignore" }
};
int selected;
char stack_buf[SDL_MAX_ASSERT_MESSAGE_STACK];
char *message = stack_buf;
size_t buf_len = sizeof(stack_buf);
int len;
(void)userdata; // unused in default handler.
// Assume the output will fit...
len = SDL_RenderAssertMessage(message, buf_len, data);
// .. and if it didn't, try to allocate as much room as we actually need.
if (len >= (int)buf_len) {
if (SDL_size_add_check_overflow(len, 1, &buf_len)) {
message = (char *)SDL_malloc(buf_len);
if (message) {
len = SDL_RenderAssertMessage(message, buf_len, data);
} else {
message = stack_buf;
}
}
}
// Something went very wrong
if (len < 0) {
if (message != stack_buf) {
SDL_free(message);
}
return SDL_ASSERTION_ABORT;
}
debug_print("\n\n%s\n\n", message);
// let env. variable override, so unit tests won't block in a GUI.
const char *hint = SDL_GetHint(SDL_HINT_ASSERT);
if (hint) {
if (message != stack_buf) {
SDL_free(message);
}
if (SDL_strcmp(hint, "abort") == 0) {
return SDL_ASSERTION_ABORT;
} else if (SDL_strcmp(hint, "break") == 0) {
return SDL_ASSERTION_BREAK;
} else if (SDL_strcmp(hint, "retry") == 0) {
return SDL_ASSERTION_RETRY;
} else if (SDL_strcmp(hint, "ignore") == 0) {
return SDL_ASSERTION_IGNORE;
} else if (SDL_strcmp(hint, "always_ignore") == 0) {
return SDL_ASSERTION_ALWAYS_IGNORE;
} else {
return SDL_ASSERTION_ABORT; // oh well.
}
}
// Leave fullscreen mode, if possible (scary!)
window = SDL_GetToplevelForKeyboardFocus();
if (window) {
if (window->fullscreen_exclusive) {
SDL_MinimizeWindow(window);
} else {
// !!! FIXME: ungrab the input if we're not fullscreen?
// No need to mess with the window
window = NULL;
}
}
// Show a messagebox if we can, otherwise fall back to stdio
SDL_zero(messagebox);
messagebox.flags = SDL_MESSAGEBOX_WARNING;
messagebox.window = window;
messagebox.title = "Assertion Failed";
messagebox.message = message;
messagebox.numbuttons = SDL_arraysize(buttons);
messagebox.buttons = buttons;
if (SDL_ShowMessageBox(&messagebox, &selected)) {
if (selected == -1) {
state = SDL_ASSERTION_IGNORE;
} else {
state = (SDL_AssertState)selected;
}
} else {
#ifdef SDL_PLATFORM_PRIVATE_ASSERT
SDL_PRIVATE_PROMPTASSERTION();
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
// This is nasty, but we can't block on a custom UI.
for (;;) {
bool okay = true;
/* *INDENT-OFF* */ // clang-format off
int reply = MAIN_THREAD_EM_ASM_INT({
var str =
UTF8ToString($0) + '\n\n' +
'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
var reply = window.prompt(str, "i");
if (reply === null) {
reply = "i";
}
return reply.length === 1 ? reply.charCodeAt(0) : -1;
}, message);
/* *INDENT-ON* */ // clang-format on
switch (reply) {
case 'a':
state = SDL_ASSERTION_ABORT;
break;
#if 0 // (currently) no break functionality on Emscripten
case 'b':
state = SDL_ASSERTION_BREAK;
break;
#endif
case 'r':
state = SDL_ASSERTION_RETRY;
break;
case 'i':
state = SDL_ASSERTION_IGNORE;
break;
case 'A':
state = SDL_ASSERTION_ALWAYS_IGNORE;
break;
default:
okay = false;
break;
}
if (okay) {
break;
}
}
#elif defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_3DS)
// this is a little hacky.
for (;;) {
char buf[32];
(void)fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : ");
(void)fflush(stderr);
if (fgets(buf, sizeof(buf), stdin) == NULL) {
break;
}
if (SDL_strncmp(buf, "a", 1) == 0) {
state = SDL_ASSERTION_ABORT;
break;
} else if (SDL_strncmp(buf, "b", 1) == 0) {
state = SDL_ASSERTION_BREAK;
break;
} else if (SDL_strncmp(buf, "r", 1) == 0) {
state = SDL_ASSERTION_RETRY;
break;
} else if (SDL_strncmp(buf, "i", 1) == 0) {
state = SDL_ASSERTION_IGNORE;
break;
} else if (SDL_strncmp(buf, "A", 1) == 0) {
state = SDL_ASSERTION_ALWAYS_IGNORE;
break;
}
}
#else
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Assertion Failed", message, window);
#endif // HAVE_STDIO_H
}
// Re-enter fullscreen mode
if (window) {
SDL_RestoreWindow(window);
}
if (message != stack_buf) {
SDL_free(message);
}
return state;
}
SDL_AssertState SDL_ReportAssertion(SDL_AssertData *data, const char *func, const char *file, int line)
{
SDL_AssertState state = SDL_ASSERTION_IGNORE;
static int assertion_running = 0;
#ifndef SDL_THREADS_DISABLED
static SDL_SpinLock spinlock = 0;
SDL_LockSpinlock(&spinlock);
if (!assertion_mutex) { // never called SDL_Init()?
assertion_mutex = SDL_CreateMutex();
if (!assertion_mutex) {
SDL_UnlockSpinlock(&spinlock);
return SDL_ASSERTION_IGNORE; // oh well, I guess.
}
}
SDL_UnlockSpinlock(&spinlock);
SDL_LockMutex(assertion_mutex);
#endif // !SDL_THREADS_DISABLED
// doing this because Visual C is upset over assigning in the macro.
if (data->trigger_count == 0) {
data->function = func;
data->filename = file;
data->linenum = line;
}
SDL_AddAssertionToReport(data);
assertion_running++;
if (assertion_running > 1) { // assert during assert! Abort.
if (assertion_running == 2) {
SDL_AbortAssertion();
} else if (assertion_running == 3) { // Abort asserted!
SDL_ExitProcess(42);
} else {
while (1) { // do nothing but spin; what else can you do?!
}
}
}
if (!data->always_ignore) {
state = assertion_handler(data, assertion_userdata);
}
switch (state) {
case SDL_ASSERTION_ALWAYS_IGNORE:
state = SDL_ASSERTION_IGNORE;
data->always_ignore = true;
break;
case SDL_ASSERTION_IGNORE:
case SDL_ASSERTION_RETRY:
case SDL_ASSERTION_BREAK:
break; // macro handles these.
case SDL_ASSERTION_ABORT:
SDL_AbortAssertion();
// break; ...shouldn't return, but oh well.
}
assertion_running--;
#ifndef SDL_THREADS_DISABLED
SDL_UnlockMutex(assertion_mutex);
#endif
return state;
}
void SDL_AssertionsQuit(void)
{
#if SDL_ASSERT_LEVEL > 0
SDL_GenerateAssertionReport();
#ifndef SDL_THREADS_DISABLED
if (assertion_mutex) {
SDL_DestroyMutex(assertion_mutex);
assertion_mutex = NULL;
}
#endif
#endif // SDL_ASSERT_LEVEL > 0
}
void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata)
{
if (handler != NULL) {
assertion_handler = handler;
assertion_userdata = userdata;
} else {
assertion_handler = SDL_PromptAssertion;
assertion_userdata = NULL;
}
}
const SDL_AssertData *SDL_GetAssertionReport(void)
{
return triggered_assertions;
}
void SDL_ResetAssertionReport(void)
{
SDL_AssertData *next = NULL;
SDL_AssertData *item;
for (item = triggered_assertions; item; item = next) {
next = (SDL_AssertData *)item->next;
item->always_ignore = false;
item->trigger_count = 0;
item->next = NULL;
}
triggered_assertions = NULL;
}
SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
{
return SDL_PromptAssertion;
}
SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata)
{
if (userdata) {
*userdata = assertion_userdata;
}
return assertion_handler;
}

28
vendor/sdl-3.2.10/src/SDL_assert_c.h vendored Normal file
View file

@ -0,0 +1,28 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_assert_c_h_
#define SDL_assert_c_h_
extern void SDL_AssertionsQuit(void);
#endif // SDL_assert_c_h_

112
vendor/sdl-3.2.10/src/SDL_error.c vendored Normal file
View file

@ -0,0 +1,112 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// Simple error handling in SDL
#include "SDL_error_c.h"
bool SDL_SetError(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
bool result;
va_start(ap, fmt);
result = SDL_SetErrorV(fmt, ap);
va_end(ap);
return result;
}
bool SDL_SetErrorV(SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)
{
// Ignore call if invalid format pointer was passed
if (fmt) {
int result;
SDL_error *error = SDL_GetErrBuf(true);
va_list ap2;
error->error = SDL_ErrorCodeGeneric;
va_copy(ap2, ap);
result = SDL_vsnprintf(error->str, error->len, fmt, ap2);
va_end(ap2);
if (result >= 0 && (size_t)result >= error->len && error->realloc_func) {
size_t len = (size_t)result + 1;
char *str = (char *)error->realloc_func(error->str, len);
if (str) {
error->str = str;
error->len = len;
va_copy(ap2, ap);
(void)SDL_vsnprintf(error->str, error->len, fmt, ap2);
va_end(ap2);
}
}
// Enable this if you want to see all errors printed as they occur.
// Note that there are many recoverable errors that may happen internally and
// can be safely ignored if the public API doesn't return an error code.
#if 0
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s", error->str);
#endif
}
return false;
}
const char *SDL_GetError(void)
{
const SDL_error *error = SDL_GetErrBuf(false);
if (!error) {
return "";
}
switch (error->error) {
case SDL_ErrorCodeGeneric:
return error->str;
case SDL_ErrorCodeOutOfMemory:
return "Out of memory";
default:
return "";
}
}
bool SDL_ClearError(void)
{
SDL_error *error = SDL_GetErrBuf(false);
if (error) {
error->error = SDL_ErrorCodeNone;
}
return true;
}
bool SDL_OutOfMemory(void)
{
SDL_error *error = SDL_GetErrBuf(true);
if (error) {
error->error = SDL_ErrorCodeOutOfMemory;
}
return false;
}

61
vendor/sdl-3.2.10/src/SDL_error_c.h vendored Normal file
View file

@ -0,0 +1,61 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/* This file defines a structure that carries language-independent
error messages
*/
#ifndef SDL_error_c_h_
#define SDL_error_c_h_
typedef enum
{
SDL_ErrorCodeNone,
SDL_ErrorCodeGeneric,
SDL_ErrorCodeOutOfMemory,
} SDL_ErrorCode;
typedef struct SDL_error
{
SDL_ErrorCode error;
char *str;
size_t len;
SDL_realloc_func realloc_func;
SDL_free_func free_func;
} SDL_error;
// Defined in SDL_thread.c
extern SDL_error *SDL_GetErrBuf(bool create);
// Macros to save and restore error values
#define SDL_PushError() \
char *saved_error = SDL_strdup(SDL_GetError())
#define SDL_PopError() \
do { \
if (saved_error) { \
SDL_SetError("%s", saved_error); \
SDL_free(saved_error); \
} \
} while (0)
#endif // SDL_error_c_h_

88
vendor/sdl-3.2.10/src/SDL_guid.c vendored Normal file
View file

@ -0,0 +1,88 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// convert the guid to a printable string
void SDL_GUIDToString(SDL_GUID guid, char *pszGUID, int cbGUID)
{
static const char k_rgchHexToASCII[] = "0123456789abcdef";
int i;
if ((!pszGUID) || (cbGUID <= 0)) {
return;
}
for (i = 0; i < sizeof(guid.data) && i < (cbGUID - 1) / 2; i++) {
// each input byte writes 2 ascii chars, and might write a null byte.
// If we don't have room for next input byte, stop
unsigned char c = guid.data[i];
*pszGUID++ = k_rgchHexToASCII[c >> 4];
*pszGUID++ = k_rgchHexToASCII[c & 0x0F];
}
*pszGUID = '\0';
}
/*-----------------------------------------------------------------------------
* Purpose: Returns the 4 bit nibble for a hex character
* Input : c -
* Output : unsigned char
*-----------------------------------------------------------------------------*/
static unsigned char nibble(unsigned char c)
{
if ((c >= '0') && (c <= '9')) {
return c - '0';
}
if ((c >= 'A') && (c <= 'F')) {
return c - 'A' + 0x0a;
}
if ((c >= 'a') && (c <= 'f')) {
return c - 'a' + 0x0a;
}
// received an invalid character, and no real way to return an error
// AssertMsg1(false, "Q_nibble invalid hex character '%c' ", c);
return 0;
}
// convert the string version of a guid to the struct
SDL_GUID SDL_StringToGUID(const char *pchGUID)
{
SDL_GUID guid;
int maxoutputbytes = sizeof(guid);
size_t len = SDL_strlen(pchGUID);
Uint8 *p;
size_t i;
// Make sure it's even
len = (len) & ~0x1;
SDL_memset(&guid, 0x00, sizeof(guid));
p = (Uint8 *)&guid;
for (i = 0; (i < len) && ((p - (Uint8 *)&guid) < maxoutputbytes); i += 2, p++) {
*p = (nibble((unsigned char)pchGUID[i]) << 4) | nibble((unsigned char)pchGUID[i + 1]);
}
return guid;
}

543
vendor/sdl-3.2.10/src/SDL_hashtable.c vendored Normal file
View file

@ -0,0 +1,543 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
typedef struct SDL_HashItem
{
// TODO: Splitting off values into a separate array might be more cache-friendly
const void *key;
const void *value;
Uint32 hash;
Uint32 probe_len : 31;
Uint32 live : 1;
} SDL_HashItem;
// Must be a power of 2 >= sizeof(SDL_HashItem)
#define MAX_HASHITEM_SIZEOF 32u
SDL_COMPILE_TIME_ASSERT(sizeof_SDL_HashItem, sizeof(SDL_HashItem) <= MAX_HASHITEM_SIZEOF);
// Anything larger than this will cause integer overflows
#define MAX_HASHTABLE_SIZE (0x80000000u / (MAX_HASHITEM_SIZEOF))
struct SDL_HashTable
{
SDL_RWLock *lock; // NULL if not created threadsafe
SDL_HashItem *table;
SDL_HashCallback hash;
SDL_HashKeyMatchCallback keymatch;
SDL_HashDestroyCallback destroy;
void *userdata;
Uint32 hash_mask;
Uint32 max_probe_len;
Uint32 num_occupied_slots;
};
static Uint32 CalculateHashBucketsFromEstimate(int estimated_capacity)
{
if (estimated_capacity <= 0) {
return 4; // start small, grow as necessary.
}
const Uint32 estimated32 = (Uint32) estimated_capacity;
Uint32 buckets = ((Uint32) 1) << SDL_MostSignificantBitIndex32(estimated32);
if (!SDL_HasExactlyOneBitSet32(estimated32)) {
buckets <<= 1; // need next power of two up to fit overflow capacity bits.
}
return SDL_min(buckets, MAX_HASHTABLE_SIZE);
}
SDL_HashTable *SDL_CreateHashTable(int estimated_capacity, bool threadsafe, SDL_HashCallback hash,
SDL_HashKeyMatchCallback keymatch,
SDL_HashDestroyCallback destroy, void *userdata)
{
const Uint32 num_buckets = CalculateHashBucketsFromEstimate(estimated_capacity);
SDL_HashTable *table = (SDL_HashTable *)SDL_calloc(1, sizeof(SDL_HashTable));
if (!table) {
return NULL;
}
if (threadsafe) {
table->lock = SDL_CreateRWLock();
if (!table->lock) {
SDL_DestroyHashTable(table);
return NULL;
}
}
table->table = (SDL_HashItem *)SDL_calloc(num_buckets, sizeof(SDL_HashItem));
if (!table->table) {
SDL_DestroyHashTable(table);
return NULL;
}
table->hash_mask = num_buckets - 1;
table->userdata = userdata;
table->hash = hash;
table->keymatch = keymatch;
table->destroy = destroy;
return table;
}
static SDL_INLINE Uint32 calc_hash(const SDL_HashTable *table, const void *key)
{
const Uint32 BitMixer = 0x9E3779B1u;
return table->hash(table->userdata, key) * BitMixer;
}
static SDL_INLINE Uint32 get_probe_length(Uint32 zero_idx, Uint32 actual_idx, Uint32 num_buckets)
{
// returns the probe sequence length from zero_idx to actual_idx
if (actual_idx < zero_idx) {
return num_buckets - zero_idx + actual_idx;
}
return actual_idx - zero_idx;
}
static SDL_HashItem *find_item(const SDL_HashTable *ht, const void *key, Uint32 hash, Uint32 *i, Uint32 *probe_len)
{
Uint32 hash_mask = ht->hash_mask;
Uint32 max_probe_len = ht->max_probe_len;
SDL_HashItem *table = ht->table;
while (true) {
SDL_HashItem *item = table + *i;
Uint32 item_hash = item->hash;
if (!item->live) {
return NULL;
}
if (item_hash == hash && ht->keymatch(ht->userdata, item->key, key)) {
return item;
}
Uint32 item_probe_len = item->probe_len;
SDL_assert(item_probe_len == get_probe_length(item_hash & hash_mask, (Uint32)(item - table), hash_mask + 1));
if (*probe_len > item_probe_len) {
return NULL;
}
if (++*probe_len > max_probe_len) {
return NULL;
}
*i = (*i + 1) & hash_mask;
}
}
static SDL_HashItem *find_first_item(const SDL_HashTable *ht, const void *key, Uint32 hash)
{
Uint32 i = hash & ht->hash_mask;
Uint32 probe_len = 0;
return find_item(ht, key, hash, &i, &probe_len);
}
static SDL_HashItem *insert_item(SDL_HashItem *item_to_insert, SDL_HashItem *table, Uint32 hash_mask, Uint32 *max_probe_len_ptr)
{
const Uint32 num_buckets = hash_mask + 1;
Uint32 idx = item_to_insert->hash & hash_mask;
SDL_HashItem *target = NULL;
SDL_HashItem temp_item;
while (true) {
SDL_HashItem *candidate = table + idx;
if (!candidate->live) {
// Found an empty slot. Put it here and we're done.
*candidate = *item_to_insert;
if (target == NULL) {
target = candidate;
}
const Uint32 probe_len = get_probe_length(candidate->hash & hash_mask, idx, num_buckets);
candidate->probe_len = probe_len;
if (*max_probe_len_ptr < probe_len) {
*max_probe_len_ptr = probe_len;
}
break;
}
const Uint32 candidate_probe_len = candidate->probe_len;
SDL_assert(candidate_probe_len == get_probe_length(candidate->hash & hash_mask, idx, num_buckets));
const Uint32 new_probe_len = get_probe_length(item_to_insert->hash & hash_mask, idx, num_buckets);
if (candidate_probe_len < new_probe_len) {
// Robin Hood hashing: the item at idx has a better probe length than our item would at this position.
// Evict it and put our item in its place, then continue looking for a new spot for the displaced item.
// This algorithm significantly reduces clustering in the table, making lookups take very few probes.
temp_item = *candidate;
*candidate = *item_to_insert;
if (target == NULL) {
target = candidate;
}
*item_to_insert = temp_item;
SDL_assert(new_probe_len == get_probe_length(candidate->hash & hash_mask, idx, num_buckets));
candidate->probe_len = new_probe_len;
if (*max_probe_len_ptr < new_probe_len) {
*max_probe_len_ptr = new_probe_len;
}
}
idx = (idx + 1) & hash_mask;
}
return target;
}
static void delete_item(SDL_HashTable *ht, SDL_HashItem *item)
{
const Uint32 hash_mask = ht->hash_mask;
SDL_HashItem *table = ht->table;
if (ht->destroy) {
ht->destroy(ht->userdata, item->key, item->value);
}
SDL_assert(ht->num_occupied_slots > 0);
ht->num_occupied_slots--;
Uint32 idx = (Uint32)(item - ht->table);
while (true) {
idx = (idx + 1) & hash_mask;
SDL_HashItem *next_item = table + idx;
if (next_item->probe_len < 1) {
SDL_zerop(item);
return;
}
*item = *next_item;
item->probe_len -= 1;
SDL_assert(item->probe_len < ht->max_probe_len);
item = next_item;
}
}
static bool resize(SDL_HashTable *ht, Uint32 new_size)
{
const Uint32 new_hash_mask = new_size - 1;
SDL_HashItem *new_table = SDL_calloc(new_size, sizeof(*new_table));
if (!new_table) {
return false;
}
SDL_HashItem *old_table = ht->table;
const Uint32 old_size = ht->hash_mask + 1;
ht->max_probe_len = 0;
ht->hash_mask = new_hash_mask;
ht->table = new_table;
for (Uint32 i = 0; i < old_size; ++i) {
SDL_HashItem *item = old_table + i;
if (item->live) {
insert_item(item, new_table, new_hash_mask, &ht->max_probe_len);
}
}
SDL_free(old_table);
return true;
}
static bool maybe_resize(SDL_HashTable *ht)
{
const Uint32 capacity = ht->hash_mask + 1;
if (capacity >= MAX_HASHTABLE_SIZE) {
return false;
}
const Uint32 max_load_factor = 217; // range: 0-255; 217 is roughly 85%
const Uint32 resize_threshold = (Uint32)((max_load_factor * (Uint64)capacity) >> 8);
if (ht->num_occupied_slots > resize_threshold) {
return resize(ht, capacity * 2);
}
return true;
}
bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value, bool replace)
{
if (!table) {
return SDL_InvalidParamError("table");
}
bool result = false;
SDL_LockRWLockForWriting(table->lock);
const Uint32 hash = calc_hash(table, key);
SDL_HashItem *item = find_first_item(table, key, hash);
bool do_insert = true;
if (item) {
if (replace) {
delete_item(table, item);
} else {
SDL_SetError("key already exists and replace is disabled");
do_insert = false;
}
}
if (do_insert) {
SDL_HashItem new_item;
new_item.key = key;
new_item.value = value;
new_item.hash = hash;
new_item.live = true;
new_item.probe_len = 0;
table->num_occupied_slots++;
if (!maybe_resize(table)) {
table->num_occupied_slots--;
} else {
// This never returns NULL
insert_item(&new_item, table->table, table->hash_mask, &table->max_probe_len);
result = true;
}
}
SDL_UnlockRWLock(table->lock);
return result;
}
bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **value)
{
if (!table) {
if (value) {
*value = NULL;
}
return SDL_InvalidParamError("table");
}
SDL_LockRWLockForReading(table->lock);
bool result = false;
const Uint32 hash = calc_hash(table, key);
SDL_HashItem *i = find_first_item(table, key, hash);
if (i) {
if (value) {
*value = i->value;
}
result = true;
}
SDL_UnlockRWLock(table->lock);
return result;
}
bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key)
{
if (!table) {
return SDL_InvalidParamError("table");
}
SDL_LockRWLockForWriting(table->lock);
bool result = false;
const Uint32 hash = calc_hash(table, key);
SDL_HashItem *item = find_first_item(table, key, hash);
if (item) {
delete_item(table, item);
result = true;
}
SDL_UnlockRWLock(table->lock);
return result;
}
bool SDL_IterateHashTable(const SDL_HashTable *table, SDL_HashTableIterateCallback callback, void *userdata)
{
if (!table) {
return SDL_InvalidParamError("table");
} else if (!callback) {
return SDL_InvalidParamError("callback");
}
SDL_LockRWLockForReading(table->lock);
SDL_HashItem *end = table->table + (table->hash_mask + 1);
Uint32 num_iterated = 0;
for (SDL_HashItem *item = table->table; item < end; item++) {
if (item->live) {
if (!callback(userdata, table, item->key, item->value)) {
break; // callback requested iteration stop.
} else if (++num_iterated >= table->num_occupied_slots) {
break; // we can drop out early because we've seen all the live items.
}
}
}
SDL_UnlockRWLock(table->lock);
return true;
}
bool SDL_HashTableEmpty(SDL_HashTable *table)
{
if (!table) {
return SDL_InvalidParamError("table");
}
SDL_LockRWLockForReading(table->lock);
const bool retval = (table->num_occupied_slots == 0);
SDL_UnlockRWLock(table->lock);
return retval;
}
static void destroy_all(SDL_HashTable *table)
{
SDL_HashDestroyCallback destroy = table->destroy;
if (destroy) {
void *userdata = table->userdata;
SDL_HashItem *end = table->table + (table->hash_mask + 1);
for (SDL_HashItem *i = table->table; i < end; ++i) {
if (i->live) {
i->live = false;
destroy(userdata, i->key, i->value);
}
}
}
}
void SDL_ClearHashTable(SDL_HashTable *table)
{
if (table) {
SDL_LockRWLockForWriting(table->lock);
{
destroy_all(table);
SDL_memset(table->table, 0, sizeof(*table->table) * (table->hash_mask + 1));
table->num_occupied_slots = 0;
}
SDL_UnlockRWLock(table->lock);
}
}
void SDL_DestroyHashTable(SDL_HashTable *table)
{
if (table) {
destroy_all(table);
if (table->lock) {
SDL_DestroyRWLock(table->lock);
}
SDL_free(table->table);
SDL_free(table);
}
}
// this is djb's xor hashing function.
static SDL_INLINE Uint32 hash_string_djbxor(const char *str, size_t len)
{
Uint32 hash = 5381;
while (len--) {
hash = ((hash << 5) + hash) ^ *(str++);
}
return hash;
}
Uint32 SDL_HashPointer(void *unused, const void *key)
{
(void)unused;
return SDL_murmur3_32(&key, sizeof(key), 0);
}
bool SDL_KeyMatchPointer(void *unused, const void *a, const void *b)
{
(void)unused;
return (a == b);
}
Uint32 SDL_HashString(void *unused, const void *key)
{
(void)unused;
const char *str = (const char *)key;
return hash_string_djbxor(str, SDL_strlen(str));
}
bool SDL_KeyMatchString(void *unused, const void *a, const void *b)
{
const char *a_string = (const char *)a;
const char *b_string = (const char *)b;
(void)unused;
if (a == b) {
return true; // same pointer, must match.
} else if (!a || !b) {
return false; // one pointer is NULL (and first test shows they aren't the same pointer), must not match.
} else if (a_string[0] != b_string[0]) {
return false; // we know they don't match
}
return (SDL_strcmp(a_string, b_string) == 0); // Check against actual string contents.
}
// We assume we can fit the ID in the key directly
SDL_COMPILE_TIME_ASSERT(SDL_HashID_KeySize, sizeof(Uint32) <= sizeof(const void *));
Uint32 SDL_HashID(void *unused, const void *key)
{
(void)unused;
return (Uint32)(uintptr_t)key;
}
bool SDL_KeyMatchID(void *unused, const void *a, const void *b)
{
(void)unused;
return (a == b);
}
void SDL_DestroyHashKeyAndValue(void *unused, const void *key, const void *value)
{
(void)unused;
SDL_free((void *)key);
SDL_free((void *)value);
}
void SDL_DestroyHashKey(void *unused, const void *key, const void *value)
{
(void)value;
(void)unused;
SDL_free((void *)key);
}
void SDL_DestroyHashValue(void *unused, const void *key, const void *value)
{
(void)key;
(void)unused;
SDL_free((void *)value);
}

633
vendor/sdl-3.2.10/src/SDL_hashtable.h vendored Normal file
View file

@ -0,0 +1,633 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* this is over-documented because it was almost a public API. Leaving the
full docs here in case it _does_ become public some day. */
/* WIKI CATEGORY: HashTable */
/**
* # CategoryHashTable
*
* SDL offers a hash table implementation, as a convenience for C code that
* needs efficient organization and access of arbitrary data.
*
* Hash tables are a popular data structure, designed to make it quick to
* store and look up arbitrary data. Data is stored with an associated "key."
* While one would look up an element of an array with an index, a hash table
* uses a unique key to find an element later.
*
* A key can be anything, as long as its unique and in a format that the table
* understands. For example, it's popular to use strings as keys: the key
* might be a username, and it is used to lookup account information for that
* user, etc.
*
* Hash tables are named because they "hash" their keys down into simple
* integers that can be used to efficiently organize and access the associated
* data.
*
* As this is a C API, there is one generic interface that is intended to work
* with different data types. This can be a little awkward to set up, but is
* easy to use after that.
*
* Hashtables are generated by a call to SDL_CreateHashTable(). This function
* requires several callbacks to be provided (for hashing keys, comparing
* entries, and cleaning up entries when removed). These are necessary to
* allow the hash to manage any arbitrary data type.
*
* Once a hash table is created, the common tasks are inserting data into the
* table, (SDL_InsertIntoHashTable), looking up previously inserted data
* (SDL_FindInHashTable), and removing data (SDL_RemoveFromHashTable and
* SDL_ClearHashTable). Less common but still useful is the ability to
* iterate through all the items in the table (SDL_IterateHashTable).
*
* The underlying hash table implementation is always subject to change, but
* at the time of writing, it uses open addressing and Robin Hood hashing.
* The technical details are explained [here](https://github.com/libsdl-org/SDL/pull/10897).
*
* Hashtables keep an SDL_RWLock internally, so multiple threads can perform
* hash lookups in parallel, while changes to the table will safely serialize
* access between threads.
*
* SDL provides a layer on top of this hash table implementation that might be
* more pleasant to use. SDL_PropertiesID maps a string to arbitrary data of
* various types in the same table, which could be both easier to use and more
* flexible. Refer to [CategoryProperties](CategoryProperties) for details.
*/
#ifndef SDL_hashtable_h_
#define SDL_hashtable_h_
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* The opaque type that represents a hash table.
*
* This is hidden behind an opaque pointer because not only does the table
* need to store arbitrary data types, but the hash table implementation may
* change in the future.
*
* \since This struct is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
typedef struct SDL_HashTable SDL_HashTable;
/**
* A function pointer representing a hash table hashing callback.
*
* This is called by SDL_HashTable when it needs to look up a key in
* its dataset. It generates a hash value from that key, and then uses that
* value as a basis for an index into an internal array.
*
* There are no rules on what hashing algorithm is used, so long as it
* can produce a reliable 32-bit value from `key`, and ideally distributes
* those values well across the 32-bit value space. The quality of a
* hashing algorithm is directly related to how well a hash table performs.
*
* Hashing can be a complicated subject, and often times what works best
* for one dataset will be suboptimal for another. There is a good discussion
* of the field [on Wikipedia](https://en.wikipedia.org/wiki/Hash_function).
*
* Also: do you _need_ to write a hashing function? SDL provides generic
* functions for strings (SDL_HashString), generic integer IDs (SDL_HashID),
* and generic pointers (SDL_HashPointer). Often you should use one of these
* before writing your own.
*
* \param userdata what was passed as `userdata` to SDL_CreateHashTable().
* \param key the key to be hashed.
* \returns a 32-bit value that represents a hash of `key`.
*
* \threadsafety This function must be thread safe if the hash table is used
* from multiple threads at the same time.
*
* \since This datatype is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
* \sa SDL_HashString
* \sa SDL_HashID
* \sa SDL_HashPointer
*/
typedef Uint32 (SDLCALL *SDL_HashCallback)(void *userdata, const void *key);
/**
* A function pointer representing a hash table matching callback.
*
* This is called by SDL_HashTable when it needs to look up a key in its
* dataset. After hashing the key, it looks for items stored in relation to
* that hash value. Since there can be more than one item found through the
* same hash value, this function verifies a specific value is actually
* correct before choosing it.
*
* So this function needs to compare the keys at `a` and `b` and decide if
* they are actually the same.
*
* For example, if the keys are C strings, this function might just be:
*
* ```c
* return (SDL_strcmp((const char *) a, const char *b) == 0);`
* ```
*
* Also: do you _need_ to write a matching function? SDL provides generic
* functions for strings (SDL_KeyMatchString), generic integer IDs
* (SDL_KeyMatchID), and generic pointers (SDL_KeyMatchPointer). Often you
* should use one of these before writing your own.
*
* \param userdata what was passed as `userdata` to SDL_CreateHashTable().
* \param a the first key to be compared.
* \param b the second key to be compared.
* \returns true if two keys are identical, false otherwise.
*
* \threadsafety This function must be thread safe if the hash table is used
* from multiple threads at the same time.
*
* \since This datatype is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
typedef bool (SDLCALL *SDL_HashKeyMatchCallback)(void *userdata, const void *a, const void *b);
/**
* A function pointer representing a hash table cleanup callback.
*
* This is called by SDL_HashTable when removing items from the hash, or
* destroying the hash table. It is used to optionally deallocate the
* key/value pairs.
*
* This is not required to do anything, if all the data in the table is
* static or POD data, but it can also do more than a simple free: for
* example, if the hash table is storing open files, it can close them here.
* It can also free only the key or only the value; it depends on what the
* hash table contains.
*
* \param userdata what was passed as `userdata` to SDL_CreateHashTable().
* \param key the key to deallocate.
* \param value the value to deallocate.
*
* \threadsafety This function must be thread safe if the hash table is used
* from multiple threads at the same time.
*
* \since This datatype is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
typedef void (SDLCALL *SDL_HashDestroyCallback)(void *userdata, const void *key, const void *value);
/**
* A function pointer representing a hash table iterator callback.
*
* This function is called once for each key/value pair to be considered
* when iterating a hash table.
*
* Iteration continues as long as there are more items to examine and this
* callback continues to return true.
*
* Do not attempt to modify the hash table during this callback, as it will
* cause incorrect behavior and possibly crashes.
*
* \param userdata what was passed as `userdata` to an iterator function.
* \param table the hash table being iterated.
* \param key the current key being iterated.
* \param value the current value being iterated.
* \returns true to keep iterating, false to stop iteration.
*
* \threadsafety A read lock is held during iteration, so other threads can
* still access the the hash table, but threads attempting to
* make changes will be blocked until iteration completes. If
* this is a concern, do as little in the callback as possible
* and finish iteration quickly.
*
* \since This datatype is available since SDL 3.4.0.
*
* \sa SDL_IterateHashTable
*/
typedef bool (SDLCALL *SDL_HashTableIterateCallback)(void *userdata, const SDL_HashTable *table, const void *key, const void *value);
/**
* Create a new hash table.
*
* To deal with different datatypes and needs of the caller, hash tables
* require several callbacks that deal with some specifics: how to hash a key,
* how to compare a key for equality, and how to clean up keys and values.
* SDL provides a few generic functions that can be used for these callbacks:
*
* - SDL_HashString and SDL_KeyMatchString for C strings.
* - SDL_HashPointer and SDL_KeyMatchPointer for generic pointers.
* - SDL_HashID and SDL_KeyMatchID for generic (possibly small) integers.
*
* Oftentimes, these are all you need for any hash table, but depending on
* your dataset, custom implementations might make more sense.
*
* You can specify an estimate of the number of items expected to be stored
* in the table, which can help make the table run more efficiently. The table
* will preallocate resources to accomodate this number of items, which is
* most useful if you intend to fill the table with a lot of data right after
* creating it. Otherwise, it might make more sense to specify the _minimum_
* you expect the table to hold and let it grow as necessary from there. This
* number is only a hint, and the table will be able to handle any amount of
* data--as long as the system doesn't run out of resources--so a perfect
* answer is not required. A value of 0 signifies no guess at all, and the
* table will start small and reallocate as necessary; often this is the
* correct thing to do.
*
* !!! FIXME: add note about `threadsafe` here. And update `threadsafety` tags.
* !!! FIXME: note that `threadsafe` tables can't be recursively locked, so
* !!! FIXME: you can't use `destroy` callbacks that might end up relocking.
*
* Note that SDL provides a higher-level option built on its hash tables:
* SDL_PropertiesID lets you map strings to various datatypes, and this
* might be easier to use. It only allows strings for keys, however. Those are
* created with SDL_CreateProperties().
*
* The returned hash table should be destroyed with SDL_DestroyHashTable()
* when no longer needed.
*
* \param estimated_capacity the approximate maximum number of items to be held
* in the hash table, or 0 for no estimate.
* \param threadsafe true to create an internal rwlock for this table.
* \param hash the function to use to hash keys.
* \param keymatch the function to use to compare keys.
* \param destroy the function to use to clean up keys and values, may be NULL.
* \param userdata a pointer that is passed to the callbacks.
* \returns a newly-created hash table, or NULL if there was an error; 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.4.0.
*
* \sa SDL_DestroyHashTable
*/
extern SDL_HashTable * SDL_CreateHashTable(int estimated_capacity,
bool threadsafe,
SDL_HashCallback hash,
SDL_HashKeyMatchCallback keymatch,
SDL_HashDestroyCallback destroy,
void *userdata);
/**
* Destroy a hash table.
*
* This will call the hash table's SDL_HashDestroyCallback for each item in
* the table, removing all inserted items, before deallocating the table
* itself.
*
* The table becomes invalid once this function is called, and no other thread
* should be accessing this table once this function has started.
*
* \param table the hash table to destroy.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*/
extern void SDL_DestroyHashTable(SDL_HashTable *table);
/**
* Add an item to a hash table.
*
* All keys in the table must be unique. If attempting to insert a key that
* already exists in the hash table, what will be done depends on the
* `replace` value:
*
* - If `replace` is false, this function will return false without modifying
* the table.
* - If `replace` is true, SDL will remove the previous item first, so the new
* value is the only one associated with that key. This will call the hash
* table's SDL_HashDestroyCallback for the previous item.
*
* \param table the hash table to insert into.
* \param key the key of the new item to insert.
* \param value the value of the new item to insert.
* \param replace true if a duplicate key should replace the previous value.
* \returns true if the new item was inserted, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*/
extern bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value, bool replace);
/**
* Look up an item in a hash table.
*
* On return, the value associated with `key` is stored to `*value`.
* If the key does not exist in the table, `*value` will be set to NULL.
*
* It is legal for `value` to be NULL, to not retrieve the key's value. In
* this case, the return value is still useful for reporting if the key exists
* in the table at all.
*
* \param table the hash table to search.
* \param key the key to search for in the table.
* \param value the found value will be stored here. Can be NULL.
* \returns true if key exists in the table, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_InsertIntoHashTable
*/
extern bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **value);
/**
* Remove an item from a hash table.
*
* If there is an item that matches `key`, it is removed from the table. This
* will call the hash table's SDL_HashDestroyCallback for the item to be
* removed.
*
* \param table the hash table to remove from.
* \param key the key of the item to remove from the table.
* \returns true if a key was removed, false if the key was not found.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*/
extern bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key);
/**
* Remove all items in a hash table.
*
* This will call the hash table's SDL_HashDestroyCallback for each item in
* the table, removing all inserted items.
*
* When this function returns, the hash table will be empty.
*
* \param table the hash table to clear.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*/
extern void SDL_ClearHashTable(SDL_HashTable *table);
/**
* Check if any items are currently stored in a hash table.
*
* If there are no items stored (the table is completely empty), this will
* return true.
*
* \param table the hash table to check.
* \returns true if the table is completely empty, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_ClearHashTable
*/
extern bool SDL_HashTableEmpty(SDL_HashTable *table);
/**
* Iterate all key/value pairs in a hash table.
*
* This function will call `callback` once for each key/value pair in the
* table, until either all pairs have been presented to the callback, or the
* callback has returned false to signal it is done.
*
* There is no guarantee what order results will be returned in.
*
* \param table the hash table to iterate.
* \param callback the function pointer to call for each value.
* \param userdata a pointer that is passed to `callback`.
* \returns true if iteration happened, false if not (bogus parameter, etc).
*
* \since This function is available since SDL 3.4.0.
*/
extern bool SDL_IterateHashTable(const SDL_HashTable *table, SDL_HashTableIterateCallback callback, void *userdata);
/* Helper functions for SDL_CreateHashTable callbacks... */
/**
* Generate a hash from a generic pointer.
*
* The key is intended to be a unique pointer to any datatype.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of keys to be used with the hash table.
*
* Note that the implementation may change in the future; do not expect
* the results to be stable vs future SDL releases. Use this in a hash table
* in the current process and don't store them to disk for the future.
*
* \param unused this parameter is ignored.
* \param key the key to hash as a generic pointer.
* \returns a 32-bit hash of the key.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern Uint32 SDL_HashPointer(void *unused, const void *key);
/**
* Compare two generic pointers as hash table keys.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of keys to be used with the hash table.
*
* \param unused this parameter is ignored.
* \param a the first generic pointer to compare.
* \param b the second generic pointer to compare.
* \returns true if the pointers are the same, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern bool SDL_KeyMatchPointer(void *unused, const void *a, const void *b);
/**
* Generate a hash from a C string.
*
* The key is intended to be a NULL-terminated string, in UTF-8 format.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of keys to be used with the hash table.
*
* Note that the implementation may change in the future; do not expect
* the results to be stable vs future SDL releases. Use this in a hash table
* in the current process and don't store them to disk for the future.
*
* \param unused this parameter is ignored.
* \param key the key to hash as a generic pointer.
* \returns a 32-bit hash of the key.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern Uint32 SDL_HashString(void *unused, const void *key);
/**
* Compare two C strings as hash table keys.
*
* Strings will be compared in a case-sensitive manner. More specifically,
* they'll be compared as NULL-terminated arrays of bytes.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of keys to be used with the hash table.
*
* \param unused this parameter is ignored.
* \param a the first string to compare.
* \param b the second string to compare.
* \returns true if the strings are the same, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern bool SDL_KeyMatchString(void *unused, const void *a, const void *b);
/**
* Generate a hash from an integer ID.
*
* The key is intended to a unique integer, possibly within a small range.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of keys to be used with the hash table.
*
* Note that the implementation may change in the future; do not expect
* the results to be stable vs future SDL releases. Use this in a hash table
* in the current process and don't store them to disk for the future.
*
* \param unused this parameter is ignored.
* \param key the key to hash as a generic pointer.
* \returns a 32-bit hash of the key.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern Uint32 SDL_HashID(void *unused, const void *key);
/**
* Compare two integer IDs as hash table keys.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of keys to be used with the hash table.
*
* \param unused this parameter is ignored.
* \param a the first ID to compare.
* \param b the second ID to compare.
* \returns true if the IDs are the same, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern bool SDL_KeyMatchID(void *unused, const void *a, const void *b);
/**
* Free both the key and value pointers of a hash table item.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of data to be used with the hash table.
*
* This literally calls `SDL_free(key);` and `SDL_free(value);`.
*
* \param unused this parameter is ignored.
* \param key the key to be destroyed.
* \param value the value to be destroyed.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern void SDL_DestroyHashKeyAndValue(void *unused, const void *key, const void *value);
/**
* Free just the value pointer of a hash table item.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of data to be used with the hash table.
*
* This literally calls `SDL_free(key);` and leaves `value` alone.
*
* \param unused this parameter is ignored.
* \param key the key to be destroyed.
* \param value the value to be destroyed.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern void SDL_DestroyHashKey(void *unused, const void *key, const void *value);
/**
* Free just the value pointer of a hash table item.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of data to be used with the hash table.
*
* This literally calls `SDL_free(value);` and leaves `key` alone.
*
* \param unused this parameter is ignored.
* \param key the key to be destroyed.
* \param value the value to be destroyed.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern void SDL_DestroyHashValue(void *unused, const void *key, const void *value);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_hashtable_h_ */

370
vendor/sdl-3.2.10/src/SDL_hints.c vendored Normal file
View file

@ -0,0 +1,370 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_hints_c.h"
typedef struct SDL_HintWatch
{
SDL_HintCallback callback;
void *userdata;
struct SDL_HintWatch *next;
} SDL_HintWatch;
typedef struct SDL_Hint
{
char *value;
SDL_HintPriority priority;
SDL_HintWatch *callbacks;
} SDL_Hint;
static SDL_AtomicU32 SDL_hint_props;
void SDL_InitHints(void)
{
}
void SDL_QuitHints(void)
{
SDL_PropertiesID props;
do {
props = SDL_GetAtomicU32(&SDL_hint_props);
} while (!SDL_CompareAndSwapAtomicU32(&SDL_hint_props, props, 0));
if (props) {
SDL_DestroyProperties(props);
}
}
static SDL_PropertiesID GetHintProperties(bool create)
{
SDL_PropertiesID props = SDL_GetAtomicU32(&SDL_hint_props);
if (!props && create) {
props = SDL_CreateProperties();
if (!SDL_CompareAndSwapAtomicU32(&SDL_hint_props, 0, props)) {
// Somebody else created hint properties before us, just use those
SDL_DestroyProperties(props);
props = SDL_GetAtomicU32(&SDL_hint_props);
}
}
return props;
}
static void SDLCALL CleanupHintProperty(void *userdata, void *value)
{
SDL_Hint *hint = (SDL_Hint *) value;
SDL_free(hint->value);
SDL_HintWatch *entry = hint->callbacks;
while (entry) {
SDL_HintWatch *freeable = entry;
entry = entry->next;
SDL_free(freeable);
}
SDL_free(hint);
}
static const char* GetHintEnvironmentVariable(const char *name)
{
const char *result = SDL_getenv(name);
if (!result && name && *name) {
// fall back to old (SDL2) names of environment variables that
// are important to users (e.g. many use SDL_VIDEODRIVER=wayland)
if (SDL_strcmp(name, SDL_HINT_VIDEO_DRIVER) == 0) {
result = SDL_getenv("SDL_VIDEODRIVER");
} else if (SDL_strcmp(name, SDL_HINT_AUDIO_DRIVER) == 0) {
result = SDL_getenv("SDL_AUDIODRIVER");
}
}
return result;
}
bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriority priority)
{
if (!name || !*name) {
return SDL_InvalidParamError("name");
}
const char *env = GetHintEnvironmentVariable(name);
if (env && (priority < SDL_HINT_OVERRIDE)) {
return SDL_SetError("An environment variable is taking priority");
}
const SDL_PropertiesID hints = GetHintProperties(true);
if (!hints) {
return false;
}
bool result = false;
SDL_LockProperties(hints);
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
if (hint) {
if (priority >= hint->priority) {
if (hint->value != value && (!value || !hint->value || SDL_strcmp(hint->value, value) != 0)) {
char *old_value = hint->value;
hint->value = value ? SDL_strdup(value) : NULL;
SDL_HintWatch *entry = hint->callbacks;
while (entry) {
// Save the next entry in case this one is deleted
SDL_HintWatch *next = entry->next;
entry->callback(entry->userdata, name, old_value, value);
entry = next;
}
SDL_free(old_value);
}
hint->priority = priority;
result = true;
}
} else { // Couldn't find the hint? Add a new one.
hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
if (hint) {
hint->value = value ? SDL_strdup(value) : NULL;
hint->priority = priority;
hint->callbacks = NULL;
result = SDL_SetPointerPropertyWithCleanup(hints, name, hint, CleanupHintProperty, NULL);
}
}
SDL_UnlockProperties(hints);
return result;
}
bool SDL_ResetHint(const char *name)
{
if (!name || !*name) {
return SDL_InvalidParamError("name");
}
const char *env = GetHintEnvironmentVariable(name);
const SDL_PropertiesID hints = GetHintProperties(false);
if (!hints) {
return false;
}
bool result = false;
SDL_LockProperties(hints);
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
if (hint) {
if ((!env && hint->value) || (env && !hint->value) || (env && SDL_strcmp(env, hint->value) != 0)) {
for (SDL_HintWatch *entry = hint->callbacks; entry;) {
// Save the next entry in case this one is deleted
SDL_HintWatch *next = entry->next;
entry->callback(entry->userdata, name, hint->value, env);
entry = next;
}
}
SDL_free(hint->value);
hint->value = NULL;
hint->priority = SDL_HINT_DEFAULT;
result = true;
}
SDL_UnlockProperties(hints);
return result;
}
static void SDLCALL ResetHintsCallback(void *userdata, SDL_PropertiesID hints, const char *name)
{
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
if (!hint) {
return; // uh...okay.
}
const char *env = GetHintEnvironmentVariable(name);
if ((!env && hint->value) || (env && !hint->value) || (env && SDL_strcmp(env, hint->value) != 0)) {
SDL_HintWatch *entry = hint->callbacks;
while (entry) {
// Save the next entry in case this one is deleted
SDL_HintWatch *next = entry->next;
entry->callback(entry->userdata, name, hint->value, env);
entry = next;
}
}
SDL_free(hint->value);
hint->value = NULL;
hint->priority = SDL_HINT_DEFAULT;
}
void SDL_ResetHints(void)
{
SDL_EnumerateProperties(GetHintProperties(false), ResetHintsCallback, NULL);
}
bool SDL_SetHint(const char *name, const char *value)
{
return SDL_SetHintWithPriority(name, value, SDL_HINT_NORMAL);
}
const char *SDL_GetHint(const char *name)
{
if (!name) {
return NULL;
}
const char *result = GetHintEnvironmentVariable(name);
const SDL_PropertiesID hints = GetHintProperties(false);
if (hints) {
SDL_LockProperties(hints);
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
if (hint) {
if (!result || hint->priority == SDL_HINT_OVERRIDE) {
result = SDL_GetPersistentString(hint->value);
}
}
SDL_UnlockProperties(hints);
}
return result;
}
int SDL_GetStringInteger(const char *value, int default_value)
{
if (!value || !*value) {
return default_value;
}
if (SDL_strcasecmp(value, "false") == 0) {
return 0;
}
if (SDL_strcasecmp(value, "true") == 0) {
return 1;
}
if (*value == '-' || SDL_isdigit(*value)) {
return SDL_atoi(value);
}
return default_value;
}
bool SDL_GetStringBoolean(const char *value, bool default_value)
{
if (!value || !*value) {
return default_value;
}
if (*value == '0' || SDL_strcasecmp(value, "false") == 0) {
return false;
}
return true;
}
bool SDL_GetHintBoolean(const char *name, bool default_value)
{
const char *hint = SDL_GetHint(name);
return SDL_GetStringBoolean(hint, default_value);
}
bool SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
{
if (!name || !*name) {
return SDL_InvalidParamError("name");
} else if (!callback) {
return SDL_InvalidParamError("callback");
}
const SDL_PropertiesID hints = GetHintProperties(true);
if (!hints) {
return false;
}
SDL_HintWatch *entry = (SDL_HintWatch *)SDL_malloc(sizeof(*entry));
if (!entry) {
return false;
}
entry->callback = callback;
entry->userdata = userdata;
bool result = false;
SDL_LockProperties(hints);
SDL_RemoveHintCallback(name, callback, userdata);
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
if (hint) {
result = true;
} else { // Need to add a hint entry for this watcher
hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
if (!hint) {
SDL_free(entry);
SDL_UnlockProperties(hints);
return false;
} else {
hint->value = NULL;
hint->priority = SDL_HINT_DEFAULT;
hint->callbacks = NULL;
result = SDL_SetPointerPropertyWithCleanup(hints, name, hint, CleanupHintProperty, NULL);
}
}
// Add it to the callbacks for this hint
entry->next = hint->callbacks;
hint->callbacks = entry;
// Now call it with the current value
const char *value = SDL_GetHint(name);
callback(userdata, name, value, value);
SDL_UnlockProperties(hints);
return result;
}
void SDL_RemoveHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
{
if (!name || !*name) {
return;
}
const SDL_PropertiesID hints = GetHintProperties(false);
if (!hints) {
return;
}
SDL_LockProperties(hints);
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
if (hint) {
SDL_HintWatch *prev = NULL;
for (SDL_HintWatch *entry = hint->callbacks; entry; entry = entry->next) {
if ((callback == entry->callback) && (userdata == entry->userdata)) {
if (prev) {
prev->next = entry->next;
} else {
hint->callbacks = entry->next;
}
SDL_free(entry);
break;
}
prev = entry;
}
}
SDL_UnlockProperties(hints);
}

33
vendor/sdl-3.2.10/src/SDL_hints_c.h vendored Normal file
View file

@ -0,0 +1,33 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// This file defines useful function for working with SDL hints
#ifndef SDL_hints_c_h_
#define SDL_hints_c_h_
extern void SDL_InitHints(void);
extern bool SDL_GetStringBoolean(const char *value, bool default_value);
extern int SDL_GetStringInteger(const char *value, int default_value);
extern void SDL_QuitHints(void);
#endif // SDL_hints_c_h_

283
vendor/sdl-3.2.10/src/SDL_internal.h vendored Normal file
View file

@ -0,0 +1,283 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_internal_h_
#define SDL_internal_h_
// Many of SDL's features require _GNU_SOURCE on various platforms
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
// Need this so Linux systems define fseek64o, ftell64o and off64_t
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE 1
#endif
/* This is for a variable-length array at the end of a struct:
struct x { int y; char z[SDL_VARIABLE_LENGTH_ARRAY]; };
Use this because GCC 2 needs different magic than other compilers. */
#if (defined(__GNUC__) && (__GNUC__ <= 2)) || defined(__CC_ARM) || defined(__cplusplus)
#define SDL_VARIABLE_LENGTH_ARRAY 1
#else
#define SDL_VARIABLE_LENGTH_ARRAY
#endif
#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) || defined(__clang__)
#define HAVE_GCC_DIAGNOSTIC_PRAGMA 1
#endif
#ifdef _MSC_VER // We use constant comparison for generated code
#pragma warning(disable : 6326)
#endif
#ifdef _MSC_VER // SDL_MAX_SMALL_ALLOC_STACKSIZE is smaller than _ALLOCA_S_THRESHOLD and should be generally safe
#pragma warning(disable : 6255)
#endif
#define SDL_MAX_SMALL_ALLOC_STACKSIZE 128
#define SDL_small_alloc(type, count, pisstack) ((*(pisstack) = ((sizeof(type) * (count)) < SDL_MAX_SMALL_ALLOC_STACKSIZE)), (*(pisstack) ? SDL_stack_alloc(type, count) : (type *)SDL_malloc(sizeof(type) * (count))))
#define SDL_small_free(ptr, isstack) \
if ((isstack)) { \
SDL_stack_free(ptr); \
} else { \
SDL_free(ptr); \
}
#include "SDL_build_config.h"
#include "dynapi/SDL_dynapi.h"
#if SDL_DYNAMIC_API
#include "dynapi/SDL_dynapi_overrides.h"
/* force SDL_DECLSPEC off...it's all internal symbols now.
These will have actual #defines during SDL_dynapi.c only */
#ifdef SDL_DECLSPEC
#undef SDL_DECLSPEC
#endif
#define SDL_DECLSPEC
#endif
#ifdef SDL_PLATFORM_APPLE
#ifndef _DARWIN_C_SOURCE
#define _DARWIN_C_SOURCE 1 // for memset_pattern4()
#endif
#include <Availability.h>
#ifndef __IPHONE_OS_VERSION_MAX_ALLOWED
#define __IPHONE_OS_VERSION_MAX_ALLOWED 0
#endif
#ifndef __APPLETV_OS_VERSION_MAX_ALLOWED
#define __APPLETV_OS_VERSION_MAX_ALLOWED 0
#endif
#ifndef __MAC_OS_X_VERSION_MAX_ALLOWED
#define __MAC_OS_X_VERSION_MAX_ALLOWED 0
#endif
#endif // SDL_PLATFORM_APPLE
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#elif defined(HAVE_MALLOC_H)
#include <malloc.h>
#endif
#ifdef HAVE_STDDEF_H
#include <stddef.h>
#endif
#ifdef HAVE_STDARG_H
#include <stdarg.h>
#endif
#ifdef HAVE_STRING_H
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <string.h>
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#elif defined(HAVE_STDINT_H)
#include <stdint.h>
#endif
#ifdef HAVE_MATH_H
#include <math.h>
#endif
#ifdef HAVE_FLOAT_H
#include <float.h>
#endif
// If you run into a warning that O_CLOEXEC is redefined, update the SDL configuration header for your platform to add HAVE_O_CLOEXEC
#ifndef HAVE_O_CLOEXEC
#define O_CLOEXEC 0
#endif
/* A few #defines to reduce SDL footprint.
Only effective when library is statically linked. */
/* Optimized functions from 'SDL_blit_0.c'
- blit with source bits_per_pixel < 8, palette */
#if !defined(SDL_HAVE_BLIT_0) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_BLIT_0 1
#endif
/* Optimized functions from 'SDL_blit_1.c'
- blit with source bytes_per_pixel == 1, palette */
#if !defined(SDL_HAVE_BLIT_1) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_BLIT_1 1
#endif
/* Optimized functions from 'SDL_blit_A.c'
- blit with 'SDL_BLENDMODE_BLEND' blending mode */
#if !defined(SDL_HAVE_BLIT_A) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_BLIT_A 1
#endif
/* Optimized functions from 'SDL_blit_N.c'
- blit with COLORKEY mode, or nothing */
#if !defined(SDL_HAVE_BLIT_N) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_BLIT_N 1
#endif
/* Optimized functions from 'SDL_blit_N.c'
- RGB565 conversion with Lookup tables */
#if !defined(SDL_HAVE_BLIT_N_RGB565) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_BLIT_N_RGB565 1
#endif
/* Optimized functions from 'SDL_blit_AUTO.c'
- blit with modulate color, modulate alpha, any blending mode
- scaling or not */
#if !defined(SDL_HAVE_BLIT_AUTO) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_BLIT_AUTO 1
#endif
/* Run-Length-Encoding
- SDL_SetSurfaceColorKey() called with SDL_RLEACCEL flag */
#if !defined(SDL_HAVE_RLE) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_RLE 1
#endif
/* Software SDL_Renderer
- creation of software renderer
- *not* general blitting functions
- {blend,draw}{fillrect,line,point} internal functions */
#if !defined(SDL_VIDEO_RENDER_SW) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_VIDEO_RENDER_SW 1
#endif
/* STB image conversion */
#if !defined(SDL_HAVE_STB) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_STB 1
#endif
/* YUV formats
- handling of YUV surfaces
- blitting and conversion functions */
#if !defined(SDL_HAVE_YUV) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_YUV 1
#endif
#ifdef SDL_CAMERA_DISABLED
#undef SDL_CAMERA_DRIVER_ANDROID
#undef SDL_CAMERA_DRIVER_COREMEDIA
#undef SDL_CAMERA_DRIVER_DUMMY
#undef SDL_CAMERA_DRIVER_EMSCRIPTEN
#undef SDL_CAMERA_DRIVER_MEDIAFOUNDATION
#undef SDL_CAMERA_DRIVER_PIPEWIRE
#undef SDL_CAMERA_DRIVER_V4L2
#undef SDL_CAMERA_DRIVER_VITA
#endif
#ifdef SDL_RENDER_DISABLED
#undef SDL_VIDEO_RENDER_SW
#undef SDL_VIDEO_RENDER_D3D
#undef SDL_VIDEO_RENDER_D3D11
#undef SDL_VIDEO_RENDER_D3D12
#undef SDL_VIDEO_RENDER_GPU
#undef SDL_VIDEO_RENDER_METAL
#undef SDL_VIDEO_RENDER_OGL
#undef SDL_VIDEO_RENDER_OGL_ES2
#undef SDL_VIDEO_RENDER_PS2
#undef SDL_VIDEO_RENDER_PSP
#undef SDL_VIDEO_RENDER_VITA_GXM
#undef SDL_VIDEO_RENDER_VULKAN
#endif // SDL_RENDER_DISABLED
#ifdef SDL_GPU_DISABLED
#undef SDL_GPU_D3D12
#undef SDL_GPU_METAL
#undef SDL_GPU_VULKAN
#undef SDL_VIDEO_RENDER_GPU
#endif // SDL_GPU_DISABLED
#if !defined(HAVE_LIBC)
// If not using _any_ C runtime, these have to be defined before SDL_thread.h
// gets included, so internal SDL_CreateThread calls will not try to reference
// the (unavailable and unneeded) _beginthreadex/_endthreadex functions.
#define SDL_BeginThreadFunction NULL
#define SDL_EndThreadFunction NULL
#endif
#ifdef SDL_NOLONGLONG
#error We cannot build a valid SDL3 library without long long support
#endif
/* Enable internal definitions in SDL API headers */
#define SDL_INTERNAL
#include <SDL3/SDL.h>
#include <SDL3/SDL_intrin.h>
#define SDL_MAIN_NOIMPL // don't drag in header-only implementation of SDL_main
#include <SDL3/SDL_main.h>
// Set up for C function definitions, even when using C++
#ifdef __cplusplus
extern "C" {
#endif
#include "SDL_utils_c.h"
#include "SDL_hashtable.h"
// Do any initialization that needs to happen before threads are started
extern void SDL_InitMainThread(void);
/* The internal implementations of these functions have up to nanosecond precision.
We can expose these functions as part of the API if we want to later.
*/
extern bool SDLCALL SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS);
extern bool SDLCALL SDL_WaitConditionTimeoutNS(SDL_Condition *cond, SDL_Mutex *mutex, Sint64 timeoutNS);
extern bool SDLCALL SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS);
// Ends C function definitions when using C++
#ifdef __cplusplus
}
#endif
#endif // SDL_internal_h_

86
vendor/sdl-3.2.10/src/SDL_list.c vendored Normal file
View file

@ -0,0 +1,86 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "./SDL_list.h"
// Push
bool SDL_ListAdd(SDL_ListNode **head, void *ent)
{
SDL_ListNode *node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
if (!node) {
return false;
}
node->entry = ent;
node->next = *head;
*head = node;
return true;
}
// Pop from end as a FIFO (if add with SDL_ListAdd)
void SDL_ListPop(SDL_ListNode **head, void **ent)
{
SDL_ListNode **ptr = head;
// Invalid or empty
if (!head || !*head) {
return;
}
while ((*ptr)->next) {
ptr = &(*ptr)->next;
}
if (ent) {
*ent = (*ptr)->entry;
}
SDL_free(*ptr);
*ptr = NULL;
}
void SDL_ListRemove(SDL_ListNode **head, void *ent)
{
SDL_ListNode **ptr = head;
while (*ptr) {
if ((*ptr)->entry == ent) {
SDL_ListNode *tmp = *ptr;
*ptr = (*ptr)->next;
SDL_free(tmp);
return;
}
ptr = &(*ptr)->next;
}
}
void SDL_ListClear(SDL_ListNode **head)
{
SDL_ListNode *l = *head;
*head = NULL;
while (l) {
SDL_ListNode *tmp = l;
l = l->next;
SDL_free(tmp);
}
}

36
vendor/sdl-3.2.10/src/SDL_list.h vendored Normal file
View file

@ -0,0 +1,36 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_list_h_
#define SDL_list_h_
typedef struct SDL_ListNode
{
void *entry;
struct SDL_ListNode *next;
} SDL_ListNode;
bool SDL_ListAdd(SDL_ListNode **head, void *ent);
void SDL_ListPop(SDL_ListNode **head, void **ent);
void SDL_ListRemove(SDL_ListNode **head, void *ent);
void SDL_ListClear(SDL_ListNode **head);
#endif // SDL_list_h_

805
vendor/sdl-3.2.10/src/SDL_log.c vendored Normal file
View file

@ -0,0 +1,805 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(SDL_PLATFORM_WINDOWS)
#include "core/windows/SDL_windows.h"
#endif
// Simple log messages in SDL
#include "SDL_log_c.h"
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef SDL_PLATFORM_ANDROID
#include <android/log.h>
#endif
#include "stdlib/SDL_vacopy.h"
// The size of the stack buffer to use for rendering log messages.
#define SDL_MAX_LOG_MESSAGE_STACK 256
#define DEFAULT_CATEGORY -1
typedef struct SDL_LogLevel
{
int category;
SDL_LogPriority priority;
struct SDL_LogLevel *next;
} SDL_LogLevel;
// The default log output function
static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority, const char *message);
static void CleanupLogPriorities(void);
static void CleanupLogPrefixes(void);
static SDL_InitState SDL_log_init;
static SDL_Mutex *SDL_log_lock;
static SDL_Mutex *SDL_log_function_lock;
static SDL_LogLevel *SDL_loglevels SDL_GUARDED_BY(SDL_log_lock);
static SDL_LogPriority SDL_log_priorities[SDL_LOG_CATEGORY_CUSTOM] SDL_GUARDED_BY(SDL_log_lock);
static SDL_LogPriority SDL_log_default_priority SDL_GUARDED_BY(SDL_log_lock);
static SDL_LogOutputFunction SDL_log_function SDL_GUARDED_BY(SDL_log_function_lock) = SDL_LogOutput;
static void *SDL_log_userdata SDL_GUARDED_BY(SDL_log_function_lock) = NULL;
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#endif
// If this list changes, update the documentation for SDL_HINT_LOGGING
static const char * const SDL_priority_names[] = {
NULL,
"TRACE",
"VERBOSE",
"DEBUG",
"INFO",
"WARN",
"ERROR",
"CRITICAL"
};
SDL_COMPILE_TIME_ASSERT(priority_names, SDL_arraysize(SDL_priority_names) == SDL_LOG_PRIORITY_COUNT);
// This is guarded by SDL_log_function_lock because it's the logging function that calls GetLogPriorityPrefix()
static char *SDL_priority_prefixes[SDL_LOG_PRIORITY_COUNT] SDL_GUARDED_BY(SDL_log_function_lock);
// If this list changes, update the documentation for SDL_HINT_LOGGING
static const char * const SDL_category_names[] = {
"APP",
"ERROR",
"ASSERT",
"SYSTEM",
"AUDIO",
"VIDEO",
"RENDER",
"INPUT",
"TEST",
"GPU"
};
SDL_COMPILE_TIME_ASSERT(category_names, SDL_arraysize(SDL_category_names) == SDL_LOG_CATEGORY_RESERVED2);
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
#pragma GCC diagnostic pop
#endif
#ifdef SDL_PLATFORM_ANDROID
static int SDL_android_priority[] = {
ANDROID_LOG_UNKNOWN,
ANDROID_LOG_VERBOSE,
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL
};
SDL_COMPILE_TIME_ASSERT(android_priority, SDL_arraysize(SDL_android_priority) == SDL_LOG_PRIORITY_COUNT);
#endif // SDL_PLATFORM_ANDROID
static void SDLCALL SDL_LoggingChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_ResetLogPriorities();
}
void SDL_InitLog(void)
{
if (!SDL_ShouldInit(&SDL_log_init)) {
return;
}
// If these fail we'll continue without them.
SDL_log_lock = SDL_CreateMutex();
SDL_log_function_lock = SDL_CreateMutex();
SDL_AddHintCallback(SDL_HINT_LOGGING, SDL_LoggingChanged, NULL);
SDL_SetInitialized(&SDL_log_init, true);
}
void SDL_QuitLog(void)
{
if (!SDL_ShouldQuit(&SDL_log_init)) {
return;
}
SDL_RemoveHintCallback(SDL_HINT_LOGGING, SDL_LoggingChanged, NULL);
CleanupLogPriorities();
CleanupLogPrefixes();
if (SDL_log_lock) {
SDL_DestroyMutex(SDL_log_lock);
SDL_log_lock = NULL;
}
if (SDL_log_function_lock) {
SDL_DestroyMutex(SDL_log_function_lock);
SDL_log_function_lock = NULL;
}
SDL_SetInitialized(&SDL_log_init, false);
}
static void SDL_CheckInitLog(void)
{
int status = SDL_GetAtomicInt(&SDL_log_init.status);
if (status == SDL_INIT_STATUS_INITIALIZED ||
(status == SDL_INIT_STATUS_INITIALIZING && SDL_log_init.thread == SDL_GetCurrentThreadID())) {
return;
}
SDL_InitLog();
}
static void CleanupLogPriorities(void)
{
while (SDL_loglevels) {
SDL_LogLevel *entry = SDL_loglevels;
SDL_loglevels = entry->next;
SDL_free(entry);
}
}
void SDL_SetLogPriorities(SDL_LogPriority priority)
{
SDL_CheckInitLog();
SDL_LockMutex(SDL_log_lock);
{
CleanupLogPriorities();
SDL_log_default_priority = priority;
for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
SDL_log_priorities[i] = priority;
}
}
SDL_UnlockMutex(SDL_log_lock);
}
void SDL_SetLogPriority(int category, SDL_LogPriority priority)
{
SDL_LogLevel *entry;
SDL_CheckInitLog();
SDL_LockMutex(SDL_log_lock);
{
if (category >= 0 && category < SDL_arraysize(SDL_log_priorities)) {
SDL_log_priorities[category] = priority;
} else {
for (entry = SDL_loglevels; entry; entry = entry->next) {
if (entry->category == category) {
entry->priority = priority;
break;
}
}
if (!entry) {
entry = (SDL_LogLevel *)SDL_malloc(sizeof(*entry));
if (entry) {
entry->category = category;
entry->priority = priority;
entry->next = SDL_loglevels;
SDL_loglevels = entry;
}
}
}
}
SDL_UnlockMutex(SDL_log_lock);
}
SDL_LogPriority SDL_GetLogPriority(int category)
{
SDL_LogLevel *entry;
SDL_LogPriority priority = SDL_LOG_PRIORITY_INVALID;
SDL_CheckInitLog();
// Bypass the lock for known categories
// Technically if the priority was set on a different CPU the value might not
// be visible on this CPU for a while, but in practice it's fast enough that
// this performance improvement is worthwhile.
if (category >= 0 && category < SDL_arraysize(SDL_log_priorities)) {
return SDL_log_priorities[category];
}
SDL_LockMutex(SDL_log_lock);
{
if (category >= 0 && category < SDL_arraysize(SDL_log_priorities)) {
priority = SDL_log_priorities[category];
} else {
for (entry = SDL_loglevels; entry; entry = entry->next) {
if (entry->category == category) {
priority = entry->priority;
break;
}
}
if (priority == SDL_LOG_PRIORITY_INVALID) {
priority = SDL_log_default_priority;
}
}
}
SDL_UnlockMutex(SDL_log_lock);
return priority;
}
static bool ParseLogCategory(const char *string, size_t length, int *category)
{
int i;
if (SDL_isdigit(*string)) {
*category = SDL_atoi(string);
return true;
}
if (*string == '*') {
*category = DEFAULT_CATEGORY;
return true;
}
for (i = 0; i < SDL_arraysize(SDL_category_names); ++i) {
if (SDL_strncasecmp(string, SDL_category_names[i], length) == 0) {
*category = i;
return true;
}
}
return false;
}
static bool ParseLogPriority(const char *string, size_t length, SDL_LogPriority *priority)
{
int i;
if (SDL_isdigit(*string)) {
i = SDL_atoi(string);
if (i == 0) {
// 0 has a special meaning of "disable this category"
*priority = SDL_LOG_PRIORITY_COUNT;
return true;
}
if (i > SDL_LOG_PRIORITY_INVALID && i < SDL_LOG_PRIORITY_COUNT) {
*priority = (SDL_LogPriority)i;
return true;
}
return false;
}
if (SDL_strncasecmp(string, "quiet", length) == 0) {
*priority = SDL_LOG_PRIORITY_COUNT;
return true;
}
for (i = SDL_LOG_PRIORITY_INVALID + 1; i < SDL_LOG_PRIORITY_COUNT; ++i) {
if (SDL_strncasecmp(string, SDL_priority_names[i], length) == 0) {
*priority = (SDL_LogPriority)i;
return true;
}
}
return false;
}
static void ParseLogPriorities(const char *hint)
{
const char *name, *next;
int category = DEFAULT_CATEGORY;
SDL_LogPriority priority = SDL_LOG_PRIORITY_INVALID;
if (SDL_strchr(hint, '=') == NULL) {
if (ParseLogPriority(hint, SDL_strlen(hint), &priority)) {
SDL_SetLogPriorities(priority);
}
return;
}
for (name = hint; name; name = next) {
const char *sep = SDL_strchr(name, '=');
if (!sep) {
break;
}
next = SDL_strchr(sep, ',');
if (next) {
++next;
}
if (ParseLogCategory(name, (sep - name), &category)) {
const char *value = sep + 1;
size_t len;
if (next) {
len = (next - value - 1);
} else {
len = SDL_strlen(value);
}
if (ParseLogPriority(value, len, &priority)) {
if (category == DEFAULT_CATEGORY) {
for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
if (SDL_log_priorities[i] == SDL_LOG_PRIORITY_INVALID) {
SDL_log_priorities[i] = priority;
}
}
SDL_log_default_priority = priority;
} else {
SDL_SetLogPriority(category, priority);
}
}
}
}
}
void SDL_ResetLogPriorities(void)
{
SDL_CheckInitLog();
SDL_LockMutex(SDL_log_lock);
{
CleanupLogPriorities();
SDL_log_default_priority = SDL_LOG_PRIORITY_INVALID;
for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
SDL_log_priorities[i] = SDL_LOG_PRIORITY_INVALID;
}
const char *hint = SDL_GetHint(SDL_HINT_LOGGING);
if (hint) {
ParseLogPriorities(hint);
}
if (SDL_log_default_priority == SDL_LOG_PRIORITY_INVALID) {
SDL_log_default_priority = SDL_LOG_PRIORITY_ERROR;
}
for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
if (SDL_log_priorities[i] != SDL_LOG_PRIORITY_INVALID) {
continue;
}
switch (i) {
case SDL_LOG_CATEGORY_APPLICATION:
SDL_log_priorities[i] = SDL_LOG_PRIORITY_INFO;
break;
case SDL_LOG_CATEGORY_ASSERT:
SDL_log_priorities[i] = SDL_LOG_PRIORITY_WARN;
break;
case SDL_LOG_CATEGORY_TEST:
SDL_log_priorities[i] = SDL_LOG_PRIORITY_VERBOSE;
break;
default:
SDL_log_priorities[i] = SDL_LOG_PRIORITY_ERROR;
break;
}
}
}
SDL_UnlockMutex(SDL_log_lock);
}
static void CleanupLogPrefixes(void)
{
for (int i = 0; i < SDL_arraysize(SDL_priority_prefixes); ++i) {
if (SDL_priority_prefixes[i]) {
SDL_free(SDL_priority_prefixes[i]);
SDL_priority_prefixes[i] = NULL;
}
}
}
static const char *GetLogPriorityPrefix(SDL_LogPriority priority)
{
if (priority <= SDL_LOG_PRIORITY_INVALID || priority >= SDL_LOG_PRIORITY_COUNT) {
return "";
}
if (SDL_priority_prefixes[priority]) {
return SDL_priority_prefixes[priority];
}
switch (priority) {
case SDL_LOG_PRIORITY_WARN:
return "WARNING: ";
case SDL_LOG_PRIORITY_ERROR:
return "ERROR: ";
case SDL_LOG_PRIORITY_CRITICAL:
return "ERROR: ";
default:
return "";
}
}
bool SDL_SetLogPriorityPrefix(SDL_LogPriority priority, const char *prefix)
{
char *prefix_copy;
if (priority <= SDL_LOG_PRIORITY_INVALID || priority >= SDL_LOG_PRIORITY_COUNT) {
return SDL_InvalidParamError("priority");
}
if (!prefix || !*prefix) {
prefix_copy = SDL_strdup("");
} else {
prefix_copy = SDL_strdup(prefix);
}
if (!prefix_copy) {
return false;
}
SDL_LockMutex(SDL_log_function_lock);
{
if (SDL_priority_prefixes[priority]) {
SDL_free(SDL_priority_prefixes[priority]);
}
SDL_priority_prefixes[priority] = prefix_copy;
}
SDL_UnlockMutex(SDL_log_function_lock);
return true;
}
void SDL_Log(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, ap);
va_end(ap);
}
void SDL_LogTrace(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_TRACE, fmt, ap);
va_end(ap);
}
void SDL_LogVerbose(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_VERBOSE, fmt, ap);
va_end(ap);
}
void SDL_LogDebug(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_DEBUG, fmt, ap);
va_end(ap);
}
void SDL_LogInfo(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_INFO, fmt, ap);
va_end(ap);
}
void SDL_LogWarn(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_WARN, fmt, ap);
va_end(ap);
}
void SDL_LogError(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_ERROR, fmt, ap);
va_end(ap);
}
void SDL_LogCritical(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_CRITICAL, fmt, ap);
va_end(ap);
}
void SDL_LogMessage(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, priority, fmt, ap);
va_end(ap);
}
#ifdef SDL_PLATFORM_ANDROID
static const char *GetCategoryPrefix(int category)
{
if (category < SDL_LOG_CATEGORY_RESERVED2) {
return SDL_category_names[category];
}
if (category < SDL_LOG_CATEGORY_CUSTOM) {
return "RESERVED";
}
return "CUSTOM";
}
#endif // SDL_PLATFORM_ANDROID
void SDL_LogMessageV(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)
{
char *message = NULL;
char stack_buf[SDL_MAX_LOG_MESSAGE_STACK];
size_t len_plus_term;
int len;
va_list aq;
// Nothing to do if we don't have an output function
if (!SDL_log_function) {
return;
}
// See if we want to do anything with this message
if (priority < SDL_GetLogPriority(category)) {
return;
}
// Render into stack buffer
va_copy(aq, ap);
len = SDL_vsnprintf(stack_buf, sizeof(stack_buf), fmt, aq);
va_end(aq);
if (len < 0) {
return;
}
// If message truncated, allocate and re-render
if (len >= sizeof(stack_buf) && SDL_size_add_check_overflow(len, 1, &len_plus_term)) {
// Allocate exactly what we need, including the zero-terminator
message = (char *)SDL_malloc(len_plus_term);
if (!message) {
return;
}
va_copy(aq, ap);
len = SDL_vsnprintf(message, len_plus_term, fmt, aq);
va_end(aq);
} else {
message = stack_buf;
}
// Chop off final endline.
if ((len > 0) && (message[len - 1] == '\n')) {
message[--len] = '\0';
if ((len > 0) && (message[len - 1] == '\r')) { // catch "\r\n", too.
message[--len] = '\0';
}
}
SDL_LockMutex(SDL_log_function_lock);
{
SDL_log_function(SDL_log_userdata, category, priority, message);
}
SDL_UnlockMutex(SDL_log_function_lock);
// Free only if dynamically allocated
if (message != stack_buf) {
SDL_free(message);
}
}
#if defined(SDL_PLATFORM_WIN32) && !defined(SDL_PLATFORM_GDK)
enum {
CONSOLE_UNATTACHED = 0,
CONSOLE_ATTACHED_CONSOLE = 1,
CONSOLE_ATTACHED_FILE = 2,
CONSOLE_ATTACHED_ERROR = -1,
} consoleAttached = CONSOLE_UNATTACHED;
// Handle to stderr output of console.
static HANDLE stderrHandle = NULL;
#endif
static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority,
const char *message)
{
#if defined(SDL_PLATFORM_WINDOWS)
// Way too many allocations here, urgh
// Note: One can't call SDL_SetError here, since that function itself logs.
{
char *output;
size_t length;
LPTSTR tstr;
bool isstack;
#if !defined(SDL_PLATFORM_GDK)
BOOL attachResult;
DWORD attachError;
DWORD consoleMode;
DWORD charsWritten;
// Maybe attach console and get stderr handle
if (consoleAttached == CONSOLE_UNATTACHED) {
attachResult = AttachConsole(ATTACH_PARENT_PROCESS);
if (!attachResult) {
attachError = GetLastError();
if (attachError == ERROR_INVALID_HANDLE) {
// This is expected when running from Visual Studio
// OutputDebugString(TEXT("Parent process has no console\r\n"));
consoleAttached = CONSOLE_ATTACHED_ERROR;
} else if (attachError == ERROR_GEN_FAILURE) {
OutputDebugString(TEXT("Could not attach to console of parent process\r\n"));
consoleAttached = CONSOLE_ATTACHED_ERROR;
} else if (attachError == ERROR_ACCESS_DENIED) {
// Already attached
consoleAttached = CONSOLE_ATTACHED_CONSOLE;
} else {
OutputDebugString(TEXT("Error attaching console\r\n"));
consoleAttached = CONSOLE_ATTACHED_ERROR;
}
} else {
// Newly attached
consoleAttached = CONSOLE_ATTACHED_CONSOLE;
}
if (consoleAttached == CONSOLE_ATTACHED_CONSOLE) {
stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
if (GetConsoleMode(stderrHandle, &consoleMode) == 0) {
// WriteConsole fails if the output is redirected to a file. Must use WriteFile instead.
consoleAttached = CONSOLE_ATTACHED_FILE;
}
}
}
#endif // !defined(SDL_PLATFORM_GDK)
length = SDL_strlen(GetLogPriorityPrefix(priority)) + SDL_strlen(message) + 1 + 1 + 1;
output = SDL_small_alloc(char, length, &isstack);
if (!output) {
return;
}
(void)SDL_snprintf(output, length, "%s%s\r\n", GetLogPriorityPrefix(priority), message);
tstr = WIN_UTF8ToString(output);
// Output to debugger
OutputDebugString(tstr);
#if !defined(SDL_PLATFORM_GDK)
// Screen output to stderr, if console was attached.
if (consoleAttached == CONSOLE_ATTACHED_CONSOLE) {
if (!WriteConsole(stderrHandle, tstr, (DWORD)SDL_tcslen(tstr), &charsWritten, NULL)) {
OutputDebugString(TEXT("Error calling WriteConsole\r\n"));
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) {
OutputDebugString(TEXT("Insufficient heap memory to write message\r\n"));
}
}
} else if (consoleAttached == CONSOLE_ATTACHED_FILE) {
if (!WriteFile(stderrHandle, output, (DWORD)SDL_strlen(output), &charsWritten, NULL)) {
OutputDebugString(TEXT("Error calling WriteFile\r\n"));
}
}
#endif // !defined(SDL_PLATFORM_GDK)
SDL_free(tstr);
SDL_small_free(output, isstack);
}
#elif defined(SDL_PLATFORM_ANDROID)
{
char tag[32];
SDL_snprintf(tag, SDL_arraysize(tag), "SDL/%s", GetCategoryPrefix(category));
__android_log_write(SDL_android_priority[priority], tag, message);
}
#elif defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))
/* Technically we don't need Cocoa/UIKit, but that's where this function is defined for now.
*/
extern void SDL_NSLog(const char *prefix, const char *text);
{
SDL_NSLog(GetLogPriorityPrefix(priority), message);
return;
}
#elif defined(SDL_PLATFORM_PSP) || defined(SDL_PLATFORM_PS2)
{
FILE *pFile;
pFile = fopen("SDL_Log.txt", "a");
if (pFile) {
(void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
(void)fclose(pFile);
}
}
#elif defined(SDL_PLATFORM_VITA)
{
FILE *pFile;
pFile = fopen("ux0:/data/SDL_Log.txt", "a");
if (pFile) {
(void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
(void)fclose(pFile);
}
}
#elif defined(SDL_PLATFORM_3DS)
{
FILE *pFile;
pFile = fopen("sdmc:/3ds/SDL_Log.txt", "a");
if (pFile) {
(void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
(void)fclose(pFile);
}
}
#endif
#if defined(HAVE_STDIO_H) && \
!(defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))) && \
!(defined(SDL_PLATFORM_WIN32))
(void)fprintf(stderr, "%s%s\n", GetLogPriorityPrefix(priority), message);
#endif
}
SDL_LogOutputFunction SDL_GetDefaultLogOutputFunction(void)
{
return SDL_LogOutput;
}
void SDL_GetLogOutputFunction(SDL_LogOutputFunction *callback, void **userdata)
{
SDL_LockMutex(SDL_log_function_lock);
{
if (callback) {
*callback = SDL_log_function;
}
if (userdata) {
*userdata = SDL_log_userdata;
}
}
SDL_UnlockMutex(SDL_log_function_lock);
}
void SDL_SetLogOutputFunction(SDL_LogOutputFunction callback, void *userdata)
{
SDL_LockMutex(SDL_log_function_lock);
{
SDL_log_function = callback;
SDL_log_userdata = userdata;
}
SDL_UnlockMutex(SDL_log_function_lock);
}

31
vendor/sdl-3.2.10/src/SDL_log_c.h vendored Normal file
View file

@ -0,0 +1,31 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// This file defines useful function for working with SDL logging
#ifndef SDL_log_c_h_
#define SDL_log_c_h_
extern void SDL_InitLog(void);
extern void SDL_QuitLog(void);
#endif // SDL_log_c_h_

824
vendor/sdl-3.2.10/src/SDL_properties.c vendored Normal file
View file

@ -0,0 +1,824 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_hints_c.h"
#include "SDL_properties_c.h"
typedef struct
{
SDL_PropertyType type;
union {
void *pointer_value;
char *string_value;
Sint64 number_value;
float float_value;
bool boolean_value;
} value;
char *string_storage;
SDL_CleanupPropertyCallback cleanup;
void *userdata;
} SDL_Property;
typedef struct
{
SDL_HashTable *props;
SDL_Mutex *lock;
} SDL_Properties;
static SDL_InitState SDL_properties_init;
static SDL_HashTable *SDL_properties;
static SDL_AtomicU32 SDL_last_properties_id;
static SDL_AtomicU32 SDL_global_properties;
static void SDL_FreePropertyWithCleanup(const void *key, const void *value, void *data, bool cleanup)
{
SDL_Property *property = (SDL_Property *)value;
if (property) {
switch (property->type) {
case SDL_PROPERTY_TYPE_POINTER:
if (property->cleanup && cleanup) {
property->cleanup(property->userdata, property->value.pointer_value);
}
break;
case SDL_PROPERTY_TYPE_STRING:
SDL_free(property->value.string_value);
break;
default:
break;
}
SDL_free(property->string_storage);
}
SDL_free((void *)key);
SDL_free((void *)value);
}
static void SDLCALL SDL_FreeProperty(void *data, const void *key, const void *value)
{
SDL_FreePropertyWithCleanup(key, value, data, true);
}
static void SDL_FreeProperties(SDL_Properties *properties)
{
if (properties) {
SDL_DestroyHashTable(properties->props);
SDL_DestroyMutex(properties->lock);
SDL_free(properties);
}
}
bool SDL_InitProperties(void)
{
if (!SDL_ShouldInit(&SDL_properties_init)) {
return true;
}
SDL_properties = SDL_CreateHashTable(0, true, SDL_HashID, SDL_KeyMatchID, NULL, NULL);
const bool initialized = (SDL_properties != NULL);
SDL_SetInitialized(&SDL_properties_init, initialized);
return initialized;
}
static bool SDLCALL FreeOneProperties(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
{
SDL_FreeProperties((SDL_Properties *)value);
return true; // keep iterating.
}
void SDL_QuitProperties(void)
{
if (!SDL_ShouldQuit(&SDL_properties_init)) {
return;
}
SDL_PropertiesID props;
do {
props = SDL_GetAtomicU32(&SDL_global_properties);
} while (!SDL_CompareAndSwapAtomicU32(&SDL_global_properties, props, 0));
if (props) {
SDL_DestroyProperties(props);
}
// this can't just DestroyHashTable with SDL_FreeProperties as the destructor, because
// other destructors under this might cause use to attempt a recursive lock on SDL_properties,
// which isn't allowed with rwlocks. So manually iterate and free everything.
SDL_HashTable *properties = SDL_properties;
SDL_properties = NULL;
SDL_IterateHashTable(properties, FreeOneProperties, NULL);
SDL_DestroyHashTable(properties);
SDL_SetInitialized(&SDL_properties_init, false);
}
static bool SDL_CheckInitProperties(void)
{
return SDL_InitProperties();
}
SDL_PropertiesID SDL_GetGlobalProperties(void)
{
SDL_PropertiesID props = SDL_GetAtomicU32(&SDL_global_properties);
if (!props) {
props = SDL_CreateProperties();
if (!SDL_CompareAndSwapAtomicU32(&SDL_global_properties, 0, props)) {
// Somebody else created global properties before us, just use those
SDL_DestroyProperties(props);
props = SDL_GetAtomicU32(&SDL_global_properties);
}
}
return props;
}
SDL_PropertiesID SDL_CreateProperties(void)
{
if (!SDL_CheckInitProperties()) {
return 0;
}
SDL_Properties *properties = (SDL_Properties *)SDL_calloc(1, sizeof(*properties));
if (!properties) {
return 0;
}
properties->lock = SDL_CreateMutex();
if (!properties->lock) {
SDL_free(properties);
return 0;
}
properties->props = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_FreeProperty, NULL);
if (!properties->props) {
SDL_DestroyMutex(properties->lock);
SDL_free(properties);
return 0;
}
SDL_PropertiesID props = 0;
while (true) {
props = (SDL_GetAtomicU32(&SDL_last_properties_id) + 1);
if (props == 0) {
continue;
} else if (SDL_CompareAndSwapAtomicU32(&SDL_last_properties_id, props - 1, props)) {
break;
}
}
SDL_assert(!SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, NULL)); // should NOT be in the hash table already.
if (!SDL_InsertIntoHashTable(SDL_properties, (const void *)(uintptr_t)props, properties, false)) {
SDL_FreeProperties(properties);
return 0;
}
return props; // All done!
}
typedef struct CopyOnePropertyData
{
SDL_Properties *dst_properties;
bool result;
} CopyOnePropertyData;
static bool SDLCALL CopyOneProperty(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
{
const SDL_Property *src_property = (const SDL_Property *)value;
if (src_property->cleanup) {
// Can't copy properties with cleanup functions, we don't know how to duplicate the data
return true; // keep iterating.
}
CopyOnePropertyData *data = (CopyOnePropertyData *) userdata;
SDL_Properties *dst_properties = data->dst_properties;
const char *src_name = (const char *)key;
SDL_Property *dst_property;
char *dst_name = SDL_strdup(src_name);
if (!dst_name) {
data->result = false;
return true; // keep iterating (I guess...?)
}
dst_property = (SDL_Property *)SDL_malloc(sizeof(*dst_property));
if (!dst_property) {
SDL_free(dst_name);
data->result = false;
return true; // keep iterating (I guess...?)
}
SDL_copyp(dst_property, src_property);
if (src_property->type == SDL_PROPERTY_TYPE_STRING) {
dst_property->value.string_value = SDL_strdup(src_property->value.string_value);
if (!dst_property->value.string_value) {
SDL_free(dst_name);
SDL_free(dst_property);
data->result = false;
return true; // keep iterating (I guess...?)
}
}
if (!SDL_InsertIntoHashTable(dst_properties->props, dst_name, dst_property, true)) {
SDL_FreePropertyWithCleanup(dst_name, dst_property, NULL, false);
data->result = false;
}
return true; // keep iterating.
}
bool SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst)
{
if (!src) {
return SDL_InvalidParamError("src");
}
if (!dst) {
return SDL_InvalidParamError("dst");
}
SDL_Properties *src_properties = NULL;
SDL_Properties *dst_properties = NULL;
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)src, (const void **)&src_properties);
if (!src_properties) {
return SDL_InvalidParamError("src");
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)dst, (const void **)&dst_properties);
if (!dst_properties) {
return SDL_InvalidParamError("dst");
}
bool result = true;
SDL_LockMutex(src_properties->lock);
SDL_LockMutex(dst_properties->lock);
{
CopyOnePropertyData data = { dst_properties, true };
SDL_IterateHashTable(src_properties->props, CopyOneProperty, &data);
result = data.result;
}
SDL_UnlockMutex(dst_properties->lock);
SDL_UnlockMutex(src_properties->lock);
return result;
}
bool SDL_LockProperties(SDL_PropertiesID props)
{
SDL_Properties *properties = NULL;
if (!props) {
return SDL_InvalidParamError("props");
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return SDL_InvalidParamError("props");
}
SDL_LockMutex(properties->lock);
return true;
}
void SDL_UnlockProperties(SDL_PropertiesID props)
{
SDL_Properties *properties = NULL;
if (!props) {
return;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return;
}
SDL_UnlockMutex(properties->lock);
}
static bool SDL_PrivateSetProperty(SDL_PropertiesID props, const char *name, SDL_Property *property)
{
SDL_Properties *properties = NULL;
bool result = true;
if (!props) {
SDL_FreePropertyWithCleanup(NULL, property, NULL, true);
return SDL_InvalidParamError("props");
}
if (!name || !*name) {
SDL_FreePropertyWithCleanup(NULL, property, NULL, true);
return SDL_InvalidParamError("name");
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
SDL_FreePropertyWithCleanup(NULL, property, NULL, true);
return SDL_InvalidParamError("props");
}
SDL_LockMutex(properties->lock);
{
SDL_RemoveFromHashTable(properties->props, name);
if (property) {
char *key = SDL_strdup(name);
if (!key || !SDL_InsertIntoHashTable(properties->props, key, property, false)) {
SDL_FreePropertyWithCleanup(key, property, NULL, true);
result = false;
}
}
}
SDL_UnlockMutex(properties->lock);
return result;
}
bool SDL_SetPointerPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, SDL_CleanupPropertyCallback cleanup, void *userdata)
{
SDL_Property *property;
if (!value) {
if (cleanup) {
cleanup(userdata, value);
}
return SDL_ClearProperty(props, name);
}
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
if (cleanup) {
cleanup(userdata, value);
}
SDL_FreePropertyWithCleanup(NULL, property, NULL, false);
return false;
}
property->type = SDL_PROPERTY_TYPE_POINTER;
property->value.pointer_value = value;
property->cleanup = cleanup;
property->userdata = userdata;
return SDL_PrivateSetProperty(props, name, property);
}
bool SDL_SetPointerProperty(SDL_PropertiesID props, const char *name, void *value)
{
SDL_Property *property;
if (!value) {
return SDL_ClearProperty(props, name);
}
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return false;
}
property->type = SDL_PROPERTY_TYPE_POINTER;
property->value.pointer_value = value;
return SDL_PrivateSetProperty(props, name, property);
}
static void SDLCALL CleanupFreeableProperty(void *userdata, void *value)
{
SDL_free(value);
}
bool SDL_SetFreeableProperty(SDL_PropertiesID props, const char *name, void *value)
{
return SDL_SetPointerPropertyWithCleanup(props, name, value, CleanupFreeableProperty, NULL);
}
static void SDLCALL CleanupSurface(void *userdata, void *value)
{
SDL_Surface *surface = (SDL_Surface *)value;
SDL_DestroySurface(surface);
}
bool SDL_SetSurfaceProperty(SDL_PropertiesID props, const char *name, SDL_Surface *surface)
{
return SDL_SetPointerPropertyWithCleanup(props, name, surface, CleanupSurface, NULL);
}
bool SDL_SetStringProperty(SDL_PropertiesID props, const char *name, const char *value)
{
SDL_Property *property;
if (!value) {
return SDL_ClearProperty(props, name);
}
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return false;
}
property->type = SDL_PROPERTY_TYPE_STRING;
property->value.string_value = SDL_strdup(value);
if (!property->value.string_value) {
SDL_free(property);
return false;
}
return SDL_PrivateSetProperty(props, name, property);
}
bool SDL_SetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 value)
{
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return false;
}
property->type = SDL_PROPERTY_TYPE_NUMBER;
property->value.number_value = value;
return SDL_PrivateSetProperty(props, name, property);
}
bool SDL_SetFloatProperty(SDL_PropertiesID props, const char *name, float value)
{
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return false;
}
property->type = SDL_PROPERTY_TYPE_FLOAT;
property->value.float_value = value;
return SDL_PrivateSetProperty(props, name, property);
}
bool SDL_SetBooleanProperty(SDL_PropertiesID props, const char *name, bool value)
{
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return false;
}
property->type = SDL_PROPERTY_TYPE_BOOLEAN;
property->value.boolean_value = value ? true : false;
return SDL_PrivateSetProperty(props, name, property);
}
bool SDL_HasProperty(SDL_PropertiesID props, const char *name)
{
return (SDL_GetPropertyType(props, name) != SDL_PROPERTY_TYPE_INVALID);
}
SDL_PropertyType SDL_GetPropertyType(SDL_PropertiesID props, const char *name)
{
SDL_Properties *properties = NULL;
SDL_PropertyType type = SDL_PROPERTY_TYPE_INVALID;
if (!props) {
return SDL_PROPERTY_TYPE_INVALID;
}
if (!name || !*name) {
return SDL_PROPERTY_TYPE_INVALID;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return SDL_PROPERTY_TYPE_INVALID;
}
SDL_LockMutex(properties->lock);
{
SDL_Property *property = NULL;
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
type = property->type;
}
}
SDL_UnlockMutex(properties->lock);
return type;
}
void *SDL_GetPointerProperty(SDL_PropertiesID props, const char *name, void *default_value)
{
SDL_Properties *properties = NULL;
void *value = default_value;
if (!props) {
return value;
}
if (!name || !*name) {
return value;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return value;
}
// Note that taking the lock here only guarantees that we won't read the
// hashtable while it's being modified. The value itself can easily be
// freed from another thread after it is returned here.
SDL_LockMutex(properties->lock);
{
SDL_Property *property = NULL;
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
if (property->type == SDL_PROPERTY_TYPE_POINTER) {
value = property->value.pointer_value;
}
}
}
SDL_UnlockMutex(properties->lock);
return value;
}
const char *SDL_GetStringProperty(SDL_PropertiesID props, const char *name, const char *default_value)
{
SDL_Properties *properties = NULL;
const char *value = default_value;
if (!props) {
return value;
}
if (!name || !*name) {
return value;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return value;
}
SDL_LockMutex(properties->lock);
{
SDL_Property *property = NULL;
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
switch (property->type) {
case SDL_PROPERTY_TYPE_STRING:
value = property->value.string_value;
break;
case SDL_PROPERTY_TYPE_NUMBER:
if (property->string_storage) {
value = property->string_storage;
} else {
SDL_asprintf(&property->string_storage, "%" SDL_PRIs64, property->value.number_value);
if (property->string_storage) {
value = property->string_storage;
}
}
break;
case SDL_PROPERTY_TYPE_FLOAT:
if (property->string_storage) {
value = property->string_storage;
} else {
SDL_asprintf(&property->string_storage, "%f", property->value.float_value);
if (property->string_storage) {
value = property->string_storage;
}
}
break;
case SDL_PROPERTY_TYPE_BOOLEAN:
value = property->value.boolean_value ? "true" : "false";
break;
default:
break;
}
}
}
SDL_UnlockMutex(properties->lock);
return value;
}
Sint64 SDL_GetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 default_value)
{
SDL_Properties *properties = NULL;
Sint64 value = default_value;
if (!props) {
return value;
}
if (!name || !*name) {
return value;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return value;
}
SDL_LockMutex(properties->lock);
{
SDL_Property *property = NULL;
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
switch (property->type) {
case SDL_PROPERTY_TYPE_STRING:
value = (Sint64)SDL_strtoll(property->value.string_value, NULL, 0);
break;
case SDL_PROPERTY_TYPE_NUMBER:
value = property->value.number_value;
break;
case SDL_PROPERTY_TYPE_FLOAT:
value = (Sint64)SDL_round((double)property->value.float_value);
break;
case SDL_PROPERTY_TYPE_BOOLEAN:
value = property->value.boolean_value;
break;
default:
break;
}
}
}
SDL_UnlockMutex(properties->lock);
return value;
}
float SDL_GetFloatProperty(SDL_PropertiesID props, const char *name, float default_value)
{
SDL_Properties *properties = NULL;
float value = default_value;
if (!props) {
return value;
}
if (!name || !*name) {
return value;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return value;
}
SDL_LockMutex(properties->lock);
{
SDL_Property *property = NULL;
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
switch (property->type) {
case SDL_PROPERTY_TYPE_STRING:
value = (float)SDL_atof(property->value.string_value);
break;
case SDL_PROPERTY_TYPE_NUMBER:
value = (float)property->value.number_value;
break;
case SDL_PROPERTY_TYPE_FLOAT:
value = property->value.float_value;
break;
case SDL_PROPERTY_TYPE_BOOLEAN:
value = (float)property->value.boolean_value;
break;
default:
break;
}
}
}
SDL_UnlockMutex(properties->lock);
return value;
}
bool SDL_GetBooleanProperty(SDL_PropertiesID props, const char *name, bool default_value)
{
SDL_Properties *properties = NULL;
bool value = default_value ? true : false;
if (!props) {
return value;
}
if (!name || !*name) {
return value;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return value;
}
SDL_LockMutex(properties->lock);
{
SDL_Property *property = NULL;
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
switch (property->type) {
case SDL_PROPERTY_TYPE_STRING:
value = SDL_GetStringBoolean(property->value.string_value, default_value);
break;
case SDL_PROPERTY_TYPE_NUMBER:
value = (property->value.number_value != 0);
break;
case SDL_PROPERTY_TYPE_FLOAT:
value = (property->value.float_value != 0.0f);
break;
case SDL_PROPERTY_TYPE_BOOLEAN:
value = property->value.boolean_value;
break;
default:
break;
}
}
}
SDL_UnlockMutex(properties->lock);
return value;
}
bool SDL_ClearProperty(SDL_PropertiesID props, const char *name)
{
return SDL_PrivateSetProperty(props, name, NULL);
}
typedef struct EnumerateOnePropertyData
{
SDL_EnumeratePropertiesCallback callback;
void *userdata;
SDL_PropertiesID props;
} EnumerateOnePropertyData;
static bool SDLCALL EnumerateOneProperty(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
{
(void) table;
(void) value;
const EnumerateOnePropertyData *data = (const EnumerateOnePropertyData *) userdata;
data->callback(data->userdata, data->props, (const char *)key);
return true; // keep iterating.
}
bool SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCallback callback, void *userdata)
{
SDL_Properties *properties = NULL;
if (!props) {
return SDL_InvalidParamError("props");
}
if (!callback) {
return SDL_InvalidParamError("callback");
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return SDL_InvalidParamError("props");
}
SDL_LockMutex(properties->lock);
{
EnumerateOnePropertyData data = { callback, userdata, props };
SDL_IterateHashTable(properties->props, EnumerateOneProperty, &data);
}
SDL_UnlockMutex(properties->lock);
return true;
}
static void SDLCALL SDL_DumpPropertiesCallback(void *userdata, SDL_PropertiesID props, const char *name)
{
switch (SDL_GetPropertyType(props, name)) {
case SDL_PROPERTY_TYPE_POINTER:
SDL_Log("%s: %p", name, SDL_GetPointerProperty(props, name, NULL));
break;
case SDL_PROPERTY_TYPE_STRING:
SDL_Log("%s: \"%s\"", name, SDL_GetStringProperty(props, name, ""));
break;
case SDL_PROPERTY_TYPE_NUMBER:
{
Sint64 value = SDL_GetNumberProperty(props, name, 0);
SDL_Log("%s: %" SDL_PRIs64 " (%" SDL_PRIx64 ")", name, value, value);
}
break;
case SDL_PROPERTY_TYPE_FLOAT:
SDL_Log("%s: %g", name, SDL_GetFloatProperty(props, name, 0.0f));
break;
case SDL_PROPERTY_TYPE_BOOLEAN:
SDL_Log("%s: %s", name, SDL_GetBooleanProperty(props, name, false) ? "true" : "false");
break;
default:
SDL_Log("%s UNKNOWN TYPE", name);
break;
}
}
bool SDL_DumpProperties(SDL_PropertiesID props)
{
return SDL_EnumerateProperties(props, SDL_DumpPropertiesCallback, NULL);
}
void SDL_DestroyProperties(SDL_PropertiesID props)
{
if (props) {
// this can't just use RemoveFromHashTable with SDL_FreeProperties as the destructor, because
// other destructors under this might cause use to attempt a recursive lock on SDL_properties,
// which isn't allowed with rwlocks. So manually look it up and remove/free it.
SDL_Properties *properties = NULL;
if (SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties)) {
SDL_FreeProperties(properties);
SDL_RemoveFromHashTable(SDL_properties, (const void *)(uintptr_t)props);
}
}
}

View file

@ -0,0 +1,26 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
extern bool SDL_InitProperties(void);
extern bool SDL_SetFreeableProperty(SDL_PropertiesID props, const char *name, void *value);
extern bool SDL_SetSurfaceProperty(SDL_PropertiesID props, const char *name, SDL_Surface *surface);
extern bool SDL_DumpProperties(SDL_PropertiesID props);
extern void SDL_QuitProperties(void);

550
vendor/sdl-3.2.10/src/SDL_utils.c vendored Normal file
View file

@ -0,0 +1,550 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS)
#include <unistd.h>
#endif
#include "joystick/SDL_joystick_c.h" // For SDL_GetGamepadTypeFromVIDPID()
// Common utility functions that aren't in the public API
int SDL_powerof2(int x)
{
int value;
if (x <= 0) {
// Return some sane value - we shouldn't hit this in our use cases
return 1;
}
// This trick works for 32-bit values
{
SDL_COMPILE_TIME_ASSERT(SDL_powerof2, sizeof(x) == sizeof(Uint32));
}
value = x;
value -= 1;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
value += 1;
return value;
}
Uint32 SDL_CalculateGCD(Uint32 a, Uint32 b)
{
if (b == 0) {
return a;
}
return SDL_CalculateGCD(b, (a % b));
}
// Algorithm adapted with thanks from John Cook's blog post:
// http://www.johndcook.com/blog/2010/10/20/best-rational-approximation
void SDL_CalculateFraction(float x, int *numerator, int *denominator)
{
const int N = 1000;
int a = 0, b = 1;
int c = 1, d = 0;
while (b <= N && d <= N) {
float mediant = (float)(a + c) / (b + d);
if (x == mediant) {
if (b + d <= N) {
*numerator = a + c;
*denominator = b + d;
} else if (d > b) {
*numerator = c;
*denominator = d;
} else {
*numerator = a;
*denominator = b;
}
return;
} else if (x > mediant) {
a = a + c;
b = b + d;
} else {
c = a + c;
d = b + d;
}
}
if (b > N) {
*numerator = c;
*denominator = d;
} else {
*numerator = a;
*denominator = b;
}
}
bool SDL_startswith(const char *string, const char *prefix)
{
if (SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0) {
return true;
}
return false;
}
bool SDL_endswith(const char *string, const char *suffix)
{
size_t string_length = string ? SDL_strlen(string) : 0;
size_t suffix_length = suffix ? SDL_strlen(suffix) : 0;
if (suffix_length > 0 && suffix_length <= string_length) {
if (SDL_memcmp(string + string_length - suffix_length, suffix, suffix_length) == 0) {
return true;
}
}
return false;
}
SDL_COMPILE_TIME_ASSERT(sizeof_object_id, sizeof(int) == sizeof(Uint32));
Uint32 SDL_GetNextObjectID(void)
{
static SDL_AtomicInt last_id;
Uint32 id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
if (id == 0) {
id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
}
return id;
}
static SDL_InitState SDL_objects_init;
static SDL_HashTable *SDL_objects;
static Uint32 SDLCALL SDL_HashObject(void *unused, const void *key)
{
return (Uint32)(uintptr_t)key;
}
static bool SDL_KeyMatchObject(void *unused, const void *a, const void *b)
{
return (a == b);
}
void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid)
{
SDL_assert(object != NULL);
if (SDL_ShouldInit(&SDL_objects_init)) {
SDL_objects = SDL_CreateHashTable(0, true, SDL_HashObject, SDL_KeyMatchObject, NULL, NULL);
const bool initialized = (SDL_objects != NULL);
SDL_SetInitialized(&SDL_objects_init, initialized);
if (!initialized) {
return;
}
}
if (valid) {
SDL_InsertIntoHashTable(SDL_objects, object, (void *)(uintptr_t)type, true);
} else {
SDL_RemoveFromHashTable(SDL_objects, object);
}
}
bool SDL_ObjectValid(void *object, SDL_ObjectType type)
{
if (!object) {
return false;
}
const void *object_type;
if (!SDL_FindInHashTable(SDL_objects, object, &object_type)) {
return false;
}
return (((SDL_ObjectType)(uintptr_t)object_type) == type);
}
typedef struct GetOneObjectData
{
const SDL_ObjectType type;
void **objects;
const int count;
int num_objects;
} GetOneObjectData;
static bool SDLCALL GetOneObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type)
{
GetOneObjectData *data = (GetOneObjectData *) userdata;
if ((SDL_ObjectType)(uintptr_t)object_type == data->type) {
if (data->num_objects < data->count) {
data->objects[data->num_objects] = (void *)object;
}
++data->num_objects;
}
return true; // keep iterating.
}
int SDL_GetObjects(SDL_ObjectType type, void **objects, int count)
{
GetOneObjectData data = { type, objects, count, 0 };
SDL_IterateHashTable(SDL_objects, GetOneObject, &data);
return data.num_objects;
}
static bool SDLCALL LogOneLeakedObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type)
{
const char *type = "unknown object";
switch ((SDL_ObjectType)(uintptr_t)object_type) {
#define SDLOBJTYPECASE(typ, name) case SDL_OBJECT_TYPE_##typ: type = name; break
SDLOBJTYPECASE(WINDOW, "SDL_Window");
SDLOBJTYPECASE(RENDERER, "SDL_Renderer");
SDLOBJTYPECASE(TEXTURE, "SDL_Texture");
SDLOBJTYPECASE(JOYSTICK, "SDL_Joystick");
SDLOBJTYPECASE(GAMEPAD, "SDL_Gamepad");
SDLOBJTYPECASE(HAPTIC, "SDL_Haptic");
SDLOBJTYPECASE(SENSOR, "SDL_Sensor");
SDLOBJTYPECASE(HIDAPI_DEVICE, "hidapi device");
SDLOBJTYPECASE(HIDAPI_JOYSTICK, "hidapi joystick");
SDLOBJTYPECASE(THREAD, "thread");
SDLOBJTYPECASE(TRAY, "SDL_Tray");
#undef SDLOBJTYPECASE
default: break;
}
SDL_Log("Leaked %s (%p)", type, object);
return true; // keep iterating.
}
void SDL_SetObjectsInvalid(void)
{
if (SDL_ShouldQuit(&SDL_objects_init)) {
// Log any leaked objects
SDL_IterateHashTable(SDL_objects, LogOneLeakedObject, NULL);
SDL_assert(SDL_HashTableEmpty(SDL_objects));
SDL_DestroyHashTable(SDL_objects);
SDL_objects = NULL;
SDL_SetInitialized(&SDL_objects_init, false);
}
}
static int SDL_URIDecode(const char *src, char *dst, int len)
{
int ri, wi, di;
char decode = '\0';
if (!src || !dst || len < 0) {
return -1;
}
if (len == 0) {
len = (int)SDL_strlen(src);
}
for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) {
if (di == 0) {
// start decoding
if (src[ri] == '%') {
decode = '\0';
di += 1;
continue;
}
// normal write
dst[wi] = src[ri];
wi += 1;
} else if (di == 1 || di == 2) {
char off = '\0';
char isa = src[ri] >= 'a' && src[ri] <= 'f';
char isA = src[ri] >= 'A' && src[ri] <= 'F';
char isn = src[ri] >= '0' && src[ri] <= '9';
if (!(isa || isA || isn)) {
// not a hexadecimal
int sri;
for (sri = ri - di; sri <= ri; sri += 1) {
dst[wi] = src[sri];
wi += 1;
}
di = 0;
continue;
}
// itsy bitsy magicsy
if (isn) {
off = 0 - '0';
} else if (isa) {
off = 10 - 'a';
} else if (isA) {
off = 10 - 'A';
}
decode |= (src[ri] + off) << (2 - di) * 4;
if (di == 2) {
dst[wi] = decode;
wi += 1;
di = 0;
} else {
di += 1;
}
}
}
dst[wi] = '\0';
return wi;
}
int SDL_URIToLocal(const char *src, char *dst)
{
if (SDL_memcmp(src, "file:/", 6) == 0) {
src += 6; // local file?
} else if (SDL_strstr(src, ":/") != NULL) {
return -1; // wrong scheme
}
bool local = src[0] != '/' || (src[0] != '\0' && src[1] == '/');
// Check the hostname, if present. RFC 3986 states that the hostname component of a URI is not case-sensitive.
if (!local && src[0] == '/' && src[2] != '/') {
char *hostname_end = SDL_strchr(src + 1, '/');
if (hostname_end) {
const size_t src_len = hostname_end - (src + 1);
size_t hostname_len;
#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS)
char hostname[257];
if (gethostname(hostname, 255) == 0) {
hostname[256] = '\0';
hostname_len = SDL_strlen(hostname);
if (hostname_len == src_len && SDL_strncasecmp(src + 1, hostname, src_len) == 0) {
src = hostname_end + 1;
local = true;
}
}
#endif
if (!local) {
static const char *localhost = "localhost";
hostname_len = SDL_strlen(localhost);
if (hostname_len == src_len && SDL_strncasecmp(src + 1, localhost, src_len) == 0) {
src = hostname_end + 1;
local = true;
}
}
}
}
if (local) {
// Convert URI escape sequences to real characters
if (src[0] == '/') {
src++;
} else {
src--;
}
return SDL_URIDecode(src, dst, 0);
}
return -1;
}
// This is a set of per-thread persistent strings that we can return from the SDL API.
// This is used for short strings that might persist past the lifetime of the object
// they are related to.
static SDL_TLSID SDL_string_storage;
static void SDL_FreePersistentStrings( void *value )
{
SDL_HashTable *strings = (SDL_HashTable *)value;
SDL_DestroyHashTable(strings);
}
const char *SDL_GetPersistentString(const char *string)
{
if (!string) {
return NULL;
}
if (!*string) {
return "";
}
SDL_HashTable *strings = (SDL_HashTable *)SDL_GetTLS(&SDL_string_storage);
if (!strings) {
strings = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_DestroyHashValue, NULL);
if (!strings) {
return NULL;
}
SDL_SetTLS(&SDL_string_storage, strings, SDL_FreePersistentStrings);
}
const char *result;
if (!SDL_FindInHashTable(strings, string, (const void **)&result)) {
char *new_string = SDL_strdup(string);
if (!new_string) {
return NULL;
}
// If the hash table insert fails, at least we can return the string we allocated
SDL_InsertIntoHashTable(strings, new_string, new_string, false);
result = new_string;
}
return result;
}
static int PrefixMatch(const char *a, const char *b)
{
int matchlen = 0;
while (*a && *b) {
if (SDL_tolower((unsigned char)*a++) == SDL_tolower((unsigned char)*b++)) {
++matchlen;
} else {
break;
}
}
return matchlen;
}
char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name)
{
static struct
{
const char *prefix;
const char *replacement;
} replacements[] = {
{ "8BitDo Tech Ltd", "8BitDo" },
{ "ASTRO Gaming", "ASTRO" },
{ "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" },
{ "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" },
{ "HORI CO.,LTD", "HORI" },
{ "HORI CO.,LTD.", "HORI" },
{ "Mad Catz Inc.", "Mad Catz" },
{ "Nintendo Co., Ltd.", "Nintendo" },
{ "NVIDIA Corporation ", "" },
{ "Performance Designed Products", "PDP" },
{ "QANBA USA, LLC", "Qanba" },
{ "QANBA USA,LLC", "Qanba" },
{ "Unknown ", "" },
};
char *name = NULL;
size_t i, len;
if (!vendor_name) {
vendor_name = "";
}
if (!product_name) {
product_name = "";
}
while (*vendor_name == ' ') {
++vendor_name;
}
while (*product_name == ' ') {
++product_name;
}
if (*vendor_name && *product_name) {
len = (SDL_strlen(vendor_name) + 1 + SDL_strlen(product_name) + 1);
name = (char *)SDL_malloc(len);
if (name) {
(void)SDL_snprintf(name, len, "%s %s", vendor_name, product_name);
}
} else if (*product_name) {
name = SDL_strdup(product_name);
} else if (vendor || product) {
// Couldn't find a controller name, try to give it one based on device type
switch (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, true)) {
case SDL_GAMEPAD_TYPE_XBOX360:
name = SDL_strdup("Xbox 360 Controller");
break;
case SDL_GAMEPAD_TYPE_XBOXONE:
name = SDL_strdup("Xbox One Controller");
break;
case SDL_GAMEPAD_TYPE_PS3:
name = SDL_strdup("PS3 Controller");
break;
case SDL_GAMEPAD_TYPE_PS4:
name = SDL_strdup("PS4 Controller");
break;
case SDL_GAMEPAD_TYPE_PS5:
name = SDL_strdup("DualSense Wireless Controller");
break;
case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
name = SDL_strdup("Nintendo Switch Pro Controller");
break;
default:
len = (6 + 1 + 6 + 1);
name = (char *)SDL_malloc(len);
if (name) {
(void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product);
}
break;
}
} else if (default_name) {
name = SDL_strdup(default_name);
}
if (!name) {
return NULL;
}
// Trim trailing whitespace
for (len = SDL_strlen(name); (len > 0 && name[len - 1] == ' '); --len) {
// continue
}
name[len] = '\0';
// Compress duplicate spaces
for (i = 0; i < (len - 1);) {
if (name[i] == ' ' && name[i + 1] == ' ') {
SDL_memmove(&name[i], &name[i + 1], (len - i));
--len;
} else {
++i;
}
}
// Perform any manufacturer replacements
for (i = 0; i < SDL_arraysize(replacements); ++i) {
size_t prefixlen = SDL_strlen(replacements[i].prefix);
if (SDL_strncasecmp(name, replacements[i].prefix, prefixlen) == 0) {
size_t replacementlen = SDL_strlen(replacements[i].replacement);
if (replacementlen <= prefixlen) {
SDL_memcpy(name, replacements[i].replacement, replacementlen);
SDL_memmove(name + replacementlen, name + prefixlen, (len - prefixlen) + 1);
len -= (prefixlen - replacementlen);
} else {
// FIXME: Need to handle the expand case by reallocating the string
}
break;
}
}
/* Remove duplicate manufacturer or product in the name
* e.g. Razer Razer Raiju Tournament Edition Wired
*/
for (i = 1; i < (len - 1); ++i) {
int matchlen = PrefixMatch(name, &name[i]);
while (matchlen > 0) {
if (name[matchlen] == ' ' || name[matchlen] == '-') {
SDL_memmove(name, name + matchlen + 1, len - matchlen);
break;
}
--matchlen;
}
if (matchlen > 0) {
// We matched the manufacturer's name and removed it
break;
}
}
return name;
}

78
vendor/sdl-3.2.10/src/SDL_utils_c.h vendored Normal file
View file

@ -0,0 +1,78 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
// This is included in SDL_internal.h
//#include "SDL_internal.h"
#ifndef SDL_utils_h_
#define SDL_utils_h_
// Common utility functions that aren't in the public API
// Return the smallest power of 2 greater than or equal to 'x'
extern int SDL_powerof2(int x);
extern Uint32 SDL_CalculateGCD(Uint32 a, Uint32 b);
extern void SDL_CalculateFraction(float x, int *numerator, int *denominator);
extern bool SDL_startswith(const char *string, const char *prefix);
extern bool SDL_endswith(const char *string, const char *suffix);
/** Convert URI to a local filename, stripping the "file://"
* preamble and hostname if present, and writes the result
* to the dst buffer. Since URI-encoded characters take
* three times the space of normal characters, src and dst
* can safely point to the same buffer for in situ conversion.
*
* Returns the number of decoded bytes that wound up in
* the destination buffer, excluding the terminating NULL byte.
*
* On error, -1 is returned.
*/
extern int SDL_URIToLocal(const char *src, char *dst);
typedef enum
{
SDL_OBJECT_TYPE_UNKNOWN,
SDL_OBJECT_TYPE_WINDOW,
SDL_OBJECT_TYPE_RENDERER,
SDL_OBJECT_TYPE_TEXTURE,
SDL_OBJECT_TYPE_JOYSTICK,
SDL_OBJECT_TYPE_GAMEPAD,
SDL_OBJECT_TYPE_HAPTIC,
SDL_OBJECT_TYPE_SENSOR,
SDL_OBJECT_TYPE_HIDAPI_DEVICE,
SDL_OBJECT_TYPE_HIDAPI_JOYSTICK,
SDL_OBJECT_TYPE_THREAD,
SDL_OBJECT_TYPE_TRAY,
} SDL_ObjectType;
extern Uint32 SDL_GetNextObjectID(void);
extern void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid);
extern bool SDL_ObjectValid(void *object, SDL_ObjectType type);
extern int SDL_GetObjects(SDL_ObjectType type, void **objects, int count);
extern void SDL_SetObjectsInvalid(void);
extern const char *SDL_GetPersistentString(const char *string);
extern char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name);
#endif // SDL_utils_h_

View file

@ -0,0 +1,382 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(_MSC_VER) && (_MSC_VER >= 1900)
#include <intrin.h>
#define HAVE_MSC_ATOMICS 1
#endif
#ifdef SDL_PLATFORM_MACOS // !!! FIXME: should we favor gcc atomics?
#include <libkern/OSAtomic.h>
#endif
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_SOLARIS)
#include <atomic.h>
#endif
// The __atomic_load_n() intrinsic showed up in different times for different compilers.
#ifdef __clang__
#if __has_builtin(__atomic_load_n) || defined(HAVE_GCC_ATOMICS)
/* !!! FIXME: this advertises as available in the NDK but uses an external symbol we don't have.
It might be in a later NDK or we might need an extra library? --ryan. */
#ifndef SDL_PLATFORM_ANDROID
#define HAVE_ATOMIC_LOAD_N 1
#endif
#endif
#elif defined(__GNUC__)
#if (__GNUC__ >= 5)
#define HAVE_ATOMIC_LOAD_N 1
#endif
#endif
/* *INDENT-OFF* */ // clang-format off
#if defined(__WATCOMC__) && defined(__386__)
SDL_COMPILE_TIME_ASSERT(intsize, 4==sizeof(int));
#define HAVE_WATCOM_ATOMICS
extern __inline int _SDL_xchg_watcom(volatile int *a, int v);
#pragma aux _SDL_xchg_watcom = \
"lock xchg [ecx], eax" \
parm [ecx] [eax] \
value [eax] \
modify exact [eax];
extern __inline unsigned char _SDL_cmpxchg_watcom(volatile int *a, int newval, int oldval);
#pragma aux _SDL_cmpxchg_watcom = \
"lock cmpxchg [edx], ecx" \
"setz al" \
parm [edx] [ecx] [eax] \
value [al] \
modify exact [eax];
extern __inline int _SDL_xadd_watcom(volatile int *a, int v);
#pragma aux _SDL_xadd_watcom = \
"lock xadd [ecx], eax" \
parm [ecx] [eax] \
value [eax] \
modify exact [eax];
#endif // __WATCOMC__ && __386__
/* *INDENT-ON* */ // clang-format on
/*
If any of the operations are not provided then we must emulate some
of them. That means we need a nice implementation of spin locks
that avoids the "one big lock" problem. We use a vector of spin
locks and pick which one to use based on the address of the operand
of the function.
To generate the index of the lock we first shift by 3 bits to get
rid on the zero bits that result from 32 and 64 bit alignment of
data. We then mask off all but 5 bits and use those 5 bits as an
index into the table.
Picking the lock this way insures that accesses to the same data at
the same time will go to the same lock. OTOH, accesses to different
data have only a 1/32 chance of hitting the same lock. That should
pretty much eliminate the chances of several atomic operations on
different data from waiting on the same "big lock". If it isn't
then the table of locks can be expanded to a new size so long as
the new size is a power of two.
Contributed by Bob Pendleton, bob@pendleton.com
*/
#if !defined(HAVE_MSC_ATOMICS) && !defined(HAVE_GCC_ATOMICS) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_SOLARIS) && !defined(HAVE_WATCOM_ATOMICS)
#define EMULATE_CAS 1
#endif
#ifdef EMULATE_CAS
static SDL_SpinLock locks[32];
static SDL_INLINE void enterLock(void *a)
{
uintptr_t index = ((((uintptr_t)a) >> 3) & 0x1f);
SDL_LockSpinlock(&locks[index]);
}
static SDL_INLINE void leaveLock(void *a)
{
uintptr_t index = ((((uintptr_t)a) >> 3) & 0x1f);
SDL_UnlockSpinlock(&locks[index]);
}
#endif
bool SDL_CompareAndSwapAtomicInt(SDL_AtomicInt *a, int oldval, int newval)
{
#ifdef HAVE_MSC_ATOMICS
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value));
return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval;
#elif defined(HAVE_WATCOM_ATOMICS)
return _SDL_cmpxchg_watcom((volatile int *)&a->value, newval, oldval);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_bool_compare_and_swap(&a->value, oldval, newval);
#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
return OSAtomicCompareAndSwap32Barrier(oldval, newval, &a->value);
#elif defined(SDL_PLATFORM_SOLARIS)
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(uint_t) == sizeof(a->value));
return ((int)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval);
#elif defined(EMULATE_CAS)
bool result = false;
enterLock(a);
if (a->value == oldval) {
a->value = newval;
result = true;
}
leaveLock(a);
return result;
#else
#error Please define your platform.
#endif
}
bool SDL_CompareAndSwapAtomicU32(SDL_AtomicU32 *a, Uint32 oldval, Uint32 newval)
{
#ifdef HAVE_MSC_ATOMICS
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value));
return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval;
#elif defined(HAVE_WATCOM_ATOMICS)
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(int) == sizeof(a->value));
return _SDL_cmpxchg_watcom((volatile int *)&a->value, (int)newval, (int)oldval);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_bool_compare_and_swap(&a->value, oldval, newval);
#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t*)&a->value);
#elif defined(SDL_PLATFORM_SOLARIS)
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(uint_t) == sizeof(a->value));
return ((Uint32)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval);
#elif defined(EMULATE_CAS)
bool result = false;
enterLock(a);
if (a->value == oldval) {
a->value = newval;
result = true;
}
leaveLock(a);
return result;
#else
#error Please define your platform.
#endif
}
bool SDL_CompareAndSwapAtomicPointer(void **a, void *oldval, void *newval)
{
#ifdef HAVE_MSC_ATOMICS
return _InterlockedCompareExchangePointer(a, newval, oldval) == oldval;
#elif defined(HAVE_WATCOM_ATOMICS)
return _SDL_cmpxchg_watcom((int *)a, (long)newval, (long)oldval);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_bool_compare_and_swap(a, oldval, newval);
#elif defined(SDL_PLATFORM_MACOS) && defined(__LP64__) // this is deprecated in 10.12 sdk; favor gcc atomics.
return OSAtomicCompareAndSwap64Barrier((int64_t)oldval, (int64_t)newval, (int64_t *)a);
#elif defined(SDL_PLATFORM_MACOS) && !defined(__LP64__) // this is deprecated in 10.12 sdk; favor gcc atomics.
return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t *)a);
#elif defined(SDL_PLATFORM_SOLARIS)
return (atomic_cas_ptr(a, oldval, newval) == oldval);
#elif defined(EMULATE_CAS)
bool result = false;
enterLock(a);
if (*a == oldval) {
*a = newval;
result = true;
}
leaveLock(a);
return result;
#else
#error Please define your platform.
#endif
}
int SDL_SetAtomicInt(SDL_AtomicInt *a, int v)
{
#ifdef HAVE_MSC_ATOMICS
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value));
return _InterlockedExchange((long *)&a->value, v);
#elif defined(HAVE_WATCOM_ATOMICS)
return _SDL_xchg_watcom(&a->value, v);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_lock_test_and_set(&a->value, v);
#elif defined(SDL_PLATFORM_SOLARIS)
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(uint_t) == sizeof(a->value));
return (int)atomic_swap_uint((volatile uint_t *)&a->value, v);
#else
int value;
do {
value = a->value;
} while (!SDL_CompareAndSwapAtomicInt(a, value, v));
return value;
#endif
}
Uint32 SDL_SetAtomicU32(SDL_AtomicU32 *a, Uint32 v)
{
#ifdef HAVE_MSC_ATOMICS
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value));
return _InterlockedExchange((long *)&a->value, v);
#elif defined(HAVE_WATCOM_ATOMICS)
return _SDL_xchg_watcom(&a->value, v);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_lock_test_and_set(&a->value, v);
#elif defined(SDL_PLATFORM_SOLARIS)
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(uint_t) == sizeof(a->value));
return (Uint32)atomic_swap_uint((volatile uint_t *)&a->value, v);
#else
Uint32 value;
do {
value = a->value;
} while (!SDL_CompareAndSwapAtomicU32(a, value, v));
return value;
#endif
}
void *SDL_SetAtomicPointer(void **a, void *v)
{
#ifdef HAVE_MSC_ATOMICS
return _InterlockedExchangePointer(a, v);
#elif defined(HAVE_WATCOM_ATOMICS)
return (void *)_SDL_xchg_watcom((int *)a, (long)v);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_lock_test_and_set(a, v);
#elif defined(SDL_PLATFORM_SOLARIS)
return atomic_swap_ptr(a, v);
#else
void *value;
do {
value = *a;
} while (!SDL_CompareAndSwapAtomicPointer(a, value, v));
return value;
#endif
}
int SDL_AddAtomicInt(SDL_AtomicInt *a, int v)
{
#ifdef HAVE_MSC_ATOMICS
SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(long) == sizeof(a->value));
return _InterlockedExchangeAdd((long *)&a->value, v);
#elif defined(HAVE_WATCOM_ATOMICS)
SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(int) == sizeof(a->value));
return _SDL_xadd_watcom((volatile int *)&a->value, v);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_fetch_and_add(&a->value, v);
#elif defined(SDL_PLATFORM_SOLARIS)
int pv = a->value;
membar_consumer();
atomic_add_int((volatile uint_t *)&a->value, v);
return pv;
#else
int value;
do {
value = a->value;
} while (!SDL_CompareAndSwapAtomicInt(a, value, (value + v)));
return value;
#endif
}
int SDL_GetAtomicInt(SDL_AtomicInt *a)
{
#ifdef HAVE_ATOMIC_LOAD_N
return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST);
#elif defined(HAVE_MSC_ATOMICS)
SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(long) == sizeof(a->value));
return _InterlockedOr((long *)&a->value, 0);
#elif defined(HAVE_WATCOM_ATOMICS)
return _SDL_xadd_watcom(&a->value, 0);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_or_and_fetch(&a->value, 0);
#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
return sizeof(a->value) == sizeof(uint32_t) ? OSAtomicOr32Barrier(0, (volatile uint32_t *)&a->value) : OSAtomicAdd64Barrier(0, (volatile int64_t *)&a->value);
#elif defined(SDL_PLATFORM_SOLARIS)
return atomic_or_uint_nv((volatile uint_t *)&a->value, 0);
#else
int value;
do {
value = a->value;
} while (!SDL_CompareAndSwapAtomicInt(a, value, value));
return value;
#endif
}
Uint32 SDL_GetAtomicU32(SDL_AtomicU32 *a)
{
#ifdef HAVE_ATOMIC_LOAD_N
return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST);
#elif defined(HAVE_MSC_ATOMICS)
SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(long) == sizeof(a->value));
return (Uint32)_InterlockedOr((long *)&a->value, 0);
#elif defined(HAVE_WATCOM_ATOMICS)
SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(int) == sizeof(a->value));
return (Uint32)_SDL_xadd_watcom((volatile int *)&a->value, 0);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_or_and_fetch(&a->value, 0);
#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
return OSAtomicOr32Barrier(0, (volatile uint32_t *)&a->value);
#elif defined(SDL_PLATFORM_SOLARIS)
SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(uint_t) == sizeof(a->value));
return (Uint32)atomic_or_uint_nv((volatile uint_t *)&a->value, 0);
#else
Uint32 value;
do {
value = a->value;
} while (!SDL_CompareAndSwapAtomicU32(a, value, value));
return value;
#endif
}
void *SDL_GetAtomicPointer(void **a)
{
#ifdef HAVE_ATOMIC_LOAD_N
return __atomic_load_n(a, __ATOMIC_SEQ_CST);
#elif defined(HAVE_MSC_ATOMICS)
return _InterlockedCompareExchangePointer(a, NULL, NULL);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_val_compare_and_swap(a, (void *)0, (void *)0);
#elif defined(SDL_PLATFORM_SOLARIS)
return atomic_cas_ptr(a, (void *)0, (void *)0);
#else
void *value;
do {
value = *a;
} while (!SDL_CompareAndSwapAtomicPointer(a, value, value));
return value;
#endif
}
#ifdef SDL_MEMORY_BARRIER_USES_FUNCTION
#error This file should be built in arm mode so the mcr instruction is available for memory barriers
#endif
void SDL_MemoryBarrierReleaseFunction(void)
{
SDL_MemoryBarrierRelease();
}
void SDL_MemoryBarrierAcquireFunction(void)
{
SDL_MemoryBarrierAcquire();
}

View file

@ -0,0 +1,203 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(SDL_PLATFORM_WINDOWS)
#include "../core/windows/SDL_windows.h"
#endif
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_SOLARIS)
#include <atomic.h>
#endif
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_RISCOS)
#include <unixlib/local.h>
#endif
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
#include <xmmintrin.h>
#endif
#ifdef PS2
#include <kernel.h>
#endif
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_MACOS)
#include <libkern/OSAtomic.h>
#endif
/* *INDENT-OFF* */ // clang-format off
#if defined(__WATCOMC__) && defined(__386__)
SDL_COMPILE_TIME_ASSERT(locksize, 4==sizeof(SDL_SpinLock));
extern __inline int _SDL_xchg_watcom(volatile int *a, int v);
#pragma aux _SDL_xchg_watcom = \
"lock xchg [ecx], eax" \
parm [ecx] [eax] \
value [eax] \
modify exact [eax];
#endif // __WATCOMC__ && __386__
/* *INDENT-ON* */ // clang-format on
// This function is where all the magic happens...
bool SDL_TryLockSpinlock(SDL_SpinLock *lock)
{
#if defined(HAVE_GCC_ATOMICS) || defined(HAVE_GCC_SYNC_LOCK_TEST_AND_SET)
return __sync_lock_test_and_set(lock, 1) == 0;
#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64))
return _InterlockedExchange_acq(lock, 1) == 0;
#elif defined(_MSC_VER)
SDL_COMPILE_TIME_ASSERT(locksize, sizeof(*lock) == sizeof(long));
return InterlockedExchange((long *)lock, 1) == 0;
#elif defined(__WATCOMC__) && defined(__386__)
return _SDL_xchg_watcom(lock, 1) == 0;
#elif defined(__GNUC__) && defined(__arm__) && \
(defined(__ARM_ARCH_3__) || defined(__ARM_ARCH_3M__) || \
defined(__ARM_ARCH_4__) || defined(__ARM_ARCH_4T__) || \
defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5TE__) || \
defined(__ARM_ARCH_5TEJ__))
int result;
#ifdef SDL_PLATFORM_RISCOS
if (__cpucap_have_rex()) {
__asm__ __volatile__(
"ldrex %0, [%2]\nteq %0, #0\nstrexeq %0, %1, [%2]"
: "=&r"(result)
: "r"(1), "r"(lock)
: "cc", "memory");
return result == 0;
}
#endif
__asm__ __volatile__(
"swp %0, %1, [%2]\n"
: "=&r,&r"(result)
: "r,0"(1), "r,r"(lock)
: "memory");
return result == 0;
#elif defined(__GNUC__) && defined(__arm__)
int result;
__asm__ __volatile__(
"ldrex %0, [%2]\nteq %0, #0\nstrexeq %0, %1, [%2]"
: "=&r"(result)
: "r"(1), "r"(lock)
: "cc", "memory");
return result == 0;
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
int result;
__asm__ __volatile__(
"lock ; xchgl %0, (%1)\n"
: "=r"(result)
: "r"(lock), "0"(1)
: "cc", "memory");
return result == 0;
#elif defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)
// Maybe used for PowerPC, but the Intel asm or gcc atomics are favored.
return OSAtomicCompareAndSwap32Barrier(0, 1, lock);
#elif defined(SDL_PLATFORM_SOLARIS) && defined(_LP64)
// Used for Solaris with non-gcc compilers.
return ((int)atomic_cas_64((volatile uint64_t *)lock, 0, 1) == 0);
#elif defined(SDL_PLATFORM_SOLARIS) && !defined(_LP64)
// Used for Solaris with non-gcc compilers.
return ((int)atomic_cas_32((volatile uint32_t *)lock, 0, 1) == 0);
#elif defined(PS2)
uint32_t oldintr;
bool res = false;
// disable interruption
oldintr = DIntr();
if (*lock == 0) {
*lock = 1;
res = true;
}
// enable interruption
if (oldintr) {
EIntr();
}
return res;
#else
// Terrible terrible damage
static SDL_Mutex *_spinlock_mutex;
if (!_spinlock_mutex) {
// Race condition on first lock...
_spinlock_mutex = SDL_CreateMutex();
}
SDL_LockMutex(_spinlock_mutex);
if (*lock == 0) {
*lock = 1;
SDL_UnlockMutex(_spinlock_mutex);
return true;
} else {
SDL_UnlockMutex(_spinlock_mutex);
return false;
}
#endif
}
void SDL_LockSpinlock(SDL_SpinLock *lock)
{
int iterations = 0;
// FIXME: Should we have an eventual timeout?
while (!SDL_TryLockSpinlock(lock)) {
if (iterations < 32) {
iterations++;
SDL_CPUPauseInstruction();
} else {
// !!! FIXME: this doesn't definitely give up the current timeslice, it does different things on various platforms.
SDL_Delay(0);
}
}
}
void SDL_UnlockSpinlock(SDL_SpinLock *lock)
{
#if defined(HAVE_GCC_ATOMICS) || defined(HAVE_GCC_SYNC_LOCK_TEST_AND_SET)
__sync_lock_release(lock);
#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64))
_InterlockedExchange_rel(lock, 0);
#elif defined(_MSC_VER)
_ReadWriteBarrier();
*lock = 0;
#elif defined(__WATCOMC__) && defined(__386__)
SDL_CompilerBarrier();
*lock = 0;
#elif defined(SDL_PLATFORM_SOLARIS)
// Used for Solaris when not using gcc.
*lock = 0;
membar_producer();
#else
*lock = 0;
#endif
}

2534
vendor/sdl-3.2.10/src/audio/SDL_audio.c vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,27 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_audio_c_h_
#define SDL_audio_c_h_
extern void SDL_UpdateAudio(void);
#endif // SDL_audio_c_h_

File diff suppressed because it is too large Load diff

1381
vendor/sdl-3.2.10/src/audio/SDL_audiocvt.c vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,124 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// Get the name of the audio device we use for output
#if defined(SDL_AUDIO_DRIVER_NETBSD) || defined(SDL_AUDIO_DRIVER_OSS)
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h> // For close()
#include "SDL_audiodev_c.h"
#ifndef SDL_PATH_DEV_DSP
#if defined(SDL_PLATFORM_NETBSD) || defined(SDL_PLATFORM_OPENBSD)
#define SDL_PATH_DEV_DSP "/dev/audio"
#else
#define SDL_PATH_DEV_DSP "/dev/dsp"
#endif
#endif
#ifndef SDL_PATH_DEV_DSP24
#define SDL_PATH_DEV_DSP24 "/dev/sound/dsp"
#endif
#ifndef SDL_PATH_DEV_AUDIO
#define SDL_PATH_DEV_AUDIO "/dev/audio"
#endif
static void test_device(const bool recording, const char *fname, int flags, bool (*test)(int fd))
{
struct stat sb;
const int audio_fd = open(fname, flags | O_CLOEXEC, 0);
if (audio_fd >= 0) {
if ((fstat(audio_fd, &sb) == 0) && (S_ISCHR(sb.st_mode))) {
const bool okay = test(audio_fd);
close(audio_fd);
if (okay) {
static size_t dummyhandle = 0;
dummyhandle++;
SDL_assert(dummyhandle != 0);
/* Note that spec is NULL; while we are opening the device
* endpoint here, the endpoint does not provide any mix format
* information, making this information inaccessible at
* enumeration time
*/
SDL_AddAudioDevice(recording, fname, NULL, (void *)(uintptr_t)dummyhandle);
}
} else {
close(audio_fd);
}
}
}
static bool test_stub(int fd)
{
return true;
}
static void SDL_EnumUnixAudioDevices_Internal(const bool recording, const bool classic, bool (*test)(int))
{
const int flags = recording ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT;
const char *audiodev;
char audiopath[1024];
if (!test) {
test = test_stub;
}
// Figure out what our audio device is
audiodev = SDL_getenv("AUDIODEV");
if (!audiodev) {
if (classic) {
audiodev = SDL_PATH_DEV_AUDIO;
} else {
struct stat sb;
// Added support for /dev/sound/\* in Linux 2.4
if (((stat("/dev/sound", &sb) == 0) && S_ISDIR(sb.st_mode)) && ((stat(SDL_PATH_DEV_DSP24, &sb) == 0) && S_ISCHR(sb.st_mode))) {
audiodev = SDL_PATH_DEV_DSP24;
} else {
audiodev = SDL_PATH_DEV_DSP;
}
}
}
test_device(recording, audiodev, flags, test);
if (SDL_strlen(audiodev) < (sizeof(audiopath) - 3)) {
int instance = 0;
while (instance <= 64) {
(void)SDL_snprintf(audiopath, SDL_arraysize(audiopath),
"%s%d", audiodev, instance);
instance++;
test_device(recording, audiopath, flags, test);
}
}
}
void SDL_EnumUnixAudioDevices(const bool classic, bool (*test)(int))
{
SDL_EnumUnixAudioDevices_Internal(true, classic, test);
SDL_EnumUnixAudioDevices_Internal(false, classic, test);
}
#endif // Audio device selection

View file

@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_audiodev_c_h_
#define SDL_audiodev_c_h_
#include "SDL_internal.h"
#include "SDL_sysaudio.h"
// Open the audio device for playback, and don't block if busy
//#define USE_BLOCKING_WRITES
#ifdef USE_BLOCKING_WRITES
#define OPEN_FLAGS_OUTPUT O_WRONLY
#define OPEN_FLAGS_INPUT O_RDONLY
#else
#define OPEN_FLAGS_OUTPUT (O_WRONLY | O_NONBLOCK)
#define OPEN_FLAGS_INPUT (O_RDONLY | O_NONBLOCK)
#endif
extern void SDL_EnumUnixAudioDevices(const bool classic, bool (*test)(int));
#endif // SDL_audiodev_c_h_

View file

@ -0,0 +1,652 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_audioqueue.h"
#include "SDL_sysaudio.h"
typedef struct SDL_MemoryPool SDL_MemoryPool;
struct SDL_MemoryPool
{
void *free_blocks;
size_t block_size;
size_t num_free;
size_t max_free;
};
struct SDL_AudioTrack
{
SDL_AudioSpec spec;
int *chmap;
bool flushed;
SDL_AudioTrack *next;
void *userdata;
SDL_ReleaseAudioBufferCallback callback;
Uint8 *data;
size_t head;
size_t tail;
size_t capacity;
int chmap_storage[SDL_MAX_CHANNELMAP_CHANNELS]; // !!! FIXME: this needs to grow if SDL ever supports more channels. But if it grows, we should probably be more clever about allocations.
};
struct SDL_AudioQueue
{
SDL_AudioTrack *head;
SDL_AudioTrack *tail;
Uint8 *history_buffer;
size_t history_length;
size_t history_capacity;
SDL_MemoryPool track_pool;
SDL_MemoryPool chunk_pool;
};
// Allocate a new block, avoiding checking for ones already in the pool
static void *AllocNewMemoryPoolBlock(const SDL_MemoryPool *pool)
{
return SDL_malloc(pool->block_size);
}
// Allocate a new block, first checking if there are any in the pool
static void *AllocMemoryPoolBlock(SDL_MemoryPool *pool)
{
if (pool->num_free == 0) {
return AllocNewMemoryPoolBlock(pool);
}
void *block = pool->free_blocks;
pool->free_blocks = *(void **)block;
--pool->num_free;
return block;
}
// Free a block, or add it to the pool if there's room
static void FreeMemoryPoolBlock(SDL_MemoryPool *pool, void *block)
{
if (pool->num_free < pool->max_free) {
*(void **)block = pool->free_blocks;
pool->free_blocks = block;
++pool->num_free;
} else {
SDL_free(block);
}
}
// Destroy a pool and all of its blocks
static void DestroyMemoryPool(SDL_MemoryPool *pool)
{
void *block = pool->free_blocks;
pool->free_blocks = NULL;
pool->num_free = 0;
while (block) {
void *next = *(void **)block;
SDL_free(block);
block = next;
}
}
// Keeping a list of free chunks reduces memory allocations,
// But also increases the amount of work to perform when freeing the track.
static void InitMemoryPool(SDL_MemoryPool *pool, size_t block_size, size_t max_free)
{
SDL_zerop(pool);
SDL_assert(block_size >= sizeof(void *));
pool->block_size = block_size;
pool->max_free = max_free;
}
// Allocates a number of blocks and adds them to the pool
static bool ReserveMemoryPoolBlocks(SDL_MemoryPool *pool, size_t num_blocks)
{
for (; num_blocks; --num_blocks) {
void *block = AllocNewMemoryPoolBlock(pool);
if (block == NULL) {
return false;
}
*(void **)block = pool->free_blocks;
pool->free_blocks = block;
++pool->num_free;
}
return true;
}
void SDL_DestroyAudioQueue(SDL_AudioQueue *queue)
{
SDL_ClearAudioQueue(queue);
DestroyMemoryPool(&queue->track_pool);
DestroyMemoryPool(&queue->chunk_pool);
SDL_aligned_free(queue->history_buffer);
SDL_free(queue);
}
SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size)
{
SDL_AudioQueue *queue = (SDL_AudioQueue *)SDL_calloc(1, sizeof(*queue));
if (!queue) {
return NULL;
}
InitMemoryPool(&queue->track_pool, sizeof(SDL_AudioTrack), 8);
InitMemoryPool(&queue->chunk_pool, chunk_size, 4);
if (!ReserveMemoryPoolBlocks(&queue->track_pool, 2)) {
SDL_DestroyAudioQueue(queue);
return NULL;
}
return queue;
}
static void DestroyAudioTrack(SDL_AudioQueue *queue, SDL_AudioTrack *track)
{
track->callback(track->userdata, track->data, (int)track->capacity);
FreeMemoryPoolBlock(&queue->track_pool, track);
}
void SDL_ClearAudioQueue(SDL_AudioQueue *queue)
{
SDL_AudioTrack *track = queue->head;
queue->head = NULL;
queue->tail = NULL;
queue->history_length = 0;
while (track) {
SDL_AudioTrack *next = track->next;
DestroyAudioTrack(queue, track);
track = next;
}
}
static void FlushAudioTrack(SDL_AudioTrack *track)
{
track->flushed = true;
}
void SDL_FlushAudioQueue(SDL_AudioQueue *queue)
{
SDL_AudioTrack *track = queue->tail;
if (track) {
FlushAudioTrack(track);
}
}
void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
{
SDL_AudioTrack *track = queue->head;
for (;;) {
bool flushed = track->flushed;
SDL_AudioTrack *next = track->next;
DestroyAudioTrack(queue, track);
track = next;
if (flushed) {
break;
}
}
queue->head = track;
queue->history_length = 0;
if (!track) {
queue->tail = NULL;
}
}
SDL_AudioTrack *SDL_CreateAudioTrack(
SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap,
Uint8 *data, size_t len, size_t capacity,
SDL_ReleaseAudioBufferCallback callback, void *userdata)
{
SDL_AudioTrack *track = (SDL_AudioTrack *)AllocMemoryPoolBlock(&queue->track_pool);
if (!track) {
return NULL;
}
SDL_zerop(track);
if (chmap) {
SDL_assert(SDL_arraysize(track->chmap_storage) >= spec->channels);
SDL_memcpy(track->chmap_storage, chmap, sizeof (*chmap) * spec->channels);
track->chmap = track->chmap_storage;
}
SDL_copyp(&track->spec, spec);
track->userdata = userdata;
track->callback = callback;
track->data = data;
track->head = 0;
track->tail = len;
track->capacity = capacity;
return track;
}
static void SDLCALL FreeChunkedAudioBuffer(void *userdata, const void *buf, int len)
{
SDL_AudioQueue *queue = (SDL_AudioQueue *)userdata;
FreeMemoryPoolBlock(&queue->chunk_pool, (void *)buf);
}
static SDL_AudioTrack *CreateChunkedAudioTrack(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap)
{
Uint8 *chunk = (Uint8 *)AllocMemoryPoolBlock(&queue->chunk_pool);
if (!chunk) {
return NULL;
}
size_t capacity = queue->chunk_pool.block_size;
capacity -= capacity % SDL_AUDIO_FRAMESIZE(*spec);
SDL_AudioTrack *track = SDL_CreateAudioTrack(queue, spec, chmap, chunk, 0, capacity, FreeChunkedAudioBuffer, queue);
if (!track) {
FreeMemoryPoolBlock(&queue->chunk_pool, chunk);
return NULL;
}
return track;
}
void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track)
{
SDL_AudioTrack *tail = queue->tail;
if (tail) {
// If the spec has changed, make sure to flush the previous track
if (!SDL_AudioSpecsEqual(&tail->spec, &track->spec, tail->chmap, track->chmap)) {
FlushAudioTrack(tail);
}
tail->next = track;
} else {
queue->head = track;
}
queue->tail = track;
}
static size_t WriteToAudioTrack(SDL_AudioTrack *track, const Uint8 *data, size_t len)
{
if (track->flushed || track->tail >= track->capacity) {
return 0;
}
len = SDL_min(len, track->capacity - track->tail);
SDL_memcpy(&track->data[track->tail], data, len);
track->tail += len;
return len;
}
bool SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap, const Uint8 *data, size_t len)
{
if (len == 0) {
return true;
}
SDL_AudioTrack *track = queue->tail;
if (track) {
if (!SDL_AudioSpecsEqual(&track->spec, spec, track->chmap, chmap)) {
FlushAudioTrack(track);
}
} else {
SDL_assert(!queue->head);
track = CreateChunkedAudioTrack(queue, spec, chmap);
if (!track) {
return false;
}
queue->head = track;
queue->tail = track;
}
for (;;) {
const size_t written = WriteToAudioTrack(track, data, len);
data += written;
len -= written;
if (len == 0) {
break;
}
SDL_AudioTrack *new_track = CreateChunkedAudioTrack(queue, spec, chmap);
if (!new_track) {
return false;
}
track->next = new_track;
queue->tail = new_track;
track = new_track;
}
return true;
}
void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue)
{
return queue->head;
}
size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed)
{
SDL_AudioTrack *iter = (SDL_AudioTrack *)(*inout_iter);
SDL_assert(iter != NULL);
SDL_copyp(out_spec, &iter->spec);
*out_chmap = iter->chmap;
bool flushed = false;
size_t queued_bytes = 0;
while (iter) {
SDL_AudioTrack *track = iter;
iter = iter->next;
size_t avail = track->tail - track->head;
if (avail >= SDL_SIZE_MAX - queued_bytes) {
queued_bytes = SDL_SIZE_MAX;
flushed = false;
break;
}
queued_bytes += avail;
flushed = track->flushed;
if (flushed) {
break;
}
}
*inout_iter = iter;
*out_flushed = flushed;
return queued_bytes;
}
static const Uint8 *PeekIntoAudioQueuePast(SDL_AudioQueue *queue, Uint8 *data, size_t len)
{
SDL_AudioTrack *track = queue->head;
if (track->head >= len) {
return &track->data[track->head - len];
}
size_t past = len - track->head;
if (past > queue->history_length) {
return NULL;
}
SDL_memcpy(data, &queue->history_buffer[queue->history_length - past], past);
SDL_memcpy(&data[past], track->data, track->head);
return data;
}
static void UpdateAudioQueueHistory(SDL_AudioQueue *queue,
const Uint8 *data, size_t len)
{
Uint8 *history_buffer = queue->history_buffer;
size_t history_bytes = queue->history_length;
if (len >= history_bytes) {
SDL_memcpy(history_buffer, &data[len - history_bytes], history_bytes);
} else {
size_t preserve = history_bytes - len;
SDL_memmove(history_buffer, &history_buffer[len], preserve);
SDL_memcpy(&history_buffer[preserve], data, len);
}
}
static const Uint8 *ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
{
SDL_AudioTrack *track = queue->head;
if (track->tail - track->head >= len) {
const Uint8 *ptr = &track->data[track->head];
track->head += len;
return ptr;
}
size_t total = 0;
for (;;) {
size_t avail = SDL_min(len - total, track->tail - track->head);
SDL_memcpy(&data[total], &track->data[track->head], avail);
track->head += avail;
total += avail;
if (total == len) {
break;
}
if (track->flushed) {
SDL_SetError("Reading past end of flushed track");
return NULL;
}
SDL_AudioTrack *next = track->next;
if (!next) {
SDL_SetError("Reading past end of incomplete track");
return NULL;
}
UpdateAudioQueueHistory(queue, track->data, track->tail);
queue->head = next;
DestroyAudioTrack(queue, track);
track = next;
}
return data;
}
static const Uint8 *PeekIntoAudioQueueFuture(SDL_AudioQueue *queue, Uint8 *data, size_t len)
{
SDL_AudioTrack *track = queue->head;
if (track->tail - track->head >= len) {
return &track->data[track->head];
}
size_t total = 0;
for (;;) {
size_t avail = SDL_min(len - total, track->tail - track->head);
SDL_memcpy(&data[total], &track->data[track->head], avail);
total += avail;
if (total == len) {
break;
}
if (track->flushed) {
// If we have run out of data, fill the rest with silence.
SDL_memset(&data[total], SDL_GetSilenceValueForFormat(track->spec.format), len - total);
break;
}
track = track->next;
if (!track) {
SDL_SetError("Peeking past end of incomplete track");
return NULL;
}
}
return data;
}
const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
int past_frames, int present_frames, int future_frames,
Uint8 *scratch, float gain)
{
SDL_AudioTrack *track = queue->head;
if (!track) {
return NULL;
}
SDL_AudioFormat src_format = track->spec.format;
int src_channels = track->spec.channels;
const int *src_map = track->chmap;
size_t src_frame_size = SDL_AUDIO_BYTESIZE(src_format) * src_channels;
size_t dst_frame_size = SDL_AUDIO_BYTESIZE(dst_format) * dst_channels;
size_t src_past_bytes = past_frames * src_frame_size;
size_t src_present_bytes = present_frames * src_frame_size;
size_t src_future_bytes = future_frames * src_frame_size;
size_t dst_past_bytes = past_frames * dst_frame_size;
size_t dst_present_bytes = present_frames * dst_frame_size;
size_t dst_future_bytes = future_frames * dst_frame_size;
const bool convert = (src_format != dst_format) || (src_channels != dst_channels) || (gain != 1.0f);
if (convert && !dst) {
// The user didn't ask for the data to be copied, but we need to convert it, so store it in the scratch buffer
dst = scratch;
}
// Can we get all of the data straight from this track?
if ((track->head >= src_past_bytes) && ((track->tail - track->head) >= (src_present_bytes + src_future_bytes))) {
const Uint8 *ptr = &track->data[track->head - src_past_bytes];
track->head += src_present_bytes;
// Do we still need to copy/convert the data?
if (dst) {
ConvertAudio(past_frames + present_frames + future_frames, ptr,
src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
ptr = dst;
}
return ptr;
}
if (!dst) {
// The user didn't ask for the data to be copied, but we need to, so store it in the scratch buffer
dst = scratch;
} else if (!convert) {
// We are only copying, not converting, so copy straight into the dst buffer
scratch = dst;
}
Uint8 *ptr = dst;
if (src_past_bytes) {
ConvertAudio(past_frames, PeekIntoAudioQueuePast(queue, scratch, src_past_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
dst += dst_past_bytes;
scratch += dst_past_bytes;
}
if (src_present_bytes) {
ConvertAudio(present_frames, ReadFromAudioQueue(queue, scratch, src_present_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
dst += dst_present_bytes;
scratch += dst_present_bytes;
}
if (src_future_bytes) {
ConvertAudio(future_frames, PeekIntoAudioQueueFuture(queue, scratch, src_future_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
dst += dst_future_bytes;
scratch += dst_future_bytes;
}
return ptr;
}
size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue)
{
size_t total = 0;
void *iter = SDL_BeginAudioQueueIter(queue);
while (iter) {
SDL_AudioSpec src_spec;
int *src_chmap;
bool flushed;
size_t avail = SDL_NextAudioQueueIter(queue, &iter, &src_spec, &src_chmap, &flushed);
if (avail >= SDL_SIZE_MAX - total) {
total = SDL_SIZE_MAX;
break;
}
total += avail;
}
return total;
}
bool SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames)
{
SDL_AudioTrack *track = queue->head;
if (!track) {
return false;
}
size_t length = num_frames * SDL_AUDIO_FRAMESIZE(track->spec);
Uint8 *history_buffer = queue->history_buffer;
if (queue->history_capacity < length) {
history_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), length);
if (!history_buffer) {
return false;
}
SDL_aligned_free(queue->history_buffer);
queue->history_buffer = history_buffer;
queue->history_capacity = length;
}
queue->history_length = length;
SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(track->spec.format), length);
return true;
}

View file

@ -0,0 +1,79 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_audioqueue_h_
#define SDL_audioqueue_h_
// Internal functions used by SDL_AudioStream for queueing audio.
typedef void (SDLCALL *SDL_ReleaseAudioBufferCallback)(void *userdata, const void *buffer, int buflen);
typedef struct SDL_AudioQueue SDL_AudioQueue;
typedef struct SDL_AudioTrack SDL_AudioTrack;
// Create a new audio queue
extern SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size);
// Destroy an audio queue
extern void SDL_DestroyAudioQueue(SDL_AudioQueue *queue);
// Completely clear the queue
extern void SDL_ClearAudioQueue(SDL_AudioQueue *queue);
// Mark the last track as flushed
extern void SDL_FlushAudioQueue(SDL_AudioQueue *queue);
// Pop the current head track
// REQUIRES: The head track must exist, and must have been flushed
extern void SDL_PopAudioQueueHead(SDL_AudioQueue *queue);
// Write data to the end of queue
// REQUIRES: If the spec has changed, the last track must have been flushed
extern bool SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap, const Uint8 *data, size_t len);
// Create a track where the input data is owned by the caller
extern SDL_AudioTrack *SDL_CreateAudioTrack(SDL_AudioQueue *queue,
const SDL_AudioSpec *spec, const int *chmap, Uint8 *data, size_t len, size_t capacity,
SDL_ReleaseAudioBufferCallback callback, void *userdata);
// Add a track to the end of the queue
// REQUIRES: `track != NULL`
extern void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track);
// Iterate over the tracks in the queue
extern void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue);
// Query and update the track iterator
// REQUIRES: `*inout_iter != NULL` (a valid iterator)
extern size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed);
extern const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
int past_frames, int present_frames, int future_frames,
Uint8 *scratch, float gain);
// Get the total number of bytes currently queued
extern size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue);
extern bool SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames);
#endif // SDL_audioqueue_h_

View file

@ -0,0 +1,706 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_sysaudio.h"
#include "SDL_audioresample.h"
// SDL's resampler uses a "bandlimited interpolation" algorithm:
// https://ccrma.stanford.edu/~jos/resample/
// TODO: Support changing this at runtime?
#if defined(SDL_SSE_INTRINSICS) || defined(SDL_NEON_INTRINSICS)
// In <current year>, SSE is basically mandatory anyway
// We want RESAMPLER_SAMPLES_PER_FRAME to be a multiple of 4, to make SIMD easier
#define RESAMPLER_ZERO_CROSSINGS 6
#else
#define RESAMPLER_ZERO_CROSSINGS 5
#endif
#define RESAMPLER_SAMPLES_PER_FRAME (RESAMPLER_ZERO_CROSSINGS * 2)
// For a given srcpos, `srcpos + frame` are sampled, where `-RESAMPLER_ZERO_CROSSINGS < frame <= RESAMPLER_ZERO_CROSSINGS`.
// Note, when upsampling, it is also possible to start sampling from `srcpos = -1`.
#define RESAMPLER_MAX_PADDING_FRAMES (RESAMPLER_ZERO_CROSSINGS + 1)
// More bits gives more precision, at the cost of a larger table.
#define RESAMPLER_BITS_PER_ZERO_CROSSING 3
#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << RESAMPLER_BITS_PER_ZERO_CROSSING)
#define RESAMPLER_FILTER_INTERP_BITS (32 - RESAMPLER_BITS_PER_ZERO_CROSSING)
#define RESAMPLER_FILTER_INTERP_RANGE (1 << RESAMPLER_FILTER_INTERP_BITS)
// ResampleFrame is just a vector/matrix/matrix multiplication.
// It performs cubic interpolation of the filter, then multiplies that with the input.
// dst = [1, frac, frac^2, frac^3] * filter * src
// Cubic Polynomial
typedef union Cubic
{
float v[4];
#ifdef SDL_SSE_INTRINSICS
// Aligned loads can be used directly as memory operands for mul/add
__m128 v128;
#endif
#ifdef SDL_NEON_INTRINSICS
float32x4_t v128;
#endif
} Cubic;
static void ResampleFrame_Generic(const float *src, float *dst, const Cubic *filter, float frac, int chans)
{
const float frac2 = frac * frac;
const float frac3 = frac * frac2;
int i, chan;
float scales[RESAMPLER_SAMPLES_PER_FRAME];
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i, ++filter) {
scales[i] = filter->v[0] + (filter->v[1] * frac) + (filter->v[2] * frac2) + (filter->v[3] * frac3);
}
for (chan = 0; chan < chans; ++chan) {
float out = 0.0f;
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i) {
out += src[i * chans + chan] * scales[i];
}
dst[chan] = out;
}
}
static void ResampleFrame_Mono(const float *src, float *dst, const Cubic *filter, float frac, int chans)
{
const float frac2 = frac * frac;
const float frac3 = frac * frac2;
int i;
float out = 0.0f;
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i, ++filter) {
// Interpolate between the nearest two filters
const float scale = filter->v[0] + (filter->v[1] * frac) + (filter->v[2] * frac2) + (filter->v[3] * frac3);
out += src[i] * scale;
}
dst[0] = out;
}
static void ResampleFrame_Stereo(const float *src, float *dst, const Cubic *filter, float frac, int chans)
{
const float frac2 = frac * frac;
const float frac3 = frac * frac2;
int i;
float out0 = 0.0f;
float out1 = 0.0f;
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i, ++filter) {
// Interpolate between the nearest two filters
const float scale = filter->v[0] + (filter->v[1] * frac) + (filter->v[2] * frac2) + (filter->v[3] * frac3);
out0 += src[i * 2 + 0] * scale;
out1 += src[i * 2 + 1] * scale;
}
dst[0] = out0;
dst[1] = out1;
}
#ifdef SDL_SSE_INTRINSICS
#define sdl_madd_ps(a, b, c) _mm_add_ps(a, _mm_mul_ps(b, c)) // Not-so-fused multiply-add
static void SDL_TARGETING("sse") ResampleFrame_Generic_SSE(const float *src, float *dst, const Cubic *filter, float frac, int chans)
{
#if RESAMPLER_SAMPLES_PER_FRAME != 12
#error Invalid samples per frame
#endif
__m128 f0, f1, f2;
{
const __m128 frac1 = _mm_set1_ps(frac);
const __m128 frac2 = _mm_mul_ps(frac1, frac1);
const __m128 frac3 = _mm_mul_ps(frac1, frac2);
// Transposed in SetupAudioResampler
// Explicitly use _mm_load_ps to workaround ICE in GCC 4.9.4 accessing Cubic.v128
#define X(out) \
out = _mm_load_ps(filter[0].v); \
out = sdl_madd_ps(out, frac1, _mm_load_ps(filter[1].v)); \
out = sdl_madd_ps(out, frac2, _mm_load_ps(filter[2].v)); \
out = sdl_madd_ps(out, frac3, _mm_load_ps(filter[3].v)); \
filter += 4
X(f0);
X(f1);
X(f2);
#undef X
}
if (chans == 2) {
// Duplicate each of the filter elements and multiply by the input
// Use two accumulators to improve throughput
__m128 out0 = _mm_mul_ps(_mm_loadu_ps(src + 0), _mm_unpacklo_ps(f0, f0));
__m128 out1 = _mm_mul_ps(_mm_loadu_ps(src + 4), _mm_unpackhi_ps(f0, f0));
out0 = sdl_madd_ps(out0, _mm_loadu_ps(src + 8), _mm_unpacklo_ps(f1, f1));
out1 = sdl_madd_ps(out1, _mm_loadu_ps(src + 12), _mm_unpackhi_ps(f1, f1));
out0 = sdl_madd_ps(out0, _mm_loadu_ps(src + 16), _mm_unpacklo_ps(f2, f2));
out1 = sdl_madd_ps(out1, _mm_loadu_ps(src + 20), _mm_unpackhi_ps(f2, f2));
// Add the accumulators together
__m128 out = _mm_add_ps(out0, out1);
// Add the lower and upper pairs together
out = _mm_add_ps(out, _mm_movehl_ps(out, out));
// Store the result
_mm_storel_pi((__m64 *)dst, out);
return;
}
if (chans == 1) {
// Multiply the filter by the input
__m128 out = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
out = sdl_madd_ps(out, f1, _mm_loadu_ps(src + 4));
out = sdl_madd_ps(out, f2, _mm_loadu_ps(src + 8));
// Horizontal sum
__m128 shuf = _mm_shuffle_ps(out, out, _MM_SHUFFLE(2, 3, 0, 1));
out = _mm_add_ps(out, shuf);
out = _mm_add_ss(out, _mm_movehl_ps(shuf, out));
_mm_store_ss(dst, out);
return;
}
int chan = 0;
// Process 4 channels at once
for (; chan + 4 <= chans; chan += 4) {
const float *in = &src[chan];
__m128 out0 = _mm_setzero_ps();
__m128 out1 = _mm_setzero_ps();
#define X(a, b, out) \
out = sdl_madd_ps(out, _mm_loadu_ps(in), _mm_shuffle_ps(a, a, _MM_SHUFFLE(b, b, b, b))); \
in += chans
#define Y(a) \
X(a, 0, out0); \
X(a, 1, out1); \
X(a, 2, out0); \
X(a, 3, out1)
Y(f0);
Y(f1);
Y(f2);
#undef X
#undef Y
// Add the accumulators together
__m128 out = _mm_add_ps(out0, out1);
_mm_storeu_ps(&dst[chan], out);
}
// Process the remaining channels one at a time.
// Channel counts 1,2,4,8 are already handled above, leaving 3,5,6,7 to deal with (looping 3,1,2,3 times).
// Without vgatherdps (AVX2), this gets quite messy.
for (; chan < chans; ++chan) {
const float *in = &src[chan];
__m128 v0, v1, v2;
#define X(x) \
x = _mm_unpacklo_ps(_mm_load_ss(in), _mm_load_ss(in + chans)); \
in += chans + chans; \
x = _mm_movelh_ps(x, _mm_unpacklo_ps(_mm_load_ss(in), _mm_load_ss(in + chans))); \
in += chans + chans
X(v0);
X(v1);
X(v2);
#undef X
__m128 out = _mm_mul_ps(f0, v0);
out = sdl_madd_ps(out, f1, v1);
out = sdl_madd_ps(out, f2, v2);
// Horizontal sum
__m128 shuf = _mm_shuffle_ps(out, out, _MM_SHUFFLE(2, 3, 0, 1));
out = _mm_add_ps(out, shuf);
out = _mm_add_ss(out, _mm_movehl_ps(shuf, out));
_mm_store_ss(&dst[chan], out);
}
}
#undef sdl_madd_ps
#endif
#ifdef SDL_NEON_INTRINSICS
static void ResampleFrame_Generic_NEON(const float *src, float *dst, const Cubic *filter, float frac, int chans)
{
#if RESAMPLER_SAMPLES_PER_FRAME != 12
#error Invalid samples per frame
#endif
float32x4_t f0, f1, f2;
{
const float32x4_t frac1 = vdupq_n_f32(frac);
const float32x4_t frac2 = vmulq_f32(frac1, frac1);
const float32x4_t frac3 = vmulq_f32(frac1, frac2);
// Transposed in SetupAudioResampler
#define X(out) \
out = vmlaq_f32(vmlaq_f32(vmlaq_f32(filter[0].v128, filter[1].v128, frac1), filter[2].v128, frac2), filter[3].v128, frac3); \
filter += 4
X(f0);
X(f1);
X(f2);
#undef X
}
if (chans == 2) {
float32x4x2_t g0 = vzipq_f32(f0, f0);
float32x4x2_t g1 = vzipq_f32(f1, f1);
float32x4x2_t g2 = vzipq_f32(f2, f2);
// Duplicate each of the filter elements and multiply by the input
// Use two accumulators to improve throughput
float32x4_t out0 = vmulq_f32(vld1q_f32(src + 0), g0.val[0]);
float32x4_t out1 = vmulq_f32(vld1q_f32(src + 4), g0.val[1]);
out0 = vmlaq_f32(out0, vld1q_f32(src + 8), g1.val[0]);
out1 = vmlaq_f32(out1, vld1q_f32(src + 12), g1.val[1]);
out0 = vmlaq_f32(out0, vld1q_f32(src + 16), g2.val[0]);
out1 = vmlaq_f32(out1, vld1q_f32(src + 20), g2.val[1]);
// Add the accumulators together
out0 = vaddq_f32(out0, out1);
// Add the lower and upper pairs together
float32x2_t out = vadd_f32(vget_low_f32(out0), vget_high_f32(out0));
// Store the result
vst1_f32(dst, out);
return;
}
if (chans == 1) {
// Multiply the filter by the input
float32x4_t out = vmulq_f32(f0, vld1q_f32(src + 0));
out = vmlaq_f32(out, f1, vld1q_f32(src + 4));
out = vmlaq_f32(out, f2, vld1q_f32(src + 8));
// Horizontal sum
float32x2_t sum = vadd_f32(vget_low_f32(out), vget_high_f32(out));
sum = vpadd_f32(sum, sum);
vst1_lane_f32(dst, sum, 0);
return;
}
int chan = 0;
// Process 4 channels at once
for (; chan + 4 <= chans; chan += 4) {
const float *in = &src[chan];
float32x4_t out0 = vdupq_n_f32(0);
float32x4_t out1 = vdupq_n_f32(0);
#define X(a, b, out) \
out = vmlaq_f32(out, vld1q_f32(in), vdupq_lane_f32(a, b)); \
in += chans
#define Y(a) \
X(vget_low_f32(a), 0, out0); \
X(vget_low_f32(a), 1, out1); \
X(vget_high_f32(a), 0, out0); \
X(vget_high_f32(a), 1, out1)
Y(f0);
Y(f1);
Y(f2);
#undef X
#undef Y
// Add the accumulators together
float32x4_t out = vaddq_f32(out0, out1);
vst1q_f32(&dst[chan], out);
}
// Process the remaining channels one at a time.
// Channel counts 1,2,4,8 are already handled above, leaving 3,5,6,7 to deal with (looping 3,1,2,3 times).
for (; chan < chans; ++chan) {
const float *in = &src[chan];
float32x4_t v0, v1, v2;
#define X(x) \
x = vld1q_dup_f32(in); \
in += chans; \
x = vld1q_lane_f32(in, x, 1); \
in += chans; \
x = vld1q_lane_f32(in, x, 2); \
in += chans; \
x = vld1q_lane_f32(in, x, 3); \
in += chans
X(v0);
X(v1);
X(v2);
#undef X
float32x4_t out = vmulq_f32(f0, v0);
out = vmlaq_f32(out, f1, v1);
out = vmlaq_f32(out, f2, v2);
// Horizontal sum
float32x2_t sum = vadd_f32(vget_low_f32(out), vget_high_f32(out));
sum = vpadd_f32(sum, sum);
vst1_lane_f32(&dst[chan], sum, 0);
}
}
#endif
// Calculate the cubic equation which passes through all four points.
// https://en.wikipedia.org/wiki/Ordinary_least_squares
// https://en.wikipedia.org/wiki/Polynomial_regression
static void CubicLeastSquares(Cubic *coeffs, float y0, float y1, float y2, float y3)
{
// Least squares matrix for xs = [0, 1/3, 2/3, 1]
// [ 1.0 0.0 0.0 0.0 ]
// [ -5.5 9.0 -4.5 1.0 ]
// [ 9.0 -22.5 18.0 -4.5 ]
// [ -4.5 13.5 -13.5 4.5 ]
coeffs->v[0] = y0;
coeffs->v[1] = -5.5f * y0 + 9.0f * y1 - 4.5f * y2 + y3;
coeffs->v[2] = 9.0f * y0 - 22.5f * y1 + 18.0f * y2 - 4.5f * y3;
coeffs->v[3] = -4.5f * y0 + 13.5f * y1 - 13.5f * y2 + 4.5f * y3;
}
// Zeroth-order modified Bessel function of the first kind
// https://mathworld.wolfram.com/ModifiedBesselFunctionoftheFirstKind.html
static float BesselI0(float x)
{
float sum = 0.0f;
float i = 1.0f;
float t = 1.0f;
x *= x * 0.25f;
while (t >= sum * SDL_FLT_EPSILON) {
sum += t;
t *= x / (i * i);
++i;
}
return sum;
}
// Pre-calculate 180 degrees of sin(pi * x) / pi
// The speedup from this isn't huge, but it also avoids precision issues.
// If sinf isn't available, SDL_sinf just calls SDL_sin.
// Know what SDL_sin(SDL_PI_F) equals? Not quite zero.
static void SincTable(float *table, int len)
{
int i;
for (i = 0; i < len; ++i) {
table[i] = SDL_sinf(i * (SDL_PI_F / len)) / SDL_PI_F;
}
}
// Calculate Sinc(x/y), using a lookup table
static float Sinc(const float *table, int x, int y)
{
float s = table[x % y];
s = ((x / y) & 1) ? -s : s;
return (s * y) / x;
}
static Cubic ResamplerFilter[RESAMPLER_SAMPLES_PER_ZERO_CROSSING][RESAMPLER_SAMPLES_PER_FRAME];
static void GenerateResamplerFilter(void)
{
enum
{
// Generate samples at 3x the target resolution, so that we have samples at [0, 1/3, 2/3, 1] of each position
TABLE_SAMPLES_PER_ZERO_CROSSING = RESAMPLER_SAMPLES_PER_ZERO_CROSSING * 3,
TABLE_SIZE = RESAMPLER_ZERO_CROSSINGS * TABLE_SAMPLES_PER_ZERO_CROSSING,
};
// if dB > 50, beta=(0.1102 * (dB - 8.7)), according to Matlab.
const float dB = 80.0f;
const float beta = 0.1102f * (dB - 8.7f);
const float bessel_beta = BesselI0(beta);
const float lensqr = TABLE_SIZE * TABLE_SIZE;
int i, j;
float sinc[TABLE_SAMPLES_PER_ZERO_CROSSING];
SincTable(sinc, TABLE_SAMPLES_PER_ZERO_CROSSING);
// Generate one wing of the filter
// https://en.wikipedia.org/wiki/Kaiser_window
// https://en.wikipedia.org/wiki/Whittaker%E2%80%93Shannon_interpolation_formula
float filter[TABLE_SIZE + 1];
filter[0] = 1.0f;
for (i = 1; i <= TABLE_SIZE; ++i) {
float b = BesselI0(beta * SDL_sqrtf((lensqr - (i * i)) / lensqr)) / bessel_beta;
float s = Sinc(sinc, i, TABLE_SAMPLES_PER_ZERO_CROSSING);
filter[i] = b * s;
}
// Generate the coefficients for each point
// When interpolating, the fraction represents how far we are between input samples,
// so we need to align the filter by "moving" it to the right.
//
// For the left wing, this means interpolating "forwards" (away from the center)
// For the right wing, this means interpolating "backwards" (towards the center)
//
// The center of the filter is at the end of the left wing (RESAMPLER_ZERO_CROSSINGS - 1)
// The left wing is the filter, but reversed
// The right wing is the filter, but offset by 1
//
// Since the right wing is offset by 1, this just means we interpolate backwards
// between the same points, instead of forwards
// interp(p[n], p[n+1], t) = interp(p[n+1], p[n+1-1], 1 - t) = interp(p[n+1], p[n], 1 - t)
for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
for (j = 0; j < RESAMPLER_ZERO_CROSSINGS; ++j) {
const float *ys = &filter[((j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING) + i) * 3];
Cubic *fwd = &ResamplerFilter[i][RESAMPLER_ZERO_CROSSINGS - j - 1];
Cubic *rev = &ResamplerFilter[RESAMPLER_SAMPLES_PER_ZERO_CROSSING - i - 1][RESAMPLER_ZERO_CROSSINGS + j];
// Calculate the cubic equation of the 4 points
CubicLeastSquares(fwd, ys[0], ys[1], ys[2], ys[3]);
CubicLeastSquares(rev, ys[3], ys[2], ys[1], ys[0]);
}
}
}
typedef void (*ResampleFrameFunc)(const float *src, float *dst, const Cubic *filter, float frac, int chans);
static ResampleFrameFunc ResampleFrame[8];
// Transpose 4x4 floats
static void Transpose4x4(Cubic *data)
{
int i, j;
Cubic temp[4] = { data[0], data[1], data[2], data[3] };
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
data[i].v[j] = temp[j].v[i];
}
}
}
static void SetupAudioResampler(void)
{
int i, j;
bool transpose = false;
GenerateResamplerFilter();
#ifdef SDL_SSE_INTRINSICS
if (SDL_HasSSE()) {
for (i = 0; i < 8; ++i) {
ResampleFrame[i] = ResampleFrame_Generic_SSE;
}
transpose = true;
} else
#endif
#ifdef SDL_NEON_INTRINSICS
if (SDL_HasNEON()) {
for (i = 0; i < 8; ++i) {
ResampleFrame[i] = ResampleFrame_Generic_NEON;
}
transpose = true;
} else
#endif
{
for (i = 0; i < 8; ++i) {
ResampleFrame[i] = ResampleFrame_Generic;
}
ResampleFrame[0] = ResampleFrame_Mono;
ResampleFrame[1] = ResampleFrame_Stereo;
}
if (transpose) {
// Transpose each set of 4 coefficients, to reduce work when resampling
for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
for (j = 0; j + 4 <= RESAMPLER_SAMPLES_PER_FRAME; j += 4) {
Transpose4x4(&ResamplerFilter[i][j]);
}
}
}
}
void SDL_SetupAudioResampler(void)
{
static SDL_InitState init;
if (SDL_ShouldInit(&init)) {
SetupAudioResampler();
SDL_SetInitialized(&init, true);
}
}
Sint64 SDL_GetResampleRate(int src_rate, int dst_rate)
{
SDL_assert(src_rate > 0);
SDL_assert(dst_rate > 0);
Sint64 numerator = (Sint64)src_rate << 32;
Sint64 denominator = (Sint64)dst_rate;
// Generally it's expected that `dst_frames = (src_frames * dst_rate) / src_rate`
// To match this as closely as possible without infinite precision, always round up the resample rate.
// For example, without rounding up, a sample ratio of 2:3 would have `sample_rate = 0xAAAAAAAA`
// After 3 frames, the position would be 0x1.FFFFFFFE, meaning we haven't fully consumed the second input frame.
// By rounding up to 0xAAAAAAAB, we would instead reach 0x2.00000001, fulling consuming the second frame.
// Technically you could say this is kicking the can 0x100000000 steps down the road, but I'm fine with that :)
// sample_rate = div_ceil(numerator, denominator)
Sint64 sample_rate = ((numerator - 1) / denominator) + 1;
SDL_assert(sample_rate > 0);
return sample_rate;
}
int SDL_GetResamplerHistoryFrames(void)
{
// Even if we aren't currently resampling, make sure to keep enough history in case we need to later.
return RESAMPLER_MAX_PADDING_FRAMES;
}
int SDL_GetResamplerPaddingFrames(Sint64 resample_rate)
{
// This must always be <= SDL_GetResamplerHistoryFrames()
return resample_rate ? RESAMPLER_MAX_PADDING_FRAMES : 0;
}
// These are not general purpose. They do not check for all possible underflow/overflow
SDL_FORCE_INLINE bool ResamplerAdd(Sint64 a, Sint64 b, Sint64 *ret)
{
if ((b > 0) && (a > SDL_MAX_SINT64 - b)) {
return false;
}
*ret = a + b;
return true;
}
SDL_FORCE_INLINE bool ResamplerMul(Sint64 a, Sint64 b, Sint64 *ret)
{
if ((b > 0) && (a > SDL_MAX_SINT64 / b)) {
return false;
}
*ret = a * b;
return true;
}
Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset)
{
// Calculate the index of the last input frame, then add 1.
// ((((output_frames - 1) * resample_rate) + resample_offset) >> 32) + 1
Sint64 output_offset;
if (!ResamplerMul(output_frames, resample_rate, &output_offset) ||
!ResamplerAdd(output_offset, -resample_rate + resample_offset + 0x100000000, &output_offset)) {
output_offset = SDL_MAX_SINT64;
}
Sint64 input_frames = (Sint64)(Sint32)(output_offset >> 32);
input_frames = SDL_max(input_frames, 0);
return input_frames;
}
Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset)
{
Sint64 resample_offset = *inout_resample_offset;
// input_offset = (input_frames << 32) - resample_offset;
Sint64 input_offset;
if (!ResamplerMul(input_frames, 0x100000000, &input_offset) ||
!ResamplerAdd(input_offset, -resample_offset, &input_offset)) {
input_offset = SDL_MAX_SINT64;
}
// output_frames = div_ceil(input_offset, resample_rate)
Sint64 output_frames = (input_offset > 0) ? ((input_offset - 1) / resample_rate) + 1 : 0;
*inout_resample_offset = (output_frames * resample_rate) - input_offset;
return output_frames;
}
void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
Sint64 resample_rate, Sint64 *inout_resample_offset)
{
int i;
Sint64 srcpos = *inout_resample_offset;
ResampleFrameFunc resample_frame = ResampleFrame[chans - 1];
SDL_assert(resample_rate > 0);
src -= (RESAMPLER_ZERO_CROSSINGS - 1) * chans;
for (i = 0; i < outframes; ++i) {
int srcindex = (int)(Sint32)(srcpos >> 32);
Uint32 srcfraction = (Uint32)(srcpos & 0xFFFFFFFF);
srcpos += resample_rate;
SDL_assert(srcindex >= -1 && srcindex < inframes);
const Cubic *filter = ResamplerFilter[srcfraction >> RESAMPLER_FILTER_INTERP_BITS];
const float frac = (float)(srcfraction & (RESAMPLER_FILTER_INTERP_RANGE - 1)) * (1.0f / RESAMPLER_FILTER_INTERP_RANGE);
const float *frame = &src[srcindex * chans];
resample_frame(frame, dst, filter, frac, chans);
dst += chans;
}
*inout_resample_offset = srcpos - ((Sint64)inframes << 32);
}

View file

@ -0,0 +1,43 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_audioresample_h_
#define SDL_audioresample_h_
// Internal functions used by SDL_AudioStream for resampling audio.
// The resampler uses 32:32 fixed-point arithmetic to track its position.
Sint64 SDL_GetResampleRate(int src_rate, int dst_rate);
int SDL_GetResamplerHistoryFrames(void);
int SDL_GetResamplerPaddingFrames(Sint64 resample_rate);
Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset);
Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset);
// Resample some audio.
// REQUIRES: `inframes >= SDL_GetResamplerInputFrames(outframes)`
// REQUIRES: At least `SDL_GetResamplerPaddingFrames(...)` extra frames to the left of src, and right of src+inframes
void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
Sint64 resample_rate, Sint64 *inout_resample_offset);
#endif // SDL_audioresample_h_

View file

@ -0,0 +1,982 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_sysaudio.h"
#ifdef SDL_NEON_INTRINSICS
#include <fenv.h>
#endif
#define DIVBY2147483648 0.0000000004656612873077392578125f // 0x1p-31f
// start fallback scalar converters
// This code requires that floats are in the IEEE-754 binary32 format
SDL_COMPILE_TIME_ASSERT(float_bits, sizeof(float) == sizeof(Uint32));
union float_bits {
Uint32 u32;
float f32;
};
static void SDL_Convert_S8_to_F32_Scalar(float *dst, const Sint8 *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("S8", "F32");
for (i = num_samples - 1; i >= 0; --i) {
/* 1) Construct a float in the range [65536.0, 65538.0)
* 2) Shift the float range to [-1.0, 1.0) */
union float_bits x;
x.u32 = (Uint8)src[i] ^ 0x47800080u;
dst[i] = x.f32 - 65537.0f;
}
}
static void SDL_Convert_U8_to_F32_Scalar(float *dst, const Uint8 *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("U8", "F32");
for (i = num_samples - 1; i >= 0; --i) {
/* 1) Construct a float in the range [65536.0, 65538.0)
* 2) Shift the float range to [-1.0, 1.0) */
union float_bits x;
x.u32 = src[i] ^ 0x47800000u;
dst[i] = x.f32 - 65537.0f;
}
}
static void SDL_Convert_S16_to_F32_Scalar(float *dst, const Sint16 *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("S16", "F32");
for (i = num_samples - 1; i >= 0; --i) {
/* 1) Construct a float in the range [256.0, 258.0)
* 2) Shift the float range to [-1.0, 1.0) */
union float_bits x;
x.u32 = (Uint16)src[i] ^ 0x43808000u;
dst[i] = x.f32 - 257.0f;
}
}
static void SDL_Convert_S32_to_F32_Scalar(float *dst, const Sint32 *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("S32", "F32");
for (i = num_samples - 1; i >= 0; --i) {
dst[i] = (float)src[i] * DIVBY2147483648;
}
}
// Create a bit-mask based on the sign-bit. Should optimize to a single arithmetic-shift-right
#define SIGNMASK(x) (Uint32)(0u - ((Uint32)(x) >> 31))
static void SDL_Convert_F32_to_S8_Scalar(Sint8 *dst, const float *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("F32", "S8");
for (i = 0; i < num_samples; ++i) {
/* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
* 2) Shift the integer range from [0x47BFFF80, 0x47C00080] to [-128, 128]
* 3) Clamp the value to [-128, 127] */
union float_bits x;
x.f32 = src[i] + 98304.0f;
Uint32 y = x.u32 - 0x47C00000u;
Uint32 z = 0x7Fu - (y ^ SIGNMASK(y));
y = y ^ (z & SIGNMASK(z));
dst[i] = (Sint8)(y & 0xFF);
}
}
static void SDL_Convert_F32_to_U8_Scalar(Uint8 *dst, const float *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("F32", "U8");
for (i = 0; i < num_samples; ++i) {
/* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
* 2) Shift the integer range from [0x47BFFF80, 0x47C00080] to [-128, 128]
* 3) Clamp the value to [-128, 127]
* 4) Shift the integer range from [-128, 127] to [0, 255] */
union float_bits x;
x.f32 = src[i] + 98304.0f;
Uint32 y = x.u32 - 0x47C00000u;
Uint32 z = 0x7Fu - (y ^ SIGNMASK(y));
y = (y ^ 0x80u) ^ (z & SIGNMASK(z));
dst[i] = (Uint8)(y & 0xFF);
}
}
static void SDL_Convert_F32_to_S16_Scalar(Sint16 *dst, const float *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("F32", "S16");
for (i = 0; i < num_samples; ++i) {
/* 1) Shift the float range from [-1.0, 1.0] to [383.0, 385.0]
* 2) Shift the integer range from [0x43BF8000, 0x43C08000] to [-32768, 32768]
* 3) Clamp values outside the [-32768, 32767] range */
union float_bits x;
x.f32 = src[i] + 384.0f;
Uint32 y = x.u32 - 0x43C00000u;
Uint32 z = 0x7FFFu - (y ^ SIGNMASK(y));
y = y ^ (z & SIGNMASK(z));
dst[i] = (Sint16)(y & 0xFFFF);
}
}
static void SDL_Convert_F32_to_S32_Scalar(Sint32 *dst, const float *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("F32", "S32");
for (i = 0; i < num_samples; ++i) {
/* 1) Shift the float range from [-1.0, 1.0] to [-2147483648.0, 2147483648.0]
* 2) Set values outside the [-2147483648.0, 2147483647.0] range to -2147483648.0
* 3) Convert the float to an integer, and fixup values outside the valid range */
union float_bits x;
x.f32 = src[i];
Uint32 y = x.u32 + 0x0F800000u;
Uint32 z = y - 0xCF000000u;
z &= SIGNMASK(y ^ z);
x.u32 = y - z;
dst[i] = (Sint32)x.f32 ^ (Sint32)SIGNMASK(z);
}
}
#undef SIGNMASK
static void SDL_Convert_Swap16_Scalar(Uint16* dst, const Uint16* src, int num_samples)
{
int i;
for (i = 0; i < num_samples; ++i) {
dst[i] = SDL_Swap16(src[i]);
}
}
static void SDL_Convert_Swap32_Scalar(Uint32* dst, const Uint32* src, int num_samples)
{
int i;
for (i = 0; i < num_samples; ++i) {
dst[i] = SDL_Swap32(src[i]);
}
}
// end fallback scalar converters
// Convert forwards, when sizeof(*src) >= sizeof(*dst)
#define CONVERT_16_FWD(CVT1, CVT16) \
int i = 0; \
if (num_samples >= 16) { \
while ((uintptr_t)(&dst[i]) & 15) { CVT1 ++i; } \
while ((i + 16) <= num_samples) { CVT16 i += 16; } \
} \
while (i < num_samples) { CVT1 ++i; }
// Convert backwards, when sizeof(*src) <= sizeof(*dst)
#define CONVERT_16_REV(CVT1, CVT16) \
int i = num_samples; \
if (i >= 16) { \
while ((uintptr_t)(&dst[i]) & 15) { --i; CVT1 } \
while (i >= 16) { i -= 16; CVT16 } \
} \
while (i > 0) { --i; CVT1 }
#ifdef SDL_SSE2_INTRINSICS
static void SDL_TARGETING("sse2") SDL_Convert_S8_to_F32_SSE2(float *dst, const Sint8 *src, int num_samples)
{
/* 1) Flip the sign bit to convert from S8 to U8 format
* 2) Construct a float in the range [65536.0, 65538.0)
* 3) Shift the float range to [-1.0, 1.0)
* dst[i] = i2f((src[i] ^ 0x80) | 0x47800000) - 65537.0 */
const __m128i zero = _mm_setzero_si128();
const __m128i flipper = _mm_set1_epi8(-0x80);
const __m128i caster = _mm_set1_epi16(0x4780 /* 0x47800000 = f2i(65536.0) */);
const __m128 offset = _mm_set1_ps(-65537.0);
LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using SSE2)");
CONVERT_16_REV({
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint8)src[i] ^ 0x47800080u)), offset));
}, {
const __m128i bytes = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i]), flipper);
const __m128i shorts0 = _mm_unpacklo_epi8(bytes, zero);
const __m128i shorts1 = _mm_unpackhi_epi8(bytes, zero);
const __m128 floats0 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts0, caster)), offset);
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts0, caster)), offset);
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
_mm_store_ps(&dst[i], floats0);
_mm_store_ps(&dst[i + 4], floats1);
_mm_store_ps(&dst[i + 8], floats2);
_mm_store_ps(&dst[i + 12], floats3);
})
}
static void SDL_TARGETING("sse2") SDL_Convert_U8_to_F32_SSE2(float *dst, const Uint8 *src, int num_samples)
{
/* 1) Construct a float in the range [65536.0, 65538.0)
* 2) Shift the float range to [-1.0, 1.0)
* dst[i] = i2f(src[i] | 0x47800000) - 65537.0 */
const __m128i zero = _mm_setzero_si128();
const __m128i caster = _mm_set1_epi16(0x4780 /* 0x47800000 = f2i(65536.0) */);
const __m128 offset = _mm_set1_ps(-65537.0);
LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using SSE2)");
CONVERT_16_REV({
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint8)src[i] ^ 0x47800000u)), offset));
}, {
const __m128i bytes = _mm_loadu_si128((const __m128i *)&src[i]);
const __m128i shorts0 = _mm_unpacklo_epi8(bytes, zero);
const __m128i shorts1 = _mm_unpackhi_epi8(bytes, zero);
const __m128 floats0 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts0, caster)), offset);
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts0, caster)), offset);
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
_mm_store_ps(&dst[i], floats0);
_mm_store_ps(&dst[i + 4], floats1);
_mm_store_ps(&dst[i + 8], floats2);
_mm_store_ps(&dst[i + 12], floats3);
})
}
static void SDL_TARGETING("sse2") SDL_Convert_S16_to_F32_SSE2(float *dst, const Sint16 *src, int num_samples)
{
/* 1) Flip the sign bit to convert from S16 to U16 format
* 2) Construct a float in the range [256.0, 258.0)
* 3) Shift the float range to [-1.0, 1.0)
* dst[i] = i2f((src[i] ^ 0x8000) | 0x43800000) - 257.0 */
const __m128i flipper = _mm_set1_epi16(-0x8000);
const __m128i caster = _mm_set1_epi16(0x4380 /* 0x43800000 = f2i(256.0) */);
const __m128 offset = _mm_set1_ps(-257.0f);
LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using SSE2)");
CONVERT_16_REV({
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint16)src[i] ^ 0x43808000u)), offset));
}, {
const __m128i shorts0 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i]), flipper);
const __m128i shorts1 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i + 8]), flipper);
const __m128 floats0 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts0, caster)), offset);
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts0, caster)), offset);
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
_mm_store_ps(&dst[i], floats0);
_mm_store_ps(&dst[i + 4], floats1);
_mm_store_ps(&dst[i + 8], floats2);
_mm_store_ps(&dst[i + 12], floats3);
})
}
static void SDL_TARGETING("sse2") SDL_Convert_S32_to_F32_SSE2(float *dst, const Sint32 *src, int num_samples)
{
// dst[i] = f32(src[i]) / f32(0x80000000)
const __m128 scaler = _mm_set1_ps(DIVBY2147483648);
LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using SSE2)");
CONVERT_16_FWD({
_mm_store_ss(&dst[i], _mm_mul_ss(_mm_cvt_si2ss(_mm_setzero_ps(), src[i]), scaler));
}, {
const __m128i ints0 = _mm_loadu_si128((const __m128i *)&src[i]);
const __m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i + 4]);
const __m128i ints2 = _mm_loadu_si128((const __m128i *)&src[i + 8]);
const __m128i ints3 = _mm_loadu_si128((const __m128i *)&src[i + 12]);
const __m128 floats0 = _mm_mul_ps(_mm_cvtepi32_ps(ints0), scaler);
const __m128 floats1 = _mm_mul_ps(_mm_cvtepi32_ps(ints1), scaler);
const __m128 floats2 = _mm_mul_ps(_mm_cvtepi32_ps(ints2), scaler);
const __m128 floats3 = _mm_mul_ps(_mm_cvtepi32_ps(ints3), scaler);
_mm_store_ps(&dst[i], floats0);
_mm_store_ps(&dst[i + 4], floats1);
_mm_store_ps(&dst[i + 8], floats2);
_mm_store_ps(&dst[i + 12], floats3);
})
}
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S8_SSE2(Sint8 *dst, const float *src, int num_samples)
{
/* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
* 2) Extract the lowest 16 bits and clamp to [-128, 127]
* Overflow is correctly handled for inputs between roughly [-255.0, 255.0]
* dst[i] = clamp(i16(f2i(src[i] + 98304.0) & 0xFFFF), -128, 127) */
const __m128 offset = _mm_set1_ps(98304.0f);
const __m128i mask = _mm_set1_epi16(0xFF);
LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using SSE2)");
CONVERT_16_FWD({
const __m128i ints = _mm_castps_si128(_mm_add_ss(_mm_load_ss(&src[i]), offset));
dst[i] = (Sint8)(_mm_cvtsi128_si32(_mm_packs_epi16(ints, ints)) & 0xFF);
}, {
const __m128 floats0 = _mm_loadu_ps(&src[i]);
const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
const __m128i ints0 = _mm_castps_si128(_mm_add_ps(floats0, offset));
const __m128i ints1 = _mm_castps_si128(_mm_add_ps(floats1, offset));
const __m128i ints2 = _mm_castps_si128(_mm_add_ps(floats2, offset));
const __m128i ints3 = _mm_castps_si128(_mm_add_ps(floats3, offset));
const __m128i shorts0 = _mm_and_si128(_mm_packs_epi16(ints0, ints1), mask);
const __m128i shorts1 = _mm_and_si128(_mm_packs_epi16(ints2, ints3), mask);
const __m128i bytes = _mm_packus_epi16(shorts0, shorts1);
_mm_store_si128((__m128i*)&dst[i], bytes);
})
}
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_U8_SSE2(Uint8 *dst, const float *src, int num_samples)
{
/* 1) Shift the float range from [-1.0, 1.0] to [98304.0, 98306.0]
* 2) Extract the lowest 16 bits and clamp to [0, 255]
* Overflow is correctly handled for inputs between roughly [-254.0, 254.0]
* dst[i] = clamp(i16(f2i(src[i] + 98305.0) & 0xFFFF), 0, 255) */
const __m128 offset = _mm_set1_ps(98305.0f);
const __m128i mask = _mm_set1_epi16(0xFF);
LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using SSE2)");
CONVERT_16_FWD({
const __m128i ints = _mm_castps_si128(_mm_add_ss(_mm_load_ss(&src[i]), offset));
dst[i] = (Uint8)(_mm_cvtsi128_si32(_mm_packus_epi16(ints, ints)) & 0xFF);
}, {
const __m128 floats0 = _mm_loadu_ps(&src[i]);
const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
const __m128i ints0 = _mm_castps_si128(_mm_add_ps(floats0, offset));
const __m128i ints1 = _mm_castps_si128(_mm_add_ps(floats1, offset));
const __m128i ints2 = _mm_castps_si128(_mm_add_ps(floats2, offset));
const __m128i ints3 = _mm_castps_si128(_mm_add_ps(floats3, offset));
const __m128i shorts0 = _mm_and_si128(_mm_packus_epi16(ints0, ints1), mask);
const __m128i shorts1 = _mm_and_si128(_mm_packus_epi16(ints2, ints3), mask);
const __m128i bytes = _mm_packus_epi16(shorts0, shorts1);
_mm_store_si128((__m128i*)&dst[i], bytes);
})
}
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S16_SSE2(Sint16 *dst, const float *src, int num_samples)
{
/* 1) Shift the float range from [-1.0, 1.0] to [256.0, 258.0]
* 2) Shift the int range from [0x43800000, 0x43810000] to [-32768,32768]
* 3) Clamp to range [-32768,32767]
* Overflow is correctly handled for inputs between roughly [-257.0, +inf)
* dst[i] = clamp(f2i(src[i] + 257.0) - 0x43808000, -32768, 32767) */
const __m128 offset = _mm_set1_ps(257.0f);
LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using SSE2)");
CONVERT_16_FWD({
const __m128i ints = _mm_sub_epi32(_mm_castps_si128(_mm_add_ss(_mm_load_ss(&src[i]), offset)), _mm_castps_si128(offset));
dst[i] = (Sint16)(_mm_cvtsi128_si32(_mm_packs_epi32(ints, ints)) & 0xFFFF);
}, {
const __m128 floats0 = _mm_loadu_ps(&src[i]);
const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
const __m128i ints0 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats0, offset)), _mm_castps_si128(offset));
const __m128i ints1 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats1, offset)), _mm_castps_si128(offset));
const __m128i ints2 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats2, offset)), _mm_castps_si128(offset));
const __m128i ints3 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats3, offset)), _mm_castps_si128(offset));
const __m128i shorts0 = _mm_packs_epi32(ints0, ints1);
const __m128i shorts1 = _mm_packs_epi32(ints2, ints3);
_mm_store_si128((__m128i*)&dst[i], shorts0);
_mm_store_si128((__m128i*)&dst[i + 8], shorts1);
})
}
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S32_SSE2(Sint32 *dst, const float *src, int num_samples)
{
/* 1) Scale the float range from [-1.0, 1.0] to [-2147483648.0, 2147483648.0]
* 2) Convert to integer (values too small/large become 0x80000000 = -2147483648)
* 3) Fixup values which were too large (0x80000000 ^ 0xFFFFFFFF = 2147483647)
* dst[i] = i32(src[i] * 2147483648.0) ^ ((src[i] >= 2147483648.0) ? 0xFFFFFFFF : 0x00000000) */
const __m128 limit = _mm_set1_ps(2147483648.0f);
LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using SSE2)");
CONVERT_16_FWD({
const __m128 floats = _mm_load_ss(&src[i]);
const __m128 values = _mm_mul_ss(floats, limit);
const __m128i ints = _mm_xor_si128(_mm_cvttps_epi32(values), _mm_castps_si128(_mm_cmpge_ss(values, limit)));
dst[i] = (Sint32)_mm_cvtsi128_si32(ints);
}, {
const __m128 floats0 = _mm_loadu_ps(&src[i]);
const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
const __m128 values1 = _mm_mul_ps(floats0, limit);
const __m128 values2 = _mm_mul_ps(floats1, limit);
const __m128 values3 = _mm_mul_ps(floats2, limit);
const __m128 values4 = _mm_mul_ps(floats3, limit);
const __m128i ints0 = _mm_xor_si128(_mm_cvttps_epi32(values1), _mm_castps_si128(_mm_cmpge_ps(values1, limit)));
const __m128i ints1 = _mm_xor_si128(_mm_cvttps_epi32(values2), _mm_castps_si128(_mm_cmpge_ps(values2, limit)));
const __m128i ints2 = _mm_xor_si128(_mm_cvttps_epi32(values3), _mm_castps_si128(_mm_cmpge_ps(values3, limit)));
const __m128i ints3 = _mm_xor_si128(_mm_cvttps_epi32(values4), _mm_castps_si128(_mm_cmpge_ps(values4, limit)));
_mm_store_si128((__m128i*)&dst[i], ints0);
_mm_store_si128((__m128i*)&dst[i + 4], ints1);
_mm_store_si128((__m128i*)&dst[i + 8], ints2);
_mm_store_si128((__m128i*)&dst[i + 12], ints3);
})
}
#endif
// FIXME: SDL doesn't have SSSE3 detection, so use the next one up
#ifdef SDL_SSE4_1_INTRINSICS
static void SDL_TARGETING("ssse3") SDL_Convert_Swap16_SSSE3(Uint16* dst, const Uint16* src, int num_samples)
{
const __m128i shuffle = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);
CONVERT_16_FWD({
dst[i] = SDL_Swap16(src[i]);
}, {
__m128i ints0 = _mm_loadu_si128((const __m128i*)&src[i]);
__m128i ints1 = _mm_loadu_si128((const __m128i*)&src[i + 8]);
ints0 = _mm_shuffle_epi8(ints0, shuffle);
ints1 = _mm_shuffle_epi8(ints1, shuffle);
_mm_store_si128((__m128i*)&dst[i], ints0);
_mm_store_si128((__m128i*)&dst[i + 8], ints1);
})
}
static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32* dst, const Uint32* src, int num_samples)
{
const __m128i shuffle = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
CONVERT_16_FWD({
dst[i] = SDL_Swap32(src[i]);
}, {
__m128i ints0 = _mm_loadu_si128((const __m128i*)&src[i]);
__m128i ints1 = _mm_loadu_si128((const __m128i*)&src[i + 4]);
__m128i ints2 = _mm_loadu_si128((const __m128i*)&src[i + 8]);
__m128i ints3 = _mm_loadu_si128((const __m128i*)&src[i + 12]);
ints0 = _mm_shuffle_epi8(ints0, shuffle);
ints1 = _mm_shuffle_epi8(ints1, shuffle);
ints2 = _mm_shuffle_epi8(ints2, shuffle);
ints3 = _mm_shuffle_epi8(ints3, shuffle);
_mm_store_si128((__m128i*)&dst[i], ints0);
_mm_store_si128((__m128i*)&dst[i + 4], ints1);
_mm_store_si128((__m128i*)&dst[i + 8], ints2);
_mm_store_si128((__m128i*)&dst[i + 12], ints3);
})
}
#endif
#ifdef SDL_NEON_INTRINSICS
// C99 requires that all code modifying floating point environment should
// be guarded by the STDC FENV_ACCESS pragma; otherwise, it's undefined
// behavior. However, the compiler support for this pragma is bad.
#if defined(__clang__)
#if __clang_major__ >= 12
#pragma STDC FENV_ACCESS ON
#endif
#elif defined(_MSC_VER)
#pragma fenv_access (on)
#elif defined(__GNUC__)
// GCC does not support the pragma at all
#else
#pragma STDC FENV_ACCESS ON
#endif
static void SDL_Convert_S8_to_F32_NEON(float *dst, const Sint8 *src, int num_samples)
{
LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using NEON)");
fenv_t fenv;
feholdexcept(&fenv);
CONVERT_16_REV({
vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32(src[i]), 7), 0);
}, {
int8x16_t bytes = vld1q_s8(&src[i]);
int16x8_t shorts0 = vmovl_s8(vget_low_s8(bytes));
int16x8_t shorts1 = vmovl_s8(vget_high_s8(bytes));
float32x4_t floats0 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts0)), 7);
float32x4_t floats1 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts0)), 7);
float32x4_t floats2 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts1)), 7);
float32x4_t floats3 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts1)), 7);
vst1q_f32(&dst[i], floats0);
vst1q_f32(&dst[i + 4], floats1);
vst1q_f32(&dst[i + 8], floats2);
vst1q_f32(&dst[i + 12], floats3);
})
fesetenv(&fenv);
}
static void SDL_Convert_U8_to_F32_NEON(float *dst, const Uint8 *src, int num_samples)
{
LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using NEON)");
fenv_t fenv;
feholdexcept(&fenv);
uint8x16_t flipper = vdupq_n_u8(0x80);
CONVERT_16_REV({
vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32((Sint8)(src[i] ^ 0x80)), 7), 0);
}, {
int8x16_t bytes = vreinterpretq_s8_u8(veorq_u8(vld1q_u8(&src[i]), flipper));
int16x8_t shorts0 = vmovl_s8(vget_low_s8(bytes));
int16x8_t shorts1 = vmovl_s8(vget_high_s8(bytes));
float32x4_t floats0 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts0)), 7);
float32x4_t floats1 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts0)), 7);
float32x4_t floats2 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts1)), 7);
float32x4_t floats3 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts1)), 7);
vst1q_f32(&dst[i], floats0);
vst1q_f32(&dst[i + 4], floats1);
vst1q_f32(&dst[i + 8], floats2);
vst1q_f32(&dst[i + 12], floats3);
})
fesetenv(&fenv);
}
static void SDL_Convert_S16_to_F32_NEON(float *dst, const Sint16 *src, int num_samples)
{
LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using NEON)");
fenv_t fenv;
feholdexcept(&fenv);
CONVERT_16_REV({
vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32(src[i]), 15), 0);
}, {
int16x8_t shorts0 = vld1q_s16(&src[i]);
int16x8_t shorts1 = vld1q_s16(&src[i + 8]);
float32x4_t floats0 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts0)), 15);
float32x4_t floats1 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts0)), 15);
float32x4_t floats2 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts1)), 15);
float32x4_t floats3 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts1)), 15);
vst1q_f32(&dst[i], floats0);
vst1q_f32(&dst[i + 4], floats1);
vst1q_f32(&dst[i + 8], floats2);
vst1q_f32(&dst[i + 12], floats3);
})
fesetenv(&fenv);
}
static void SDL_Convert_S32_to_F32_NEON(float *dst, const Sint32 *src, int num_samples)
{
LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using NEON)");
fenv_t fenv;
feholdexcept(&fenv);
CONVERT_16_FWD({
vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vld1_dup_s32(&src[i]), 31), 0);
}, {
int32x4_t ints0 = vld1q_s32(&src[i]);
int32x4_t ints1 = vld1q_s32(&src[i + 4]);
int32x4_t ints2 = vld1q_s32(&src[i + 8]);
int32x4_t ints3 = vld1q_s32(&src[i + 12]);
float32x4_t floats0 = vcvtq_n_f32_s32(ints0, 31);
float32x4_t floats1 = vcvtq_n_f32_s32(ints1, 31);
float32x4_t floats2 = vcvtq_n_f32_s32(ints2, 31);
float32x4_t floats3 = vcvtq_n_f32_s32(ints3, 31);
vst1q_f32(&dst[i], floats0);
vst1q_f32(&dst[i + 4], floats1);
vst1q_f32(&dst[i + 8], floats2);
vst1q_f32(&dst[i + 12], floats3);
})
fesetenv(&fenv);
}
static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_samples)
{
LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using NEON)");
fenv_t fenv;
feholdexcept(&fenv);
CONVERT_16_FWD({
vst1_lane_s8(&dst[i], vreinterpret_s8_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)), 3);
}, {
float32x4_t floats0 = vld1q_f32(&src[i]);
float32x4_t floats1 = vld1q_f32(&src[i + 4]);
float32x4_t floats2 = vld1q_f32(&src[i + 8]);
float32x4_t floats3 = vld1q_f32(&src[i + 12]);
int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
int16x8_t shorts0 = vcombine_s16(vshrn_n_s32(ints0, 16), vshrn_n_s32(ints1, 16));
int16x8_t shorts1 = vcombine_s16(vshrn_n_s32(ints2, 16), vshrn_n_s32(ints3, 16));
int8x16_t bytes = vcombine_s8(vshrn_n_s16(shorts0, 8), vshrn_n_s16(shorts1, 8));
vst1q_s8(&dst[i], bytes);
})
fesetenv(&fenv);
}
static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_samples)
{
LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using NEON)");
fenv_t fenv;
feholdexcept(&fenv);
uint8x16_t flipper = vdupq_n_u8(0x80);
CONVERT_16_FWD({
vst1_lane_u8(&dst[i],
veor_u8(vreinterpret_u8_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)),
vget_low_u8(flipper)), 3);
}, {
float32x4_t floats0 = vld1q_f32(&src[i]);
float32x4_t floats1 = vld1q_f32(&src[i + 4]);
float32x4_t floats2 = vld1q_f32(&src[i + 8]);
float32x4_t floats3 = vld1q_f32(&src[i + 12]);
int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
int16x8_t shorts0 = vcombine_s16(vshrn_n_s32(ints0, 16), vshrn_n_s32(ints1, 16));
int16x8_t shorts1 = vcombine_s16(vshrn_n_s32(ints2, 16), vshrn_n_s32(ints3, 16));
uint8x16_t bytes = veorq_u8(vreinterpretq_u8_s8(
vcombine_s8(vshrn_n_s16(shorts0, 8), vshrn_n_s16(shorts1, 8))),
flipper);
vst1q_u8(&dst[i], bytes);
})
fesetenv(&fenv);
}
static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_samples)
{
LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using NEON)");
fenv_t fenv;
feholdexcept(&fenv);
CONVERT_16_FWD({
vst1_lane_s16(&dst[i], vreinterpret_s16_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)), 1);
}, {
float32x4_t floats0 = vld1q_f32(&src[i]);
float32x4_t floats1 = vld1q_f32(&src[i + 4]);
float32x4_t floats2 = vld1q_f32(&src[i + 8]);
float32x4_t floats3 = vld1q_f32(&src[i + 12]);
int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
int16x8_t shorts0 = vcombine_s16(vshrn_n_s32(ints0, 16), vshrn_n_s32(ints1, 16));
int16x8_t shorts1 = vcombine_s16(vshrn_n_s32(ints2, 16), vshrn_n_s32(ints3, 16));
vst1q_s16(&dst[i], shorts0);
vst1q_s16(&dst[i + 8], shorts1);
})
fesetenv(&fenv);
}
static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_samples)
{
LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using NEON)");
fenv_t fenv;
feholdexcept(&fenv);
CONVERT_16_FWD({
vst1_lane_s32(&dst[i], vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31), 0);
}, {
float32x4_t floats0 = vld1q_f32(&src[i]);
float32x4_t floats1 = vld1q_f32(&src[i + 4]);
float32x4_t floats2 = vld1q_f32(&src[i + 8]);
float32x4_t floats3 = vld1q_f32(&src[i + 12]);
int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
vst1q_s32(&dst[i], ints0);
vst1q_s32(&dst[i + 4], ints1);
vst1q_s32(&dst[i + 8], ints2);
vst1q_s32(&dst[i + 12], ints3);
})
fesetenv(&fenv);
}
static void SDL_Convert_Swap16_NEON(Uint16* dst, const Uint16* src, int num_samples)
{
CONVERT_16_FWD({
dst[i] = SDL_Swap16(src[i]);
}, {
uint8x16_t ints0 = vld1q_u8((const Uint8*)&src[i]);
uint8x16_t ints1 = vld1q_u8((const Uint8*)&src[i + 8]);
ints0 = vrev16q_u8(ints0);
ints1 = vrev16q_u8(ints1);
vst1q_u8((Uint8*)&dst[i], ints0);
vst1q_u8((Uint8*)&dst[i + 8], ints1);
})
}
static void SDL_Convert_Swap32_NEON(Uint32* dst, const Uint32* src, int num_samples)
{
CONVERT_16_FWD({
dst[i] = SDL_Swap32(src[i]);
}, {
uint8x16_t ints0 = vld1q_u8((const Uint8*)&src[i]);
uint8x16_t ints1 = vld1q_u8((const Uint8*)&src[i + 4]);
uint8x16_t ints2 = vld1q_u8((const Uint8*)&src[i + 8]);
uint8x16_t ints3 = vld1q_u8((const Uint8*)&src[i + 12]);
ints0 = vrev32q_u8(ints0);
ints1 = vrev32q_u8(ints1);
ints2 = vrev32q_u8(ints2);
ints3 = vrev32q_u8(ints3);
vst1q_u8((Uint8*)&dst[i], ints0);
vst1q_u8((Uint8*)&dst[i + 4], ints1);
vst1q_u8((Uint8*)&dst[i + 8], ints2);
vst1q_u8((Uint8*)&dst[i + 12], ints3);
})
}
#if defined(__clang__)
#if __clang_major__ >= 12
#pragma STDC FENV_ACCESS DEFAULT
#endif
#elif defined(_MSC_VER)
#pragma fenv_access (off)
#elif defined(__GNUC__)
//
#else
#pragma STDC FENV_ACCESS DEFAULT
#endif
#endif
#undef CONVERT_16_FWD
#undef CONVERT_16_REV
// Function pointers set to a CPU-specific implementation.
static void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples) = NULL;
static void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples) = NULL;
static void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples) = NULL;
static void (*SDL_Convert_S32_to_F32)(float *dst, const Sint32 *src, int num_samples) = NULL;
static void (*SDL_Convert_F32_to_S8)(Sint8 *dst, const float *src, int num_samples) = NULL;
static void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples) = NULL;
static void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples) = NULL;
static void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples) = NULL;
static void (*SDL_Convert_Swap16)(Uint16* dst, const Uint16* src, int num_samples) = NULL;
static void (*SDL_Convert_Swap32)(Uint32* dst, const Uint32* src, int num_samples) = NULL;
void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt)
{
switch (src_fmt) {
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_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) src, num_samples);
SDL_Convert_S16_to_F32(dst, (const Sint16 *) dst, num_samples);
break;
case SDL_AUDIO_S32:
SDL_Convert_S32_to_F32(dst, (const Sint32 *) src, num_samples);
break;
case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples);
SDL_Convert_S32_to_F32(dst, (const Sint32 *) dst, num_samples);
break;
case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples);
break;
default: SDL_assert(!"Unexpected audio format!"); break;
}
}
void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt)
{
switch (dst_fmt) {
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_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
SDL_Convert_F32_to_S16((Sint16 *) dst, src, num_samples);
SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) dst, num_samples);
break;
case SDL_AUDIO_S32:
SDL_Convert_F32_to_S32((Sint32 *) dst, src, num_samples);
break;
case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
SDL_Convert_F32_to_S32((Sint32 *) dst, src, num_samples);
SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) dst, num_samples);
break;
case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples);
break;
default: SDL_assert(!"Unexpected audio format!"); break;
}
}
void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize)
{
switch (bitsize) {
case 16: SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) src, num_samples); break;
case 32: SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples); break;
default: SDL_assert(!"Unexpected audio format!"); break;
}
}
void SDL_ChooseAudioConverters(void)
{
static bool converters_chosen = false;
if (converters_chosen) {
return;
}
#define SET_CONVERTER_FUNCS(fntype) \
SDL_Convert_Swap16 = SDL_Convert_Swap16_##fntype; \
SDL_Convert_Swap32 = SDL_Convert_Swap32_##fntype;
#ifdef SDL_SSE4_1_INTRINSICS
if (SDL_HasSSE41()) {
SET_CONVERTER_FUNCS(SSSE3);
} else
#endif
#ifdef SDL_NEON_INTRINSICS
if (SDL_HasNEON()) {
SET_CONVERTER_FUNCS(NEON);
} else
#endif
{
SET_CONVERTER_FUNCS(Scalar);
}
#undef SET_CONVERTER_FUNCS
#define SET_CONVERTER_FUNCS(fntype) \
SDL_Convert_S8_to_F32 = SDL_Convert_S8_to_F32_##fntype; \
SDL_Convert_U8_to_F32 = SDL_Convert_U8_to_F32_##fntype; \
SDL_Convert_S16_to_F32 = SDL_Convert_S16_to_F32_##fntype; \
SDL_Convert_S32_to_F32 = SDL_Convert_S32_to_F32_##fntype; \
SDL_Convert_F32_to_S8 = SDL_Convert_F32_to_S8_##fntype; \
SDL_Convert_F32_to_U8 = SDL_Convert_F32_to_U8_##fntype; \
SDL_Convert_F32_to_S16 = SDL_Convert_F32_to_S16_##fntype; \
SDL_Convert_F32_to_S32 = SDL_Convert_F32_to_S32_##fntype; \
#ifdef SDL_SSE2_INTRINSICS
if (SDL_HasSSE2()) {
SET_CONVERTER_FUNCS(SSE2);
} else
#endif
#ifdef SDL_NEON_INTRINSICS
if (SDL_HasNEON()) {
SET_CONVERTER_FUNCS(NEON);
} else
#endif
{
SET_CONVERTER_FUNCS(Scalar);
}
#undef SET_CONVERTER_FUNCS
converters_chosen = true;
}

290
vendor/sdl-3.2.10/src/audio/SDL_mixer.c vendored Normal file
View file

@ -0,0 +1,290 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// This provides the default mixing callback for the SDL audio routines
#include "SDL_sysaudio.h"
/* This table is used to add two sound values together and pin
* the value to avoid overflow. (used with permission from ARDI)
*/
static const Uint8 mix8[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24,
0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B,
0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71,
0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C,
0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92,
0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8,
0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3,
0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9,
0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4,
0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA,
0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5,
0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
// The volume ranges from 0 - 128
#define MIX_MAXVOLUME 128
#define ADJUST_VOLUME(type, s, v) ((s) = (type)(((s) * (v)) / MIX_MAXVOLUME))
#define ADJUST_VOLUME_U8(s, v) ((s) = (Uint8)(((((s) - 128) * (v)) / MIX_MAXVOLUME) + 128))
// !!! FIXME: This needs some SIMD magic.
// !!! FIXME: Add fast-path for volume = 1
// !!! FIXME: Use larger scales for 16-bit/32-bit integers
bool SDL_MixAudio(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, Uint32 len, float fvolume)
{
int volume = (int)SDL_roundf(fvolume * MIX_MAXVOLUME);
if (volume == 0) {
return true;
}
switch (format) {
case SDL_AUDIO_U8:
{
Uint8 src_sample;
while (len--) {
src_sample = *src;
ADJUST_VOLUME_U8(src_sample, volume);
*dst = mix8[*dst + src_sample];
++dst;
++src;
}
} break;
case SDL_AUDIO_S8:
{
Sint8 *dst8, *src8;
Sint8 src_sample;
int dst_sample;
const int max_audioval = SDL_MAX_SINT8;
const int min_audioval = SDL_MIN_SINT8;
src8 = (Sint8 *)src;
dst8 = (Sint8 *)dst;
while (len--) {
src_sample = *src8;
ADJUST_VOLUME(Sint8, src_sample, volume);
dst_sample = *dst8 + src_sample;
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*dst8 = (Sint8)dst_sample;
++dst8;
++src8;
}
} break;
case SDL_AUDIO_S16LE:
{
Sint16 src1, src2;
int dst_sample;
const int max_audioval = SDL_MAX_SINT16;
const int min_audioval = SDL_MIN_SINT16;
len /= 2;
while (len--) {
src1 = SDL_Swap16LE(*(Sint16 *)src);
ADJUST_VOLUME(Sint16, src1, volume);
src2 = SDL_Swap16LE(*(Sint16 *)dst);
src += 2;
dst_sample = src1 + src2;
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(Sint16 *)dst = SDL_Swap16LE((Sint16)dst_sample);
dst += 2;
}
} break;
case SDL_AUDIO_S16BE:
{
Sint16 src1, src2;
int dst_sample;
const int max_audioval = SDL_MAX_SINT16;
const int min_audioval = SDL_MIN_SINT16;
len /= 2;
while (len--) {
src1 = SDL_Swap16BE(*(Sint16 *)src);
ADJUST_VOLUME(Sint16, src1, volume);
src2 = SDL_Swap16BE(*(Sint16 *)dst);
src += 2;
dst_sample = src1 + src2;
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(Sint16 *)dst = SDL_Swap16BE((Sint16)dst_sample);
dst += 2;
}
} break;
case SDL_AUDIO_S32LE:
{
const Uint32 *src32 = (Uint32 *)src;
Uint32 *dst32 = (Uint32 *)dst;
Sint64 src1, src2;
Sint64 dst_sample;
const Sint64 max_audioval = SDL_MAX_SINT32;
const Sint64 min_audioval = SDL_MIN_SINT32;
len /= 4;
while (len--) {
src1 = (Sint64)((Sint32)SDL_Swap32LE(*src32));
src32++;
ADJUST_VOLUME(Sint64, src1, volume);
src2 = (Sint64)((Sint32)SDL_Swap32LE(*dst32));
dst_sample = src1 + src2;
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(dst32++) = SDL_Swap32LE((Uint32)((Sint32)dst_sample));
}
} break;
case SDL_AUDIO_S32BE:
{
const Uint32 *src32 = (Uint32 *)src;
Uint32 *dst32 = (Uint32 *)dst;
Sint64 src1, src2;
Sint64 dst_sample;
const Sint64 max_audioval = SDL_MAX_SINT32;
const Sint64 min_audioval = SDL_MIN_SINT32;
len /= 4;
while (len--) {
src1 = (Sint64)((Sint32)SDL_Swap32BE(*src32));
src32++;
ADJUST_VOLUME(Sint64, src1, volume);
src2 = (Sint64)((Sint32)SDL_Swap32BE(*dst32));
dst_sample = src1 + src2;
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(dst32++) = SDL_Swap32BE((Uint32)((Sint32)dst_sample));
}
} break;
case SDL_AUDIO_F32LE:
{
const float *src32 = (float *)src;
float *dst32 = (float *)dst;
float src1, src2;
float dst_sample;
const float max_audioval = 1.0f;
const float min_audioval = -1.0f;
len /= 4;
while (len--) {
src1 = SDL_SwapFloatLE(*src32) * fvolume;
src2 = SDL_SwapFloatLE(*dst32);
src32++;
dst_sample = src1 + src2;
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(dst32++) = SDL_SwapFloatLE(dst_sample);
}
} break;
case SDL_AUDIO_F32BE:
{
const float *src32 = (float *)src;
float *dst32 = (float *)dst;
float src1, src2;
float dst_sample;
const float max_audioval = 1.0f;
const float min_audioval = -1.0f;
len /= 4;
while (len--) {
src1 = SDL_SwapFloatBE(*src32) * fvolume;
src2 = SDL_SwapFloatBE(*dst32);
src32++;
dst_sample = src1 + src2;
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(dst32++) = SDL_SwapFloatBE(dst_sample);
}
} break;
default: // If this happens... FIXME!
return SDL_SetError("SDL_MixAudio(): unknown audio format");
}
return true;
}

View file

@ -0,0 +1,392 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_
#define SDL_sysaudio_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.", from, to);
#else
#define LOG_DEBUG_AUDIO_CONVERT(from, to)
#endif
// !!! FIXME: These are wordy and unlocalized...
#define DEFAULT_PLAYBACK_DEVNAME "System audio playback device"
#define DEFAULT_RECORDING_DEVNAME "System audio recording device"
// these are used when no better specifics are known. We default to CD audio quality.
#define DEFAULT_AUDIO_PLAYBACK_FORMAT SDL_AUDIO_S16
#define DEFAULT_AUDIO_PLAYBACK_CHANNELS 2
#define DEFAULT_AUDIO_PLAYBACK_FREQUENCY 44100
#define DEFAULT_AUDIO_RECORDING_FORMAT SDL_AUDIO_S16
#define DEFAULT_AUDIO_RECORDING_CHANNELS 1
#define DEFAULT_AUDIO_RECORDING_FREQUENCY 44100
#define SDL_MAX_CHANNELMAP_CHANNELS 8 // !!! FIXME: if SDL ever supports more channels, clean this out and make those parts dynamic.
typedef struct SDL_AudioDevice SDL_AudioDevice;
typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice;
// Used by src/SDL.c to initialize a particular audio driver.
extern bool 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.
extern void SDL_ChooseAudioConverters(void);
extern void SDL_SetupAudioResampler(void);
/* 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(bool recording, const char *name, const SDL_AudioSpec *spec, void *handle);
/* 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);
// 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 bool SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
// Same as above, but assume the device is already locked.
extern bool 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);
// Find an SDL_AudioDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(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);
// Backends can call this to get a reasonable default sample frame count for a device's sample rate.
int SDL_GetDefaultSampleFramesFromFreq(const int freq);
// Backends can call this to get a standardized name for a thread to power a specific audio device.
extern char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen);
// Backends can call these to change a device's refcount.
extern void RefPhysicalAudioDevice(SDL_AudioDevice *device);
extern void UnrefPhysicalAudioDevice(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_PlaybackAudioThreadSetup(SDL_AudioDevice *device);
extern bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device);
extern void SDL_PlaybackAudioThreadShutdown(SDL_AudioDevice *device);
extern void SDL_RecordingAudioThreadSetup(SDL_AudioDevice *device);
extern bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device);
extern void SDL_RecordingAudioThreadShutdown(SDL_AudioDevice *device);
extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device);
extern void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt);
extern void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt);
extern void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize);
extern bool SDL_ChannelMapIsDefault(const int *map, int channels);
extern bool SDL_ChannelMapIsBogus(const int *map, int channels);
// this gets used from the audio device threads. It has rules, don't use this if you don't know how to use it!
extern void ConvertAudio(int num_frames,
const void *src, SDL_AudioFormat src_format, int src_channels, const int *src_map,
void *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
void* scratch, float gain);
// Compare two SDL_AudioSpecs, return true if they match exactly.
// Using SDL_memcmp directly isn't safe, since potential padding might not be initialized.
// either channel map can be NULL for the default (and both should be if you don't care about them).
extern bool SDL_AudioSpecsEqual(const SDL_AudioSpec *a, const SDL_AudioSpec *b, const int *channel_map_a, const int *channel_map_b);
// See if two channel maps match
// either channel map can be NULL for the default (and both should be if you don't care about them).
extern bool SDL_AudioChannelMapsEqual(int channels, const int *channel_map_a, const int *channel_map_b);
// allocate+copy a channel map.
extern int *SDL_ChannelMapDup(const int *origchmap, int channels);
// Special case to let something in SDL_audiocvt.c access something in SDL_audio.c. Don't use this.
extern void OnAudioStreamCreated(SDL_AudioStream *stream);
extern void OnAudioStreamDestroy(SDL_AudioStream *stream);
// This just lets audio playback apply logical device gain at the same time as audiostream gain, so it's one multiplication instead of thousands.
extern int SDL_GetAudioStreamDataAdjustGain(SDL_AudioStream *stream, void *voidbuf, int len, float extra_gain);
// This is the bulk of `SDL_SetAudioStream*putChannelMap`'s work, but it lets you skip the check about changing the device end of a stream if isinput==-1.
extern bool SetAudioStreamChannelMap(SDL_AudioStream *stream, const SDL_AudioSpec *spec, int **stream_chmap, const int *chmap, int channels, int isinput);
typedef struct SDL_AudioDriverImpl
{
void (*DetectDevices)(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording);
bool (*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
bool (*WaitDevice)(SDL_AudioDevice *device);
bool (*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);
bool (*WaitRecordingDevice)(SDL_AudioDevice *device);
int (*RecordDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
void (*FlushRecording)(SDL_AudioDevice *device);
void (*CloseDevice)(SDL_AudioDevice *device);
void (*FreeDeviceHandle)(SDL_AudioDevice *device); // SDL is done with this device; free the handle from SDL_AddAudioDevice()
void (*DeinitializeStart)(void); // SDL calls this, then starts destroying objects, then calls Deinitialize. This is a good place to stop hotplug detection.
void (*Deinitialize)(void);
// Some flags to push duplicate code into the core and reduce #ifdefs.
bool ProvidesOwnCallbackThread; // !!! FIXME: rename this, it's not a callback thread anymore.
bool HasRecordingSupport;
bool OnlyHasDefaultPlaybackDevice;
bool OnlyHasDefaultRecordingDevice; // !!! FIXME: is there ever a time where you'd have a default playback and not a default recording (or vice versa)?
} SDL_AudioDriverImpl;
typedef struct SDL_PendingAudioDeviceEvent
{
Uint32 type;
SDL_AudioDeviceID devid;
struct SDL_PendingAudioDeviceEvent *next;
} SDL_PendingAudioDeviceEvent;
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_hash_lock; // A rwlock that protects `device_hash`
SDL_HashTable *device_hash; // the collection of currently-available audio devices (recording, playback, logical and physical!)
SDL_AudioStream *existing_streams; // a list of all existing SDL_AudioStreams.
SDL_AudioDeviceID default_playback_device_id;
SDL_AudioDeviceID default_recording_device_id;
SDL_PendingAudioDeviceEvent pending_events;
SDL_PendingAudioDeviceEvent *pending_events_tail;
// !!! FIXME: most (all?) of these don't have to be atomic.
SDL_AtomicInt playback_device_count;
SDL_AtomicInt recording_device_count;
SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs.
} SDL_AudioDriver;
struct SDL_AudioQueue; // forward decl.
struct SDL_AudioStream
{
SDL_Mutex* lock;
SDL_PropertiesID props;
SDL_AudioStreamCallback get_callback;
void *get_callback_userdata;
SDL_AudioStreamCallback put_callback;
void *put_callback_userdata;
SDL_AudioSpec src_spec;
SDL_AudioSpec dst_spec;
int *src_chmap;
int *dst_chmap;
float freq_ratio;
float gain;
struct SDL_AudioQueue* queue;
SDL_AudioSpec input_spec; // The spec of input data currently being processed
int *input_chmap;
int input_chmap_storage[SDL_MAX_CHANNELMAP_CHANNELS]; // !!! FIXME: this needs to grow if SDL ever supports more channels. But if it grows, we should probably be more clever about allocations.
Sint64 resample_offset;
Uint8 *work_buffer; // used for scratch space during data conversion/resampling.
size_t work_buffer_allocation;
bool simplified; // true if created via SDL_OpenAudioDeviceStream
SDL_LogicalAudioDevice *bound_device;
SDL_AudioStream *next_binding;
SDL_AudioStream *prev_binding;
SDL_AudioStream *prev; // linked list of all existing streams (so we can free them on shutdown).
SDL_AudioStream *next; // linked list of all existing streams (so we can free them on shutdown).
};
/* 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;
// Volume of the device output.
float gain;
// double-linked list of all audio streams currently bound to this opened device.
SDL_AudioStream *bound_streams;
// true if this was opened as a default device.
bool opened_as_default;
// true if device was opened with SDL_OpenAudioDeviceStream (so it forbids binding changes, etc).
bool simplified;
// If non-NULL, callback into the app that lets them access the final postmix buffer.
SDL_AudioPostmixCallback postmix;
// App-supplied pointer for postmix callback.
void *postmix_userdata;
// 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
SDL_Mutex *lock;
// A condition variable to protect device close, where we can't hold the device lock forever.
SDL_Condition *close_cond;
// Reference count of the device; logical devices, device threads, etc, add to this.
SDL_AtomicInt refcount;
// These are, initially, set from current_audio, but we might swap them out with Zombie versions on disconnect/failure.
bool (*WaitDevice)(SDL_AudioDevice *device);
bool (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen);
Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
bool (*WaitRecordingDevice)(SDL_AudioDevice *device);
int (*RecordDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
void (*FlushRecording)(SDL_AudioDevice *device);
// 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;
// The size, in bytes, of the device's playback/recording buffer.
int buffer_size;
// The device's channel map, or NULL for SDL default layout.
int *chmap;
// The device's default audio specification
SDL_AudioSpec default_spec;
// 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
int silence_value;
// non-zero if we are signaling the audio thread to end.
SDL_AtomicInt shutdown;
// non-zero if this was a disconnected device and we're waiting for it to be decommissioned.
SDL_AtomicInt zombie;
// true if this is a recording device instead of an playback device
bool recording;
// true if audio thread can skip silence/mix/convert stages and just do a basic memcpy.
bool simple_copy;
// Scratch buffers used for mixing.
Uint8 *work_buffer;
Uint8 *mix_buffer;
float *postmix_buffer;
// Size of work_buffer (and mix_buffer) in bytes.
int work_buffer_size;
// A thread to feed the audio device
SDL_Thread *thread;
// true if this physical device is currently opened by the backend.
bool currently_opened;
// Data private to this driver
struct SDL_PrivateAudioData *hidden;
// All logical devices associated with this physical device.
SDL_LogicalAudioDevice *logical_devices;
};
typedef struct AudioBootStrap
{
const char *name;
const char *desc;
bool (*init)(SDL_AudioDriverImpl *impl);
bool demand_only; // if true: request explicitly, or it won't be available.
bool is_preferred;
} AudioBootStrap;
// Not all of these are available in a given build. Use #ifdefs, etc.
extern AudioBootStrap PRIVATEAUDIO_bootstrap;
extern AudioBootStrap PIPEWIRE_PREFERRED_bootstrap;
extern AudioBootStrap PIPEWIRE_bootstrap;
extern AudioBootStrap PULSEAUDIO_bootstrap;
extern AudioBootStrap ALSA_bootstrap;
extern AudioBootStrap JACK_bootstrap;
extern AudioBootStrap SNDIO_bootstrap;
extern AudioBootStrap NETBSDAUDIO_bootstrap;
extern AudioBootStrap DSP_bootstrap;
extern AudioBootStrap WASAPI_bootstrap;
extern AudioBootStrap DSOUND_bootstrap;
extern AudioBootStrap WINMM_bootstrap;
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 PS2AUDIO_bootstrap;
extern AudioBootStrap PSPAUDIO_bootstrap;
extern AudioBootStrap VITAAUD_bootstrap;
extern AudioBootStrap N3DSAUDIO_bootstrap;
extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
extern AudioBootStrap QSAAUDIO_bootstrap;
#endif // SDL_sysaudio_h_

2151
vendor/sdl-3.2.10/src/audio/SDL_wave.c vendored Normal file

File diff suppressed because it is too large Load diff

151
vendor/sdl-3.2.10/src/audio/SDL_wave.h vendored Normal file
View file

@ -0,0 +1,151 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// RIFF WAVE files are little-endian
/*******************************************/
// Define values for Microsoft WAVE format
/*******************************************/
// FOURCC
#define RIFF 0x46464952 // "RIFF"
#define WAVE 0x45564157 // "WAVE"
#define FACT 0x74636166 // "fact"
#define LIST 0x5453494c // "LIST"
#define BEXT 0x74786562 // "bext"
#define JUNK 0x4B4E554A // "JUNK"
#define FMT 0x20746D66 // "fmt "
#define DATA 0x61746164 // "data"
// Format tags
#define UNKNOWN_CODE 0x0000
#define PCM_CODE 0x0001
#define MS_ADPCM_CODE 0x0002
#define IEEE_FLOAT_CODE 0x0003
#define ALAW_CODE 0x0006
#define MULAW_CODE 0x0007
#define IMA_ADPCM_CODE 0x0011
#define MPEG_CODE 0x0050
#define MPEGLAYER3_CODE 0x0055
#define EXTENSIBLE_CODE 0xFFFE
// Stores the WAVE format information.
typedef struct WaveFormat
{
Uint16 formattag; // Raw value of the first field in the fmt chunk data.
Uint16 encoding; // Actual encoding, possibly from the extensible header.
Uint16 channels; // Number of channels.
Uint32 frequency; // Sampling rate in Hz.
Uint32 byterate; // Average bytes per second.
Uint16 blockalign; // Bytes per block.
Uint16 bitspersample; // Currently supported are 8, 16, 24, 32, and 4 for ADPCM.
/* Extra information size. Number of extra bytes starting at byte 18 in the
* fmt chunk data. This is at least 22 for the extensible header.
*/
Uint16 extsize;
// Extensible WAVE header fields
Uint16 validsamplebits;
Uint32 samplesperblock; // For compressed formats. Can be zero. Actually 16 bits in the header.
Uint32 channelmask;
Uint8 subformat[16]; // A format GUID.
} WaveFormat;
// Stores information on the fact chunk.
typedef struct WaveFact
{
/* Represents the state of the fact chunk in the WAVE file.
* Set to -1 if the fact chunk is invalid.
* Set to 0 if the fact chunk is not present
* Set to 1 if the fact chunk is present and valid.
* Set to 2 if samplelength is going to be used as the number of sample frames.
*/
Sint32 status;
/* Version 1 of the RIFF specification calls the field in the fact chunk
* dwFileSize. The Standards Update then calls it dwSampleLength and specifies
* that it is 'the length of the data in samples'. WAVE files from Windows
* with this chunk have it set to the samples per channel (sample frames).
* This is useful to truncate compressed audio to a specific sample count
* because a compressed block is usually decoded to a fixed number of
* sample frames.
*/
Uint32 samplelength; // Raw sample length value from the fact chunk.
} WaveFact;
// Generic struct for the chunks in the WAVE file.
typedef struct WaveChunk
{
Uint32 fourcc; // FOURCC of the chunk.
Uint32 length; // Size of the chunk data.
Sint64 position; // Position of the data in the stream.
Uint8 *data; // When allocated, this points to the chunk data. length is used for the memory allocation size.
size_t size; // Number of bytes in data that could be read from the stream. Can be smaller than length.
} WaveChunk;
// Controls how the size of the RIFF chunk affects the loading of a WAVE file.
typedef enum WaveRiffSizeHint
{
RiffSizeNoHint,
RiffSizeForce,
RiffSizeIgnoreZero,
RiffSizeIgnore,
RiffSizeMaximum
} WaveRiffSizeHint;
// Controls how a truncated WAVE file is handled.
typedef enum WaveTruncationHint
{
TruncNoHint,
TruncVeryStrict,
TruncStrict,
TruncDropFrame,
TruncDropBlock
} WaveTruncationHint;
// Controls how the fact chunk affects the loading of a WAVE file.
typedef enum WaveFactChunkHint
{
FactNoHint,
FactTruncate,
FactStrict,
FactIgnoreZero,
FactIgnore
} WaveFactChunkHint;
typedef struct WaveFile
{
WaveChunk chunk;
WaveFormat format;
WaveFact fact;
/* Number of sample frames that will be decoded. Calculated either with the
* size of the data chunk or, if the appropriate hint is enabled, with the
* sample length value from the fact chunk.
*/
Sint64 sampleframes;
void *decoderdata; // Some decoders require extra data for a state.
WaveRiffSizeHint riffhint;
WaveTruncationHint trunchint;
WaveFactChunkHint facthint;
} WaveFile;

View file

@ -0,0 +1,551 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_AAUDIO
#include "../SDL_sysaudio.h"
#include "SDL_aaudio.h"
#include "../../core/android/SDL_android.h"
#include <aaudio/AAudio.h>
#if __ANDROID_API__ < 31
#define AAUDIO_FORMAT_PCM_I32 4
#endif
struct SDL_PrivateAudioData
{
AAudioStream *stream;
int num_buffers;
Uint8 *mixbuf; // Raw mixing buffer
size_t mixbuf_bytes; // num_buffers * device->buffer_size
size_t callback_bytes;
size_t processed_bytes;
SDL_Semaphore *semaphore;
SDL_AtomicInt error_callback_triggered;
};
// Debug
#if 0
#define LOGI(...) SDL_Log(__VA_ARGS__);
#else
#define LOGI(...)
#endif
#define LIB_AAUDIO_SO "libaaudio.so"
typedef struct AAUDIO_Data
{
SDL_SharedObject *handle;
#define SDL_PROC(ret, func, params) ret (*func) params;
#include "SDL_aaudiofuncs.h"
} AAUDIO_Data;
static AAUDIO_Data ctx;
static bool AAUDIO_LoadFunctions(AAUDIO_Data *data)
{
#define SDL_PROC(ret, func, params) \
do { \
data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \
if (!data->func) { \
return SDL_SetError("Couldn't load AAUDIO function %s: %s", #func, SDL_GetError()); \
} \
} while (0);
#include "SDL_aaudiofuncs.h"
return true;
}
static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error)
{
LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error));
// 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 PlayDevice instead.
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
SDL_SetAtomicInt(&device->hidden->error_callback_triggered, (int) error); // AAUDIO_OK is zero, so !triggered means no error.
SDL_SignalSemaphore(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)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
struct SDL_PrivateAudioData *hidden = device->hidden;
size_t framesize = SDL_AUDIO_FRAMESIZE(device->spec);
size_t callback_bytes = numFrames * framesize;
size_t old_buffer_index = hidden->callback_bytes / device->buffer_size;
if (device->recording) {
const Uint8 *input = (const Uint8 *)audioData;
size_t available_bytes = hidden->mixbuf_bytes - (hidden->callback_bytes - hidden->processed_bytes);
size_t size = SDL_min(available_bytes, callback_bytes);
size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
size_t end = (offset + size) % hidden->mixbuf_bytes;
SDL_assert(size <= hidden->mixbuf_bytes);
//LOGI("Recorded %zu frames, %zu available, %zu max (%zu written, %zu read)", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->callback_bytes / framesize, hidden->processed_bytes / framesize);
if (offset <= end) {
SDL_memcpy(&hidden->mixbuf[offset], input, size);
} else {
size_t partial = (hidden->mixbuf_bytes - offset);
SDL_memcpy(&hidden->mixbuf[offset], &input[0], partial);
SDL_memcpy(&hidden->mixbuf[0], &input[partial], end);
}
SDL_MemoryBarrierRelease();
hidden->callback_bytes += size;
if (size < callback_bytes) {
LOGI("Audio recording overflow, dropped %zu frames", (callback_bytes - size) / framesize);
}
} else {
Uint8 *output = (Uint8 *)audioData;
size_t available_bytes = (hidden->processed_bytes - hidden->callback_bytes);
size_t size = SDL_min(available_bytes, callback_bytes);
size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
size_t end = (offset + size) % hidden->mixbuf_bytes;
SDL_assert(size <= hidden->mixbuf_bytes);
//LOGI("Playing %zu frames, %zu available, %zu max (%zu written, %zu read)", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->processed_bytes / framesize, hidden->callback_bytes / framesize);
SDL_MemoryBarrierAcquire();
if (offset <= end) {
SDL_memcpy(output, &hidden->mixbuf[offset], size);
} else {
size_t partial = (hidden->mixbuf_bytes - offset);
SDL_memcpy(&output[0], &hidden->mixbuf[offset], partial);
SDL_memcpy(&output[partial], &hidden->mixbuf[0], end);
}
hidden->callback_bytes += size;
if (size < callback_bytes) {
LOGI("Audio playback underflow, missed %zu frames", (callback_bytes - size) / framesize);
SDL_memset(&output[size], device->silence_value, (callback_bytes - size));
}
}
size_t new_buffer_index = hidden->callback_bytes / device->buffer_size;
while (old_buffer_index < new_buffer_index) {
// Trigger audio processing
SDL_SignalSemaphore(hidden->semaphore);
++old_buffer_index;
}
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
return &hidden->mixbuf[offset];
}
static bool AAUDIO_WaitDevice(SDL_AudioDevice *device)
{
while (!SDL_GetAtomicInt(&device->shutdown)) {
// this semaphore won't fire when the app is in the background (AAUDIO_PauseDevices was called).
if (SDL_WaitSemaphoreTimeout(device->hidden->semaphore, 100)) {
return true; // semaphore was signaled, let's go!
}
// Still waiting on the semaphore (or the system), check other things then wait again.
}
return true;
}
static bool BuildAAudioStream(SDL_AudioDevice *device);
static bool RecoverAAudioDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
// attempt to build a new stream, in case there's a new default device.
ctx.AAudioStream_requestStop(hidden->stream);
ctx.AAudioStream_close(hidden->stream);
hidden->stream = NULL;
SDL_aligned_free(hidden->mixbuf);
hidden->mixbuf = NULL;
SDL_DestroySemaphore(hidden->semaphore);
hidden->semaphore = NULL;
const int prev_sample_frames = device->sample_frames;
SDL_AudioSpec prevspec;
SDL_copyp(&prevspec, &device->spec);
if (!BuildAAudioStream(device)) {
return false; // oh well, we tried.
}
// we don't know the new device spec until we open the new device, so we saved off the old one and force it back
// so SDL_AudioDeviceFormatChanged can set up all the important state if necessary and then set it back to the new spec.
const int new_sample_frames = device->sample_frames;
SDL_AudioSpec newspec;
SDL_copyp(&newspec, &device->spec);
device->sample_frames = prev_sample_frames;
SDL_copyp(&device->spec, &prevspec);
if (!SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames)) {
return false; // ugh
}
return true;
}
static bool AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
// AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
const aaudio_result_t err = (aaudio_result_t) SDL_GetAtomicInt(&hidden->error_callback_triggered);
if (err) {
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "aaudio: Audio device triggered error %d (%s)", (int) err, ctx.AAudio_convertResultToText(err));
if (!RecoverAAudioDevice(device)) {
return false; // oh well, we went down hard.
}
} else {
SDL_MemoryBarrierRelease();
hidden->processed_bytes += buflen;
}
return true;
}
static int AAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
// AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
if (SDL_GetAtomicInt(&hidden->error_callback_triggered)) {
SDL_SetAtomicInt(&hidden->error_callback_triggered, 0);
return -1;
}
SDL_assert(buflen == device->buffer_size); // If this isn't true, we need to change semaphore trigger logic and account for wrapping copies here
size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
SDL_MemoryBarrierAcquire();
SDL_memcpy(buffer, &hidden->mixbuf[offset], buflen);
hidden->processed_bytes += buflen;
return buflen;
}
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_aligned_free(hidden->mixbuf);
SDL_free(hidden);
device->hidden = NULL;
}
}
static bool BuildAAudioStream(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
const bool recording = device->recording;
aaudio_result_t res;
SDL_SetAtomicInt(&hidden->error_callback_triggered, 0);
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) {
LOGI("SDL Failed AAudio_createStreamBuilder - builder NULL");
return SDL_SetError("SDL Failed AAudio_createStreamBuilder - builder NULL");
}
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
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);
#endif
aaudio_format_t format;
if ((device->spec.format == SDL_AUDIO_S32) && (SDL_GetAndroidSDKVersion() >= 31)) {
format = AAUDIO_FORMAT_PCM_I32;
} else if (device->spec.format == SDL_AUDIO_F32) {
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_setSampleRate(builder, device->spec.freq);
ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels);
const aaudio_direction_t direction = (recording ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT);
ctx.AAudioStreamBuilder_setDirection(builder, direction);
ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, device);
ctx.AAudioStreamBuilder_setDataCallback(builder, AAUDIO_dataCallback, device);
// Some devices have flat sounding audio when low latency mode is enabled, but this is a better experience for most people
if (SDL_GetHintBoolean(SDL_HINT_ANDROID_LOW_LATENCY_AUDIO, true)) {
SDL_Log("Low latency audio enabled");
ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
} else {
SDL_Log("Low latency audio disabled");
}
LOGI("AAudio Try to open %u hz %s %u channels samples %u",
device->spec.freq, SDL_GetAudioFormatName(device->spec.format),
device->spec.channels, device->sample_frames);
res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream);
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));
}
ctx.AAudioStreamBuilder_delete(builder);
device->sample_frames = (int)ctx.AAudioStream_getFramesPerDataCallback(hidden->stream);
if (device->sample_frames == AAUDIO_UNSPECIFIED) {
// We'll get variable frames in the callback, make sure we have at least half a buffer available
device->sample_frames = (int)ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 2;
}
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_S16;
} else if (format == AAUDIO_FORMAT_PCM_I32) {
device->spec.format = SDL_AUDIO_S32;
} else if (format == AAUDIO_FORMAT_PCM_FLOAT) {
device->spec.format = SDL_AUDIO_F32;
} else {
return SDL_SetError("Got unexpected audio format %d from AAudioStream_getFormat", (int) format);
}
SDL_UpdatedAudioDeviceFormat(device);
// Allocate a triple buffered mixing buffer
// Two buffers can be in the process of being filled while the third is being read
hidden->num_buffers = 3;
hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size);
hidden->mixbuf = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), hidden->mixbuf_bytes);
if (!hidden->mixbuf) {
return false;
}
hidden->processed_bytes = 0;
hidden->callback_bytes = 0;
hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers);
if (!hidden->semaphore) {
LOGI("SDL Failed SDL_CreateSemaphore %s recording:%d", SDL_GetError(), recording);
return false;
}
LOGI("AAudio Actually opened %u hz %s %u channels samples %u, buffers %d",
device->spec.freq, SDL_GetAudioFormatName(device->spec.format),
device->spec.channels, device->sample_frames, hidden->num_buffers);
res = ctx.AAudioStream_requestStart(hidden->stream);
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStream_requestStart %d recording:%d", res, recording);
return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
}
LOGI("SDL AAudioStream_requestStart OK");
return true;
}
// !!! FIXME: make this non-blocking!
static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted)
{
SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1);
}
static bool AAUDIO_OpenDevice(SDL_AudioDevice *device)
{
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
SDL_assert(device->handle); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero.
#endif
LOGI(__func__);
if (device->recording) {
// !!! FIXME: make this non-blocking!
SDL_AtomicInt permission_response;
SDL_SetAtomicInt(&permission_response, 0);
if (!SDL_RequestAndroidPermission("android.permission.RECORD_AUDIO", RequestAndroidPermissionBlockingCallback, &permission_response)) {
return false;
}
while (SDL_GetAtomicInt(&permission_response) == 0) {
SDL_Delay(10);
}
if (SDL_GetAtomicInt(&permission_response) < 0) {
LOGI("This app doesn't have RECORD_AUDIO permission");
return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
}
}
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
return BuildAAudioStream(device);
}
static bool PauseOneDevice(SDL_AudioDevice *device, void *userdata)
{
struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden;
if (hidden) {
if (hidden->stream) {
aaudio_result_t res;
if (device->recording) {
// Pause() isn't implemented for recording, 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));
}
}
}
return false; // keep enumerating.
}
// Pause (block) all non already paused audio devices by taking their mixer lock
void AAUDIO_PauseDevices(void)
{
if (ctx.handle) { // AAUDIO driver is used?
(void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneDevice, NULL);
}
}
// Resume (unblock) all non already paused audio devices by releasing their mixer lock
static bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
if (hidden) {
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 false; // keep enumerating.
}
void AAUDIO_ResumeDevices(void)
{
if (ctx.handle) { // AAUDIO driver is used?
(void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneDevice, NULL);
}
}
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 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 false;
}
SDL_zero(ctx);
ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO);
if (!ctx.handle) {
LOGI("SDL couldn't find " LIB_AAUDIO_SO);
return false;
}
if (!AAUDIO_LoadFunctions(&ctx)) {
SDL_UnloadObject(ctx.handle);
SDL_zero(ctx);
return false;
}
impl->ThreadInit = Android_AudioThreadInit;
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->WaitRecordingDevice = AAUDIO_WaitDevice;
impl->RecordDevice = AAUDIO_RecordDevice;
impl->HasRecordingSupport = true;
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
impl->DetectDevices = Android_StartAudioHotplug;
#else
impl->OnlyHasDefaultPlaybackDevice = true;
impl->OnlyHasDefaultRecordingDevice = true;
#endif
LOGI("SDL AAUDIO_Init OK");
return true;
}
AudioBootStrap AAUDIO_bootstrap = {
"AAudio", "AAudio audio driver", AAUDIO_Init, false, false
};
#endif // SDL_AUDIO_DRIVER_AAUDIO

View file

@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_aaudio_h_
#define SDL_aaudio_h_
#ifdef SDL_AUDIO_DRIVER_AAUDIO
extern void AAUDIO_ResumeDevices(void);
extern void AAUDIO_PauseDevices(void);
#else
#define AAUDIO_ResumeDevices()
#define AAUDIO_PauseDevices()
#endif
#endif // SDL_aaudio_h_

View file

@ -0,0 +1,82 @@
/*
Simple DirectMedia Layer
Copyright , (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#define SDL_PROC_UNUSED(ret, func, params)
SDL_PROC(const char *, AAudio_convertResultToText, (aaudio_result_t returnCode))
SDL_PROC(const char *, AAudio_convertStreamStateToText, (aaudio_stream_state_t state))
SDL_PROC(aaudio_result_t, AAudio_createStreamBuilder, (AAudioStreamBuilder * *builder))
SDL_PROC(void, AAudioStreamBuilder_setDeviceId, (AAudioStreamBuilder * builder, int32_t deviceId))
SDL_PROC(void, AAudioStreamBuilder_setSampleRate, (AAudioStreamBuilder * builder, int32_t sampleRate))
SDL_PROC(void, AAudioStreamBuilder_setChannelCount, (AAudioStreamBuilder * builder, int32_t channelCount))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSamplesPerFrame, (AAudioStreamBuilder * builder, int32_t samplesPerFrame))
SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aaudio_format_t format))
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(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(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))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_release, (AAudioStream * stream)) // API 30
SDL_PROC(aaudio_result_t, AAudioStream_close, (AAudioStream * stream))
SDL_PROC(aaudio_result_t, AAudioStream_requestStart, (AAudioStream * stream))
SDL_PROC(aaudio_result_t, AAudioStream_requestPause, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_requestFlush, (AAudioStream * stream))
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_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_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(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))
SDL_PROC_UNUSED(int32_t, AAudioStream_getDeviceId, (AAudioStream * stream))
SDL_PROC(aaudio_format_t, AAudioStream_getFormat, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_sharing_mode_t, AAudioStream_getSharingMode, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_performance_mode_t, AAudioStream_getPerformanceMode, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_direction_t, AAudioStream_getDirection, (AAudioStream * stream))
SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesWritten, (AAudioStream * stream))
SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesRead, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_session_id_t, AAudioStream_getSessionId, (AAudioStream * stream)) // API 28
SDL_PROC(aaudio_result_t, AAudioStream_getTimestamp, (AAudioStream * stream, clockid_t clockid, int64_t *framePosition, int64_t *timeNanoseconds))
SDL_PROC_UNUSED(aaudio_usage_t, AAudioStream_getUsage, (AAudioStream * stream)) // API 28
SDL_PROC_UNUSED(aaudio_content_type_t, AAudioStream_getContentType, (AAudioStream * stream)) // API 28
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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_ALSA_audio_h_
#define SDL_ALSA_audio_h_
#include <alsa/asoundlib.h>
#include "../SDL_sysaudio.h"
#define SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX 8
#define SDL_AUDIO_ALSA__SDL_CHMAPS_N 9 // from 0 channels to 8 channels
struct SDL_PrivateAudioData
{
// The audio device handle
snd_pcm_t *pcm;
// Raw mixing buffer
Uint8 *mixbuf;
};
#endif // SDL_ALSA_audio_h_

View file

@ -0,0 +1,68 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_coreaudio_h_
#define SDL_coreaudio_h_
#include "../SDL_sysaudio.h"
#ifndef SDL_PLATFORM_IOS
#define MACOSX_COREAUDIO
#endif
#ifdef MACOSX_COREAUDIO
#include <CoreAudio/CoreAudio.h>
#else
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIApplication.h>
#endif
#include <AudioToolbox/AudioToolbox.h>
#include <AudioUnit/AudioUnit.h>
// Things named "Master" were renamed to "Main" in macOS 12.0's SDK.
#ifdef MACOSX_COREAUDIO
#include <AvailabilityMacros.h>
#ifndef MAC_OS_VERSION_12_0
#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster
#endif
#endif
struct SDL_PrivateAudioData
{
SDL_Thread *thread;
AudioQueueRef audioQueue;
int numAudioBuffers;
AudioQueueBufferRef *audioBuffer;
AudioQueueBufferRef current_buffer;
AudioStreamBasicDescription strdesc;
SDL_Semaphore *ready_semaphore;
char *thread_error;
#ifdef MACOSX_COREAUDIO
AudioDeviceID deviceID;
#else
bool interrupted;
CFTypeRef interruption_listener;
#endif
};
#endif // SDL_coreaudio_h_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,680 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_DSOUND
#include "../SDL_sysaudio.h"
#include "SDL_directsound.h"
#include <mmreg.h>
#ifdef HAVE_MMDEVICEAPI_H
#include "../../core/windows/SDL_immdevice.h"
#endif
#ifndef WAVE_FORMAT_IEEE_FLOAT
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
#endif
// For Vista+, we can enumerate DSound devices with IMMDevice
#ifdef HAVE_MMDEVICEAPI_H
static bool SupportsIMMDevice = false;
#endif
// DirectX function pointers for audio
static SDL_SharedObject *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;
#include <initguid.h>
DEFINE_GUID(SDL_DSDEVID_DefaultPlayback, 0xdef00000, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
DEFINE_GUID(SDL_DSDEVID_DefaultCapture, 0xdef00001, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
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 } };
static void DSOUND_Unload(void)
{
pDirectSoundCreate8 = NULL;
pDirectSoundEnumerateW = NULL;
pDirectSoundCaptureCreate8 = NULL;
pDirectSoundCaptureEnumerateW = NULL;
pGetDeviceID = NULL;
if (DSoundDLL) {
SDL_UnloadObject(DSoundDLL);
DSoundDLL = NULL;
}
}
static bool DSOUND_Load(void)
{
bool loaded = false;
DSOUND_Unload();
DSoundDLL = SDL_LoadObject("DSOUND.DLL");
if (!DSoundDLL) {
SDL_SetError("DirectSound: failed to load DSOUND.DLL");
} else {
// Now make sure we have DirectX 8 or better...
#define DSOUNDLOAD(f) \
{ \
p##f = (fn##f)SDL_LoadFunction(DSoundDLL, #f); \
if (!p##f) \
loaded = false; \
}
loaded = true; // will reset if necessary.
DSOUNDLOAD(DirectSoundCreate8);
DSOUNDLOAD(DirectSoundEnumerateW);
DSOUNDLOAD(DirectSoundCaptureCreate8);
DSOUNDLOAD(DirectSoundCaptureEnumerateW);
DSOUNDLOAD(GetDeviceID);
#undef DSOUNDLOAD
if (!loaded) {
SDL_SetError("DirectSound: System doesn't appear to have DX8.");
}
}
if (!loaded) {
DSOUND_Unload();
}
return loaded;
}
static bool SetDSerror(const char *function, int code)
{
const char *error;
switch (code) {
case E_NOINTERFACE:
error = "Unsupported interface -- Is DirectX 8.0 or later installed?";
break;
case DSERR_ALLOCATED:
error = "Audio device in use";
break;
case DSERR_BADFORMAT:
error = "Unsupported audio format";
break;
case DSERR_BUFFERLOST:
error = "Mixing buffer was lost";
break;
case DSERR_CONTROLUNAVAIL:
error = "Control requested is not available";
break;
case DSERR_INVALIDCALL:
error = "Invalid call for the current state";
break;
case DSERR_INVALIDPARAM:
error = "Invalid parameter";
break;
case DSERR_NODRIVER:
error = "No audio device found";
break;
case DSERR_OUTOFMEMORY:
error = "Out of memory";
break;
case DSERR_PRIOLEVELNEEDED:
error = "Caller doesn't have priority";
break;
case DSERR_UNSUPPORTED:
error = "Function not supported";
break;
default:
error = "Unknown DirectSound error";
break;
}
return SDL_SetError("%s: %s (0x%x)", function, error, code);
}
static void DSOUND_FreeDeviceHandle(SDL_AudioDevice *device)
{
#ifdef HAVE_MMDEVICEAPI_H
if (SupportsIMMDevice) {
SDL_IMMDevice_FreeDeviceHandle(device);
} else
#endif
{
SDL_free(device->handle);
}
}
// FindAllDevs is presumably only used on WinXP; Vista and later can use IMMDevice for better results.
typedef struct FindAllDevsData
{
bool recording;
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) {
LPGUID cpyguid = (LPGUID)SDL_malloc(sizeof(GUID));
if (cpyguid) {
SDL_copyp(cpyguid, 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_AudioDevice *device = SDL_AddAudioDevice(data->recording, 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.
}
static void DSOUND_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
{
#ifdef HAVE_MMDEVICEAPI_H
if (SupportsIMMDevice) {
SDL_IMMDevice_EnumerateEndpoints(default_playback, default_recording);
} 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.recording = true;
data.default_device = default_recording;
data.default_device_guid = (pGetDeviceID(&SDL_DSDEVID_DefaultCapture, &guid) == DS_OK) ? &guid : NULL;
pDirectSoundCaptureEnumerateW(FindAllDevs, &data);
data.recording = false;
data.default_device = default_playback;
data.default_device_guid = (pGetDeviceID(&SDL_DSDEVID_DefaultPlayback, &guid) == DS_OK) ? &guid : NULL;
pDirectSoundEnumerateW(FindAllDevs, &data);
}
}
static bool DSOUND_WaitDevice(SDL_AudioDevice *device)
{
/* Semi-busy wait, since we have no way of getting play notification
on a primary mixing buffer located in hardware (DirectX 5.0)
*/
while (!SDL_GetAtomicInt(&device->shutdown)) {
DWORD status = 0;
DWORD cursor = 0;
DWORD junk = 0;
HRESULT result = DS_OK;
// Try to restore a lost sound buffer
IDirectSoundBuffer_GetStatus(device->hidden->mixbuf, &status);
if (status & DSBSTATUS_BUFFERLOST) {
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
} else if (!(status & DSBSTATUS_PLAYING)) {
result = IDirectSoundBuffer_Play(device->hidden->mixbuf, 0, 0, DSBPLAY_LOOPING);
} else {
// Find out where we are playing
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, &junk, &cursor);
if ((result == DS_OK) && ((cursor / device->buffer_size) != device->hidden->lastchunk)) {
break; // ready for next chunk!
}
}
if ((result != DS_OK) && (result != DSERR_BUFFERLOST)) {
return false;
}
SDL_Delay(1); // not ready yet; sleep a bit.
}
return true;
}
static bool DSOUND_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
// Unlock the buffer, allowing it to play
SDL_assert(buflen == device->buffer_size);
if (IDirectSoundBuffer_Unlock(device->hidden->mixbuf, (LPVOID) buffer, buflen, NULL, 0) != DS_OK) {
return false;
}
return true;
}
static Uint8 *DSOUND_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
DWORD cursor = 0;
DWORD junk = 0;
HRESULT result = DS_OK;
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(device->hidden->mixbuf);
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf,
&junk, &cursor);
}
if (result != DS_OK) {
SetDSerror("DirectSound GetCurrentPosition", result);
return NULL;
}
cursor /= device->buffer_size;
#ifdef DEBUG_SOUND
// Detect audio dropouts
{
DWORD spot = cursor;
if (spot < device->hidden->lastchunk) {
spot += device->hidden->num_buffers;
}
if (spot > device->hidden->lastchunk + 1) {
fprintf(stderr, "Audio dropout, missed %d fragments\n",
(spot - (device->hidden->lastchunk + 1)));
}
}
#endif
device->hidden->lastchunk = cursor;
cursor = (cursor + 1) % device->hidden->num_buffers;
cursor *= device->buffer_size;
// 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(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 device->hidden->locked_buf;
}
static bool DSOUND_WaitRecordingDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *h = device->hidden;
while (!SDL_GetAtomicInt(&device->shutdown)) {
DWORD junk, cursor;
if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) {
return false;
} else if ((cursor / device->buffer_size) != h->lastchunk) {
break;
}
SDL_Delay(1);
}
return true;
}
static int DSOUND_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
struct SDL_PrivateAudioData *h = device->hidden;
DWORD ptr1len, ptr2len;
VOID *ptr1, *ptr2;
SDL_assert(buflen == device->buffer_size);
if (IDirectSoundCaptureBuffer_Lock(h->capturebuf, h->lastchunk * buflen, buflen, &ptr1, &ptr1len, &ptr2, &ptr2len, 0) != DS_OK) {
return -1;
}
SDL_assert(ptr1len == (DWORD)buflen);
SDL_assert(ptr2 == NULL);
SDL_assert(ptr2len == 0);
SDL_memcpy(buffer, ptr1, ptr1len);
if (IDirectSoundCaptureBuffer_Unlock(h->capturebuf, ptr1, ptr1len, ptr2, ptr2len) != DS_OK) {
return -1;
}
h->lastchunk = (h->lastchunk + 1) % h->num_buffers;
return (int) ptr1len;
}
static void DSOUND_FlushRecording(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *h = device->hidden;
DWORD junk, cursor;
if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) == DS_OK) {
h->lastchunk = cursor / device->buffer_size;
}
}
static void DSOUND_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->mixbuf) {
IDirectSoundBuffer_Stop(device->hidden->mixbuf);
IDirectSoundBuffer_Release(device->hidden->mixbuf);
}
if (device->hidden->sound) {
IDirectSound_Release(device->hidden->sound);
}
if (device->hidden->capturebuf) {
IDirectSoundCaptureBuffer_Stop(device->hidden->capturebuf);
IDirectSoundCaptureBuffer_Release(device->hidden->capturebuf);
}
if (device->hidden->capture) {
IDirectSoundCapture_Release(device->hidden->capture);
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
/* 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 recording.
*/
static bool CreateSecondary(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt)
{
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
SDL_zero(format);
format.dwSize = sizeof(format);
format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
format.dwFlags |= DSBCAPS_GLOBALFOCUS;
format.dwBufferBytes = bufsize;
format.lpwfxFormat = wfmt;
result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
if (result != DS_OK) {
return SetDSerror("DirectSound CreateSoundBuffer", result);
}
IDirectSoundBuffer_SetFormat(*sndbuf, wfmt);
// 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, device->silence_value, dwAudioBytes1);
IDirectSoundBuffer_Unlock(*sndbuf,
(LPVOID)pvAudioPtr1, dwAudioBytes1,
(LPVOID)pvAudioPtr2, dwAudioBytes2);
}
return true; // 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
recording devices, not playback.
*/
static bool CreateCaptureBuffer(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt)
{
LPDIRECTSOUNDCAPTURE capture = device->hidden->capture;
LPDIRECTSOUNDCAPTUREBUFFER *capturebuf = &device->hidden->capturebuf;
DSCBUFFERDESC format;
HRESULT result;
SDL_zero(format);
format.dwSize = sizeof(format);
format.dwFlags = DSCBCAPS_WAVEMAPPED;
format.dwBufferBytes = bufsize;
format.lpwfxFormat = wfmt;
result = IDirectSoundCapture_CreateCaptureBuffer(capture, &format, capturebuf, NULL);
if (result != DS_OK) {
return SetDSerror("DirectSound CreateCaptureBuffer", result);
}
result = IDirectSoundCaptureBuffer_Start(*capturebuf, DSCBSTART_LOOPING);
if (result != DS_OK) {
IDirectSoundCaptureBuffer_Release(*capturebuf);
return SetDSerror("DirectSound Start", result);
}
#if 0
// presumably this starts at zero, but just in case...
result = IDirectSoundCaptureBuffer_GetCurrentPosition(*capturebuf, &junk, &cursor);
if (result != DS_OK) {
IDirectSoundCaptureBuffer_Stop(*capturebuf);
IDirectSoundCaptureBuffer_Release(*capturebuf);
return SetDSerror("DirectSound GetCurrentPosition", result);
}
device->hidden->lastchunk = cursor / device->buffer_size;
#endif
return true;
}
static bool DSOUND_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) {
return false;
}
// 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->recording) {
result = pDirectSoundCaptureCreate8(guid, &device->hidden->capture, NULL);
if (result != DS_OK) {
return SetDSerror("DirectSoundCaptureCreate8", result);
}
} else {
result = pDirectSoundCreate8(guid, &device->hidden->sound, NULL);
if (result != DS_OK) {
return SetDSerror("DirectSoundCreate8", result);
}
result = IDirectSound_SetCooperativeLevel(device->hidden->sound,
GetDesktopWindow(),
DSSCL_NORMAL);
if (result != DS_OK) {
return SetDSerror("DirectSound SetCooperativeLevel", result);
}
}
const DWORD numchunks = 8;
DWORD bufsize;
bool tried_format = 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:
case SDL_AUDIO_S16:
case SDL_AUDIO_S32:
case SDL_AUDIO_F32:
tried_format = true;
device->spec.format = test_format;
// Update the fragment size as size in bytes
SDL_UpdatedAudioDeviceFormat(device);
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 {
WAVEFORMATEXTENSIBLE wfmt;
SDL_zero(wfmt);
if (device->spec.channels > 2) {
wfmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfmt.Format.cbSize = sizeof(wfmt) - sizeof(WAVEFORMATEX);
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(device->spec.format);
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
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
break;
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
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
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
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(!"Unsupported channel count!");
break;
}
} 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(device->spec.format);
wfmt.Format.nChannels = (WORD)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;
const bool rc = device->recording ? CreateCaptureBuffer(device, bufsize, (WAVEFORMATEX *)&wfmt) : CreateSecondary(device, bufsize, (WAVEFORMATEX *)&wfmt);
if (rc) {
device->hidden->num_buffers = numchunks;
break;
}
}
continue;
default:
continue;
}
break;
}
if (!test_format) {
if (tried_format) {
return false; // CreateSecondary() should have called SDL_SetError().
}
return SDL_SetError("%s: Unsupported audio format", "directsound");
}
// Playback buffers will auto-start playing in DSOUND_WaitDevice()
return true; // good to go.
}
static void DSOUND_DeinitializeStart(void)
{
#ifdef HAVE_MMDEVICEAPI_H
if (SupportsIMMDevice) {
SDL_IMMDevice_Quit();
}
#endif
}
static void DSOUND_Deinitialize(void)
{
DSOUND_Unload();
#ifdef HAVE_MMDEVICEAPI_H
SupportsIMMDevice = false;
#endif
}
static bool DSOUND_Init(SDL_AudioDriverImpl *impl)
{
if (!DSOUND_Load()) {
return false;
}
#ifdef HAVE_MMDEVICEAPI_H
SupportsIMMDevice = SDL_IMMDevice_Init(NULL);
#endif
impl->DetectDevices = DSOUND_DetectDevices;
impl->OpenDevice = DSOUND_OpenDevice;
impl->PlayDevice = DSOUND_PlayDevice;
impl->WaitDevice = DSOUND_WaitDevice;
impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
impl->WaitRecordingDevice = DSOUND_WaitRecordingDevice;
impl->RecordDevice = DSOUND_RecordDevice;
impl->FlushRecording = DSOUND_FlushRecording;
impl->CloseDevice = DSOUND_CloseDevice;
impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle;
impl->DeinitializeStart = DSOUND_DeinitializeStart;
impl->Deinitialize = DSOUND_Deinitialize;
impl->HasRecordingSupport = true;
return true;
}
AudioBootStrap DSOUND_bootstrap = {
"directsound", "DirectSound", DSOUND_Init, false, false
};
#endif // SDL_AUDIO_DRIVER_DSOUND

View file

@ -0,0 +1,43 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_directsound_h_
#define SDL_directsound_h_
#include "../../core/windows/SDL_directx.h"
#include "../SDL_sysaudio.h"
// The DirectSound objects
struct SDL_PrivateAudioData
{
// !!! FIXME: make this a union with capture/playback sections?
LPDIRECTSOUND sound;
LPDIRECTSOUNDBUFFER mixbuf;
LPDIRECTSOUNDCAPTURE capture;
LPDIRECTSOUNDCAPTUREBUFFER capturebuf;
int num_buffers;
DWORD lastchunk;
Uint8 *locked_buf;
};
#endif // SDL_directsound_h_

View file

@ -0,0 +1,171 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_DISK
// Output raw audio data to a file.
#include "../SDL_sysaudio.h"
#include "SDL_diskaudio.h"
#define DISKDEFAULT_OUTFILE "sdlaudio.raw"
#define DISKDEFAULT_INFILE "sdlaudio-in.raw"
static bool DISKAUDIO_WaitDevice(SDL_AudioDevice *device)
{
SDL_Delay(device->hidden->io_delay);
return true;
}
static bool DISKAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
{
const int written = (int)SDL_WriteIO(device->hidden->io, buffer, (size_t)buffer_size);
if (written != buffer_size) { // If we couldn't write, assume fatal error for now
return false;
}
#ifdef DEBUG_AUDIO
SDL_Log("DISKAUDIO: Wrote %d bytes of audio data", (int) written);
#endif
return true;
}
static Uint8 *DISKAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int DISKAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
struct SDL_PrivateAudioData *h = device->hidden;
const int origbuflen = buflen;
if (h->io) {
const int br = (int)SDL_ReadIO(h->io, buffer, (size_t)buflen);
buflen -= br;
buffer = ((Uint8 *)buffer) + br;
if (buflen > 0) { // EOF (or error, but whatever).
SDL_CloseIO(h->io);
h->io = NULL;
}
}
// if we ran out of file, just write silence.
SDL_memset(buffer, device->silence_value, buflen);
return origbuflen;
}
static void DISKAUDIO_FlushRecording(SDL_AudioDevice *device)
{
// no op...we don't advance the file pointer or anything.
}
static void DISKAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->io) {
SDL_CloseIO(device->hidden->io);
}
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static const char *get_filename(const bool recording)
{
const char *devname = SDL_GetHint(recording ? SDL_HINT_AUDIO_DISK_INPUT_FILE : SDL_HINT_AUDIO_DISK_OUTPUT_FILE);
if (!devname) {
devname = recording ? DISKDEFAULT_INFILE : DISKDEFAULT_OUTFILE;
}
return devname;
}
static bool DISKAUDIO_OpenDevice(SDL_AudioDevice *device)
{
bool recording = device->recording;
const char *fname = get_filename(recording);
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq);
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DISK_TIMESCALE);
if (hint) {
double scale = SDL_atof(hint);
if (scale >= 0.0) {
device->hidden->io_delay = (Uint32)SDL_round(device->hidden->io_delay * scale);
}
}
// Open the "audio device"
device->hidden->io = SDL_IOFromFile(fname, recording ? "rb" : "wb");
if (!device->hidden->io) {
return false;
}
// Allocate mixing buffer
if (!recording) {
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (!device->hidden->mixbuf) {
return false;
}
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!");
SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s].", recording ? "Reading from" : "Writing to", fname);
return true; // We're ready to rock and roll. :-)
}
static void DISKAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
{
*default_playback = SDL_AddAudioDevice(false, DEFAULT_PLAYBACK_DEVNAME, NULL, (void *)0x1);
*default_recording = SDL_AddAudioDevice(true, DEFAULT_RECORDING_DEVNAME, NULL, (void *)0x2);
}
static bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = DISKAUDIO_OpenDevice;
impl->WaitDevice = DISKAUDIO_WaitDevice;
impl->WaitRecordingDevice = DISKAUDIO_WaitDevice;
impl->PlayDevice = DISKAUDIO_PlayDevice;
impl->GetDeviceBuf = DISKAUDIO_GetDeviceBuf;
impl->RecordDevice = DISKAUDIO_RecordDevice;
impl->FlushRecording = DISKAUDIO_FlushRecording;
impl->CloseDevice = DISKAUDIO_CloseDevice;
impl->DetectDevices = DISKAUDIO_DetectDevices;
impl->HasRecordingSupport = true;
return true;
}
AudioBootStrap DISKAUDIO_bootstrap = {
"disk", "direct-to-disk audio", DISKAUDIO_Init, true, false
};
#endif // SDL_AUDIO_DRIVER_DISK

View file

@ -0,0 +1,36 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_diskaudio_h_
#define SDL_diskaudio_h_
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
// The file descriptor for the audio device
SDL_IOStream *io;
Uint32 io_delay;
Uint8 *mixbuf;
};
#endif // SDL_diskaudio_h_

View file

@ -0,0 +1,303 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// !!! FIXME: clean out perror and fprintf calls in here.
#ifdef SDL_AUDIO_DRIVER_OSS
#include <stdio.h> // For perror()
#include <string.h> // For strerror()
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/soundcard.h>
#include "../SDL_audiodev_c.h"
#include "SDL_dspaudio.h"
static void DSP_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
{
SDL_EnumUnixAudioDevices(false, NULL);
}
static void DSP_CloseDevice(SDL_AudioDevice *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 bool 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;
}
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
// Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
const int flags = ((device->recording) ? 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()
{
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
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
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);
#endif
switch (test_format) {
case SDL_AUDIO_U8:
if (value & AFMT_U8) {
format = AFMT_U8;
}
break;
case SDL_AUDIO_S16LE:
if (value & AFMT_S16_LE) {
format = AFMT_S16_LE;
}
break;
case SDL_AUDIO_S16BE:
if (value & AFMT_S16_BE) {
format = AFMT_S16_BE;
}
break;
default:
continue;
}
break;
}
if (format == 0) {
return SDL_SetError("Couldn't find any hardware audio formats");
}
device->spec.format = test_format;
// Set the audio format
value = format;
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 = 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");
}
device->spec.channels = value;
// 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");
}
device->spec.freq = value;
// Calculate the final parameters for this audio specification
SDL_UpdatedAudioDeviceFormat(device);
/* 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
// Set the audio buffering parameters
#ifdef DEBUG_AUDIO
fprintf(stderr, "Requesting %d fragments of size %d\n",
(frag_spec >> 16), 1 << (frag_spec & 0xFFFF));
#endif
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0) {
perror("SNDCTL_DSP_SETFRAGMENT");
}
#ifdef DEBUG_AUDIO
{
audio_buf_info 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);
fprintf(stderr, "bytes = %d\n", info.bytes);
}
#endif
// Allocate mixing buffer
if (!device->recording) {
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (!device->hidden->mixbuf) {
return false;
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
}
return true; // We're ready to rock and roll. :-)
}
static bool DSP_WaitDevice(SDL_AudioDevice *device)
{
const unsigned long ioctlreq = device->recording ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE;
struct SDL_PrivateAudioData *h = device->hidden;
while (!SDL_GetAtomicInt(&device->shutdown)) {
audio_buf_info info;
const int rc = ioctl(h->audio_fd, ioctlreq, &info);
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));
return false;
} else if (info.bytes < device->buffer_size) {
SDL_Delay(10);
} else {
break; // ready to go!
}
}
return true;
}
static bool 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");
return false;
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", h->mixlen);
#endif
return true;
}
static Uint8 *DSP_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int DSP_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
return (int)read(device->hidden->audio_fd, buffer, buflen);
}
static void DSP_FlushRecording(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *h = device->hidden;
audio_buf_info info;
if (ioctl(h->audio_fd, SNDCTL_DSP_GETISPACE, &info) == 0) {
while (info.bytes > 0) {
char buf[512];
const size_t len = SDL_min(sizeof(buf), info.bytes);
const ssize_t br = read(h->audio_fd, buf, len);
if (br <= 0) {
break;
}
info.bytes -= br;
}
}
}
static bool InitTimeDevicesExist = false;
static bool look_for_devices_test(int fd)
{
InitTimeDevicesExist = true; // note that _something_ exists.
// Don't add to the device list, we're just seeing if any devices exist.
return false;
}
static bool DSP_Init(SDL_AudioDriverImpl *impl)
{
InitTimeDevicesExist = false;
SDL_EnumUnixAudioDevices(false, look_for_devices_test);
if (!InitTimeDevicesExist) {
SDL_SetError("dsp: No such audio device");
return false; // maybe try a different backend.
}
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->WaitRecordingDevice = DSP_WaitDevice;
impl->RecordDevice = DSP_RecordDevice;
impl->FlushRecording = DSP_FlushRecording;
impl->HasRecordingSupport = true;
return true;
}
AudioBootStrap DSP_bootstrap = {
"dsp", "Open Sound System (/dev/dsp)", DSP_Init, false, false
};
#endif // SDL_AUDIO_DRIVER_OSS

View file

@ -0,0 +1,37 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_dspaudio_h_
#define SDL_dspaudio_h_
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
// The file descriptor for the audio device
int audio_fd;
// Raw mixing buffer
Uint8 *mixbuf;
};
#endif // SDL_dspaudio_h_

View file

@ -0,0 +1,135 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// Output audio to nowhere...
#include "../SDL_sysaudio.h"
#include "SDL_dummyaudio.h"
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
#include <emscripten/emscripten.h>
#endif
static bool DUMMYAUDIO_WaitDevice(SDL_AudioDevice *device)
{
SDL_Delay(device->hidden->io_delay);
return true;
}
static bool DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
if (!device->recording) {
device->hidden->mixbuf = (Uint8 *) SDL_malloc(device->buffer_size);
if (!device->hidden->mixbuf) {
return false;
}
}
device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq);
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DUMMY_TIMESCALE);
if (hint) {
double scale = SDL_atof(hint);
if (scale >= 0.0) {
device->hidden->io_delay = (Uint32)SDL_round(device->hidden->io_delay * scale);
}
}
// on Emscripten without threads, we just fire a repeating timer to consume audio.
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
MAIN_THREAD_EM_ASM({
var a = Module['SDL3'].dummy_audio;
if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); }
a.timers[$0] = setInterval(function() { dynCall('vi', $3, [$4]); }, ($1 / $2) * 1000);
}, device->recording ? 1 : 0, device->sample_frames, device->spec.freq, device->recording ? SDL_RecordingAudioThreadIterate : SDL_PlaybackAudioThreadIterate, device);
#endif
return true; // we're good; don't change reported device format.
}
static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
// on Emscripten without threads, we just fire a repeating timer to consume audio.
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
MAIN_THREAD_EM_ASM({
var a = Module['SDL3'].dummy_audio;
if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); }
a.timers[$0] = undefined;
}, device->recording ? 1 : 0);
#endif
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static Uint8 *DUMMYAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int DUMMYAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
// always return a full buffer of silence.
SDL_memset(buffer, device->silence_value, buflen);
return buflen;
}
static bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = DUMMYAUDIO_OpenDevice;
impl->CloseDevice = DUMMYAUDIO_CloseDevice;
impl->WaitDevice = DUMMYAUDIO_WaitDevice;
impl->GetDeviceBuf = DUMMYAUDIO_GetDeviceBuf;
impl->WaitRecordingDevice = DUMMYAUDIO_WaitDevice;
impl->RecordDevice = DUMMYAUDIO_RecordDevice;
impl->OnlyHasDefaultPlaybackDevice = true;
impl->OnlyHasDefaultRecordingDevice = true;
impl->HasRecordingSupport = true;
// on Emscripten without threads, we just fire a repeating timer to consume audio.
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
MAIN_THREAD_EM_ASM({
if (typeof(Module['SDL3']) === 'undefined') {
Module['SDL3'] = {};
}
Module['SDL3'].dummy_audio = {};
Module['SDL3'].dummy_audio.timers = [];
Module['SDL3'].dummy_audio.timers[0] = undefined;
Module['SDL3'].dummy_audio.timers[1] = undefined;
});
impl->ProvidesOwnCallbackThread = true;
#endif
return true;
}
AudioBootStrap DUMMYAUDIO_bootstrap = {
"dummy", "SDL dummy audio driver", DUMMYAUDIO_Init, true, false
};

View file

@ -0,0 +1,34 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_dummyaudio_h_
#define SDL_dummyaudio_h_
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
Uint8 *mixbuf; // The file descriptor for the audio device
Uint32 io_delay; // milliseconds to sleep in WaitDevice.
};
#endif // SDL_dummyaudio_h_

View file

@ -0,0 +1,359 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN
#include "../SDL_sysaudio.h"
#include "SDL_emscriptenaudio.h"
#include <emscripten/emscripten.h>
// 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 Uint8 *EMSCRIPTENAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static bool EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
{
const int framelen = SDL_AUDIO_FRAMESIZE(device->spec);
MAIN_THREAD_EM_ASM({
/* Convert incoming buf pointer to a HEAPF32 offset. */
#ifdef __wasm64__
var buf = $0 / 4;
#else
var buf = $0 >>> 2;
#endif
var SDL3 = Module['SDL3'];
var numChannels = SDL3.audio_playback.currentPlaybackBuffer['numberOfChannels'];
for (var c = 0; c < numChannels; ++c) {
var channelData = SDL3.audio_playback.currentPlaybackBuffer['getChannelData'](c);
if (channelData.length != $1) {
throw 'Web Audio playback buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
}
for (var j = 0; j < $1; ++j) {
channelData[j] = HEAPF32[buf + (j*numChannels + c)];
}
}
}, buffer, buffer_size / framelen);
return true;
}
static void EMSCRIPTENAUDIO_FlushRecording(SDL_AudioDevice *device)
{
// Do nothing, the new data will just be dropped.
}
static int EMSCRIPTENAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
MAIN_THREAD_EM_ASM({
var SDL3 = Module['SDL3'];
var numChannels = SDL3.audio_recording.currentRecordingBuffer.numberOfChannels;
for (var c = 0; c < numChannels; ++c) {
var channelData = SDL3.audio_recording.currentRecordingBuffer.getChannelData(c);
if (channelData.length != $1) {
throw 'Web Audio recording buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
}
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');
}
} else {
for (var j = 0; j < $1; ++j) {
setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float');
}
}
}
}, buffer, (buflen / sizeof(float)) / device->spec.channels);
return buflen;
}
static void EMSCRIPTENAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (!device->hidden) {
return;
}
MAIN_THREAD_EM_ASM({
var SDL3 = Module['SDL3'];
if ($0) {
if (SDL3.audio_recording.silenceTimer !== undefined) {
clearInterval(SDL3.audio_recording.silenceTimer);
}
if (SDL3.audio_recording.stream !== undefined) {
var tracks = SDL3.audio_recording.stream.getAudioTracks();
for (var i = 0; i < tracks.length; i++) {
SDL3.audio_recording.stream.removeTrack(tracks[i]);
}
}
if (SDL3.audio_recording.scriptProcessorNode !== undefined) {
SDL3.audio_recording.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {};
SDL3.audio_recording.scriptProcessorNode.disconnect();
}
if (SDL3.audio_recording.mediaStreamNode !== undefined) {
SDL3.audio_recording.mediaStreamNode.disconnect();
}
SDL3.audio_recording = undefined;
} else {
if (SDL3.audio_playback.scriptProcessorNode != undefined) {
SDL3.audio_playback.scriptProcessorNode.disconnect();
}
if (SDL3.audio_playback.silenceTimer !== undefined) {
clearInterval(SDL3.audio_playback.silenceTimer);
}
SDL3.audio_playback = undefined;
}
if ((SDL3.audioContext !== undefined) && (SDL3.audio_playback === undefined) && (SDL3.audio_recording === undefined)) {
SDL3.audioContext.close();
SDL3.audioContext = undefined;
}
}, device->recording);
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
device->hidden = NULL;
SDL_AudioThreadFinalize(device);
}
EM_JS_DEPS(sdlaudio, "$autoResumeAudioContext,$dynCall");
static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device)
{
// based on parts of library_sdl.js
// create context
const bool result = MAIN_THREAD_EM_ASM_INT({
if (typeof(Module['SDL3']) === 'undefined') {
Module['SDL3'] = {};
}
var SDL3 = Module['SDL3'];
if (!$0) {
SDL3.audio_playback = {};
} else {
SDL3.audio_recording = {};
}
if (!SDL3.audioContext) {
if (typeof(AudioContext) !== 'undefined') {
SDL3.audioContext = new AudioContext();
} else if (typeof(webkitAudioContext) !== 'undefined') {
SDL3.audioContext = new webkitAudioContext();
}
if (SDL3.audioContext) {
if ((typeof navigator.userActivation) === 'undefined') {
autoResumeAudioContext(SDL3.audioContext);
}
}
}
return (SDL3.audioContext !== undefined);
}, device->recording);
if (!result) {
return SDL_SetError("Web Audio API is not available!");
}
device->spec.format = SDL_AUDIO_F32; // web audio only supports floats
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
// limit to native freq
device->spec.freq = EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; });
device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq) * 2; // double the buffer size, some browsers need more, and we'll just have to live with the latency.
SDL_UpdatedAudioDeviceFormat(device);
if (!device->recording) {
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (!device->hidden->mixbuf) {
return false;
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
}
if (device->recording) {
/* The idea is to take the recording 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.
This should, strictly speaking, use MediaRecorder for recording, 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
that the audiocontext uselessly mixes into any playback. On the
upside, both of those things are not only run in native code in
the browser, they're probably SIMD code, too. MediaRecorder
feels like it's a pretty inefficient tapdance in similar ways,
to be honest. */
MAIN_THREAD_EM_ASM({
var SDL3 = Module['SDL3'];
var have_microphone = function(stream) {
//console.log('SDL audio recording: we have a microphone! Replacing silence callback.');
if (SDL3.audio_recording.silenceTimer !== undefined) {
clearInterval(SDL3.audio_recording.silenceTimer);
SDL3.audio_recording.silenceTimer = undefined;
SDL3.audio_recording.silenceBuffer = undefined
}
SDL3.audio_recording.mediaStreamNode = SDL3.audioContext.createMediaStreamSource(stream);
SDL3.audio_recording.scriptProcessorNode = SDL3.audioContext.createScriptProcessor($1, $0, 1);
SDL3.audio_recording.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {
if ((SDL3 === undefined) || (SDL3.audio_recording === undefined)) { return; }
audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0);
SDL3.audio_recording.currentRecordingBuffer = audioProcessingEvent.inputBuffer;
dynCall('ip', $2, [$3]);
};
SDL3.audio_recording.mediaStreamNode.connect(SDL3.audio_recording.scriptProcessorNode);
SDL3.audio_recording.scriptProcessorNode.connect(SDL3.audioContext.destination);
SDL3.audio_recording.stream = stream;
};
var no_microphone = function(error) {
//console.log('SDL audio recording: 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).
SDL3.audio_recording.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
SDL3.audio_recording.silenceBuffer.getChannelData(0).fill(0.0);
var silence_callback = function() {
SDL3.audio_recording.currentRecordingBuffer = SDL3.audio_recording.silenceBuffer;
dynCall('ip', $2, [$3]);
};
SDL3.audio_recording.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) {
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone);
} else if (navigator.webkitGetUserMedia !== undefined) {
navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone);
}
}, device->spec.channels, device->sample_frames, SDL_RecordingAudioThreadIterate, device);
} else {
// setup a ScriptProcessorNode
MAIN_THREAD_EM_ASM({
var SDL3 = Module['SDL3'];
SDL3.audio_playback.scriptProcessorNode = SDL3.audioContext['createScriptProcessor']($1, 0, $0);
SDL3.audio_playback.scriptProcessorNode['onaudioprocess'] = function (e) {
if ((SDL3 === undefined) || (SDL3.audio_playback === undefined)) { return; }
// if we're actually running the node, we don't need the fake callback anymore, so kill it.
if (SDL3.audio_playback.silenceTimer !== undefined) {
clearInterval(SDL3.audio_playback.silenceTimer);
SDL3.audio_playback.silenceTimer = undefined;
SDL3.audio_playback.silenceBuffer = undefined;
}
SDL3.audio_playback.currentPlaybackBuffer = e['outputBuffer'];
dynCall('ip', $2, [$3]);
};
SDL3.audio_playback.scriptProcessorNode['connect'](SDL3.audioContext['destination']);
if (SDL3.audioContext.state === 'suspended') { // uhoh, autoplay is blocked.
SDL3.audio_playback.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
SDL3.audio_playback.silenceBuffer.getChannelData(0).fill(0.0);
var silence_callback = function() {
if ((typeof navigator.userActivation) !== 'undefined') {
if (navigator.userActivation.hasBeenActive) {
SDL3.audioContext.resume();
}
}
// the buffer that gets filled here just gets ignored, so the app can make progress
// and/or avoid flooding audio queues until we can actually play audio.
SDL3.audio_playback.currentPlaybackBuffer = SDL3.audio_playback.silenceBuffer;
dynCall('ip', $2, [$3]);
SDL3.audio_playback.currentPlaybackBuffer = undefined;
};
SDL3.audio_playback.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
}
}, device->spec.channels, device->sample_frames, SDL_PlaybackAudioThreadIterate, device);
}
return true;
}
static bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl)
{
bool available, recording_available;
impl->OpenDevice = EMSCRIPTENAUDIO_OpenDevice;
impl->CloseDevice = EMSCRIPTENAUDIO_CloseDevice;
impl->GetDeviceBuf = EMSCRIPTENAUDIO_GetDeviceBuf;
impl->PlayDevice = EMSCRIPTENAUDIO_PlayDevice;
impl->FlushRecording = EMSCRIPTENAUDIO_FlushRecording;
impl->RecordDevice = EMSCRIPTENAUDIO_RecordDevice;
impl->OnlyHasDefaultPlaybackDevice = true;
// 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 = true;
// check availability
available = MAIN_THREAD_EM_ASM_INT({
if (typeof(AudioContext) !== 'undefined') {
return true;
} else if (typeof(webkitAudioContext) !== 'undefined') {
return true;
}
return false;
});
if (!available) {
SDL_SetError("No audio context available");
}
recording_available = available && MAIN_THREAD_EM_ASM_INT({
if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) {
return true;
} else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') {
return true;
}
return false;
});
impl->HasRecordingSupport = recording_available;
impl->OnlyHasDefaultRecordingDevice = recording_available;
return available;
}
AudioBootStrap EMSCRIPTENAUDIO_bootstrap = {
"emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, false, false
};
/* *INDENT-ON* */ // clang-format on
#endif // SDL_AUDIO_DRIVER_EMSCRIPTEN

View file

@ -0,0 +1,33 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_emscriptenaudio_h_
#define SDL_emscriptenaudio_h_
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
Uint8 *mixbuf;
};
#endif // SDL_emscriptenaudio_h_

View file

@ -0,0 +1,222 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_HAIKU
// Allow access to the audio stream on Haiku
#include <SoundPlayer.h>
#include <signal.h>
#include "../../core/haiku/SDL_BeApp.h"
extern "C"
{
#include "../SDL_sysaudio.h"
#include "SDL_haikuaudio.h"
}
static Uint8 *HAIKUAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
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 bool HAIKUAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
{
// 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;
return true;
}
// 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_PlaybackAudioThreadIterate(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);
}
}
static const int sig_list[] = {
SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGALRM, SIGTERM, SIGWINCH, 0
};
static inline void MaskSignals(sigset_t * omask)
{
sigset_t mask;
int i;
sigemptyset(&mask);
for (i = 0; sig_list[i]; ++i) {
sigaddset(&mask, sig_list[i]);
}
sigprocmask(SIG_BLOCK, &mask, omask);
}
static inline void UnmaskSignals(sigset_t * omask)
{
sigprocmask(SIG_SETMASK, omask, NULL);
}
static bool HAIKUAUDIO_OpenDevice(SDL_AudioDevice *device)
{
// Initialize all variables that we clean on shutdown
device->hidden = new SDL_PrivateAudioData;
if (!device->hidden) {
return false;
}
SDL_zerop(device->hidden);
// 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) device->spec.freq;
format.channel_count = device->spec.channels; // !!! FIXME: support > 2?
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:
format.format = media_raw_audio_format::B_AUDIO_CHAR;
break;
case SDL_AUDIO_U8:
format.format = media_raw_audio_format::B_AUDIO_UCHAR;
break;
case SDL_AUDIO_S16LE:
format.format = media_raw_audio_format::B_AUDIO_SHORT;
break;
case SDL_AUDIO_S16BE:
format.format = media_raw_audio_format::B_AUDIO_SHORT;
format.byte_order = B_MEDIA_BIG_ENDIAN;
break;
case SDL_AUDIO_S32LE:
format.format = media_raw_audio_format::B_AUDIO_INT;
break;
case SDL_AUDIO_S32BE:
format.format = media_raw_audio_format::B_AUDIO_INT;
format.byte_order = B_MEDIA_BIG_ENDIAN;
break;
case SDL_AUDIO_F32LE:
format.format = media_raw_audio_format::B_AUDIO_FLOAT;
break;
case SDL_AUDIO_F32BE:
format.format = media_raw_audio_format::B_AUDIO_FLOAT;
format.byte_order = B_MEDIA_BIG_ENDIAN;
break;
default:
continue;
}
break;
}
if (!test_format) { // shouldn't happen, but just in case...
return SDL_SetError("HAIKU: Unsupported audio format");
}
device->spec.format = test_format;
// Calculate the final parameters for this audio specification
SDL_UpdatedAudioDeviceFormat(device);
format.buffer_size = device->buffer_size;
// Subscribe to the audio stream (creates a new thread)
sigset_t omask;
MaskSignals(&omask);
device->hidden->audio_obj = new BSoundPlayer(&format, "SDL Audio",
FillSound, NULL, device);
UnmaskSignals(&omask);
if (device->hidden->audio_obj->Start() == B_NO_ERROR) {
device->hidden->audio_obj->SetHasData(true);
} else {
return SDL_SetError("Unable to start Haiku audio");
}
return true; // We're running!
}
static void HAIKUAUDIO_Deinitialize(void)
{
SDL_QuitBeApp();
}
static bool HAIKUAUDIO_Init(SDL_AudioDriverImpl *impl)
{
if (!SDL_InitBeApp()) {
return false;
}
// 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 = true;
impl->OnlyHasDefaultPlaybackDevice = true;
return true;
}
extern "C" { extern AudioBootStrap HAIKUAUDIO_bootstrap; }
AudioBootStrap HAIKUAUDIO_bootstrap = {
"haiku", "Haiku BSoundPlayer", HAIKUAUDIO_Init, false, false
};
#endif // SDL_AUDIO_DRIVER_HAIKU

View file

@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_haikuaudio_h_
#define SDL_haikuaudio_h_
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
BSoundPlayer *audio_obj;
Uint8 *current_buffer;
int current_buffer_len;
};
#endif // SDL_haikuaudio_h_

View file

@ -0,0 +1,435 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_JACK
#include "../SDL_sysaudio.h"
#include "SDL_jackaudio.h"
#include "../../thread/SDL_systhread.h"
static jack_client_t *(*JACK_jack_client_open)(const char *, jack_options_t, jack_status_t *, ...);
static int (*JACK_jack_client_close)(jack_client_t *);
static void (*JACK_jack_on_shutdown)(jack_client_t *, JackShutdownCallback, void *);
static int (*JACK_jack_activate)(jack_client_t *);
static int (*JACK_jack_deactivate)(jack_client_t *);
static void *(*JACK_jack_port_get_buffer)(jack_port_t *, jack_nframes_t);
static int (*JACK_jack_port_unregister)(jack_client_t *, jack_port_t *);
static void (*JACK_jack_free)(void *);
static const char **(*JACK_jack_get_ports)(jack_client_t *, const char *, const char *, unsigned long);
static jack_nframes_t (*JACK_jack_get_sample_rate)(jack_client_t *);
static jack_nframes_t (*JACK_jack_get_buffer_size)(jack_client_t *);
static jack_port_t *(*JACK_jack_port_register)(jack_client_t *, const char *, const char *, unsigned long, unsigned long);
static jack_port_t *(*JACK_jack_port_by_name)(jack_client_t *, const char *);
static const char *(*JACK_jack_port_name)(const jack_port_t *);
static const char *(*JACK_jack_port_type)(const jack_port_t *);
static int (*JACK_jack_connect)(jack_client_t *, const char *, const char *);
static int (*JACK_jack_set_process_callback)(jack_client_t *, JackProcessCallback, void *);
static int (*JACK_jack_set_sample_rate_callback)(jack_client_t *, JackSampleRateCallback, void *);
static int (*JACK_jack_set_buffer_size_callback)(jack_client_t *, JackBufferSizeCallback, void *);
static bool load_jack_syms(void);
#ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC
static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC;
static SDL_SharedObject *jack_handle = NULL;
// !!! FIXME: this is copy/pasted in several places now
static bool load_jack_sym(const char *fn, void **addr)
{
*addr = SDL_LoadFunction(jack_handle, fn);
if (!*addr) {
// Don't call SDL_SetError(): SDL_LoadFunction already did.
return false;
}
return true;
}
// cast funcs to char* first, to please GCC's strict aliasing rules.
#define SDL_JACK_SYM(x) \
if (!load_jack_sym(#x, (void **)(char *)&JACK_##x)) \
return false
static void UnloadJackLibrary(void)
{
if (jack_handle) {
SDL_UnloadObject(jack_handle);
jack_handle = NULL;
}
}
static bool LoadJackLibrary(void)
{
bool result = true;
if (!jack_handle) {
jack_handle = SDL_LoadObject(jack_library);
if (!jack_handle) {
result = false;
// Don't call SDL_SetError(): SDL_LoadObject already did.
} else {
result = load_jack_syms();
if (!result) {
UnloadJackLibrary();
}
}
}
return result;
}
#else
#define SDL_JACK_SYM(x) JACK_##x = x
static void UnloadJackLibrary(void)
{
}
static bool LoadJackLibrary(void)
{
load_jack_syms();
return true;
}
#endif // SDL_AUDIO_DRIVER_JACK_DYNAMIC
static bool load_jack_syms(void)
{
SDL_JACK_SYM(jack_client_open);
SDL_JACK_SYM(jack_client_close);
SDL_JACK_SYM(jack_on_shutdown);
SDL_JACK_SYM(jack_activate);
SDL_JACK_SYM(jack_deactivate);
SDL_JACK_SYM(jack_port_get_buffer);
SDL_JACK_SYM(jack_port_unregister);
SDL_JACK_SYM(jack_free);
SDL_JACK_SYM(jack_get_ports);
SDL_JACK_SYM(jack_get_sample_rate);
SDL_JACK_SYM(jack_get_buffer_size);
SDL_JACK_SYM(jack_port_register);
SDL_JACK_SYM(jack_port_by_name);
SDL_JACK_SYM(jack_port_name);
SDL_JACK_SYM(jack_port_type);
SDL_JACK_SYM(jack_connect);
SDL_JACK_SYM(jack_set_process_callback);
SDL_JACK_SYM(jack_set_sample_rate_callback);
SDL_JACK_SYM(jack_set_buffer_size_callback);
return true;
}
static void jackShutdownCallback(void *arg) // JACK went away; device is lost.
{
SDL_AudioDeviceDisconnected((SDL_AudioDevice *)arg);
}
static int jackSampleRateCallback(jack_nframes_t nframes, void *arg)
{
//SDL_Log("JACK Sample Rate Callback! %d", (int) nframes);
SDL_AudioDevice *device = (SDL_AudioDevice *) arg;
SDL_AudioSpec newspec;
SDL_copyp(&newspec, &device->spec);
newspec.freq = (int) nframes;
if (!SDL_AudioDeviceFormatChanged(device, &newspec, device->sample_frames)) {
SDL_AudioDeviceDisconnected(device);
}
return 0;
}
static int jackBufferSizeCallback(jack_nframes_t nframes, void *arg)
{
//SDL_Log("JACK Buffer Size Callback! %d", (int) nframes);
SDL_AudioDevice *device = (SDL_AudioDevice *) arg;
SDL_AudioSpec newspec;
SDL_copyp(&newspec, &device->spec);
if (!SDL_AudioDeviceFormatChanged(device, &newspec, (int) nframes)) {
SDL_AudioDeviceDisconnected(device);
}
return 0;
}
static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg)
{
SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
SDL_PlaybackAudioThreadIterate((SDL_AudioDevice *)arg);
return 0;
}
static bool JACK_PlayDevice(SDL_AudioDevice *device, const Uint8 *ui8buffer, int buflen)
{
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;
}
}
}
return true;
}
static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return (Uint8 *)device->hidden->iobuffer;
}
static int jackProcessRecordingCallback(jack_nframes_t nframes, void *arg)
{
SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
SDL_RecordingAudioThreadIterate((SDL_AudioDevice *)arg);
return 0;
}
static int JACK_RecordDevice(SDL_AudioDevice *device, void *vbuffer, int buflen)
{
float *buffer = (float *) vbuffer;
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);
if (src) {
float *dst = buffer + channelsi;
for (int framesi = 0; framesi < total_frames; framesi++) {
*dst = *(src++);
dst += total_channels;
}
}
}
return buflen;
}
static void JACK_FlushRecording(SDL_AudioDevice *device)
{
// do nothing, the data will just be replaced next callback.
}
static void JACK_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->client) {
JACK_jack_deactivate(device->hidden->client);
if (device->hidden->sdlports) {
const int channels = device->spec.channels;
int i;
for (i = 0; i < channels; i++) {
JACK_jack_port_unregister(device->hidden->client, device->hidden->sdlports[i]);
}
SDL_free(device->hidden->sdlports);
}
JACK_jack_client_close(device->hidden->client);
}
SDL_free(device->hidden->iobuffer);
SDL_free(device->hidden);
device->hidden = NULL;
SDL_AudioThreadFinalize(device);
}
}
// !!! FIXME: unify this (PulseAudio has a getAppName, Pipewire has a thing, etc)
static const char *GetJackAppName(void)
{
return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
}
static bool JACK_OpenDevice(SDL_AudioDevice *device)
{
/* Note that JACK uses "output" for recording 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 recording will be "input" (we read data in). */
const bool recording = device->recording;
const unsigned long sysportflags = recording ? JackPortIsOutput : JackPortIsInput;
const unsigned long sdlportflags = recording ? JackPortIsInput : JackPortIsOutput;
const JackProcessCallback callback = recording ? jackProcessRecordingCallback : jackProcessPlaybackCallback;
const char *sdlportstr = recording ? "input" : "output";
const char **devports = NULL;
int *audio_ports;
jack_client_t *client = NULL;
jack_status_t status;
int channels = 0;
int ports = 0;
int i;
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
client = JACK_jack_client_open(GetJackAppName(), JackNoStartServer, &status, NULL);
device->hidden->client = client;
if (!client) {
return SDL_SetError("Can't open JACK client");
}
devports = JACK_jack_get_ports(client, NULL, NULL, JackPortIsPhysical | sysportflags);
if (!devports || !devports[0]) {
return SDL_SetError("No physical JACK ports available");
}
while (devports[++ports]) {
// spin to count devports
}
// Filter out non-audio ports
audio_ports = SDL_calloc(ports, sizeof(*audio_ports));
for (i = 0; i < ports; i++) {
const jack_port_t *dport = JACK_jack_port_by_name(client, devports[i]);
const char *type = JACK_jack_port_type(dport);
const int len = SDL_strlen(type);
// See if type ends with "audio"
if (len >= 5 && !SDL_memcmp(type + len - 5, "audio", 5)) {
audio_ports[channels++] = i;
}
}
if (channels == 0) {
SDL_free(audio_ports);
return SDL_SetError("No physical JACK ports available");
}
// Jack pretty much demands what it wants.
device->spec.format = SDL_AUDIO_F32;
device->spec.freq = JACK_jack_get_sample_rate(client);
device->spec.channels = channels;
device->sample_frames = JACK_jack_get_buffer_size(client);
SDL_UpdatedAudioDeviceFormat(device);
if (!recording) {
device->hidden->iobuffer = (float *)SDL_calloc(1, device->buffer_size);
if (!device->hidden->iobuffer) {
SDL_free(audio_ports);
return false;
}
}
// Build SDL's ports, which we will connect to the device ports.
device->hidden->sdlports = (jack_port_t **)SDL_calloc(channels, sizeof(jack_port_t *));
if (!device->hidden->sdlports) {
SDL_free(audio_ports);
return false;
}
for (i = 0; i < channels; i++) {
char portname[32];
(void)SDL_snprintf(portname, sizeof(portname), "sdl_jack_%s_%d", sdlportstr, i);
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_buffer_size_callback(client, jackBufferSizeCallback, device) != 0) {
SDL_free(audio_ports);
return SDL_SetError("JACK: Couldn't set buffer size callback");
} else if (JACK_jack_set_sample_rate_callback(client, jackSampleRateCallback, device) != 0) {
SDL_free(audio_ports);
return SDL_SetError("JACK: Couldn't set sample rate callback");
} else 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, device);
if (JACK_jack_activate(client) != 0) {
SDL_free(audio_ports);
return SDL_SetError("Failed to activate JACK client");
}
// once activated, we can connect all the ports.
for (i = 0; i < channels; i++) {
const char *sdlport = JACK_jack_port_name(device->hidden->sdlports[i]);
const char *srcport = recording ? devports[audio_ports[i]] : sdlport;
const char *dstport = recording ? sdlport : devports[audio_ports[i]];
if (JACK_jack_connect(client, srcport, dstport) != 0) {
SDL_free(audio_ports);
return SDL_SetError("Couldn't connect JACK ports: %s => %s", srcport, dstport);
}
}
// don't need these anymore.
JACK_jack_free(devports);
SDL_free(audio_ports);
// We're ready to rock and roll. :-)
return true;
}
static void JACK_Deinitialize(void)
{
UnloadJackLibrary();
}
static bool JACK_Init(SDL_AudioDriverImpl *impl)
{
if (!LoadJackLibrary()) {
return false;
} else {
// Make sure a JACK server is running and available.
jack_status_t status;
jack_client_t *client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL);
if (!client) {
UnloadJackLibrary();
return SDL_SetError("Can't open JACK client");
}
JACK_jack_client_close(client);
}
impl->OpenDevice = JACK_OpenDevice;
impl->GetDeviceBuf = JACK_GetDeviceBuf;
impl->PlayDevice = JACK_PlayDevice;
impl->CloseDevice = JACK_CloseDevice;
impl->Deinitialize = JACK_Deinitialize;
impl->RecordDevice = JACK_RecordDevice;
impl->FlushRecording = JACK_FlushRecording;
impl->OnlyHasDefaultPlaybackDevice = true;
impl->OnlyHasDefaultRecordingDevice = true;
impl->HasRecordingSupport = true;
impl->ProvidesOwnCallbackThread = true;
return true;
}
AudioBootStrap JACK_bootstrap = {
"jack", "JACK Audio Connection Kit", JACK_Init, false, false
};
#endif // SDL_AUDIO_DRIVER_JACK

View file

@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_jackaudio_h_
#define SDL_jackaudio_h_
#include <jack/jack.h>
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
jack_client_t *client;
jack_port_t **sdlports;
float *iobuffer;
};
#endif // SDL_jackaudio_h_

View file

@ -0,0 +1,287 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_N3DS
// N3DS Audio driver
#include "../SDL_sysaudio.h"
#include "SDL_n3dsaudio.h"
#define N3DSAUDIO_DRIVER_NAME "n3ds"
static dspHookCookie dsp_hook;
static SDL_AudioDevice *audio_device;
// fully local functions related to the wavebufs / DSP, not the same as the `device->lock` SDL_Mutex!
static SDL_INLINE void contextLock(SDL_AudioDevice *device)
{
LightLock_Lock(&device->hidden->lock);
}
static SDL_INLINE void contextUnlock(SDL_AudioDevice *device)
{
LightLock_Unlock(&device->hidden->lock);
}
static void N3DSAUD_DspHook(DSP_HookType hook)
{
if (hook == DSPHOOK_ONCANCEL) {
contextLock(audio_device);
audio_device->hidden->isCancelled = true;
SDL_AudioDeviceDisconnected(audio_device);
CondVar_Broadcast(&audio_device->hidden->cv);
contextUnlock(audio_device);
}
}
static void AudioFrameFinished(void *vdevice)
{
bool shouldBroadcast = false;
unsigned i;
SDL_AudioDevice *device = (SDL_AudioDevice *)vdevice;
contextLock(device);
for (i = 0; i < NUM_BUFFERS; i++) {
if (device->hidden->waveBuf[i].status == NDSP_WBUF_DONE) {
device->hidden->waveBuf[i].status = NDSP_WBUF_FREE;
shouldBroadcast = true;
}
}
if (shouldBroadcast) {
CondVar_Broadcast(&device->hidden->cv);
}
contextUnlock(device);
}
static bool N3DSAUDIO_OpenDevice(SDL_AudioDevice *device)
{
Result ndsp_init_res;
Uint8 *data_vaddr;
float mix[12];
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
// 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)) {
return SDL_SetError("DSP init failed: dspfirm.cdc missing!");
} else {
return SDL_SetError("DSP init failed. Error code: 0x%lX", ndsp_init_res);
}
}
// Initialise internal state
LightLock_Init(&device->hidden->lock);
CondVar_Init(&device->hidden->cv);
if (device->spec.channels > 2) {
device->spec.channels = 2;
}
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.");
}
device->spec.format = test_format;
// 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.");
}
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (!device->hidden->mixbuf) {
return false;
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
data_vaddr = (Uint8 *)linearAlloc(device->buffer_size * NUM_BUFFERS);
if (!data_vaddr) {
return SDL_OutOfMemory();
}
SDL_memset(data_vaddr, 0, device->buffer_size * NUM_BUFFERS);
DSP_FlushDataCache(data_vaddr, device->buffer_size * NUM_BUFFERS);
device->hidden->nextbuf = 0;
ndspChnReset(0);
ndspChnSetInterp(0, NDSP_INTERP_LINEAR);
ndspChnSetRate(0, device->spec.freq);
ndspChnSetFormat(0, format);
SDL_zeroa(mix);
mix[0] = mix[1] = 1.0f;
ndspChnSetMix(0, mix);
SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
const int sample_frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
for (unsigned i = 0; i < NUM_BUFFERS; i++) {
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 = device;
ndspSetCallback(AudioFrameFinished, device);
dspHook(&dsp_hook, N3DSAUD_DspHook);
return true;
}
static bool N3DSAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
contextLock(device);
const size_t nextbuf = device->hidden->nextbuf;
if (device->hidden->isCancelled ||
device->hidden->waveBuf[nextbuf].status != NDSP_WBUF_FREE) {
contextUnlock(device);
return true; // !!! FIXME: is this a fatal error? If so, this should return false.
}
device->hidden->nextbuf = (nextbuf + 1) % NUM_BUFFERS;
contextUnlock(device);
SDL_memcpy((void *)device->hidden->waveBuf[nextbuf].data_vaddr, buffer, buflen);
DSP_FlushDataCache(device->hidden->waveBuf[nextbuf].data_vaddr, buflen);
ndspChnWaveBufAdd(0, &device->hidden->waveBuf[nextbuf]);
return true;
}
static bool N3DSAUDIO_WaitDevice(SDL_AudioDevice *device)
{
contextLock(device);
while (!device->hidden->isCancelled && !SDL_GetAtomicInt(&device->shutdown) &&
device->hidden->waveBuf[device->hidden->nextbuf].status != NDSP_WBUF_FREE) {
CondVar_Wait(&device->hidden->cv, &device->hidden->lock);
}
contextUnlock(device);
return true;
}
static Uint8 *N3DSAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static void N3DSAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (!device->hidden) {
return;
}
contextLock(device);
dspUnhook(&dsp_hook);
ndspSetCallback(NULL, NULL);
if (!device->hidden->isCancelled) {
ndspChnReset(0);
SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
CondVar_Broadcast(&device->hidden->cv);
}
contextUnlock(device);
ndspExit();
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 *device)
{
s32 current_priority = 0x30;
svcGetThreadPriority(&current_priority, CUR_THREAD_HANDLE);
current_priority--;
// 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 bool N3DSAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = N3DSAUDIO_OpenDevice;
impl->PlayDevice = N3DSAUDIO_PlayDevice;
impl->WaitDevice = N3DSAUDIO_WaitDevice;
impl->GetDeviceBuf = N3DSAUDIO_GetDeviceBuf;
impl->CloseDevice = N3DSAUDIO_CloseDevice;
impl->ThreadInit = N3DSAUDIO_ThreadInit;
impl->OnlyHasDefaultPlaybackDevice = true;
// Should be possible, but micInit would fail
impl->HasRecordingSupport = false;
return true;
}
AudioBootStrap N3DSAUDIO_bootstrap = {
N3DSAUDIO_DRIVER_NAME,
"SDL N3DS audio driver",
N3DSAUDIO_Init,
false,
false
};
#endif // SDL_AUDIO_DRIVER_N3DS

View file

@ -0,0 +1,40 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_n3dsaudio_h
#define SDL_n3dsaudio_h
#include <3ds.h>
#define NUM_BUFFERS 3 // -- Minimum 2!
struct SDL_PrivateAudioData
{
// Speaker data
Uint8 *mixbuf;
Uint32 nextbuf;
ndspWaveBuf waveBuf[NUM_BUFFERS];
LightLock lock;
CondVar cv;
bool isCancelled;
};
#endif // SDL_n3dsaudio_h

View file

@ -0,0 +1,328 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_NETBSD
// Driver for native NetBSD audio(4).
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/audioio.h>
#include "../../core/unix/SDL_poll.h"
#include "../SDL_audiodev_c.h"
#include "SDL_netbsdaudio.h"
//#define DEBUG_AUDIO
static void NETBSDAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
{
SDL_EnumUnixAudioDevices(false, NULL);
}
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(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
fprintf(stderr, "AUDIO_GETINFO failed.\n");
return;
}
prinfo = device->recording ? &info.record : &info.play;
fprintf(stderr, "\n"
"[%s info]\n"
"buffer size : %d bytes\n"
"sample rate : %i Hz\n"
"channels : %i\n"
"precision : %i-bit\n"
"encoding : 0x%x\n"
"seek : %i\n"
"sample count : %i\n"
"EOF count : %i\n"
"paused : %s\n"
"error occurred : %s\n"
"waiting : %s\n"
"active : %s\n"
"",
device->recording ? "record" : "play",
prinfo->buffer_size,
prinfo->sample_rate,
prinfo->channels,
prinfo->precision,
prinfo->encoding,
prinfo->seek,
prinfo->samples,
prinfo->eof,
prinfo->pause ? "yes" : "no",
prinfo->error ? "yes" : "no",
prinfo->waiting ? "yes" : "no",
prinfo->active ? "yes" : "no");
fprintf(stderr, "\n"
"[audio info]\n"
"monitor_gain : %i\n"
"hw block size : %d bytes\n"
"hi watermark : %i\n"
"lo watermark : %i\n"
"audio mode : %s\n"
"",
info.monitor_gain,
info.blocksize,
info.hiwat, info.lowat,
(info.mode == AUMODE_PLAY) ? "PLAY"
: (info.mode == AUMODE_RECORD) ? "RECORD"
: (info.mode == AUMODE_PLAY_ALL ? "PLAY_ALL" : "?"));
fprintf(stderr, "\n"
"[audio spec]\n"
"format : 0x%x\n"
"size : %u\n"
"",
device->spec.format,
device->buffer_size);
/* *INDENT-ON* */ // clang-format on
#endif // DEBUG_AUDIO
}
static bool NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device)
{
const bool recording = device->recording;
while (!SDL_GetAtomicInt(&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));
return false;
}
const size_t remain = (size_t)((recording ? info.record.seek : info.play.seek) * SDL_AUDIO_BYTESIZE(device->spec.format));
if (!recording && (remain >= device->buffer_size)) {
SDL_Delay(10);
} else if (recording && (remain < device->buffer_size)) {
SDL_Delay(10);
} else {
break; // ready to go!
}
}
return true;
}
static bool 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 != buflen) { // Treat even partial writes as fatal errors.
return false;
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", written);
#endif
return true;
}
static Uint8 *NETBSDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int NETBSDAUDIO_RecordDevice(SDL_AudioDevice *device, void *vbuffer, int 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!!!
perror("audio");
return -1;
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Recorded %d bytes of audio data\n", br);
#endif
return br;
}
static void NETBSDAUDIO_FlushRecording(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *h = device->hidden;
audio_info_t info;
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) == 0) {
size_t remain = (size_t)(info.record.seek * SDL_AUDIO_BYTESIZE(device->spec.format));
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;
}
}
}
static void NETBSDAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->audio_fd >= 0) {
close(device->hidden->audio_fd);
}
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static bool NETBSDAUDIO_OpenDevice(SDL_AudioDevice *device)
{
const bool recording = device->recording;
int encoding = AUDIO_ENCODING_NONE;
audio_info_t info, hwinfo;
struct audio_prinfo *prinfo = recording ? &info.record : &info.play;
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
// Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
const int flags = ((device->recording) ? 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(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 = recording ? hwinfo.record.sample_rate : hwinfo.play.sample_rate;
}
#endif
prinfo->sample_rate = device->spec.freq;
prinfo->channels = device->spec.channels;
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:
encoding = AUDIO_ENCODING_ULINEAR;
break;
case SDL_AUDIO_S8:
encoding = AUDIO_ENCODING_SLINEAR;
break;
case SDL_AUDIO_S16LE:
encoding = AUDIO_ENCODING_SLINEAR_LE;
break;
case SDL_AUDIO_S16BE:
encoding = AUDIO_ENCODING_SLINEAR_BE;
break;
case SDL_AUDIO_S32LE:
encoding = AUDIO_ENCODING_SLINEAR_LE;
break;
case SDL_AUDIO_S32BE:
encoding = AUDIO_ENCODING_SLINEAR_BE;
break;
default:
continue;
}
break;
}
if (!test_format) {
return SDL_SetError("%s: Unsupported audio format", "netbsd");
}
prinfo->encoding = encoding;
prinfo->precision = SDL_AUDIO_BITSIZE(test_format);
info.hiwat = 5;
info.lowat = 3;
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(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.
device->spec.format = test_format;
device->spec.freq = prinfo->sample_rate;
device->spec.channels = prinfo->channels;
SDL_UpdatedAudioDeviceFormat(device);
if (!recording) {
// Allocate mixing buffer
device->hidden->mixlen = device->buffer_size;
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->hidden->mixlen);
if (!device->hidden->mixbuf) {
return false;
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
}
NETBSDAUDIO_Status(device);
return true; // We're ready to rock and roll. :-)
}
static bool NETBSDAUDIO_Init(SDL_AudioDriverImpl *impl)
{
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->WaitRecordingDevice = NETBSDAUDIO_WaitDevice;
impl->RecordDevice = NETBSDAUDIO_RecordDevice;
impl->FlushRecording = NETBSDAUDIO_FlushRecording;
impl->HasRecordingSupport = true;
return true;
}
AudioBootStrap NETBSDAUDIO_bootstrap = {
"netbsd", "NetBSD audio", NETBSDAUDIO_Init, false, false
};
#endif // SDL_AUDIO_DRIVER_NETBSD

View file

@ -0,0 +1,44 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_netbsdaudio_h_
#define SDL_netbsdaudio_h_
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
// The file descriptor for the audio device
int audio_fd;
// Raw mixing buffer
Uint8 *mixbuf;
int mixlen;
// Support for audio timing using a timer, in addition to SDL_IOReady()
float frame_ticks;
float next_frame;
};
#define FUDGE_TICKS 10 // The scheduler overhead ticks per frame
#endif // SDL_netbsdaudio_h_

View file

@ -0,0 +1,807 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#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
#include "../SDL_sysaudio.h"
#include "SDL_openslES.h"
#include "../../core/android/SDL_android.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <android/log.h>
#define NUM_BUFFERS 2 // -- Don't lower this!
struct SDL_PrivateAudioData
{
Uint8 *mixbuff;
int next_buffer;
Uint8 *pmixbuff[NUM_BUFFERS];
SDL_Semaphore *playsem;
};
#if 0
#define LOG_TAG "SDL_openslES"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
//#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)
#define LOGV(...)
#else
#define LOGE(...)
#define LOGI(...)
#define LOGV(...)
#endif
/*
#define SL_SPEAKER_FRONT_LEFT ((SLuint32) 0x00000001)
#define SL_SPEAKER_FRONT_RIGHT ((SLuint32) 0x00000002)
#define SL_SPEAKER_FRONT_CENTER ((SLuint32) 0x00000004)
#define SL_SPEAKER_LOW_FREQUENCY ((SLuint32) 0x00000008)
#define SL_SPEAKER_BACK_LEFT ((SLuint32) 0x00000010)
#define SL_SPEAKER_BACK_RIGHT ((SLuint32) 0x00000020)
#define SL_SPEAKER_FRONT_LEFT_OF_CENTER ((SLuint32) 0x00000040)
#define SL_SPEAKER_FRONT_RIGHT_OF_CENTER ((SLuint32) 0x00000080)
#define SL_SPEAKER_BACK_CENTER ((SLuint32) 0x00000100)
#define SL_SPEAKER_SIDE_LEFT ((SLuint32) 0x00000200)
#define SL_SPEAKER_SIDE_RIGHT ((SLuint32) 0x00000400)
#define SL_SPEAKER_TOP_CENTER ((SLuint32) 0x00000800)
#define SL_SPEAKER_TOP_FRONT_LEFT ((SLuint32) 0x00001000)
#define SL_SPEAKER_TOP_FRONT_CENTER ((SLuint32) 0x00002000)
#define SL_SPEAKER_TOP_FRONT_RIGHT ((SLuint32) 0x00004000)
#define SL_SPEAKER_TOP_BACK_LEFT ((SLuint32) 0x00008000)
#define SL_SPEAKER_TOP_BACK_CENTER ((SLuint32) 0x00010000)
#define SL_SPEAKER_TOP_BACK_RIGHT ((SLuint32) 0x00020000)
*/
#define SL_ANDROID_SPEAKER_STEREO (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT)
#define SL_ANDROID_SPEAKER_QUAD (SL_ANDROID_SPEAKER_STEREO | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT)
#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
static SLObjectItf engineObject = NULL;
static SLEngineItf engineEngine = NULL;
// output mix interfaces
static SLObjectItf outputMixObject = NULL;
// buffer queue player interfaces
static SLObjectItf bqPlayerObject = NULL;
static SLPlayItf bqPlayerPlay = NULL;
static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL;
#if 0
static SLVolumeItf bqPlayerVolume;
#endif
// recorder interfaces
static SLObjectItf recorderObject = NULL;
static SLRecordItf recorderRecord = NULL;
static SLAndroidSimpleBufferQueueItf recorderBufferQueue = NULL;
#if 0
static const char *sldevaudiorecorderstr = "SLES Audio Recorder";
static const char *sldevaudioplayerstr = "SLES Audio Player";
#define SLES_DEV_AUDIO_RECORDER sldevaudiorecorderstr
#define SLES_DEV_AUDIO_PLAYER sldevaudioplayerstr
static void OPENSLES_DetectDevices( int recording )
{
LOGI( "openSLES_DetectDevices()" );
if ( recording )
addfn( SLES_DEV_AUDIO_RECORDER );
else
addfn( SLES_DEV_AUDIO_PLAYER );
}
#endif
static void OPENSLES_DestroyEngine(void)
{
LOGI("OPENSLES_DestroyEngine()");
// 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
if (engineObject != NULL) {
(*engineObject)->Destroy(engineObject);
engineObject = NULL;
engineEngine = NULL;
}
}
static bool OPENSLES_CreateEngine(void)
{
const SLInterfaceID ids[1] = { SL_IID_VOLUME };
const SLboolean req[1] = { SL_BOOLEAN_FALSE };
SLresult result;
LOGI("openSLES_CreateEngine()");
// create engine
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
if (SL_RESULT_SUCCESS != result) {
LOGE("slCreateEngine failed: %d", result);
goto error;
}
LOGI("slCreateEngine OK");
// realize the engine
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
LOGE("RealizeEngine failed: %d", result);
goto error;
}
LOGI("RealizeEngine OK");
// 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);
goto error;
}
LOGI("EngineGetInterface OK");
// create output mix
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
if (SL_RESULT_SUCCESS != result) {
LOGE("CreateOutputMix failed: %d", result);
goto error;
}
LOGI("CreateOutputMix OK");
// realize the output mix
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
LOGE("RealizeOutputMix failed: %d", result);
goto error;
}
return true;
error:
OPENSLES_DestroyEngine();
return false;
}
// 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;
LOGV("SLES: Recording Callback");
SDL_SignalSemaphore(audiodata->playsem);
}
static void OPENSLES_DestroyPCMRecorder(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
SLresult result;
// stop recording
if (recorderRecord != NULL) {
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
if (SL_RESULT_SUCCESS != result) {
LOGE("SetRecordState stopped: %d", result);
}
}
// destroy audio recorder object, and invalidate all associated interfaces
if (recorderObject != NULL) {
(*recorderObject)->Destroy(recorderObject);
recorderObject = NULL;
recorderRecord = NULL;
recorderBufferQueue = NULL;
}
if (audiodata->playsem) {
SDL_DestroySemaphore(audiodata->playsem);
audiodata->playsem = NULL;
}
if (audiodata->mixbuff) {
SDL_free(audiodata->mixbuff);
}
}
// !!! FIXME: make this non-blocking!
static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted)
{
SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1);
}
static bool OPENSLES_CreatePCMRecorder(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
SLDataFormat_PCM format_pcm;
SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
SLDataSink audioSnk;
SLDataLocator_IODevice loc_dev;
SLDataSource audioSrc;
const SLInterfaceID ids[1] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
const SLboolean req[1] = { SL_BOOLEAN_TRUE };
SLresult result;
int i;
// !!! FIXME: make this non-blocking!
{
SDL_AtomicInt permission_response;
SDL_SetAtomicInt(&permission_response, 0);
if (!SDL_RequestAndroidPermission("android.permission.RECORD_AUDIO", RequestAndroidPermissionBlockingCallback, &permission_response)) {
return false;
}
while (SDL_GetAtomicInt(&permission_response) == 0) {
SDL_Delay(10);
}
if (SDL_GetAtomicInt(&permission_response) < 0) {
LOGE("This app doesn't have RECORD_AUDIO permission");
return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
}
}
// Just go with signed 16-bit audio as it's the most compatible
device->spec.format = SDL_AUDIO_S16;
device->spec.channels = 1;
//device->spec.freq = SL_SAMPLINGRATE_16 / 1000;*/
// Update the fragment size as size in bytes
SDL_UpdatedAudioDeviceFormat(device);
LOGI("Try to open %u hz %u bit %u channels %s samples %u",
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
// configure audio source
loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT;
loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
loc_dev.device = NULL;
audioSrc.pLocator = &loc_dev;
audioSrc.pFormat = NULL;
// configure audio sink
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
loc_bufq.numBuffers = NUM_BUFFERS;
format_pcm.formatType = SL_DATAFORMAT_PCM;
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)
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
result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
LOGE("RealizeAudioPlayer failed: %d", result);
goto failed;
}
// 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
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 *)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
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 * device->buffer_size);
if (!audiodata->mixbuff) {
LOGE("mixbuffer allocate - out of memory");
goto failed;
}
for (i = 0; i < NUM_BUFFERS; i++) {
audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size;
}
// 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
for (i = 0; i < NUM_BUFFERS; i++) {
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
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING);
if (SL_RESULT_SUCCESS != result) {
LOGE("Record set state failed: %d", result);
goto failed;
}
return true;
failed:
return SDL_SetError("Open device failed!");
}
// 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;
LOGV("SLES: Playback Callback");
SDL_SignalSemaphore(audiodata->playsem);
}
static void OPENSLES_DestroyPCMPlayer(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
// set the player's state to 'stopped'
if (bqPlayerPlay != NULL) {
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
if (bqPlayerObject != NULL) {
(*bqPlayerObject)->Destroy(bqPlayerObject);
bqPlayerObject = NULL;
bqPlayerPlay = NULL;
bqPlayerBufferQueue = NULL;
}
if (audiodata->playsem) {
SDL_DestroySemaphore(audiodata->playsem);
audiodata->playsem = NULL;
}
if (audiodata->mixbuff) {
SDL_free(audiodata->mixbuff);
}
}
static bool OPENSLES_CreatePCMPlayer(SDL_AudioDevice *device)
{
/* 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(device->spec.format);
SDL_AudioFormat test_format;
while ((test_format = *(closefmts++)) != 0) {
if (SDL_AUDIO_ISSIGNED(test_format)) {
break;
}
}
if (!test_format) {
// Didn't find a compatible format :
LOGI("No compatible audio format, using signed 16-bit audio");
test_format = SDL_AUDIO_S16;
}
device->spec.format = test_format;
} else {
// Just go with signed 16-bit audio as it's the most compatible
device->spec.format = SDL_AUDIO_S16;
}
// Update the fragment size as size in bytes
SDL_UpdatedAudioDeviceFormat(device);
LOGI("Try to open %u hz %s %u bit %u channels %s samples %u",
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
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 = 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(device->spec.format)) {
format_pcm.endianness = SL_BYTEORDER_BIGENDIAN;
} else {
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
}
switch (device->spec.channels) {
case 1:
format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT;
break;
case 2:
format_pcm.channelMask = SL_ANDROID_SPEAKER_STEREO;
break;
case 3:
format_pcm.channelMask = SL_ANDROID_SPEAKER_STEREO | SL_SPEAKER_FRONT_CENTER;
break;
case 4:
format_pcm.channelMask = SL_ANDROID_SPEAKER_QUAD;
break;
case 5:
format_pcm.channelMask = SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER;
break;
case 6:
format_pcm.channelMask = SL_ANDROID_SPEAKER_5DOT1;
break;
case 7:
format_pcm.channelMask = SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_BACK_CENTER;
break;
case 8:
format_pcm.channelMask = SL_ANDROID_SPEAKER_7DOT1;
break;
default:
// Unknown number of channels, fall back to stereo
device->spec.channels = 2;
format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
break;
}
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;
format_pcm_ex.numChannels = format_pcm.numChannels;
format_pcm_ex.sampleRate = format_pcm.samplesPerSec;
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;
// 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
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
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
LOGE("RealizeAudioPlayer failed: %d", result);
goto failed;
}
// 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
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 *)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
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
if (SL_RESULT_SUCCESS != result) {
LOGE("SL_IID_VOLUME interface get failed: %d", result);
// goto failed;
}
#endif
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 * device->buffer_size);
if (!audiodata->mixbuff) {
LOGE("mixbuffer allocate - out of memory");
goto failed;
}
for (int i = 0; i < NUM_BUFFERS; i++) {
audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size;
}
// 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);
goto failed;
}
return true;
failed:
return false;
}
static bool OPENSLES_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
if (device->recording) {
LOGI("OPENSLES_OpenDevice() for recording");
return OPENSLES_CreatePCMRecorder(device);
} else {
bool ret;
LOGI("OPENSLES_OpenDevice() for playback");
ret = OPENSLES_CreatePCMPlayer(device);
if (!ret) {
// 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) {
return SDL_SetError("Open device failed!");
}
}
return true;
}
static bool OPENSLES_WaitDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
LOGV("OPENSLES_WaitDevice()");
while (!SDL_GetAtomicInt(&device->shutdown)) {
// this semaphore won't fire when the app is in the background (OPENSLES_PauseDevices was called).
if (SDL_WaitSemaphoreTimeout(audiodata->playsem, 100)) {
return true; // semaphore was signaled, let's go!
}
// Still waiting on the semaphore (or the system), check other things then wait again.
}
return true;
}
static bool OPENSLES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
LOGV("======OPENSLES_PlayDevice()======");
// 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 (SL_RESULT_SUCCESS != result) {
SDL_SignalSemaphore(audiodata->playsem);
}
return true;
}
/// 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 *device, int *bufsize)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
LOGV("OPENSLES_GetDeviceBuf()");
return audiodata->pmixbuff[audiodata->next_buffer];
}
static int OPENSLES_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
// Copy it to the output buffer
SDL_assert(buflen == device->buffer_size);
SDL_memcpy(buffer, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_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;
}
audiodata->next_buffer++;
if (audiodata->next_buffer >= NUM_BUFFERS) {
audiodata->next_buffer = 0;
}
return device->buffer_size;
}
static void OPENSLES_CloseDevice(SDL_AudioDevice *device)
{
// struct SDL_PrivateAudioData *audiodata = device->hidden;
if (device->hidden) {
if (device->recording) {
LOGI("OPENSLES_CloseDevice() for recording");
OPENSLES_DestroyPCMRecorder(device);
} else {
LOGI("OPENSLES_CloseDevice() for playing");
OPENSLES_DestroyPCMPlayer(device);
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static bool OPENSLES_Init(SDL_AudioDriverImpl *impl)
{
LOGI("OPENSLES_Init() called");
if (!OPENSLES_CreateEngine()) {
return false;
}
LOGI("OPENSLES_Init() - set pointers");
// 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->WaitRecordingDevice = OPENSLES_WaitDevice;
impl->RecordDevice = OPENSLES_RecordDevice;
impl->CloseDevice = OPENSLES_CloseDevice;
impl->Deinitialize = OPENSLES_DestroyEngine;
// and the capabilities
impl->HasRecordingSupport = true;
impl->OnlyHasDefaultPlaybackDevice = true;
impl->OnlyHasDefaultRecordingDevice = true;
LOGI("OPENSLES_Init() - success");
// this audio target is available.
return true;
}
AudioBootStrap OPENSLES_bootstrap = {
"openslES", "OpenSL ES audio driver", OPENSLES_Init, false, false
};
void OPENSLES_ResumeDevices(void)
{
if (bqPlayerPlay != NULL) {
// 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);
}
}
}
void OPENSLES_PauseDevices(void)
{
if (bqPlayerPlay != NULL) {
// 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);
}
}
}
#endif // SDL_AUDIO_DRIVER_OPENSLES

View file

@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_openslesaudio_h_
#define SDL_openslesaudio_h_
#ifdef SDL_AUDIO_DRIVER_OPENSLES
extern void OPENSLES_ResumeDevices(void);
extern void OPENSLES_PauseDevices(void);
#else
#define OPENSLES_ResumeDevices()
#define OPENSLES_PauseDevices()
#endif
#endif // SDL_openslesaudio_h_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,43 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_pipewire_h_
#define SDL_pipewire_h_
#include "../SDL_sysaudio.h"
#include <pipewire/pipewire.h>
struct SDL_PrivateAudioData
{
struct pw_thread_loop *loop;
struct pw_stream *stream;
struct pw_context *context;
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_

View file

@ -0,0 +1,159 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "../SDL_sysaudio.h"
#include "SDL_ps2audio.h"
#include <kernel.h>
#include <audsrv.h>
#include <ps2_audio_driver.h>
static bool PS2AUDIO_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
// These are the native supported audio PS2 configs
switch (device->spec.freq) {
case 11025:
case 12000:
case 22050:
case 24000:
case 32000:
case 44100:
case 48000:
break; // acceptable value, keep it
default:
device->spec.freq = 48000;
break;
}
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;
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;
device->hidden->channel = audsrv_set_format(&format);
audsrv_set_volume(MAX_VOLUME);
if (device->hidden->channel < 0) {
return SDL_SetError("Couldn't reserve hardware channel");
}
// 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. */
const int mixlen = device->buffer_size * NUM_BUFFERS;
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
if (!device->hidden->rawbuf) {
return SDL_SetError("Couldn't allocate mixing buffer");
}
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];
}
return true;
}
static bool PS2AUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
// this returns number of bytes accepted or a negative error. We assume anything other than buflen is a fatal error.
return (audsrv_play_audio((char *)buffer, buflen) == buflen);
}
static bool PS2AUDIO_WaitDevice(SDL_AudioDevice *device)
{
audsrv_wait_audio(device->buffer_size);
return true;
}
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;
}
static void PS2AUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->channel >= 0) {
audsrv_stop_audio();
device->hidden->channel = -1;
}
if (device->hidden->rawbuf) {
SDL_aligned_free(device->hidden->rawbuf);
device->hidden->rawbuf = NULL;
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static void PS2AUDIO_ThreadInit(SDL_AudioDevice *device)
{
/* Increase the priority of this audio thread by 1 to put it
ahead of other SDL threads. */
const int32_t thid = GetThreadId();
ee_thread_status_t status;
if (ReferThreadStatus(thid, &status) == 0) {
ChangeThreadPriority(thid, status.current_priority - 1);
}
}
static void PS2AUDIO_Deinitialize(void)
{
deinit_audio_driver();
}
static bool PS2AUDIO_Init(SDL_AudioDriverImpl *impl)
{
if (init_audio_driver() < 0) {
return false;
}
impl->OpenDevice = PS2AUDIO_OpenDevice;
impl->PlayDevice = PS2AUDIO_PlayDevice;
impl->WaitDevice = PS2AUDIO_WaitDevice;
impl->GetDeviceBuf = PS2AUDIO_GetDeviceBuf;
impl->CloseDevice = PS2AUDIO_CloseDevice;
impl->ThreadInit = PS2AUDIO_ThreadInit;
impl->Deinitialize = PS2AUDIO_Deinitialize;
impl->OnlyHasDefaultPlaybackDevice = true;
return true; // this audio target is available.
}
AudioBootStrap PS2AUDIO_bootstrap = {
"ps2", "PS2 audio driver", PS2AUDIO_Init, false, false
};

View file

@ -0,0 +1,42 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_ps2audio_h_
#define SDL_ps2audio_h_
#include "../SDL_sysaudio.h"
#define NUM_BUFFERS 2
struct SDL_PrivateAudioData
{
// The hardware output channel.
int channel;
// The raw allocated mixing buffer.
Uint8 *rawbuf;
// Individual mixing buffers.
Uint8 *mixbufs[NUM_BUFFERS];
// Index of the next available mixing buffer.
int next_buffer;
};
#endif // SDL_ps2audio_h_

View file

@ -0,0 +1,183 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_PSP
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "../SDL_audiodev_c.h"
#include "../SDL_sysaudio.h"
#include "SDL_pspaudio.h"
#include <pspaudio.h>
#include <pspthreadman.h>
static bool isBasicAudioConfig(const SDL_AudioSpec *spec)
{
return spec->freq == 44100;
}
static bool PSPAUDIO_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
// device only natively supports S16LSB
device->spec.format = SDL_AUDIO_S16LE;
/* 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(&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;
const int format = (device->spec.channels == 1) ? PSP_AUDIO_FORMAT_MONO : PSP_AUDIO_FORMAT_STEREO;
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) {
case 8000:
case 11025:
case 12000:
case 16000:
case 22050:
case 24000:
case 32000:
case 44100:
case 48000:
break; // acceptable, keep it
default:
device->spec.freq = 48000;
break;
}
// 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 (device->hidden->channel < 0) {
return SDL_SetError("Couldn't reserve hardware channel");
}
// 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. */
const int mixlen = device->buffer_size * NUM_BUFFERS;
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
if (!device->hidden->rawbuf) {
return SDL_SetError("Couldn't allocate mixing buffer");
}
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];
}
return true;
}
static bool PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
int rc;
if (!isBasicAudioConfig(&device->spec)) {
SDL_assert(device->spec.channels == 2);
rc = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, (void *) buffer);
} else {
rc = sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, (void *) buffer);
}
return (rc == 0);
}
static bool PSPAUDIO_WaitDevice(SDL_AudioDevice *device)
{
return true; // Because we block when sending audio, there's no need for this function to do anything.
}
static Uint8 *PSPAUDIO_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;
}
static void PSPAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->channel >= 0) {
if (!isBasicAudioConfig(&device->spec)) {
sceAudioSRCChRelease();
} else {
sceAudioChRelease(device->hidden->channel);
}
device->hidden->channel = -1;
}
if (device->hidden->rawbuf) {
SDL_aligned_free(device->hidden->rawbuf);
device->hidden->rawbuf = NULL;
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
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();
SceKernelThreadInfo status;
status.size = sizeof(SceKernelThreadInfo);
if (sceKernelReferThreadStatus(thid, &status) == 0) {
sceKernelChangeThreadPriority(thid, status.currentPriority - 1);
}
}
static bool PSPAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = PSPAUDIO_OpenDevice;
impl->PlayDevice = PSPAUDIO_PlayDevice;
impl->WaitDevice = PSPAUDIO_WaitDevice;
impl->GetDeviceBuf = PSPAUDIO_GetDeviceBuf;
impl->CloseDevice = PSPAUDIO_CloseDevice;
impl->ThreadInit = PSPAUDIO_ThreadInit;
impl->OnlyHasDefaultPlaybackDevice = true;
//impl->HasRecordingSupport = true;
//impl->OnlyHasDefaultRecordingDevice = true;
return true;
}
AudioBootStrap PSPAUDIO_bootstrap = {
"psp", "PSP audio driver", PSPAUDIO_Init, false, false
};
#endif // SDL_AUDIO_DRIVER_PSP

View file

@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_pspaudio_h_
#define SDL_pspaudio_h_
#include "../SDL_sysaudio.h"
#define NUM_BUFFERS 2
struct SDL_PrivateAudioData
{
// The hardware output channel.
int channel;
// The raw allocated mixing buffer.
Uint8 *rawbuf;
// Individual mixing buffers.
Uint8 *mixbufs[NUM_BUFFERS];
// Index of the next available mixing buffer.
int next_buffer;
};
#endif // SDL_pspaudio_h_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,44 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_pulseaudio_h_
#define SDL_pulseaudio_h_
#include <pulse/pulseaudio.h>
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
// pulseaudio structures
pa_stream *stream;
// Raw mixing buffer
Uint8 *mixbuf;
int bytes_requested; // bytes of data the hardware wants _now_.
const Uint8 *recordingbuf;
int recordinglen;
};
#endif // SDL_pulseaudio_h_

View file

@ -0,0 +1,451 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
// !!! FIXME: can this target support hotplugging?
#include "../../SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_QNX
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sched.h>
#include <sys/select.h>
#include <sys/neutrino.h>
#include <sys/asoundlib.h>
#include "SDL3/SDL_timer.h"
#include "SDL3/SDL_audio.h"
#include "../../core/unix/SDL_poll.h"
#include "../SDL_sysaudio.h"
#include "SDL_qsa_audio.h"
// default channel communication parameters
#define DEFAULT_CPARAMS_RATE 44100
#define DEFAULT_CPARAMS_VOICES 1
#define DEFAULT_CPARAMS_FRAG_SIZE 4096
#define DEFAULT_CPARAMS_FRAGS_MIN 1
#define DEFAULT_CPARAMS_FRAGS_MAX 1
#define QSA_MAX_NAME_LENGTH 81+16 // Hardcoded in QSA, can't be changed
static bool 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 *device)
{
// Increase default 10 priority to 25 to avoid jerky sound
struct sched_param param;
if (SchedGet(0, 0, &param) != -1) {
param.sched_priority = param.sched_curpriority + 15;
SchedSet(0, 0, SCHED_NOCHANGE, &param);
}
}
// PCM channel parameters initialize function
static void QSA_InitAudioParams(snd_pcm_channel_params_t * cpars)
{
SDL_zerop(cpars);
cpars->channel = SND_PCM_CHANNEL_PLAYBACK;
cpars->mode = SND_PCM_MODE_BLOCK;
cpars->start_mode = SND_PCM_START_DATA;
cpars->stop_mode = SND_PCM_STOP_STOP;
cpars->format.format = SND_PCM_SFMT_S16_LE;
cpars->format.interleave = 1;
cpars->format.rate = DEFAULT_CPARAMS_RATE;
cpars->format.voices = DEFAULT_CPARAMS_VOICES;
cpars->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE;
cpars->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN;
cpars->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX;
}
// This function waits until it is possible to write a full sound buffer
static bool QSA_WaitDevice(SDL_AudioDevice *device)
{
// 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 !
const int result = SDL_IOReady(device->hidden->audio_fd,
device->recording ? SDL_IOR_READ : SDL_IOR_WRITE,
2 * 1000);
switch (result) {
case -1:
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "QSA: SDL_IOReady() failed: %s", strerror(errno));
return false;
case 0:
device->hidden->timeout_on_wait = true; // !!! FIXME: Should we just disconnect the device in this case?
break;
default:
device->hidden->timeout_on_wait = false;
break;
}
return true;
}
static bool QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
if (SDL_GetAtomicInt(&device->shutdown) || !device->hidden) {
return true;
}
int towrite = buflen;
// Write the audio data, checking for EAGAIN (buffer full) and underrun
while ((towrite > 0) && !SDL_GetAtomicInt(&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 true; // oh well, try again next time. !!! FIXME: Should we just disconnect the device in this case?
}
}
// Check for errors or conditions
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
SDL_Delay(1); // Let a little CPU time go by and try to write again
// 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->recording ? 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 false;
} else if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY)) {
status = snd_pcm_plugin_prepare(device->hidden->audio_handle, device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
if (status < 0) {
QSA_SetError("snd_pcm_plugin_prepare", status);
return false;
}
}
continue;
} else {
return false;
}
} else {
// we wrote all remaining data
towrite -= bw;
buffer += bw * device->spec.channels;
}
}
// If we couldn't write, assume fatal error for now
return (towrite == 0);
}
static Uint8 *QSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->pcm_buf;
}
static void QSA_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->audio_handle) {
#if _NTO_VERSION < 710
// Finish playing available samples or cancel unread samples during recording
snd_pcm_plugin_flush(device->hidden->audio_handle, device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
#endif
snd_pcm_close(device->hidden->audio_handle);
}
SDL_free(device->hidden->pcm_buf);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static bool QSA_OpenDevice(SDL_AudioDevice *device)
{
if (device->recording) {
return SDL_SetError("SDL recording support isn't available on QNX atm"); // !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in!
}
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 bool recording = device->recording;
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 false;
}
// Initialize channel transfer parameters to default
snd_pcm_channel_params_t cparams;
QSA_InitAudioParams(&cparams);
// Open requested audio device
status = snd_pcm_open(&device->hidden->audio_handle, cardno, deviceno, recording ? SND_PCM_OPEN_CAPTURE : SND_PCM_OPEN_PLAYBACK);
if (status < 0) {
device->hidden->audio_handle = NULL;
return QSA_SetError("snd_pcm_open", status);
}
// 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
switch (test_format) {
#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);
CHECKFMT(S16MSB, S16_BE);
CHECKFMT(S32LSB, S32_LE);
CHECKFMT(S32MSB, S32_BE);
CHECKFMT(F32LSB, FLOAT_LE);
CHECKFMT(F32MSB, FLOAT_BE);
#undef CHECKFMT
default: continue;
}
break;
}
// assumes test_format not 0 on success
if (test_format == 0) {
return SDL_SetError("QSA: Couldn't find any hardware audio formats");
}
device->spec.format = test_format;
// Set mono/stereo/4ch/6ch/8ch audio
cparams.format.voices = device->spec.channels;
// Set rate
cparams.format.rate = device->spec.freq;
// 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
snd_pcm_channel_setup_t csetup;
SDL_zero(csetup);
csetup.channel = recording ? 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");
}
device->sample_frames = csetup.buf.block.frag_size;
// Calculate the final parameters for this audio specification
SDL_UpdatedAudioDeviceFormat(device);
device->hidden->pcm_buf = (Uint8 *) SDL_malloc(device->buffer_size);
if (device->hidden->pcm_buf == NULL) {
return false;
}
SDL_memset(device->hidden->pcm_buf, device->silence_value, device->buffer_size);
// 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);
}
return true; // We're really ready to rock and roll. :-)
}
static SDL_AudioFormat QnxFormatToSDLFormat(const int32_t qnxfmt)
{
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_S16; // oh well.
}
static void QSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
{
// 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);
bool isstack = 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 (num_cards == 0) { // not any available audio devices?
SDL_small_free(cards, isstack);
return;
}
// 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];
status = snd_card_get_longname(card, name, sizeof (name));
if (status == EOK) {
snd_pcm_t *handle;
// 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
bool recording = false;
status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_PLAYBACK);
if (status != EOK) { // no? See if it's a recording device instead.
#if 0 // !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in!
status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_CAPTURE);
if (status == EOK) {
recording = true;
}
#endif
}
if (status == EOK) {
SDL_AudioSpec spec;
SDL_zero(spec);
SDL_AudioSpec *pspec = &spec;
snd_pcm_channel_setup_t csetup;
SDL_zero(csetup);
csetup.channel = recording ? 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 {
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(recording, fullname, pspec, (void *) ((size_t) sdlhandle));
}
} else {
// Check if we got end of devices list
if (status == -ENOENT) {
break;
}
}
} else {
break;
}
}
}
SDL_small_free(cards, isstack);
// 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_playback = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
}
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_recording = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
}
}
static void QSA_Deinitialize(void)
{
// nothing to do here atm.
}
static bool QSA_Init(SDL_AudioDriverImpl * impl)
{
impl->DetectDevices = QSA_DetectDevices;
impl->OpenDevice = QSA_OpenDevice;
impl->ThreadInit = QSA_ThreadInit;
impl->WaitDevice = QSA_WaitDevice;
impl->PlayDevice = QSA_PlayDevice;
impl->GetDeviceBuf = QSA_GetDeviceBuf;
impl->CloseDevice = QSA_CloseDevice;
impl->Deinitialize = QSA_Deinitialize;
// !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in!
//impl->HasRecordingSupport = true;
return true;
}
AudioBootStrap QSAAUDIO_bootstrap = {
"qsa", "QNX QSA Audio", QSA_Init, false, false
};
#endif // SDL_AUDIO_DRIVER_QNX

View file

@ -0,0 +1,40 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_QSA_AUDIO_H__
#define __SDL_QSA_AUDIO_H__
#include <sys/asoundlib.h>
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
snd_pcm_t *audio_handle; // The audio device handle
int audio_fd; // The audio file descriptor, for selecting on
bool timeout_on_wait; // Select timeout status
Uint8 *pcm_buf; // Raw mixing buffer
};
#endif // __SDL_QSA_AUDIO_H__

View file

@ -0,0 +1,356 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_SNDIO
// OpenBSD sndio target
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#include <poll.h>
#include <unistd.h>
#include "../SDL_sysaudio.h"
#include "SDL_sndioaudio.h"
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
#endif
#ifndef INFTIM
#define INFTIM -1
#endif
#ifndef SIO_DEVANY
#define SIO_DEVANY "default"
#endif
static struct sio_hdl *(*SNDIO_sio_open)(const char *, unsigned int, int);
static void (*SNDIO_sio_close)(struct sio_hdl *);
static int (*SNDIO_sio_setpar)(struct sio_hdl *, struct sio_par *);
static int (*SNDIO_sio_getpar)(struct sio_hdl *, struct sio_par *);
static int (*SNDIO_sio_start)(struct sio_hdl *);
static int (*SNDIO_sio_stop)(struct sio_hdl *);
static size_t (*SNDIO_sio_read)(struct sio_hdl *, void *, size_t);
static size_t (*SNDIO_sio_write)(struct sio_hdl *, const void *, size_t);
static int (*SNDIO_sio_nfds)(struct sio_hdl *);
static int (*SNDIO_sio_pollfd)(struct sio_hdl *, struct pollfd *, int);
static int (*SNDIO_sio_revents)(struct sio_hdl *, struct pollfd *);
static int (*SNDIO_sio_eof)(struct sio_hdl *);
static void (*SNDIO_sio_initpar)(struct sio_par *);
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
static const char *sndio_library = SDL_AUDIO_DRIVER_SNDIO_DYNAMIC;
static SDL_SharedObject *sndio_handle = NULL;
static bool load_sndio_sym(const char *fn, void **addr)
{
*addr = SDL_LoadFunction(sndio_handle, fn);
if (!*addr) {
return false; // Don't call SDL_SetError(): SDL_LoadFunction already did.
}
return true;
}
// 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 false
#else
#define SDL_SNDIO_SYM(x) SNDIO_##x = x
#endif
static bool load_sndio_syms(void)
{
SDL_SNDIO_SYM(sio_open);
SDL_SNDIO_SYM(sio_close);
SDL_SNDIO_SYM(sio_setpar);
SDL_SNDIO_SYM(sio_getpar);
SDL_SNDIO_SYM(sio_start);
SDL_SNDIO_SYM(sio_stop);
SDL_SNDIO_SYM(sio_read);
SDL_SNDIO_SYM(sio_write);
SDL_SNDIO_SYM(sio_nfds);
SDL_SNDIO_SYM(sio_pollfd);
SDL_SNDIO_SYM(sio_revents);
SDL_SNDIO_SYM(sio_eof);
SDL_SNDIO_SYM(sio_initpar);
return true;
}
#undef SDL_SNDIO_SYM
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
static void UnloadSNDIOLibrary(void)
{
if (sndio_handle) {
SDL_UnloadObject(sndio_handle);
sndio_handle = NULL;
}
}
static bool LoadSNDIOLibrary(void)
{
bool result = true;
if (!sndio_handle) {
sndio_handle = SDL_LoadObject(sndio_library);
if (!sndio_handle) {
result = false; // Don't call SDL_SetError(): SDL_LoadObject already did.
} else {
result = load_sndio_syms();
if (!result) {
UnloadSNDIOLibrary();
}
}
}
return result;
}
#else
static void UnloadSNDIOLibrary(void)
{
}
static bool LoadSNDIOLibrary(void)
{
load_sndio_syms();
return true;
}
#endif // SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
static bool SNDIO_WaitDevice(SDL_AudioDevice *device)
{
const bool recording = device->recording;
while (!SDL_GetAtomicInt(&device->shutdown)) {
if (SNDIO_sio_eof(device->hidden->dev)) {
return false;
}
const int nfds = SNDIO_sio_pollfd(device->hidden->dev, device->hidden->pfd, recording ? POLLIN : POLLOUT);
if (nfds <= 0 || poll(device->hidden->pfd, nfds, 10) < 0) {
return false;
}
const int revents = SNDIO_sio_revents(device->hidden->dev, device->hidden->pfd);
if (recording && (revents & POLLIN)) {
break;
} else if (!recording && (revents & POLLOUT)) {
break;
} else if (revents & POLLHUP) {
return false;
}
}
return true;
}
static bool SNDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
// !!! 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) {
return false; // If we couldn't write, assume fatal error for now
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", written);
#endif
return true;
}
static int SNDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
// We set recording 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) br;
}
static void SNDIO_FlushRecording(SDL_AudioDevice *device)
{
char buf[512];
while (!SDL_GetAtomicInt(&device->shutdown) && (SNDIO_sio_read(device->hidden->dev, buf, sizeof(buf)) > 0)) {
// do nothing
}
}
static Uint8 *SNDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static void SNDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->dev) {
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;
}
}
static bool SNDIO_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
// Recording devices must be non-blocking for SNDIO_FlushRecording
device->hidden->dev = SNDIO_sio_open(SIO_DEVANY,
device->recording ? SIO_REC : SIO_PLAY, device->recording);
if (!device->hidden->dev) {
return SDL_SetError("sio_open() failed");
}
device->hidden->pfd = SDL_malloc(sizeof(struct pollfd) * SNDIO_sio_nfds(device->hidden->dev));
if (!device->hidden->pfd) {
return false;
}
struct sio_par par;
SNDIO_sio_initpar(&par);
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
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(device->hidden->dev, &par) == 0) {
continue;
}
if (SNDIO_sio_getpar(device->hidden->dev, &par) == 0) {
return SDL_SetError("sio_getpar() failed");
}
if (par.bps != SIO_BPS(par.bits)) {
continue;
}
if ((par.bits == 8 * par.bps) || (par.msb)) {
break;
}
}
}
if (!test_format) {
return SDL_SetError("sndio: Unsupported audio format");
}
if ((par.bps == 4) && (par.sig) && (par.le)) {
device->spec.format = SDL_AUDIO_S32LE;
} else if ((par.bps == 4) && (par.sig) && (!par.le)) {
device->spec.format = SDL_AUDIO_S32BE;
} else if ((par.bps == 2) && (par.sig) && (par.le)) {
device->spec.format = SDL_AUDIO_S16LE;
} else if ((par.bps == 2) && (par.sig) && (!par.le)) {
device->spec.format = SDL_AUDIO_S16BE;
} else if ((par.bps == 1) && (par.sig)) {
device->spec.format = SDL_AUDIO_S8;
} else if ((par.bps == 1) && (!par.sig)) {
device->spec.format = SDL_AUDIO_U8;
} else {
return SDL_SetError("sndio: Got unsupported hardware audio format.");
}
device->spec.freq = par.rate;
device->spec.channels = par.pchan;
device->sample_frames = par.round;
// Calculate the final parameters for this audio specification
SDL_UpdatedAudioDeviceFormat(device);
// Allocate mixing buffer
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (!device->hidden->mixbuf) {
return false;
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
if (!SNDIO_sio_start(device->hidden->dev)) {
return SDL_SetError("sio_start() failed");
}
return true; // We're ready to rock and roll. :-)
}
static void SNDIO_Deinitialize(void)
{
UnloadSNDIOLibrary();
}
static void SNDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
{
*default_playback = SDL_AddAudioDevice(false, DEFAULT_PLAYBACK_DEVNAME, NULL, (void *)0x1);
*default_recording = SDL_AddAudioDevice(true, DEFAULT_RECORDING_DEVNAME, NULL, (void *)0x2);
}
static bool SNDIO_Init(SDL_AudioDriverImpl *impl)
{
if (!LoadSNDIOLibrary()) {
return false;
}
impl->OpenDevice = SNDIO_OpenDevice;
impl->WaitDevice = SNDIO_WaitDevice;
impl->PlayDevice = SNDIO_PlayDevice;
impl->GetDeviceBuf = SNDIO_GetDeviceBuf;
impl->CloseDevice = SNDIO_CloseDevice;
impl->WaitRecordingDevice = SNDIO_WaitDevice;
impl->RecordDevice = SNDIO_RecordDevice;
impl->FlushRecording = SNDIO_FlushRecording;
impl->Deinitialize = SNDIO_Deinitialize;
impl->DetectDevices = SNDIO_DetectDevices;
impl->HasRecordingSupport = true;
return true;
}
AudioBootStrap SNDIO_bootstrap = {
"sndio", "OpenBSD sndio", SNDIO_Init, false, false
};
#endif // SDL_AUDIO_DRIVER_SNDIO

View file

@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_sndioaudio_h_
#define SDL_sndioaudio_h_
#include <poll.h>
#include <sndio.h>
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
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_

View file

@ -0,0 +1,238 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_VITA
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "../SDL_audiodev_c.h"
#include "../SDL_sysaudio.h"
#include "SDL_vitaaudio.h"
#include <psp2/kernel/threadmgr.h>
#include <psp2/audioout.h>
#include <psp2/audioin.h>
#define SCE_AUDIO_SAMPLE_ALIGN(s) (((s) + 63) & ~63)
#define SCE_AUDIO_MAX_VOLUME 0x8000
static bool VITAAUD_OpenRecordingDevice(SDL_AudioDevice *device)
{
device->spec.freq = 16000;
device->spec.channels = 1;
device->sample_frames = 512;
SDL_UpdatedAudioDeviceFormat(device);
device->hidden->port = sceAudioInOpenPort(SCE_AUDIO_IN_PORT_TYPE_VOICE, 512, 16000, SCE_AUDIO_IN_PARAM_FORMAT_S16_MONO);
if (device->hidden->port < 0) {
return SDL_SetError("Couldn't open audio in port: %x", device->hidden->port);
}
return true;
}
static bool 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 };
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts;
device->hidden = (struct SDL_PrivateAudioData *)
SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return false;
}
closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
if (test_format == SDL_AUDIO_S16LE) {
device->spec.format = test_format;
break;
}
}
if (!test_format) {
return SDL_SetError("Unsupported audio format");
}
if (device->recording) {
return VITAAUD_OpenRecordingDevice(device);
}
// 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_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;
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
if (!device->hidden->rawbuf) {
return SDL_SetError("Couldn't allocate mixing buffer");
}
// Setup the hardware channel.
if (device->spec.channels == 1) {
format = SCE_AUDIO_OUT_MODE_MONO;
} else {
format = SCE_AUDIO_OUT_MODE_STEREO;
}
// 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;
}
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(device->hidden->port, SCE_AUDIO_VOLUME_FLAG_L_CH | SCE_AUDIO_VOLUME_FLAG_R_CH, vols);
SDL_memset(device->hidden->rawbuf, 0, mixlen);
for (i = 0; i < NUM_BUFFERS; i++) {
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
}
device->hidden->next_buffer = 0;
return true;
}
static bool VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
{
return (sceAudioOutOutput(device->hidden->port, buffer) == 0);
}
// This function waits until it is possible to write a full sound buffer
static bool VITAAUD_WaitDevice(SDL_AudioDevice *device)
{
// !!! FIXME: we might just need to sleep roughly as long as playback buffers take to process, based on sample rate, etc.
while (!SDL_GetAtomicInt(&device->shutdown) && (sceAudioOutGetRestSample(device->hidden->port) >= device->buffer_size)) {
SDL_Delay(1);
}
return true;
}
static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
Uint8 *result = device->hidden->mixbufs[device->hidden->next_buffer];
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
return result;
}
static void VITAAUD_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->port >= 0) {
if (device->recording) {
sceAudioInReleasePort(device->hidden->port);
} else {
sceAudioOutReleasePort(device->hidden->port);
}
device->hidden->port = -1;
}
if (!device->recording && device->hidden->rawbuf) {
SDL_aligned_free(device->hidden->rawbuf); // this uses SDL_aligned_alloc(), not SDL_malloc()
device->hidden->rawbuf = NULL;
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static bool VITAAUD_WaitRecordingDevice(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_GetAtomicInt(&device->shutdown) && (SDL_GetTicks() < endticks)) {
SDL_Delay(1);
}
return true;
}
static int VITAAUD_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
int ret;
SDL_assert(buflen == device->buffer_size);
ret = sceAudioInInput(device->hidden->port, buffer);
if (ret < 0) {
SDL_SetError("Failed to record from device: %x", ret);
return -1;
}
return device->buffer_size;
}
static void VITAAUD_FlushRecording(SDL_AudioDevice *device)
{
// 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();
info.size = sizeof(SceKernelThreadInfo);
if (sceKernelGetThreadInfo(thid, &info) == 0) {
sceKernelChangeThreadPriority(thid, info.currentPriority - 1);
}
}
static bool VITAAUD_Init(SDL_AudioDriverImpl *impl)
{
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->WaitRecordingDevice = VITAAUD_WaitRecordingDevice;
impl->FlushRecording = VITAAUD_FlushRecording;
impl->RecordDevice = VITAAUD_RecordDevice;
impl->HasRecordingSupport = true;
impl->OnlyHasDefaultPlaybackDevice = true;
impl->OnlyHasDefaultRecordingDevice = true;
return true;
}
AudioBootStrap VITAAUD_bootstrap = {
"vita", "VITA audio driver", VITAAUD_Init, false, false
};
#endif // SDL_AUDIO_DRIVER_VITA

View file

@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_vitaaudio_h
#define SDL_vitaaudio_h
#include "../SDL_sysaudio.h"
#define NUM_BUFFERS 2
struct SDL_PrivateAudioData
{
// The hardware input/output port.
int port;
// The raw allocated mixing buffer.
Uint8 *rawbuf;
// Individual mixing buffers.
Uint8 *mixbufs[NUM_BUFFERS];
// Index of the next available mixing buffer.
int next_buffer;
};
#endif // SDL_vitaaudio_h

View file

@ -0,0 +1,963 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_WASAPI
#include "../../core/windows/SDL_windows.h"
#include "../../core/windows/SDL_immdevice.h"
#include "../../thread/SDL_systhread.h"
#include "../SDL_sysaudio.h"
#define COBJMACROS
#include <audioclient.h>
#include "SDL_wasapi.h"
// These constants aren't available in older SDKs
#ifndef AUDCLNT_STREAMFLAGS_RATEADJUST
#define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000
#endif
#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
#endif
#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
#endif
// handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency).
static HMODULE libavrt = NULL;
typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL;
static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL;
// 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 const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
#ifdef __IAudioClient3_INTERFACE_DEFINED__
static const IID SDL_IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4, { 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } };
#endif //
static bool immdevice_initialized = false;
// 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
{
ManagementThreadTask fn;
void *userdata;
bool 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 = (ManagementThreadPendingTask *)SDL_GetAtomicPointer((void **)&ManagementThreadPendingTasks)) != NULL) || !SDL_GetAtomicInt(&ManagementThreadShutdown)) {
if (!task) {
SDL_WaitCondition(ManagementThreadCondition, ManagementThreadLock); // block until there's something to do.
} else {
SDL_SetAtomicPointer((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?
task->errorstr = SDL_strdup(SDL_GetError());
SDL_SignalSemaphore(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.
}
bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, bool *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) && (SDL_GetCurrentThreadID() == SDL_GetThreadID(ManagementThread))) {
*wait_on_result = task(userdata);
return true; // completed!
}
if (SDL_GetAtomicInt(&ManagementThreadShutdown)) {
return SDL_SetError("Can't add task, we're shutting down");
}
ManagementThreadPendingTask *pending = (ManagementThreadPendingTask *)SDL_calloc(1, sizeof(ManagementThreadPendingTask));
if (!pending) {
return false;
}
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 false;
}
}
pending->next = NULL;
SDL_LockMutex(ManagementThreadLock);
// add to end of task list.
ManagementThreadPendingTask *prev = NULL;
for (ManagementThreadPendingTask *i = (ManagementThreadPendingTask *)SDL_GetAtomicPointer((void **)&ManagementThreadPendingTasks); i; i = i->next) {
prev = i;
}
if (prev) {
prev->next = pending;
} else {
SDL_SetAtomicPointer((void **) &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 true; // successfully added (and possibly executed)!
}
static bool mgmtthrtask_AudioDeviceDisconnected(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
SDL_AudioDeviceDisconnected(device);
UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
return true;
}
static void AudioDeviceDisconnected(SDL_AudioDevice *device)
{
// don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
if (device) {
RefPhysicalAudioDevice(device); // make sure this lives until the task completes.
WASAPI_ProxyToManagementThread(mgmtthrtask_AudioDeviceDisconnected, device, NULL);
}
}
static bool mgmtthrtask_DefaultAudioDeviceChanged(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
SDL_DefaultAudioDeviceChanged(device);
UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
return true;
}
static void DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
{
// don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
if (new_default_device) {
RefPhysicalAudioDevice(new_default_device); // make sure this lives until the task completes.
WASAPI_ProxyToManagementThread(mgmtthrtask_DefaultAudioDeviceChanged, new_default_device, NULL);
}
}
static void StopWasapiHotplug(void)
{
if (immdevice_initialized) {
SDL_IMMDevice_Quit();
immdevice_initialized = false;
}
}
static void Deinit(void)
{
if (libavrt) {
FreeLibrary(libavrt);
libavrt = NULL;
}
pAvSetMmThreadCharacteristicsW = NULL;
pAvRevertMmThreadCharacteristics = NULL;
StopWasapiHotplug();
WIN_CoUninitialize();
}
static bool ManagementThreadPrepare(void)
{
const SDL_IMMDevice_callbacks callbacks = { AudioDeviceDisconnected, DefaultAudioDeviceChanged };
if (FAILED(WIN_CoInitialize())) {
return SDL_SetError("CoInitialize() failed");
} else if (!SDL_IMMDevice_Init(&callbacks)) {
return false; // Error string is set by SDL_IMMDevice_Init
}
immdevice_initialized = true;
libavrt = LoadLibrary(TEXT("avrt.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
if (libavrt) {
pAvSetMmThreadCharacteristicsW = (pfnAvSetMmThreadCharacteristicsW)GetProcAddress(libavrt, "AvSetMmThreadCharacteristicsW");
pAvRevertMmThreadCharacteristics = (pfnAvRevertMmThreadCharacteristics)GetProcAddress(libavrt, "AvRevertMmThreadCharacteristics");
}
ManagementThreadLock = SDL_CreateMutex();
if (!ManagementThreadLock) {
Deinit();
return false;
}
ManagementThreadCondition = SDL_CreateCondition();
if (!ManagementThreadCondition) {
SDL_DestroyMutex(ManagementThreadLock);
ManagementThreadLock = NULL;
Deinit();
return false;
}
return true;
}
typedef struct
{
char *errorstr;
SDL_Semaphore *ready_sem;
} ManagementThreadEntryData;
static int ManagementThreadEntry(void *userdata)
{
ManagementThreadEntryData *data = (ManagementThreadEntryData *)userdata;
if (!ManagementThreadPrepare()) {
data->errorstr = SDL_strdup(SDL_GetError());
SDL_SignalSemaphore(data->ready_sem); // unblock calling thread.
return 0;
}
SDL_SignalSemaphore(data->ready_sem); // unblock calling thread.
ManagementThreadMainloop();
Deinit();
return 0;
}
static bool InitManagementThread(void)
{
ManagementThreadEntryData mgmtdata;
SDL_zero(mgmtdata);
mgmtdata.ready_sem = SDL_CreateSemaphore(0);
if (!mgmtdata.ready_sem) {
return false;
}
SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, NULL);
SDL_SetAtomicInt(&ManagementThreadShutdown, 0);
ManagementThread = SDL_CreateThreadWithStackSize(ManagementThreadEntry, "SDLWASAPIMgmt", 256 * 1024, &mgmtdata); // !!! FIXME: maybe even smaller stack size?
if (!ManagementThread) {
return false;
}
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 false;
}
return true;
}
static void DeinitManagementThread(void)
{
if (ManagementThread) {
SDL_SetAtomicInt(&ManagementThreadShutdown, 1);
SDL_LockMutex(ManagementThreadLock);
SDL_SignalCondition(ManagementThreadCondition);
SDL_UnlockMutex(ManagementThreadLock);
SDL_WaitThread(ManagementThread, NULL);
ManagementThread = NULL;
}
SDL_assert(SDL_GetAtomicPointer((void **) &ManagementThreadPendingTasks) == NULL);
SDL_DestroyCondition(ManagementThreadCondition);
SDL_DestroyMutex(ManagementThreadLock);
ManagementThreadCondition = NULL;
ManagementThreadLock = NULL;
SDL_SetAtomicInt(&ManagementThreadShutdown, 0);
}
typedef struct
{
SDL_AudioDevice **default_playback;
SDL_AudioDevice **default_recording;
} mgmtthrtask_DetectDevicesData;
static bool mgmtthrtask_DetectDevices(void *userdata)
{
mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata;
SDL_IMMDevice_EnumerateEndpoints(data->default_playback, data->default_recording);
return true;
}
static void WASAPI_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
{
bool rc;
// this blocks because it needs to finish before the audio subsystem inits
mgmtthrtask_DetectDevicesData data;
data.default_playback = default_playback;
data.default_recording = default_recording;
WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc);
}
static bool mgmtthrtask_DisconnectDevice(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
SDL_AudioDeviceDisconnected(device);
UnrefPhysicalAudioDevice(device);
return true;
}
void WASAPI_DisconnectDevice(SDL_AudioDevice *device)
{
if (SDL_CompareAndSwapAtomicInt(&device->hidden->device_disconnecting, 0, 1)) {
RefPhysicalAudioDevice(device); // will unref when the task ends.
WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, NULL);
}
}
static bool WasapiFailed(SDL_AudioDevice *device, const HRESULT err)
{
if (err == S_OK) {
return false;
} else if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
device->hidden->device_lost = true;
} else {
device->hidden->device_dead = true;
}
return true;
}
static bool mgmtthrtask_StopAndReleaseClient(void *userdata)
{
IAudioClient *client = (IAudioClient *) userdata;
IAudioClient_Stop(client);
IAudioClient_Release(client);
return true;
}
static bool mgmtthrtask_ReleaseCaptureClient(void *userdata)
{
IAudioCaptureClient_Release((IAudioCaptureClient *)userdata);
return true;
}
static bool mgmtthrtask_ReleaseRenderClient(void *userdata)
{
IAudioRenderClient_Release((IAudioRenderClient *)userdata);
return true;
}
static bool mgmtthrtask_CoTaskMemFree(void *userdata)
{
CoTaskMemFree(userdata);
return true;
}
static bool mgmtthrtask_CloseHandle(void *userdata)
{
CloseHandle((HANDLE) userdata);
return true;
}
static void ResetWasapiDevice(SDL_AudioDevice *device)
{
if (!device || !device->hidden) {
return;
}
// just queue up all the tasks in the management thread and don't block.
// We don't care when any of these actually get free'd.
if (device->hidden->client) {
IAudioClient *client = device->hidden->client;
device->hidden->client = NULL;
WASAPI_ProxyToManagementThread(mgmtthrtask_StopAndReleaseClient, client, NULL);
}
if (device->hidden->render) {
IAudioRenderClient *render = device->hidden->render;
device->hidden->render = NULL;
WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseRenderClient, render, NULL);
}
if (device->hidden->capture) {
IAudioCaptureClient *capture = device->hidden->capture;
device->hidden->capture = NULL;
WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseCaptureClient, capture, NULL);
}
if (device->hidden->waveformat) {
void *ptr = device->hidden->waveformat;
device->hidden->waveformat = NULL;
WASAPI_ProxyToManagementThread(mgmtthrtask_CoTaskMemFree, ptr, NULL);
}
if (device->hidden->event) {
HANDLE event = device->hidden->event;
device->hidden->event = NULL;
WASAPI_ProxyToManagementThread(mgmtthrtask_CloseHandle, (void *) event, NULL);
}
}
static bool mgmtthrtask_ActivateDevice(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
IMMDevice *immdevice = NULL;
if (!SDL_IMMDevice_Get(device, &immdevice, device->recording)) {
device->hidden->client = NULL;
return false; // This is already set by SDL_IMMDevice_Get
}
// 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(device->hidden->client == NULL);
return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret);
}
SDL_assert(device->hidden->client != NULL);
if (!WASAPI_PrepDevice(device)) { // not async, fire it right away.
return false;
}
return true; // good to go.
}
static bool ActivateWasapiDevice(SDL_AudioDevice *device)
{
// this blocks because we're either being notified from a background thread or we're running during device open,
// both of which won't deadlock vs the device thread.
bool rc = false;
return (WASAPI_ProxyToManagementThread(mgmtthrtask_ActivateDevice, device, &rc) && rc);
}
// do not call when holding the device lock!
static 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)) {
WASAPI_DisconnectDevice(device);
return false;
}
device->hidden->device_lost = false;
return true; // okay, carry on with new device details!
}
// do not call when holding the device lock!
static bool RecoverWasapiIfLost(SDL_AudioDevice *device)
{
if (SDL_GetAtomicInt(&device->shutdown)) {
return false; // closing, stop trying.
} else if (SDL_GetAtomicInt(&device->hidden->device_disconnecting)) {
return false; // failing via the WASAPI management thread, stop trying.
} else if (device->hidden->device_dead) { // had a fatal error elsewhere, clean up and quit
IAudioClient_Stop(device->hidden->client);
WASAPI_DisconnectDevice(device);
SDL_assert(SDL_GetAtomicInt(&device->shutdown)); // so we don't come back through here.
return false; // already failed.
} else if (SDL_GetAtomicInt(&device->zombie)) {
return false; // we're already dead, so just leave and let the Zombie implementations take over.
} else if (!device->hidden->client) {
return true; // still waiting for activation.
}
return device->hidden->device_lost ? RecoverWasapiDevice(device) : true;
}
static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
// get an endpoint buffer from WASAPI.
BYTE *buffer = NULL;
if (device->hidden->render) {
const HRESULT ret = IAudioRenderClient_GetBuffer(device->hidden->render, device->sample_frames, &buffer);
if (ret == AUDCLNT_E_BUFFER_TOO_LARGE) {
SDL_assert(buffer == NULL);
*buffer_size = 0; // just go back to WaitDevice and try again after the hardware has consumed some more data.
} else if (WasapiFailed(device, ret)) {
SDL_assert(buffer == NULL);
if (device->hidden->device_lost) { // just use an available buffer, we won't be playing it anyhow.
*buffer_size = 0; // we'll recover during WaitDevice and try again.
}
}
}
return (Uint8 *)buffer;
}
static bool WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
if (device->hidden->render && !SDL_GetAtomicInt(&device->hidden->device_disconnecting)) { // definitely activated?
// WasapiFailed() will mark the device for reacquisition or removal elsewhere.
WasapiFailed(device, IAudioRenderClient_ReleaseBuffer(device->hidden->render, device->sample_frames, 0));
}
return true;
}
static bool WASAPI_WaitDevice(SDL_AudioDevice *device)
{
// WaitDevice does not hold the device lock, so check for recovery/disconnect details here.
while (RecoverWasapiIfLost(device) && device->hidden->client && device->hidden->event) {
if (device->recording) {
// Recording devices should return immediately if there is any data available
UINT32 padding = 0;
if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
//SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);
if (padding > 0) {
break;
}
}
switch (WaitForSingleObjectEx(device->hidden->event, 200, FALSE)) {
case WAIT_OBJECT_0:
case WAIT_TIMEOUT:
break;
default:
//SDL_Log("WASAPI FAILED EVENT!");
IAudioClient_Stop(device->hidden->client);
return false;
}
} else {
DWORD waitResult = WaitForSingleObjectEx(device->hidden->event, 200, FALSE);
if (waitResult == WAIT_OBJECT_0) {
UINT32 padding = 0;
if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
//SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);
if (padding <= (UINT32)device->sample_frames) {
break;
}
}
} else if (waitResult != WAIT_TIMEOUT) {
//SDL_Log("WASAPI FAILED EVENT!");*/
IAudioClient_Stop(device->hidden->client);
return false;
}
}
}
return true;
}
static int WASAPI_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
BYTE *ptr = NULL;
UINT32 frames = 0;
DWORD flags = 0;
while (device->hidden->capture) {
const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
if (ret == AUDCLNT_S_BUFFER_EMPTY) {
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 SDL3.
}
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 bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? true : 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);
}
WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames));
return cpy;
}
}
return -1; // unrecoverable error.
}
static void WASAPI_FlushRecording(SDL_AudioDevice *device)
{
BYTE *ptr = NULL;
UINT32 frames = 0;
DWORD flags = 0;
// just read until we stop getting packets, throwing them away.
while (!SDL_GetAtomicInt(&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 *device)
{
if (device->hidden) {
ResetWasapiDevice(device);
SDL_free(device->hidden->devid);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static bool 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
!!! everything else. Doing this means any other sound using this device will
!!! stop playing, including the user's MP3 player and system notification
!!! sounds. You'd probably need to release the device when the app isn't in
!!! the foreground, to be a good citizen of the system. It's doable, but it's
!!! more work and causes some annoyances, and I don't know what the latency
!!! 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 AUDCLNT_SHAREMODE sharemode = AUDCLNT_SHAREMODE_SHARED;
IAudioClient *client = device->hidden->client;
SDL_assert(client != NULL);
device->hidden->event = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!device->hidden->event) {
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);
device->hidden->waveformat = waveformat;
SDL_AudioSpec newspec;
newspec.channels = (Uint8)waveformat->nChannels;
// Make sure we have a valid format that we can convert to whatever WASAPI wants.
const SDL_AudioFormat wasapi_format = SDL_WaveFormatExToSDLFormat(waveformat);
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
if (test_format == wasapi_format) {
newspec.format = test_format;
break;
}
}
if (!test_format) {
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 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 = device->spec.freq;
waveformat->nAvgBytesPerSec = waveformat->nSamplesPerSec * waveformat->nChannels * (waveformat->wBitsPerSample / 8);
}
#endif
newspec.freq = waveformat->nSamplesPerSec;
streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
int new_sample_frames = 0;
bool iaudioclient3_initialized = false;
#ifdef __IAudioClient3_INTERFACE_DEFINED__
// Try querying IAudioClient3 if sharemode is AUDCLNT_SHAREMODE_SHARED
if (sharemode == AUDCLNT_SHAREMODE_SHARED) {
IAudioClient3 *client3 = NULL;
ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient3, (void**)&client3);
if (SUCCEEDED(ret)) {
UINT32 default_period_in_frames = 0;
UINT32 fundamental_period_in_frames = 0;
UINT32 min_period_in_frames = 0;
UINT32 max_period_in_frames = 0;
ret = IAudioClient3_GetSharedModeEnginePeriod(client3, waveformat,
&default_period_in_frames, &fundamental_period_in_frames, &min_period_in_frames, &max_period_in_frames);
if (SUCCEEDED(ret)) {
// IAudioClient3_InitializeSharedAudioStream only accepts the integral multiple of fundamental_period_in_frames
UINT32 period_in_frames = fundamental_period_in_frames * (UINT32)SDL_round((double)device->sample_frames / fundamental_period_in_frames);
period_in_frames = SDL_clamp(period_in_frames, min_period_in_frames, max_period_in_frames);
ret = IAudioClient3_InitializeSharedAudioStream(client3, streamflags, period_in_frames, waveformat, NULL);
if (SUCCEEDED(ret)) {
new_sample_frames = (int)period_in_frames;
iaudioclient3_initialized = true;
}
}
IAudioClient3_Release(client3);
}
}
#endif
if (!iaudioclient3_initialized)
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, 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);
}
// Match the callback size to the period size to cut down on the number of
// interrupts waited for in each call to WaitDevice
if (new_sample_frames <= 0) {
const float period_millis = default_period / 10000.0f;
const float period_frames = period_millis * newspec.freq / 1000.0f;
new_sample_frames = (int) SDL_ceilf(period_frames);
}
// regardless of what we calculated for the period size, clamp it to the expected hardware buffer size.
if (new_sample_frames > (int) bufsize) {
new_sample_frames = (int) bufsize;
}
// Update the fragment size as size in bytes
if (!SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames)) {
return false;
}
device->hidden->framesize = SDL_AUDIO_FRAMESIZE(device->spec);
if (device->recording) {
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);
device->hidden->capture = capture;
ret = IAudioClient_Start(client);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't start capture", ret);
}
WASAPI_FlushRecording(device); // MSDN says you should flush the recording 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);
device->hidden->render = render;
ret = IAudioClient_Start(client);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't start playback", ret);
}
}
return true; // good to go.
}
// This is called once a device is activated, possibly asynchronously.
bool WASAPI_PrepDevice(SDL_AudioDevice *device)
{
bool rc = true;
return (WASAPI_ProxyToManagementThread(mgmtthrtask_PrepDevice, device, &rc) && rc);
}
static bool 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) {
return false;
} else if (!ActivateWasapiDevice(device)) {
return false; // already set error.
}
/* Ready, but possibly waiting for async device activation.
Until activation is successful, we will report silence from recording
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 true;
}
static void WASAPI_ThreadInit(SDL_AudioDevice *device)
{
// this thread uses COM.
if (SUCCEEDED(WIN_CoInitialize())) { // can't report errors, hope it worked!
device->hidden->coinitialized = true;
}
// Set this thread to very high "Pro Audio" priority.
if (pAvSetMmThreadCharacteristicsW) {
DWORD idx = 0;
device->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx);
} else {
SDL_SetCurrentThreadPriority(device->recording ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
}
}
static void WASAPI_ThreadDeinit(SDL_AudioDevice *device)
{
// Set this thread back to normal priority.
if (device->hidden->task && pAvRevertMmThreadCharacteristics) {
pAvRevertMmThreadCharacteristics(device->hidden->task);
device->hidden->task = NULL;
}
if (device->hidden->coinitialized) {
WIN_CoUninitialize();
device->hidden->coinitialized = false;
}
}
static bool mgmtthrtask_FreeDeviceHandle(void *userdata)
{
SDL_IMMDevice_FreeDeviceHandle((SDL_AudioDevice *) userdata);
return true;
}
static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device)
{
bool rc;
WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc);
}
static bool mgmtthrtask_DeinitializeStart(void *userdata)
{
StopWasapiHotplug();
return true;
}
static void WASAPI_DeinitializeStart(void)
{
bool rc;
WASAPI_ProxyToManagementThread(mgmtthrtask_DeinitializeStart, NULL, &rc);
}
static void WASAPI_Deinitialize(void)
{
DeinitManagementThread();
}
static bool WASAPI_Init(SDL_AudioDriverImpl *impl)
{
if (!InitManagementThread()) {
return false;
}
impl->DetectDevices = WASAPI_DetectDevices;
impl->ThreadInit = WASAPI_ThreadInit;
impl->ThreadDeinit = WASAPI_ThreadDeinit;
impl->OpenDevice = WASAPI_OpenDevice;
impl->PlayDevice = WASAPI_PlayDevice;
impl->WaitDevice = WASAPI_WaitDevice;
impl->GetDeviceBuf = WASAPI_GetDeviceBuf;
impl->WaitRecordingDevice = WASAPI_WaitDevice;
impl->RecordDevice = WASAPI_RecordDevice;
impl->FlushRecording = WASAPI_FlushRecording;
impl->CloseDevice = WASAPI_CloseDevice;
impl->DeinitializeStart = WASAPI_DeinitializeStart;
impl->Deinitialize = WASAPI_Deinitialize;
impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle;
impl->HasRecordingSupport = true;
return true;
}
AudioBootStrap WASAPI_bootstrap = {
"wasapi", "WASAPI", WASAPI_Init, false, false
};
#endif // SDL_AUDIO_DRIVER_WASAPI

View file

@ -0,0 +1,61 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_wasapi_h_
#define SDL_wasapi_h_
#ifdef __cplusplus
extern "C" {
#endif
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
WCHAR *devid;
WAVEFORMATEX *waveformat;
IAudioClient *client;
IAudioRenderClient *render;
IAudioCaptureClient *capture;
HANDLE event;
HANDLE task;
bool coinitialized;
int framesize;
SDL_AtomicInt device_disconnecting;
bool device_lost;
bool device_dead;
};
// win32 implementation calls into these.
bool WASAPI_PrepDevice(SDL_AudioDevice *device);
void WASAPI_DisconnectDevice(SDL_AudioDevice *device); // don't hold the device lock when calling this!
// BE CAREFUL: if you are holding the device lock and proxy to the management thread with wait_until_complete, and grab the lock again, you will deadlock.
typedef bool (*ManagementThreadTask)(void *userdata);
bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, bool *wait_until_complete);
#ifdef __cplusplus
}
#endif
#endif // SDL_wasapi_h_

1583
vendor/sdl-3.2.10/src/camera/SDL_camera.c vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_camera_c_h_
#define SDL_camera_c_h_
// Initialize the camera subsystem
extern bool SDL_CameraInit(const char *driver_name);
// Shutdown the camera subsystem
extern void SDL_QuitCamera(void);
// "Pump" the event queue.
extern void SDL_UpdateCamera(void);
#endif // SDL_camera_c_h_

View file

@ -0,0 +1,224 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_syscamera_h_
#define SDL_syscamera_h_
#include "../video/SDL_surface_c.h"
#define DEBUG_CAMERA 0
/* Backends should call this as devices are added to the system (such as
a USB camera being plugged in), and should also be called for
for every device found during DetectDevices(). */
extern SDL_Camera *SDL_AddCamera(const char *name, SDL_CameraPosition position, int num_specs, const SDL_CameraSpec *specs, void *handle);
/* Backends should call this if an opened camera device is lost.
This can happen due to i/o errors, or a device being unplugged, etc. */
extern void SDL_CameraDisconnected(SDL_Camera *device);
// Find an SDL_Camera, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
extern SDL_Camera *SDL_FindPhysicalCameraByCallback(bool (*callback)(SDL_Camera *device, void *userdata), void *userdata);
// Backends should call this when the user has approved/denied access to a camera.
extern void SDL_CameraPermissionOutcome(SDL_Camera *device, bool approved);
// Backends can call this to get a standardized name for a thread to power a specific camera device.
extern char *SDL_GetCameraThreadName(SDL_Camera *device, char *buf, size_t buflen);
// Backends can call these to change a device's refcount.
extern void RefPhysicalCamera(SDL_Camera *device);
extern void UnrefPhysicalCamera(SDL_Camera *device);
// These functions are the heart of the camera threads. Backends can call them directly if they aren't using the SDL-provided thread.
extern void SDL_CameraThreadSetup(SDL_Camera *device);
extern bool SDL_CameraThreadIterate(SDL_Camera *device);
extern void SDL_CameraThreadShutdown(SDL_Camera *device);
// Backends can call this if they have to finish initializing later, like Emscripten. Most backends should _not_ call this directly!
extern bool SDL_PrepareCameraSurfaces(SDL_Camera *device);
// common utility functionality to gather up camera specs. Not required!
typedef struct CameraFormatAddData
{
SDL_CameraSpec *specs;
int num_specs;
int allocated_specs;
} CameraFormatAddData;
bool SDL_AddCameraFormat(CameraFormatAddData *data, SDL_PixelFormat format, SDL_Colorspace colorspace, int w, int h, int framerate_numerator, int framerate_denominator);
typedef enum SDL_CameraFrameResult
{
SDL_CAMERA_FRAME_ERROR,
SDL_CAMERA_FRAME_SKIP,
SDL_CAMERA_FRAME_READY
} SDL_CameraFrameResult;
typedef struct SurfaceList
{
SDL_Surface *surface;
Uint64 timestampNS;
struct SurfaceList *next;
} SurfaceList;
// Define the SDL camera driver structure
struct SDL_Camera
{
// A mutex for locking
SDL_Mutex *lock;
// Human-readable device name.
char *name;
// Position of camera (front-facing, back-facing, etc).
SDL_CameraPosition position;
// When refcount hits zero, we destroy the device object.
SDL_AtomicInt refcount;
// These are, initially, set from camera_driver, but we might swap them out with Zombie versions on disconnect/failure.
bool (*WaitDevice)(SDL_Camera *device);
SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS);
void (*ReleaseFrame)(SDL_Camera *device, SDL_Surface *frame);
// All supported formats/dimensions for this device.
SDL_CameraSpec *all_specs;
// Elements in all_specs.
int num_specs;
// The device's actual specification that the camera is outputting, before conversion.
SDL_CameraSpec actual_spec;
// The device's current camera specification, after conversions.
SDL_CameraSpec spec;
// Unique value assigned at creation time.
SDL_CameraID instance_id;
// Driver-specific hardware data on how to open device (`hidden` is driver-specific data _when opened_).
void *handle;
// Dropping the first frame(s) after open seems to help timing on some platforms.
int drop_frames;
// Backend timestamp of first acquired frame, so we can keep these meaningful regardless of epoch.
Uint64 base_timestamp;
// SDL timestamp of first acquired frame, so we can roughly convert to SDL ticks.
Uint64 adjust_timestamp;
// Pixel data flows from the driver into these, then gets converted for the app if necessary.
SDL_Surface *acquire_surface;
// acquire_surface converts or scales to this surface before landing in output_surfaces, if necessary.
SDL_Surface *conversion_surface;
// A queue of surfaces that buffer converted/scaled frames of video until the app claims them.
SurfaceList output_surfaces[8];
SurfaceList filled_output_surfaces; // this is FIFO
SurfaceList empty_output_surfaces; // this is LIFO
SurfaceList app_held_output_surfaces;
// A fake video frame we allocate if the camera fails/disconnects.
Uint8 *zombie_pixels;
// non-zero if acquire_surface needs to be scaled for final output.
int needs_scaling; // -1: downscale, 0: no scaling, 1: upscale
// true if acquire_surface needs to be converted for final output.
bool needs_conversion;
// Current state flags
SDL_AtomicInt shutdown;
SDL_AtomicInt zombie;
// A thread to feed the camera device
SDL_Thread *thread;
// Optional properties.
SDL_PropertiesID props;
// -1: user denied permission, 0: waiting for user response, 1: user approved permission.
int permission;
// Data private to this driver, used when device is opened and running.
struct SDL_PrivateCameraData *hidden;
};
typedef struct SDL_CameraDriverImpl
{
void (*DetectDevices)(void);
bool (*OpenDevice)(SDL_Camera *device, const SDL_CameraSpec *spec);
void (*CloseDevice)(SDL_Camera *device);
bool (*WaitDevice)(SDL_Camera *device);
SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS); // set frame->pixels, frame->pitch, and *timestampNS!
void (*ReleaseFrame)(SDL_Camera *device, SDL_Surface *frame); // Reclaim frame->pixels and frame->pitch!
void (*FreeDeviceHandle)(SDL_Camera *device); // SDL is done with this device; free the handle from SDL_AddCamera()
void (*Deinitialize)(void);
bool ProvidesOwnCallbackThread;
} SDL_CameraDriverImpl;
typedef struct SDL_PendingCameraEvent
{
Uint32 type;
SDL_CameraID devid;
struct SDL_PendingCameraEvent *next;
} SDL_PendingCameraEvent;
typedef struct SDL_CameraDriver
{
const char *name; // The name of this camera driver
const char *desc; // The description of this camera driver
SDL_CameraDriverImpl impl; // the backend's interface
SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash` // !!! FIXME: device_hash _also_ has a rwlock, see if we still need this one.
SDL_HashTable *device_hash; // the collection of currently-available camera devices
SDL_PendingCameraEvent pending_events;
SDL_PendingCameraEvent *pending_events_tail;
SDL_AtomicInt device_count;
SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs.
} SDL_CameraDriver;
typedef struct CameraBootStrap
{
const char *name;
const char *desc;
bool (*init)(SDL_CameraDriverImpl *impl);
bool demand_only; // if true: request explicitly, or it won't be available.
} CameraBootStrap;
// Not all of these are available in a given build. Use #ifdefs, etc.
extern CameraBootStrap DUMMYCAMERA_bootstrap;
extern CameraBootStrap PIPEWIRECAMERA_bootstrap;
extern CameraBootStrap V4L2_bootstrap;
extern CameraBootStrap COREMEDIA_bootstrap;
extern CameraBootStrap ANDROIDCAMERA_bootstrap;
extern CameraBootStrap EMSCRIPTENCAMERA_bootstrap;
extern CameraBootStrap MEDIAFOUNDATION_bootstrap;
extern CameraBootStrap VITACAMERA_bootstrap;
#endif // SDL_syscamera_h_

View file

@ -0,0 +1,905 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "../SDL_syscamera.h"
#include "../SDL_camera_c.h"
#include "../../video/SDL_pixels_c.h"
#include "../../video/SDL_surface_c.h"
#include "../../thread/SDL_systhread.h"
#ifdef SDL_CAMERA_DRIVER_ANDROID
/*
* AndroidManifest.xml:
* <uses-permission android:name="android.permission.CAMERA"></uses-permission>
* <uses-feature android:name="android.hardware.camera" />
*
* Very likely SDL must be build with YUV support (done by default)
*
* https://developer.android.com/reference/android/hardware/camera2/CameraManager
* "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
* before configuring sessions on any of the camera devices."
*/
// this is kinda gross, but on older NDK headers all the camera stuff is
// gated behind __ANDROID_API__. We'll dlopen() it at runtime, so we'll do
// the right thing on pre-Android 7.0 devices, but we still
// need the struct declarations and such in those headers.
// The other option is to make a massive jump in minimum Android version we
// support--going from ancient to merely really old--but this seems less
// distasteful and using dlopen matches practices on other SDL platforms.
// We'll see if it works out.
#if __ANDROID_API__ < 24
#undef __ANDROID_API__
#define __ANDROID_API__ 24
#endif
#include <dlfcn.h>
#include <camera/NdkCameraDevice.h>
#include <camera/NdkCameraManager.h>
#include <media/NdkImage.h>
#include <media/NdkImageReader.h>
#include "../../core/android/SDL_android.h"
static void *libcamera2ndk = NULL;
typedef ACameraManager* (*pfnACameraManager_create)(void);
typedef camera_status_t (*pfnACameraManager_registerAvailabilityCallback)(ACameraManager*, const ACameraManager_AvailabilityCallbacks*);
typedef camera_status_t (*pfnACameraManager_unregisterAvailabilityCallback)(ACameraManager*, const ACameraManager_AvailabilityCallbacks*);
typedef camera_status_t (*pfnACameraManager_getCameraIdList)(ACameraManager*, ACameraIdList**);
typedef void (*pfnACameraManager_deleteCameraIdList)(ACameraIdList*);
typedef void (*pfnACameraCaptureSession_close)(ACameraCaptureSession*);
typedef void (*pfnACaptureRequest_free)(ACaptureRequest*);
typedef void (*pfnACameraOutputTarget_free)(ACameraOutputTarget*);
typedef camera_status_t (*pfnACameraDevice_close)(ACameraDevice*);
typedef void (*pfnACameraManager_delete)(ACameraManager*);
typedef void (*pfnACaptureSessionOutputContainer_free)(ACaptureSessionOutputContainer*);
typedef void (*pfnACaptureSessionOutput_free)(ACaptureSessionOutput*);
typedef camera_status_t (*pfnACameraManager_openCamera)(ACameraManager*, const char*, ACameraDevice_StateCallbacks*, ACameraDevice**);
typedef camera_status_t (*pfnACameraDevice_createCaptureRequest)(const ACameraDevice*, ACameraDevice_request_template, ACaptureRequest**);
typedef camera_status_t (*pfnACameraDevice_createCaptureSession)(ACameraDevice*, const ACaptureSessionOutputContainer*, const ACameraCaptureSession_stateCallbacks*,ACameraCaptureSession**);
typedef camera_status_t (*pfnACameraManager_getCameraCharacteristics)(ACameraManager*, const char*, ACameraMetadata**);
typedef void (*pfnACameraMetadata_free)(ACameraMetadata*);
typedef camera_status_t (*pfnACameraMetadata_getConstEntry)(const ACameraMetadata*, uint32_t tag, ACameraMetadata_const_entry*);
typedef camera_status_t (*pfnACameraCaptureSession_setRepeatingRequest)(ACameraCaptureSession*, ACameraCaptureSession_captureCallbacks*, int numRequests, ACaptureRequest**, int*);
typedef camera_status_t (*pfnACameraOutputTarget_create)(ACameraWindowType*,ACameraOutputTarget**);
typedef camera_status_t (*pfnACaptureRequest_addTarget)(ACaptureRequest*, const ACameraOutputTarget*);
typedef camera_status_t (*pfnACaptureSessionOutputContainer_add)(ACaptureSessionOutputContainer*, const ACaptureSessionOutput*);
typedef camera_status_t (*pfnACaptureSessionOutputContainer_create)(ACaptureSessionOutputContainer**);
typedef camera_status_t (*pfnACaptureSessionOutput_create)(ACameraWindowType*, ACaptureSessionOutput**);
static pfnACameraManager_create pACameraManager_create = NULL;
static pfnACameraManager_registerAvailabilityCallback pACameraManager_registerAvailabilityCallback = NULL;
static pfnACameraManager_unregisterAvailabilityCallback pACameraManager_unregisterAvailabilityCallback = NULL;
static pfnACameraManager_getCameraIdList pACameraManager_getCameraIdList = NULL;
static pfnACameraManager_deleteCameraIdList pACameraManager_deleteCameraIdList = NULL;
static pfnACameraCaptureSession_close pACameraCaptureSession_close = NULL;
static pfnACaptureRequest_free pACaptureRequest_free = NULL;
static pfnACameraOutputTarget_free pACameraOutputTarget_free = NULL;
static pfnACameraDevice_close pACameraDevice_close = NULL;
static pfnACameraManager_delete pACameraManager_delete = NULL;
static pfnACaptureSessionOutputContainer_free pACaptureSessionOutputContainer_free = NULL;
static pfnACaptureSessionOutput_free pACaptureSessionOutput_free = NULL;
static pfnACameraManager_openCamera pACameraManager_openCamera = NULL;
static pfnACameraDevice_createCaptureRequest pACameraDevice_createCaptureRequest = NULL;
static pfnACameraDevice_createCaptureSession pACameraDevice_createCaptureSession = NULL;
static pfnACameraManager_getCameraCharacteristics pACameraManager_getCameraCharacteristics = NULL;
static pfnACameraMetadata_free pACameraMetadata_free = NULL;
static pfnACameraMetadata_getConstEntry pACameraMetadata_getConstEntry = NULL;
static pfnACameraCaptureSession_setRepeatingRequest pACameraCaptureSession_setRepeatingRequest = NULL;
static pfnACameraOutputTarget_create pACameraOutputTarget_create = NULL;
static pfnACaptureRequest_addTarget pACaptureRequest_addTarget = NULL;
static pfnACaptureSessionOutputContainer_add pACaptureSessionOutputContainer_add = NULL;
static pfnACaptureSessionOutputContainer_create pACaptureSessionOutputContainer_create = NULL;
static pfnACaptureSessionOutput_create pACaptureSessionOutput_create = NULL;
static void *libmediandk = NULL;
typedef void (*pfnAImage_delete)(AImage*);
typedef media_status_t (*pfnAImage_getTimestamp)(const AImage*, int64_t*);
typedef media_status_t (*pfnAImage_getNumberOfPlanes)(const AImage*, int32_t*);
typedef media_status_t (*pfnAImage_getPlaneRowStride)(const AImage*, int, int32_t*);
typedef media_status_t (*pfnAImage_getPlaneData)(const AImage*, int, uint8_t**, int*);
typedef media_status_t (*pfnAImageReader_acquireNextImage)(AImageReader*, AImage**);
typedef void (*pfnAImageReader_delete)(AImageReader*);
typedef media_status_t (*pfnAImageReader_setImageListener)(AImageReader*, AImageReader_ImageListener*);
typedef media_status_t (*pfnAImageReader_getWindow)(AImageReader*, ANativeWindow**);
typedef media_status_t (*pfnAImageReader_new)(int32_t, int32_t, int32_t, int32_t, AImageReader**);
static pfnAImage_delete pAImage_delete = NULL;
static pfnAImage_getTimestamp pAImage_getTimestamp = NULL;
static pfnAImage_getNumberOfPlanes pAImage_getNumberOfPlanes = NULL;
static pfnAImage_getPlaneRowStride pAImage_getPlaneRowStride = NULL;
static pfnAImage_getPlaneData pAImage_getPlaneData = NULL;
static pfnAImageReader_acquireNextImage pAImageReader_acquireNextImage = NULL;
static pfnAImageReader_delete pAImageReader_delete = NULL;
static pfnAImageReader_setImageListener pAImageReader_setImageListener = NULL;
static pfnAImageReader_getWindow pAImageReader_getWindow = NULL;
static pfnAImageReader_new pAImageReader_new = NULL;
typedef media_status_t (*pfnAImage_getWidth)(const AImage*, int32_t*);
typedef media_status_t (*pfnAImage_getHeight)(const AImage*, int32_t*);
static pfnAImage_getWidth pAImage_getWidth = NULL;
static pfnAImage_getHeight pAImage_getHeight = NULL;
struct SDL_PrivateCameraData
{
ACameraDevice *device;
AImageReader *reader;
ANativeWindow *window;
ACaptureSessionOutput *sessionOutput;
ACaptureSessionOutputContainer *sessionOutputContainer;
ACameraOutputTarget *outputTarget;
ACaptureRequest *request;
ACameraCaptureSession *session;
SDL_CameraSpec requested_spec;
};
static bool SetErrorStr(const char *what, const char *errstr, const int rc)
{
char errbuf[128];
if (!errstr) {
SDL_snprintf(errbuf, sizeof (errbuf), "Unknown error #%d", rc);
errstr = errbuf;
}
return SDL_SetError("%s: %s", what, errstr);
}
static const char *CameraStatusStr(const camera_status_t rc)
{
switch (rc) {
case ACAMERA_OK: return "no error";
case ACAMERA_ERROR_UNKNOWN: return "unknown error";
case ACAMERA_ERROR_INVALID_PARAMETER: return "invalid parameter";
case ACAMERA_ERROR_CAMERA_DISCONNECTED: return "camera disconnected";
case ACAMERA_ERROR_NOT_ENOUGH_MEMORY: return "not enough memory";
case ACAMERA_ERROR_METADATA_NOT_FOUND: return "metadata not found";
case ACAMERA_ERROR_CAMERA_DEVICE: return "camera device error";
case ACAMERA_ERROR_CAMERA_SERVICE: return "camera service error";
case ACAMERA_ERROR_SESSION_CLOSED: return "session closed";
case ACAMERA_ERROR_INVALID_OPERATION: return "invalid operation";
case ACAMERA_ERROR_STREAM_CONFIGURE_FAIL: return "configure failure";
case ACAMERA_ERROR_CAMERA_IN_USE: return "camera in use";
case ACAMERA_ERROR_MAX_CAMERA_IN_USE: return "max cameras in use";
case ACAMERA_ERROR_CAMERA_DISABLED: return "camera disabled";
case ACAMERA_ERROR_PERMISSION_DENIED: return "permission denied";
case ACAMERA_ERROR_UNSUPPORTED_OPERATION: return "unsupported operation";
default: break;
}
return NULL; // unknown error
}
static bool SetCameraError(const char *what, const camera_status_t rc)
{
return SetErrorStr(what, CameraStatusStr(rc), (int) rc);
}
static const char *MediaStatusStr(const media_status_t rc)
{
switch (rc) {
case AMEDIA_OK: return "no error";
case AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE: return "insufficient resources";
case AMEDIACODEC_ERROR_RECLAIMED: return "reclaimed";
case AMEDIA_ERROR_UNKNOWN: return "unknown error";
case AMEDIA_ERROR_MALFORMED: return "malformed";
case AMEDIA_ERROR_UNSUPPORTED: return "unsupported";
case AMEDIA_ERROR_INVALID_OBJECT: return "invalid object";
case AMEDIA_ERROR_INVALID_PARAMETER: return "invalid parameter";
case AMEDIA_ERROR_INVALID_OPERATION: return "invalid operation";
case AMEDIA_ERROR_END_OF_STREAM: return "end of stream";
case AMEDIA_ERROR_IO: return "i/o error";
case AMEDIA_ERROR_WOULD_BLOCK: return "operation would block";
case AMEDIA_DRM_NOT_PROVISIONED: return "DRM not provisioned";
case AMEDIA_DRM_RESOURCE_BUSY: return "DRM resource busy";
case AMEDIA_DRM_DEVICE_REVOKED: return "DRM device revoked";
case AMEDIA_DRM_SHORT_BUFFER: return "DRM short buffer";
case AMEDIA_DRM_SESSION_NOT_OPENED: return "DRM session not opened";
case AMEDIA_DRM_TAMPER_DETECTED: return "DRM tampering detected";
case AMEDIA_DRM_VERIFY_FAILED: return "DRM verify failed";
case AMEDIA_DRM_NEED_KEY: return "DRM need key";
case AMEDIA_DRM_LICENSE_EXPIRED: return "DRM license expired";
case AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE: return "no buffer available";
case AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED: return "maximum images acquired";
case AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE: return "cannot lock image";
case AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE: return "cannot unlock image";
case AMEDIA_IMGREADER_IMAGE_NOT_LOCKED: return "image not locked";
default: break;
}
return NULL; // unknown error
}
static bool SetMediaError(const char *what, const media_status_t rc)
{
return SetErrorStr(what, MediaStatusStr(rc), (int) rc);
}
static ACameraManager *cameraMgr = NULL;
static bool CreateCameraManager(void)
{
SDL_assert(cameraMgr == NULL);
cameraMgr = pACameraManager_create();
if (!cameraMgr) {
return SDL_SetError("Error creating ACameraManager");
}
return true;
}
static void DestroyCameraManager(void)
{
if (cameraMgr) {
pACameraManager_delete(cameraMgr);
cameraMgr = NULL;
}
}
static void format_android_to_sdl(Uint32 fmt, SDL_PixelFormat *format, SDL_Colorspace *colorspace)
{
switch (fmt) {
#define CASE(x, y, z) case x: *format = y; *colorspace = z; return
CASE(AIMAGE_FORMAT_YUV_420_888, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED);
CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565, SDL_COLORSPACE_SRGB);
CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_XRGB8888, SDL_COLORSPACE_SRGB);
CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888, SDL_COLORSPACE_SRGB);
CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888, SDL_COLORSPACE_SRGB);
CASE(AIMAGE_FORMAT_RGBA_FP16, SDL_PIXELFORMAT_RGBA64_FLOAT, SDL_COLORSPACE_SRGB);
#undef CASE
default: break;
}
#if DEBUG_CAMERA
//SDL_Log("Unknown format AIMAGE_FORMAT '%d'", fmt);
#endif
*format = SDL_PIXELFORMAT_UNKNOWN;
*colorspace = SDL_COLORSPACE_UNKNOWN;
}
static Uint32 format_sdl_to_android(SDL_PixelFormat fmt)
{
switch (fmt) {
#define CASE(x, y) case y: return x
CASE(AIMAGE_FORMAT_YUV_420_888, SDL_PIXELFORMAT_NV12);
CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565);
CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_XRGB8888);
CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888);
CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888);
#undef CASE
default:
return 0;
}
}
static bool ANDROIDCAMERA_WaitDevice(SDL_Camera *device)
{
return true; // this isn't used atm, since we run our own thread via onImageAvailable callbacks.
}
static SDL_CameraFrameResult ANDROIDCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
{
SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
media_status_t res;
AImage *image = NULL;
res = pAImageReader_acquireNextImage(device->hidden->reader, &image);
// We could also use this one:
//res = AImageReader_acquireLatestImage(device->hidden->reader, &image);
SDL_assert(res != AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE); // we should only be here if onImageAvailable was called.
if (res != AMEDIA_OK) {
SetMediaError("Error AImageReader_acquireNextImage", res);
return SDL_CAMERA_FRAME_ERROR;
}
int64_t atimestamp = 0;
if (pAImage_getTimestamp(image, &atimestamp) == AMEDIA_OK) {
*timestampNS = (Uint64) atimestamp;
} else {
*timestampNS = 0;
}
// !!! FIXME: this currently copies the data to the surface (see FIXME about non-contiguous planar surfaces, but in theory we could just keep this locked until ReleaseFrame...
int32_t num_planes = 0;
pAImage_getNumberOfPlanes(image, &num_planes);
if ((num_planes == 3) && (device->spec.format == SDL_PIXELFORMAT_NV12)) {
num_planes--; // treat the interleaved planes as one.
}
size_t buflen = 0;
pAImage_getPlaneRowStride(image, 0, &frame->pitch);
for (int i = 0; (i < num_planes) && (i < 3); i++) {
int32_t expected;
if (i == 0) {
expected = frame->pitch * frame->h;
} else {
expected = frame->pitch * (frame->h + 1) / 2;
}
buflen += expected;
}
frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
if (frame->pixels == NULL) {
result = SDL_CAMERA_FRAME_ERROR;
} else {
Uint8 *dst = frame->pixels;
for (int i = 0; (i < num_planes) && (i < 3); i++) {
uint8_t *data = NULL;
int32_t datalen = 0;
int32_t expected;
if (i == 0) {
expected = frame->pitch * frame->h;
} else {
expected = frame->pitch * (frame->h + 1) / 2;
}
pAImage_getPlaneData(image, i, &data, &datalen);
int32_t row_stride = 0;
pAImage_getPlaneRowStride(image, i, &row_stride);
SDL_assert(row_stride == frame->pitch);
SDL_memcpy(dst, data, SDL_min(expected, datalen));
dst += expected;
}
}
pAImage_delete(image);
return result;
}
static void ANDROIDCAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
{
// !!! FIXME: this currently copies the data to the surface, but in theory we could just keep the AImage until ReleaseFrame...
SDL_aligned_free(frame->pixels);
}
static void onImageAvailable(void *context, AImageReader *reader)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onImageAvailable");
#endif
SDL_Camera *device = (SDL_Camera *) context;
SDL_CameraThreadIterate(device);
}
static void onDisconnected(void *context, ACameraDevice *device)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onDisconnected");
#endif
SDL_CameraDisconnected((SDL_Camera *) context);
}
static void onError(void *context, ACameraDevice *device, int error)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onError");
#endif
SDL_CameraDisconnected((SDL_Camera *) context);
}
static void onClosed(void* context, ACameraCaptureSession *session)
{
// SDL_Camera *_this = (SDL_Camera *) context;
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onClosed");
#endif
}
static void onReady(void* context, ACameraCaptureSession *session)
{
// SDL_Camera *_this = (SDL_Camera *) context;
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onReady");
#endif
}
static void onActive(void* context, ACameraCaptureSession *session)
{
// SDL_Camera *_this = (SDL_Camera *) context;
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onActive");
#endif
}
static void ANDROIDCAMERA_CloseDevice(SDL_Camera *device)
{
if (device && device->hidden) {
struct SDL_PrivateCameraData *hidden = device->hidden;
device->hidden = NULL;
if (hidden->reader) {
pAImageReader_setImageListener(hidden->reader, NULL);
}
if (hidden->session) {
pACameraCaptureSession_close(hidden->session);
}
if (hidden->request) {
pACaptureRequest_free(hidden->request);
}
if (hidden->outputTarget) {
pACameraOutputTarget_free(hidden->outputTarget);
}
if (hidden->sessionOutputContainer) {
pACaptureSessionOutputContainer_free(hidden->sessionOutputContainer);
}
if (hidden->sessionOutput) {
pACaptureSessionOutput_free(hidden->sessionOutput);
}
// we don't free hidden->window here, it'll be cleaned up by AImageReader_delete.
if (hidden->reader) {
pAImageReader_delete(hidden->reader);
}
if (hidden->device) {
pACameraDevice_close(hidden->device);
}
SDL_free(hidden);
}
}
// this is where the "opening" of the camera happens, after permission is granted.
static bool PrepareCamera(SDL_Camera *device)
{
SDL_assert(device->hidden != NULL);
camera_status_t res;
media_status_t res2;
ACameraDevice_StateCallbacks dev_callbacks;
SDL_zero(dev_callbacks);
dev_callbacks.context = device;
dev_callbacks.onDisconnected = onDisconnected;
dev_callbacks.onError = onError;
ACameraCaptureSession_stateCallbacks capture_callbacks;
SDL_zero(capture_callbacks);
capture_callbacks.context = device;
capture_callbacks.onClosed = onClosed;
capture_callbacks.onReady = onReady;
capture_callbacks.onActive = onActive;
AImageReader_ImageListener imglistener;
SDL_zero(imglistener);
imglistener.context = device;
imglistener.onImageAvailable = onImageAvailable;
// just in case SDL_OpenCamera is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy.
const SDL_CameraSpec *spec = &device->hidden->requested_spec;
if ((res = pACameraManager_openCamera(cameraMgr, (const char *) device->handle, &dev_callbacks, &device->hidden->device)) != ACAMERA_OK) {
return SetCameraError("Failed to open camera", res);
} else if ((res2 = pAImageReader_new(spec->width, spec->height, format_sdl_to_android(spec->format), 10 /* nb buffers */, &device->hidden->reader)) != AMEDIA_OK) {
return SetMediaError("Error AImageReader_new", res2);
} else if ((res2 = pAImageReader_getWindow(device->hidden->reader, &device->hidden->window)) != AMEDIA_OK) {
return SetMediaError("Error AImageReader_getWindow", res2);
} else if ((res = pACaptureSessionOutput_create(device->hidden->window, &device->hidden->sessionOutput)) != ACAMERA_OK) {
return SetCameraError("Error ACaptureSessionOutput_create", res);
} else if ((res = pACaptureSessionOutputContainer_create(&device->hidden->sessionOutputContainer)) != ACAMERA_OK) {
return SetCameraError("Error ACaptureSessionOutputContainer_create", res);
} else if ((res = pACaptureSessionOutputContainer_add(device->hidden->sessionOutputContainer, device->hidden->sessionOutput)) != ACAMERA_OK) {
return SetCameraError("Error ACaptureSessionOutputContainer_add", res);
} else if ((res = pACameraOutputTarget_create(device->hidden->window, &device->hidden->outputTarget)) != ACAMERA_OK) {
return SetCameraError("Error ACameraOutputTarget_create", res);
} else if ((res = pACameraDevice_createCaptureRequest(device->hidden->device, TEMPLATE_RECORD, &device->hidden->request)) != ACAMERA_OK) {
return SetCameraError("Error ACameraDevice_createCaptureRequest", res);
} else if ((res = pACaptureRequest_addTarget(device->hidden->request, device->hidden->outputTarget)) != ACAMERA_OK) {
return SetCameraError("Error ACaptureRequest_addTarget", res);
} else if ((res = pACameraDevice_createCaptureSession(device->hidden->device, device->hidden->sessionOutputContainer, &capture_callbacks, &device->hidden->session)) != ACAMERA_OK) {
return SetCameraError("Error ACameraDevice_createCaptureSession", res);
} else if ((res = pACameraCaptureSession_setRepeatingRequest(device->hidden->session, NULL, 1, &device->hidden->request, NULL)) != ACAMERA_OK) {
return SetCameraError("Error ACameraCaptureSession_setRepeatingRequest", res);
} else if ((res2 = pAImageReader_setImageListener(device->hidden->reader, &imglistener)) != AMEDIA_OK) {
return SetMediaError("Error AImageReader_setImageListener", res2);
}
return true;
}
static void SDLCALL CameraPermissionCallback(void *userdata, const char *permission, bool granted)
{
SDL_Camera *device = (SDL_Camera *) userdata;
if (device->hidden != NULL) { // if device was already closed, don't send an event.
if (!granted) {
SDL_CameraPermissionOutcome(device, false); // sorry, permission denied.
} else if (!PrepareCamera(device)) { // permission given? Actually open the camera now.
// uhoh, setup failed; since the app thinks we already "opened" the device, mark it as disconnected and don't report the permission.
SDL_CameraDisconnected(device);
} else {
// okay! We have permission to use the camera _and_ opening the hardware worked out, report that the camera is usable!
SDL_CameraPermissionOutcome(device, true); // go go go!
}
}
UnrefPhysicalCamera(device); // we ref'd this in OpenDevice, release the extra reference.
}
static bool ANDROIDCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
{
#if 0 // !!! FIXME: for now, we'll just let this fail if it is going to fail, without checking for this
/* Cannot open a second camera, while the first one is opened.
* If you want to play several camera, they must all be opened first, then played.
*
* https://developer.android.com/reference/android/hardware/camera2/CameraManager
* "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
* before configuring sessions on any of the camera devices. * "
*
*/
if (CheckDevicePlaying()) {
return SDL_SetError("A camera is already playing");
}
#endif
device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
if (device->hidden == NULL) {
return false;
}
RefPhysicalCamera(device); // ref'd until permission callback fires.
// just in case SDL_OpenCamera is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy.
SDL_copyp(&device->hidden->requested_spec, spec);
if (!SDL_RequestAndroidPermission("android.permission.CAMERA", CameraPermissionCallback, device)) {
UnrefPhysicalCamera(device);
return false;
}
return true; // we don't open the camera until permission is granted, so always succeed for now.
}
static void ANDROIDCAMERA_FreeDeviceHandle(SDL_Camera *device)
{
if (device) {
SDL_free(device->handle);
}
}
static void GatherCameraSpecs(const char *devid, CameraFormatAddData *add_data, char **fullname, SDL_CameraPosition *position)
{
SDL_zerop(add_data);
ACameraMetadata *metadata = NULL;
ACameraMetadata_const_entry cfgentry;
ACameraMetadata_const_entry durentry;
ACameraMetadata_const_entry infoentry;
// This can fail with an "unknown error" (with `adb logcat` reporting "no such file or directory")
// for "LEGACY" level cameras. I saw this happen on a 30-dollar budget phone I have for testing
// (but a different brand budget phone worked, so it's not strictly the low-end of Android devices).
// LEGACY devices are seen by onCameraAvailable, but are not otherwise accessible through
// libcamera2ndk. The Java camera2 API apparently _can_ access these cameras, but we're going on
// without them here for now, in hopes that such hardware is a dying breed.
if (pACameraManager_getCameraCharacteristics(cameraMgr, devid, &metadata) != ACAMERA_OK) {
return; // oh well.
} else if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &cfgentry) != ACAMERA_OK) {
pACameraMetadata_free(metadata);
return; // oh well.
} else if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS, &durentry) != ACAMERA_OK) {
pACameraMetadata_free(metadata);
return; // oh well.
}
*fullname = NULL;
if (pACameraMetadata_getConstEntry(metadata, ACAMERA_INFO_VERSION, &infoentry) == ACAMERA_OK) {
*fullname = (char *) SDL_malloc(infoentry.count + 1);
if (*fullname) {
SDL_strlcpy(*fullname, (const char *) infoentry.data.u8, infoentry.count + 1);
}
}
ACameraMetadata_const_entry posentry;
if (pACameraMetadata_getConstEntry(metadata, ACAMERA_LENS_FACING, &posentry) == ACAMERA_OK) { // ignore this if it fails.
if (*posentry.data.u8 == ACAMERA_LENS_FACING_FRONT) {
*position = SDL_CAMERA_POSITION_FRONT_FACING;
if (!*fullname) {
*fullname = SDL_strdup("Front-facing camera");
}
} else if (*posentry.data.u8 == ACAMERA_LENS_FACING_BACK) {
*position = SDL_CAMERA_POSITION_BACK_FACING;
if (!*fullname) {
*fullname = SDL_strdup("Back-facing camera");
}
}
}
if (!*fullname) {
*fullname = SDL_strdup("Generic camera"); // we tried.
}
const int32_t *i32ptr = cfgentry.data.i32;
for (int i = 0; i < cfgentry.count; i++, i32ptr += 4) {
const int32_t fmt = i32ptr[0];
const int w = i32ptr[1];
const int h = i32ptr[2];
const int32_t type = i32ptr[3];
SDL_PixelFormat sdlfmt = SDL_PIXELFORMAT_UNKNOWN;
SDL_Colorspace colorspace = SDL_COLORSPACE_UNKNOWN;
if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) {
continue;
} else if ((w <= 0) || (h <= 0)) {
continue;
} else {
format_android_to_sdl(fmt, &sdlfmt, &colorspace);
if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) {
continue;
}
}
#if 0 // !!! FIXME: these all come out with 0 durations on my test phone. :(
const int64_t *i64ptr = durentry.data.i64;
for (int j = 0; j < durentry.count; j++, i64ptr += 4) {
const int32_t fpsfmt = (int32_t) i64ptr[0];
const int fpsw = (int) i64ptr[1];
const int fpsh = (int) i64ptr[2];
const long long duration = (long long) i64ptr[3];
SDL_Log("CAMERA: possible fps %s %dx%d duration=%lld", SDL_GetPixelFormatName(sdlfmt), fpsw, fpsh, duration);
if ((duration > 0) && (fpsfmt == fmt) && (fpsw == w) && (fpsh == h)) {
SDL_AddCameraFormat(add_data, sdlfmt, colorspace, w, h, 1000000000, duration);
}
}
#else
SDL_AddCameraFormat(add_data, sdlfmt, colorspace, w, h, 30, 1);
#endif
}
pACameraMetadata_free(metadata);
}
static bool FindAndroidCameraByID(SDL_Camera *device, void *userdata)
{
const char *devid = (const char *) userdata;
return (SDL_strcmp(devid, (const char *) device->handle) == 0);
}
static void MaybeAddDevice(const char *devid)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: MaybeAddDevice('%s')", devid);
#endif
if (SDL_FindPhysicalCameraByCallback(FindAndroidCameraByID, (void *) devid)) {
return; // already have this one.
}
SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
char *fullname = NULL;
CameraFormatAddData add_data;
GatherCameraSpecs(devid, &add_data, &fullname, &position);
if (add_data.num_specs > 0) {
char *namecpy = SDL_strdup(devid);
if (namecpy) {
SDL_Camera *device = SDL_AddCamera(fullname, position, add_data.num_specs, add_data.specs, namecpy);
if (!device) {
SDL_free(namecpy);
}
}
}
SDL_free(fullname);
SDL_free(add_data.specs);
}
// note that camera "availability" covers both hotplugging and whether another
// has the device opened, but for something like Android, it's probably fine
// to treat both unplugging and loss of access as disconnection events. When
// the other app closes the camera, we get an available event as if it was
// just plugged back in.
static void onCameraAvailable(void *context, const char *cameraId)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onCameraAvailable('%s')", cameraId);
#endif
SDL_assert(cameraId != NULL);
MaybeAddDevice(cameraId);
}
static void onCameraUnavailable(void *context, const char *cameraId)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onCameraUnvailable('%s')", cameraId);
#endif
SDL_assert(cameraId != NULL);
// THIS CALLBACK FIRES WHEN YOU OPEN THE DEVICE YOURSELF. :(
// Make sure we don't have the device opened, in which case onDisconnected will fire instead if actually lost.
SDL_Camera *device = SDL_FindPhysicalCameraByCallback(FindAndroidCameraByID, (void *) cameraId);
if (device && !device->hidden) {
SDL_CameraDisconnected(device);
}
}
static const ACameraManager_AvailabilityCallbacks camera_availability_listener = {
NULL,
onCameraAvailable,
onCameraUnavailable
};
static void ANDROIDCAMERA_DetectDevices(void)
{
ACameraIdList *list = NULL;
camera_status_t res = pACameraManager_getCameraIdList(cameraMgr, &list);
if ((res == ACAMERA_OK) && list) {
const int total = list->numCameras;
for (int i = 0; i < total; i++) {
MaybeAddDevice(list->cameraIds[i]);
}
pACameraManager_deleteCameraIdList(list);
}
pACameraManager_registerAvailabilityCallback(cameraMgr, &camera_availability_listener);
}
static void ANDROIDCAMERA_Deinitialize(void)
{
pACameraManager_unregisterAvailabilityCallback(cameraMgr, &camera_availability_listener);
DestroyCameraManager();
dlclose(libcamera2ndk);
libcamera2ndk = NULL;
pACameraManager_create = NULL;
pACameraManager_registerAvailabilityCallback = NULL;
pACameraManager_unregisterAvailabilityCallback = NULL;
pACameraManager_getCameraIdList = NULL;
pACameraManager_deleteCameraIdList = NULL;
pACameraCaptureSession_close = NULL;
pACaptureRequest_free = NULL;
pACameraOutputTarget_free = NULL;
pACameraDevice_close = NULL;
pACameraManager_delete = NULL;
pACaptureSessionOutputContainer_free = NULL;
pACaptureSessionOutput_free = NULL;
pACameraManager_openCamera = NULL;
pACameraDevice_createCaptureRequest = NULL;
pACameraDevice_createCaptureSession = NULL;
pACameraManager_getCameraCharacteristics = NULL;
pACameraMetadata_free = NULL;
pACameraMetadata_getConstEntry = NULL;
pACameraCaptureSession_setRepeatingRequest = NULL;
pACameraOutputTarget_create = NULL;
pACaptureRequest_addTarget = NULL;
pACaptureSessionOutputContainer_add = NULL;
pACaptureSessionOutputContainer_create = NULL;
pACaptureSessionOutput_create = NULL;
dlclose(libmediandk);
libmediandk = NULL;
pAImage_delete = NULL;
pAImage_getTimestamp = NULL;
pAImage_getNumberOfPlanes = NULL;
pAImage_getPlaneRowStride = NULL;
pAImage_getPlaneData = NULL;
pAImageReader_acquireNextImage = NULL;
pAImageReader_delete = NULL;
pAImageReader_setImageListener = NULL;
pAImageReader_getWindow = NULL;
pAImageReader_new = NULL;
}
static bool ANDROIDCAMERA_Init(SDL_CameraDriverImpl *impl)
{
// !!! FIXME: slide this off into a subroutine
// system libraries are in android-24 and later; we currently target android-16 and later, so check if they exist at runtime.
void *libcamera2 = dlopen("libcamera2ndk.so", RTLD_NOW | RTLD_LOCAL);
if (!libcamera2) {
SDL_Log("CAMERA: libcamera2ndk.so can't be loaded: %s", dlerror());
return false;
}
void *libmedia = dlopen("libmediandk.so", RTLD_NOW | RTLD_LOCAL);
if (!libmedia) {
SDL_Log("CAMERA: libmediandk.so can't be loaded: %s", dlerror());
dlclose(libcamera2);
return false;
}
bool okay = true;
#define LOADSYM(lib, fn) if (okay) { p##fn = (pfn##fn) dlsym(lib, #fn); if (!p##fn) { SDL_Log("CAMERA: symbol '%s' can't be found in %s: %s", #fn, #lib "ndk.so", dlerror()); okay = false; } }
//#define LOADSYM(lib, fn) p##fn = (pfn##fn) fn
LOADSYM(libcamera2, ACameraManager_create);
LOADSYM(libcamera2, ACameraManager_registerAvailabilityCallback);
LOADSYM(libcamera2, ACameraManager_unregisterAvailabilityCallback);
LOADSYM(libcamera2, ACameraManager_getCameraIdList);
LOADSYM(libcamera2, ACameraManager_deleteCameraIdList);
LOADSYM(libcamera2, ACameraCaptureSession_close);
LOADSYM(libcamera2, ACaptureRequest_free);
LOADSYM(libcamera2, ACameraOutputTarget_free);
LOADSYM(libcamera2, ACameraDevice_close);
LOADSYM(libcamera2, ACameraManager_delete);
LOADSYM(libcamera2, ACaptureSessionOutputContainer_free);
LOADSYM(libcamera2, ACaptureSessionOutput_free);
LOADSYM(libcamera2, ACameraManager_openCamera);
LOADSYM(libcamera2, ACameraDevice_createCaptureRequest);
LOADSYM(libcamera2, ACameraDevice_createCaptureSession);
LOADSYM(libcamera2, ACameraManager_getCameraCharacteristics);
LOADSYM(libcamera2, ACameraMetadata_free);
LOADSYM(libcamera2, ACameraMetadata_getConstEntry);
LOADSYM(libcamera2, ACameraCaptureSession_setRepeatingRequest);
LOADSYM(libcamera2, ACameraOutputTarget_create);
LOADSYM(libcamera2, ACaptureRequest_addTarget);
LOADSYM(libcamera2, ACaptureSessionOutputContainer_add);
LOADSYM(libcamera2, ACaptureSessionOutputContainer_create);
LOADSYM(libcamera2, ACaptureSessionOutput_create);
LOADSYM(libmedia, AImage_delete);
LOADSYM(libmedia, AImage_getTimestamp);
LOADSYM(libmedia, AImage_getNumberOfPlanes);
LOADSYM(libmedia, AImage_getPlaneRowStride);
LOADSYM(libmedia, AImage_getPlaneData);
LOADSYM(libmedia, AImageReader_acquireNextImage);
LOADSYM(libmedia, AImageReader_delete);
LOADSYM(libmedia, AImageReader_setImageListener);
LOADSYM(libmedia, AImageReader_getWindow);
LOADSYM(libmedia, AImageReader_new);
LOADSYM(libmedia, AImage_getWidth);
LOADSYM(libmedia, AImage_getHeight);
#undef LOADSYM
if (!okay) {
dlclose(libmedia);
dlclose(libcamera2);
}
if (!CreateCameraManager()) {
dlclose(libmedia);
dlclose(libcamera2);
return false;
}
libcamera2ndk = libcamera2;
libmediandk = libmedia;
impl->DetectDevices = ANDROIDCAMERA_DetectDevices;
impl->OpenDevice = ANDROIDCAMERA_OpenDevice;
impl->CloseDevice = ANDROIDCAMERA_CloseDevice;
impl->WaitDevice = ANDROIDCAMERA_WaitDevice;
impl->AcquireFrame = ANDROIDCAMERA_AcquireFrame;
impl->ReleaseFrame = ANDROIDCAMERA_ReleaseFrame;
impl->FreeDeviceHandle = ANDROIDCAMERA_FreeDeviceHandle;
impl->Deinitialize = ANDROIDCAMERA_Deinitialize;
impl->ProvidesOwnCallbackThread = true;
return true;
}
CameraBootStrap ANDROIDCAMERA_bootstrap = {
"android", "SDL Android camera driver", ANDROIDCAMERA_Init, false
};
#endif

View file

@ -0,0 +1,508 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_CAMERA_DRIVER_COREMEDIA
#include "../SDL_syscamera.h"
#include "../SDL_camera_c.h"
#include "../../thread/SDL_systhread.h"
#import <AVFoundation/AVFoundation.h>
#import <CoreMedia/CoreMedia.h>
/*
* Need to link with:: CoreMedia CoreVideo
*
* Add in pInfo.list:
* <key>NSCameraUsageDescription</key> <string>Access camera</string>
*
*
* MACOSX:
* Add to the Code Sign Entitlement file:
* <key>com.apple.security.device.camera</key> <true/>
*/
static void CoreMediaFormatToSDL(FourCharCode fmt, SDL_PixelFormat *pixel_format, SDL_Colorspace *colorspace)
{
switch (fmt) {
#define CASE(x, y, z) case x: *pixel_format = y; *colorspace = z; return
// the 16LE ones should use 16BE if we're on a Bigendian system like PowerPC,
// but at current time there is no bigendian Apple platform that has CoreMedia.
CASE(kCMPixelFormat_16LE555, SDL_PIXELFORMAT_XRGB1555, SDL_COLORSPACE_SRGB);
CASE(kCMPixelFormat_16LE5551, SDL_PIXELFORMAT_RGBA5551, SDL_COLORSPACE_SRGB);
CASE(kCMPixelFormat_16LE565, SDL_PIXELFORMAT_RGB565, SDL_COLORSPACE_SRGB);
CASE(kCMPixelFormat_24RGB, SDL_PIXELFORMAT_RGB24, SDL_COLORSPACE_SRGB);
CASE(kCMPixelFormat_32ARGB, SDL_PIXELFORMAT_ARGB32, SDL_COLORSPACE_SRGB);
CASE(kCMPixelFormat_32BGRA, SDL_PIXELFORMAT_BGRA32, SDL_COLORSPACE_SRGB);
CASE(kCMPixelFormat_422YpCbCr8, SDL_PIXELFORMAT_UYVY, SDL_COLORSPACE_BT709_LIMITED);
CASE(kCMPixelFormat_422YpCbCr8_yuvs, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED);
CASE(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED);
CASE(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_FULL);
CASE(kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange, SDL_PIXELFORMAT_P010, SDL_COLORSPACE_BT2020_LIMITED);
CASE(kCVPixelFormatType_420YpCbCr10BiPlanarFullRange, SDL_PIXELFORMAT_P010, SDL_COLORSPACE_BT2020_FULL);
#undef CASE
default:
#if DEBUG_CAMERA
SDL_Log("CAMERA: Unknown format FourCharCode '%d'", (int) fmt);
#endif
break;
}
*pixel_format = SDL_PIXELFORMAT_UNKNOWN;
*colorspace = SDL_COLORSPACE_UNKNOWN;
}
@class SDLCaptureVideoDataOutputSampleBufferDelegate;
// just a simple wrapper to help ARC manage memory...
@interface SDLPrivateCameraData : NSObject
@property(nonatomic, retain) AVCaptureSession *session;
@property(nonatomic, retain) SDLCaptureVideoDataOutputSampleBufferDelegate *delegate;
@property(nonatomic, assign) CMSampleBufferRef current_sample;
@end
@implementation SDLPrivateCameraData
@end
static bool CheckCameraPermissions(SDL_Camera *device)
{
if (device->permission == 0) { // still expecting a permission result.
if (@available(macOS 14, *)) {
const AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (status != AVAuthorizationStatusNotDetermined) { // NotDetermined == still waiting for an answer from the user.
SDL_CameraPermissionOutcome(device, (status == AVAuthorizationStatusAuthorized) ? true : false);
}
} else {
SDL_CameraPermissionOutcome(device, true); // always allowed (or just unqueryable...?) on older macOS.
}
}
return (device->permission > 0);
}
// this delegate just receives new video frames on a Grand Central Dispatch queue, and fires off the
// main device thread iterate function directly to consume it.
@interface SDLCaptureVideoDataOutputSampleBufferDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
@property SDL_Camera *device;
-(id) init:(SDL_Camera *) dev;
-(void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
@end
@implementation SDLCaptureVideoDataOutputSampleBufferDelegate
-(id) init:(SDL_Camera *) dev {
if ( self = [super init] ) {
_device = dev;
}
return self;
}
- (void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
SDL_Camera *device = self.device;
if (!device || !device->hidden) {
return; // oh well.
}
if (!CheckCameraPermissions(device)) {
return; // nothing to do right now, dump what is probably a completely black frame.
}
SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
hidden.current_sample = sampleBuffer;
SDL_CameraThreadIterate(device);
hidden.current_sample = NULL;
}
- (void)captureOutput:(AVCaptureOutput *)output didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: Drop frame.");
#endif
}
@end
static bool COREMEDIA_WaitDevice(SDL_Camera *device)
{
return true; // this isn't used atm, since we run our own thread out of Grand Central Dispatch.
}
static SDL_CameraFrameResult COREMEDIA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
{
SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
CMSampleBufferRef sample_buffer = hidden.current_sample;
hidden.current_sample = NULL;
SDL_assert(sample_buffer != NULL); // should only have been called from our delegate with a new frame.
CMSampleTimingInfo timinginfo;
if (CMSampleBufferGetSampleTimingInfo(sample_buffer, 0, &timinginfo) == noErr) {
*timestampNS = (Uint64) (CMTimeGetSeconds(timinginfo.presentationTimeStamp) * ((Float64) SDL_NS_PER_SECOND));
} else {
SDL_assert(!"this shouldn't happen, I think.");
*timestampNS = 0;
}
CVImageBufferRef image = CMSampleBufferGetImageBuffer(sample_buffer); // does not retain `image` (and we don't want it to).
const int numPlanes = (int) CVPixelBufferGetPlaneCount(image);
const int planar = (int) CVPixelBufferIsPlanar(image);
#if DEBUG_CAMERA
const int w = (int) CVPixelBufferGetWidth(image);
const int h = (int) CVPixelBufferGetHeight(image);
const int sz = (int) CVPixelBufferGetDataSize(image);
const int pitch = (int) CVPixelBufferGetBytesPerRow(image);
SDL_Log("CAMERA: buffer planar=%d numPlanes=%d %d x %d sz=%d pitch=%d", planar, numPlanes, w, h, sz, pitch);
#endif
// !!! FIXME: this currently copies the data to the surface (see FIXME about non-contiguous planar surfaces, but in theory we could just keep this locked until ReleaseFrame...
CVPixelBufferLockBaseAddress(image, 0);
frame->w = (int)CVPixelBufferGetWidth(image);
frame->h = (int)CVPixelBufferGetHeight(image);
if ((planar == 0) && (numPlanes == 0)) {
const int pitch = (int) CVPixelBufferGetBytesPerRow(image);
const size_t buflen = pitch * frame->h;
frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
if (frame->pixels == NULL) {
result = SDL_CAMERA_FRAME_ERROR;
} else {
frame->pitch = pitch;
SDL_memcpy(frame->pixels, CVPixelBufferGetBaseAddress(image), buflen);
}
} else {
// !!! FIXME: we have an open issue in SDL3 to allow SDL_Surface to support non-contiguous planar data, but we don't have it yet.
size_t buflen = 0;
for (int i = 0; i < numPlanes; i++) {
size_t plane_height = CVPixelBufferGetHeightOfPlane(image, i);
size_t plane_pitch = CVPixelBufferGetBytesPerRowOfPlane(image, i);
size_t plane_size = (plane_pitch * plane_height);
buflen += plane_size;
}
frame->pitch = (int)CVPixelBufferGetBytesPerRowOfPlane(image, 0); // this is what SDL3 currently expects
frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
if (frame->pixels == NULL) {
result = SDL_CAMERA_FRAME_ERROR;
} else {
Uint8 *dst = frame->pixels;
for (int i = 0; i < numPlanes; i++) {
const void *src = CVPixelBufferGetBaseAddressOfPlane(image, i);
size_t plane_height = CVPixelBufferGetHeightOfPlane(image, i);
size_t plane_pitch = CVPixelBufferGetBytesPerRowOfPlane(image, i);
size_t plane_size = (plane_pitch * plane_height);
SDL_memcpy(dst, src, plane_size);
dst += plane_size;
}
}
}
CVPixelBufferUnlockBaseAddress(image, 0);
return result;
}
static void COREMEDIA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
{
// !!! FIXME: this currently copies the data to the surface, but in theory we could just keep this locked until ReleaseFrame...
SDL_aligned_free(frame->pixels);
}
static void COREMEDIA_CloseDevice(SDL_Camera *device)
{
if (device && device->hidden) {
SDLPrivateCameraData *hidden = (SDLPrivateCameraData *) CFBridgingRelease(device->hidden);
device->hidden = NULL;
AVCaptureSession *session = hidden.session;
if (session) {
hidden.session = nil;
[session stopRunning];
[session removeInput:[session.inputs objectAtIndex:0]];
[session removeOutput:(AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0]];
session = nil;
}
hidden.delegate = NULL;
hidden.current_sample = NULL;
}
}
static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
{
AVCaptureDevice *avdevice = (__bridge AVCaptureDevice *) device->handle;
// Pick format that matches the spec
const int w = spec->width;
const int h = spec->height;
const float rate = (float)spec->framerate_numerator / spec->framerate_denominator;
AVCaptureDeviceFormat *spec_format = nil;
NSArray<AVCaptureDeviceFormat *> *formats = [avdevice formats];
for (AVCaptureDeviceFormat *format in formats) {
CMFormatDescriptionRef formatDescription = [format formatDescription];
SDL_PixelFormat device_format = SDL_PIXELFORMAT_UNKNOWN;
SDL_Colorspace device_colorspace = SDL_COLORSPACE_UNKNOWN;
CoreMediaFormatToSDL(CMFormatDescriptionGetMediaSubType(formatDescription), &device_format, &device_colorspace);
if (device_format != spec->format || device_colorspace != spec->colorspace) {
continue;
}
const CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDescription);
if ((int)dim.width != w || (int)dim.height != h) {
continue;
}
const float FRAMERATE_EPSILON = 0.01f;
for (AVFrameRateRange *framerate in format.videoSupportedFrameRateRanges) {
if (rate > (framerate.minFrameRate - FRAMERATE_EPSILON) &&
rate < (framerate.maxFrameRate + FRAMERATE_EPSILON)) {
spec_format = format;
break;
}
}
if (spec_format != nil) {
break;
}
}
if (spec_format == nil) {
return SDL_SetError("camera spec format not available");
} else if (![avdevice lockForConfiguration:NULL]) {
return SDL_SetError("Cannot lockForConfiguration");
}
avdevice.activeFormat = spec_format;
[avdevice unlockForConfiguration];
AVCaptureSession *session = [[AVCaptureSession alloc] init];
if (session == nil) {
return SDL_SetError("Failed to allocate/init AVCaptureSession");
}
session.sessionPreset = AVCaptureSessionPresetHigh;
#if defined(SDL_PLATFORM_IOS)
if (@available(iOS 10.0, tvOS 17.0, *)) {
session.automaticallyConfiguresCaptureDeviceForWideColor = NO;
}
#endif
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:avdevice error:&error];
if (!input) {
return SDL_SetError("Cannot create AVCaptureDeviceInput");
}
AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
if (!output) {
return SDL_SetError("Cannot create AVCaptureVideoDataOutput");
}
output.videoSettings = @{
(id)kCVPixelBufferWidthKey : @(spec->width),
(id)kCVPixelBufferHeightKey : @(spec->height),
(id)kCVPixelBufferPixelFormatTypeKey : @(CMFormatDescriptionGetMediaSubType([spec_format formatDescription]))
};
char threadname[64];
SDL_GetCameraThreadName(device, threadname, sizeof (threadname));
dispatch_queue_t queue = dispatch_queue_create(threadname, NULL);
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
if (!queue) {
return SDL_SetError("dispatch_queue_create() failed");
}
SDLCaptureVideoDataOutputSampleBufferDelegate *delegate = [[SDLCaptureVideoDataOutputSampleBufferDelegate alloc] init:device];
if (delegate == nil) {
return SDL_SetError("Cannot create SDLCaptureVideoDataOutputSampleBufferDelegate");
}
[output setSampleBufferDelegate:delegate queue:queue];
if (![session canAddInput:input]) {
return SDL_SetError("Cannot add AVCaptureDeviceInput");
}
[session addInput:input];
if (![session canAddOutput:output]) {
return SDL_SetError("Cannot add AVCaptureVideoDataOutput");
}
[session addOutput:output];
[session commitConfiguration];
SDLPrivateCameraData *hidden = [[SDLPrivateCameraData alloc] init];
if (hidden == nil) {
return SDL_SetError("Cannot create SDLPrivateCameraData");
}
hidden.session = session;
hidden.delegate = delegate;
hidden.current_sample = NULL;
device->hidden = (struct SDL_PrivateCameraData *)CFBridgingRetain(hidden);
[session startRunning]; // !!! FIXME: docs say this can block while camera warms up and shouldn't be done on main thread. Maybe push through `queue`?
CheckCameraPermissions(device); // check right away, in case the process is already granted permission.
return true;
}
static void COREMEDIA_FreeDeviceHandle(SDL_Camera *device)
{
if (device && device->handle) {
CFBridgingRelease(device->handle);
}
}
static void GatherCameraSpecs(AVCaptureDevice *device, CameraFormatAddData *add_data)
{
SDL_zerop(add_data);
for (AVCaptureDeviceFormat *fmt in device.formats) {
if (CMFormatDescriptionGetMediaType(fmt.formatDescription) != kCMMediaType_Video) {
continue;
}
//NSLog(@"Available camera format: %@\n", fmt);
SDL_PixelFormat device_format = SDL_PIXELFORMAT_UNKNOWN;
SDL_Colorspace device_colorspace = SDL_COLORSPACE_UNKNOWN;
CoreMediaFormatToSDL(CMFormatDescriptionGetMediaSubType(fmt.formatDescription), &device_format, &device_colorspace);
if (device_format == SDL_PIXELFORMAT_UNKNOWN) {
continue;
}
const CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(fmt.formatDescription);
const int w = (int) dims.width;
const int h = (int) dims.height;
for (AVFrameRateRange *framerate in fmt.videoSupportedFrameRateRanges) {
int min_numerator = 0, min_denominator = 1;
int max_numerator = 0, max_denominator = 1;
SDL_CalculateFraction(framerate.minFrameRate, &min_numerator, &min_denominator);
SDL_AddCameraFormat(add_data, device_format, device_colorspace, w, h, min_numerator, min_denominator);
SDL_CalculateFraction(framerate.maxFrameRate, &max_numerator, &max_denominator);
if (max_numerator != min_numerator || max_denominator != min_denominator) {
SDL_AddCameraFormat(add_data, device_format, device_colorspace, w, h, max_numerator, max_denominator);
}
}
}
}
static bool FindCoreMediaCameraByUniqueID(SDL_Camera *device, void *userdata)
{
NSString *uniqueid = (__bridge NSString *) userdata;
AVCaptureDevice *avdev = (__bridge AVCaptureDevice *) device->handle;
return ([uniqueid isEqualToString:avdev.uniqueID]) ? true : false;
}
static void MaybeAddDevice(AVCaptureDevice *avdevice)
{
if (!avdevice.connected) {
return; // not connected.
} else if (![avdevice hasMediaType:AVMediaTypeVideo]) {
return; // not a camera.
} else if (SDL_FindPhysicalCameraByCallback(FindCoreMediaCameraByUniqueID, (__bridge void *) avdevice.uniqueID)) {
return; // already have this one.
}
CameraFormatAddData add_data;
GatherCameraSpecs(avdevice, &add_data);
if (add_data.num_specs > 0) {
SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
if (avdevice.position == AVCaptureDevicePositionFront) {
position = SDL_CAMERA_POSITION_FRONT_FACING;
} else if (avdevice.position == AVCaptureDevicePositionBack) {
position = SDL_CAMERA_POSITION_BACK_FACING;
}
SDL_AddCamera(avdevice.localizedName.UTF8String, position, add_data.num_specs, add_data.specs, (void *) CFBridgingRetain(avdevice));
}
SDL_free(add_data.specs);
}
static void COREMEDIA_DetectDevices(void)
{
NSArray<AVCaptureDevice *> *devices = nil;
if (@available(macOS 10.15, iOS 13, *)) {
// kind of annoying that there isn't a "give me anything that looks like a camera" option,
// so this list will need to be updated when Apple decides to add
// AVCaptureDeviceTypeBuiltInQuadrupleCamera some day.
NSArray *device_types = @[
#ifdef SDL_PLATFORM_IOS
AVCaptureDeviceTypeBuiltInTelephotoCamera,
AVCaptureDeviceTypeBuiltInDualCamera,
AVCaptureDeviceTypeBuiltInDualWideCamera,
AVCaptureDeviceTypeBuiltInTripleCamera,
AVCaptureDeviceTypeBuiltInUltraWideCamera,
#else
AVCaptureDeviceTypeExternalUnknown,
#endif
AVCaptureDeviceTypeBuiltInWideAngleCamera
];
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
discoverySessionWithDeviceTypes:device_types
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionUnspecified];
devices = discoverySession.devices;
// !!! FIXME: this can use Key Value Observation to get hotplug events.
} else {
// this is deprecated but works back to macOS 10.7; 10.15 added AVCaptureDeviceDiscoverySession as a replacement.
devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
// !!! FIXME: this can use AVCaptureDeviceWasConnectedNotification and AVCaptureDeviceWasDisconnectedNotification with NSNotificationCenter to get hotplug events.
}
for (AVCaptureDevice *device in devices) {
MaybeAddDevice(device);
}
}
static void COREMEDIA_Deinitialize(void)
{
// !!! FIXME: disable hotplug.
}
static bool COREMEDIA_Init(SDL_CameraDriverImpl *impl)
{
impl->DetectDevices = COREMEDIA_DetectDevices;
impl->OpenDevice = COREMEDIA_OpenDevice;
impl->CloseDevice = COREMEDIA_CloseDevice;
impl->WaitDevice = COREMEDIA_WaitDevice;
impl->AcquireFrame = COREMEDIA_AcquireFrame;
impl->ReleaseFrame = COREMEDIA_ReleaseFrame;
impl->FreeDeviceHandle = COREMEDIA_FreeDeviceHandle;
impl->Deinitialize = COREMEDIA_Deinitialize;
impl->ProvidesOwnCallbackThread = true;
return true;
}
CameraBootStrap COREMEDIA_bootstrap = {
"coremedia", "SDL Apple CoreMedia camera driver", COREMEDIA_Init, false
};
#endif // SDL_CAMERA_DRIVER_COREMEDIA

View file

@ -0,0 +1,81 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_CAMERA_DRIVER_DUMMY
#include "../SDL_syscamera.h"
static bool DUMMYCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
{
return SDL_Unsupported();
}
static void DUMMYCAMERA_CloseDevice(SDL_Camera *device)
{
}
static bool DUMMYCAMERA_WaitDevice(SDL_Camera *device)
{
return SDL_Unsupported();
}
static SDL_CameraFrameResult DUMMYCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
{
SDL_Unsupported();
return SDL_CAMERA_FRAME_ERROR;
}
static void DUMMYCAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
{
}
static void DUMMYCAMERA_DetectDevices(void)
{
}
static void DUMMYCAMERA_FreeDeviceHandle(SDL_Camera *device)
{
}
static void DUMMYCAMERA_Deinitialize(void)
{
}
static bool DUMMYCAMERA_Init(SDL_CameraDriverImpl *impl)
{
impl->DetectDevices = DUMMYCAMERA_DetectDevices;
impl->OpenDevice = DUMMYCAMERA_OpenDevice;
impl->CloseDevice = DUMMYCAMERA_CloseDevice;
impl->WaitDevice = DUMMYCAMERA_WaitDevice;
impl->AcquireFrame = DUMMYCAMERA_AcquireFrame;
impl->ReleaseFrame = DUMMYCAMERA_ReleaseFrame;
impl->FreeDeviceHandle = DUMMYCAMERA_FreeDeviceHandle;
impl->Deinitialize = DUMMYCAMERA_Deinitialize;
return true;
}
CameraBootStrap DUMMYCAMERA_bootstrap = {
"dummy", "SDL dummy camera driver", DUMMYCAMERA_Init, true
};
#endif // SDL_CAMERA_DRIVER_DUMMY

View file

@ -0,0 +1,275 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_CAMERA_DRIVER_EMSCRIPTEN
#include "../SDL_syscamera.h"
#include "../SDL_camera_c.h"
#include "../../video/SDL_pixels_c.h"
#include "../../video/SDL_surface_c.h"
#include <emscripten/emscripten.h>
// 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
EM_JS_DEPS(sdlcamera, "$dynCall");
static bool EMSCRIPTENCAMERA_WaitDevice(SDL_Camera *device)
{
SDL_assert(!"This shouldn't be called"); // we aren't using SDL's internal thread.
return false;
}
static SDL_CameraFrameResult EMSCRIPTENCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
{
void *rgba = SDL_malloc(device->actual_spec.width * device->actual_spec.height * 4);
if (!rgba) {
return SDL_CAMERA_FRAME_ERROR;
}
*timestampNS = SDL_GetTicksNS(); // best we can do here.
const int rc = MAIN_THREAD_EM_ASM_INT({
const w = $0;
const h = $1;
const rgba = $2;
const SDL3 = Module['SDL3'];
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.ctx2d) === 'undefined')) {
return 0; // don't have something we need, oh well.
}
SDL3.camera.ctx2d.drawImage(SDL3.camera.video, 0, 0, w, h);
const imgrgba = SDL3.camera.ctx2d.getImageData(0, 0, w, h).data;
Module.HEAPU8.set(imgrgba, rgba);
return 1;
}, device->actual_spec.width, device->actual_spec.height, rgba);
if (!rc) {
SDL_free(rgba);
return SDL_CAMERA_FRAME_ERROR; // something went wrong, maybe shutting down; just don't return a frame.
}
frame->pixels = rgba;
frame->pitch = device->actual_spec.width * 4;
return SDL_CAMERA_FRAME_READY;
}
static void EMSCRIPTENCAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
{
SDL_free(frame->pixels);
}
static void EMSCRIPTENCAMERA_CloseDevice(SDL_Camera *device)
{
if (device) {
MAIN_THREAD_EM_ASM({
const SDL3 = Module['SDL3'];
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) {
return; // camera was closed and/or subsystem was shut down, we're already done.
}
SDL3.camera.stream.getTracks().forEach(track => track.stop()); // stop all recording.
SDL3.camera = {}; // dump our references to everything.
});
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static int SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved, int w, int h, int fps)
{
if (approved) {
device->actual_spec.format = SDL_PIXELFORMAT_RGBA32;
device->actual_spec.width = w;
device->actual_spec.height = h;
device->actual_spec.framerate_numerator = fps;
device->actual_spec.framerate_denominator = 1;
if (!SDL_PrepareCameraSurfaces(device)) {
// uhoh, we're in trouble. Probably ran out of memory.
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Camera could not prepare surfaces: %s ... revoking approval!", SDL_GetError());
approved = 0; // disconnecting the SDL camera might not be safe here, just mark it as denied by user.
}
}
SDL_CameraPermissionOutcome(device, approved ? true : false);
return approved;
}
static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
{
MAIN_THREAD_EM_ASM({
// Since we can't get actual specs until we make a move that prompts the user for
// permission, we don't list any specs for the device and wrangle it during device open.
const device = $0;
const w = $1;
const h = $2;
const framerate_numerator = $3;
const framerate_denominator = $4;
const outcome = $5;
const iterate = $6;
const constraints = {};
if ((w <= 0) || (h <= 0)) {
constraints.video = true; // didn't ask for anything, let the system choose.
} else {
constraints.video = {}; // asked for a specific thing: request it as "ideal" but take closest hardware will offer.
constraints.video.width = w;
constraints.video.height = h;
}
if ((framerate_numerator > 0) && (framerate_denominator > 0)) {
var fps = framerate_numerator / framerate_denominator;
constraints.video.frameRate = { ideal: fps };
}
function grabNextCameraFrame() { // !!! FIXME: this (currently) runs as a requestAnimationFrame callback, for lack of a better option.
const SDL3 = Module['SDL3'];
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) {
return; // camera was closed and/or subsystem was shut down, stop iterating here.
}
// time for a new frame from the camera?
const nextframems = SDL3.camera.next_frame_time;
const now = performance.now();
if (now >= nextframems) {
dynCall('vi', iterate, [device]); // calls SDL_CameraThreadIterate, which will call our AcquireFrame implementation.
// bump ahead but try to stay consistent on timing, in case we dropped frames.
while (SDL3.camera.next_frame_time < now) {
SDL3.camera.next_frame_time += SDL3.camera.fpsincrms;
}
}
requestAnimationFrame(grabNextCameraFrame); // run this function again at the display framerate. (!!! FIXME: would this be better as requestIdleCallback?)
}
navigator.mediaDevices.getUserMedia(constraints)
.then((stream) => {
const settings = stream.getVideoTracks()[0].getSettings();
const actualw = settings.width;
const actualh = settings.height;
const actualfps = settings.frameRate;
console.log("Camera is opened! Actual spec: (" + actualw + "x" + actualh + "), fps=" + actualfps);
if (dynCall('iiiiii', outcome, [device, 1, actualw, actualh, actualfps])) {
const video = document.createElement("video");
video.width = actualw;
video.height = actualh;
video.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels.
video.srcObject = stream;
const canvas = document.createElement("canvas");
canvas.width = actualw;
canvas.height = actualh;
canvas.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels.
const ctx2d = canvas.getContext('2d');
const SDL3 = Module['SDL3'];
SDL3.camera.width = actualw;
SDL3.camera.height = actualh;
SDL3.camera.fps = actualfps;
SDL3.camera.fpsincrms = 1000.0 / actualfps;
SDL3.camera.stream = stream;
SDL3.camera.video = video;
SDL3.camera.canvas = canvas;
SDL3.camera.ctx2d = ctx2d;
SDL3.camera.next_frame_time = performance.now();
video.play();
video.addEventListener('loadedmetadata', () => {
grabNextCameraFrame(); // start this loop going.
});
}
})
.catch((err) => {
console.error("Tried to open camera but it threw an error! " + err.name + ": " + err.message);
dynCall('iiiiii', outcome, [device, 0, 0, 0, 0]); // we call this a permission error, because it probably is.
});
}, device, spec->width, spec->height, spec->framerate_numerator, spec->framerate_denominator, SDLEmscriptenCameraPermissionOutcome, SDL_CameraThreadIterate);
return true; // the real work waits until the user approves a camera.
}
static void EMSCRIPTENCAMERA_FreeDeviceHandle(SDL_Camera *device)
{
// no-op.
}
static void EMSCRIPTENCAMERA_Deinitialize(void)
{
MAIN_THREAD_EM_ASM({
if (typeof(Module['SDL3']) !== 'undefined') {
Module['SDL3'].camera = undefined;
}
});
}
static void EMSCRIPTENCAMERA_DetectDevices(void)
{
// `navigator.mediaDevices` is not defined if unsupported or not in a secure context!
const int supported = MAIN_THREAD_EM_ASM_INT({ return (navigator.mediaDevices === undefined) ? 0 : 1; });
// if we have support at all, report a single generic camera with no specs.
// We'll find out if there really _is_ a camera when we try to open it, but querying it for real here
// will pop up a user permission dialog warning them we're trying to access the camera, and we generally
// don't want that during SDL_Init().
if (supported) {
SDL_AddCamera("Web browser's camera", SDL_CAMERA_POSITION_UNKNOWN, 0, NULL, (void *) (size_t) 0x1);
}
}
static bool EMSCRIPTENCAMERA_Init(SDL_CameraDriverImpl *impl)
{
MAIN_THREAD_EM_ASM({
if (typeof(Module['SDL3']) === 'undefined') {
Module['SDL3'] = {};
}
Module['SDL3'].camera = {};
});
impl->DetectDevices = EMSCRIPTENCAMERA_DetectDevices;
impl->OpenDevice = EMSCRIPTENCAMERA_OpenDevice;
impl->CloseDevice = EMSCRIPTENCAMERA_CloseDevice;
impl->WaitDevice = EMSCRIPTENCAMERA_WaitDevice;
impl->AcquireFrame = EMSCRIPTENCAMERA_AcquireFrame;
impl->ReleaseFrame = EMSCRIPTENCAMERA_ReleaseFrame;
impl->FreeDeviceHandle = EMSCRIPTENCAMERA_FreeDeviceHandle;
impl->Deinitialize = EMSCRIPTENCAMERA_Deinitialize;
impl->ProvidesOwnCallbackThread = true;
return true;
}
CameraBootStrap EMSCRIPTENCAMERA_bootstrap = {
"emscripten", "SDL Emscripten MediaStream camera driver", EMSCRIPTENCAMERA_Init, false
};
/* *INDENT-ON* */ // clang-format on
#endif // SDL_CAMERA_DRIVER_EMSCRIPTEN

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,929 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_CAMERA_DRIVER_V4L2
#include <dirent.h>
#include <errno.h>
#include <fcntl.h> // low-level i/o
#include <stddef.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/videodev2.h>
#ifndef V4L2_CAP_DEVICE_CAPS
// device_caps was added to struct v4l2_capability as of kernel 3.4.
#define device_caps reserved[0]
SDL_COMPILE_TIME_ASSERT(v4l2devicecaps, offsetof(struct v4l2_capability,device_caps) == offsetof(struct v4l2_capability,capabilities) + 4);
#endif
#include "../SDL_syscamera.h"
#include "../SDL_camera_c.h"
#include "../../video/SDL_pixels_c.h"
#include "../../video/SDL_surface_c.h"
#include "../../thread/SDL_systhread.h"
#include "../../core/linux/SDL_evdev_capabilities.h"
#include "../../core/linux/SDL_udev.h"
#ifndef SDL_USE_LIBUDEV
#include <dirent.h>
#endif
typedef struct V4L2DeviceHandle
{
char *bus_info;
char *path;
} V4L2DeviceHandle;
typedef enum io_method {
IO_METHOD_INVALID,
IO_METHOD_READ,
IO_METHOD_MMAP,
IO_METHOD_USERPTR
} io_method;
struct buffer {
void *start;
size_t length;
int available; // Is available in userspace
};
struct SDL_PrivateCameraData
{
int fd;
io_method io;
int nb_buffers;
struct buffer *buffers;
int driver_pitch;
};
static int xioctl(int fh, int request, void *arg)
{
int r;
do {
r = ioctl(fh, request, arg);
} while ((r == -1) && (errno == EINTR));
return r;
}
static bool V4L2_WaitDevice(SDL_Camera *device)
{
const int fd = device->hidden->fd;
int rc;
do {
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100 * 1000;
rc = select(fd + 1, &fds, NULL, NULL, &tv);
if ((rc == -1) && (errno == EINTR)) {
rc = 0; // pretend it was a timeout, keep looping.
} else if (rc > 0) {
return true;
}
// Thread is requested to shut down
if (SDL_GetAtomicInt(&device->shutdown)) {
return true;
}
} while (rc == 0);
return false;
}
static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
{
const int fd = device->hidden->fd;
const io_method io = device->hidden->io;
size_t size = device->hidden->buffers[0].length;
struct v4l2_buffer buf;
ssize_t amount;
switch (io) {
case IO_METHOD_READ:
if ((amount = read(fd, device->hidden->buffers[0].start, size)) == -1) {
switch (errno) {
case EAGAIN:
return SDL_CAMERA_FRAME_SKIP;
case EIO:
// Could ignore EIO, see spec.
// fall through
default:
SDL_SetError("read");
return SDL_CAMERA_FRAME_ERROR;
}
}
*timestampNS = SDL_GetTicksNS(); // oh well, close enough.
frame->pixels = device->hidden->buffers[0].start;
if (device->hidden->driver_pitch) {
frame->pitch = device->hidden->driver_pitch;
} else {
frame->pitch = (int)amount;
}
break;
case IO_METHOD_MMAP:
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
switch (errno) {
case EAGAIN:
return SDL_CAMERA_FRAME_SKIP;
case EIO:
// Could ignore EIO, see spec.
// fall through
default:
SDL_SetError("VIDIOC_DQBUF: %d", errno);
return SDL_CAMERA_FRAME_ERROR;
}
}
if ((int)buf.index < 0 || (int)buf.index >= device->hidden->nb_buffers) {
SDL_SetError("invalid buffer index");
return SDL_CAMERA_FRAME_ERROR;
}
frame->pixels = device->hidden->buffers[buf.index].start;
if (device->hidden->driver_pitch) {
frame->pitch = device->hidden->driver_pitch;
} else {
frame->pitch = buf.bytesused;
}
device->hidden->buffers[buf.index].available = 1;
*timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec);
#if DEBUG_CAMERA
SDL_Log("CAMERA: debug mmap: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void*)frame->pixels);
#endif
break;
case IO_METHOD_USERPTR:
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
switch (errno) {
case EAGAIN:
return SDL_CAMERA_FRAME_SKIP;
case EIO:
// Could ignore EIO, see spec.
// fall through
default:
SDL_SetError("VIDIOC_DQBUF");
return SDL_CAMERA_FRAME_ERROR;
}
}
int i;
for (i = 0; i < device->hidden->nb_buffers; ++i) {
if (buf.m.userptr == (unsigned long)device->hidden->buffers[i].start && buf.length == size) {
break;
}
}
if (i >= device->hidden->nb_buffers) {
SDL_SetError("invalid buffer index");
return SDL_CAMERA_FRAME_ERROR;
}
frame->pixels = (void*)buf.m.userptr;
if (device->hidden->driver_pitch) {
frame->pitch = device->hidden->driver_pitch;
} else {
frame->pitch = buf.bytesused;
}
device->hidden->buffers[i].available = 1;
*timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec);
#if DEBUG_CAMERA
SDL_Log("CAMERA: debug userptr: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void*)frame->pixels);
#endif
break;
case IO_METHOD_INVALID:
SDL_assert(!"Shouldn't have hit this");
break;
}
return SDL_CAMERA_FRAME_READY;
}
static void V4L2_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
{
struct v4l2_buffer buf;
const int fd = device->hidden->fd;
const io_method io = device->hidden->io;
int i;
for (i = 0; i < device->hidden->nb_buffers; ++i) {
if (frame->pixels == device->hidden->buffers[i].start) {
break;
}
}
if (i >= device->hidden->nb_buffers) {
return; // oh well, we didn't own this.
}
switch (io) {
case IO_METHOD_READ:
break;
case IO_METHOD_MMAP:
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
// !!! FIXME: disconnect the device.
return; //SDL_SetError("VIDIOC_QBUF");
}
device->hidden->buffers[i].available = 0;
break;
case IO_METHOD_USERPTR:
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
buf.index = i;
buf.m.userptr = (unsigned long)frame->pixels;
buf.length = (int) device->hidden->buffers[i].length;
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
// !!! FIXME: disconnect the device.
return; //SDL_SetError("VIDIOC_QBUF");
}
device->hidden->buffers[i].available = 0;
break;
case IO_METHOD_INVALID:
SDL_assert(!"Shouldn't have hit this");
break;
}
}
static bool EnqueueBuffers(SDL_Camera *device)
{
const int fd = device->hidden->fd;
const io_method io = device->hidden->io;
switch (io) {
case IO_METHOD_READ:
break;
case IO_METHOD_MMAP:
for (int i = 0; i < device->hidden->nb_buffers; ++i) {
if (device->hidden->buffers[i].available == 0) {
struct v4l2_buffer buf;
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
return SDL_SetError("VIDIOC_QBUF");
}
}
}
break;
case IO_METHOD_USERPTR:
for (int i = 0; i < device->hidden->nb_buffers; ++i) {
if (device->hidden->buffers[i].available == 0) {
struct v4l2_buffer buf;
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
buf.index = i;
buf.m.userptr = (unsigned long)device->hidden->buffers[i].start;
buf.length = (int) device->hidden->buffers[i].length;
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
return SDL_SetError("VIDIOC_QBUF");
}
}
}
break;
case IO_METHOD_INVALID: SDL_assert(!"Shouldn't have hit this"); break;
}
return true;
}
static bool AllocBufferRead(SDL_Camera *device, size_t buffer_size)
{
device->hidden->buffers[0].length = buffer_size;
device->hidden->buffers[0].start = SDL_calloc(1, buffer_size);
return (device->hidden->buffers[0].start != NULL);
}
static bool AllocBufferMmap(SDL_Camera *device)
{
const int fd = device->hidden->fd;
int i;
for (i = 0; i < device->hidden->nb_buffers; ++i) {
struct v4l2_buffer buf;
SDL_zero(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
return SDL_SetError("VIDIOC_QUERYBUF");
}
device->hidden->buffers[i].length = buf.length;
device->hidden->buffers[i].start =
mmap(NULL /* start anywhere */,
buf.length,
PROT_READ | PROT_WRITE /* required */,
MAP_SHARED /* recommended */,
fd, buf.m.offset);
if (MAP_FAILED == device->hidden->buffers[i].start) {
return SDL_SetError("mmap");
}
}
return true;
}
static bool AllocBufferUserPtr(SDL_Camera *device, size_t buffer_size)
{
int i;
for (i = 0; i < device->hidden->nb_buffers; ++i) {
device->hidden->buffers[i].length = buffer_size;
device->hidden->buffers[i].start = SDL_calloc(1, buffer_size);
if (!device->hidden->buffers[i].start) {
return false;
}
}
return true;
}
static void format_v4l2_to_sdl(Uint32 fmt, SDL_PixelFormat *format, SDL_Colorspace *colorspace)
{
switch (fmt) {
#define CASE(x, y, z) case x: *format = y; *colorspace = z; return
CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED);
CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_SRGB);
#undef CASE
default:
#if DEBUG_CAMERA
SDL_Log("CAMERA: Unknown format V4L2_PIX_FORMAT '%c%c%c%c' (0x%x)",
(char)(Uint8)(fmt >> 0),
(char)(Uint8)(fmt >> 8),
(char)(Uint8)(fmt >> 16),
(char)(Uint8)(fmt >> 24), fmt);
#endif
break;
}
*format = SDL_PIXELFORMAT_UNKNOWN;
*colorspace = SDL_COLORSPACE_UNKNOWN;
}
static Uint32 format_sdl_to_v4l2(SDL_PixelFormat fmt)
{
switch (fmt) {
#define CASE(y, x) case x: return y
CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2);
CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG);
#undef CASE
default:
return 0;
}
}
static void V4L2_CloseDevice(SDL_Camera *device)
{
if (!device) {
return;
}
if (device->hidden) {
const io_method io = device->hidden->io;
const int fd = device->hidden->fd;
if ((io == IO_METHOD_MMAP) || (io == IO_METHOD_USERPTR)) {
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
xioctl(fd, VIDIOC_STREAMOFF, &type);
}
if (device->hidden->buffers) {
switch (io) {
case IO_METHOD_INVALID:
break;
case IO_METHOD_READ:
SDL_free(device->hidden->buffers[0].start);
break;
case IO_METHOD_MMAP:
for (int i = 0; i < device->hidden->nb_buffers; ++i) {
if (munmap(device->hidden->buffers[i].start, device->hidden->buffers[i].length) == -1) {
SDL_SetError("munmap");
}
}
break;
case IO_METHOD_USERPTR:
for (int i = 0; i < device->hidden->nb_buffers; ++i) {
SDL_free(device->hidden->buffers[i].start);
}
break;
}
SDL_free(device->hidden->buffers);
}
if (fd != -1) {
close(fd);
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static bool V4L2_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
{
const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
struct stat st;
struct v4l2_capability cap;
const int fd = open(handle->path, O_RDWR /* required */ | O_NONBLOCK, 0);
// most of this probably shouldn't fail unless the filesystem node changed out from under us since MaybeAddDevice().
if (fd == -1) {
return SDL_SetError("Cannot open '%s': %d, %s", handle->path, errno, strerror(errno));
} else if (fstat(fd, &st) == -1) {
close(fd);
return SDL_SetError("Cannot identify '%s': %d, %s", handle->path, errno, strerror(errno));
} else if (!S_ISCHR(st.st_mode)) {
close(fd);
return SDL_SetError("%s is not a character device", handle->path);
} else if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
const int err = errno;
close(fd);
if (err == EINVAL) {
return SDL_SetError("%s is unexpectedly not a V4L2 device", handle->path);
}
return SDL_SetError("Error VIDIOC_QUERYCAP errno=%d device%s is no V4L2 device", err, handle->path);
} else if ((cap.device_caps & V4L2_CAP_VIDEO_CAPTURE) == 0) {
close(fd);
return SDL_SetError("%s is unexpectedly not a video capture device", handle->path);
}
device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
if (device->hidden == NULL) {
close(fd);
return false;
}
device->hidden->fd = fd;
device->hidden->io = IO_METHOD_INVALID;
// Select video input, video standard and tune here.
// errors in the crop code are not fatal.
struct v4l2_cropcap cropcap;
SDL_zero(cropcap);
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) {
struct v4l2_crop crop;
SDL_zero(crop);
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
crop.c = cropcap.defrect; // reset to default
xioctl(fd, VIDIOC_S_CROP, &crop);
}
struct v4l2_format fmt;
SDL_zero(fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = spec->width;
fmt.fmt.pix.height = spec->height;
fmt.fmt.pix.pixelformat = format_sdl_to_v4l2(spec->format);
//fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
#if DEBUG_CAMERA
SDL_Log("CAMERA: set SDL format %s", SDL_GetPixelFormatName(spec->format));
{ const Uint32 f = fmt.fmt.pix.pixelformat; SDL_Log("CAMERA: set format V4L2_format=%d %c%c%c%c", f, (f >> 0) & 0xff, (f >> 8) & 0xff, (f >> 16) & 0xff, (f >> 24) & 0xff); }
#endif
if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
return SDL_SetError("Error VIDIOC_S_FMT");
}
if (spec->framerate_numerator && spec->framerate_denominator) {
struct v4l2_streamparm setfps;
SDL_zero(setfps);
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_G_PARM, &setfps) == 0) {
if ( (setfps.parm.capture.timeperframe.denominator != spec->framerate_numerator) ||
(setfps.parm.capture.timeperframe.numerator = spec->framerate_denominator) ) {
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
setfps.parm.capture.timeperframe.numerator = spec->framerate_denominator;
setfps.parm.capture.timeperframe.denominator = spec->framerate_numerator;
if (xioctl(fd, VIDIOC_S_PARM, &setfps) == -1) {
return SDL_SetError("Error VIDIOC_S_PARM");
}
}
}
}
SDL_zero(fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
return SDL_SetError("Error VIDIOC_G_FMT");
}
device->hidden->driver_pitch = fmt.fmt.pix.bytesperline;
io_method io = IO_METHOD_INVALID;
if ((io == IO_METHOD_INVALID) && (cap.device_caps & V4L2_CAP_STREAMING)) {
struct v4l2_requestbuffers req;
SDL_zero(req);
req.count = 8;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if ((xioctl(fd, VIDIOC_REQBUFS, &req) == 0) && (req.count >= 2)) {
io = IO_METHOD_MMAP;
device->hidden->nb_buffers = req.count;
} else { // mmap didn't work out? Try USERPTR.
SDL_zero(req);
req.count = 8;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_USERPTR;
if (xioctl(fd, VIDIOC_REQBUFS, &req) == 0) {
io = IO_METHOD_USERPTR;
device->hidden->nb_buffers = 8;
}
}
}
if ((io == IO_METHOD_INVALID) && (cap.device_caps & V4L2_CAP_READWRITE)) {
io = IO_METHOD_READ;
device->hidden->nb_buffers = 1;
}
if (io == IO_METHOD_INVALID) {
return SDL_SetError("Don't have a way to talk to this device");
}
device->hidden->io = io;
device->hidden->buffers = SDL_calloc(device->hidden->nb_buffers, sizeof(*device->hidden->buffers));
if (!device->hidden->buffers) {
return false;
}
size_t size, pitch;
if (!SDL_CalculateSurfaceSize(device->spec.format, device->spec.width, device->spec.height, &size, &pitch, false)) {
return false;
}
bool rc = true;
switch (io) {
case IO_METHOD_READ:
rc = AllocBufferRead(device, size);
break;
case IO_METHOD_MMAP:
rc = AllocBufferMmap(device);
break;
case IO_METHOD_USERPTR:
rc = AllocBufferUserPtr(device, size);
break;
case IO_METHOD_INVALID:
SDL_assert(!"Shouldn't have hit this");
break;
}
if (!rc) {
return false;
} else if (!EnqueueBuffers(device)) {
return false;
} else if (io != IO_METHOD_READ) {
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_STREAMON, &type) == -1) {
return SDL_SetError("VIDIOC_STREAMON");
}
}
// Currently there is no user permission prompt for camera access, but maybe there will be a D-Bus portal interface at some point.
SDL_CameraPermissionOutcome(device, true);
return true;
}
static bool FindV4L2CameraByBusInfoCallback(SDL_Camera *device, void *userdata)
{
const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
return (SDL_strcmp(handle->bus_info, (const char *) userdata) == 0);
}
static bool AddCameraFormat(const int fd, CameraFormatAddData *data, SDL_PixelFormat sdlfmt, SDL_Colorspace colorspace, Uint32 v4l2fmt, int w, int h)
{
struct v4l2_frmivalenum frmivalenum;
SDL_zero(frmivalenum);
frmivalenum.pixel_format = v4l2fmt;
frmivalenum.width = (Uint32) w;
frmivalenum.height = (Uint32) h;
while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmivalenum) == 0) {
if (frmivalenum.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
const int numerator = (int) frmivalenum.discrete.numerator;
const int denominator = (int) frmivalenum.discrete.denominator;
#if DEBUG_CAMERA
const float fps = (float) denominator / (float) numerator;
SDL_Log("CAMERA: * Has discrete frame interval (%d / %d), fps=%f", numerator, denominator, fps);
#endif
if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, denominator, numerator)) {
return false; // Probably out of memory; we'll go with what we have, if anything.
}
frmivalenum.index++; // set up for the next one.
} else if ((frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) || (frmivalenum.type == V4L2_FRMIVAL_TYPE_CONTINUOUS)) {
int d = frmivalenum.stepwise.min.denominator;
// !!! FIXME: should we step by the numerator...?
for (int n = (int) frmivalenum.stepwise.min.numerator; n <= (int) frmivalenum.stepwise.max.numerator; n += (int) frmivalenum.stepwise.step.numerator) {
#if DEBUG_CAMERA
const float fps = (float) d / (float) n;
SDL_Log("CAMERA: * Has %s frame interval (%d / %d), fps=%f", (frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) ? "stepwise" : "continuous", n, d, fps);
#endif
// SDL expects framerate, V4L2 provides interval
if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, d, n)) {
return false; // Probably out of memory; we'll go with what we have, if anything.
}
d += (int) frmivalenum.stepwise.step.denominator;
}
break;
}
}
return true;
}
static void MaybeAddDevice(const char *path)
{
if (!path) {
return;
}
struct stat st;
const int fd = open(path, O_RDWR /* required */ | O_NONBLOCK, 0);
if (fd == -1) {
return; // can't open it? skip it.
} else if (fstat(fd, &st) == -1) {
close(fd);
return; // can't stat it? skip it.
} else if (!S_ISCHR(st.st_mode)) {
close(fd);
return; // not a character device.
}
struct v4l2_capability vcap;
const int rc = ioctl(fd, VIDIOC_QUERYCAP, &vcap);
if (rc != 0) {
close(fd);
return; // probably not a v4l2 device at all.
} else if ((vcap.device_caps & V4L2_CAP_VIDEO_CAPTURE) == 0) {
close(fd);
return; // not a video capture device.
} else if (SDL_FindPhysicalCameraByCallback(FindV4L2CameraByBusInfoCallback, vcap.bus_info)) {
close(fd);
return; // already have it.
}
#if DEBUG_CAMERA
SDL_Log("CAMERA: V4L2 camera path='%s' bus_info='%s' name='%s'", path, (const char *) vcap.bus_info, vcap.card);
#endif
CameraFormatAddData add_data;
SDL_zero(add_data);
struct v4l2_fmtdesc fmtdesc;
SDL_zero(fmtdesc);
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
SDL_PixelFormat sdlfmt = SDL_PIXELFORMAT_UNKNOWN;
SDL_Colorspace colorspace = SDL_COLORSPACE_UNKNOWN;
format_v4l2_to_sdl(fmtdesc.pixelformat, &sdlfmt, &colorspace);
#if DEBUG_CAMERA
SDL_Log("CAMERA: - Has format '%s'%s%s", SDL_GetPixelFormatName(sdlfmt),
(fmtdesc.flags & V4L2_FMT_FLAG_EMULATED) ? " [EMULATED]" : "",
(fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED) ? " [COMPRESSED]" : "");
#endif
fmtdesc.index++; // prepare for next iteration.
if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) {
continue; // unsupported by SDL atm.
}
struct v4l2_frmsizeenum frmsizeenum;
SDL_zero(frmsizeenum);
frmsizeenum.pixel_format = fmtdesc.pixelformat;
while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) {
if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
const int w = (int) frmsizeenum.discrete.width;
const int h = (int) frmsizeenum.discrete.height;
#if DEBUG_CAMERA
SDL_Log("CAMERA: * Has discrete size %dx%d", w, h);
#endif
if (!AddCameraFormat(fd, &add_data, sdlfmt, colorspace, fmtdesc.pixelformat, w, h)) {
break; // Probably out of memory; we'll go with what we have, if anything.
}
frmsizeenum.index++; // set up for the next one.
} else if ((frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) || (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS)) {
const int minw = (int) frmsizeenum.stepwise.min_width;
const int minh = (int) frmsizeenum.stepwise.min_height;
const int maxw = (int) frmsizeenum.stepwise.max_width;
const int maxh = (int) frmsizeenum.stepwise.max_height;
const int stepw = (int) frmsizeenum.stepwise.step_width;
const int steph = (int) frmsizeenum.stepwise.step_height;
for (int w = minw; w <= maxw; w += stepw) {
for (int h = minh; w <= maxh; w += steph) {
#if DEBUG_CAMERA
SDL_Log("CAMERA: * Has %s size %dx%d", (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) ? "stepwise" : "continuous", w, h);
#endif
if (!AddCameraFormat(fd, &add_data, sdlfmt, colorspace, fmtdesc.pixelformat, w, h)) {
break; // Probably out of memory; we'll go with what we have, if anything.
}
}
}
break;
}
}
}
close(fd);
#if DEBUG_CAMERA
SDL_Log("CAMERA: (total specs: %d)", add_data.num_specs);
#endif
if (add_data.num_specs > 0) {
V4L2DeviceHandle *handle = (V4L2DeviceHandle *) SDL_calloc(1, sizeof (V4L2DeviceHandle));
if (handle) {
handle->path = SDL_strdup(path);
if (handle->path) {
handle->bus_info = SDL_strdup((char *)vcap.bus_info);
if (handle->bus_info) {
if (SDL_AddCamera((const char *) vcap.card, SDL_CAMERA_POSITION_UNKNOWN, add_data.num_specs, add_data.specs, handle)) {
SDL_free(add_data.specs);
return; // good to go.
}
SDL_free(handle->bus_info);
}
SDL_free(handle->path);
}
SDL_free(handle);
}
}
SDL_free(add_data.specs);
}
static void V4L2_FreeDeviceHandle(SDL_Camera *device)
{
if (device) {
V4L2DeviceHandle *handle = (V4L2DeviceHandle *) device->handle;
SDL_free(handle->path);
SDL_free(handle->bus_info);
SDL_free(handle);
}
}
#ifdef SDL_USE_LIBUDEV
static bool FindV4L2CameraByPathCallback(SDL_Camera *device, void *userdata)
{
const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
return (SDL_strcmp(handle->path, (const char *) userdata) == 0);
}
static void MaybeRemoveDevice(const char *path)
{
if (path) {
SDL_CameraDisconnected(SDL_FindPhysicalCameraByCallback(FindV4L2CameraByPathCallback, (void *) path));
}
}
static void CameraUdevCallback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath)
{
if (devpath && (udev_class & SDL_UDEV_DEVICE_VIDEO_CAPTURE)) {
if (udev_type == SDL_UDEV_DEVICEADDED) {
MaybeAddDevice(devpath);
} else if (udev_type == SDL_UDEV_DEVICEREMOVED) {
MaybeRemoveDevice(devpath);
}
}
}
#endif // SDL_USE_LIBUDEV
static void V4L2_Deinitialize(void)
{
#ifdef SDL_USE_LIBUDEV
SDL_UDEV_DelCallback(CameraUdevCallback);
SDL_UDEV_Quit();
#endif // SDL_USE_LIBUDEV
}
static void V4L2_DetectDevices(void)
{
#ifdef SDL_USE_LIBUDEV
if (SDL_UDEV_Init()) {
if (SDL_UDEV_AddCallback(CameraUdevCallback)) {
SDL_UDEV_Scan(); // Force a scan to build the initial device list
}
return;
}
#endif // SDL_USE_LIBUDEV
DIR *dirp = opendir("/dev");
if (dirp) {
struct dirent *dent;
while ((dent = readdir(dirp)) != NULL) {
int num = 0;
if (SDL_sscanf(dent->d_name, "video%d", &num) == 1) {
char fullpath[64];
SDL_snprintf(fullpath, sizeof (fullpath), "/dev/video%d", num);
MaybeAddDevice(fullpath);
}
}
closedir(dirp);
}
}
static bool V4L2_Init(SDL_CameraDriverImpl *impl)
{
impl->DetectDevices = V4L2_DetectDevices;
impl->OpenDevice = V4L2_OpenDevice;
impl->CloseDevice = V4L2_CloseDevice;
impl->WaitDevice = V4L2_WaitDevice;
impl->AcquireFrame = V4L2_AcquireFrame;
impl->ReleaseFrame = V4L2_ReleaseFrame;
impl->FreeDeviceHandle = V4L2_FreeDeviceHandle;
impl->Deinitialize = V4L2_Deinitialize;
return true;
}
CameraBootStrap V4L2_bootstrap = {
"v4l2", "SDL Video4Linux2 camera driver", V4L2_Init, false
};
#endif // SDL_CAMERA_DRIVER_V4L2

View file

@ -0,0 +1,258 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_CAMERA_DRIVER_VITA
#include "../SDL_syscamera.h"
#include <psp2/camera.h>
#include <psp2/kernel/sysmem.h>
static struct {
Sint32 w;
Sint32 h;
Sint32 res;
} resolutions[] = {
{640, 480, SCE_CAMERA_RESOLUTION_640_480},
{320, 240, SCE_CAMERA_RESOLUTION_320_240},
{160, 120, SCE_CAMERA_RESOLUTION_160_120},
{352, 288, SCE_CAMERA_RESOLUTION_352_288},
{176, 144, SCE_CAMERA_RESOLUTION_176_144},
{480, 272, SCE_CAMERA_RESOLUTION_480_272},
{640, 360, SCE_CAMERA_RESOLUTION_640_360},
{0, 0, 0}
};
static Sint32 fps[] = {5, 10, 15, 20, 24, 25, 30, 60, 0};
static void GatherCameraSpecs(Sint32 devid, CameraFormatAddData *add_data, char **fullname, SDL_CameraPosition *position)
{
SDL_zerop(add_data);
if (devid == SCE_CAMERA_DEVICE_FRONT) {
*position = SDL_CAMERA_POSITION_FRONT_FACING;
*fullname = SDL_strdup("Front-facing camera");
} else if (devid == SCE_CAMERA_DEVICE_BACK) {
*position = SDL_CAMERA_POSITION_BACK_FACING;
*fullname = SDL_strdup("Back-facing camera");
}
if (!*fullname) {
*fullname = SDL_strdup("Generic camera");
}
// Note: there are actually more fps and pixelformats. Planar YUV is fastest. Support only YUV and integer fps for now
Sint32 idx = 0;
while (resolutions[idx].res > 0) {
Sint32 fps_idx = 0;
while (fps[fps_idx] > 0) {
SDL_AddCameraFormat(add_data, SDL_PIXELFORMAT_IYUV, SDL_COLORSPACE_BT601_LIMITED, resolutions[idx].w, resolutions[idx].h, fps[fps_idx], 1); /* SCE_CAMERA_FORMAT_ARGB */
fps_idx++;
}
idx++;
}
}
static bool FindVitaCameraByID(SDL_Camera *device, void *userdata)
{
Sint32 devid = (Sint32) userdata;
return (devid == (Sint32)device->handle);
}
static void MaybeAddDevice(Sint32 devid)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: MaybeAddDevice('%d')", devid);
#endif
if (SDL_FindPhysicalCameraByCallback(FindVitaCameraByID, (void *) devid)) {
return; // already have this one.
}
SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
char *fullname = NULL;
CameraFormatAddData add_data;
GatherCameraSpecs(devid, &add_data, &fullname, &position);
if (add_data.num_specs > 0) {
SDL_AddCamera(fullname, position, add_data.num_specs, add_data.specs, (void*)devid);
}
SDL_free(fullname);
SDL_free(add_data.specs);
}
static SceUID imbUid = -1;
static void freeBuffers(SceCameraInfo* info)
{
if (imbUid != -1) {
sceKernelFreeMemBlock(imbUid);
info->pIBase = NULL;
imbUid = -1;
}
}
static bool VITACAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
{
// we can't open more than one camera, so error-out early
if (imbUid != -1) {
return SDL_SetError("Only one camera can be active");
}
SceCameraInfo* info = (SceCameraInfo*)SDL_calloc(1, sizeof(SceCameraInfo));
info->size = sizeof(SceCameraInfo);
info->priority = SCE_CAMERA_PRIORITY_SHARE;
info->buffer = 0; // target buffer set by sceCameraOpen
info->framerate = spec->framerate_numerator / spec->framerate_denominator;
Sint32 idx = 0;
while (resolutions[idx].res > 0) {
if (spec->width == resolutions[idx].w && spec->height == resolutions[idx].h) {
info->resolution = resolutions[idx].res;
break;
}
idx++;
}
info->range = 1;
info->format = SCE_CAMERA_FORMAT_YUV420_PLANE;
info->pitch = 0; // same size surface
info->sizeIBase = spec->width*spec->height;;
info->sizeUBase = ((spec->width+1)/2) * ((spec->height+1) / 2);
info->sizeVBase = ((spec->width+1)/2) * ((spec->height+1) / 2);
// PHYCONT memory size *must* be a multiple of 1MB, we can just always spend 2MB, since we don't use PHYCONT anywhere else
imbUid = sceKernelAllocMemBlock("CameraI", SCE_KERNEL_MEMBLOCK_TYPE_USER_MAIN_PHYCONT_NC_RW, 2*1024*1024 , NULL);
if (imbUid < 0)
{
return SDL_SetError("sceKernelAllocMemBlock error: 0x%08X", imbUid);
}
sceKernelGetMemBlockBase(imbUid, &(info->pIBase));
info->pUBase = info->pIBase + info->sizeIBase;
info->pVBase = info->pIBase + (info->sizeIBase + info->sizeUBase);
device->hidden = (struct SDL_PrivateCameraData *)info;
int ret = sceCameraOpen((int)device->handle, info);
if (ret == 0) {
ret = sceCameraStart((int)device->handle);
if (ret == 0) {
SDL_CameraPermissionOutcome(device, true);
return true;
} else {
SDL_SetError("sceCameraStart error: 0x%08X", imbUid);
}
} else {
SDL_SetError("sceCameraOpen error: 0x%08X", imbUid);
}
freeBuffers(info);
return false;
}
static void VITACAMERA_CloseDevice(SDL_Camera *device)
{
if (device->hidden) {
sceCameraStop((int)device->handle);
sceCameraClose((int)device->handle);
freeBuffers((SceCameraInfo*)device->hidden);
SDL_free(device->hidden);
}
}
static bool VITACAMERA_WaitDevice(SDL_Camera *device)
{
while(!sceCameraIsActive((int)device->handle)) {}
return true;
}
static SDL_CameraFrameResult VITACAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
{
SceCameraRead read = {0};
read.size = sizeof(SceCameraRead);
read.mode = 1; // don't wait next frame
int ret = sceCameraRead((int)device->handle, &read);
if (ret < 0) {
SDL_SetError("sceCameraRead error: 0x%08X", ret);
return SDL_CAMERA_FRAME_ERROR;
}
*timestampNS = read.timestamp;
SceCameraInfo* info = (SceCameraInfo*)(device->hidden);
frame->pitch = info->width;
frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), info->sizeIBase + info->sizeUBase + info->sizeVBase);
if (frame->pixels) {
SDL_memcpy(frame->pixels, info->pIBase, info->sizeIBase + info->sizeUBase + info->sizeVBase);
return SDL_CAMERA_FRAME_READY;
}
return SDL_CAMERA_FRAME_ERROR;
}
static void VITACAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
{
SDL_aligned_free(frame->pixels);
}
static void VITACAMERA_DetectDevices(void)
{
MaybeAddDevice(SCE_CAMERA_DEVICE_FRONT);
MaybeAddDevice(SCE_CAMERA_DEVICE_BACK);
}
static void VITACAMERA_FreeDeviceHandle(SDL_Camera *device)
{
}
static void VITACAMERA_Deinitialize(void)
{
}
static bool VITACAMERA_Init(SDL_CameraDriverImpl *impl)
{
impl->DetectDevices = VITACAMERA_DetectDevices;
impl->OpenDevice = VITACAMERA_OpenDevice;
impl->CloseDevice = VITACAMERA_CloseDevice;
impl->WaitDevice = VITACAMERA_WaitDevice;
impl->AcquireFrame = VITACAMERA_AcquireFrame;
impl->ReleaseFrame = VITACAMERA_ReleaseFrame;
impl->FreeDeviceHandle = VITACAMERA_FreeDeviceHandle;
impl->Deinitialize = VITACAMERA_Deinitialize;
return true;
}
CameraBootStrap VITACAMERA_bootstrap = {
"vita", "SDL PSVita camera driver", VITACAMERA_Init, false
};
#endif // SDL_CAMERA_DRIVER_VITA

View file

@ -0,0 +1,213 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_VIDEO_DRIVER_X11
SDL_DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata);
void SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata)
{
}
#endif
#ifndef SDL_PLATFORM_LINUX
SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriority(Sint64 threadID, int priority);
bool SDL_SetLinuxThreadPriority(Sint64 threadID, int priority)
{
(void)threadID;
(void)priority;
return SDL_Unsupported();
}
SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy);
bool SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy)
{
(void)threadID;
(void)sdlPriority;
(void)schedPolicy;
return SDL_Unsupported();
}
#endif
#ifndef SDL_PLATFORM_GDK
SDL_DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void);
void SDL_GDKSuspendComplete(void)
{
SDL_Unsupported();
}
SDL_DECLSPEC bool SDLCALL SDL_GetGDKDefaultUser(void *outUserHandle); /* XUserHandle *outUserHandle */
bool SDL_GetGDKDefaultUser(void *outUserHandle)
{
return SDL_Unsupported();
}
SDL_DECLSPEC void SDLCALL SDL_GDKSuspendGPU(SDL_GPUDevice *device);
void SDL_GDKSuspendGPU(SDL_GPUDevice *device)
{
}
SDL_DECLSPEC void SDLCALL SDL_GDKResumeGPU(SDL_GPUDevice *device);
void SDL_GDKResumeGPU(SDL_GPUDevice *device)
{
}
#endif
#if !defined(SDL_PLATFORM_WINDOWS)
SDL_DECLSPEC bool SDLCALL SDL_RegisterApp(const char *name, Uint32 style, void *hInst);
bool SDL_RegisterApp(const char *name, Uint32 style, void *hInst)
{
(void)name;
(void)style;
(void)hInst;
return SDL_Unsupported();
}
SDL_DECLSPEC void SDLCALL SDL_SetWindowsMessageHook(void *callback, void *userdata); // SDL_WindowsMessageHook callback
void SDL_SetWindowsMessageHook(void *callback, void *userdata)
{
(void)callback;
(void)userdata;
SDL_Unsupported();
}
SDL_DECLSPEC void SDLCALL SDL_UnregisterApp(void);
void SDL_UnregisterApp(void)
{
SDL_Unsupported();
}
#endif
#ifndef SDL_PLATFORM_ANDROID
SDL_DECLSPEC void SDLCALL SDL_SendAndroidBackButton(void);
void SDL_SendAndroidBackButton(void)
{
SDL_Unsupported();
}
SDL_DECLSPEC void * SDLCALL SDL_GetAndroidActivity(void);
void *SDL_GetAndroidActivity(void)
{
SDL_Unsupported();
return NULL;
}
SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidCachePath(void);
const char* SDL_GetAndroidCachePath(void)
{
SDL_Unsupported();
return NULL;
}
SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidExternalStoragePath(void);
const char* SDL_GetAndroidExternalStoragePath(void)
{
SDL_Unsupported();
return NULL;
}
SDL_DECLSPEC Uint32 SDLCALL SDL_GetAndroidExternalStorageState(void);
Uint32 SDL_GetAndroidExternalStorageState(void)
{
SDL_Unsupported();
return 0;
}
SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidInternalStoragePath(void);
const char *SDL_GetAndroidInternalStoragePath(void)
{
SDL_Unsupported();
return NULL;
}
SDL_DECLSPEC void * SDLCALL SDL_GetAndroidJNIEnv(void);
void *SDL_GetAndroidJNIEnv(void)
{
SDL_Unsupported();
return NULL;
}
typedef void (SDLCALL *SDL_RequestAndroidPermissionCallback)(void *userdata, const char *permission, bool granted);
SDL_DECLSPEC bool SDLCALL SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata);
bool SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata)
{
(void)permission;
(void)cb;
(void)userdata;
return SDL_Unsupported();
}
SDL_DECLSPEC bool SDLCALL SDL_SendAndroidMessage(Uint32 command, int param);
bool SDL_SendAndroidMessage(Uint32 command, int param)
{
(void)command;
(void)param;
return SDL_Unsupported();
}
SDL_DECLSPEC bool SDLCALL SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xoffset, int yoffset);
bool SDL_ShowAndroidToast(const char* message, int duration, int gravity, int xoffset, int yoffset)
{
(void)message;
(void)duration;
(void)gravity;
(void)xoffset;
(void)yoffset;
return SDL_Unsupported();
}
SDL_DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void);
int SDL_GetAndroidSDKVersion(void)
{
return SDL_Unsupported();
}
SDL_DECLSPEC bool SDLCALL SDL_IsChromebook(void);
bool SDL_IsChromebook(void)
{
SDL_Unsupported();
return false;
}
SDL_DECLSPEC bool SDLCALL SDL_IsDeXMode(void);
bool SDL_IsDeXMode(void)
{
SDL_Unsupported();
return false;
}
SDL_DECLSPEC Sint32 SDLCALL JNI_OnLoad(void *vm, void *reserved);
Sint32 JNI_OnLoad(void *vm, void *reserved)
{
(void)vm;
(void)reserved;
SDL_Unsupported();
return -1; // JNI_ERR
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,163 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
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_android_h
#define SDL_android_h
// Set up for C function definitions, even when using C++
#ifdef __cplusplus
/* *INDENT-OFF* */
extern "C" {
/* *INDENT-ON* */
#endif
#include <EGL/eglplatform.h>
#include <android/native_window_jni.h>
#include "../../audio/SDL_sysaudio.h"
// this appears to be broken right now (on Android, not SDL, I think...?).
#define ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES 0
// Life cycle
typedef enum
{
SDL_ANDROID_LIFECYCLE_WAKE,
SDL_ANDROID_LIFECYCLE_PAUSE,
SDL_ANDROID_LIFECYCLE_RESUME,
SDL_ANDROID_LIFECYCLE_LOWMEMORY,
SDL_ANDROID_LIFECYCLE_DESTROY,
SDL_NUM_ANDROID_LIFECYCLE_EVENTS
} SDL_AndroidLifecycleEvent;
void Android_SendLifecycleEvent(SDL_AndroidLifecycleEvent event);
bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeoutNS);
void Android_LockActivityMutex(void);
void Android_UnlockActivityMutex(void);
// Interface from the SDL library into the Android Java activity
extern void Android_JNI_SetActivityTitle(const char *title);
extern void Android_JNI_SetWindowStyle(bool fullscreen);
extern void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint);
extern void Android_JNI_MinizeWindow(void);
extern bool Android_JNI_ShouldMinimizeOnFocusLoss(void);
extern bool Android_JNI_GetAccelerometerValues(float values[3]);
extern void Android_JNI_ShowScreenKeyboard(int input_type, SDL_Rect *inputRect);
extern void Android_JNI_HideScreenKeyboard(void);
extern bool Android_JNI_IsScreenKeyboardShown(void);
extern ANativeWindow *Android_JNI_GetNativeWindow(void);
extern SDL_DisplayOrientation Android_JNI_GetDisplayNaturalOrientation(void);
extern SDL_DisplayOrientation Android_JNI_GetDisplayCurrentOrientation(void);
// Audio support
void Android_StartAudioHotplug(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording);
void Android_StopAudioHotplug(void);
extern void Android_AudioThreadInit(SDL_AudioDevice *device);
// Detecting device type
extern bool Android_IsDeXMode(void);
extern bool Android_IsChromebook(void);
bool Android_JNI_FileOpen(void **puserdata, const char *fileName, const char *mode);
Sint64 Android_JNI_FileSize(void *userdata);
Sint64 Android_JNI_FileSeek(void *userdata, Sint64 offset, SDL_IOWhence whence);
size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOStatus *status);
size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status);
bool Android_JNI_FileClose(void *userdata);
// Environment support
void Android_JNI_GetManifestEnvironmentVariables(void);
int Android_JNI_OpenFileDescriptor(const char *uri, const char *mode);
// Clipboard support
bool Android_JNI_SetClipboardText(const char *text);
char *Android_JNI_GetClipboardText(void);
bool Android_JNI_HasClipboardText(void);
// Power support
int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent);
// Joystick support
void Android_JNI_PollInputDevices(void);
// Haptic support
void Android_JNI_PollHapticDevices(void);
void Android_JNI_HapticRun(int device_id, float intensity, int length);
void Android_JNI_HapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length);
void Android_JNI_HapticStop(int device_id);
// Video
bool Android_JNI_SuspendScreenSaver(bool suspend);
// Touch support
void Android_JNI_InitTouch(void);
// Threads
#include <jni.h>
JNIEnv *Android_JNI_GetEnv(void);
bool Android_JNI_SetupThread(void);
// Locale
bool Android_JNI_GetLocale(char *buf, size_t buflen);
// Generic messages
bool Android_JNI_SendMessage(int command, int param);
// MessageBox
bool Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID);
// Cursor support
int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y);
void Android_JNI_DestroyCustomCursor(int cursorID);
bool Android_JNI_SetCustomCursor(int cursorID);
bool Android_JNI_SetSystemCursor(int cursorID);
// Relative mouse support
bool Android_JNI_SupportsRelativeMouse(void);
bool Android_JNI_SetRelativeMouseEnabled(bool enabled);
// Show toast notification
bool Android_JNI_ShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset);
bool Android_JNI_OpenURL(const char *url);
int SDL_GetAndroidSDKVersion(void);
bool SDL_IsAndroidTablet(void);
bool SDL_IsAndroidTV(void);
// File Dialogs
bool Android_JNI_OpenFileDialog(SDL_DialogFileCallback callback, void* userdata,
const SDL_DialogFileFilter *filters, int nfilters, bool forwrite,
bool multiple);
// Ends C function definitions when using C++
#ifdef __cplusplus
/* *INDENT-OFF* */
}
/* *INDENT-ON* */
#endif
#endif // SDL_android_h

View file

@ -0,0 +1,167 @@
#include <sys/kbio.h>
/* *INDENT-OFF* */ // clang-format off
/*
* Automatically generated from /usr/share/vt/keymaps/us.acc.kbd.
* DO NOT EDIT!
*/
static keymap_t keymap_default_us_acc = { 0x6d, {
/* alt
* scan cntrl alt alt cntrl
* code base shift cntrl shift alt shift cntrl shift spcl flgs
* ---------------------------------------------------------------------------
*/
/*00*/{{ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, }, 0xFF,0x00 },
/*01*/{{ 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, DBG, DBG, }, 0x03,0x00 },
/*02*/{{ '1', '!', NOP, NOP, '1', '!', NOP, NOP, }, 0x33,0x00 },
/*03*/{{ '2', '@', 0x00, 0x00, '2', '@', 0x00, 0x00, }, 0x00,0x00 },
/*04*/{{ '3', '#', NOP, NOP, '3', '#', NOP, NOP, }, 0x33,0x00 },
/*05*/{{ '4', '$', NOP, NOP, '4', '$', NOP, NOP, }, 0x33,0x00 },
/*06*/{{ '5', '%', NOP, NOP, '5', '%', NOP, NOP, }, 0x33,0x00 },
/*07*/{{ '6', '^', 0x1E, 0x1E, '6', DCIR, 0x1E, 0x1E, }, 0x04,0x00 },
/*08*/{{ '7', '&', NOP, NOP, '7', '&', NOP, NOP, }, 0x33,0x00 },
/*09*/{{ '8', '*', NOP, NOP, '8', DRIN, NOP, NOP, }, 0x37,0x00 },
/*0a*/{{ '9', '(', NOP, NOP, '9', '(', NOP, NOP, }, 0x33,0x00 },
/*0b*/{{ '0', ')', NOP, NOP, '0', ')', NOP, NOP, }, 0x33,0x00 },
/*0c*/{{ '-', '_', 0x1F, 0x1F, '-', '_', 0x1F, 0x1F, }, 0x00,0x00 },
/*0d*/{{ '=', '+', NOP, NOP, '=', '+', NOP, NOP, }, 0x33,0x00 },
/*0e*/{{ 0x08, 0x08, 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, }, 0x00,0x00 },
/*0f*/{{ 0x09, BTAB, NEXT, NEXT, 0x09, BTAB, NOP, NOP, }, 0x77,0x00 },
/*10*/{{ 'q', 'Q', 0x11, 0x11, 'q', 'Q', 0x11, 0x11, }, 0x00,0x01 },
/*11*/{{ 'w', 'W', 0x17, 0x17, 'w', 'W', 0x17, 0x17, }, 0x00,0x01 },
/*12*/{{ 'e', 'E', 0x05, 0x05, 'e', 'E', 0x05, 0x05, }, 0x00,0x01 },
/*13*/{{ 'r', 'R', 0x12, 0x12, 'r', 'R', 0x12, 0x12, }, 0x00,0x01 },
/*14*/{{ 't', 'T', 0x14, 0x14, 't', 'T', 0x14, 0x14, }, 0x00,0x01 },
/*15*/{{ 'y', 'Y', 0x19, 0x19, 'y', 'Y', 0x19, 0x19, }, 0x00,0x01 },
/*16*/{{ 'u', 'U', 0x15, 0x15, 'u', 'U', 0x15, 0x15, }, 0x00,0x01 },
/*17*/{{ 'i', 'I', 0x09, 0x09, 'i', 'I', 0x09, 0x09, }, 0x00,0x01 },
/*18*/{{ 'o', 'O', 0x0F, 0x0F, 'o', 'O', 0x0F, 0x0F, }, 0x00,0x01 },
/*19*/{{ 'p', 'P', 0x10, 0x10, 'p', 'P', 0x10, 0x10, }, 0x00,0x01 },
/*1a*/{{ '[', '{', 0x1B, 0x1B, '[', '{', 0x1B, 0x1B, }, 0x00,0x00 },
/*1b*/{{ ']', '}', 0x1D, 0x1D, ']', '}', 0x1D, 0x1D, }, 0x00,0x00 },
/*1c*/{{ 0x0D, 0x0D, 0x0A, 0x0A, 0x0D, 0x0D, 0x0A, 0x0A, }, 0x00,0x00 },
/*1d*/{{ LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, }, 0xFF,0x00 },
/*1e*/{{ 'a', 'A', 0x01, 0x01, 'a', 'A', 0x01, 0x01, }, 0x00,0x01 },
/*1f*/{{ 's', 'S', 0x13, 0x13, 's', 'S', 0x13, 0x13, }, 0x00,0x01 },
/*20*/{{ 'd', 'D', 0x04, 0x04, 'd', 'D', 0x04, 0x04, }, 0x00,0x01 },
/*21*/{{ 'f', 'F', 0x06, 0x06, 'f', 'F', 0x06, 0x06, }, 0x00,0x01 },
/*22*/{{ 'g', 'G', 0x07, 0x07, 'g', 'G', 0x07, 0x07, }, 0x00,0x01 },
/*23*/{{ 'h', 'H', 0x08, 0x08, 'h', 'H', 0x08, 0x08, }, 0x00,0x01 },
/*24*/{{ 'j', 'J', 0x0A, 0x0A, 'j', 'J', 0x0A, 0x0A, }, 0x00,0x01 },
/*25*/{{ 'k', 'K', 0x0B, 0x0B, 'k', 'K', 0x0B, 0x0B, }, 0x00,0x01 },
/*26*/{{ 'l', 'L', 0x0C, 0x0C, 'l', 'L', 0x0C, 0x0C, }, 0x00,0x01 },
/*27*/{{ ';', ':', NOP, NOP, ';', ':', NOP, NOP, }, 0x33,0x00 },
/*28*/{{ '\'', '"', NOP, NOP, DACU, DUML, NOP, NOP, }, 0x3F,0x00 },
/*29*/{{ '`', '~', NOP, NOP, DGRA, DTIL, NOP, NOP, }, 0x3F,0x00 },
/*2a*/{{ LSH, LSH, LSH, LSH, LSH, LSH, LSH, LSH, }, 0xFF,0x00 },
/*2b*/{{ '\\', '|', 0x1C, 0x1C, '\\', '|', 0x1C, 0x1C, }, 0x00,0x00 },
/*2c*/{{ 'z', 'Z', 0x1A, 0x1A, 'z', 'Z', 0x1A, 0x1A, }, 0x00,0x01 },
/*2d*/{{ 'x', 'X', 0x18, 0x18, 'x', 'X', 0x18, 0x18, }, 0x00,0x01 },
/*2e*/{{ 'c', 'C', 0x03, 0x03, 'c', 'C', 0x03, 0x03, }, 0x00,0x01 },
/*2f*/{{ 'v', 'V', 0x16, 0x16, 'v', 'V', 0x16, 0x16, }, 0x00,0x01 },
/*30*/{{ 'b', 'B', 0x02, 0x02, 'b', 'B', 0x02, 0x02, }, 0x00,0x01 },
/*31*/{{ 'n', 'N', 0x0E, 0x0E, 'n', 'N', 0x0E, 0x0E, }, 0x00,0x01 },
/*32*/{{ 'm', 'M', 0x0D, 0x0D, 'm', 'M', 0x0D, 0x0D, }, 0x00,0x01 },
/*33*/{{ ',', '<', NOP, NOP, DCED, '<', NOP, NOP, }, 0x3B,0x00 },
/*34*/{{ '.', '>', NOP, NOP, '.', '>', NOP, NOP, }, 0x33,0x00 },
/*35*/{{ '/', '?', NOP, NOP, '/', '?', NOP, NOP, }, 0x33,0x00 },
/*36*/{{ RSH, RSH, RSH, RSH, RSH, RSH, RSH, RSH, }, 0xFF,0x00 },
/*37*/{{ '*', '*', '*', '*', '*', '*', '*', '*', }, 0x00,0x00 },
/*38*/{{ LALT, LALT, LALT, LALT, LALT, LALT, LALT, LALT, }, 0xFF,0x00 },
/*39*/{{ ' ', ' ', 0x00, 0x00, ' ', ' ', SUSP, SUSP, }, 0x03,0x00 },
/*3a*/{{ CLK, CLK, CLK, CLK, CLK, CLK, CLK, CLK, }, 0xFF,0x00 },
/*3b*/{{ F( 1), F(13), F(25), F(37), S( 1), S(11), S( 1), S(11),}, 0xFF,0x00 },
/*3c*/{{ F( 2), F(14), F(26), F(38), S( 2), S(12), S( 2), S(12),}, 0xFF,0x00 },
/*3d*/{{ F( 3), F(15), F(27), F(39), S( 3), S(13), S( 3), S(13),}, 0xFF,0x00 },
/*3e*/{{ F( 4), F(16), F(28), F(40), S( 4), S(14), S( 4), S(14),}, 0xFF,0x00 },
/*3f*/{{ F( 5), F(17), F(29), F(41), S( 5), S(15), S( 5), S(15),}, 0xFF,0x00 },
/*40*/{{ F( 6), F(18), F(30), F(42), S( 6), S(16), S( 6), S(16),}, 0xFF,0x00 },
/*41*/{{ F( 7), F(19), F(31), F(43), S( 7), S( 7), S( 7), S( 7),}, 0xFF,0x00 },
/*42*/{{ F( 8), F(20), F(32), F(44), S( 8), S( 8), S( 8), S( 8),}, 0xFF,0x00 },
/*43*/{{ F( 9), F(21), F(33), F(45), S( 9), S( 9), S( 9), S( 9),}, 0xFF,0x00 },
/*44*/{{ F(10), F(22), F(34), F(46), S(10), S(10), S(10), S(10),}, 0xFF,0x00 },
/*45*/{{ NLK, NLK, NLK, NLK, NLK, NLK, NLK, NLK, }, 0xFF,0x00 },
/*46*/{{ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, }, 0xFF,0x00 },
/*47*/{{ F(49), '7', '7', '7', '7', '7', '7', '7', }, 0x80,0x02 },
/*48*/{{ F(50), '8', '8', '8', '8', '8', '8', '8', }, 0x80,0x02 },
/*49*/{{ F(51), '9', '9', '9', '9', '9', '9', '9', }, 0x80,0x02 },
/*4a*/{{ F(52), '-', '-', '-', '-', '-', '-', '-', }, 0x80,0x02 },
/*4b*/{{ F(53), '4', '4', '4', '4', '4', '4', '4', }, 0x80,0x02 },
/*4c*/{{ F(54), '5', '5', '5', '5', '5', '5', '5', }, 0x80,0x02 },
/*4d*/{{ F(55), '6', '6', '6', '6', '6', '6', '6', }, 0x80,0x02 },
/*4e*/{{ F(56), '+', '+', '+', '+', '+', '+', '+', }, 0x80,0x02 },
/*4f*/{{ F(57), '1', '1', '1', '1', '1', '1', '1', }, 0x80,0x02 },
/*50*/{{ F(58), '2', '2', '2', '2', '2', '2', '2', }, 0x80,0x02 },
/*51*/{{ F(59), '3', '3', '3', '3', '3', '3', '3', }, 0x80,0x02 },
/*52*/{{ F(60), '0', '0', '0', '0', '0', '0', '0', }, 0x80,0x02 },
/*53*/{{ 0x7F, '.', '.', '.', '.', '.', RBT, RBT, }, 0x03,0x02 },
/*54*/{{ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, }, 0xFF,0x00 },
/*55*/{{ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, }, 0xFF,0x00 },
/*56*/{{ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, }, 0xFF,0x00 },
/*57*/{{ F(11), F(23), F(35), F(47), S(11), S(11), S(11), S(11),}, 0xFF,0x00 },
/*58*/{{ F(12), F(24), F(36), F(48), S(12), S(12), S(12), S(12),}, 0xFF,0x00 },
/*59*/{{ 0x0D, 0x0D, 0x0A, 0x0A, 0x0D, 0x0D, 0x0A, 0x0A, }, 0x00,0x00 },
/*5a*/{{ RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, }, 0xFF,0x00 },
/*5b*/{{ '/', '/', '/', '/', '/', '/', '/', '/', }, 0x00,0x02 },
/*5c*/{{ NEXT, NEXT, NOP, NOP, DBG, DBG, DBG, DBG, }, 0xFF,0x00 },
/*5d*/{{ RALT, RALT, RALT, RALT, RALT, RALT, RALT, RALT, }, 0xFF,0x00 },
/*5e*/{{ F(49), F(49), F(49), F(49), F(49), F(49), F(49), F(49),}, 0xFF,0x00 },
/*5f*/{{ F(50), F(50), F(50), F(50), F(50), F(50), F(50), F(50),}, 0xFF,0x00 },
/*60*/{{ F(51), F(51), F(51), F(51), F(51), F(51), F(51), F(51),}, 0xFF,0x00 },
/*61*/{{ F(53), F(53), F(53), F(53), F(53), F(53), F(53), F(53),}, 0xFF,0x00 },
/*62*/{{ F(55), F(55), F(55), F(55), F(55), F(55), F(55), F(55),}, 0xFF,0x00 },
/*63*/{{ F(57), F(57), F(57), F(57), F(57), F(57), F(57), F(57),}, 0xFF,0x00 },
/*64*/{{ F(58), F(58), F(58), F(58), F(58), F(58), F(58), F(58),}, 0xFF,0x00 },
/*65*/{{ F(59), F(59), F(59), F(59), F(59), F(59), F(59), F(59),}, 0xFF,0x00 },
/*66*/{{ F(60), F(60), F(60), F(60), F(60), F(60), F(60), F(60),}, 0xFF,0x00 },
/*67*/{{ F(61), F(61), F(61), F(61), F(61), F(61), RBT, F(61),}, 0xFF,0x00 },
/*68*/{{ SPSC, SPSC, SUSP, SUSP, NOP, NOP, SUSP, SUSP, }, 0xFF,0x00 },
/*69*/{{ F(62), F(62), F(62), F(62), F(62), F(62), F(62), F(62),}, 0xFF,0x00 },
/*6a*/{{ F(63), F(63), F(63), F(63), F(63), F(63), F(63), F(63),}, 0xFF,0x00 },
/*6b*/{{ F(64), F(64), F(64), F(64), F(64), F(64), F(64), F(64),}, 0xFF,0x00 },
/*6c*/{{ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, }, 0xFF,0x00 },
} };
static accentmap_t accentmap_default_us_acc = { 11, {
// dgra=0
{ '`', { { 'a',0xe0 }, { 'A',0xc0 }, { 'e',0xe8 }, { 'E',0xc8 },
{ 'i',0xec }, { 'I',0xcc }, { 'o',0xf2 }, { 'O',0xd2 },
{ 'u',0xf9 }, { 'U',0xd9 }, }, },
// dacu=1
{ 0xb4, { { 'a',0xe1 }, { 'A',0xc1 }, { 'e',0xe9 }, { 'E',0xc9 },
{ 'i',0xed }, { 'I',0xcd }, { 'o',0xf3 }, { 'O',0xd3 },
{ 'u',0xfa }, { 'U',0xda }, { 'y',0xfd }, { 'Y',0xdd }, }, },
// dcir=2
{ '^', { { 'a',0xe2 }, { 'A',0xc2 }, { 'e',0xea }, { 'E',0xca },
{ 'i',0xee }, { 'I',0xce }, { 'o',0xf4 }, { 'O',0xd4 },
{ 'u',0xfb }, { 'U',0xdb }, }, },
// dtil=3
{ '~', { { 'a',0xe3 }, { 'A',0xc3 }, { 'n',0xf1 }, { 'N',0xd1 },
{ 'o',0xf5 }, { 'O',0xd5 }, }, },
// dmac=4
{ 0x00 },
// dbre=5
{ 0x00 },
// ddot=6
{ 0x00 },
// duml=7
{ 0xa8, { { 'a',0xe4 }, { 'A',0xc4 }, { 'e',0xeb }, { 'E',0xcb },
{ 'i',0xef }, { 'I',0xcf }, { 'o',0xf6 }, { 'O',0xd6 },
{ 'u',0xfc }, { 'U',0xdc }, { 'y',0xff }, }, },
// dsla=8
{ 0x00 },
// drin=9
{ 0xb0, { { 'a',0xe5 }, { 'A',0xc5 }, }, },
// dced=10
{ 0xb8, { { 'c',0xe7 }, { 'C',0xc7 }, }, },
// dapo=11
{ 0x00 },
// ddac=12
{ 0x00 },
// dogo=13
{ 0x00 },
// dcar=14
{ 0x00 },
} };
/* *INDENT-ON* */ // clang-format on

View file

@ -0,0 +1,613 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "../linux/SDL_evdev_kbd.h"
#ifdef SDL_INPUT_FBSDKBIO
// This logic is adapted from drivers/tty/vt/keyboard.c in the Linux kernel source, slightly modified to work with FreeBSD
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/kbio.h>
#include <sys/consio.h>
#include <signal.h>
#include "../../events/SDL_events_c.h"
#include "SDL_evdev_kbd_default_keyaccmap.h"
typedef void(fn_handler_fn)(SDL_EVDEV_keyboard_state *kbd);
/*
* Keyboard State
*/
struct SDL_EVDEV_keyboard_state
{
int console_fd;
int keyboard_fd;
unsigned long old_kbd_mode;
unsigned short **key_maps;
keymap_t *key_map;
keyboard_info_t *kbInfo;
unsigned char shift_down[4]; // shift state counters..
bool dead_key_next;
int npadch; // -1 or number assembled on pad
accentmap_t *accents;
unsigned int diacr;
bool rep; // flag telling character repeat
unsigned char lockstate;
unsigned char ledflagstate;
char shift_state;
char text[128];
unsigned int text_len;
};
static bool SDL_EVDEV_kbd_load_keymaps(SDL_EVDEV_keyboard_state *kbd)
{
return ioctl(kbd->keyboard_fd, GIO_KEYMAP, kbd->key_map) >= 0;
}
static SDL_EVDEV_keyboard_state *kbd_cleanup_state = NULL;
static int kbd_cleanup_sigactions_installed = 0;
static int kbd_cleanup_atexit_installed = 0;
static struct sigaction old_sigaction[NSIG];
static int fatal_signals[] = {
// Handlers for SIGTERM and SIGINT are installed in SDL_InitQuit.
SIGHUP, SIGQUIT, SIGILL, SIGABRT,
SIGFPE, SIGSEGV, SIGPIPE, SIGBUS,
SIGSYS
};
static void kbd_cleanup(void)
{
struct mouse_info mData;
SDL_EVDEV_keyboard_state *kbd = kbd_cleanup_state;
if (!kbd) {
return;
}
kbd_cleanup_state = NULL;
SDL_zero(mData);
mData.operation = MOUSE_SHOW;
ioctl(kbd->keyboard_fd, KDSKBMODE, kbd->old_kbd_mode);
if (kbd->keyboard_fd != kbd->console_fd) {
close(kbd->keyboard_fd);
}
ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index));
ioctl(kbd->console_fd, CONS_MOUSECTL, &mData);
}
void SDL_EVDEV_kbd_reraise_signal(int sig)
{
raise(sig);
}
siginfo_t *SDL_EVDEV_kdb_cleanup_siginfo = NULL;
void *SDL_EVDEV_kdb_cleanup_ucontext = NULL;
static void kbd_cleanup_signal_action(int signum, siginfo_t *info, void *ucontext)
{
struct sigaction *old_action_p = &(old_sigaction[signum]);
sigset_t sigset;
// Restore original signal handler before going any further.
sigaction(signum, old_action_p, NULL);
// Unmask current signal.
sigemptyset(&sigset);
sigaddset(&sigset, signum);
sigprocmask(SIG_UNBLOCK, &sigset, NULL);
// Save original signal info and context for archeologists.
SDL_EVDEV_kdb_cleanup_siginfo = info;
SDL_EVDEV_kdb_cleanup_ucontext = ucontext;
// Restore keyboard.
kbd_cleanup();
// Reraise signal.
SDL_EVDEV_kbd_reraise_signal(signum);
}
static void kbd_unregister_emerg_cleanup(void)
{
int tabidx;
kbd_cleanup_state = NULL;
if (!kbd_cleanup_sigactions_installed) {
return;
}
kbd_cleanup_sigactions_installed = 0;
for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) {
struct sigaction *old_action_p;
struct sigaction cur_action;
int signum = fatal_signals[tabidx];
old_action_p = &(old_sigaction[signum]);
// Examine current signal action
if (sigaction(signum, NULL, &cur_action)) {
continue;
}
// Check if action installed and not modified
if (!(cur_action.sa_flags & SA_SIGINFO) || cur_action.sa_sigaction != &kbd_cleanup_signal_action) {
continue;
}
// Restore original action
sigaction(signum, old_action_p, NULL);
}
}
static void kbd_cleanup_atexit(void)
{
// Restore keyboard.
kbd_cleanup();
// Try to restore signal handlers in case shared library is being unloaded
kbd_unregister_emerg_cleanup();
}
static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd)
{
int tabidx;
if (kbd_cleanup_state) {
return;
}
kbd_cleanup_state = kbd;
if (!kbd_cleanup_atexit_installed) {
/* Since glibc 2.2.3, atexit() (and on_exit(3)) can be used within a shared library to establish
* functions that are called when the shared library is unloaded.
* -- man atexit(3)
*/
atexit(kbd_cleanup_atexit);
kbd_cleanup_atexit_installed = 1;
}
if (kbd_cleanup_sigactions_installed) {
return;
}
kbd_cleanup_sigactions_installed = 1;
for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) {
struct sigaction *old_action_p;
struct sigaction new_action;
int signum = fatal_signals[tabidx];
old_action_p = &(old_sigaction[signum]);
if (sigaction(signum, NULL, old_action_p)) {
continue;
}
/* Skip SIGHUP and SIGPIPE if handler is already installed
* - assume the handler will do the cleanup
*/
if ((signum == SIGHUP || signum == SIGPIPE) && (old_action_p->sa_handler != SIG_DFL || (void (*)(int))old_action_p->sa_sigaction != SIG_DFL)) {
continue;
}
new_action = *old_action_p;
new_action.sa_flags |= SA_SIGINFO;
new_action.sa_sigaction = &kbd_cleanup_signal_action;
sigaction(signum, &new_action, NULL);
}
}
SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void)
{
SDL_EVDEV_keyboard_state *kbd;
struct mouse_info mData;
char flag_state;
char *devicePath;
SDL_zero(mData);
mData.operation = MOUSE_HIDE;
kbd = (SDL_EVDEV_keyboard_state *)SDL_calloc(1, sizeof(SDL_EVDEV_keyboard_state));
if (!kbd) {
return NULL;
}
kbd->npadch = -1;
// This might fail if we're not connected to a tty (e.g. on the Steam Link)
kbd->keyboard_fd = kbd->console_fd = open("/dev/tty", O_RDONLY | O_CLOEXEC);
kbd->shift_state = 0;
kbd->accents = SDL_calloc(1, sizeof(accentmap_t));
kbd->key_map = SDL_calloc(1, sizeof(keymap_t));
kbd->kbInfo = SDL_calloc(1, sizeof(keyboard_info_t));
ioctl(kbd->console_fd, KDGKBINFO, kbd->kbInfo);
ioctl(kbd->console_fd, CONS_MOUSECTL, &mData);
if (ioctl(kbd->console_fd, KDGKBSTATE, &flag_state) == 0) {
kbd->ledflagstate = flag_state;
}
if (ioctl(kbd->console_fd, GIO_DEADKEYMAP, kbd->accents) < 0) {
SDL_free(kbd->accents);
kbd->accents = &accentmap_default_us_acc;
}
if (ioctl(kbd->console_fd, KDGKBMODE, &kbd->old_kbd_mode) == 0) {
// Set the keyboard in XLATE mode and load the keymaps
ioctl(kbd->console_fd, KDSKBMODE, (unsigned long)(K_XLATE));
if (!SDL_EVDEV_kbd_load_keymaps(kbd)) {
SDL_free(kbd->key_map);
kbd->key_map = &keymap_default_us_acc;
}
if (SDL_GetHintBoolean(SDL_HINT_MUTE_CONSOLE_KEYBOARD, true)) {
/* Take keyboard from console and open the actual keyboard device.
* Ensures that the keystrokes do not leak through to the console.
*/
ioctl(kbd->console_fd, CONS_RELKBD, 1ul);
SDL_asprintf(&devicePath, "/dev/kbd%d", kbd->kbInfo->kb_index);
kbd->keyboard_fd = open(devicePath, O_WRONLY | O_CLOEXEC);
if (kbd->keyboard_fd == -1) {
// Give keyboard back.
ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index));
kbd->keyboard_fd = kbd->console_fd;
}
/* Make sure to restore keyboard if application fails to call
* SDL_Quit before exit or fatal signal is raised.
*/
if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, false)) {
kbd_register_emerg_cleanup(kbd);
}
SDL_free(devicePath);
} else
kbd->keyboard_fd = kbd->console_fd;
}
return kbd;
}
void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *kbd)
{
struct mouse_info mData;
if (!kbd) {
return;
}
SDL_zero(mData);
mData.operation = MOUSE_SHOW;
ioctl(kbd->console_fd, CONS_MOUSECTL, &mData);
kbd_unregister_emerg_cleanup();
if (kbd->keyboard_fd >= 0) {
// Restore the original keyboard mode
ioctl(kbd->keyboard_fd, KDSKBMODE, kbd->old_kbd_mode);
close(kbd->keyboard_fd);
if (kbd->console_fd != kbd->keyboard_fd && kbd->console_fd >= 0) {
// Give back keyboard.
ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index));
}
kbd->console_fd = kbd->keyboard_fd = -1;
}
SDL_free(kbd);
}
void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted)
{
}
void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data)
{
}
void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state)
{
}
/*
* Helper Functions.
*/
static void put_queue(SDL_EVDEV_keyboard_state *kbd, uint c)
{
// c is already part of a UTF-8 sequence and safe to add as a character
if (kbd->text_len < (sizeof(kbd->text) - 1)) {
kbd->text[kbd->text_len++] = (char)c;
}
}
static void put_utf8(SDL_EVDEV_keyboard_state *kbd, uint c)
{
if (c < 0x80)
/* 0******* */
put_queue(kbd, c);
else if (c < 0x800) {
/* 110***** 10****** */
put_queue(kbd, 0xc0 | (c >> 6));
put_queue(kbd, 0x80 | (c & 0x3f));
} else if (c < 0x10000) {
if (c >= 0xD800 && c < 0xE000) {
return;
}
if (c == 0xFFFF) {
return;
}
/* 1110**** 10****** 10****** */
put_queue(kbd, 0xe0 | (c >> 12));
put_queue(kbd, 0x80 | ((c >> 6) & 0x3f));
put_queue(kbd, 0x80 | (c & 0x3f));
} else if (c < 0x110000) {
/* 11110*** 10****** 10****** 10****** */
put_queue(kbd, 0xf0 | (c >> 18));
put_queue(kbd, 0x80 | ((c >> 12) & 0x3f));
put_queue(kbd, 0x80 | ((c >> 6) & 0x3f));
put_queue(kbd, 0x80 | (c & 0x3f));
}
}
/*
* We have a combining character DIACR here, followed by the character CH.
* If the combination occurs in the table, return the corresponding value.
* Otherwise, if CH is a space or equals DIACR, return DIACR.
* Otherwise, conclude that DIACR was not combining after all,
* queue it and return CH.
*/
static unsigned int handle_diacr(SDL_EVDEV_keyboard_state *kbd, unsigned int ch)
{
unsigned int d = kbd->diacr;
unsigned int i, j;
kbd->diacr = 0;
for (i = 0; i < kbd->accents->n_accs; i++) {
if (kbd->accents->acc[i].accchar == d) {
for (j = 0; j < NUM_ACCENTCHARS; ++j) {
if (kbd->accents->acc[i].map[j][0] == 0) { // end of table
break;
}
if (kbd->accents->acc[i].map[j][0] == ch) {
return kbd->accents->acc[i].map[j][1];
}
}
}
}
if (ch == ' ' || ch == d) {
put_utf8(kbd, d);
return 0;
}
put_utf8(kbd, d);
return ch;
}
static bool vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
return (kbd->ledflagstate & flag) != 0;
}
static void chg_vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->ledflagstate ^= flag;
ioctl(kbd->keyboard_fd, KDSKBSTATE, (unsigned long)(kbd->ledflagstate));
}
/*
* Special function handlers
*/
static void k_self(SDL_EVDEV_keyboard_state *kbd, unsigned int value, char up_flag)
{
if (up_flag) {
return; // no action, if this is a key release
}
if (kbd->diacr) {
value = handle_diacr(kbd, value);
}
if (kbd->dead_key_next) {
kbd->dead_key_next = false;
kbd->diacr = value;
return;
}
put_utf8(kbd, value);
}
static void k_deadunicode(SDL_EVDEV_keyboard_state *kbd, unsigned int value, char up_flag)
{
if (up_flag)
return;
kbd->diacr = (kbd->diacr ? handle_diacr(kbd, value) : value);
}
static void k_shift(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
int old_state = kbd->shift_state;
if (kbd->rep)
return;
if (up_flag) {
/*
* handle the case that two shift or control
* keys are depressed simultaneously
*/
if (kbd->shift_down[value]) {
kbd->shift_down[value]--;
}
} else
kbd->shift_down[value]++;
if (kbd->shift_down[value])
kbd->shift_state |= (1 << value);
else
kbd->shift_state &= ~(1 << value);
// kludge
if (up_flag && kbd->shift_state != old_state && kbd->npadch != -1) {
put_utf8(kbd, kbd->npadch);
kbd->npadch = -1;
}
}
void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *kbd, unsigned int keycode, int down)
{
keymap_t key_map;
struct keyent_t keysym;
unsigned int final_key_state;
unsigned int map_from_key_sym;
if (!kbd) {
return;
}
key_map = *kbd->key_map;
kbd->rep = (down == 2);
if (keycode < NUM_KEYS) {
if (keycode >= 89 && keycode <= 95) {
// These constitute unprintable language-related keys, so ignore them.
return;
}
if (keycode > 95) {
keycode -= 7;
}
if (vc_kbd_led(kbd, ALKED) || (kbd->shift_state & 0x8)) {
keycode += ALTGR_OFFSET;
}
keysym = key_map.key[keycode];
} else {
return;
}
final_key_state = kbd->shift_state & 0x7;
if ((keysym.flgs & FLAG_LOCK_C) && vc_kbd_led(kbd, LED_CAP)) {
final_key_state ^= 0x1;
}
if ((keysym.flgs & FLAG_LOCK_N) && vc_kbd_led(kbd, LED_NUM)) {
final_key_state ^= 0x1;
}
map_from_key_sym = keysym.map[final_key_state];
if ((keysym.spcl & (0x80 >> final_key_state)) || (map_from_key_sym & SPCLKEY)) {
// Special function.
if (map_from_key_sym == 0)
return; // Nothing to do.
if (map_from_key_sym & SPCLKEY) {
map_from_key_sym &= ~SPCLKEY;
}
if (map_from_key_sym >= F_ACC && map_from_key_sym <= L_ACC) {
// Accent function.
unsigned int accent_index = map_from_key_sym - F_ACC;
if (kbd->accents->acc[accent_index].accchar != 0) {
k_deadunicode(kbd, kbd->accents->acc[accent_index].accchar, !down);
}
} else {
switch (map_from_key_sym) {
case ASH: // alt/meta shift
k_shift(kbd, 3, down == 0);
break;
case LSHA: // left shift + alt lock
case RSHA: // right shift + alt lock
if (down == 0) {
chg_vc_kbd_led(kbd, ALKED);
}
SDL_FALLTHROUGH;
case LSH: // left shift
case RSH: // right shift
k_shift(kbd, 0, down == 0);
break;
case LCTRA: // left ctrl + alt lock
case RCTRA: // right ctrl + alt lock
if (down == 0) {
chg_vc_kbd_led(kbd, ALKED);
}
SDL_FALLTHROUGH;
case LCTR: // left ctrl
case RCTR: // right ctrl
k_shift(kbd, 1, down == 0);
break;
case LALTA: // left alt + alt lock
case RALTA: // right alt + alt lock
if (down == 0) {
chg_vc_kbd_led(kbd, ALKED);
}
SDL_FALLTHROUGH;
case LALT: // left alt
case RALT: // right alt
k_shift(kbd, 2, down == 0);
break;
case ALK: // alt lock
if (down == 1) {
chg_vc_kbd_led(kbd, ALKED);
}
break;
case CLK: // caps lock
if (down == 1) {
chg_vc_kbd_led(kbd, CLKED);
}
break;
case NLK: // num lock
if (down == 1) {
chg_vc_kbd_led(kbd, NLKED);
}
break;
case SLK: // scroll lock
if (down == 1) {
chg_vc_kbd_led(kbd, SLKED);
}
break;
default:
return;
}
}
} else {
if (map_from_key_sym == '\n' || map_from_key_sym == '\r') {
if (kbd->diacr) {
kbd->diacr = 0;
return;
}
}
if (map_from_key_sym >= ' ' && map_from_key_sym != 127) {
k_self(kbd, map_from_key_sym, !down);
}
}
if (kbd->text_len > 0) {
kbd->text[kbd->text_len] = '\0';
SDL_SendKeyboardText(kbd->text);
kbd->text_len = 0;
}
}
#endif // SDL_INPUT_FBSDKBIO

View file

@ -0,0 +1,159 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
extern "C" {
#include "../windows/SDL_windows.h"
#include "../../events/SDL_events_c.h"
}
#include <XGameRuntime.h>
#include <xsapi-c/services_c.h>
#include <appnotify.h>
static XTaskQueueHandle GDK_GlobalTaskQueue;
PAPPSTATE_REGISTRATION hPLM = {};
PAPPCONSTRAIN_REGISTRATION hCPLM = {};
HANDLE plmSuspendComplete = nullptr;
extern "C"
bool SDL_GetGDKTaskQueue(XTaskQueueHandle *outTaskQueue)
{
// If this is the first call, first create the global task queue.
if (!GDK_GlobalTaskQueue) {
HRESULT hr;
hr = XTaskQueueCreate(XTaskQueueDispatchMode::ThreadPool,
XTaskQueueDispatchMode::Manual,
&GDK_GlobalTaskQueue);
if (FAILED(hr)) {
return SDL_SetError("[GDK] Could not create global task queue");
}
// The initial call gets the non-duplicated handle so they can clean it up
*outTaskQueue = GDK_GlobalTaskQueue;
} else {
// Duplicate the global task queue handle into outTaskQueue
if (FAILED(XTaskQueueDuplicateHandle(GDK_GlobalTaskQueue, outTaskQueue))) {
return SDL_SetError("[GDK] Unable to acquire global task queue");
}
}
return true;
}
extern "C"
void GDK_DispatchTaskQueue(void)
{
/* If there is no global task queue, don't do anything.
* This gives the option to opt-out for those who want to handle everything themselves.
*/
if (GDK_GlobalTaskQueue) {
// Dispatch any callbacks which are ready.
while (XTaskQueueDispatch(GDK_GlobalTaskQueue, XTaskQueuePort::Completion, 0))
;
}
}
extern "C"
bool GDK_RegisterChangeNotifications(void)
{
// Register suspend/resume handling
plmSuspendComplete = CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
if (!plmSuspendComplete) {
return SDL_SetError("[GDK] Unable to create plmSuspendComplete event");
}
auto rascn = [](BOOLEAN quiesced, PVOID context) {
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "[GDK] in RegisterAppStateChangeNotification handler");
if (quiesced) {
ResetEvent(plmSuspendComplete);
SDL_SendAppEvent(SDL_EVENT_DID_ENTER_BACKGROUND);
// To defer suspension, we must wait to exit this callback.
// IMPORTANT: The app must call SDL_GDKSuspendComplete() to release this lock.
(void)WaitForSingleObject(plmSuspendComplete, INFINITE);
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "[GDK] in RegisterAppStateChangeNotification handler: plmSuspendComplete event signaled.");
} else {
SDL_SendAppEvent(SDL_EVENT_WILL_ENTER_FOREGROUND);
}
};
if (RegisterAppStateChangeNotification(rascn, NULL, &hPLM)) {
return SDL_SetError("[GDK] Unable to call RegisterAppStateChangeNotification");
}
// Register constrain/unconstrain handling
auto raccn = [](BOOLEAN constrained, PVOID context) {
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "[GDK] in RegisterAppConstrainedChangeNotification handler");
SDL_VideoDevice *_this = SDL_GetVideoDevice();
if (_this) {
if (constrained) {
SDL_SetKeyboardFocus(NULL);
} else {
SDL_SetKeyboardFocus(_this->windows);
}
}
};
if (RegisterAppConstrainedChangeNotification(raccn, NULL, &hCPLM)) {
return SDL_SetError("[GDK] Unable to call RegisterAppConstrainedChangeNotification");
}
return true;
}
extern "C"
void GDK_UnregisterChangeNotifications(void)
{
// Unregister suspend/resume handling
UnregisterAppStateChangeNotification(hPLM);
CloseHandle(plmSuspendComplete);
// Unregister constrain/unconstrain handling
UnregisterAppConstrainedChangeNotification(hCPLM);
}
extern "C"
void SDL_GDKSuspendComplete()
{
if (plmSuspendComplete) {
SetEvent(plmSuspendComplete);
}
}
extern "C"
bool SDL_GetGDKDefaultUser(XUserHandle *outUserHandle)
{
XAsyncBlock block = { 0 };
HRESULT result;
if (FAILED(result = XUserAddAsync(XUserAddOptions::AddDefaultUserAllowingUI, &block))) {
return WIN_SetErrorFromHRESULT("XUserAddAsync", result);
}
do {
result = XUserAddResult(&block, outUserHandle);
} while (result == E_PENDING);
if (FAILED(result)) {
return WIN_SetErrorFromHRESULT("XUserAddResult", result);
}
return true;
}

View file

@ -0,0 +1,27 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// This is called from WIN_PumpEvents on GDK
extern void GDK_DispatchTaskQueue(void);
extern bool GDK_RegisterChangeNotifications(void);
extern void GDK_UnregisterChangeNotifications(void);

View file

@ -0,0 +1,426 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_BAPP_H
#define SDL_BAPP_H
#include <Path.h>
#include <InterfaceKit.h>
#include <LocaleRoster.h>
#ifdef SDL_VIDEO_OPENGL
#include <OpenGLKit.h>
#endif
#include "../../video/haiku/SDL_bkeyboard.h"
#ifdef __cplusplus
extern "C" {
#endif
#include "SDL_internal.h"
// Local includes
#include "../../events/SDL_events_c.h"
#include "../../video/haiku/SDL_bframebuffer.h"
#ifdef __cplusplus
}
#endif
#include <vector>
// Forward declarations
class SDL_BLooper;
class SDL_BWin;
// Message constants
enum ToSDL
{
// Intercepted by BWindow on its way to BView
BAPP_MOUSE_MOVED,
BAPP_MOUSE_BUTTON,
BAPP_MOUSE_WHEEL,
BAPP_KEY,
BAPP_REPAINT, // from _UPDATE_
// From BWindow
BAPP_MAXIMIZE, // from B_ZOOM
BAPP_MINIMIZE,
BAPP_RESTORE, // TODO: IMPLEMENT!
BAPP_SHOW,
BAPP_HIDE,
BAPP_MOUSE_FOCUS, // caused by MOUSE_MOVE
BAPP_KEYBOARD_FOCUS, // from WINDOW_ACTIVATED
BAPP_WINDOW_CLOSE_REQUESTED,
BAPP_WINDOW_MOVED,
BAPP_WINDOW_RESIZED,
BAPP_SCREEN_CHANGED
};
extern "C" SDL_BLooper *SDL_Looper;
// Create a descendant of BLooper
class SDL_BLooper : public BLooper
{
public:
SDL_BLooper(const char* name) : BLooper(name)
{
#ifdef SDL_VIDEO_OPENGL
_current_context = NULL;
#endif
}
virtual ~SDL_BLooper()
{
}
// Event-handling functions
virtual void MessageReceived(BMessage *message)
{
// Sort out SDL-related messages
switch (message->what) {
case BAPP_MOUSE_MOVED:
_HandleMouseMove(message);
break;
case BAPP_MOUSE_BUTTON:
_HandleMouseButton(message);
break;
case BAPP_MOUSE_WHEEL:
_HandleMouseWheel(message);
break;
case BAPP_KEY:
_HandleKey(message);
break;
case BAPP_REPAINT:
_HandleBasicWindowEvent(message, SDL_EVENT_WINDOW_EXPOSED);
break;
case BAPP_MAXIMIZE:
_HandleBasicWindowEvent(message, SDL_EVENT_WINDOW_MAXIMIZED);
break;
case BAPP_MINIMIZE:
_HandleBasicWindowEvent(message, SDL_EVENT_WINDOW_MINIMIZED);
break;
case BAPP_SHOW:
_HandleBasicWindowEvent(message, SDL_EVENT_WINDOW_SHOWN);
break;
case BAPP_HIDE:
_HandleBasicWindowEvent(message, SDL_EVENT_WINDOW_HIDDEN);
break;
case BAPP_MOUSE_FOCUS:
_HandleMouseFocus(message);
break;
case BAPP_KEYBOARD_FOCUS:
_HandleKeyboardFocus(message);
break;
case BAPP_WINDOW_CLOSE_REQUESTED:
_HandleBasicWindowEvent(message, SDL_EVENT_WINDOW_CLOSE_REQUESTED);
break;
case BAPP_WINDOW_MOVED:
_HandleWindowMoved(message);
break;
case BAPP_WINDOW_RESIZED:
_HandleWindowResized(message);
break;
case B_LOCALE_CHANGED:
SDL_SendLocaleChangedEvent();
break;
case BAPP_SCREEN_CHANGED:
// TODO: Handle screen resize or workspace change
break;
default:
BLooper::MessageReceived(message);
break;
}
}
// Window creation/destruction methods
int32 GetID(SDL_Window *win)
{
int32 i;
for (i = 0; i < _GetNumWindowSlots(); ++i) {
if (GetSDLWindow(i) == NULL) {
_SetSDLWindow(win, i);
return i;
}
}
// Expand the vector if all slots are full
if (i == _GetNumWindowSlots()) {
_PushBackWindow(win);
return i;
}
// TODO: error handling
return 0;
}
/* FIXME: Bad coding practice, but I can't include SDL_BWin.h here. Is
there another way to do this? */
void ClearID(SDL_BWin *bwin); // Defined in SDL_BeApp.cc
SDL_Window *GetSDLWindow(int32 winID)
{
return _window_map[winID];
}
#ifdef SDL_VIDEO_OPENGL
BGLView *GetCurrentContext()
{
return _current_context;
}
void SetCurrentContext(BGLView *newContext)
{
if (_current_context)
_current_context->UnlockGL();
_current_context = newContext;
if (_current_context)
_current_context->LockGL();
}
#endif
private:
// Event management
void _HandleBasicWindowEvent(BMessage *msg, SDL_EventType sdlEventType)
{
SDL_Window *win;
int32 winID;
if (
!_GetWinID(msg, &winID)) {
return;
}
win = GetSDLWindow(winID);
SDL_SendWindowEvent(win, sdlEventType, 0, 0);
}
void _HandleMouseMove(BMessage *msg)
{
SDL_Window *win;
int32 winID;
int32 x = 0, y = 0;
if (
!_GetWinID(msg, &winID) ||
msg->FindInt32("x", &x) != B_OK || // x movement
msg->FindInt32("y", &y) != B_OK // y movement
) {
return;
}
win = GetSDLWindow(winID);
// Simple relative mode support for mouse.
if (SDL_GetMouse()->relative_mode) {
int winWidth, winHeight, winPosX, winPosY;
SDL_GetWindowSize(win, &winWidth, &winHeight);
SDL_GetWindowPosition(win, &winPosX, &winPosY);
int dx = x - (winWidth / 2);
int dy = y - (winHeight / 2);
SDL_SendMouseMotion(0, win, SDL_DEFAULT_MOUSE_ID, SDL_GetMouse()->relative_mode, (float)dx, (float)dy);
set_mouse_position((winPosX + winWidth / 2), (winPosY + winHeight / 2));
if (!be_app->IsCursorHidden())
be_app->HideCursor();
} else {
SDL_SendMouseMotion(0, win, SDL_DEFAULT_MOUSE_ID, false, (float)x, (float)y);
if (SDL_CursorVisible() && be_app->IsCursorHidden())
be_app->ShowCursor();
}
}
void _HandleMouseButton(BMessage *msg)
{
SDL_Window *win;
int32 winID;
int32 button;
bool down;
if (
!_GetWinID(msg, &winID) ||
msg->FindInt32("button-id", &button) != B_OK ||
msg->FindBool("button-down", &down) != B_OK) {
return;
}
win = GetSDLWindow(winID);
SDL_SendMouseButton(0, win, SDL_DEFAULT_MOUSE_ID, button, down);
}
void _HandleMouseWheel(BMessage *msg)
{
SDL_Window *win;
int32 winID;
int32 xTicks, yTicks;
if (
!_GetWinID(msg, &winID) ||
msg->FindInt32("xticks", &xTicks) != B_OK ||
msg->FindInt32("yticks", &yTicks) != B_OK) {
return;
}
win = GetSDLWindow(winID);
SDL_SendMouseWheel(0, win, SDL_DEFAULT_MOUSE_ID, xTicks, -yTicks, SDL_MOUSEWHEEL_NORMAL);
}
void _HandleKey(BMessage *msg)
{
int32 scancode;
bool down;
if (
msg->FindInt32("key-scancode", &scancode) != B_OK ||
msg->FindBool("key-down", &down) != B_OK) {
return;
}
SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, scancode, HAIKU_GetScancodeFromBeKey(scancode), down);
if (down) {
SDL_Window *win = SDL_GetKeyboardFocus();
if (win && SDL_TextInputActive(win)) {
const int8 *keyUtf8;
ssize_t count;
if (msg->FindData("key-utf8", B_INT8_TYPE, (const void **)&keyUtf8, &count) == B_OK) {
char text[64];
SDL_zeroa(text);
SDL_memcpy(text, keyUtf8, count);
SDL_SendKeyboardText(text);
}
}
}
}
void _HandleMouseFocus(BMessage *msg)
{
SDL_Window *win;
int32 winID;
bool bSetFocus; // If false, lose focus
if (
!_GetWinID(msg, &winID) ||
msg->FindBool("focusGained", &bSetFocus) != B_OK) {
return;
}
win = GetSDLWindow(winID);
if (bSetFocus) {
SDL_SetMouseFocus(win);
} else if (SDL_GetMouseFocus() == win) {
// Only lose all focus if this window was the current focus
SDL_SetMouseFocus(NULL);
}
}
void _HandleKeyboardFocus(BMessage *msg)
{
SDL_Window *win;
int32 winID;
bool bSetFocus; // If false, lose focus
if (
!_GetWinID(msg, &winID) ||
msg->FindBool("focusGained", &bSetFocus) != B_OK) {
return;
}
win = GetSDLWindow(winID);
if (bSetFocus) {
SDL_SetKeyboardFocus(win);
} else if (SDL_GetKeyboardFocus() == win) {
// Only lose all focus if this window was the current focus
SDL_SetKeyboardFocus(NULL);
}
}
void _HandleWindowMoved(BMessage *msg)
{
SDL_Window *win;
int32 winID;
int32 xPos, yPos;
// Get the window id and new x/y position of the window
if (
!_GetWinID(msg, &winID) ||
msg->FindInt32("window-x", &xPos) != B_OK ||
msg->FindInt32("window-y", &yPos) != B_OK) {
return;
}
win = GetSDLWindow(winID);
SDL_SendWindowEvent(win, SDL_EVENT_WINDOW_MOVED, xPos, yPos);
}
void _HandleWindowResized(BMessage *msg)
{
SDL_Window *win;
int32 winID;
int32 w, h;
// Get the window id ]and new x/y position of the window
if (
!_GetWinID(msg, &winID) ||
msg->FindInt32("window-w", &w) != B_OK ||
msg->FindInt32("window-h", &h) != B_OK) {
return;
}
win = GetSDLWindow(winID);
SDL_SendWindowEvent(win, SDL_EVENT_WINDOW_RESIZED, w, h);
}
bool _GetWinID(BMessage *msg, int32 *winID)
{
return msg->FindInt32("window-id", winID) == B_OK;
}
/* Vector functions: Wraps vector stuff in case we need to change
implementation */
void _SetSDLWindow(SDL_Window *win, int32 winID)
{
_window_map[winID] = win;
}
int32 _GetNumWindowSlots()
{
return _window_map.size();
}
void _PopBackWindow()
{
_window_map.pop_back();
}
void _PushBackWindow(SDL_Window *win)
{
_window_map.push_back(win);
}
// Members
std::vector<SDL_Window *> _window_map; // Keeps track of SDL_Windows by index-id
#ifdef SDL_VIDEO_OPENGL
BGLView *_current_context;
#endif
};
#endif

View file

@ -0,0 +1,182 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_PLATFORM_HAIKU
// Handle the BeApp specific portions of the application
#include <AppKit.h>
#include <storage/AppFileInfo.h>
#include <storage/Path.h>
#include <storage/Entry.h>
#include <storage/File.h>
#include <unistd.h>
#include <memory>
#include "SDL_BApp.h" // SDL_BLooper class definition
#include "SDL_BeApp.h"
#include "../../video/haiku/SDL_BWin.h"
#ifdef __cplusplus
extern "C" {
#endif
#include "../../thread/SDL_systhread.h"
// Flag to tell whether or not the Be application and looper are active or not
static int SDL_BeAppActive = 0;
static SDL_Thread *SDL_AppThread = NULL;
SDL_BLooper *SDL_Looper = NULL;
// Default application signature
const char *SDL_signature = "application/x-SDL-executable";
// Create a descendant of BApplication
class SDL_BApp : public BApplication {
public:
SDL_BApp(const char* signature) :
BApplication(signature) {
}
virtual ~SDL_BApp() {
}
virtual void RefsReceived(BMessage* message) {
entry_ref entryRef;
for (int32 i = 0; message->FindRef("refs", i, &entryRef) == B_OK; i++) {
BPath referencePath = BPath(&entryRef);
SDL_SendDropFile(NULL, NULL, referencePath.Path());
}
return;
}
};
static int StartBeApp(void *unused)
{
std::unique_ptr<BApplication> App;
(void)unused;
// dig resources for correct signature
image_info info;
int32 cookie = 0;
if (get_next_image_info(B_CURRENT_TEAM, &cookie, &info) == B_OK) {
BFile f(info.name, O_RDONLY);
if (f.InitCheck() == B_OK) {
BAppFileInfo app_info(&f);
if (app_info.InitCheck() == B_OK) {
char sig[B_MIME_TYPE_LENGTH];
if (app_info.GetSignature(sig) == B_OK) {
SDL_signature = strndup(sig, B_MIME_TYPE_LENGTH);
}
}
}
}
App = std::unique_ptr<BApplication>(new SDL_BApp(SDL_signature));
App->Run();
return 0;
}
static bool StartBeLooper()
{
if (!be_app) {
SDL_AppThread = SDL_CreateThread(StartBeApp, "SDLApplication", NULL);
if (!SDL_AppThread) {
return SDL_SetError("Couldn't create BApplication thread");
}
do {
SDL_Delay(10);
} while ((!be_app) || be_app->IsLaunching());
}
SDL_Looper = new SDL_BLooper("SDLLooper");
SDL_Looper->Run();
return true;
}
// Initialize the Be Application, if it's not already started
bool SDL_InitBeApp(void)
{
// Create the BApplication that handles appserver interaction
if (SDL_BeAppActive <= 0) {
if (!StartBeLooper()) {
return false;
}
// Mark the application active
SDL_BeAppActive = 0;
}
// Increment the application reference count
++SDL_BeAppActive;
// The app is running, and we're ready to go
return true;
}
// Quit the Be Application, if there's nothing left to do
void SDL_QuitBeApp(void)
{
// Decrement the application reference count
--SDL_BeAppActive;
// If the reference count reached zero, clean up the app
if (SDL_BeAppActive == 0) {
SDL_Looper->Lock();
SDL_Looper->Quit();
SDL_Looper = NULL;
if (SDL_AppThread) {
if (be_app != NULL) { // Not tested
be_app->PostMessage(B_QUIT_REQUESTED);
}
SDL_WaitThread(SDL_AppThread, NULL);
SDL_AppThread = NULL;
}
// be_app should now be NULL since be_app has quit
}
}
#ifdef __cplusplus
}
#endif
// SDL_BApp functions
void SDL_BLooper::ClearID(SDL_BWin *bwin) {
_SetSDLWindow(NULL, bwin->GetID());
int32 i = _GetNumWindowSlots() - 1;
while (i >= 0 && GetSDLWindow(i) == NULL) {
_PopBackWindow();
--i;
}
}
#endif // SDL_PLATFORM_HAIKU

View file

@ -0,0 +1,40 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef __cplusplus
extern "C" {
#endif
// Handle the BeApp specific portions of the application
// Initialize the Be Application, if it's not already started
extern bool SDL_InitBeApp(void);
// Quit the Be Application, if there's nothing left to do
extern void SDL_QuitBeApp(void);
// Be Application Signature
extern const char *SDL_signature;
#ifdef __cplusplus
}
#endif

Some files were not shown because too many files have changed in this diff Show more