This commit is contained in:
Nitload
2025-08-25 09:50:30 +08:00
parent bd0e31e922
commit 1359ff4ecc
1630 changed files with 738486 additions and 2399 deletions
+852
View File
@@ -0,0 +1,852 @@
/*
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_PLATFORM_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_PLATFORM_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_PLATFORM_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_NGAGE)
return "Nokia N-Gage";
#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
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;
}
+114
View File
@@ -0,0 +1,114 @@
/*
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 "stdlib/SDL_vacopy.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;
}
+88
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
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);
}
+403
View File
@@ -0,0 +1,403 @@
/*
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"
#ifdef SDL_PLATFORM_ANDROID
#include "core/android/SDL_android.h"
#endif
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);
}
}
#ifdef SDL_PLATFORM_ANDROID
if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) {
// Special handling for this hint, which needs to persist outside the normal application flow
Android_SetAllowRecreateActivity(SDL_GetStringBoolean(value, false));
}
#endif // SDL_PLATFORM_ANDROID
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;
}
#ifdef SDL_PLATFORM_ANDROID
if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) {
// Special handling for this hint, which needs to persist outside the normal application flow
if (env) {
Android_SetAllowRecreateActivity(SDL_GetStringBoolean(env, false));
} else {
Android_SetAllowRecreateActivity(false);
}
}
#endif // SDL_PLATFORM_ANDROID
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;
#ifdef SDL_PLATFORM_ANDROID
if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) {
// Special handling for this hint, which needs to persist outside the normal application flow
if (env) {
Android_SetAllowRecreateActivity(SDL_GetStringBoolean(env, false));
} else {
Android_SetAllowRecreateActivity(false);
}
}
#endif // SDL_PLATFORM_ANDROID
}
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);
}
+86
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);
}
}
+829
View File
@@ -0,0 +1,829 @@
/*
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;
}
#if defined(SDL_PLATFORM_NGAGE)
extern void NGAGE_vnprintf(char *buf, size_t size, const char *fmt, va_list ap);
char buf[1024];
NGAGE_vnprintf(buf, sizeof(buf), fmt, ap);
#ifdef ENABLE_FILE_LOG
FILE* file;
file = fopen("E:/SDL_Log.txt", "a");
if (file)
{
vfprintf(file, fmt, ap);
fprintf(file, "\n");
(void)fclose(file);
}
#endif
return;
#endif
// 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);
}
}
#elif defined(SDL_PLATFORM_NGAGE)
{
/* Nothing to do here. */
}
#endif
#if defined(HAVE_STDIO_H) && \
!(defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))) && \
!(defined(SDL_PLATFORM_NGAGE)) && \
!(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);
}
+824
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);
}
}
}
+563
View File
@@ -0,0 +1,563 @@
/*
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;
// Fixes the "HORI HORl Taiko No Tatsujin Drum Controller"
if (SDL_strncmp(a, "HORI ", 5) == 0 && SDL_strncmp(b, "HORl ", 5) == 0) {
return 5;
}
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;
}
void SDL_LogBackend(const char *subsystem, const char *backend)
{
if (SDL_GetHintBoolean(SDL_HINT_LOG_BACKENDS, false)) {
SDL_Log("SDL_BACKEND: %s -> '%s'", subsystem, backend);
}
}
+382
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();
}
@@ -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
}
File diff suppressed because it is too large Load Diff
@@ -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
File diff suppressed because it is too large Load Diff
@@ -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
@@ -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_
@@ -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;
}
@@ -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 SDL_AudioStreamDataCompleteCallback SDL_ReleaseAudioBufferCallback;
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_
@@ -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);
}
@@ -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_
@@ -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
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;
}
@@ -0,0 +1,400 @@
/*
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 alerts it to start/end of device iteration.
SDL_AudioIterationCallback iteration_start;
SDL_AudioIterationCallback iteration_end;
// App-supplied pointer for iteration callbacks.
void *iteration_userdata;
// 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 NGAGEAUDIO_bootstrap;
extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
extern AudioBootStrap QSAAUDIO_bootstrap;
#endif // SDL_sysaudio_h_
File diff suppressed because it is too large Load Diff
+151
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;
@@ -0,0 +1,557 @@
/*
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);
// If no specific buffer size has been requested, the device will pick the optimal
if(SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) {
ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * device->sample_frames); // AAudio requires that the buffer capacity is at least
ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, device->sample_frames); // twice the size of the data callback buffer size
}
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 - 1);
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
@@ -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_
@@ -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(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
@@ -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_
@@ -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_
@@ -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, SDL_AUDIO_UNKNOWN);
} 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
@@ -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_
@@ -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_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 const char *AudioFormatString(SDL_AudioFormat fmt)
{
const char *str = SDL_GetAudioFormatName(fmt);
SDL_assert(str);
if (SDL_strncmp(str, "SDL_AUDIO_", 10) == 0) {
str += 10; // so we return "S8" instead of "SDL_AUDIO_S8", etc.
}
return str;
}
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], format=%s channels=%d freq=%d.",
recording ? "Reading from" : "Writing to", fname,
AudioFormatString(device->spec.format), device->spec.channels, device->spec.freq);
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
@@ -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_
@@ -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
@@ -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_
@@ -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
};
@@ -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_
@@ -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 = MAIN_THREAD_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
@@ -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_
@@ -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_
@@ -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
@@ -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_
@@ -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
@@ -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
@@ -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
@@ -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_
@@ -0,0 +1,103 @@
/*
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_NGAGE
#include "../SDL_sysaudio.h"
#include "SDL_ngageaudio.h"
static SDL_AudioDevice *devptr = NULL;
SDL_AudioDevice *NGAGE_GetAudioDeviceAddr()
{
return devptr;
}
static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice *device)
{
SDL_PrivateAudioData *phdata = SDL_calloc(1, sizeof(SDL_PrivateAudioData));
if (!phdata) {
SDL_OutOfMemory();
return false;
}
device->hidden = phdata;
phdata->buffer = SDL_calloc(1, device->buffer_size);
if (!phdata->buffer) {
SDL_OutOfMemory();
SDL_free(phdata);
return false;
}
devptr = device;
// Since the phone can change the sample rate during a phone call,
// we set the sample rate to 8KHz to be safe. Even though it
// might be possible to adjust the sample rate dynamically, it's
// not supported by the current implementation.
device->spec.format = SDL_AUDIO_S16LE;
device->spec.channels = 1;
device->spec.freq = 8000;
SDL_UpdatedAudioDeviceFormat(device);
return true;
}
static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
if (!phdata) {
*buffer_size = 0;
return 0;
}
*buffer_size = device->buffer_size;
return phdata->buffer;
}
static void NGAGEAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
SDL_free(device->hidden->buffer);
SDL_free(device->hidden);
}
return;
}
static bool NGAGEAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = NGAGEAUDIO_OpenDevice;
impl->GetDeviceBuf = NGAGEAUDIO_GetDeviceBuf;
impl->CloseDevice = NGAGEAUDIO_CloseDevice;
impl->ProvidesOwnCallbackThread = true;
impl->OnlyHasDefaultPlaybackDevice = true;
return true;
}
AudioBootStrap NGAGEAUDIO_bootstrap = { "N-Gage", "N-Gage audio driver", NGAGEAUDIO_Init, false };
#endif // SDL_AUDIO_DRIVER_NGAGE
@@ -0,0 +1,368 @@
/*
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.
*/
#ifdef __cplusplus
extern "C" {
#endif
#include "SDL_ngageaudio.h"
#include "../SDL_sysaudio.h"
#include "SDL_internal.h"
#ifdef __cplusplus
}
#endif
#ifdef SDL_AUDIO_DRIVER_NGAGE
#include "SDL_ngageaudio.hpp"
CAudio::CAudio() : CActive(EPriorityStandard), iBufDes(NULL, 0) {}
CAudio *CAudio::NewL(TInt aLatency)
{
CAudio *self = new (ELeave) CAudio();
CleanupStack::PushL(self);
self->ConstructL(aLatency);
CleanupStack::Pop(self);
return self;
}
void CAudio::ConstructL(TInt aLatency)
{
CActiveScheduler::Add(this);
User::LeaveIfError(iTimer.CreateLocal());
iTimerCreated = ETrue;
iStream = CMdaAudioOutputStream::NewL(*this);
if (!iStream) {
SDL_Log("Error: Failed to create audio stream");
User::Leave(KErrNoMemory);
}
iLatency = aLatency;
iLatencySamples = aLatency * 8; // 8kHz.
// Determine minimum and maximum number of samples to write with one
// WriteL request.
iMinWrite = iLatencySamples / 8;
iMaxWrite = iLatencySamples / 2;
// Set defaults.
iState = EStateNone;
iTimerCreated = EFalse;
iTimerActive = EFalse;
}
CAudio::~CAudio()
{
if (iStream) {
iStream->Stop();
while (iState != EStateDone) {
User::After(100000); // 100ms.
}
delete iStream;
}
}
void CAudio::Start()
{
if (iStream) {
// Set to 8kHz mono audio.
iStreamSettings.iChannels = TMdaAudioDataSettings::EChannelsMono;
iStreamSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate8000Hz;
iStream->Open(&iStreamSettings);
iState = EStateOpening;
} else {
SDL_Log("Error: Failed to open audio stream");
}
}
// Feeds more processed data to the audio stream.
void CAudio::Feed()
{
// If a WriteL is already in progress, or we aren't even playing;
// do nothing!
if ((iState != EStateWriting) && (iState != EStatePlaying)) {
return;
}
// Figure out the number of samples that really have been played
// through the output.
TTimeIntervalMicroSeconds pos = iStream->Position();
TInt played = 8 * (pos.Int64() / TInt64(1000)).GetTInt(); // 8kHz.
played += iBaseSamplesPlayed;
// Determine the difference between the number of samples written to
// CMdaAudioOutputStream and the number of samples it has played.
// The difference is the amount of data in the buffers.
if (played < 0) {
played = 0;
}
TInt buffered = iSamplesWritten - played;
if (buffered < 0) {
buffered = 0;
}
if (iState == EStateWriting) {
return;
}
// The trick for low latency: Do not let the buffers fill up beyond the
// latency desired! We write as many samples as the difference between
// the latency target (in samples) and the amount of data buffered.
TInt samplesToWrite = iLatencySamples - buffered;
// Do not write very small blocks. This should improve efficiency, since
// writes to the streaming API are likely to be expensive.
if (samplesToWrite < iMinWrite) {
// Not enough data to write, set up a timer to fire after a while.
// Try againwhen it expired.
if (iTimerActive) {
return;
}
iTimerActive = ETrue;
SetActive();
iTimer.After(iStatus, (1000 * iLatency) / 8);
return;
}
// Do not write more than the set number of samples at once.
int numSamples = samplesToWrite;
if (numSamples > iMaxWrite) {
numSamples = iMaxWrite;
}
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
if (device) {
SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
iBufDes.Set(phdata->buffer, 2 * numSamples, 2 * numSamples);
iStream->WriteL(iBufDes);
iState = EStateWriting;
// Keep track of the number of samples written (for latency calculations).
iSamplesWritten += numSamples;
} else {
// Output device not ready yet. Let's go for another round.
if (iTimerActive) {
return;
}
iTimerActive = ETrue;
SetActive();
iTimer.After(iStatus, (1000 * iLatency) / 8);
}
}
void CAudio::RunL()
{
iTimerActive = EFalse;
Feed();
}
void CAudio::DoCancel()
{
iTimerActive = EFalse;
iTimer.Cancel();
}
void CAudio::StartThread()
{
TInt heapMinSize = 8192; // 8 KB initial heap size.
TInt heapMaxSize = 1024 * 1024; // 1 MB maximum heap size.
TInt err = iProcess.Create(_L("ProcessThread"), ProcessThreadCB, KDefaultStackSize * 2, heapMinSize, heapMaxSize, this);
if (err == KErrNone) {
iProcess.SetPriority(EPriorityLess);
iProcess.Resume();
} else {
SDL_Log("Error: Failed to create audio processing thread: %d", err);
}
}
void CAudio::StopThread()
{
if (iStreamStarted) {
iProcess.Kill(KErrNone);
iProcess.Close();
iStreamStarted = EFalse;
}
}
TInt CAudio::ProcessThreadCB(TAny *aPtr)
{
CAudio *self = static_cast<CAudio *>(aPtr);
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
while (self->iStreamStarted) {
if (device) {
SDL_PlaybackAudioThreadIterate(device);
} else {
device = NGAGE_GetAudioDeviceAddr();
}
User::After(100000); // 100ms.
}
return KErrNone;
}
void CAudio::MaoscOpenComplete(TInt aError)
{
if (aError == KErrNone) {
iStream->SetVolume(1);
iStreamStarted = ETrue;
StartThread();
} else {
SDL_Log("Error: Failed to open audio stream: %d", aError);
}
}
void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/)
{
if (aError == KErrNone) {
iState = EStatePlaying;
Feed();
} else if (aError == KErrAbort) {
// The stream has been stopped.
iState = EStateDone;
} else {
SDL_Log("Error: Failed to copy audio buffer: %d", aError);
}
}
void CAudio::MaoscPlayComplete(TInt aError)
{
// If we finish due to an underflow, we'll need to restart playback.
// Normally KErrUnderlow is raised at stream end, but in our case the API
// should never see the stream end -- we are continuously feeding it more
// data! Many underflow errors mean that the latency target is too low.
if (aError == KErrUnderflow) {
// The number of samples played gets resetted to zero when we restart
// playback after underflow.
iBaseSamplesPlayed = iSamplesWritten;
iStream->Stop();
Cancel();
iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono);
iState = EStatePlaying;
Feed();
return;
} else if (aError != KErrNone) {
// Handle error.
}
// We shouldn't get here.
SDL_Log("%s: %d", __FUNCTION__, aError);
}
static TBool gAudioRunning;
TBool AudioIsReady()
{
return gAudioRunning;
}
TInt AudioThreadCB(TAny *aParams)
{
CTrapCleanup *cleanup = CTrapCleanup::New();
if (!cleanup) {
return KErrNoMemory;
}
CActiveScheduler *scheduler = new CActiveScheduler();
if (!scheduler) {
delete cleanup;
return KErrNoMemory;
}
CActiveScheduler::Install(scheduler);
TRAPD(err,
{
TInt latency = *(TInt *)aParams;
CAudio *audio = CAudio::NewL(latency);
CleanupStack::PushL(audio);
gAudioRunning = ETrue;
audio->Start();
TBool once = EFalse;
while (gAudioRunning) {
// Allow active scheduler to process any events.
TInt error;
CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
if (!once) {
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
if (device) {
// Stream ready; start feeding audio data.
// After feeding it once, the callbacks will take over.
audio->iState = CAudio::EStatePlaying;
audio->Feed();
once = ETrue;
}
}
User::After(100000); // 100ms.
}
CleanupStack::PopAndDestroy(audio);
});
delete scheduler;
delete cleanup;
return err;
}
RThread audioThread;
void InitAudio(TInt *aLatency)
{
_LIT(KAudioThreadName, "AudioThread");
TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency);
if (err != KErrNone) {
User::Leave(err);
}
audioThread.Resume();
}
void DeinitAudio()
{
gAudioRunning = EFalse;
TRequestStatus status;
audioThread.Logon(status);
User::WaitForRequest(status);
audioThread.Close();
}
#endif // SDL_AUDIO_DRIVER_NGAGE
@@ -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_ngageaudio_h
#define SDL_ngageaudio_h
typedef struct SDL_PrivateAudioData
{
Uint8 *buffer;
} SDL_PrivateAudioData;
#ifdef __cplusplus
extern "C" {
#endif
#include "../SDL_sysaudio.h"
SDL_AudioDevice *NGAGE_GetAudioDeviceAddr();
#ifdef __cplusplus
}
#endif
#endif // SDL_ngageaudio_h
@@ -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
@@ -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
@@ -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_
@@ -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
};
@@ -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_
@@ -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
@@ -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
@@ -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_
@@ -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
@@ -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__
@@ -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
@@ -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_
@@ -0,0 +1,239 @@
/*
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)
{
// sceAudioOutOutput returns amount of samples queued or < 0 on error
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
@@ -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
@@ -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, SDL_AUDIO_F32);
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
@@ -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_
File diff suppressed because it is too large Load Diff
@@ -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_
@@ -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_
@@ -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
@@ -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
@@ -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;
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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,167 @@
/*
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);
void Android_SetAllowRecreateActivity(bool enabled);
// 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);
bool Android_JNI_EnumerateAssetDirectory(const char *path, SDL_EnumerateDirectoryCallback cb, void *userdata);
bool Android_JNI_GetAssetPathInfo(const char *path, SDL_PathInfo *info);
// 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
@@ -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
@@ -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
@@ -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 && !((_this->windows) && _this->windows->text_input_active)) {
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;
}
@@ -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);
@@ -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
@@ -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
@@ -0,0 +1,826 @@
/*
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_dbus.h"
#include "../../stdlib/SDL_vacopy.h"
#ifdef SDL_USE_LIBDBUS
// we never link directly to libdbus.
static const char *dbus_library = "libdbus-1.so.3";
static SDL_SharedObject *dbus_handle = NULL;
static char *inhibit_handle = NULL;
static unsigned int screensaver_cookie = 0;
static SDL_DBusContext dbus;
static bool LoadDBUSSyms(void)
{
#define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \
dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y)
#define SDL_DBUS_SYM2(TYPE, x, y) \
if (!(dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y))) \
return false
#define SDL_DBUS_SYM_OPTIONAL(TYPE, x) \
SDL_DBUS_SYM2_OPTIONAL(TYPE, x, dbus_##x)
#define SDL_DBUS_SYM(TYPE, x) \
SDL_DBUS_SYM2(TYPE, x, dbus_##x)
SDL_DBUS_SYM(DBusConnection *(*)(DBusBusType, DBusError *), bus_get_private);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusError *), bus_register);
SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_add_match);
SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_remove_match);
SDL_DBUS_SYM(const char *(*)(DBusConnection *), bus_get_unique_name);
SDL_DBUS_SYM(DBusConnection *(*)(const char *, DBusError *), connection_open_private);
SDL_DBUS_SYM(void (*)(DBusConnection *, dbus_bool_t), connection_set_exit_on_disconnect);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *), connection_get_is_connected);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *, DBusFreeFunction), connection_add_filter);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *), connection_remove_filter);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, const char *, const DBusObjectPathVTable *, void *, DBusError *), connection_try_register_object_path);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusMessage *, dbus_uint32_t *), connection_send);
SDL_DBUS_SYM(DBusMessage *(*)(DBusConnection *, DBusMessage *, int, DBusError *), connection_send_with_reply_and_block);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_close);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_ref);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_unref);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_flush);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write_dispatch);
SDL_DBUS_SYM(DBusDispatchStatus (*)(DBusConnection *), connection_dispatch);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path);
SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *, const char *), message_new_method_call);
SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *), message_new_signal);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, ...), message_append_args);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, va_list), message_append_args_valist);
SDL_DBUS_SYM(void (*)(DBusMessage *, DBusMessageIter *), message_iter_init_append);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const char *, DBusMessageIter *), message_iter_open_container);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const void *), message_iter_append_basic);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, DBusMessageIter *), message_iter_close_container);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, ...), message_get_args);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, va_list), message_get_args_valist);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusMessageIter *), message_iter_init);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *), message_iter_next);
SDL_DBUS_SYM(void (*)(DBusMessageIter *, void *), message_iter_get_basic);
SDL_DBUS_SYM(int (*)(DBusMessageIter *), message_iter_get_arg_type);
SDL_DBUS_SYM(void (*)(DBusMessageIter *, DBusMessageIter *), message_iter_recurse);
SDL_DBUS_SYM(void (*)(DBusMessage *), message_unref);
SDL_DBUS_SYM(dbus_bool_t (*)(void), threads_init_default);
SDL_DBUS_SYM(void (*)(DBusError *), error_init);
SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *), error_is_set);
SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *, const char *), error_has_name);
SDL_DBUS_SYM(void (*)(DBusError *), error_free);
SDL_DBUS_SYM(char *(*)(void), get_local_machine_id);
SDL_DBUS_SYM_OPTIONAL(char *(*)(DBusError *), try_get_local_machine_id);
SDL_DBUS_SYM(void (*)(void *), free);
SDL_DBUS_SYM(void (*)(char **), free_string_array);
SDL_DBUS_SYM(void (*)(void), shutdown);
#undef SDL_DBUS_SYM
#undef SDL_DBUS_SYM2
return true;
}
static void UnloadDBUSLibrary(void)
{
if (dbus_handle) {
SDL_UnloadObject(dbus_handle);
dbus_handle = NULL;
}
}
static bool LoadDBUSLibrary(void)
{
bool result = true;
if (!dbus_handle) {
dbus_handle = SDL_LoadObject(dbus_library);
if (!dbus_handle) {
result = false;
// Don't call SDL_SetError(): SDL_LoadObject already did.
} else {
result = LoadDBUSSyms();
if (!result) {
UnloadDBUSLibrary();
}
}
}
return result;
}
static SDL_InitState dbus_init;
void SDL_DBus_Init(void)
{
static bool is_dbus_available = true;
if (!is_dbus_available) {
return; // don't keep trying if this fails.
}
if (!SDL_ShouldInit(&dbus_init)) {
return;
}
if (!LoadDBUSLibrary()) {
goto error;
}
if (!dbus.threads_init_default()) {
goto error;
}
DBusError err;
dbus.error_init(&err);
// session bus is required
dbus.session_conn = dbus.bus_get_private(DBUS_BUS_SESSION, &err);
if (dbus.error_is_set(&err)) {
dbus.error_free(&err);
goto error;
}
dbus.connection_set_exit_on_disconnect(dbus.session_conn, 0);
// system bus is optional
dbus.system_conn = dbus.bus_get_private(DBUS_BUS_SYSTEM, &err);
if (!dbus.error_is_set(&err)) {
dbus.connection_set_exit_on_disconnect(dbus.system_conn, 0);
}
dbus.error_free(&err);
SDL_SetInitialized(&dbus_init, true);
return;
error:
is_dbus_available = false;
SDL_SetInitialized(&dbus_init, true);
SDL_DBus_Quit();
}
void SDL_DBus_Quit(void)
{
if (!SDL_ShouldQuit(&dbus_init)) {
return;
}
if (dbus.system_conn) {
dbus.connection_close(dbus.system_conn);
dbus.connection_unref(dbus.system_conn);
}
if (dbus.session_conn) {
dbus.connection_close(dbus.session_conn);
dbus.connection_unref(dbus.session_conn);
}
if (SDL_GetHintBoolean(SDL_HINT_SHUTDOWN_DBUS_ON_QUIT, false)) {
if (dbus.shutdown) {
dbus.shutdown();
}
UnloadDBUSLibrary();
} else {
/* Leaving libdbus loaded when skipping dbus_shutdown() avoids
* spurious leak warnings from LeakSanitizer on internal D-Bus
* allocations that would be freed by dbus_shutdown(). */
dbus_handle = NULL;
}
SDL_zero(dbus);
if (inhibit_handle) {
SDL_free(inhibit_handle);
inhibit_handle = NULL;
}
SDL_SetInitialized(&dbus_init, false);
}
SDL_DBusContext *SDL_DBus_GetContext(void)
{
if (!dbus_handle || !dbus.session_conn) {
SDL_DBus_Init();
}
return (dbus_handle && dbus.session_conn) ? &dbus : NULL;
}
static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap)
{
bool result = false;
if (conn) {
DBusMessage *msg = dbus.message_new_method_call(node, path, interface, method);
if (msg) {
int firstarg;
va_list ap_reply;
va_copy(ap_reply, ap); // copy the arg list so we don't compete with D-Bus for it
firstarg = va_arg(ap, int);
if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) {
DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL);
if (reply) {
// skip any input args, get to output args.
while ((firstarg = va_arg(ap_reply, int)) != DBUS_TYPE_INVALID) {
// we assume D-Bus already validated all this.
{
void *dumpptr = va_arg(ap_reply, void *);
(void)dumpptr;
}
if (firstarg == DBUS_TYPE_ARRAY) {
{
const int dumpint = va_arg(ap_reply, int);
(void)dumpint;
}
}
}
firstarg = va_arg(ap_reply, int);
if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_get_args_valist(reply, NULL, firstarg, ap_reply)) {
result = true;
}
dbus.message_unref(reply);
}
}
va_end(ap_reply);
dbus.message_unref(msg);
}
}
return result;
}
bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...)
{
bool result;
va_list ap;
va_start(ap, method);
result = SDL_DBus_CallMethodInternal(conn, node, path, interface, method, ap);
va_end(ap);
return result;
}
bool SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...)
{
bool result;
va_list ap;
va_start(ap, method);
result = SDL_DBus_CallMethodInternal(dbus.session_conn, node, path, interface, method, ap);
va_end(ap);
return result;
}
static bool SDL_DBus_CallVoidMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap)
{
bool result = false;
if (conn) {
DBusMessage *msg = dbus.message_new_method_call(node, path, interface, method);
if (msg) {
int firstarg = va_arg(ap, int);
if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) {
if (dbus.connection_send(conn, msg, NULL)) {
dbus.connection_flush(conn);
result = true;
}
}
dbus.message_unref(msg);
}
}
return result;
}
static bool SDL_DBus_CallWithBasicReply(DBusConnection *conn, DBusMessage *msg, const int expectedtype, void *result)
{
bool retval = false;
DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL);
if (reply) {
DBusMessageIter iter, actual_iter;
dbus.message_iter_init(reply, &iter);
if (dbus.message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT) {
dbus.message_iter_recurse(&iter, &actual_iter);
} else {
actual_iter = iter;
}
if (dbus.message_iter_get_arg_type(&actual_iter) == expectedtype) {
dbus.message_iter_get_basic(&actual_iter, result);
retval = true;
}
dbus.message_unref(reply);
}
return retval;
}
bool SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...)
{
bool result;
va_list ap;
va_start(ap, method);
result = SDL_DBus_CallVoidMethodInternal(conn, node, path, interface, method, ap);
va_end(ap);
return result;
}
bool SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *interface, const char *method, ...)
{
bool result;
va_list ap;
va_start(ap, method);
result = SDL_DBus_CallVoidMethodInternal(dbus.session_conn, node, path, interface, method, ap);
va_end(ap);
return result;
}
bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result)
{
bool retval = false;
if (conn) {
DBusMessage *msg = dbus.message_new_method_call(node, path, "org.freedesktop.DBus.Properties", "Get");
if (msg) {
if (dbus.message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) {
retval = SDL_DBus_CallWithBasicReply(conn, msg, expectedtype, result);
}
dbus.message_unref(msg);
}
}
return retval;
}
bool SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result)
{
return SDL_DBus_QueryPropertyOnConnection(dbus.session_conn, node, path, interface, property, expectedtype, result);
}
void SDL_DBus_ScreensaverTickle(void)
{
if (screensaver_cookie == 0 && !inhibit_handle) { // no need to tickle if we're inhibiting.
// org.gnome.ScreenSaver is the legacy interface, but it'll either do nothing or just be a second harmless tickle on newer systems, so we leave it for now.
SDL_DBus_CallVoidMethod("org.gnome.ScreenSaver", "/org/gnome/ScreenSaver", "org.gnome.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID);
SDL_DBus_CallVoidMethod("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID);
}
}
static bool SDL_DBus_AppendDictWithKeysAndValues(DBusMessageIter *iterInit, const char **keys, const char **values, int count)
{
DBusMessageIter iterDict;
if (!dbus.message_iter_open_container(iterInit, DBUS_TYPE_ARRAY, "{sv}", &iterDict)) {
goto failed;
}
for (int i = 0; i < count; i++) {
DBusMessageIter iterEntry, iterValue;
const char *key = keys[i];
const char *value = values[i];
if (!dbus.message_iter_open_container(&iterDict, DBUS_TYPE_DICT_ENTRY, NULL, &iterEntry)) {
goto failed;
}
if (!dbus.message_iter_append_basic(&iterEntry, DBUS_TYPE_STRING, &key)) {
goto failed;
}
if (!dbus.message_iter_open_container(&iterEntry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &iterValue)) {
goto failed;
}
if (!dbus.message_iter_append_basic(&iterValue, DBUS_TYPE_STRING, &value)) {
goto failed;
}
if (!dbus.message_iter_close_container(&iterEntry, &iterValue) || !dbus.message_iter_close_container(&iterDict, &iterEntry)) {
goto failed;
}
}
if (!dbus.message_iter_close_container(iterInit, &iterDict)) {
goto failed;
}
return true;
failed:
/* message_iter_abandon_container_if_open() and message_iter_abandon_container() might be
* missing if libdbus is too old. Instead, we just return without cleaning up any eventual
* open container */
return false;
}
static bool SDL_DBus_AppendDictWithKeyValue(DBusMessageIter *iterInit, const char *key, const char *value)
{
const char *keys[1];
const char *values[1];
keys[0] = key;
values[0] = value;
return SDL_DBus_AppendDictWithKeysAndValues(iterInit, keys, values, 1);
}
bool SDL_DBus_ScreensaverInhibit(bool inhibit)
{
const char *default_inhibit_reason = "Playing a game";
if ((inhibit && (screensaver_cookie != 0 || inhibit_handle)) || (!inhibit && (screensaver_cookie == 0 && !inhibit_handle))) {
return true;
}
if (!dbus.session_conn) {
/* We either lost connection to the session bus or were not able to
* load the D-Bus library at all. */
return false;
}
if (SDL_GetSandbox() != SDL_SANDBOX_NONE) {
const char *bus_name = "org.freedesktop.portal.Desktop";
const char *path = "/org/freedesktop/portal/desktop";
const char *interface = "org.freedesktop.portal.Inhibit";
const char *window = ""; // As a future improvement we could gather the X11 XID or Wayland surface identifier
static const unsigned int INHIBIT_IDLE = 8; // Taken from the portal API reference
DBusMessageIter iterInit;
if (inhibit) {
DBusMessage *msg;
bool result = false;
const char *key = "reason";
const char *reply = NULL;
const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME);
if (!reason || !reason[0]) {
reason = default_inhibit_reason;
}
msg = dbus.message_new_method_call(bus_name, path, interface, "Inhibit");
if (!msg) {
return false;
}
if (!dbus.message_append_args(msg, DBUS_TYPE_STRING, &window, DBUS_TYPE_UINT32, &INHIBIT_IDLE, DBUS_TYPE_INVALID)) {
dbus.message_unref(msg);
return false;
}
dbus.message_iter_init_append(msg, &iterInit);
// a{sv}
if (!SDL_DBus_AppendDictWithKeyValue(&iterInit, key, reason)) {
dbus.message_unref(msg);
return false;
}
if (SDL_DBus_CallWithBasicReply(dbus.session_conn, msg, DBUS_TYPE_OBJECT_PATH, &reply)) {
inhibit_handle = SDL_strdup(reply);
result = true;
}
dbus.message_unref(msg);
return result;
} else {
if (!SDL_DBus_CallVoidMethod(bus_name, inhibit_handle, "org.freedesktop.portal.Request", "Close", DBUS_TYPE_INVALID)) {
return false;
}
SDL_free(inhibit_handle);
inhibit_handle = NULL;
}
} else {
const char *bus_name = "org.freedesktop.ScreenSaver";
const char *path = "/org/freedesktop/ScreenSaver";
const char *interface = "org.freedesktop.ScreenSaver";
if (inhibit) {
const char *app = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME);
if (!reason || !reason[0]) {
reason = default_inhibit_reason;
}
if (!SDL_DBus_CallMethod(bus_name, path, interface, "Inhibit",
DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING, &reason, DBUS_TYPE_INVALID,
DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) {
return false;
}
return (screensaver_cookie != 0);
} else {
if (!SDL_DBus_CallVoidMethod(bus_name, path, interface, "UnInhibit", DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) {
return false;
}
screensaver_cookie = 0;
}
}
return true;
}
void SDL_DBus_PumpEvents(void)
{
if (dbus.session_conn) {
dbus.connection_read_write(dbus.session_conn, 0);
while (dbus.connection_dispatch(dbus.session_conn) == DBUS_DISPATCH_DATA_REMAINS) {
// Do nothing, actual work happens in DBus_MessageFilter
SDL_DelayNS(SDL_US_TO_NS(10));
}
}
}
/*
* Get the machine ID if possible. Result must be freed with dbus->free().
*/
char *SDL_DBus_GetLocalMachineId(void)
{
DBusError err;
char *result;
dbus.error_init(&err);
if (dbus.try_get_local_machine_id) {
// Available since dbus 1.12.0, has proper error-handling
result = dbus.try_get_local_machine_id(&err);
} else {
/* Available since time immemorial, but has no error-handling:
* if the machine ID can't be read, many versions of libdbus will
* treat that as a fatal mis-installation and abort() */
result = dbus.get_local_machine_id();
}
if (result) {
return result;
}
if (dbus.error_is_set(&err)) {
SDL_SetError("%s: %s", err.name, err.message);
dbus.error_free(&err);
} else {
SDL_SetError("Error getting D-Bus machine ID");
}
return NULL;
}
/*
* Convert file drops with mime type "application/vnd.portal.filetransfer" to file paths
* Result must be freed with dbus->free_string_array().
* https://flatpak.github.io/xdg-desktop-portal/#gdbus-method-org-freedesktop-portal-FileTransfer.RetrieveFiles
*/
char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *path_count)
{
DBusError err;
DBusMessageIter iter, iterDict;
char **paths = NULL;
DBusMessage *reply = NULL;
DBusMessage *msg = dbus.message_new_method_call("org.freedesktop.portal.Documents", // Node
"/org/freedesktop/portal/documents", // Path
"org.freedesktop.portal.FileTransfer", // Interface
"RetrieveFiles"); // Method
// Make sure we have a connection to the dbus session bus
if (!SDL_DBus_GetContext() || !dbus.session_conn) {
/* We either cannot connect to the session bus or were unable to
* load the D-Bus library at all. */
return NULL;
}
dbus.error_init(&err);
// First argument is a "application/vnd.portal.filetransfer" key from a DnD or clipboard event
if (!dbus.message_append_args(msg, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {
SDL_OutOfMemory();
dbus.message_unref(msg);
goto failed;
}
/* Second argument is a variant dictionary for options.
* The spec doesn't define any entries yet so it's empty. */
dbus.message_iter_init_append(msg, &iter);
if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) ||
!dbus.message_iter_close_container(&iter, &iterDict)) {
SDL_OutOfMemory();
dbus.message_unref(msg);
goto failed;
}
reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err);
dbus.message_unref(msg);
if (reply) {
dbus.message_get_args(reply, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &paths, path_count, DBUS_TYPE_INVALID);
dbus.message_unref(reply);
}
if (paths) {
return paths;
}
failed:
if (dbus.error_is_set(&err)) {
SDL_SetError("%s: %s", err.name, err.message);
dbus.error_free(&err);
} else {
SDL_SetError("Error retrieving paths for documents portal \"%s\"", key);
}
return NULL;
}
typedef struct SDL_DBus_CameraPortalMessageHandlerData
{
uint32_t response;
char *path;
DBusError *err;
bool done;
} SDL_DBus_CameraPortalMessageHandlerData;
static DBusHandlerResult SDL_DBus_CameraPortalMessageHandler(DBusConnection *conn, DBusMessage *msg, void *v)
{
SDL_DBus_CameraPortalMessageHandlerData *data = v;
const char *name, *old, *new;
if (dbus.message_is_signal(msg, "org.freedesktop.DBus", "NameOwnerChanged")) {
if (!dbus.message_get_args(msg, data->err,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_STRING, &old,
DBUS_TYPE_STRING, &new,
DBUS_TYPE_INVALID)) {
data->done = true;
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (SDL_strcmp(name, "org.freedesktop.portal.Desktop") != 0 ||
SDL_strcmp(new, "") != 0) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
data->done = true;
data->response = -1;
return DBUS_HANDLER_RESULT_HANDLED;
}
if (!dbus.message_has_path(msg, data->path) || !dbus.message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
dbus.message_get_args(msg, data->err, DBUS_TYPE_UINT32, &data->response, DBUS_TYPE_INVALID);
data->done = true;
return DBUS_HANDLER_RESULT_HANDLED;
}
#define SIGNAL_NAMEOWNERCHANGED "type='signal',\
sender='org.freedesktop.DBus',\
interface='org.freedesktop.DBus',\
member='NameOwnerChanged',\
arg0='org.freedesktop.portal.Desktop',\
arg2=''"
/*
* Requests access for the camera. Returns -1 on error, -2 on denied access or
* missing portal, otherwise returns a file descriptor to be used by the Pipewire driver.
* https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Camera.html
*/
int SDL_DBus_CameraPortalRequestAccess(void)
{
SDL_DBus_CameraPortalMessageHandlerData data;
DBusError err;
DBusMessageIter iter, iterDict;
DBusMessage *reply, *msg;
int fd;
if (SDL_GetSandbox() == SDL_SANDBOX_NONE) {
return -2;
}
if (!SDL_DBus_GetContext()) {
return -2;
}
dbus.error_init(&err);
msg = dbus.message_new_method_call("org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Camera",
"AccessCamera");
dbus.message_iter_init_append(msg, &iter);
if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) ||
!dbus.message_iter_close_container(&iter, &iterDict)) {
SDL_OutOfMemory();
dbus.message_unref(msg);
goto failed;
}
reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err);
dbus.message_unref(msg);
if (reply) {
dbus.message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &data.path, DBUS_TYPE_INVALID);
if (dbus.error_is_set(&err)) {
dbus.message_unref(reply);
goto failed;
}
if ((data.path = SDL_strdup(data.path)) == NULL) {
dbus.message_unref(reply);
SDL_OutOfMemory();
goto failed;
}
dbus.message_unref(reply);
} else {
if (dbus.error_has_name(&err, DBUS_ERROR_NAME_HAS_NO_OWNER)) {
return -2;
}
goto failed;
}
dbus.bus_add_match(dbus.session_conn, SIGNAL_NAMEOWNERCHANGED, &err);
if (dbus.error_is_set(&err)) {
SDL_free(data.path);
goto failed;
}
data.err = &err;
data.done = false;
if (!dbus.connection_add_filter(dbus.session_conn, SDL_DBus_CameraPortalMessageHandler, &data, NULL)) {
SDL_free(data.path);
SDL_OutOfMemory();
goto failed;
}
while (!data.done && dbus.connection_read_write_dispatch(dbus.session_conn, -1)) {
;
}
dbus.bus_remove_match(dbus.session_conn, SIGNAL_NAMEOWNERCHANGED, &err);
if (dbus.error_is_set(&err)) {
SDL_free(data.path);
goto failed;
}
dbus.connection_remove_filter(dbus.session_conn, SDL_DBus_CameraPortalMessageHandler, &data);
SDL_free(data.path);
if (!data.done) {
goto failed;
}
if (dbus.error_is_set(&err)) { // from the message handler
goto failed;
}
if (data.response == 1 || data.response == 2) {
return -2;
} else if (data.response != 0) {
goto failed;
}
msg = dbus.message_new_method_call("org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Camera",
"OpenPipeWireRemote");
dbus.message_iter_init_append(msg, &iter);
if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) ||
!dbus.message_iter_close_container(&iter, &iterDict)) {
SDL_OutOfMemory();
dbus.message_unref(msg);
goto failed;
}
reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err);
dbus.message_unref(msg);
if (reply) {
dbus.message_get_args(reply, &err, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_INVALID);
dbus.message_unref(reply);
if (dbus.error_is_set(&err)) {
goto failed;
}
} else {
goto failed;
}
return fd;
failed:
if (dbus.error_is_set(&err)) {
if (dbus.error_has_name(&err, DBUS_ERROR_NO_MEMORY)) {
SDL_OutOfMemory();
}
SDL_SetError("%s: %s", err.name, err.message);
dbus.error_free(&err);
} else {
SDL_SetError("Error requesting access for the camera");
}
return -1;
}
#endif
@@ -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"
#ifndef SDL_dbus_h_
#define SDL_dbus_h_
#ifdef HAVE_DBUS_DBUS_H
#define SDL_USE_LIBDBUS 1
#include <dbus/dbus.h>
#ifndef DBUS_TIMEOUT_USE_DEFAULT
#define DBUS_TIMEOUT_USE_DEFAULT -1
#endif
#ifndef DBUS_TIMEOUT_INFINITE
#define DBUS_TIMEOUT_INFINITE ((int) 0x7fffffff)
#endif
#ifndef DBUS_TYPE_UNIX_FD
#define DBUS_TYPE_UNIX_FD ((int) 'h')
#endif
typedef struct SDL_DBusContext
{
DBusConnection *session_conn;
DBusConnection *system_conn;
DBusConnection *(*bus_get_private)(DBusBusType, DBusError *);
dbus_bool_t (*bus_register)(DBusConnection *, DBusError *);
void (*bus_add_match)(DBusConnection *, const char *, DBusError *);
void (*bus_remove_match)(DBusConnection *, const char *, DBusError *);
const char *(*bus_get_unique_name)(DBusConnection *);
DBusConnection *(*connection_open_private)(const char *, DBusError *);
void (*connection_set_exit_on_disconnect)(DBusConnection *, dbus_bool_t);
dbus_bool_t (*connection_get_is_connected)(DBusConnection *);
dbus_bool_t (*connection_add_filter)(DBusConnection *, DBusHandleMessageFunction, void *, DBusFreeFunction);
dbus_bool_t (*connection_remove_filter)(DBusConnection *, DBusHandleMessageFunction, void *);
dbus_bool_t (*connection_try_register_object_path)(DBusConnection *, const char *,
const DBusObjectPathVTable *, void *, DBusError *);
dbus_bool_t (*connection_send)(DBusConnection *, DBusMessage *, dbus_uint32_t *);
DBusMessage *(*connection_send_with_reply_and_block)(DBusConnection *, DBusMessage *, int, DBusError *);
void (*connection_close)(DBusConnection *);
void (*connection_ref)(DBusConnection *);
void (*connection_unref)(DBusConnection *);
void (*connection_flush)(DBusConnection *);
dbus_bool_t (*connection_read_write)(DBusConnection *, int);
dbus_bool_t (*connection_read_write_dispatch)(DBusConnection *, int);
DBusDispatchStatus (*connection_dispatch)(DBusConnection *);
dbus_bool_t (*message_is_signal)(DBusMessage *, const char *, const char *);
dbus_bool_t (*message_has_path)(DBusMessage *, const char *);
DBusMessage *(*message_new_method_call)(const char *, const char *, const char *, const char *);
DBusMessage *(*message_new_signal)(const char *, const char *, const char *);
dbus_bool_t (*message_append_args)(DBusMessage *, int, ...);
dbus_bool_t (*message_append_args_valist)(DBusMessage *, int, va_list);
void (*message_iter_init_append)(DBusMessage *, DBusMessageIter *);
dbus_bool_t (*message_iter_open_container)(DBusMessageIter *, int, const char *, DBusMessageIter *);
dbus_bool_t (*message_iter_append_basic)(DBusMessageIter *, int, const void *);
dbus_bool_t (*message_iter_close_container)(DBusMessageIter *, DBusMessageIter *);
dbus_bool_t (*message_get_args)(DBusMessage *, DBusError *, int, ...);
dbus_bool_t (*message_get_args_valist)(DBusMessage *, DBusError *, int, va_list);
dbus_bool_t (*message_iter_init)(DBusMessage *, DBusMessageIter *);
dbus_bool_t (*message_iter_next)(DBusMessageIter *);
void (*message_iter_get_basic)(DBusMessageIter *, void *);
int (*message_iter_get_arg_type)(DBusMessageIter *);
void (*message_iter_recurse)(DBusMessageIter *, DBusMessageIter *);
void (*message_unref)(DBusMessage *);
dbus_bool_t (*threads_init_default)(void);
void (*error_init)(DBusError *);
dbus_bool_t (*error_is_set)(const DBusError *);
dbus_bool_t (*error_has_name)(const DBusError *, const char *);
void (*error_free)(DBusError *);
char *(*get_local_machine_id)(void);
char *(*try_get_local_machine_id)(DBusError *);
void (*free)(void *);
void (*free_string_array)(char **);
void (*shutdown)(void);
} SDL_DBusContext;
extern void SDL_DBus_Init(void);
extern void SDL_DBus_Quit(void);
extern SDL_DBusContext *SDL_DBus_GetContext(void);
// These use the built-in Session connection.
extern bool SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...);
extern bool SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *interface, const char *method, ...);
extern bool SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result);
// These use whatever connection you like.
extern bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...);
extern bool SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...);
extern bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result);
extern void SDL_DBus_ScreensaverTickle(void);
extern bool SDL_DBus_ScreensaverInhibit(bool inhibit);
extern void SDL_DBus_PumpEvents(void);
extern char *SDL_DBus_GetLocalMachineId(void);
extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_count);
extern int SDL_DBus_CameraPortalRequestAccess(void);
#endif // HAVE_DBUS_DBUS_H
#endif // SDL_dbus_h_
File diff suppressed because it is too large Load Diff
@@ -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_evdev_h_
#define SDL_evdev_h_
#ifdef SDL_INPUT_LINUXEV
struct input_event;
extern bool SDL_EVDEV_Init(void);
extern void SDL_EVDEV_Quit(void);
extern void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void *), void *release_callback_data,
void (*acquire_callback)(void *), void *acquire_callback_data);
extern int SDL_EVDEV_GetDeviceCount(int device_class);
extern void SDL_EVDEV_Poll(void);
extern Uint64 SDL_EVDEV_GetEventTimestamp(struct input_event *event);
#endif // SDL_INPUT_LINUXEV
#endif // SDL_evdev_h_
@@ -0,0 +1,168 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 2020 Collabora Ltd.
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_evdev_capabilities.h"
#ifdef HAVE_LINUX_INPUT_H
// missing defines in older Linux kernel headers
#ifndef BTN_TRIGGER_HAPPY
#define BTN_TRIGGER_HAPPY 0x2c0
#endif
#ifndef BTN_DPAD_UP
#define BTN_DPAD_UP 0x220
#endif
#ifndef KEY_ALS_TOGGLE
#define KEY_ALS_TOGGLE 0x230
#endif
extern int
SDL_EVDEV_GuessDeviceClass(const unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)],
const unsigned long bitmask_ev[NBITS(EV_MAX)],
const unsigned long bitmask_abs[NBITS(ABS_MAX)],
const unsigned long bitmask_key[NBITS(KEY_MAX)],
const unsigned long bitmask_rel[NBITS(REL_MAX)])
{
struct range
{
unsigned start;
unsigned end;
};
// key code ranges above BTN_MISC (start is inclusive, stop is exclusive)
static const struct range high_key_blocks[] = {
{ KEY_OK, BTN_DPAD_UP },
{ KEY_ALS_TOGGLE, BTN_TRIGGER_HAPPY }
};
int devclass = 0;
unsigned long keyboard_mask;
// If the kernel specifically says it's an accelerometer, believe it
if (test_bit(INPUT_PROP_ACCELEROMETER, bitmask_props)) {
return SDL_UDEV_DEVICE_ACCELEROMETER;
}
// We treat pointing sticks as indistinguishable from mice
if (test_bit(INPUT_PROP_POINTING_STICK, bitmask_props)) {
return SDL_UDEV_DEVICE_MOUSE;
}
// We treat buttonpads as equivalent to touchpads
if (test_bit(INPUT_PROP_TOPBUTTONPAD, bitmask_props) ||
test_bit(INPUT_PROP_BUTTONPAD, bitmask_props) ||
test_bit(INPUT_PROP_SEMI_MT, bitmask_props)) {
return SDL_UDEV_DEVICE_TOUCHPAD;
}
// X, Y, Z axes but no buttons probably means an accelerometer
if (test_bit(EV_ABS, bitmask_ev) &&
test_bit(ABS_X, bitmask_abs) &&
test_bit(ABS_Y, bitmask_abs) &&
test_bit(ABS_Z, bitmask_abs) &&
!test_bit(EV_KEY, bitmask_ev)) {
return SDL_UDEV_DEVICE_ACCELEROMETER;
}
/* RX, RY, RZ axes but no buttons probably means a gyro or
* accelerometer (we don't distinguish) */
if (test_bit(EV_ABS, bitmask_ev) &&
test_bit(ABS_RX, bitmask_abs) &&
test_bit(ABS_RY, bitmask_abs) &&
test_bit(ABS_RZ, bitmask_abs) &&
!test_bit(EV_KEY, bitmask_ev)) {
return SDL_UDEV_DEVICE_ACCELEROMETER;
}
if (test_bit(EV_ABS, bitmask_ev) &&
test_bit(ABS_X, bitmask_abs) && test_bit(ABS_Y, bitmask_abs)) {
if (test_bit(BTN_STYLUS, bitmask_key) || test_bit(BTN_TOOL_PEN, bitmask_key)) {
; // ID_INPUT_TABLET
} else if (test_bit(BTN_TOOL_FINGER, bitmask_key) && !test_bit(BTN_TOOL_PEN, bitmask_key)) {
devclass |= SDL_UDEV_DEVICE_TOUCHPAD; // ID_INPUT_TOUCHPAD
} else if (test_bit(BTN_MOUSE, bitmask_key)) {
devclass |= SDL_UDEV_DEVICE_MOUSE; // ID_INPUT_MOUSE
} else if (test_bit(BTN_TOUCH, bitmask_key)) {
/* TODO: better determining between touchscreen and multitouch touchpad,
see https://github.com/systemd/systemd/blob/master/src/udev/udev-builtin-input_id.c */
devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN; // ID_INPUT_TOUCHSCREEN
}
if (test_bit(BTN_TRIGGER, bitmask_key) ||
test_bit(BTN_A, bitmask_key) ||
test_bit(BTN_1, bitmask_key) ||
test_bit(ABS_RX, bitmask_abs) ||
test_bit(ABS_RY, bitmask_abs) ||
test_bit(ABS_RZ, bitmask_abs) ||
test_bit(ABS_THROTTLE, bitmask_abs) ||
test_bit(ABS_RUDDER, bitmask_abs) ||
test_bit(ABS_WHEEL, bitmask_abs) ||
test_bit(ABS_GAS, bitmask_abs) ||
test_bit(ABS_BRAKE, bitmask_abs)) {
devclass |= SDL_UDEV_DEVICE_JOYSTICK; // ID_INPUT_JOYSTICK
}
}
if (test_bit(EV_REL, bitmask_ev) &&
test_bit(REL_X, bitmask_rel) && test_bit(REL_Y, bitmask_rel) &&
test_bit(BTN_MOUSE, bitmask_key)) {
devclass |= SDL_UDEV_DEVICE_MOUSE; // ID_INPUT_MOUSE
}
if (test_bit(EV_KEY, bitmask_ev)) {
unsigned i;
unsigned long found = 0;
for (i = 0; i < BTN_MISC / BITS_PER_LONG; ++i) {
found |= bitmask_key[i];
}
// If there are no keys in the lower block, check the higher blocks
if (!found) {
unsigned block;
for (block = 0; block < (sizeof(high_key_blocks) / sizeof(struct range)); ++block) {
for (i = high_key_blocks[block].start; i < high_key_blocks[block].end; ++i) {
if (test_bit(i, bitmask_key)) {
found = 1;
break;
}
}
}
}
if (found > 0) {
devclass |= SDL_UDEV_DEVICE_HAS_KEYS; // ID_INPUT_KEY
}
}
/* the first 32 bits are ESC, numbers, and Q to D, so if we have all of
* those, consider it to be a fully-featured keyboard;
* do not test KEY_RESERVED, though */
keyboard_mask = 0xFFFFFFFE;
if ((bitmask_key[0] & keyboard_mask) == keyboard_mask) {
devclass |= SDL_UDEV_DEVICE_KEYBOARD; // ID_INPUT_KEYBOARD
}
return devclass;
}
#endif // HAVE_LINUX_INPUT_H
@@ -0,0 +1,76 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 2020 Collabora Ltd.
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_evdev_capabilities_h_
#define SDL_evdev_capabilities_h_
#ifdef HAVE_LINUX_INPUT_H
#include <linux/input.h>
#ifndef INPUT_PROP_SEMI_MT
#define INPUT_PROP_SEMI_MT 0x03
#endif
#ifndef INPUT_PROP_TOPBUTTONPAD
#define INPUT_PROP_TOPBUTTONPAD 0x04
#endif
#ifndef INPUT_PROP_POINTING_STICK
#define INPUT_PROP_POINTING_STICK 0x05
#endif
#ifndef INPUT_PROP_ACCELEROMETER
#define INPUT_PROP_ACCELEROMETER 0x06
#endif
#ifndef INPUT_PROP_MAX
#define INPUT_PROP_MAX 0x1f
#endif
// A device can be any combination of these classes
typedef enum
{
SDL_UDEV_DEVICE_UNKNOWN = 0x0000,
SDL_UDEV_DEVICE_MOUSE = 0x0001,
SDL_UDEV_DEVICE_KEYBOARD = 0x0002,
SDL_UDEV_DEVICE_JOYSTICK = 0x0004,
SDL_UDEV_DEVICE_SOUND = 0x0008,
SDL_UDEV_DEVICE_TOUCHSCREEN = 0x0010,
SDL_UDEV_DEVICE_ACCELEROMETER = 0x0020,
SDL_UDEV_DEVICE_TOUCHPAD = 0x0040,
SDL_UDEV_DEVICE_HAS_KEYS = 0x0080,
SDL_UDEV_DEVICE_VIDEO_CAPTURE = 0x0100,
} SDL_UDEV_deviceclass;
#define BITS_PER_LONG (sizeof(unsigned long) * 8)
#define NBITS(x) ((((x)-1) / BITS_PER_LONG) + 1)
#define EVDEV_OFF(x) ((x) % BITS_PER_LONG)
#define EVDEV_LONG(x) ((x) / BITS_PER_LONG)
#define test_bit(bit, array) ((array[EVDEV_LONG(bit)] >> EVDEV_OFF(bit)) & 1)
extern int SDL_EVDEV_GuessDeviceClass(const unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)],
const unsigned long bitmask_ev[NBITS(EV_MAX)],
const unsigned long bitmask_abs[NBITS(ABS_MAX)],
const unsigned long bitmask_key[NBITS(KEY_MAX)],
const unsigned long bitmask_rel[NBITS(REL_MAX)]);
#endif // HAVE_LINUX_INPUT_H
#endif // SDL_evdev_capabilities_h_
@@ -0,0 +1,997 @@
/*
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_evdev_kbd.h"
#ifdef SDL_INPUT_LINUXKD
// This logic is adapted from drivers/tty/vt/keyboard.c in the Linux kernel source
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/kd.h>
#include <linux/keyboard.h>
#include <linux/vt.h>
#include <linux/tiocl.h> // for TIOCL_GETSHIFTSTATE
#include <signal.h>
#include "../../events/SDL_events_c.h"
#include "SDL_evdev_kbd_default_accents.h"
#include "SDL_evdev_kbd_default_keymap.h"
// These are not defined in older Linux kernel headers
#ifndef K_UNICODE
#define K_UNICODE 0x03
#endif
#ifndef K_OFF
#define K_OFF 0x04
#endif
/*
* Handler Tables.
*/
#define K_HANDLERS \
k_self, k_fn, k_spec, k_pad, \
k_dead, k_cons, k_cur, k_shift, \
k_meta, k_ascii, k_lock, k_lowercase, \
k_slock, k_dead2, k_brl, k_ignore
typedef void(k_handler_fn)(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag);
static k_handler_fn K_HANDLERS;
static k_handler_fn *k_handler[16] = { K_HANDLERS };
typedef void(fn_handler_fn)(SDL_EVDEV_keyboard_state *kbd);
static void fn_enter(SDL_EVDEV_keyboard_state *kbd);
static void fn_caps_toggle(SDL_EVDEV_keyboard_state *kbd);
static void fn_caps_on(SDL_EVDEV_keyboard_state *kbd);
static void fn_num(SDL_EVDEV_keyboard_state *kbd);
static void fn_compose(SDL_EVDEV_keyboard_state *kbd);
static fn_handler_fn *fn_handler[] = {
NULL, fn_enter, NULL, NULL,
NULL, NULL, NULL, fn_caps_toggle,
fn_num, NULL, NULL, NULL,
NULL, fn_caps_on, fn_compose, NULL,
NULL, NULL, NULL, fn_num
};
/*
* Keyboard State
*/
struct SDL_EVDEV_keyboard_state
{
int console_fd;
bool muted;
int old_kbd_mode;
unsigned short **key_maps;
unsigned char shift_down[NR_SHIFT]; // shift state counters..
bool dead_key_next;
int npadch; // -1 or number assembled on pad
struct kbdiacrs *accents;
unsigned int diacr;
bool rep; // flag telling character repeat
unsigned char lockstate;
unsigned char slockstate;
unsigned char ledflagstate;
char shift_state;
char text[128];
unsigned int text_len;
void (*vt_release_callback)(void *);
void *vt_release_callback_data;
void (*vt_acquire_callback)(void *);
void *vt_acquire_callback_data;
};
#ifdef DUMP_ACCENTS
static void SDL_EVDEV_dump_accents(SDL_EVDEV_keyboard_state *kbd)
{
unsigned int i;
printf("static struct kbdiacrs default_accents = {\n");
printf(" %d,\n", kbd->accents->kb_cnt);
printf(" {\n");
for (i = 0; i < kbd->accents->kb_cnt; ++i) {
struct kbdiacr *diacr = &kbd->accents->kbdiacr[i];
printf(" { 0x%.2x, 0x%.2x, 0x%.2x },\n",
diacr->diacr, diacr->base, diacr->result);
}
while (i < 256) {
printf(" { 0x00, 0x00, 0x00 },\n");
++i;
}
printf(" }\n");
printf("};\n");
}
#endif // DUMP_ACCENTS
#ifdef DUMP_KEYMAP
static void SDL_EVDEV_dump_keymap(SDL_EVDEV_keyboard_state *kbd)
{
int i, j;
for (i = 0; i < MAX_NR_KEYMAPS; ++i) {
if (kbd->key_maps[i]) {
printf("static unsigned short default_key_map_%d[NR_KEYS] = {", i);
for (j = 0; j < NR_KEYS; ++j) {
if ((j % 8) == 0) {
printf("\n ");
}
printf("0x%.4x, ", kbd->key_maps[i][j]);
}
printf("\n};\n");
}
}
printf("\n");
printf("static unsigned short *default_key_maps[MAX_NR_KEYMAPS] = {\n");
for (i = 0; i < MAX_NR_KEYMAPS; ++i) {
if (kbd->key_maps[i]) {
printf(" default_key_map_%d,\n", i);
} else {
printf(" NULL,\n");
}
}
printf("};\n");
}
#endif // DUMP_KEYMAP
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)
{
SDL_EVDEV_keyboard_state *kbd = kbd_cleanup_state;
if (!kbd) {
return;
}
kbd_cleanup_state = NULL;
ioctl(kbd->console_fd, KDSKBMODE, kbd->old_kbd_mode);
}
static void SDL_EVDEV_kbd_reraise_signal(int sig)
{
(void)raise(sig);
}
static siginfo_t *SDL_EVDEV_kdb_cleanup_siginfo = NULL;
static 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)
*/
(void)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);
}
}
enum {
VT_SIGNAL_NONE,
VT_SIGNAL_RELEASE,
VT_SIGNAL_ACQUIRE,
};
static int vt_release_signal;
static int vt_acquire_signal;
static SDL_AtomicInt vt_signal_pending;
typedef void (*signal_handler)(int signum);
static void kbd_vt_release_signal_action(int signum)
{
SDL_SetAtomicInt(&vt_signal_pending, VT_SIGNAL_RELEASE);
}
static void kbd_vt_acquire_signal_action(int signum)
{
SDL_SetAtomicInt(&vt_signal_pending, VT_SIGNAL_ACQUIRE);
}
static bool setup_vt_signal(int signum, signal_handler handler)
{
struct sigaction *old_action_p;
struct sigaction new_action;
old_action_p = &(old_sigaction[signum]);
SDL_zero(new_action);
new_action.sa_handler = handler;
new_action.sa_flags = SA_RESTART;
if (sigaction(signum, &new_action, old_action_p) < 0) {
return false;
}
if (old_action_p->sa_handler != SIG_DFL) {
// This signal is already in use
sigaction(signum, old_action_p, NULL);
return false;
}
return true;
}
static int find_free_signal(signal_handler handler)
{
#ifdef SIGRTMIN
int i;
for (i = SIGRTMIN + 2; i <= SIGRTMAX; ++i) {
if (setup_vt_signal(i, handler)) {
return i;
}
}
#endif
if (setup_vt_signal(SIGUSR1, handler)) {
return SIGUSR1;
}
if (setup_vt_signal(SIGUSR2, handler)) {
return SIGUSR2;
}
return 0;
}
static void kbd_vt_quit(int console_fd)
{
struct vt_mode mode;
if (vt_release_signal) {
sigaction(vt_release_signal, &old_sigaction[vt_release_signal], NULL);
vt_release_signal = 0;
}
if (vt_acquire_signal) {
sigaction(vt_acquire_signal, &old_sigaction[vt_acquire_signal], NULL);
vt_acquire_signal = 0;
}
SDL_zero(mode);
mode.mode = VT_AUTO;
ioctl(console_fd, VT_SETMODE, &mode);
}
static bool kbd_vt_init(int console_fd)
{
struct vt_mode mode;
vt_release_signal = find_free_signal(kbd_vt_release_signal_action);
vt_acquire_signal = find_free_signal(kbd_vt_acquire_signal_action);
if (!vt_release_signal || !vt_acquire_signal ) {
kbd_vt_quit(console_fd);
return false;
}
SDL_zero(mode);
mode.mode = VT_PROCESS;
mode.relsig = vt_release_signal;
mode.acqsig = vt_acquire_signal;
mode.frsig = SIGIO;
if (ioctl(console_fd, VT_SETMODE, &mode) < 0) {
kbd_vt_quit(console_fd);
return false;
}
return true;
}
static void kbd_vt_update(SDL_EVDEV_keyboard_state *state)
{
int signal_pending = SDL_GetAtomicInt(&vt_signal_pending);
if (signal_pending != VT_SIGNAL_NONE) {
if (signal_pending == VT_SIGNAL_RELEASE) {
if (state->vt_release_callback) {
state->vt_release_callback(state->vt_release_callback_data);
}
ioctl(state->console_fd, VT_RELDISP, 1);
} else {
if (state->vt_acquire_callback) {
state->vt_acquire_callback(state->vt_acquire_callback_data);
}
ioctl(state->console_fd, VT_RELDISP, VT_ACKACQ);
}
SDL_CompareAndSwapAtomicInt(&vt_signal_pending, signal_pending, VT_SIGNAL_NONE);
}
}
SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void)
{
SDL_EVDEV_keyboard_state *kbd;
char flag_state;
char kbtype;
char shift_state[sizeof(long)] = { TIOCL_GETSHIFTSTATE, 0 };
kbd = (SDL_EVDEV_keyboard_state *)SDL_calloc(1, sizeof(*kbd));
if (!kbd) {
return NULL;
}
// This might fail if we're not connected to a tty (e.g. on the Steam Link)
kbd->console_fd = open("/dev/tty", O_RDONLY | O_CLOEXEC);
if (!((ioctl(kbd->console_fd, KDGKBTYPE, &kbtype) == 0) && ((kbtype == KB_101) || (kbtype == KB_84)))) {
close(kbd->console_fd);
kbd->console_fd = -1;
}
kbd->npadch = -1;
if (ioctl(kbd->console_fd, TIOCLINUX, shift_state) == 0) {
kbd->shift_state = *shift_state;
}
if (ioctl(kbd->console_fd, KDGKBLED, &flag_state) == 0) {
kbd->ledflagstate = flag_state;
}
kbd->accents = &default_accents;
kbd->key_maps = default_key_maps;
if (ioctl(kbd->console_fd, KDGKBMODE, &kbd->old_kbd_mode) == 0) {
// Set the keyboard in UNICODE mode and load the keymaps
ioctl(kbd->console_fd, KDSKBMODE, K_UNICODE);
}
kbd_vt_init(kbd->console_fd);
return kbd;
}
void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted)
{
if (!state) {
return;
}
if (muted == state->muted) {
return;
}
if (muted) {
if (SDL_GetHintBoolean(SDL_HINT_MUTE_CONSOLE_KEYBOARD, true)) {
/* Mute the keyboard so keystrokes only generate evdev events
* and do not leak through to the console
*/
ioctl(state->console_fd, KDSKBMODE, K_OFF);
/* 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(state);
}
}
} else {
kbd_unregister_emerg_cleanup();
// Restore the original keyboard mode
ioctl(state->console_fd, KDSKBMODE, state->old_kbd_mode);
}
state->muted = 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)
{
if (state == NULL) {
return;
}
state->vt_release_callback = release_callback;
state->vt_release_callback_data = release_callback_data;
state->vt_acquire_callback = acquire_callback;
state->vt_acquire_callback_data = acquire_callback_data;
}
void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state)
{
if (!state) {
return;
}
kbd_vt_update(state);
}
void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state)
{
if (state == NULL) {
return;
}
SDL_EVDEV_kbd_set_muted(state, false);
kbd_vt_quit(state->console_fd);
if (state->console_fd >= 0) {
close(state->console_fd);
state->console_fd = -1;
}
if (state->key_maps && state->key_maps != default_key_maps) {
int i;
for (i = 0; i < MAX_NR_KEYMAPS; ++i) {
if (state->key_maps[i]) {
SDL_free(state->key_maps[i]);
}
}
SDL_free(state->key_maps);
}
SDL_free(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) {
put_queue(kbd, c); /* 0******* */
} 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;
kbd->diacr = 0;
if (kbd->console_fd >= 0)
if (ioctl(kbd->console_fd, KDGKBDIACR, kbd->accents) < 0) {
// No worries, we'll use the default accent table
}
for (i = 0; i < kbd->accents->kb_cnt; i++) {
if (kbd->accents->kbdiacr[i].diacr == d &&
kbd->accents->kbdiacr[i].base == ch) {
return kbd->accents->kbdiacr[i].result;
}
}
if (ch == ' ' || ch == d) {
return d;
}
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 set_vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->ledflagstate |= flag;
ioctl(kbd->console_fd, KDSETLED, (unsigned long)(kbd->ledflagstate));
}
static void clr_vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->ledflagstate &= ~flag;
ioctl(kbd->console_fd, KDSETLED, (unsigned long)(kbd->ledflagstate));
}
static void chg_vc_kbd_lock(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->lockstate ^= 1 << flag;
}
static void chg_vc_kbd_slock(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->slockstate ^= 1 << flag;
}
static void chg_vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->ledflagstate ^= flag;
ioctl(kbd->console_fd, KDSETLED, (unsigned long)(kbd->ledflagstate));
}
/*
* Special function handlers
*/
static void fn_enter(SDL_EVDEV_keyboard_state *kbd)
{
if (kbd->diacr) {
put_utf8(kbd, kbd->diacr);
kbd->diacr = 0;
}
}
static void fn_caps_toggle(SDL_EVDEV_keyboard_state *kbd)
{
if (kbd->rep) {
return;
}
chg_vc_kbd_led(kbd, K_CAPSLOCK);
}
static void fn_caps_on(SDL_EVDEV_keyboard_state *kbd)
{
if (kbd->rep) {
return;
}
set_vc_kbd_led(kbd, K_CAPSLOCK);
}
static void fn_num(SDL_EVDEV_keyboard_state *kbd)
{
if (!kbd->rep) {
chg_vc_kbd_led(kbd, K_NUMLOCK);
}
}
static void fn_compose(SDL_EVDEV_keyboard_state *kbd)
{
kbd->dead_key_next = true;
}
/*
* Special key handlers
*/
static void k_ignore(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_spec(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
if (up_flag) {
return;
}
if (value >= SDL_arraysize(fn_handler)) {
return;
}
if (fn_handler[value]) {
fn_handler[value](kbd);
}
}
static void k_lowercase(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_self(SDL_EVDEV_keyboard_state *kbd, unsigned char 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_dead(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
const unsigned char ret_diacr[NR_DEAD] = { '`', '\'', '^', '~', '"', ',' };
k_deadunicode(kbd, ret_diacr[value], up_flag);
}
static void k_dead2(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
k_deadunicode(kbd, value, up_flag);
}
static void k_cons(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_fn(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_cur(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_pad(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
static const char pad_chars[] = "0123456789+-*/\015,.?()#";
if (up_flag) {
return; // no action, if this is a key release
}
if (!vc_kbd_led(kbd, K_NUMLOCK)) {
// unprintable action
return;
}
put_queue(kbd, pad_chars[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;
}
/*
* Mimic typewriter:
* a CapsShift key acts like Shift but undoes CapsLock
*/
if (value == KVAL(K_CAPSSHIFT)) {
value = KVAL(K_SHIFT);
if (!up_flag) {
clr_vc_kbd_led(kbd, K_CAPSLOCK);
}
}
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;
}
}
static void k_meta(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_ascii(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
int base;
if (up_flag) {
return;
}
if (value < 10) {
// decimal input of code, while Alt depressed
base = 10;
} else {
// hexadecimal input of code, while AltGr depressed
value -= 10;
base = 16;
}
if (kbd->npadch == -1) {
kbd->npadch = value;
} else {
kbd->npadch = kbd->npadch * base + value;
}
}
static void k_lock(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
if (up_flag || kbd->rep) {
return;
}
chg_vc_kbd_lock(kbd, value);
}
static void k_slock(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
k_shift(kbd, value, up_flag);
if (up_flag || kbd->rep) {
return;
}
chg_vc_kbd_slock(kbd, value);
// try to make Alt, oops, AltGr and such work
if (!kbd->key_maps[kbd->lockstate ^ kbd->slockstate]) {
kbd->slockstate = 0;
chg_vc_kbd_slock(kbd, value);
}
}
static void k_brl(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down)
{
unsigned char shift_final;
unsigned char type;
unsigned short *key_map;
unsigned short keysym;
if (!state) {
return;
}
state->rep = (down == 2);
shift_final = (state->shift_state | state->slockstate) ^ state->lockstate;
key_map = state->key_maps[shift_final];
if (!key_map) {
// Unsupported shift state (e.g. ctrl = 4, alt = 8), just reset to the default state
state->shift_state = 0;
state->slockstate = 0;
state->lockstate = 0;
return;
}
if (keycode < NR_KEYS) {
if (state->console_fd < 0) {
keysym = key_map[keycode];
} else {
struct kbentry kbe;
kbe.kb_table = shift_final;
kbe.kb_index = keycode;
if (ioctl(state->console_fd, KDGKBENT, &kbe) == 0)
keysym = (kbe.kb_value ^ 0xf000);
else
return;
}
} else {
return;
}
type = KTYP(keysym);
if (type < 0xf0) {
if (down) {
put_utf8(state, keysym);
}
} else {
type -= 0xf0;
// if type is KT_LETTER then it can be affected by Caps Lock
if (type == KT_LETTER) {
type = KT_LATIN;
if (vc_kbd_led(state, K_CAPSLOCK)) {
shift_final = shift_final ^ (1 << KG_SHIFT);
key_map = state->key_maps[shift_final];
if (key_map) {
if (state->console_fd < 0) {
keysym = key_map[keycode];
} else {
struct kbentry kbe;
kbe.kb_table = shift_final;
kbe.kb_index = keycode;
if (ioctl(state->console_fd, KDGKBENT, &kbe) == 0)
keysym = (kbe.kb_value ^ 0xf000);
}
}
}
}
(*k_handler[type])(state, keysym & 0xff, !down);
if (type != KT_SLOCK) {
state->slockstate = 0;
}
}
if (state->text_len > 0) {
state->text[state->text_len] = '\0';
SDL_SendKeyboardText(state->text);
state->text_len = 0;
}
}
#elif !defined(SDL_INPUT_FBSDKBIO) // !SDL_INPUT_LINUXKD
SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void)
{
return NULL;
}
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)
{
}
void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down)
{
}
void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state)
{
}
#endif // SDL_INPUT_LINUXKD
@@ -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_evdev_kbd_h_
#define SDL_evdev_kbd_h_
struct SDL_EVDEV_keyboard_state;
typedef struct SDL_EVDEV_keyboard_state SDL_EVDEV_keyboard_state;
extern SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void);
extern void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted);
extern 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);
extern void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state);
extern void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down);
extern void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state);
#endif // SDL_evdev_kbd_h_
@@ -0,0 +1,282 @@
/*
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.
*/
static struct kbdiacrs default_accents = {
68,
{
{ 0x60, 0x41, 0xc0 },
{ 0x60, 0x61, 0xe0 },
{ 0x27, 0x41, 0xc1 },
{ 0x27, 0x61, 0xe1 },
{ 0x5e, 0x41, 0xc2 },
{ 0x5e, 0x61, 0xe2 },
{ 0x7e, 0x41, 0xc3 },
{ 0x7e, 0x61, 0xe3 },
{ 0x22, 0x41, 0xc4 },
{ 0x22, 0x61, 0xe4 },
{ 0x4f, 0x41, 0xc5 },
{ 0x6f, 0x61, 0xe5 },
{ 0x30, 0x41, 0xc5 },
{ 0x30, 0x61, 0xe5 },
{ 0x41, 0x41, 0xc5 },
{ 0x61, 0x61, 0xe5 },
{ 0x41, 0x45, 0xc6 },
{ 0x61, 0x65, 0xe6 },
{ 0x2c, 0x43, 0xc7 },
{ 0x2c, 0x63, 0xe7 },
{ 0x60, 0x45, 0xc8 },
{ 0x60, 0x65, 0xe8 },
{ 0x27, 0x45, 0xc9 },
{ 0x27, 0x65, 0xe9 },
{ 0x5e, 0x45, 0xca },
{ 0x5e, 0x65, 0xea },
{ 0x22, 0x45, 0xcb },
{ 0x22, 0x65, 0xeb },
{ 0x60, 0x49, 0xcc },
{ 0x60, 0x69, 0xec },
{ 0x27, 0x49, 0xcd },
{ 0x27, 0x69, 0xed },
{ 0x5e, 0x49, 0xce },
{ 0x5e, 0x69, 0xee },
{ 0x22, 0x49, 0xcf },
{ 0x22, 0x69, 0xef },
{ 0x2d, 0x44, 0xd0 },
{ 0x2d, 0x64, 0xf0 },
{ 0x7e, 0x4e, 0xd1 },
{ 0x7e, 0x6e, 0xf1 },
{ 0x60, 0x4f, 0xd2 },
{ 0x60, 0x6f, 0xf2 },
{ 0x27, 0x4f, 0xd3 },
{ 0x27, 0x6f, 0xf3 },
{ 0x5e, 0x4f, 0xd4 },
{ 0x5e, 0x6f, 0xf4 },
{ 0x7e, 0x4f, 0xd5 },
{ 0x7e, 0x6f, 0xf5 },
{ 0x22, 0x4f, 0xd6 },
{ 0x22, 0x6f, 0xf6 },
{ 0x2f, 0x4f, 0xd8 },
{ 0x2f, 0x6f, 0xf8 },
{ 0x60, 0x55, 0xd9 },
{ 0x60, 0x75, 0xf9 },
{ 0x27, 0x55, 0xda },
{ 0x27, 0x75, 0xfa },
{ 0x5e, 0x55, 0xdb },
{ 0x5e, 0x75, 0xfb },
{ 0x22, 0x55, 0xdc },
{ 0x22, 0x75, 0xfc },
{ 0x27, 0x59, 0xdd },
{ 0x27, 0x79, 0xfd },
{ 0x54, 0x48, 0xde },
{ 0x74, 0x68, 0xfe },
{ 0x73, 0x73, 0xdf },
{ 0x22, 0x79, 0xff },
{ 0x73, 0x7a, 0xdf },
{ 0x69, 0x6a, 0xff },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
{ 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 },
}
};
File diff suppressed because it is too large Load Diff

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