Add SDL3 3.1.1-preview

This commit is contained in:
Mark Jansen
2024-03-30 18:51:35 +01:00
parent 2c854305d7
commit 1a1a8f6cb3
1774 changed files with 811659 additions and 0 deletions
+716
View File
@@ -0,0 +1,716 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_WIN32) || defined(SDL_PLATFORM_GDK)
#include "core/windows/SDL_windows.h"
#elif !defined(SDL_PLATFORM_WINRT)
#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 "cpuinfo/SDL_cpuinfo_c.h"
#include "video/SDL_video_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 "sensor/SDL_sensor_c.h"
#include "camera/SDL_camera_c.h"
#define SDL_INIT_EVERYTHING ~0U
/* Initialization/Cleanup routines */
#include "time/SDL_time_c.h"
#include "timer/SDL_timer_c.h"
#ifdef SDL_VIDEO_DRIVER_WINDOWS
extern int SDL_HelperWindowCreate(void);
extern int 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_PATCHLEVEL == SDL_BUILD_MICRO_VERSION);
#endif
SDL_COMPILE_TIME_ASSERT(SDL_MAJOR_VERSION_min, SDL_MAJOR_VERSION >= 0);
/* Limited only by the need to fit in SDL_Version */
SDL_COMPILE_TIME_ASSERT(SDL_MAJOR_VERSION_max, SDL_MAJOR_VERSION <= 255);
SDL_COMPILE_TIME_ASSERT(SDL_MINOR_VERSION_min, SDL_MINOR_VERSION >= 0);
/* Limited only by the need to fit in SDL_Version */
SDL_COMPILE_TIME_ASSERT(SDL_MINOR_VERSION_max, SDL_MINOR_VERSION <= 255);
SDL_COMPILE_TIME_ASSERT(SDL_PATCHLEVEL_min, SDL_PATCHLEVEL >= 0);
/* Limited by its encoding in SDL_VERSIONNUM and in the ABI versions */
SDL_COMPILE_TIME_ASSERT(SDL_PATCHLEVEL_max, SDL_PATCHLEVEL <= 99);
/* 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_WIN32) || defined(SDL_PLATFORM_GDK)
/* "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
}
/* The initialized subsystems */
#ifdef SDL_MAIN_NEEDED
static SDL_bool SDL_MainIsReady = SDL_FALSE;
#else
static SDL_bool SDL_MainIsReady = SDL_TRUE;
#endif
static SDL_bool SDL_bInMainQuit = SDL_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 SDL_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 SDL_bool SDL_ShouldQuitSubsystem(Uint32 subsystem)
{
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
if ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 0)) {
return SDL_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 SDL_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 SDL_FALSE;
}
if (SDL_SubsystemRefCount[subsystem_index] > 0) {
++SDL_SubsystemRefCount[subsystem_index];
return SDL_TRUE;
}
return SDL_InitSubSystem(subsystem) == 0;
}
void SDL_SetMainReady(void)
{
SDL_MainIsReady = SDL_TRUE;
}
int SDL_InitSubSystem(Uint32 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_InitLog();
SDL_InitProperties();
SDL_GetGlobalProperties();
/* Clear the error message */
SDL_ClearError();
#ifdef SDL_USE_LIBDBUS
SDL_DBus_Init();
#endif
#ifdef SDL_VIDEO_DRIVER_WINDOWS
if (flags & (SDL_INIT_HAPTIC | SDL_INIT_JOYSTICK)) {
if (SDL_HelperWindowCreate() < 0) {
goto quit_and_error;
}
}
#endif
SDL_InitTime();
SDL_InitTicks();
/* Initialize the event subsystem */
if (flags & SDL_INIT_EVENTS) {
if (SDL_ShouldInitSubsystem(SDL_INIT_EVENTS)) {
SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS);
if (SDL_InitEvents() < 0) {
SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS);
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS);
}
flags_initialized |= SDL_INIT_EVENTS;
}
/* Initialize the timer subsystem */
if (flags & SDL_INIT_TIMER) {
if (SDL_ShouldInitSubsystem(SDL_INIT_TIMER)) {
SDL_IncrementSubsystemRefCount(SDL_INIT_TIMER);
if (SDL_InitTimers() < 0) {
SDL_DecrementSubsystemRefCount(SDL_INIT_TIMER);
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_TIMER);
}
flags_initialized |= SDL_INIT_TIMER;
}
/* 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;
}
SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO);
if (SDL_VideoInit(NULL) < 0) {
SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);
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) < 0) {
SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO);
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() < 0) {
SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK);
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() < 0) {
SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD);
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() < 0) {
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() < 0) {
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) < 0) {
SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
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 0;
quit_and_error:
SDL_QuitSubSystem(flags_initialized);
return -1;
}
int SDL_Init(Uint32 flags)
{
return SDL_InitSubSystem(flags);
}
void SDL_QuitSubSystem(Uint32 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_VideoQuit();
/* video implies events */
SDL_QuitSubSystem(SDL_INIT_EVENTS);
}
SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);
}
#endif
if (flags & SDL_INIT_TIMER) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_TIMER)) {
SDL_QuitTimers();
}
SDL_DecrementSubsystemRefCount(SDL_INIT_TIMER);
}
if (flags & SDL_INIT_EVENTS) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_EVENTS)) {
SDL_QuitEvents();
}
SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS);
}
}
Uint32 SDL_WasInit(Uint32 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 = SDL_TRUE;
/* Quit all subsystems */
#ifdef SDL_VIDEO_DRIVER_WINDOWS
SDL_HelperWindowDestroy();
#endif
SDL_QuitSubSystem(SDL_INIT_EVERYTHING);
SDL_QuitTicks();
SDL_QuitTime();
#ifdef SDL_USE_LIBDBUS
SDL_DBus_Quit();
#endif
SDL_ClearHints();
SDL_AssertionsQuit();
SDL_QuitCPUInfo();
SDL_QuitProperties();
SDL_QuitLog();
/* 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_CleanupTLS();
SDL_bInMainQuit = SDL_FALSE;
}
/* Assume we can wrap SDL_AtomicInt values and cast to Uint32 */
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;
}
/* Get the library version number */
int SDL_GetVersion(SDL_Version *ver)
{
static SDL_bool check_hint = SDL_TRUE;
static SDL_bool legacy_version = SDL_FALSE;
if (!ver) {
return SDL_InvalidParamError("ver");
}
SDL_VERSION(ver);
if (check_hint) {
check_hint = SDL_FALSE;
legacy_version = SDL_GetHintBoolean("SDL_LEGACY_VERSION", SDL_FALSE);
}
if (legacy_version) {
/* Prior to SDL 2.24.0, the patch version was incremented with every release */
ver->patch = ver->minor;
ver->minor = 0;
}
return 0;
}
/* 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_AIX)
return "AIX";
#elif defined(SDL_PLATFORM_ANDROID)
return "Android";
#elif defined(SDL_PLATFORM_BSDI)
return "BSDI";
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
return "Emscripten";
#elif defined(SDL_PLATFORM_FREEBSD)
return "FreeBSD";
#elif defined(SDL_PLATFORM_HAIKU)
return "Haiku";
#elif defined(SDL_PLATFORM_HPUX)
return "HP-UX";
#elif defined(SDL_PLATFORM_IRIX)
return "Irix";
#elif defined(SDL_PLATFORM_LINUX)
return "Linux";
#elif defined(__MINT__)
return "Atari MiNT";
#elif defined(SDL_PLATFORM_MACOS)
return "macOS";
#elif defined(SDL_PLATFORM_NETBSD)
return "NetBSD";
#elif defined(SDL_PLATFORM_OPENBSD)
return "OpenBSD";
#elif defined(SDL_PLATFORM_OS2)
return "OS/2";
#elif defined(SDL_PLATFORM_OSF)
return "OSF/1";
#elif defined(SDL_PLATFORM_QNXNTO)
return "QNX Neutrino";
#elif defined(SDL_PLATFORM_RISCOS)
return "RISC OS";
#elif defined(SDL_PLATFORM_SOLARIS)
return "Solaris";
#elif defined(SDL_PLATFORM_WIN32)
return "Windows";
#elif defined(SDL_PLATFORM_WINRT)
return "WinRT";
#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_NGAGE)
return "Nokia N-Gage";
#elif defined(SDL_PLATFORM_3DS)
return "Nintendo 3DS";
#elif defined(__managarm__)
return "Managarm";
#else
return "Unknown (see SDL_platform.h)";
#endif
}
SDL_bool SDL_IsTablet(void)
{
#ifdef SDL_PLATFORM_ANDROID
extern SDL_bool SDL_IsAndroidTablet(void);
return SDL_IsAndroidTablet();
#elif defined(SDL_PLATFORM_IOS)
extern SDL_bool SDL_IsIPad(void);
return SDL_IsIPad();
#else
return SDL_FALSE;
#endif
}
#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) || defined(SDL_PLATFORM_GDK) */
+453
View File
@@ -0,0 +1,453 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_WIN32) || defined(SDL_PLATFORM_GDK)
#include "core/windows/SDL_windows.h"
#endif
#include "SDL_assert_c.h"
#include "video/SDL_sysvideo.h"
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_GDK)
#ifndef WS_OVERLAPPEDWINDOW
#define WS_OVERLAPPEDWINDOW 0
#endif
#endif
#ifdef SDL_PLATFORM_EMSCRIPTEN
#include <emscripten.h>
/* older Emscriptens don't have this, but we need to for wasm64 compatibility. */
#ifndef MAIN_THREAD_EM_ASM_PTR
#ifdef __wasm64__
#error You need to upgrade your Emscripten compiler to support wasm64
#else
#define MAIN_THREAD_EM_ASM_PTR MAIN_THREAD_EM_ASM_INT
#endif
#endif
#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_WIN32) || defined(SDL_PLATFORM_GDK)
#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)
{
const char *envr;
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_overflow(len, 1, &buf_len) == 0) {
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. */
envr = SDL_getenv("SDL_ASSERT");
if (envr) {
if (message != stack_buf) {
SDL_free(message);
}
if (SDL_strcmp(envr, "abort") == 0) {
return SDL_ASSERTION_ABORT;
} else if (SDL_strcmp(envr, "break") == 0) {
return SDL_ASSERTION_BREAK;
} else if (SDL_strcmp(envr, "retry") == 0) {
return SDL_ASSERTION_RETRY;
} else if (SDL_strcmp(envr, "ignore") == 0) {
return SDL_ASSERTION_IGNORE;
} else if (SDL_strcmp(envr, "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) == 0) {
if (selected == -1) {
state = SDL_ASSERTION_IGNORE;
} else {
state = (SDL_AssertState)selected;
}
} else {
#ifdef SDL_PLATFORM_EMSCRIPTEN
/* This is nasty, but we can't block on a custom UI. */
for (;;) {
SDL_bool okay = SDL_TRUE;
/* *INDENT-OFF* */ /* clang-format off */
char *buf = (char *) MAIN_THREAD_EM_ASM_PTR({
var str =
UTF8ToString($0) + '\n\n' +
'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
var reply = window.prompt(str, "i");
if (reply === null) {
reply = "i";
}
return allocate(intArrayFromString(reply), 'i8', ALLOC_NORMAL);
}, message);
/* *INDENT-ON* */ /* clang-format on */
if (SDL_strcmp(buf, "a") == 0) {
state = SDL_ASSERTION_ABORT;
#if 0 /* (currently) no break functionality on Emscripten */
} else if (SDL_strcmp(buf, "b") == 0) {
state = SDL_ASSERTION_BREAK;
#endif
} else if (SDL_strcmp(buf, "r") == 0) {
state = SDL_ASSERTION_RETRY;
} else if (SDL_strcmp(buf, "i") == 0) {
state = SDL_ASSERTION_IGNORE;
} else if (SDL_strcmp(buf, "A") == 0) {
state = SDL_ASSERTION_ALWAYS_IGNORE;
} else {
okay = SDL_FALSE;
}
free(buf); /* This should NOT be SDL_free() */
if (okay) {
break;
}
}
#elif defined(HAVE_STDIO_H)
/* 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;
}
}
#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 = 1;
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 = SDL_FALSE;
item->trigger_count = 0;
item->next = NULL;
}
triggered_assertions = NULL;
}
SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
{
return SDL_PromptAssertion;
}
SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata)
{
if (userdata) {
*userdata = assertion_userdata;
}
return assertion_handler;
}
+28
View File
@@ -0,0 +1,28 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_assert_c_h_
#define SDL_assert_c_h_
extern void SDL_AssertionsQuit(void);
#endif /* SDL_assert_c_h_ */
+108
View File
@@ -0,0 +1,108 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/* Simple error handling in SDL */
#include "SDL_error_c.h"
int SDL_SetError(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
/* Ignore call if invalid format pointer was passed */
if (fmt) {
va_list ap;
int result;
SDL_error *error = SDL_GetErrBuf(SDL_TRUE);
error->error = SDL_ErrorCodeGeneric;
va_start(ap, fmt);
result = SDL_vsnprintf(error->str, error->len, fmt, ap);
va_end(ap);
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_start(ap, fmt);
(void)SDL_vsnprintf(error->str, error->len, fmt, ap);
va_end(ap);
}
}
if (SDL_LogGetPriority(SDL_LOG_CATEGORY_ERROR) <= SDL_LOG_PRIORITY_DEBUG) {
/* If we are in debug mode, print out the error message */
SDL_LogDebug(SDL_LOG_CATEGORY_ERROR, "%s", error->str);
}
}
return -1;
}
/* Available for backwards compatibility */
const char *SDL_GetError(void)
{
const SDL_error *error = SDL_GetErrBuf(SDL_FALSE);
if (!error) {
return "";
}
switch (error->error) {
case SDL_ErrorCodeGeneric:
return error->str;
case SDL_ErrorCodeOutOfMemory:
return "Out of memory";
default:
return "";
}
}
void SDL_ClearError(void)
{
SDL_error *error = SDL_GetErrBuf(SDL_FALSE);
if (error) {
error->error = SDL_ErrorCodeNone;
}
}
/* Very common errors go here */
int SDL_Error(SDL_errorcode code)
{
switch (code) {
case SDL_ENOMEM:
SDL_GetErrBuf(SDL_TRUE)->error = SDL_ErrorCodeOutOfMemory;
return -1;
case SDL_EFREAD:
return SDL_SetError("Error reading from datastream");
case SDL_EFWRITE:
return SDL_SetError("Error writing to datastream");
case SDL_EFSEEK:
return SDL_SetError("Error seeking in datastream");
case SDL_UNSUPPORTED:
return SDL_SetError("That operation is not supported");
default:
return SDL_SetError("Unknown SDL error");
}
}
+49
View File
@@ -0,0 +1,49 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/* This file defines a structure that carries language-independent
error messages
*/
#ifndef SDL_error_c_h_
#define SDL_error_c_h_
typedef enum
{
SDL_ErrorCodeNone,
SDL_ErrorCodeGeneric,
SDL_ErrorCodeOutOfMemory,
} SDL_ErrorCode;
typedef struct SDL_error
{
SDL_ErrorCode error;
char *str;
size_t len;
SDL_realloc_func realloc_func;
SDL_free_func free_func;
} SDL_error;
/* Defined in SDL_thread.c */
extern SDL_error *SDL_GetErrBuf(SDL_bool create);
#endif /* SDL_error_c_h_ */
+92
View File
@@ -0,0 +1,92 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 */
int SDL_GUIDToString(SDL_GUID guid, char *pszGUID, int cbGUID)
{
static const char k_rgchHexToASCII[] = "0123456789abcdef";
int i;
if (!pszGUID) {
return SDL_InvalidParamError("pszGUID");
}
if (cbGUID <= 0) {
return SDL_InvalidParamError("cbGUID");
}
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';
return 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_GUIDFromString(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;
}
+273
View File
@@ -0,0 +1,273 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_hashtable.h"
typedef struct SDL_HashItem
{
const void *key;
const void *value;
struct SDL_HashItem *next;
} SDL_HashItem;
struct SDL_HashTable
{
SDL_HashItem **table;
Uint32 table_len;
SDL_bool stackable;
void *data;
SDL_HashTable_HashFn hash;
SDL_HashTable_KeyMatchFn keymatch;
SDL_HashTable_NukeFn nuke;
};
SDL_HashTable *SDL_CreateHashTable(void *data, const Uint32 num_buckets, const SDL_HashTable_HashFn hashfn,
const SDL_HashTable_KeyMatchFn keymatchfn,
const SDL_HashTable_NukeFn nukefn,
const SDL_bool stackable)
{
SDL_HashTable *table;
// num_buckets must be a power of two so we get a solid block of bits to mask hash values against.
if ((num_buckets == 0) || ((num_buckets & (num_buckets - 1)) != 0)) {
SDL_SetError("num_buckets must be a power of two");
return NULL;
}
table = (SDL_HashTable *) SDL_calloc(1, sizeof (SDL_HashTable));
if (!table) {
return NULL;
}
table->table = (SDL_HashItem **) SDL_calloc(num_buckets, sizeof (SDL_HashItem *));
if (!table->table) {
SDL_free(table);
return NULL;
}
table->table_len = num_buckets;
table->stackable = stackable;
table->data = data;
table->hash = hashfn;
table->keymatch = keymatchfn;
table->nuke = nukefn;
return table;
}
static SDL_INLINE Uint32 calc_hash(const SDL_HashTable *table, const void *key)
{
return table->hash(key, table->data) & (table->table_len - 1);
}
SDL_bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value)
{
SDL_HashItem *item;
const Uint32 hash = calc_hash(table, key);
if ( (!table->stackable) && (SDL_FindInHashTable(table, key, NULL)) ) {
return SDL_FALSE;
}
// !!! FIXME: grow and rehash table if it gets too saturated.
item = (SDL_HashItem *) SDL_malloc(sizeof (SDL_HashItem));
if (!item) {
return SDL_FALSE;
}
item->key = key;
item->value = value;
item->next = table->table[hash];
table->table[hash] = item;
return SDL_TRUE;
}
SDL_bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **_value)
{
const Uint32 hash = calc_hash(table, key);
void *data = table->data;
SDL_HashItem *i;
for (i = table->table[hash]; i; i = i->next) {
if (table->keymatch(key, i->key, data)) {
if (_value) {
*_value = i->value;
}
return SDL_TRUE;
}
}
return SDL_FALSE;
}
SDL_bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key)
{
const Uint32 hash = calc_hash(table, key);
SDL_HashItem *item = NULL;
SDL_HashItem *prev = NULL;
void *data = table->data;
for (item = table->table[hash]; item; item = item->next) {
if (table->keymatch(key, item->key, data)) {
if (prev) {
prev->next = item->next;
} else {
table->table[hash] = item->next;
}
table->nuke(item->key, item->value, data);
SDL_free(item);
return SDL_TRUE;
}
prev = item;
}
return SDL_FALSE;
}
SDL_bool SDL_IterateHashTableKey(const SDL_HashTable *table, const void *key, const void **_value, void **iter)
{
SDL_HashItem *item = *iter ? ((SDL_HashItem *) *iter)->next : table->table[calc_hash(table, key)];
while (item) {
if (table->keymatch(key, item->key, table->data)) {
*_value = item->value;
*iter = item;
return SDL_TRUE;
}
item = item->next;
}
// no more matches.
*_value = NULL;
*iter = NULL;
return SDL_FALSE;
}
SDL_bool SDL_IterateHashTable(const SDL_HashTable *table, const void **_key, const void **_value, void **iter)
{
SDL_HashItem *item = (SDL_HashItem *) *iter;
Uint32 idx = 0;
if (item) {
const SDL_HashItem *orig = item;
item = item->next;
if (!item) {
idx = calc_hash(table, orig->key) + 1; // !!! FIXME: we probably shouldn't rehash each time.
}
}
while (!item && (idx < table->table_len)) {
item = table->table[idx++]; // skip empty buckets...
}
if (!item) { // no more matches?
*_key = NULL;
*iter = NULL;
return SDL_FALSE;
}
*_key = item->key;
*_value = item->value;
*iter = item;
return SDL_TRUE;
}
SDL_bool SDL_HashTableEmpty(SDL_HashTable *table)
{
if (table) {
Uint32 i;
for (i = 0; i < table->table_len; i++) {
SDL_HashItem *item = table->table[i];
if (item) {
return SDL_FALSE;
}
}
}
return SDL_TRUE;
}
void SDL_DestroyHashTable(SDL_HashTable *table)
{
if (table) {
void *data = table->data;
Uint32 i;
for (i = 0; i < table->table_len; i++) {
SDL_HashItem *item = table->table[i];
while (item) {
SDL_HashItem *next = item->next;
table->nuke(item->key, item->value, data);
SDL_free(item);
item = next;
}
}
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_HashString(const void *key, void *data)
{
const char *str = (const char *)key;
return hash_string_djbxor(str, SDL_strlen(str));
}
SDL_bool SDL_KeyMatchString(const void *a, const void *b, void *data)
{
if (a == b) {
return SDL_TRUE; // same pointer, must match.
} else if (!a || !b) {
return SDL_FALSE; // one pointer is NULL (and first test shows they aren't the same pointer), must not match.
}
return (SDL_strcmp((const char *)a, (const char *)b) == 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(const void *key, void *unused)
{
return (Uint32)(uintptr_t)key;
}
SDL_bool SDL_KeyMatchID(const void *a, const void *b, void *unused)
{
if (a == b) {
return SDL_TRUE;
}
return SDL_FALSE;
}
+57
View File
@@ -0,0 +1,57 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_hashtable_h_
#define SDL_hashtable_h_
/* this is not (currently) a public API. But maybe it should be! */
struct SDL_HashTable;
typedef struct SDL_HashTable SDL_HashTable;
typedef Uint32 (*SDL_HashTable_HashFn)(const void *key, void *data);
typedef SDL_bool (*SDL_HashTable_KeyMatchFn)(const void *a, const void *b, void *data);
typedef void (*SDL_HashTable_NukeFn)(const void *key, const void *value, void *data);
SDL_HashTable *SDL_CreateHashTable(void *data,
const Uint32 num_buckets,
const SDL_HashTable_HashFn hashfn,
const SDL_HashTable_KeyMatchFn keymatchfn,
const SDL_HashTable_NukeFn nukefn,
const SDL_bool stackable);
void SDL_DestroyHashTable(SDL_HashTable *table);
SDL_bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value);
SDL_bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key);
SDL_bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **_value);
SDL_bool SDL_HashTableEmpty(SDL_HashTable *table);
// iterate all values for a specific key. This only makes sense if the hash is stackable. If not-stackable, just use SDL_FindInHashTable().
SDL_bool SDL_IterateHashTableKey(const SDL_HashTable *table, const void *key, const void **_value, void **iter);
// iterate all key/value pairs in a hash (stackable hashes can have duplicate keys with multiple values).
SDL_bool SDL_IterateHashTable(const SDL_HashTable *table, const void **_key, const void **_value, void **iter);
Uint32 SDL_HashString(const void *key, void *unused);
SDL_bool SDL_KeyMatchString(const void *a, const void *b, void *unused);
Uint32 SDL_HashID(const void *key, void *unused);
SDL_bool SDL_KeyMatchID(const void *a, const void *b, void *unused);
#endif /* SDL_hashtable_h_ */
+312
View File
@@ -0,0 +1,312 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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"
/* Assuming there aren't many hints set and they aren't being queried in
critical performance paths, we'll just use linked lists here.
*/
typedef struct SDL_HintWatch
{
SDL_HintCallback callback;
void *userdata;
struct SDL_HintWatch *next;
} SDL_HintWatch;
typedef struct SDL_Hint
{
char *name;
char *value;
SDL_HintPriority priority;
SDL_HintWatch *callbacks;
struct SDL_Hint *next;
} SDL_Hint;
static SDL_Hint *SDL_hints;
SDL_bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriority priority)
{
const char *env;
SDL_Hint *hint;
SDL_HintWatch *entry;
if (!name) {
return SDL_FALSE;
}
env = SDL_getenv(name);
if (env && priority < SDL_HINT_OVERRIDE) {
return SDL_FALSE;
}
for (hint = SDL_hints; hint; hint = hint->next) {
if (SDL_strcmp(name, hint->name) == 0) {
if (priority < hint->priority) {
return SDL_FALSE;
}
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;
for (entry = hint->callbacks; 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;
}
if (old_value) {
SDL_free(old_value);
}
}
hint->priority = priority;
return SDL_TRUE;
}
}
/* Couldn't find the hint, add a new one */
hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
if (!hint) {
return SDL_FALSE;
}
hint->name = SDL_strdup(name);
hint->value = value ? SDL_strdup(value) : NULL;
hint->priority = priority;
hint->callbacks = NULL;
hint->next = SDL_hints;
SDL_hints = hint;
return SDL_TRUE;
}
SDL_bool SDL_ResetHint(const char *name)
{
const char *env;
SDL_Hint *hint;
SDL_HintWatch *entry;
if (!name) {
return SDL_FALSE;
}
env = SDL_getenv(name);
for (hint = SDL_hints; hint; hint = hint->next) {
if (SDL_strcmp(name, hint->name) == 0) {
if ((!env && hint->value) ||
(env && !hint->value) ||
(env && SDL_strcmp(env, hint->value) != 0)) {
for (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;
return SDL_TRUE;
}
}
return SDL_FALSE;
}
void SDL_ResetHints(void)
{
const char *env;
SDL_Hint *hint;
SDL_HintWatch *entry;
for (hint = SDL_hints; hint; hint = hint->next) {
env = SDL_getenv(hint->name);
if ((!env && hint->value) ||
(env && !hint->value) ||
(env && SDL_strcmp(env, hint->value) != 0)) {
for (entry = hint->callbacks; entry;) {
/* Save the next entry in case this one is deleted */
SDL_HintWatch *next = entry->next;
entry->callback(entry->userdata, hint->name, hint->value, env);
entry = next;
}
}
SDL_free(hint->value);
hint->value = NULL;
hint->priority = SDL_HINT_DEFAULT;
}
}
SDL_bool SDL_SetHint(const char *name, const char *value)
{
return SDL_SetHintWithPriority(name, value, SDL_HINT_NORMAL);
}
const char *SDL_GetHint(const char *name)
{
const char *env;
SDL_Hint *hint;
env = SDL_getenv(name);
for (hint = SDL_hints; hint; hint = hint->next) {
if (SDL_strcmp(name, hint->name) == 0) {
if (!env || hint->priority == SDL_HINT_OVERRIDE) {
return hint->value;
}
break;
}
}
return env;
}
int SDL_GetStringInteger(const char *value, int default_value)
{
if (!value || !*value) {
return default_value;
}
if (*value == '0' || SDL_strcasecmp(value, "false") == 0) {
return 0;
}
if (*value == '1' || SDL_strcasecmp(value, "true") == 0) {
return 1;
}
if (*value == '-' || SDL_isdigit(*value)) {
return SDL_atoi(value);
}
return default_value;
}
SDL_bool SDL_GetStringBoolean(const char *value, SDL_bool default_value)
{
if (!value || !*value) {
return default_value;
}
if (*value == '0' || SDL_strcasecmp(value, "false") == 0) {
return SDL_FALSE;
}
return SDL_TRUE;
}
SDL_bool SDL_GetHintBoolean(const char *name, SDL_bool default_value)
{
const char *hint = SDL_GetHint(name);
return SDL_GetStringBoolean(hint, default_value);
}
int SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
{
SDL_Hint *hint;
SDL_HintWatch *entry;
const char *value;
if (!name || !*name) {
return SDL_InvalidParamError("name");
}
if (!callback) {
return SDL_InvalidParamError("callback");
}
SDL_DelHintCallback(name, callback, userdata);
entry = (SDL_HintWatch *)SDL_malloc(sizeof(*entry));
if (!entry) {
return -1;
}
entry->callback = callback;
entry->userdata = userdata;
for (hint = SDL_hints; hint; hint = hint->next) {
if (SDL_strcmp(name, hint->name) == 0) {
break;
}
}
if (!hint) {
/* Need to add a hint entry for this watcher */
hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
if (!hint) {
SDL_free(entry);
return -1;
}
hint->name = SDL_strdup(name);
if (!hint->name) {
SDL_free(entry);
SDL_free(hint);
return -1;
}
hint->value = NULL;
hint->priority = SDL_HINT_DEFAULT;
hint->callbacks = NULL;
hint->next = SDL_hints;
SDL_hints = hint;
}
/* Add it to the callbacks for this hint */
entry->next = hint->callbacks;
hint->callbacks = entry;
/* Now call it with the current value */
value = SDL_GetHint(name);
callback(userdata, name, value, value);
return 0;
}
void SDL_DelHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
{
SDL_Hint *hint;
SDL_HintWatch *entry, *prev;
for (hint = SDL_hints; hint; hint = hint->next) {
if (SDL_strcmp(name, hint->name) == 0) {
prev = NULL;
for (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;
}
return;
}
}
}
void SDL_ClearHints(void)
{
SDL_Hint *hint;
SDL_HintWatch *entry;
while (SDL_hints) {
hint = SDL_hints;
SDL_hints = hint->next;
SDL_free(hint->name);
SDL_free(hint->value);
for (entry = hint->callbacks; entry;) {
SDL_HintWatch *freeable = entry;
entry = entry->next;
SDL_free(freeable);
}
SDL_free(hint);
}
}
+32
View File
@@ -0,0 +1,32 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/* This file defines useful function for working with SDL hints */
#ifndef SDL_hints_c_h_
#define SDL_hints_c_h_
extern SDL_bool SDL_GetStringBoolean(const char *value, SDL_bool default_value);
extern int SDL_GetStringInteger(const char *value, int default_value);
extern void SDL_ClearHints(void);
#endif /* SDL_hints_c_h_ */
+292
View File
@@ -0,0 +1,292 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_internal_h_
#define SDL_internal_h_
/* Many of SDL's features require _GNU_SOURCE on various platforms */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
/* Need this so Linux systems define fseek64o, ftell64o and off64_t */
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE 1
#endif
/* This is for a variable-length array at the end of a struct:
struct x { int y; char z[SDL_VARIABLE_LENGTH_ARRAY]; };
Use this because GCC 2 needs different magic than other compilers. */
#if (defined(__GNUC__) && (__GNUC__ <= 2)) || defined(__CC_ARM) || defined(__cplusplus)
#define SDL_VARIABLE_LENGTH_ARRAY 1
#else
#define SDL_VARIABLE_LENGTH_ARRAY
#endif
#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) || defined(__clang__)
#define HAVE_GCC_DIAGNOSTIC_PRAGMA 1
#endif
#ifdef _MSC_VER /* We use constant comparison for generated code */
#pragma warning(disable : 6326)
#endif
#ifdef _MSC_VER /* SDL_MAX_SMALL_ALLOC_STACKSIZE is smaller than _ALLOCA_S_THRESHOLD and should be generally safe */
#pragma warning(disable : 6255)
#endif
#define SDL_MAX_SMALL_ALLOC_STACKSIZE 128
#define SDL_small_alloc(type, count, pisstack) ((*(pisstack) = ((sizeof(type) * (count)) < SDL_MAX_SMALL_ALLOC_STACKSIZE)), (*(pisstack) ? SDL_stack_alloc(type, count) : (type *)SDL_malloc(sizeof(type) * (count))))
#define SDL_small_free(ptr, isstack) \
if ((isstack)) { \
SDL_stack_free(ptr); \
} else { \
SDL_free(ptr); \
}
#include "build_config/SDL_build_config.h"
#include "dynapi/SDL_dynapi.h"
#if SDL_DYNAMIC_API
#include "dynapi/SDL_dynapi_overrides.h"
/* force DECLSPEC off...it's all internal symbols now.
These will have actual #defines during SDL_dynapi.c only */
#ifdef DECLSPEC
#undef DECLSPEC
#endif
#define DECLSPEC
#endif
#ifdef SDL_PLATFORM_APPLE
#ifndef _DARWIN_C_SOURCE
#define _DARWIN_C_SOURCE 1 /* for memset_pattern4() */
#endif
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#elif defined(HAVE_MALLOC_H)
#include <malloc.h>
#endif
#ifdef HAVE_STDDEF_H
#include <stddef.h>
#endif
#ifdef HAVE_STDARG_H
#include <stdarg.h>
#endif
#ifdef HAVE_STRING_H
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <string.h>
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#elif defined(HAVE_STDINT_H)
#include <stdint.h>
#endif
#ifdef HAVE_MATH_H
#include <math.h>
#endif
#ifdef HAVE_FLOAT_H
#include <float.h>
#endif
/* If you run into a warning that O_CLOEXEC is redefined, update the SDL configuration header for your platform to add HAVE_O_CLOEXEC */
#ifndef HAVE_O_CLOEXEC
#define O_CLOEXEC 0
#endif
/* A few #defines to reduce SDL footprint.
Only effective when library is statically linked.
You have to manually edit this file. */
#ifndef SDL_LEAN_AND_MEAN
#define SDL_LEAN_AND_MEAN 0
#endif
/* Optimized functions from 'SDL_blit_0.c'
- blit with source bits_per_pixel < 8, palette */
#ifndef SDL_HAVE_BLIT_0
#define SDL_HAVE_BLIT_0 !SDL_LEAN_AND_MEAN
#endif
/* Optimized functions from 'SDL_blit_1.c'
- blit with source bytes_per_pixel == 1, palette */
#ifndef SDL_HAVE_BLIT_1
#define SDL_HAVE_BLIT_1 !SDL_LEAN_AND_MEAN
#endif
/* Optimized functions from 'SDL_blit_A.c'
- blit with 'SDL_BLENDMODE_BLEND' blending mode */
#ifndef SDL_HAVE_BLIT_A
#define SDL_HAVE_BLIT_A !SDL_LEAN_AND_MEAN
#endif
/* Optimized functions from 'SDL_blit_N.c'
- blit with COLORKEY mode, or nothing */
#ifndef SDL_HAVE_BLIT_N
#define SDL_HAVE_BLIT_N !SDL_LEAN_AND_MEAN
#endif
/* Optimized functions from 'SDL_blit_N.c'
- RGB565 conversion with Lookup tables */
#ifndef SDL_HAVE_BLIT_N_RGB565
#define SDL_HAVE_BLIT_N_RGB565 !SDL_LEAN_AND_MEAN
#endif
/* Optimized functions from 'SDL_blit_AUTO.c'
- blit with modulate color, modulate alpha, any blending mode
- scaling or not */
#ifndef SDL_HAVE_BLIT_AUTO
#define SDL_HAVE_BLIT_AUTO !SDL_LEAN_AND_MEAN
#endif
/* Run-Length-Encoding
- SDL_SetSurfaceColorKey() called with SDL_RLEACCEL flag */
#ifndef SDL_HAVE_RLE
#define SDL_HAVE_RLE !SDL_LEAN_AND_MEAN
#endif
/* Software SDL_Renderer
- creation of software renderer
- *not* general blitting functions
- {blend,draw}{fillrect,line,point} internal functions */
#ifndef SDL_VIDEO_RENDER_SW
#define SDL_VIDEO_RENDER_SW !SDL_LEAN_AND_MEAN
#endif
/* YUV formats
- handling of YUV surfaces
- blitting and conversion functions */
#ifndef SDL_HAVE_YUV
#define SDL_HAVE_YUV !SDL_LEAN_AND_MEAN
#endif
#ifndef SDL_RENDER_DISABLED
/* define the not defined ones as 0 */
#ifndef SDL_VIDEO_RENDER_D3D
#define SDL_VIDEO_RENDER_D3D 0
#endif
#ifndef SDL_VIDEO_RENDER_D3D11
#define SDL_VIDEO_RENDER_D3D11 0
#endif
#ifndef SDL_VIDEO_RENDER_D3D12
#define SDL_VIDEO_RENDER_D3D12 0
#endif
#ifndef SDL_VIDEO_RENDER_METAL
#define SDL_VIDEO_RENDER_METAL 0
#endif
#ifndef SDL_VIDEO_RENDER_OGL
#define SDL_VIDEO_RENDER_OGL 0
#endif
#ifndef SDL_VIDEO_RENDER_OGL_ES2
#define SDL_VIDEO_RENDER_OGL_ES2 0
#endif
#ifndef SDL_VIDEO_RENDER_PS2
#define SDL_VIDEO_RENDER_PS2 0
#endif
#ifndef SDL_VIDEO_RENDER_PSP
#define SDL_VIDEO_RENDER_PSP 0
#endif
#ifndef SDL_VIDEO_RENDER_VITA_GXM
#define SDL_VIDEO_RENDER_VITA_GXM 0
#endif
#ifndef SDL_VIDEO_RENDER_VULKAN
#define SDL_VIDEO_RENDER_VULKAN 0
#endif
#else /* define all as 0 */
#undef SDL_VIDEO_RENDER_SW
#define SDL_VIDEO_RENDER_SW 0
#undef SDL_VIDEO_RENDER_D3D
#define SDL_VIDEO_RENDER_D3D 0
#undef SDL_VIDEO_RENDER_D3D11
#define SDL_VIDEO_RENDER_D3D11 0
#undef SDL_VIDEO_RENDER_D3D12
#define SDL_VIDEO_RENDER_D3D12 0
#undef SDL_VIDEO_RENDER_METAL
#define SDL_VIDEO_RENDER_METAL 0
#undef SDL_VIDEO_RENDER_OGL
#define SDL_VIDEO_RENDER_OGL 0
#undef SDL_VIDEO_RENDER_OGL_ES2
#define SDL_VIDEO_RENDER_OGL_ES2 0
#undef SDL_VIDEO_RENDER_PS2
#define SDL_VIDEO_RENDER_PS2 0
#undef SDL_VIDEO_RENDER_PSP
#define SDL_VIDEO_RENDER_PSP 0
#undef SDL_VIDEO_RENDER_VITA_GXM
#define SDL_VIDEO_RENDER_VITA_GXM 0
#undef SDL_VIDEO_RENDER_VULKAN
#define SDL_VIDEO_RENDER_VULKAN 0
#endif /* SDL_RENDER_DISABLED */
#define SDL_HAS_RENDER_DRIVER \
(SDL_VIDEO_RENDER_SW | \
SDL_VIDEO_RENDER_D3D | \
SDL_VIDEO_RENDER_D3D11 | \
SDL_VIDEO_RENDER_D3D12 | \
SDL_VIDEO_RENDER_METAL | \
SDL_VIDEO_RENDER_OGL | \
SDL_VIDEO_RENDER_OGL_ES2 | \
SDL_VIDEO_RENDER_PS2 | \
SDL_VIDEO_RENDER_PSP | \
SDL_VIDEO_RENDER_VITA_GXM | \
SDL_VIDEO_RENDER_VULKAN )
#if !defined(SDL_RENDER_DISABLED) && !SDL_HAS_RENDER_DRIVER
#error SDL_RENDER enabled without any backend drivers.
#endif
#include <SDL3/SDL.h>
#include <SDL3/SDL_intrin.h>
#define SDL_MAIN_NOIMPL /* don't drag in header-only implementation of SDL_main */
#include <SDL3/SDL_main.h>
/* The internal implementations of these functions have up to nanosecond precision.
We can expose these functions as part of the API if we want to later.
*/
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
extern DECLSPEC Uint32 SDLCALL SDL_GetNextObjectID(void);
extern DECLSPEC int SDLCALL SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS);
extern DECLSPEC int SDLCALL SDL_WaitConditionTimeoutNS(SDL_Condition *cond, SDL_Mutex *mutex, Sint64 timeoutNS);
extern DECLSPEC SDL_bool SDLCALL SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#endif /* SDL_internal_h_ */
+86
View File
@@ -0,0 +1,86 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 */
int SDL_ListAdd(SDL_ListNode **head, void *ent)
{
SDL_ListNode *node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
if (!node) {
return -1;
}
node->entry = ent;
node->next = *head;
*head = node;
return 0;
}
/* Pop from end as a FIFO (if add with SDL_ListAdd) */
void SDL_ListPop(SDL_ListNode **head, void **ent)
{
SDL_ListNode **ptr = head;
/* Invalid or empty */
if (!head || !*head) {
return;
}
while ((*ptr)->next) {
ptr = &(*ptr)->next;
}
if (ent) {
*ent = (*ptr)->entry;
}
SDL_free(*ptr);
*ptr = NULL;
}
void SDL_ListRemove(SDL_ListNode **head, void *ent)
{
SDL_ListNode **ptr = head;
while (*ptr) {
if ((*ptr)->entry == ent) {
SDL_ListNode *tmp = *ptr;
*ptr = (*ptr)->next;
SDL_free(tmp);
return;
}
ptr = &(*ptr)->next;
}
}
void SDL_ListClear(SDL_ListNode **head)
{
SDL_ListNode *l = *head;
*head = NULL;
while (l) {
SDL_ListNode *tmp = l;
l = l->next;
SDL_free(tmp);
}
}
+36
View File
@@ -0,0 +1,36 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_list_h_
#define SDL_list_h_
typedef struct SDL_ListNode
{
void *entry;
struct SDL_ListNode *next;
} SDL_ListNode;
int SDL_ListAdd(SDL_ListNode **head, void *ent);
void SDL_ListPop(SDL_ListNode **head, void **ent);
void SDL_ListRemove(SDL_ListNode **head, void *ent);
void SDL_ListClear(SDL_ListNode **head);
#endif /* SDL_list_h_ */
+611
View File
@@ -0,0 +1,611 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_WIN32) || defined(SDL_PLATFORM_WINRT) || defined(SDL_PLATFORM_GDK)
#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 SDL_LogLevel *SDL_loglevels;
static SDL_bool SDL_forced_priority = SDL_FALSE;
static SDL_LogPriority SDL_forced_priority_level;
static SDL_LogOutputFunction SDL_log_function = SDL_LogOutput;
static void *SDL_log_userdata = NULL;
static SDL_Mutex *log_function_mutex = 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 *SDL_priority_prefixes[] = {
NULL,
"VERBOSE",
"DEBUG",
"INFO",
"WARN",
"ERROR",
"CRITICAL"
};
SDL_COMPILE_TIME_ASSERT(priority_prefixes, SDL_arraysize(SDL_priority_prefixes) == SDL_NUM_LOG_PRIORITIES);
/* If this list changes, update the documentation for SDL_HINT_LOGGING */
static const char *SDL_category_prefixes[] = {
"APP",
"ERROR",
"ASSERT",
"SYSTEM",
"AUDIO",
"VIDEO",
"RENDER",
"INPUT",
"TEST"
};
SDL_COMPILE_TIME_ASSERT(category_prefixes, SDL_arraysize(SDL_category_prefixes) == SDL_LOG_CATEGORY_RESERVED1);
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
#pragma GCC diagnostic pop
#endif
#ifdef SDL_PLATFORM_ANDROID
static int SDL_android_priority[SDL_NUM_LOG_PRIORITIES] = {
ANDROID_LOG_UNKNOWN,
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL
};
#endif /* SDL_PLATFORM_ANDROID */
void SDL_InitLog(void)
{
if (!log_function_mutex) {
/* if this fails we'll try to continue without it. */
log_function_mutex = SDL_CreateMutex();
}
}
void SDL_QuitLog(void)
{
SDL_LogResetPriorities();
if (log_function_mutex) {
SDL_DestroyMutex(log_function_mutex);
log_function_mutex = NULL;
}
}
void SDL_LogSetAllPriority(SDL_LogPriority priority)
{
SDL_LogLevel *entry;
for (entry = SDL_loglevels; entry; entry = entry->next) {
entry->priority = priority;
}
SDL_forced_priority = SDL_TRUE;
SDL_forced_priority_level = priority;
}
void SDL_LogSetPriority(int category, SDL_LogPriority priority)
{
SDL_LogLevel *entry;
for (entry = SDL_loglevels; entry; entry = entry->next) {
if (entry->category == category) {
entry->priority = priority;
return;
}
}
/* Create a new entry */
entry = (SDL_LogLevel *)SDL_malloc(sizeof(*entry));
if (entry) {
entry->category = category;
entry->priority = priority;
entry->next = SDL_loglevels;
SDL_loglevels = entry;
}
}
static SDL_bool SDL_ParseLogCategory(const char *string, size_t length, int *category)
{
int i;
if (SDL_isdigit(*string)) {
*category = SDL_atoi(string);
return SDL_TRUE;
}
if (*string == '*') {
*category = DEFAULT_CATEGORY;
return SDL_TRUE;
}
for (i = 0; i < SDL_arraysize(SDL_category_prefixes); ++i) {
if (SDL_strncasecmp(string, SDL_category_prefixes[i], length) == 0) {
*category = i;
return SDL_TRUE;
}
}
return SDL_FALSE;
}
static SDL_bool SDL_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_NUM_LOG_PRIORITIES;
return SDL_TRUE;
}
if (i >= SDL_LOG_PRIORITY_VERBOSE && i < SDL_NUM_LOG_PRIORITIES) {
*priority = (SDL_LogPriority)i;
return SDL_TRUE;
}
return SDL_FALSE;
}
if (SDL_strncasecmp(string, "quiet", length) == 0) {
*priority = SDL_NUM_LOG_PRIORITIES;
return SDL_TRUE;
}
for (i = SDL_LOG_PRIORITY_VERBOSE; i < SDL_NUM_LOG_PRIORITIES; ++i) {
if (SDL_strncasecmp(string, SDL_priority_prefixes[i], length) == 0) {
*priority = (SDL_LogPriority)i;
return SDL_TRUE;
}
}
return SDL_FALSE;
}
static SDL_bool SDL_ParseLogCategoryPriority(const char *hint, int category, SDL_LogPriority *priority)
{
const char *name, *next;
int current_category;
if (category == DEFAULT_CATEGORY && SDL_strchr(hint, '=') == NULL) {
return SDL_ParseLogPriority(hint, SDL_strlen(hint), priority);
}
for (name = hint; name; name = next) {
const char *sep = SDL_strchr(name, '=');
if (!sep) {
break;
}
next = SDL_strchr(sep, ',');
if (next) {
++next;
}
if (SDL_ParseLogCategory(name, (sep - name), &current_category)) {
if (current_category == category) {
const char *value = sep + 1;
size_t len;
if (next) {
len = (next - value - 1);
} else {
len = SDL_strlen(value);
}
return SDL_ParseLogPriority(value, len, priority);
}
}
}
return SDL_FALSE;
}
static SDL_LogPriority SDL_GetDefaultLogPriority(int category)
{
const char *hint = SDL_GetHint(SDL_HINT_LOGGING);
if (hint) {
SDL_LogPriority priority;
if (SDL_ParseLogCategoryPriority(hint, category, &priority)) {
return priority;
}
if (SDL_ParseLogCategoryPriority(hint, DEFAULT_CATEGORY, &priority)) {
return priority;
}
}
switch (category) {
case SDL_LOG_CATEGORY_APPLICATION:
return SDL_LOG_PRIORITY_INFO;
case SDL_LOG_CATEGORY_ASSERT:
return SDL_LOG_PRIORITY_WARN;
case SDL_LOG_CATEGORY_TEST:
return SDL_LOG_PRIORITY_VERBOSE;
default:
return SDL_LOG_PRIORITY_ERROR;
}
}
SDL_LogPriority SDL_LogGetPriority(int category)
{
SDL_LogLevel *entry;
for (entry = SDL_loglevels; entry; entry = entry->next) {
if (entry->category == category) {
return entry->priority;
}
}
if (SDL_forced_priority) {
return SDL_forced_priority_level;
}
return SDL_GetDefaultLogPriority(category);
}
void SDL_LogResetPriorities(void)
{
SDL_LogLevel *entry;
while (SDL_loglevels) {
entry = SDL_loglevels;
SDL_loglevels = entry->next;
SDL_free(entry);
}
SDL_forced_priority = SDL_FALSE;
}
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_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_RESERVED1) {
return SDL_category_prefixes[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;
}
/* Make sure we don't exceed array bounds */
if ((int)priority < 0 || priority >= SDL_NUM_LOG_PRIORITIES) {
return;
}
/* See if we want to do anything with this message */
if (priority < SDL_LogGetPriority(category)) {
return;
}
if (!log_function_mutex) {
/* this mutex creation can race if you log from two threads at startup. You should have called SDL_Init first! */
log_function_mutex = SDL_CreateMutex();
}
/* 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_overflow(len, 1, &len_plus_term) == 0) {
/* 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(log_function_mutex);
SDL_log_function(SDL_log_userdata, category, priority, message);
SDL_UnlockMutex(log_function_mutex);
/* Free only if dynamically allocated */
if (message != stack_buf) {
SDL_free(message);
}
}
#if defined(SDL_PLATFORM_WIN32) && !defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_GDK)
/* Flag tracking the attachment of the console: 0=unattached, 1=attached to a console, 2=attached to a file, -1=error */
static int consoleAttached = 0;
/* 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_WIN32) || defined(SDL_PLATFORM_WINRT) || defined(SDL_PLATFORM_GDK)
/* 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;
SDL_bool isstack;
#if !defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_GDK)
BOOL attachResult;
DWORD attachError;
DWORD charsWritten;
DWORD consoleMode;
/* Maybe attach console and get stderr handle */
if (consoleAttached == 0) {
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 = -1;
} else if (attachError == ERROR_GEN_FAILURE) {
OutputDebugString(TEXT("Could not attach to console of parent process\r\n"));
consoleAttached = -1;
} else if (attachError == ERROR_ACCESS_DENIED) {
/* Already attached */
consoleAttached = 1;
} else {
OutputDebugString(TEXT("Error attaching console\r\n"));
consoleAttached = -1;
}
} else {
/* Newly attached */
consoleAttached = 1;
}
if (consoleAttached == 1) {
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 = 2;
}
}
}
#endif /* !defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_GDK) */
length = SDL_strlen(SDL_priority_prefixes[priority]) + 2 + SDL_strlen(message) + 1 + 1 + 1;
output = SDL_small_alloc(char, length, &isstack);
(void)SDL_snprintf(output, length, "%s: %s\r\n", SDL_priority_prefixes[priority], message);
tstr = WIN_UTF8ToString(output);
/* Output to debugger */
OutputDebugString(tstr);
#if !defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_GDK)
/* Screen output to stderr, if console was attached. */
if (consoleAttached == 1) {
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 == 2) {
if (!WriteFile(stderrHandle, output, (DWORD)SDL_strlen(output), &charsWritten, NULL)) {
OutputDebugString(TEXT("Error calling WriteFile\r\n"));
}
}
#endif /* !defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINRT) && !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(SDL_priority_prefixes[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", SDL_priority_prefixes[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", SDL_priority_prefixes[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", SDL_priority_prefixes[priority], message);
(void)fclose(pFile);
}
}
#endif
#if defined(HAVE_STDIO_H) && \
!(defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT)))
(void)fprintf(stderr, "%s: %s\n", SDL_priority_prefixes[priority], message);
#endif
}
void SDL_GetLogOutputFunction(SDL_LogOutputFunction *callback, void **userdata)
{
if (callback) {
*callback = SDL_log_function;
}
if (userdata) {
*userdata = SDL_log_userdata;
}
}
void SDL_SetLogOutputFunction(SDL_LogOutputFunction callback, void *userdata)
{
SDL_log_function = callback;
SDL_log_userdata = userdata;
}
+31
View File
@@ -0,0 +1,31 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/* This file defines useful function for working with SDL logging */
#ifndef SDL_log_c_h_
#define SDL_log_c_h_
extern void SDL_InitLog(void);
extern void SDL_QuitLog(void);
#endif /* SDL_log_c_h_ */
+785
View File
@@ -0,0 +1,785 @@
/*
Simple DiretMedia Layer
Copyright (C) 1997-2024 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_hashtable.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;
SDL_bool boolean_value;
} value;
char *string_storage;
void (SDLCALL *cleanup)(void *userdata, void *value);
void *userdata;
} SDL_Property;
typedef struct
{
SDL_HashTable *props;
SDL_Mutex *lock;
} SDL_Properties;
static SDL_HashTable *SDL_properties;
static SDL_Mutex *SDL_properties_lock;
static SDL_PropertiesID SDL_last_properties_id;
static SDL_PropertiesID SDL_global_properties;
static void SDL_FreePropertyWithCleanup(const void *key, const void *value, void *data, SDL_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;
}
if (property->string_storage) {
SDL_free(property->string_storage);
}
}
SDL_free((void *)key);
SDL_free((void *)value);
}
static void SDL_FreeProperty(const void *key, const void *value, void *data)
{
SDL_FreePropertyWithCleanup(key, value, data, SDL_TRUE);
}
static void SDL_FreeProperties(const void *key, const void *value, void *data)
{
SDL_Properties *properties = (SDL_Properties *)value;
if (properties) {
if (properties->props) {
SDL_DestroyHashTable(properties->props);
properties->props = NULL;
}
if (properties->lock) {
SDL_DestroyMutex(properties->lock);
properties->lock = NULL;
}
SDL_free(properties);
}
}
int SDL_InitProperties(void)
{
if (!SDL_properties_lock) {
SDL_properties_lock = SDL_CreateMutex();
if (!SDL_properties_lock) {
return -1;
}
}
if (!SDL_properties) {
SDL_properties = SDL_CreateHashTable(NULL, 16, SDL_HashID, SDL_KeyMatchID, SDL_FreeProperties, SDL_FALSE);
if (!SDL_properties) {
return -1;
}
}
return 0;
}
void SDL_QuitProperties(void)
{
if (SDL_global_properties) {
SDL_DestroyProperties(SDL_global_properties);
SDL_global_properties = 0;
}
if (SDL_properties) {
SDL_DestroyHashTable(SDL_properties);
SDL_properties = NULL;
}
if (SDL_properties_lock) {
SDL_DestroyMutex(SDL_properties_lock);
SDL_properties_lock = NULL;
}
}
SDL_PropertiesID SDL_GetGlobalProperties(void)
{
if (!SDL_global_properties) {
SDL_global_properties = SDL_CreateProperties();
}
return SDL_global_properties;
}
SDL_PropertiesID SDL_CreateProperties(void)
{
SDL_PropertiesID props = 0;
SDL_Properties *properties = NULL;
SDL_bool inserted = SDL_FALSE;
if (!SDL_properties && SDL_InitProperties() < 0) {
return 0;
}
properties = (SDL_Properties *)SDL_calloc(1, sizeof(*properties));
if (!properties) {
goto error;
}
properties->props = SDL_CreateHashTable(NULL, 4, SDL_HashString, SDL_KeyMatchString, SDL_FreeProperty, SDL_FALSE);
if (!properties->props) {
goto error;
}
properties->lock = SDL_CreateMutex();
if (!properties->lock) {
goto error;
}
if (SDL_InitProperties() < 0) {
goto error;
}
SDL_LockMutex(SDL_properties_lock);
++SDL_last_properties_id;
if (SDL_last_properties_id == 0) {
++SDL_last_properties_id;
}
props = SDL_last_properties_id;
if (SDL_InsertIntoHashTable(SDL_properties, (const void *)(uintptr_t)props, properties)) {
inserted = SDL_TRUE;
}
SDL_UnlockMutex(SDL_properties_lock);
if (inserted) {
/* All done! */
return props;
}
error:
SDL_FreeProperties(NULL, properties, NULL);
return 0;
}
int SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst)
{
SDL_Properties *src_properties = NULL;
SDL_Properties *dst_properties = NULL;
int result = 0;
if (!src) {
return SDL_InvalidParamError("src");
}
if (!dst) {
return SDL_InvalidParamError("dst");
}
SDL_LockMutex(SDL_properties_lock);
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)src, (const void **)&src_properties);
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)dst, (const void **)&dst_properties);
SDL_UnlockMutex(SDL_properties_lock);
if (!src_properties) {
return SDL_InvalidParamError("src");
}
if (!dst_properties) {
return SDL_InvalidParamError("dst");
}
SDL_LockMutex(src_properties->lock);
SDL_LockMutex(dst_properties->lock);
{
void *iter;
const void *key, *value;
iter = NULL;
while (SDL_IterateHashTable(src_properties->props, &key, &value, &iter)) {
const char *src_name = (const char *)key;
const SDL_Property *src_property = (const SDL_Property *)value;
char *dst_name;
SDL_Property *dst_property;
if (src_property->cleanup) {
/* Can't copy properties with cleanup functions, we don't know how to duplicate the data */
continue;
}
SDL_RemoveFromHashTable(dst_properties->props, src_name);
dst_name = SDL_strdup(src_name);
if (!dst_name) {
result = -1;
continue;
}
dst_property = (SDL_Property *)SDL_malloc(sizeof(*dst_property));
if (!dst_property) {
SDL_free(dst_name);
result = -1;
continue;
}
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 (!SDL_InsertIntoHashTable(dst_properties->props, dst_name, dst_property)) {
SDL_FreePropertyWithCleanup(dst_name, dst_property, NULL, SDL_FALSE);
result = -1;
}
}
}
SDL_UnlockMutex(dst_properties->lock);
SDL_UnlockMutex(src_properties->lock);
return result;
}
int SDL_LockProperties(SDL_PropertiesID props)
{
SDL_Properties *properties = NULL;
if (!props) {
return SDL_InvalidParamError("props");
}
SDL_LockMutex(SDL_properties_lock);
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
SDL_UnlockMutex(SDL_properties_lock);
if (!properties) {
return SDL_InvalidParamError("props");
}
SDL_LockMutex(properties->lock);
return 0;
}
void SDL_UnlockProperties(SDL_PropertiesID props)
{
SDL_Properties *properties = NULL;
if (!props) {
return;
}
SDL_LockMutex(SDL_properties_lock);
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
SDL_UnlockMutex(SDL_properties_lock);
if (!properties) {
return;
}
SDL_UnlockMutex(properties->lock);
}
static int SDL_PrivateSetProperty(SDL_PropertiesID props, const char *name, SDL_Property *property)
{
SDL_Properties *properties = NULL;
int result = 0;
if (!props) {
SDL_FreePropertyWithCleanup(NULL, property, NULL, SDL_FALSE);
return SDL_InvalidParamError("props");
}
if (!name || !*name) {
SDL_FreePropertyWithCleanup(NULL, property, NULL, SDL_FALSE);
return SDL_InvalidParamError("name");
}
SDL_LockMutex(SDL_properties_lock);
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
SDL_UnlockMutex(SDL_properties_lock);
if (!properties) {
SDL_FreePropertyWithCleanup(NULL, property, NULL, SDL_FALSE);
return SDL_InvalidParamError("props");
}
SDL_LockMutex(properties->lock);
{
SDL_RemoveFromHashTable(properties->props, name);
if (property) {
char *key = SDL_strdup(name);
if (!SDL_InsertIntoHashTable(properties->props, key, property)) {
SDL_FreePropertyWithCleanup(key, property, NULL, SDL_FALSE);
result = -1;
}
}
}
SDL_UnlockMutex(properties->lock);
return result;
}
int SDL_SetPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, void (SDLCALL *cleanup)(void *userdata, void *value), void *userdata)
{
SDL_Property *property;
if (!value) {
return SDL_ClearProperty(props, name);
}
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
SDL_FreePropertyWithCleanup(NULL, property, NULL, SDL_FALSE);
return -1;
}
property->type = SDL_PROPERTY_TYPE_POINTER;
property->value.pointer_value = value;
property->cleanup = cleanup;
property->userdata = userdata;
return SDL_PrivateSetProperty(props, name, property);
}
int SDL_SetProperty(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 -1;
}
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);
}
int SDL_SetFreeableProperty(SDL_PropertiesID props, const char *name, void *value)
{
return SDL_SetPropertyWithCleanup(props, name, value, CleanupFreeableProperty, NULL);
}
static void SDLCALL CleanupSurface(void *userdata, void *value)
{
SDL_Surface *surface = (SDL_Surface *)value;
SDL_DestroySurface(surface);
}
int SDL_SetSurfaceProperty(SDL_PropertiesID props, const char *name, SDL_Surface *surface)
{
return SDL_SetPropertyWithCleanup(props, name, surface, CleanupSurface, NULL);
}
int 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 -1;
}
property->type = SDL_PROPERTY_TYPE_STRING;
property->value.string_value = SDL_strdup(value);
if (!property->value.string_value) {
SDL_free(property);
return -1;
}
return SDL_PrivateSetProperty(props, name, property);
}
int SDL_SetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 value)
{
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return -1;
}
property->type = SDL_PROPERTY_TYPE_NUMBER;
property->value.number_value = value;
return SDL_PrivateSetProperty(props, name, property);
}
int SDL_SetFloatProperty(SDL_PropertiesID props, const char *name, float value)
{
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return -1;
}
property->type = SDL_PROPERTY_TYPE_FLOAT;
property->value.float_value = value;
return SDL_PrivateSetProperty(props, name, property);
}
int SDL_SetBooleanProperty(SDL_PropertiesID props, const char *name, SDL_bool value)
{
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return -1;
}
property->type = SDL_PROPERTY_TYPE_BOOLEAN;
property->value.boolean_value = value ? SDL_TRUE : SDL_FALSE;
return SDL_PrivateSetProperty(props, name, property);
}
SDL_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_LockMutex(SDL_properties_lock);
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
SDL_UnlockMutex(SDL_properties_lock);
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_GetProperty(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_LockMutex(SDL_properties_lock);
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
SDL_UnlockMutex(SDL_properties_lock);
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_LockMutex(SDL_properties_lock);
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
SDL_UnlockMutex(SDL_properties_lock);
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.
*
* FIXME: Should we SDL_strdup() the return value to avoid this?
*/
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_LockMutex(SDL_properties_lock);
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
SDL_UnlockMutex(SDL_properties_lock);
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_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_LockMutex(SDL_properties_lock);
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
SDL_UnlockMutex(SDL_properties_lock);
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;
}
SDL_bool SDL_GetBooleanProperty(SDL_PropertiesID props, const char *name, SDL_bool default_value)
{
SDL_Properties *properties = NULL;
SDL_bool value = default_value;
if (!props) {
return value;
}
if (!name || !*name) {
return value;
}
SDL_LockMutex(SDL_properties_lock);
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
SDL_UnlockMutex(SDL_properties_lock);
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;
}
int SDL_ClearProperty(SDL_PropertiesID props, const char *name)
{
return SDL_PrivateSetProperty(props, name, NULL);
}
int 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_LockMutex(SDL_properties_lock);
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
SDL_UnlockMutex(SDL_properties_lock);
if (!properties) {
return SDL_InvalidParamError("props");
}
SDL_LockMutex(properties->lock);
{
void *iter;
const void *key, *value;
iter = NULL;
while (SDL_IterateHashTable(properties->props, &key, &value, &iter)) {
callback(userdata, props, (const char *)key);
}
}
SDL_UnlockMutex(properties->lock);
return 0;
}
void SDL_DestroyProperties(SDL_PropertiesID props)
{
if (!props) {
return;
}
SDL_LockMutex(SDL_properties_lock);
SDL_RemoveFromHashTable(SDL_properties, (const void *)(uintptr_t)props);
SDL_UnlockMutex(SDL_properties_lock);
}
+25
View File
@@ -0,0 +1,25 @@
/*
Simple DiretMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
extern int SDL_InitProperties(void);
extern int SDL_SetFreeableProperty(SDL_PropertiesID props, const char *name, void *value);
extern int SDL_SetSurfaceProperty(SDL_PropertiesID props, const char *name, SDL_Surface *surface);
extern void SDL_QuitProperties(void);
+63
View File
@@ -0,0 +1,63 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_utils_c.h"
/* 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;
}
SDL_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 SDL_TRUE;
}
}
return SDL_FALSE;
}
+33
View File
@@ -0,0 +1,33 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_utils_h_
#define SDL_utils_h_
/* Common utility functions that aren't in the public API */
/* Return the smallest power of 2 greater than or equal to 'x' */
extern int SDL_powerof2(int x);
SDL_bool SDL_endswith(const char *string, const char *suffix);
#endif /* SDL_utils_h_ */
+301
View File
@@ -0,0 +1,301 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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
SDL_bool SDL_AtomicCompareAndSwap(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(&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)
return ((int)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval);
#elif defined(EMULATE_CAS)
SDL_bool retval = SDL_FALSE;
enterLock(a);
if (a->value == oldval) {
a->value = newval;
retval = SDL_TRUE;
}
leaveLock(a);
return retval;
#else
#error Please define your platform.
#endif
}
SDL_bool SDL_AtomicCompareAndSwapPointer(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)
SDL_bool retval = SDL_FALSE;
enterLock(a);
if (*a == oldval) {
*a = newval;
retval = SDL_TRUE;
}
leaveLock(a);
return retval;
#else
#error Please define your platform.
#endif
}
int SDL_AtomicSet(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)
return (int)atomic_swap_uint((volatile uint_t *)&a->value, v);
#else
int value;
do {
value = a->value;
} while (!SDL_AtomicCompareAndSwap(a, value, v));
return value;
#endif
}
void *SDL_AtomicSetPtr(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_AtomicCompareAndSwapPointer(a, value, v));
return value;
#endif
}
int SDL_AtomicAdd(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)
return _SDL_xadd_watcom(&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_AtomicCompareAndSwap(a, value, (value + v)));
return value;
#endif
}
int SDL_AtomicGet(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((volatile uint_t *)&a->value, 0);
#else
int value;
do {
value = a->value;
} while (!SDL_AtomicCompareAndSwap(a, value, value));
return value;
#endif
}
void *SDL_AtomicGetPtr(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_AtomicCompareAndSwapPointer(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();
}
+203
View File
@@ -0,0 +1,203 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_WIN32) || defined(SDL_PLATFORM_WINRT) || defined(SDL_PLATFORM_GDK)
#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... */
SDL_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;
SDL_bool res = SDL_FALSE;
// disable interuption
oldintr = DIntr();
if (*lock == 0) {
*lock = 1;
res = SDL_TRUE;
}
// enable interuption
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 SDL_TRUE;
} else {
SDL_UnlockMutex(_spinlock_mutex);
return SDL_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
+27
View File
@@ -0,0 +1,27 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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
@@ -0,0 +1,544 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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.
*/
// DO NOT EDIT, THIS FILE WAS GENERATED BY build-scripts/gen_audio_resampler_filter.c
#define RESAMPLER_ZERO_CROSSINGS 5
#define RESAMPLER_BITS_PER_SAMPLE 16
#define RESAMPLER_BITS_PER_ZERO_CROSSING ((RESAMPLER_BITS_PER_SAMPLE / 2) + 1)
#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << RESAMPLER_BITS_PER_ZERO_CROSSING)
#define RESAMPLER_FILTER_SIZE (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * RESAMPLER_ZERO_CROSSINGS)
static const float ResamplerFilter[RESAMPLER_FILTER_SIZE] = {
1.000000000f, 0.000000000f,-0.000000000f, 0.000000000f,-0.000000000f,
0.999993165f,-0.001679888f, 0.000529080f,-0.000151513f, 0.000027455f,
0.999972661f,-0.003351212f, 0.001055794f,-0.000302183f, 0.000054683f,
0.999938488f,-0.005013955f, 0.001580128f,-0.000452009f, 0.000081685f,
0.999890647f,-0.006668099f, 0.002102071f,-0.000600987f, 0.000108459f,
0.999829139f,-0.008313629f, 0.002621611f,-0.000749115f, 0.000135007f,
0.999753966f,-0.009950528f, 0.003138734f,-0.000896389f, 0.000161328f,
0.999665131f,-0.011578779f, 0.003653429f,-0.001042807f, 0.000187423f,
0.999562634f,-0.013198368f, 0.004165684f,-0.001188367f, 0.000213291f,
0.999446480f,-0.014809279f, 0.004675488f,-0.001333066f, 0.000238933f,
0.999316672f,-0.016411497f, 0.005182828f,-0.001476901f, 0.000264348f,
0.999173212f,-0.018005007f, 0.005687694f,-0.001619871f, 0.000289537f,
0.999016105f,-0.019589795f, 0.006190074f,-0.001761971f, 0.000314501f,
0.998845356f,-0.021165846f, 0.006689957f,-0.001903201f, 0.000339239f,
0.998660968f,-0.022733147f, 0.007187332f,-0.002043558f, 0.000363751f,
0.998462946f,-0.024291684f, 0.007682189f,-0.002183039f, 0.000388037f,
0.998251297f,-0.025841443f, 0.008174516f,-0.002321643f, 0.000412099f,
0.998026026f,-0.027382413f, 0.008664303f,-0.002459367f, 0.000435935f,
0.997787138f,-0.028914579f, 0.009151540f,-0.002596209f, 0.000459547f,
0.997534641f,-0.030437930f, 0.009636217f,-0.002732167f, 0.000482934f,
0.997268542f,-0.031952453f, 0.010118324f,-0.002867240f, 0.000506097f,
0.996988847f,-0.033458137f, 0.010597850f,-0.003001425f, 0.000529036f,
0.996695563f,-0.034954970f, 0.011074786f,-0.003134721f, 0.000551752f,
0.996388700f,-0.036442941f, 0.011549123f,-0.003267125f, 0.000574244f,
0.996068266f,-0.037922039f, 0.012020851f,-0.003398637f, 0.000596512f,
0.995734268f,-0.039392253f, 0.012489961f,-0.003529253f, 0.000618558f,
0.995386717f,-0.040853574f, 0.012956443f,-0.003658973f, 0.000640382f,
0.995025621f,-0.042305990f, 0.013420290f,-0.003787796f, 0.000661984f,
0.994650990f,-0.043749493f, 0.013881491f,-0.003915718f, 0.000683363f,
0.994262835f,-0.045184072f, 0.014340039f,-0.004042740f, 0.000704522f,
0.993861166f,-0.046609719f, 0.014795924f,-0.004168860f, 0.000725459f,
0.993445994f,-0.048026424f, 0.015249139f,-0.004294075f, 0.000746176f,
0.993017331f,-0.049434180f, 0.015699676f,-0.004418386f, 0.000766672f,
0.992575187f,-0.050832978f, 0.016147525f,-0.004541790f, 0.000786949f,
0.992119574f,-0.052222809f, 0.016592680f,-0.004664287f, 0.000807006f,
0.991650506f,-0.053603666f, 0.017035133f,-0.004785875f, 0.000826844f,
0.991167995f,-0.054975543f, 0.017474875f,-0.004906553f, 0.000846464f,
0.990672054f,-0.056338431f, 0.017911900f,-0.005026320f, 0.000865865f,
0.990162696f,-0.057692323f, 0.018346201f,-0.005145175f, 0.000885049f,
0.989639935f,-0.059037214f, 0.018777770f,-0.005263117f, 0.000904016f,
0.989103786f,-0.060373097f, 0.019206599f,-0.005380146f, 0.000922766f,
0.988554262f,-0.061699966f, 0.019632684f,-0.005496260f, 0.000941300f,
0.987991380f,-0.063017815f, 0.020056015f,-0.005611458f, 0.000959619f,
0.987415153f,-0.064326639f, 0.020476588f,-0.005725741f, 0.000977722f,
0.986825598f,-0.065626433f, 0.020894396f,-0.005839106f, 0.000995611f,
0.986222730f,-0.066917192f, 0.021309432f,-0.005951554f, 0.001013285f,
0.985606567f,-0.068198912f, 0.021721690f,-0.006063084f, 0.001030746f,
0.984977124f,-0.069471588f, 0.022131165f,-0.006173695f, 0.001047994f,
0.984334418f,-0.070735217f, 0.022537850f,-0.006283387f, 0.001065030f,
0.983678468f,-0.071989794f, 0.022941741f,-0.006392159f, 0.001081853f,
0.983009290f,-0.073235317f, 0.023342830f,-0.006500011f, 0.001098466f,
0.982326903f,-0.074471782f, 0.023741114f,-0.006606943f, 0.001114868f,
0.981631326f,-0.075699186f, 0.024136587f,-0.006712954f, 0.001131059f,
0.980922577f,-0.076917527f, 0.024529243f,-0.006818044f, 0.001147042f,
0.980200675f,-0.078126804f, 0.024919078f,-0.006922213f, 0.001162815f,
0.979465640f,-0.079327013f, 0.025306087f,-0.007025460f, 0.001178380f,
0.978717491f,-0.080518153f, 0.025690266f,-0.007127786f, 0.001193738f,
0.977956250f,-0.081700223f, 0.026071609f,-0.007229191f, 0.001208889f,
0.977181936f,-0.082873221f, 0.026450113f,-0.007329674f, 0.001223833f,
0.976394570f,-0.084037148f, 0.026825773f,-0.007429235f, 0.001238572f,
0.975594175f,-0.085192002f, 0.027198586f,-0.007527875f, 0.001253106f,
0.974780770f,-0.086337783f, 0.027568547f,-0.007625593f, 0.001267436f,
0.973954379f,-0.087474491f, 0.027935652f,-0.007722391f, 0.001281562f,
0.973115024f,-0.088602126f, 0.028299898f,-0.007818267f, 0.001295485f,
0.972262727f,-0.089720690f, 0.028661282f,-0.007913223f, 0.001309207f,
0.971397512f,-0.090830182f, 0.029019799f,-0.008007258f, 0.001322726f,
0.970519401f,-0.091930604f, 0.029375448f,-0.008100373f, 0.001336045f,
0.969628418f,-0.093021958f, 0.029728224f,-0.008192568f, 0.001349164f,
0.968724588f,-0.094104245f, 0.030078125f,-0.008283845f, 0.001362084f,
0.967807935f,-0.095177467f, 0.030425148f,-0.008374202f, 0.001374806f,
0.966878483f,-0.096241627f, 0.030769290f,-0.008463642f, 0.001387329f,
0.965936258f,-0.097296726f, 0.031110550f,-0.008552163f, 0.001399656f,
0.964981285f,-0.098342768f, 0.031448923f,-0.008639768f, 0.001411786f,
0.964013590f,-0.099379756f, 0.031784409f,-0.008726456f, 0.001423721f,
0.963033199f,-0.100407693f, 0.032117005f,-0.008812229f, 0.001435461f,
0.962040138f,-0.101426582f, 0.032446709f,-0.008897086f, 0.001447008f,
0.961034434f,-0.102436428f, 0.032773519f,-0.008981030f, 0.001458361f,
0.960016114f,-0.103437235f, 0.033097434f,-0.009064060f, 0.001469522f,
0.958985206f,-0.104429007f, 0.033418451f,-0.009146178f, 0.001480492f,
0.957941737f,-0.105411749f, 0.033736571f,-0.009227385f, 0.001491271f,
0.956885736f,-0.106385466f, 0.034051790f,-0.009307680f, 0.001501860f,
0.955817231f,-0.107350163f, 0.034364109f,-0.009387067f, 0.001512261f,
0.954736250f,-0.108305845f, 0.034673526f,-0.009465545f, 0.001522473f,
0.953642823f,-0.109252518f, 0.034980040f,-0.009543115f, 0.001532497f,
0.952536979f,-0.110190189f, 0.035283651f,-0.009619779f, 0.001542336f,
0.951418748f,-0.111118864f, 0.035584357f,-0.009695538f, 0.001551988f,
0.950288160f,-0.112038548f, 0.035882158f,-0.009770393f, 0.001561456f,
0.949145245f,-0.112949250f, 0.036177055f,-0.009844346f, 0.001570741f,
0.947990034f,-0.113850976f, 0.036469046f,-0.009917397f, 0.001579842f,
0.946822559f,-0.114743733f, 0.036758132f,-0.009989548f, 0.001588761f,
0.945642850f,-0.115627529f, 0.037044312f,-0.010060800f, 0.001597498f,
0.944450939f,-0.116502372f, 0.037327588f,-0.010131156f, 0.001606056f,
0.943246858f,-0.117368270f, 0.037607958f,-0.010200615f, 0.001614434f,
0.942030639f,-0.118225231f, 0.037885424f,-0.010269180f, 0.001622633f,
0.940802316f,-0.119073264f, 0.038159985f,-0.010336852f, 0.001630655f,
0.939561921f,-0.119912378f, 0.038431644f,-0.010403633f, 0.001638500f,
0.938309487f,-0.120742582f, 0.038700400f,-0.010469524f, 0.001646169f,
0.937045048f,-0.121563886f, 0.038966254f,-0.010534527f, 0.001653663f,
0.935768638f,-0.122376299f, 0.039229208f,-0.010598644f, 0.001660984f,
0.934480291f,-0.123179830f, 0.039489262f,-0.010661876f, 0.001668131f,
0.933180042f,-0.123974491f, 0.039746418f,-0.010724225f, 0.001675106f,
0.931867925f,-0.124760291f, 0.040000678f,-0.010785693f, 0.001681910f,
0.930543975f,-0.125537242f, 0.040252042f,-0.010846282f, 0.001688544f,
0.929208228f,-0.126305353f, 0.040500513f,-0.010905994f, 0.001695008f,
0.927860720f,-0.127064637f, 0.040746092f,-0.010964829f, 0.001701305f,
0.926501487f,-0.127815104f, 0.040988782f,-0.011022792f, 0.001707433f,
0.925130565f,-0.128556767f, 0.041228583f,-0.011079882f, 0.001713396f,
0.923747991f,-0.129289637f, 0.041465499f,-0.011136103f, 0.001719193f,
0.922353802f,-0.130013726f, 0.041699532f,-0.011191456f, 0.001724825f,
0.920948034f,-0.130729047f, 0.041930683f,-0.011245944f, 0.001730294f,
0.919530726f,-0.131435613f, 0.042158956f,-0.011299568f, 0.001735601f,
0.918101916f,-0.132133435f, 0.042384354f,-0.011352330f, 0.001740746f,
0.916661641f,-0.132822528f, 0.042606878f,-0.011404234f, 0.001745730f,
0.915209940f,-0.133502905f, 0.042826532f,-0.011455280f, 0.001750555f,
0.913746852f,-0.134174578f, 0.043043318f,-0.011505472f, 0.001755221f,
0.912272416f,-0.134837563f, 0.043257241f,-0.011554812f, 0.001759730f,
0.910786671f,-0.135491873f, 0.043468303f,-0.011603301f, 0.001764082f,
0.909289657f,-0.136137522f, 0.043676506f,-0.011650942f, 0.001768278f,
0.907781413f,-0.136774525f, 0.043881856f,-0.011697738f, 0.001772320f,
0.906261980f,-0.137402897f, 0.044084355f,-0.011743690f, 0.001776208f,
0.904731398f,-0.138022653f, 0.044284007f,-0.011788802f, 0.001779944f,
0.903189708f,-0.138633807f, 0.044480816f,-0.011833076f, 0.001783528f,
0.901636952f,-0.139236376f, 0.044674785f,-0.011876514f, 0.001786962f,
0.900073170f,-0.139830375f, 0.044865920f,-0.011919118f, 0.001790246f,
0.898498403f,-0.140415819f, 0.045054222f,-0.011960892f, 0.001793381f,
0.896912695f,-0.140992726f, 0.045239698f,-0.012001838f, 0.001796369f,
0.895316086f,-0.141561111f, 0.045422352f,-0.012041958f, 0.001799211f,
0.893708620f,-0.142120991f, 0.045602186f,-0.012081256f, 0.001801907f,
0.892090339f,-0.142672383f, 0.045779208f,-0.012119733f, 0.001804459f,
0.890461286f,-0.143215304f, 0.045953420f,-0.012157393f, 0.001806868f,
0.888821505f,-0.143749771f, 0.046124828f,-0.012194238f, 0.001809134f,
0.887171038f,-0.144275802f, 0.046293436f,-0.012230270f, 0.001811259f,
0.885509930f,-0.144793414f, 0.046459250f,-0.012265494f, 0.001813243f,
0.883838224f,-0.145302625f, 0.046622274f,-0.012299911f, 0.001815089f,
0.882155965f,-0.145803453f, 0.046782515f,-0.012333524f, 0.001816796f,
0.880463198f,-0.146295917f, 0.046939976f,-0.012366337f, 0.001818366f,
0.878759967f,-0.146780035f, 0.047094664f,-0.012398351f, 0.001819800f,
0.877046317f,-0.147255826f, 0.047246583f,-0.012429571f, 0.001821099f,
0.875322295f,-0.147723309f, 0.047395741f,-0.012459998f, 0.001822264f,
0.873587944f,-0.148182503f, 0.047542141f,-0.012489637f, 0.001823295f,
0.871843312f,-0.148633428f, 0.047685790f,-0.012518489f, 0.001824196f,
0.870088444f,-0.149076103f, 0.047826695f,-0.012546558f, 0.001824965f,
0.868323386f,-0.149510548f, 0.047964860f,-0.012573847f, 0.001825604f,
0.866548186f,-0.149936783f, 0.048100292f,-0.012600359f, 0.001826115f,
0.864762890f,-0.150354828f, 0.048232997f,-0.012626097f, 0.001826498f,
0.862967545f,-0.150764704f, 0.048362981f,-0.012651064f, 0.001826754f,
0.861162199f,-0.151166432f, 0.048490252f,-0.012675264f, 0.001826885f,
0.859346899f,-0.151560031f, 0.048614814f,-0.012698699f, 0.001826891f,
0.857521693f,-0.151945524f, 0.048736676f,-0.012721373f, 0.001826774f,
0.855686629f,-0.152322931f, 0.048855842f,-0.012743288f, 0.001826534f,
0.853841755f,-0.152692274f, 0.048972321f,-0.012764449f, 0.001826173f,
0.851987121f,-0.153053574f, 0.049086119f,-0.012784858f, 0.001825691f,
0.850122774f,-0.153406854f, 0.049197244f,-0.012804518f, 0.001825091f,
0.848248764f,-0.153752135f, 0.049305701f,-0.012823434f, 0.001824372f,
0.846365140f,-0.154089440f, 0.049411498f,-0.012841607f, 0.001823536f,
0.844471951f,-0.154418791f, 0.049514643f,-0.012859042f, 0.001822584f,
0.842569248f,-0.154740210f, 0.049615142f,-0.012875742f, 0.001821517f,
0.840657079f,-0.155053721f, 0.049713003f,-0.012891710f, 0.001820336f,
0.838735496f,-0.155359346f, 0.049808234f,-0.012906950f, 0.001819042f,
0.836804549f,-0.155657108f, 0.049900842f,-0.012921465f, 0.001817636f,
0.834864288f,-0.155947032f, 0.049990834f,-0.012935259f, 0.001816120f,
0.832914765f,-0.156229140f, 0.050078219f,-0.012948334f, 0.001814493f,
0.830956029f,-0.156503456f, 0.050163005f,-0.012960695f, 0.001812758f,
0.828988133f,-0.156770004f, 0.050245198f,-0.012972345f, 0.001810916f,
0.827011128f,-0.157028808f, 0.050324808f,-0.012983287f, 0.001808967f,
0.825025066f,-0.157279893f, 0.050401842f,-0.012993525f, 0.001806912f,
0.823029998f,-0.157523282f, 0.050476308f,-0.013003063f, 0.001804753f,
0.821025977f,-0.157759001f, 0.050548215f,-0.013011904f, 0.001802491f,
0.819013055f,-0.157987074f, 0.050617571f,-0.013020051f, 0.001800126f,
0.816991284f,-0.158207526f, 0.050684384f,-0.013027509f, 0.001797660f,
0.814960718f,-0.158420382f, 0.050748664f,-0.013034280f, 0.001795094f,
0.812921409f,-0.158625668f, 0.050810417f,-0.013040370f, 0.001792428f,
0.810873410f,-0.158823410f, 0.050869654f,-0.013045780f, 0.001789664f,
0.808816775f,-0.159013631f, 0.050926382f,-0.013050515f, 0.001786804f,
0.806751557f,-0.159196360f, 0.050980610f,-0.013054579f, 0.001783847f,
0.804677811f,-0.159371620f, 0.051032348f,-0.013057974f, 0.001780795f,
0.802595589f,-0.159539440f, 0.051081605f,-0.013060706f, 0.001777649f,
0.800504946f,-0.159699844f, 0.051128389f,-0.013062778f, 0.001774411f,
0.798405936f,-0.159852860f, 0.051172709f,-0.013064192f, 0.001771080f,
0.796298614f,-0.159998514f, 0.051214574f,-0.013064954f, 0.001767659f,
0.794183034f,-0.160136832f, 0.051253995f,-0.013065067f, 0.001764147f,
0.792059252f,-0.160267843f, 0.051290979f,-0.013064535f, 0.001760547f,
0.789927322f,-0.160391572f, 0.051325537f,-0.013063361f, 0.001756860f,
0.787787300f,-0.160508047f, 0.051357678f,-0.013061549f, 0.001753085f,
0.785639241f,-0.160617296f, 0.051387412f,-0.013059104f, 0.001749225f,
0.783483200f,-0.160719346f, 0.051414747f,-0.013056029f, 0.001745280f,
0.781319234f,-0.160814225f, 0.051439694f,-0.013052327f, 0.001741252f,
0.779147398f,-0.160901960f, 0.051462263f,-0.013048003f, 0.001737141f,
0.776967749f,-0.160982580f, 0.051482462f,-0.013043061f, 0.001732948f,
0.774780342f,-0.161056113f, 0.051500303f,-0.013037504f, 0.001728675f,
0.772585234f,-0.161122587f, 0.051515795f,-0.013031336f, 0.001724323f,
0.770382481f,-0.161182031f, 0.051528947f,-0.013024562f, 0.001719892f,
0.768172142f,-0.161234473f, 0.051539771f,-0.013017185f, 0.001715383f,
0.765954271f,-0.161279942f, 0.051548275f,-0.013009209f, 0.001710798f,
0.763728927f,-0.161318466f, 0.051554471f,-0.013000638f, 0.001706137f,
0.761496167f,-0.161350075f, 0.051558368f,-0.012991476f, 0.001701402f,
0.759256048f,-0.161374798f, 0.051559977f,-0.012981727f, 0.001696593f,
0.757008627f,-0.161392665f, 0.051559309f,-0.012971395f, 0.001691712f,
0.754753963f,-0.161403704f, 0.051556372f,-0.012960484f, 0.001686760f,
0.752492113f,-0.161407945f, 0.051551179f,-0.012948997f, 0.001681737f,
0.750223135f,-0.161405418f, 0.051543739f,-0.012936940f, 0.001676644f,
0.747947088f,-0.161396153f, 0.051534064f,-0.012924315f, 0.001671483f,
0.745664029f,-0.161380179f, 0.051522163f,-0.012911128f, 0.001666254f,
0.743374018f,-0.161357527f, 0.051508048f,-0.012897381f, 0.001660959f,
0.741077112f,-0.161328227f, 0.051491729f,-0.012883079f, 0.001655598f,
0.738773370f,-0.161292309f, 0.051473217f,-0.012868227f, 0.001650173f,
0.736462852f,-0.161249804f, 0.051452522f,-0.012852827f, 0.001644684f,
0.734145616f,-0.161200742f, 0.051429657f,-0.012836884f, 0.001639133f,
0.731821721f,-0.161145154f, 0.051404631f,-0.012820403f, 0.001633520f,
0.729491227f,-0.161083070f, 0.051377457f,-0.012803386f, 0.001627846f,
0.727154193f,-0.161014523f, 0.051348144f,-0.012785839f, 0.001622112f,
0.724810678f,-0.160939542f, 0.051316704f,-0.012767765f, 0.001616320f,
0.722460743f,-0.160858158f, 0.051283148f,-0.012749169f, 0.001610470f,
0.720104446f,-0.160770404f, 0.051247488f,-0.012730054f, 0.001604563f,
0.717741848f,-0.160676310f, 0.051209734f,-0.012710425f, 0.001598600f,
0.715373009f,-0.160575909f, 0.051169898f,-0.012690285f, 0.001592582f,
0.712997988f,-0.160469230f, 0.051127991f,-0.012669640f, 0.001586510f,
0.710616847f,-0.160356307f, 0.051084025f,-0.012648492f, 0.001580385f,
0.708229645f,-0.160237171f, 0.051038012f,-0.012626846f, 0.001574207f,
0.705836443f,-0.160111854f, 0.050989962f,-0.012604706f, 0.001567979f,
0.703437301f,-0.159980389f, 0.050939887f,-0.012582077f, 0.001561700f,
0.701032280f,-0.159842806f, 0.050887799f,-0.012558961f, 0.001555372f,
0.698621441f,-0.159699138f, 0.050833709f,-0.012535365f, 0.001548995f,
0.696204845f,-0.159549419f, 0.050777630f,-0.012511290f, 0.001542571f,
0.693782552f,-0.159393679f, 0.050719572f,-0.012486743f, 0.001536101f,
0.691354624f,-0.159231952f, 0.050659547f,-0.012461726f, 0.001529584f,
0.688921121f,-0.159064270f, 0.050597568f,-0.012436245f, 0.001523023f,
0.686482106f,-0.158890666f, 0.050533646f,-0.012410302f, 0.001516417f,
0.684037639f,-0.158711173f, 0.050467793f,-0.012383903f, 0.001509769f,
0.681587783f,-0.158525823f, 0.050400021f,-0.012357051f, 0.001503079f,
0.679132597f,-0.158334650f, 0.050330342f,-0.012329751f, 0.001496347f,
0.676672145f,-0.158137687f, 0.050258767f,-0.012302006f, 0.001489575f,
0.674206487f,-0.157934966f, 0.050185310f,-0.012273821f, 0.001482764f,
0.671735685f,-0.157726522f, 0.050109981f,-0.012245200f, 0.001475914f,
0.669259802f,-0.157512387f, 0.050032793f,-0.012216147f, 0.001469026f,
0.666778900f,-0.157292594f, 0.049953758f,-0.012186667f, 0.001462101f,
0.664293039f,-0.157067178f, 0.049872888f,-0.012156762f, 0.001455141f,
0.661802283f,-0.156836172f, 0.049790195f,-0.012126439f, 0.001448145f,
0.659306693f,-0.156599609f, 0.049705692f,-0.012095699f, 0.001441115f,
0.656806333f,-0.156357523f, 0.049619391f,-0.012064549f, 0.001434051f,
0.654301263f,-0.156109948f, 0.049531303f,-0.012032992f, 0.001426955f,
0.651791546f,-0.155856918f, 0.049441442f,-0.012001031f, 0.001419827f,
0.649277246f,-0.155598467f, 0.049349819f,-0.011968672f, 0.001412668f,
0.646758423f,-0.155334628f, 0.049256448f,-0.011935918f, 0.001405479f,
0.644235142f,-0.155065436f, 0.049161340f,-0.011902774f, 0.001398261f,
0.641707464f,-0.154790925f, 0.049064507f,-0.011869243f, 0.001391015f,
0.639175452f,-0.154511129f, 0.048965963f,-0.011835330f, 0.001383741f,
0.636639169f,-0.154226083f, 0.048865719f,-0.011801038f, 0.001376440f,
0.634098677f,-0.153935820f, 0.048763788f,-0.011766373f, 0.001369113f,
0.631554040f,-0.153640376f, 0.048660183f,-0.011731337f, 0.001361761f,
0.629005320f,-0.153339784f, 0.048554915f,-0.011695936f, 0.001354384f,
0.626452580f,-0.153034079f, 0.048447999f,-0.011660173f, 0.001346984f,
0.623895883f,-0.152723296f, 0.048339445f,-0.011624053f, 0.001339561f,
0.621335293f,-0.152407470f, 0.048229267f,-0.011587579f, 0.001332116f,
0.618770871f,-0.152086634f, 0.048117478f,-0.011550756f, 0.001324650f,
0.616202682f,-0.151760824f, 0.048004090f,-0.011513587f, 0.001317164f,
0.613630788f,-0.151430075f, 0.047889115f,-0.011476077f, 0.001309657f,
0.611055252f,-0.151094421f, 0.047772566f,-0.011438230f, 0.001302132f,
0.608476138f,-0.150753897f, 0.047654456f,-0.011400050f, 0.001294589f,
0.605893509f,-0.150408538f, 0.047534798f,-0.011361541f, 0.001287028f,
0.603307427f,-0.150058380f, 0.047413604f,-0.011322706f, 0.001279451f,
0.600717957f,-0.149703457f, 0.047290887f,-0.011283552f, 0.001271857f,
0.598125161f,-0.149343804f, 0.047166660f,-0.011244080f, 0.001264249f,
0.595529103f,-0.148979456f, 0.047040936f,-0.011204296f, 0.001256626f,
0.592929846f,-0.148610450f, 0.046913726f,-0.011164202f, 0.001248990f,
0.590327454f,-0.148236818f, 0.046785045f,-0.011123805f, 0.001241340f,
0.587721989f,-0.147858598f, 0.046654904f,-0.011083106f, 0.001233679f,
0.585113515f,-0.147475824f, 0.046523317f,-0.011042111f, 0.001226005f,
0.582502096f,-0.147088532f, 0.046390297f,-0.011000824f, 0.001218321f,
0.579887795f,-0.146696757f, 0.046255856f,-0.010959248f, 0.001210627f,
0.577270674f,-0.146300533f, 0.046120007f,-0.010917387f, 0.001202924f,
0.574650799f,-0.145899898f, 0.045982762f,-0.010875246f, 0.001195211f,
0.572028231f,-0.145494885f, 0.045844136f,-0.010832828f, 0.001187491f,
0.569403034f,-0.145085532f, 0.045704140f,-0.010790137f, 0.001179764f,
0.566775272f,-0.144671872f, 0.045562787f,-0.010747178f, 0.001172030f,
0.564145009f,-0.144253941f, 0.045420091f,-0.010703955f, 0.001164289f,
0.561512306f,-0.143831776f, 0.045276064f,-0.010660470f, 0.001156544f,
0.558877229f,-0.143405412f, 0.045130719f,-0.010616729f, 0.001148794f,
0.556239839f,-0.142974883f, 0.044984069f,-0.010572735f, 0.001141040f,
0.553600201f,-0.142540227f, 0.044836126f,-0.010528493f, 0.001133283f,
0.550958378f,-0.142101478f, 0.044686904f,-0.010484005f, 0.001125523f,
0.548314433f,-0.141658672f, 0.044536416f,-0.010439276f, 0.001117761f,
0.545668429f,-0.141211844f, 0.044384675f,-0.010394310f, 0.001109997f,
0.543020430f,-0.140761032f, 0.044231692f,-0.010349111f, 0.001102233f,
0.540370499f,-0.140306269f, 0.044077482f,-0.010303683f, 0.001094469f,
0.537718699f,-0.139847592f, 0.043922057f,-0.010258029f, 0.001086705f,
0.535065094f,-0.139385036f, 0.043765430f,-0.010212153f, 0.001078942f,
0.532409746f,-0.138918637f, 0.043607614f,-0.010166060f, 0.001071182f,
0.529752718f,-0.138448432f, 0.043448622f,-0.010119753f, 0.001063423f,
0.527094075f,-0.137974455f, 0.043288466f,-0.010073235f, 0.001055668f,
0.524433878f,-0.137496742f, 0.043127159f,-0.010026512f, 0.001047916f,
0.521772191f,-0.137015329f, 0.042964716f,-0.009979586f, 0.001040168f,
0.519109077f,-0.136530252f, 0.042801147f,-0.009932461f, 0.001032425f,
0.516444599f,-0.136041547f, 0.042636467f,-0.009885141f, 0.001024687f,
0.513778819f,-0.135549248f, 0.042470687f,-0.009837631f, 0.001016955f,
0.511111801f,-0.135053393f, 0.042303821f,-0.009789933f, 0.001009230f,
0.508443608f,-0.134554017f, 0.042135882f,-0.009742051f, 0.001001511f,
0.505774302f,-0.134051154f, 0.041966883f,-0.009693990f, 0.000993800f,
0.503103946f,-0.133544842f, 0.041796836f,-0.009645752f, 0.000986098f,
0.500432602f,-0.133035116f, 0.041625754f,-0.009597342f, 0.000978404f,
0.497760334f,-0.132522012f, 0.041453650f,-0.009548764f, 0.000970719f,
0.495087203f,-0.132005564f, 0.041280536f,-0.009500020f, 0.000963044f,
0.492413273f,-0.131485810f, 0.041106427f,-0.009451116f, 0.000955380f,
0.489738606f,-0.130962784f, 0.040931333f,-0.009402053f, 0.000947726f,
0.487063264f,-0.130436523f, 0.040755269f,-0.009352837f, 0.000940083f,
0.484387309f,-0.129907061f, 0.040578247f,-0.009303470f, 0.000932453f,
0.481710804f,-0.129374435f, 0.040400279f,-0.009253957f, 0.000924835f,
0.479033811f,-0.128838680f, 0.040221378f,-0.009204300f, 0.000917229f,
0.476356392f,-0.128299831f, 0.040041558f,-0.009154505f, 0.000909637f,
0.473678610f,-0.127757925f, 0.039860830f,-0.009104573f, 0.000902059f,
0.471000525f,-0.127212997f, 0.039679208f,-0.009054509f, 0.000894496f,
0.468322201f,-0.126665082f, 0.039496704f,-0.009004316f, 0.000886947f,
0.465643698f,-0.126114216f, 0.039313331f,-0.008953998f, 0.000879414f,
0.462965079f,-0.125560435f, 0.039129101f,-0.008903558f, 0.000871896f,
0.460286405f,-0.125003773f, 0.038944027f,-0.008853000f, 0.000864395f,
0.457607738f,-0.124444266f, 0.038758122f,-0.008802328f, 0.000856910f,
0.454929140f,-0.123881950f, 0.038571399f,-0.008751544f, 0.000849443f,
0.452250671f,-0.123316860f, 0.038383869f,-0.008700653f, 0.000841994f,
0.449572393f,-0.122749031f, 0.038195545f,-0.008649657f, 0.000834562f,
0.446894368f,-0.122178499f, 0.038006440f,-0.008598561f, 0.000827149f,
0.444216656f,-0.121605299f, 0.037816567f,-0.008547367f, 0.000819755f,
0.441539319f,-0.121029465f, 0.037625937f,-0.008496080f, 0.000812381f,
0.438862417f,-0.120451035f, 0.037434564f,-0.008444702f, 0.000805026f,
0.436186011f,-0.119870041f, 0.037242460f,-0.008393237f, 0.000797692f,
0.433510163f,-0.119286520f, 0.037049637f,-0.008341688f, 0.000790378f,
0.430834932f,-0.118700507f, 0.036856108f,-0.008290058f, 0.000783086f,
0.428160380f,-0.118112036f, 0.036661885f,-0.008238352f, 0.000775815f,
0.425486566f,-0.117521144f, 0.036466980f,-0.008186572f, 0.000768565f,
0.422813551f,-0.116927863f, 0.036271406f,-0.008134722f, 0.000761339f,
0.420141396f,-0.116332231f, 0.036075174f,-0.008082804f, 0.000754135f,
0.417470160f,-0.115734281f, 0.035878299f,-0.008030823f, 0.000746954f,
0.414799903f,-0.115134048f, 0.035680790f,-0.007978781f, 0.000739796f,
0.412130686f,-0.114531567f, 0.035482662f,-0.007926681f, 0.000732662f,
0.409462568f,-0.113926872f, 0.035283926f,-0.007874528f, 0.000725553f,
0.406795609f,-0.113320000f, 0.035084593f,-0.007822324f, 0.000718468f,
0.404129868f,-0.112710983f, 0.034884678f,-0.007770072f, 0.000711408f,
0.401465405f,-0.112099857f, 0.034684190f,-0.007717775f, 0.000704373f,
0.398802279f,-0.111486655f, 0.034483143f,-0.007665437f, 0.000697364f,
0.396140548f,-0.110871414f, 0.034281549f,-0.007613061f, 0.000690381f,
0.393480274f,-0.110254166f, 0.034079419f,-0.007560650f, 0.000683424f,
0.390821513f,-0.109634947f, 0.033876767f,-0.007508207f, 0.000676494f,
0.388164325f,-0.109013790f, 0.033673602f,-0.007455735f, 0.000669590f,
0.385508769f,-0.108390730f, 0.033469939f,-0.007403238f, 0.000662714f,
0.382854903f,-0.107765801f, 0.033265787f,-0.007350717f, 0.000655866f,
0.380202785f,-0.107139037f, 0.033061161f,-0.007298177f, 0.000649046f,
0.377552474f,-0.106510472f, 0.032856070f,-0.007245621f, 0.000642253f,
0.374904028f,-0.105880140f, 0.032650528f,-0.007193050f, 0.000635490f,
0.372257505f,-0.105248075f, 0.032444545f,-0.007140469f, 0.000628755f,
0.369612962f,-0.104614312f, 0.032238134f,-0.007087881f, 0.000622049f,
0.366970458f,-0.103978882f, 0.032031306f,-0.007035287f, 0.000615373f,
0.364330050f,-0.103341821f, 0.031824073f,-0.006982692f, 0.000608726f,
0.361691794f,-0.102703162f, 0.031616447f,-0.006930098f, 0.000602110f,
0.359055750f,-0.102062939f, 0.031408439f,-0.006877508f, 0.000595523f,
0.356421972f,-0.101421184f, 0.031200061f,-0.006824926f, 0.000588967f,
0.353790520f,-0.100777932f, 0.030991325f,-0.006772353f, 0.000582442f,
0.351161448f,-0.100133216f, 0.030782241f,-0.006719792f, 0.000575948f,
0.348534815f,-0.099487069f, 0.030572821f,-0.006667247f, 0.000569485f,
0.345910675f,-0.098839524f, 0.030363078f,-0.006614721f, 0.000563054f,
0.343289087f,-0.098190615f, 0.030153021f,-0.006562215f, 0.000556654f,
0.340670105f,-0.097540374f, 0.029942663f,-0.006509734f, 0.000550287f,
0.338053786f,-0.096888834f, 0.029732015f,-0.006457279f, 0.000543951f,
0.335440186f,-0.096236029f, 0.029521089f,-0.006404853f, 0.000537649f,
0.332829360f,-0.095581991f, 0.029309894f,-0.006352460f, 0.000531378f,
0.330221364f,-0.094926753f, 0.029098443f,-0.006300101f, 0.000525141f,
0.327616253f,-0.094270347f, 0.028886747f,-0.006247780f, 0.000518937f,
0.325014082f,-0.093612807f, 0.028674817f,-0.006195500f, 0.000512766f,
0.322414906f,-0.092954163f, 0.028462663f,-0.006143262f, 0.000506629f,
0.319818781f,-0.092294450f, 0.028250298f,-0.006091069f, 0.000500525f,
0.317225760f,-0.091633699f, 0.028037732f,-0.006038925f, 0.000494456f,
0.314635898f,-0.090971943f, 0.027824976f,-0.005986831f, 0.000488420f,
0.312049250f,-0.090309213f, 0.027612040f,-0.005934790f, 0.000482419f,
0.309465869f,-0.089645542f, 0.027398936f,-0.005882806f, 0.000476453f,
0.306885810f,-0.088980961f, 0.027185675f,-0.005830879f, 0.000470521f,
0.304309126f,-0.088315503f, 0.026972268f,-0.005779014f, 0.000464623f,
0.301735870f,-0.087649199f, 0.026758724f,-0.005727211f, 0.000458761f,
0.299166097f,-0.086982081f, 0.026545055f,-0.005675475f, 0.000452934f,
0.296599859f,-0.086314180f, 0.026331272f,-0.005623807f, 0.000447143f,
0.294037209f,-0.085645528f, 0.026117385f,-0.005572209f, 0.000441387f,
0.291478200f,-0.084976157f, 0.025903405f,-0.005520685f, 0.000435666f,
0.288922885f,-0.084306097f, 0.025689341f,-0.005469237f, 0.000429981f,
0.286371316f,-0.083635380f, 0.025475206f,-0.005417866f, 0.000424332f,
0.283823545f,-0.082964036f, 0.025261008f,-0.005366575f, 0.000418720f,
0.281279624f,-0.082292098f, 0.025046759f,-0.005315368f, 0.000413143f,
0.278739606f,-0.081619595f, 0.024832469f,-0.005264245f, 0.000407603f,
0.276203542f,-0.080946559f, 0.024618148f,-0.005213209f, 0.000402099f,
0.273671482f,-0.080273020f, 0.024403807f,-0.005162263f, 0.000396632f,
0.271143479f,-0.079599009f, 0.024189455f,-0.005111409f, 0.000391201f,
0.268619584f,-0.078924556f, 0.023975103f,-0.005060649f, 0.000385807f,
0.266099847f,-0.078249692f, 0.023760760f,-0.005009985f, 0.000380450f,
0.263584318f,-0.077574447f, 0.023546438f,-0.004959420f, 0.000375130f,
0.261073049f,-0.076898851f, 0.023332145f,-0.004908956f, 0.000369847f,
0.258566090f,-0.076222934f, 0.023117893f,-0.004858594f, 0.000364602f,
0.256063490f,-0.075546727f, 0.022903690f,-0.004808337f, 0.000359393f,
0.253565299f,-0.074870258f, 0.022689547f,-0.004758188f, 0.000354222f,
0.251071568f,-0.074193558f, 0.022475473f,-0.004708148f, 0.000349088f,
0.248582344f,-0.073516656f, 0.022261479f,-0.004658219f, 0.000343992f,
0.246097678f,-0.072839582f, 0.022047574f,-0.004608404f, 0.000338934f,
0.243617618f,-0.072162365f, 0.021833767f,-0.004558704f, 0.000333913f,
0.241142213f,-0.071485035f, 0.021620069f,-0.004509122f, 0.000328930f,
0.238671511f,-0.070807621f, 0.021406488f,-0.004459659f, 0.000323984f,
0.236205561f,-0.070130151f, 0.021193035f,-0.004410318f, 0.000319077f,
0.233744411f,-0.069452655f, 0.020979718f,-0.004361100f, 0.000314207f,
0.231288109f,-0.068775161f, 0.020766548f,-0.004312008f, 0.000309375f,
0.228836701f,-0.068097699f, 0.020553533f,-0.004263043f, 0.000304582f,
0.226390237f,-0.067420297f, 0.020340683f,-0.004214207f, 0.000299826f,
0.223948762f,-0.066742982f, 0.020128008f,-0.004165503f, 0.000295108f,
0.221512323f,-0.066065785f, 0.019915515f,-0.004116932f, 0.000290429f,
0.219080968f,-0.065388732f, 0.019703215f,-0.004068496f, 0.000285787f,
0.216654743f,-0.064711852f, 0.019491117f,-0.004020196f, 0.000281184f,
0.214233694f,-0.064035173f, 0.019279229f,-0.003972036f, 0.000276619f,
0.211817866f,-0.063358724f, 0.019067561f,-0.003924015f, 0.000272092f,
0.209407307f,-0.062682530f, 0.018856122f,-0.003876137f, 0.000267604f,
0.207002061f,-0.062006621f, 0.018644920f,-0.003828403f, 0.000263153f,
0.204602173f,-0.061331024f, 0.018433965f,-0.003780814f, 0.000258741f,
0.202207689f,-0.060655766f, 0.018223264f,-0.003733373f, 0.000254367f,
0.199818654f,-0.059980874f, 0.018012827f,-0.003686081f, 0.000250032f,
0.197435111f,-0.059306375f, 0.017802663f,-0.003638940f, 0.000245734f,
0.195057107f,-0.058632298f, 0.017592780f,-0.003591951f, 0.000241475f,
0.192684683f,-0.057958667f, 0.017383186f,-0.003545116f, 0.000237254f,
0.190317885f,-0.057285511f, 0.017173891f,-0.003498437f, 0.000233071f,
0.187956756f,-0.056612855f, 0.016964902f,-0.003451915f, 0.000228927f,
0.185601339f,-0.055940727f, 0.016756228f,-0.003405553f, 0.000224821f,
0.183251678f,-0.055269152f, 0.016547878f,-0.003359350f, 0.000220752f,
0.180907815f,-0.054598157f, 0.016339859f,-0.003313310f, 0.000216722f,
0.178569792f,-0.053927768f, 0.016132180f,-0.003267434f, 0.000212731f,
0.176237654f,-0.053258011f, 0.015924850f,-0.003221722f, 0.000208777f,
0.173911440f,-0.052588912f, 0.015717875f,-0.003176177f, 0.000204861f,
0.171591194f,-0.051920496f, 0.015511264f,-0.003130800f, 0.000200983f,
0.169276957f,-0.051252790f, 0.015305026f,-0.003085592f, 0.000197143f,
0.166968771f,-0.050585817f, 0.015099168f,-0.003040555f, 0.000193341f,
0.164666676f,-0.049919605f, 0.014893698f,-0.002995691f, 0.000189577f,
0.162370713f,-0.049254177f, 0.014688624f,-0.002951000f, 0.000185851f,
0.160080923f,-0.048589560f, 0.014483954f,-0.002906485f, 0.000182163f,
0.157797346f,-0.047925777f, 0.014279695f,-0.002862145f, 0.000178512f,
0.155520023f,-0.047262854f, 0.014075855f,-0.002817984f, 0.000174899f,
0.153248993f,-0.046600815f, 0.013872442f,-0.002774001f, 0.000171324f,
0.150984296f,-0.045939685f, 0.013669462f,-0.002730199f, 0.000167786f,
0.148725971f,-0.045279488f, 0.013466925f,-0.002686579f, 0.000164285f,
0.146474057f,-0.044620249f, 0.013264837f,-0.002643141f, 0.000160822f,
0.144228593f,-0.043961991f, 0.013063205f,-0.002599888f, 0.000157396f,
0.141989618f,-0.043304738f, 0.012862038f,-0.002556820f, 0.000154008f,
0.139757169f,-0.042648514f, 0.012661341f,-0.002513938f, 0.000150656f,
0.137531285f,-0.041993344f, 0.012461123f,-0.002471244f, 0.000147342f,
0.135312004f,-0.041339249f, 0.012261390f,-0.002428739f, 0.000144064f,
0.133099362f,-0.040686254f, 0.012062150f,-0.002386425f, 0.000140824f,
0.130893398f,-0.040034383f, 0.011863409f,-0.002344301f, 0.000137620f,
0.128694148f,-0.039383657f, 0.011665175f,-0.002302370f, 0.000134453f,
0.126501649f,-0.038734100f, 0.011467454f,-0.002260631f, 0.000131323f,
0.124315937f,-0.038085735f, 0.011270254f,-0.002219088f, 0.000128229f,
0.122137048f,-0.037438584f, 0.011073581f,-0.002177740f, 0.000125171f,
0.119965018f,-0.036792671f, 0.010877441f,-0.002136588f, 0.000122150f,
0.117799883f,-0.036148017f, 0.010681842f,-0.002095634f, 0.000119165f,
0.115641679f,-0.035504644f, 0.010486791f,-0.002054878f, 0.000116216f,
0.113490439f,-0.034862575f, 0.010292293f,-0.002014322f, 0.000113304f,
0.111346199f,-0.034221832f, 0.010098355f,-0.001973967f, 0.000110427f,
0.109208994f,-0.033582436f, 0.009904984f,-0.001933813f, 0.000107585f,
0.107078857f,-0.032944409f, 0.009712186f,-0.001893861f, 0.000104780f,
0.104955823f,-0.032307773f, 0.009519967f,-0.001854112f, 0.000102010f,
0.102839925f,-0.031672548f, 0.009328334f,-0.001814568f, 0.000099275f,
0.100731196f,-0.031038757f, 0.009137293f,-0.001775228f, 0.000096576f,
0.098629671f,-0.030406420f, 0.008946849f,-0.001736095f, 0.000093911f,
0.096535380f,-0.029775558f, 0.008757010f,-0.001697168f, 0.000091282f,
0.094448358f,-0.029146192f, 0.008567781f,-0.001658448f, 0.000088688f,
0.092368636f,-0.028518343f, 0.008379168f,-0.001619937f, 0.000086128f,
0.090296247f,-0.027892030f, 0.008191177f,-0.001581635f, 0.000083603f,
0.088231221f,-0.027267275f, 0.008003813f,-0.001543542f, 0.000081113f,
0.086173591f,-0.026644097f, 0.007817084f,-0.001505661f, 0.000078656f,
0.084123388f,-0.026022517f, 0.007630993f,-0.001467990f, 0.000076234f,
0.082080642f,-0.025402554f, 0.007445548f,-0.001430532f, 0.000073846f,
0.080045384f,-0.024784228f, 0.007260753f,-0.001393286f, 0.000071492f,
0.078017645f,-0.024167559f, 0.007076615f,-0.001356254f, 0.000069172f,
0.075997454f,-0.023552566f, 0.006893138f,-0.001319435f, 0.000066885f,
0.073984841f,-0.022939269f, 0.006710328f,-0.001282832f, 0.000064632f,
0.071979836f,-0.022327686f, 0.006528191f,-0.001246443f, 0.000062412f,
0.069982467f,-0.021717836f, 0.006346731f,-0.001210271f, 0.000060225f,
0.067992764f,-0.021109739f, 0.006165955f,-0.001174315f, 0.000058072f,
0.066010755f,-0.020503413f, 0.005985866f,-0.001138576f, 0.000055951f,
0.064036468f,-0.019898876f, 0.005806472f,-0.001103055f, 0.000053862f,
0.062069932f,-0.019296147f, 0.005627775f,-0.001067751f, 0.000051807f,
0.060111174f,-0.018695244f, 0.005449782f,-0.001032667f, 0.000049783f,
0.058160222f,-0.018096185f, 0.005272497f,-0.000997801f, 0.000047792f,
0.056217102f,-0.017498988f, 0.005095926f,-0.000963156f, 0.000045833f,
0.054281842f,-0.016903671f, 0.004920073f,-0.000928730f, 0.000043905f,
0.052354468f,-0.016310251f, 0.004744943f,-0.000894525f, 0.000042010f,
0.050435006f,-0.015718746f, 0.004570540f,-0.000860541f, 0.000040146f,
0.048523483f,-0.015129173f, 0.004396870f,-0.000826779f, 0.000038313f,
0.046619924f,-0.014541549f, 0.004223937f,-0.000793238f, 0.000036511f,
0.044724355f,-0.013955892f, 0.004051745f,-0.000759920f, 0.000034740f,
0.042836800f,-0.013372217f, 0.003880299f,-0.000726824f, 0.000033000f,
0.040957285f,-0.012790543f, 0.003709604f,-0.000693951f, 0.000031291f,
0.039085833f,-0.012210884f, 0.003539663f,-0.000661301f, 0.000029612f,
0.037222470f,-0.011633259f, 0.003370481f,-0.000628876f, 0.000027964f,
0.035367219f,-0.011057682f, 0.003202062f,-0.000596674f, 0.000026345f,
0.033520103f,-0.010484170f, 0.003034411f,-0.000564696f, 0.000024756f,
0.031681147f,-0.009912739f, 0.002867532f,-0.000532943f, 0.000023197f,
0.029850373f,-0.009343404f, 0.002701428f,-0.000501414f, 0.000021668f,
0.028027804f,-0.008776182f, 0.002536104f,-0.000470111f, 0.000020168f,
0.026213462f,-0.008211087f, 0.002371564f,-0.000439032f, 0.000018697f,
0.024407370f,-0.007648135f, 0.002207811f,-0.000408180f, 0.000017255f,
0.022609549f,-0.007087341f, 0.002044849f,-0.000377553f, 0.000015842f,
0.020820021f,-0.006528720f, 0.001882682f,-0.000347151f, 0.000014457f,
0.019038808f,-0.005972286f, 0.001721315f,-0.000316976f, 0.000013101f,
0.017265930f,-0.005418056f, 0.001560750f,-0.000287027f, 0.000011773f,
0.015501408f,-0.004866042f, 0.001400991f,-0.000257305f, 0.000010473f,
0.013745262f,-0.004316260f, 0.001242041f,-0.000227808f, 0.000009201f,
0.011997513f,-0.003768723f, 0.001083905f,-0.000198539f, 0.000007956f,
0.010258180f,-0.003223445f, 0.000926585f,-0.000169495f, 0.000006739f,
0.008527283f,-0.002680442f, 0.000770086f,-0.000140679f, 0.000005549f,
0.006804842f,-0.002139725f, 0.000614409f,-0.000112090f, 0.000004386f,
0.005090874f,-0.001601309f, 0.000459559f,-0.000083727f, 0.000003250f,
0.003385399f,-0.001065208f, 0.000305539f,-0.000055591f, 0.000002140f,
0.001688435f,-0.000531434f, 0.000152351f,-0.000027682f, 0.000001057f,
};
File diff suppressed because it is too large Load Diff
+127
View File
@@ -0,0 +1,127 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 SDL_bool iscapture, const char *fname, int flags, SDL_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 SDL_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(iscapture, fname, NULL, (void *)(uintptr_t)dummyhandle);
}
} else {
close(audio_fd);
}
}
}
static SDL_bool test_stub(int fd)
{
return SDL_TRUE;
}
static void SDL_EnumUnixAudioDevices_Internal(const SDL_bool iscapture, const SDL_bool classic, SDL_bool (*test)(int))
{
const int flags = iscapture ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT;
const char *audiodev;
char audiopath[1024];
if (!test) {
test = test_stub;
}
// Figure out what our audio device is
audiodev = SDL_getenv("SDL_PATH_DSP");
if (!audiodev) {
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(iscapture, 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(iscapture, audiopath, flags, test);
}
}
}
void SDL_EnumUnixAudioDevices(const SDL_bool classic, SDL_bool (*test)(int))
{
SDL_EnumUnixAudioDevices_Internal(SDL_TRUE, classic, test);
SDL_EnumUnixAudioDevices_Internal(SDL_FALSE, classic, test);
}
#endif // Audio device selection
+41
View File
@@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 SDL_bool classic, SDL_bool (*test)(int));
#endif // SDL_audiodev_c_h_
+514
View File
@@ -0,0 +1,514 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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"
#define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq))
struct SDL_AudioTrack
{
SDL_AudioSpec spec;
SDL_bool flushed;
SDL_AudioTrack *next;
size_t (*avail)(void *ctx);
int (*write)(void *ctx, const Uint8 *buf, size_t len);
size_t (*read)(void *ctx, Uint8 *buf, size_t len, SDL_bool advance);
void (*destroy)(void *ctx);
};
struct SDL_AudioQueue
{
SDL_AudioTrack *head;
SDL_AudioTrack *tail;
size_t chunk_size;
};
typedef struct SDL_AudioChunk SDL_AudioChunk;
struct SDL_AudioChunk
{
SDL_AudioChunk *next;
size_t head;
size_t tail;
Uint8 data[SDL_VARIABLE_LENGTH_ARRAY];
};
typedef struct SDL_ChunkedAudioTrack
{
SDL_AudioTrack track;
size_t chunk_size;
SDL_AudioChunk *head;
SDL_AudioChunk *tail;
size_t queued_bytes;
SDL_AudioChunk *free_chunks;
size_t num_free_chunks;
} SDL_ChunkedAudioTrack;
static void DestroyAudioChunk(SDL_AudioChunk *chunk)
{
SDL_free(chunk);
}
static void DestroyAudioChunks(SDL_AudioChunk *chunk)
{
while (chunk) {
SDL_AudioChunk *next = chunk->next;
DestroyAudioChunk(chunk);
chunk = next;
}
}
static void ResetAudioChunk(SDL_AudioChunk *chunk)
{
chunk->next = NULL;
chunk->head = 0;
chunk->tail = 0;
}
static SDL_AudioChunk *CreateAudioChunk(size_t chunk_size)
{
SDL_AudioChunk *chunk = (SDL_AudioChunk *)SDL_malloc(sizeof(*chunk) + chunk_size);
if (!chunk) {
return NULL;
}
ResetAudioChunk(chunk);
return chunk;
}
static void DestroyAudioTrackChunk(SDL_ChunkedAudioTrack *track, SDL_AudioChunk *chunk)
{
// Keeping a list of free chunks reduces memory allocations,
// But also increases the amount of work to perform when freeing the track.
const size_t max_free_bytes = 64 * 1024;
if (track->chunk_size * track->num_free_chunks < max_free_bytes) {
chunk->next = track->free_chunks;
track->free_chunks = chunk;
++track->num_free_chunks;
} else {
DestroyAudioChunk(chunk);
}
}
static SDL_AudioChunk *CreateAudioTrackChunk(SDL_ChunkedAudioTrack *track)
{
if (track->num_free_chunks > 0) {
SDL_AudioChunk *chunk = track->free_chunks;
track->free_chunks = chunk->next;
--track->num_free_chunks;
ResetAudioChunk(chunk);
return chunk;
}
return CreateAudioChunk(track->chunk_size);
}
static size_t AvailChunkedAudioTrack(void *ctx)
{
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx;
return track->queued_bytes;
}
static int WriteToChunkedAudioTrack(void *ctx, const Uint8 *data, size_t len)
{
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx;
SDL_AudioChunk *chunk = track->tail;
// Handle the first chunk
if (!chunk) {
chunk = CreateAudioTrackChunk(track);
if (!chunk) {
return -1;
}
SDL_assert((track->head == NULL) && (track->tail == NULL) && (track->queued_bytes == 0));
track->head = chunk;
track->tail = chunk;
}
size_t total = 0;
size_t old_tail = chunk->tail;
size_t chunk_size = track->chunk_size;
while (chunk) {
size_t to_write = chunk_size - chunk->tail;
to_write = SDL_min(to_write, len - total);
SDL_memcpy(&chunk->data[chunk->tail], &data[total], to_write);
total += to_write;
chunk->tail += to_write;
if (total == len) {
break;
}
SDL_AudioChunk *next = CreateAudioTrackChunk(track);
chunk->next = next;
chunk = next;
}
// Roll back the changes if we couldn't write all the data
if (!chunk) {
chunk = track->tail;
SDL_AudioChunk *next = chunk->next;
chunk->next = NULL;
chunk->tail = old_tail;
DestroyAudioChunks(next);
return -1;
}
track->tail = chunk;
track->queued_bytes += total;
return 0;
}
static size_t ReadFromChunkedAudioTrack(void *ctx, Uint8 *data, size_t len, SDL_bool advance)
{
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx;
SDL_AudioChunk *chunk = track->head;
size_t total = 0;
size_t head = 0;
while (chunk) {
head = chunk->head;
size_t to_read = chunk->tail - head;
to_read = SDL_min(to_read, len - total);
SDL_memcpy(&data[total], &chunk->data[head], to_read);
total += to_read;
SDL_AudioChunk *next = chunk->next;
if (total == len) {
head += to_read;
break;
}
if (advance) {
DestroyAudioTrackChunk(track, chunk);
}
chunk = next;
}
if (advance) {
if (chunk) {
chunk->head = head;
track->head = chunk;
} else {
track->head = NULL;
track->tail = NULL;
}
track->queued_bytes -= total;
}
return total;
}
static void DestroyChunkedAudioTrack(void *ctx)
{
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx;
DestroyAudioChunks(track->head);
DestroyAudioChunks(track->free_chunks);
SDL_free(track);
}
static SDL_AudioTrack *CreateChunkedAudioTrack(const SDL_AudioSpec *spec, size_t chunk_size)
{
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)SDL_calloc(1, sizeof(*track));
if (!track) {
return NULL;
}
SDL_copyp(&track->track.spec, spec);
track->track.avail = AvailChunkedAudioTrack;
track->track.write = WriteToChunkedAudioTrack;
track->track.read = ReadFromChunkedAudioTrack;
track->track.destroy = DestroyChunkedAudioTrack;
track->chunk_size = chunk_size;
return &track->track;
}
SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size)
{
SDL_AudioQueue *queue = (SDL_AudioQueue *)SDL_calloc(1, sizeof(*queue));
if (!queue) {
return NULL;
}
queue->chunk_size = chunk_size;
return queue;
}
void SDL_DestroyAudioQueue(SDL_AudioQueue *queue)
{
SDL_ClearAudioQueue(queue);
SDL_free(queue);
}
void SDL_ClearAudioQueue(SDL_AudioQueue *queue)
{
SDL_AudioTrack *track = queue->head;
queue->head = NULL;
queue->tail = NULL;
while (track) {
SDL_AudioTrack *next = track->next;
track->destroy(track);
track = next;
}
}
static void SDL_FlushAudioTrack(SDL_AudioTrack *track)
{
track->flushed = SDL_TRUE;
track->write = NULL;
}
void SDL_FlushAudioQueue(SDL_AudioQueue *queue)
{
SDL_AudioTrack *track = queue->tail;
if (track) {
SDL_FlushAudioTrack(track);
}
}
void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
{
SDL_AudioTrack *track = queue->head;
for (;;) {
SDL_bool flushed = track->flushed;
SDL_AudioTrack *next = track->next;
track->destroy(track);
track = next;
if (flushed) {
break;
}
}
queue->head = track;
if (!track) {
queue->tail = NULL;
}
}
size_t SDL_GetAudioQueueChunkSize(SDL_AudioQueue *queue)
{
return queue->chunk_size;
}
SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uint8 *data, size_t len, size_t chunk_size)
{
SDL_AudioTrack *track = CreateChunkedAudioTrack(spec, chunk_size);
if (!track) {
return NULL;
}
if (track->write(track, data, len) != 0) {
track->destroy(track);
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 (!AUDIO_SPECS_EQUAL(tail->spec, track->spec)) {
SDL_FlushAudioTrack(tail);
}
tail->next = track;
} else {
queue->head = track;
}
queue->tail = track;
}
int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len)
{
if (len == 0) {
return 0;
}
SDL_AudioTrack *track = queue->tail;
if ((track) && !AUDIO_SPECS_EQUAL(track->spec, *spec)) {
SDL_FlushAudioTrack(track);
}
if ((!track) || (!track->write)) {
SDL_AudioTrack *new_track = CreateChunkedAudioTrack(spec, queue->chunk_size);
if (!new_track) {
return -1;
}
if (track) {
track->next = new_track;
} else {
queue->head = new_track;
}
queue->tail = new_track;
track = new_track;
}
return track->write(track, data, len);
}
void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue)
{
return queue->head;
}
size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed)
{
SDL_AudioTrack *iter = (SDL_AudioTrack *)(*inout_iter);
SDL_assert(iter != NULL);
SDL_copyp(out_spec, &iter->spec);
SDL_bool flushed = SDL_FALSE;
size_t queued_bytes = 0;
while (iter) {
SDL_AudioTrack *track = iter;
iter = iter->next;
size_t avail = track->avail(track);
if (avail >= SDL_SIZE_MAX - queued_bytes) {
queued_bytes = SDL_SIZE_MAX;
flushed = SDL_FALSE;
break;
}
queued_bytes += avail;
flushed = track->flushed;
if (flushed) {
break;
}
}
*inout_iter = iter;
*out_flushed = flushed;
return queued_bytes;
}
int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
{
size_t total = 0;
SDL_AudioTrack *track = queue->head;
for (;;) {
if (!track) {
return SDL_SetError("Reading past end of queue");
}
total += track->read(track, &data[total], len - total, SDL_TRUE);
if (total == len) {
return 0;
}
if (track->flushed) {
return SDL_SetError("Reading past end of flushed track");
}
SDL_AudioTrack *next = track->next;
if (!next) {
return SDL_SetError("Reading past end of incomplete track");
}
queue->head = next;
track->destroy(track);
track = next;
}
}
int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
{
size_t total = 0;
SDL_AudioTrack *track = queue->head;
for (;;) {
if (!track) {
return SDL_SetError("Peeking past end of queue");
}
total += track->read(track, &data[total], len - total, SDL_FALSE);
if (total == len) {
return 0;
}
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);
return 0;
}
track = track->next;
}
}
+77
View File
@@ -0,0 +1,77 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 struct SDL_AudioQueue SDL_AudioQueue;
typedef struct SDL_AudioTrack SDL_AudioTrack;
// Create a new audio queue
SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size);
// Destroy an audio queue
void SDL_DestroyAudioQueue(SDL_AudioQueue *queue);
// Completely clear the queue
void SDL_ClearAudioQueue(SDL_AudioQueue *queue);
// Mark the last track as flushed
void SDL_FlushAudioQueue(SDL_AudioQueue *queue);
// Pop the current head track
// REQUIRES: The head track must exist, and must have been flushed
void SDL_PopAudioQueueHead(SDL_AudioQueue *queue);
// Get the chunk size, mostly for use with SDL_CreateChunkedAudioTrack
// This can be called from any thread
size_t SDL_GetAudioQueueChunkSize(SDL_AudioQueue *queue);
// Write data to the end of queue
// REQUIRES: If the spec has changed, the last track must have been flushed
int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len);
// Create a track without needing to hold any locks
SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uint8 *data, size_t len, size_t chunk_size);
// Add a track to the end of the queue
// REQUIRES: `track != NULL`
void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track);
// Iterate over the tracks in the queue
void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue);
// Query and update the track iterator
// REQUIRES: `*inout_iter != NULL` (a valid iterator)
size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed);
// Read data from the start of the queue
// REQUIRES: There must be enough data in the queue
int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len);
// Peek into the start of the queue
// REQUIRES: There must be enough data in the queue, unless it has been flushed, in which case missing data is filled with silence.
int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len);
#endif // SDL_audioqueue_h_
+335
View File
@@ -0,0 +1,335 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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/
#include "SDL_audio_resampler_filter.h"
// 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)
#define RESAMPLER_FILTER_INTERP_BITS (32 - RESAMPLER_BITS_PER_ZERO_CROSSING)
#define RESAMPLER_FILTER_INTERP_RANGE (1 << RESAMPLER_FILTER_INTERP_BITS)
#define RESAMPLER_SAMPLES_PER_FRAME (RESAMPLER_ZERO_CROSSINGS * 2)
#define RESAMPLER_FULL_FILTER_SIZE (RESAMPLER_SAMPLES_PER_FRAME * (RESAMPLER_SAMPLES_PER_ZERO_CROSSING + 1))
static void ResampleFrame_Scalar(const float *src, float *dst, const float *raw_filter, float interp, int chans)
{
int i, chan;
float filter[RESAMPLER_SAMPLES_PER_FRAME];
// Interpolate between the nearest two filters
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
filter[i] = (raw_filter[i] * (1.0f - interp)) + (raw_filter[i + RESAMPLER_SAMPLES_PER_FRAME] * interp);
}
if (chans == 2) {
float out[2];
out[0] = 0.0f;
out[1] = 0.0f;
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
const float scale = filter[i];
out[0] += src[i * 2 + 0] * scale;
out[1] += src[i * 2 + 1] * scale;
}
dst[0] = out[0];
dst[1] = out[1];
return;
}
if (chans == 1) {
float out = 0.0f;
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
out += src[i] * filter[i];
}
dst[0] = out;
return;
}
for (chan = 0; chan < chans; chan++) {
float f = 0.0f;
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
f += src[i * chans + chan] * filter[i];
}
dst[chan] = f;
}
}
#ifdef SDL_SSE_INTRINSICS
static void SDL_TARGETING("sse") ResampleFrame_SSE(const float *src, float *dst, const float *raw_filter, float interp, int chans)
{
#if RESAMPLER_SAMPLES_PER_FRAME != 10
#error Invalid samples per frame
#endif
// Load the filter
__m128 f0 = _mm_loadu_ps(raw_filter + 0);
__m128 f1 = _mm_loadu_ps(raw_filter + 4);
__m128 f2 = _mm_loadl_pi(_mm_setzero_ps(), (const __m64 *)(raw_filter + 8));
__m128 g0 = _mm_loadu_ps(raw_filter + 10);
__m128 g1 = _mm_loadu_ps(raw_filter + 14);
__m128 g2 = _mm_loadl_pi(_mm_setzero_ps(), (const __m64 *)(raw_filter + 18));
__m128 interp1 = _mm_set1_ps(interp);
__m128 interp2 = _mm_sub_ps(_mm_set1_ps(1.0f), _mm_set1_ps(interp));
// Linear interpolate the filter
f0 = _mm_add_ps(_mm_mul_ps(f0, interp2), _mm_mul_ps(g0, interp1));
f1 = _mm_add_ps(_mm_mul_ps(f1, interp2), _mm_mul_ps(g1, interp1));
f2 = _mm_add_ps(_mm_mul_ps(f2, interp2), _mm_mul_ps(g2, interp1));
if (chans == 2) {
// Duplicate each of the filter elements
g0 = _mm_unpackhi_ps(f0, f0);
f0 = _mm_unpacklo_ps(f0, f0);
g1 = _mm_unpackhi_ps(f1, f1);
f1 = _mm_unpacklo_ps(f1, f1);
f2 = _mm_unpacklo_ps(f2, f2);
// Multiply the filter by the input
f0 = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
g0 = _mm_mul_ps(g0, _mm_loadu_ps(src + 4));
f1 = _mm_mul_ps(f1, _mm_loadu_ps(src + 8));
g1 = _mm_mul_ps(g1, _mm_loadu_ps(src + 12));
f2 = _mm_mul_ps(f2, _mm_loadu_ps(src + 16));
// Calculate the sum
f0 = _mm_add_ps(_mm_add_ps(_mm_add_ps(f0, g0), _mm_add_ps(f1, g1)), f2);
f0 = _mm_add_ps(f0, _mm_movehl_ps(f0, f0));
// Store the result
_mm_storel_pi((__m64 *)dst, f0);
return;
}
if (chans == 1) {
// Multiply the filter by the input
f0 = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
f1 = _mm_mul_ps(f1, _mm_loadu_ps(src + 4));
f2 = _mm_mul_ps(f2, _mm_loadl_pi(_mm_setzero_ps(), (const __m64 *)(src + 8)));
// Calculate the sum
f0 = _mm_add_ps(f0, f1);
f0 = _mm_add_ps(_mm_add_ps(f0, f2), _mm_movehl_ps(f0, f0));
f0 = _mm_add_ss(f0, _mm_shuffle_ps(f0, f0, _MM_SHUFFLE(1, 1, 1, 1)));
// Store the result
_mm_store_ss(dst, f0);
return;
}
float filter[RESAMPLER_SAMPLES_PER_FRAME];
_mm_storeu_ps(filter + 0, f0);
_mm_storeu_ps(filter + 4, f1);
_mm_storel_pi((__m64 *)(filter + 8), f2);
int i, chan = 0;
for (; chan + 4 <= chans; chan += 4) {
f0 = _mm_setzero_ps();
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
f0 = _mm_add_ps(f0, _mm_mul_ps(_mm_loadu_ps(&src[i * chans + chan]), _mm_load1_ps(&filter[i])));
}
_mm_storeu_ps(&dst[chan], f0);
}
for (; chan < chans; chan++) {
f0 = _mm_setzero_ps();
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
f0 = _mm_add_ss(f0, _mm_mul_ss(_mm_load_ss(&src[i * chans + chan]), _mm_load_ss(&filter[i])));
}
_mm_store_ss(&dst[chan], f0);
}
}
#endif
static void (*ResampleFrame)(const float *src, float *dst, const float *raw_filter, float interp, int chans);
static float FullResamplerFilter[RESAMPLER_FULL_FILTER_SIZE];
void SDL_SetupAudioResampler(void)
{
static SDL_bool setup = SDL_FALSE;
if (setup) {
return;
}
// Build a table combining the left and right wings, for faster access
int i, j;
for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
for (j = 0; j < RESAMPLER_ZERO_CROSSINGS; j++) {
int lwing = (i * RESAMPLER_SAMPLES_PER_FRAME) + (RESAMPLER_ZERO_CROSSINGS - 1) - j;
int rwing = (RESAMPLER_FULL_FILTER_SIZE - 1) - lwing;
float value = ResamplerFilter[(i * RESAMPLER_ZERO_CROSSINGS) + j];
FullResamplerFilter[lwing] = value;
FullResamplerFilter[rwing] = value;
}
}
for (i = 0; i < RESAMPLER_ZERO_CROSSINGS; ++i) {
int rwing = i + RESAMPLER_ZERO_CROSSINGS;
int lwing = (RESAMPLER_FULL_FILTER_SIZE - 1) - rwing;
FullResamplerFilter[lwing] = 0.0f;
FullResamplerFilter[rwing] = 0.0f;
}
ResampleFrame = ResampleFrame_Scalar;
#ifdef SDL_SSE_INTRINSICS
if (SDL_HasSSE()) {
ResampleFrame = ResampleFrame_SSE;
}
#endif
setup = SDL_TRUE;
}
Sint64 SDL_GetResampleRate(int src_rate, int dst_rate)
{
SDL_assert(src_rate > 0);
SDL_assert(dst_rate > 0);
Sint64 sample_rate = ((Sint64)src_rate << 32) / (Sint64)dst_rate;
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 Sint64 ResamplerAdd(Sint64 a, Sint64 b, Sint64 *ret)
{
if ((b > 0) && (a > SDL_MAX_SINT64 - b)) {
return -1;
}
*ret = a + b;
return 0;
}
SDL_FORCE_INLINE Sint64 ResamplerMul(Sint64 a, Sint64 b, Sint64 *ret)
{
if ((b > 0) && (a > SDL_MAX_SINT64 / b)) {
return -1;
}
*ret = a * b;
return 0;
}
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;
SDL_assert(resample_rate > 0);
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 float *filter = &FullResamplerFilter[(srcfraction >> RESAMPLER_FILTER_INTERP_BITS) * RESAMPLER_SAMPLES_PER_FRAME];
const float interp = (float)(srcfraction & (RESAMPLER_FILTER_INTERP_RANGE - 1)) * (1.0f / RESAMPLER_FILTER_INTERP_RANGE);
const float *frame = &src[(srcindex - (RESAMPLER_ZERO_CROSSINGS - 1)) * chans];
ResampleFrame(frame, dst, filter, interp, chans);
dst += chans;
}
*inout_resample_offset = srcpos - ((Sint64)inframes << 32);
}
+43
View File
@@ -0,0 +1,43 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_
+990
View File
@@ -0,0 +1,990 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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"
// TODO: NEON is disabled until https://github.com/libsdl-org/SDL/issues/8352 can be fixed
#undef SDL_NEON_INTRINSICS
#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
// end fallback scalar converters
#ifdef SDL_SSE2_INTRINSICS
static void SDL_TARGETING("sse2") SDL_Convert_S8_to_F32_SSE2(float *dst, const Sint8 *src, int num_samples)
{
int i = 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)");
while (i >= 16) {
i -= 16;
const __m128i bytes = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i]), flipper);
const __m128i shorts1 = _mm_unpacklo_epi8(bytes, zero);
const __m128i shorts2 = _mm_unpackhi_epi8(bytes, zero);
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts2, caster)), offset);
const __m128 floats4 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts2, caster)), offset);
_mm_storeu_ps(&dst[i], floats1);
_mm_storeu_ps(&dst[i + 4], floats2);
_mm_storeu_ps(&dst[i + 8], floats3);
_mm_storeu_ps(&dst[i + 12], floats4);
}
while (i) {
--i;
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint8)src[i] ^ 0x47800080u)), offset));
}
}
static void SDL_TARGETING("sse2") SDL_Convert_U8_to_F32_SSE2(float *dst, const Uint8 *src, int num_samples)
{
int i = 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)");
while (i >= 16) {
i -= 16;
const __m128i bytes = _mm_loadu_si128((const __m128i *)&src[i]);
const __m128i shorts1 = _mm_unpacklo_epi8(bytes, zero);
const __m128i shorts2 = _mm_unpackhi_epi8(bytes, zero);
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts2, caster)), offset);
const __m128 floats4 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts2, caster)), offset);
_mm_storeu_ps(&dst[i], floats1);
_mm_storeu_ps(&dst[i + 4], floats2);
_mm_storeu_ps(&dst[i + 8], floats3);
_mm_storeu_ps(&dst[i + 12], floats4);
}
while (i) {
--i;
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint8)src[i] ^ 0x47800000u)), offset));
}
}
static void SDL_TARGETING("sse2") SDL_Convert_S16_to_F32_SSE2(float *dst, const Sint16 *src, int num_samples)
{
int i = 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)");
while (i >= 16) {
i -= 16;
const __m128i shorts1 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i]), flipper);
const __m128i shorts2 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i + 8]), flipper);
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts2, caster)), offset);
const __m128 floats4 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts2, caster)), offset);
_mm_storeu_ps(&dst[i], floats1);
_mm_storeu_ps(&dst[i + 4], floats2);
_mm_storeu_ps(&dst[i + 8], floats3);
_mm_storeu_ps(&dst[i + 12], floats4);
}
while (i) {
--i;
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint16)src[i] ^ 0x43808000u)), offset));
}
}
static void SDL_TARGETING("sse2") SDL_Convert_S32_to_F32_SSE2(float *dst, const Sint32 *src, int num_samples)
{
int i = num_samples;
// dst[i] = f32(src[i]) / f32(0x80000000)
const __m128 scaler = _mm_set1_ps(DIVBY2147483648);
LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using SSE2)");
while (i >= 16) {
i -= 16;
const __m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i]);
const __m128i ints2 = _mm_loadu_si128((const __m128i *)&src[i + 4]);
const __m128i ints3 = _mm_loadu_si128((const __m128i *)&src[i + 8]);
const __m128i ints4 = _mm_loadu_si128((const __m128i *)&src[i + 12]);
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);
const __m128 floats4 = _mm_mul_ps(_mm_cvtepi32_ps(ints4), scaler);
_mm_storeu_ps(&dst[i], floats1);
_mm_storeu_ps(&dst[i + 4], floats2);
_mm_storeu_ps(&dst[i + 8], floats3);
_mm_storeu_ps(&dst[i + 12], floats4);
}
while (i) {
--i;
_mm_store_ss(&dst[i], _mm_mul_ss(_mm_cvt_si2ss(_mm_setzero_ps(), src[i]), scaler));
}
}
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S8_SSE2(Sint8 *dst, const float *src, int num_samples)
{
int i = 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)");
while (i >= 16) {
const __m128 floats1 = _mm_loadu_ps(&src[0]);
const __m128 floats2 = _mm_loadu_ps(&src[4]);
const __m128 floats3 = _mm_loadu_ps(&src[8]);
const __m128 floats4 = _mm_loadu_ps(&src[12]);
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 ints4 = _mm_castps_si128(_mm_add_ps(floats4, offset));
const __m128i shorts1 = _mm_and_si128(_mm_packs_epi16(ints1, ints2), mask);
const __m128i shorts2 = _mm_and_si128(_mm_packs_epi16(ints3, ints4), mask);
const __m128i bytes = _mm_packus_epi16(shorts1, shorts2);
_mm_storeu_si128((__m128i*)dst, bytes);
i -= 16;
src += 16;
dst += 16;
}
while (i) {
const __m128i ints = _mm_castps_si128(_mm_add_ss(_mm_load_ss(src), offset));
*dst = (Sint8)(_mm_cvtsi128_si32(_mm_packs_epi16(ints, ints)) & 0xFF);
--i;
++src;
++dst;
}
}
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_U8_SSE2(Uint8 *dst, const float *src, int num_samples)
{
int i = 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)");
while (i >= 16) {
const __m128 floats1 = _mm_loadu_ps(&src[0]);
const __m128 floats2 = _mm_loadu_ps(&src[4]);
const __m128 floats3 = _mm_loadu_ps(&src[8]);
const __m128 floats4 = _mm_loadu_ps(&src[12]);
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 ints4 = _mm_castps_si128(_mm_add_ps(floats4, offset));
const __m128i shorts1 = _mm_and_si128(_mm_packus_epi16(ints1, ints2), mask);
const __m128i shorts2 = _mm_and_si128(_mm_packus_epi16(ints3, ints4), mask);
const __m128i bytes = _mm_packus_epi16(shorts1, shorts2);
_mm_storeu_si128((__m128i*)dst, bytes);
i -= 16;
src += 16;
dst += 16;
}
while (i) {
const __m128i ints = _mm_castps_si128(_mm_add_ss(_mm_load_ss(src), offset));
*dst = (Uint8)(_mm_cvtsi128_si32(_mm_packus_epi16(ints, ints)) & 0xFF);
--i;
++src;
++dst;
}
}
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S16_SSE2(Sint16 *dst, const float *src, int num_samples)
{
int i = 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)");
while (i >= 16) {
const __m128 floats1 = _mm_loadu_ps(&src[0]);
const __m128 floats2 = _mm_loadu_ps(&src[4]);
const __m128 floats3 = _mm_loadu_ps(&src[8]);
const __m128 floats4 = _mm_loadu_ps(&src[12]);
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 ints4 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats4, offset)), _mm_castps_si128(offset));
const __m128i shorts1 = _mm_packs_epi32(ints1, ints2);
const __m128i shorts2 = _mm_packs_epi32(ints3, ints4);
_mm_storeu_si128((__m128i*)&dst[0], shorts1);
_mm_storeu_si128((__m128i*)&dst[8], shorts2);
i -= 16;
src += 16;
dst += 16;
}
while (i) {
const __m128i ints = _mm_sub_epi32(_mm_castps_si128(_mm_add_ss(_mm_load_ss(src), offset)), _mm_castps_si128(offset));
*dst = (Sint16)(_mm_cvtsi128_si32(_mm_packs_epi32(ints, ints)) & 0xFFFF);
--i;
++src;
++dst;
}
}
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S32_SSE2(Sint32 *dst, const float *src, int num_samples)
{
int i = 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)");
while (i >= 16) {
const __m128 floats1 = _mm_loadu_ps(&src[0]);
const __m128 floats2 = _mm_loadu_ps(&src[4]);
const __m128 floats3 = _mm_loadu_ps(&src[8]);
const __m128 floats4 = _mm_loadu_ps(&src[12]);
const __m128 values1 = _mm_mul_ps(floats1, limit);
const __m128 values2 = _mm_mul_ps(floats2, limit);
const __m128 values3 = _mm_mul_ps(floats3, limit);
const __m128 values4 = _mm_mul_ps(floats4, limit);
const __m128i ints1 = _mm_xor_si128(_mm_cvttps_epi32(values1), _mm_castps_si128(_mm_cmpge_ps(values1, limit)));
const __m128i ints2 = _mm_xor_si128(_mm_cvttps_epi32(values2), _mm_castps_si128(_mm_cmpge_ps(values2, limit)));
const __m128i ints3 = _mm_xor_si128(_mm_cvttps_epi32(values3), _mm_castps_si128(_mm_cmpge_ps(values3, limit)));
const __m128i ints4 = _mm_xor_si128(_mm_cvttps_epi32(values4), _mm_castps_si128(_mm_cmpge_ps(values4, limit)));
_mm_storeu_si128((__m128i*)&dst[0], ints1);
_mm_storeu_si128((__m128i*)&dst[4], ints2);
_mm_storeu_si128((__m128i*)&dst[8], ints3);
_mm_storeu_si128((__m128i*)&dst[12], ints4);
i -= 16;
src += 16;
dst += 16;
}
while (i) {
const __m128 floats = _mm_load_ss(src);
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 = (Sint32)_mm_cvtsi128_si32(ints);
--i;
++src;
++dst;
}
}
#endif
#ifdef SDL_NEON_INTRINSICS
#define DIVBY128 0.0078125f // 0x1p-7f
#define DIVBY32768 0.000030517578125f // 0x1p-15f
#define DIVBY8388607 0.00000011920930376163766f // 0x1.000002p-23f
static void SDL_Convert_S8_to_F32_NEON(float *dst, const Sint8 *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using NEON)");
src += num_samples - 1;
dst += num_samples - 1;
// Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src)
for (i = num_samples; i && (((size_t)(dst - 15)) & 15); --i, --src, --dst) {
*dst = ((float)*src) * DIVBY128;
}
src -= 15;
dst -= 15; // adjust to read NEON blocks from the start.
SDL_assert(!i || !(((size_t)dst) & 15));
// Make sure src is aligned too.
if (!(((size_t)src) & 15)) {
// Aligned! Do NEON blocks as long as we have 16 bytes available.
const int8_t *mmsrc = (const int8_t *)src;
const float32x4_t divby128 = vdupq_n_f32(DIVBY128);
while (i >= 16) { // 16 * 8-bit
const int8x16_t bytes = vld1q_s8(mmsrc); // get 16 sint8 into a NEON register.
const int16x8_t int16hi = vmovl_s8(vget_high_s8(bytes)); // convert top 8 bytes to 8 int16
const int16x8_t int16lo = vmovl_s8(vget_low_s8(bytes)); // convert bottom 8 bytes to 8 int16
// split int16 to two int32, then convert to float, then multiply to normalize, store.
vst1q_f32(dst, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(int16lo))), divby128));
vst1q_f32(dst + 4, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(int16lo))), divby128));
vst1q_f32(dst + 8, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(int16hi))), divby128));
vst1q_f32(dst + 12, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(int16hi))), divby128));
i -= 16;
mmsrc -= 16;
dst -= 16;
}
src = (const Sint8 *)mmsrc;
}
src += 15;
dst += 15; // adjust for any scalar finishing.
// Finish off any leftovers with scalar operations.
while (i) {
*dst = ((float)*src) * DIVBY128;
i--;
src--;
dst--;
}
}
static void SDL_Convert_U8_to_F32_NEON(float *dst, const Uint8 *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using NEON)");
src += num_samples - 1;
dst += num_samples - 1;
// Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src)
for (i = num_samples; i && (((size_t)(dst - 15)) & 15); --i, --src, --dst) {
*dst = (((float)*src) * DIVBY128) - 1.0f;
}
src -= 15;
dst -= 15; // adjust to read NEON blocks from the start.
SDL_assert(!i || !(((size_t)dst) & 15));
// Make sure src is aligned too.
if (!(((size_t)src) & 15)) {
// Aligned! Do NEON blocks as long as we have 16 bytes available.
const uint8_t *mmsrc = (const uint8_t *)src;
const float32x4_t divby128 = vdupq_n_f32(DIVBY128);
const float32x4_t negone = vdupq_n_f32(-1.0f);
while (i >= 16) { // 16 * 8-bit
const uint8x16_t bytes = vld1q_u8(mmsrc); // get 16 uint8 into a NEON register.
const uint16x8_t uint16hi = vmovl_u8(vget_high_u8(bytes)); // convert top 8 bytes to 8 uint16
const uint16x8_t uint16lo = vmovl_u8(vget_low_u8(bytes)); // convert bottom 8 bytes to 8 uint16
// split uint16 to two uint32, then convert to float, then multiply to normalize, subtract to adjust for sign, store.
vst1q_f32(dst, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_low_u16(uint16lo))), divby128));
vst1q_f32(dst + 4, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_high_u16(uint16lo))), divby128));
vst1q_f32(dst + 8, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_low_u16(uint16hi))), divby128));
vst1q_f32(dst + 12, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_high_u16(uint16hi))), divby128));
i -= 16;
mmsrc -= 16;
dst -= 16;
}
src = (const Uint8 *)mmsrc;
}
src += 15;
dst += 15; // adjust for any scalar finishing.
// Finish off any leftovers with scalar operations.
while (i) {
*dst = (((float)*src) * DIVBY128) - 1.0f;
i--;
src--;
dst--;
}
}
static void SDL_Convert_S16_to_F32_NEON(float *dst, const Sint16 *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using NEON)");
src += num_samples - 1;
dst += num_samples - 1;
// Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src)
for (i = num_samples; i && (((size_t)(dst - 7)) & 15); --i, --src, --dst) {
*dst = ((float)*src) * DIVBY32768;
}
src -= 7;
dst -= 7; // adjust to read NEON blocks from the start.
SDL_assert(!i || !(((size_t)dst) & 15));
// Make sure src is aligned too.
if (!(((size_t)src) & 15)) {
// Aligned! Do NEON blocks as long as we have 16 bytes available.
const float32x4_t divby32768 = vdupq_n_f32(DIVBY32768);
while (i >= 8) { // 8 * 16-bit
const int16x8_t ints = vld1q_s16((int16_t const *)src); // get 8 sint16 into a NEON register.
// split int16 to two int32, then convert to float, then multiply to normalize, store.
vst1q_f32(dst, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(ints))), divby32768));
vst1q_f32(dst + 4, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(ints))), divby32768));
i -= 8;
src -= 8;
dst -= 8;
}
}
src += 7;
dst += 7; // adjust for any scalar finishing.
// Finish off any leftovers with scalar operations.
while (i) {
*dst = ((float)*src) * DIVBY32768;
i--;
src--;
dst--;
}
}
static void SDL_Convert_S32_to_F32_NEON(float *dst, const Sint32 *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using NEON)");
// Get dst aligned to 16 bytes
for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) {
*dst = ((float)(*src >> 8)) * DIVBY8388607;
}
SDL_assert(!i || !(((size_t)dst) & 15));
// Make sure src is aligned too.
if (!(((size_t)src) & 15)) {
// Aligned! Do NEON blocks as long as we have 16 bytes available.
const float32x4_t divby8388607 = vdupq_n_f32(DIVBY8388607);
const int32_t *mmsrc = (const int32_t *)src;
while (i >= 4) { // 4 * sint32
// shift out lowest bits so int fits in a float32. Small precision loss, but much faster.
vst1q_f32(dst, vmulq_f32(vcvtq_f32_s32(vshrq_n_s32(vld1q_s32(mmsrc), 8)), divby8388607));
i -= 4;
mmsrc += 4;
dst += 4;
}
src = (const Sint32 *)mmsrc;
}
// Finish off any leftovers with scalar operations.
while (i) {
*dst = ((float)(*src >> 8)) * DIVBY8388607;
i--;
src++;
dst++;
}
}
static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using NEON)");
// Get dst aligned to 16 bytes
for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) {
const float sample = *src;
if (sample >= 1.0f) {
*dst = 127;
} else if (sample <= -1.0f) {
*dst = -128;
} else {
*dst = (Sint8)(sample * 127.0f);
}
}
SDL_assert(!i || !(((size_t)dst) & 15));
// Make sure src is aligned too.
if (!(((size_t)src) & 15)) {
// Aligned! Do NEON blocks as long as we have 16 bytes available.
const float32x4_t one = vdupq_n_f32(1.0f);
const float32x4_t negone = vdupq_n_f32(-1.0f);
const float32x4_t mulby127 = vdupq_n_f32(127.0f);
int8_t *mmdst = (int8_t *)dst;
while (i >= 16) { // 16 * float32
const int32x4_t ints1 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), mulby127)); // load 4 floats, clamp, convert to sint32
const int32x4_t ints2 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 4)), one), mulby127)); // load 4 floats, clamp, convert to sint32
const int32x4_t ints3 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 8)), one), mulby127)); // load 4 floats, clamp, convert to sint32
const int32x4_t ints4 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 12)), one), mulby127)); // load 4 floats, clamp, convert to sint32
const int8x8_t i8lo = vmovn_s16(vcombine_s16(vmovn_s32(ints1), vmovn_s32(ints2))); // narrow to sint16, combine, narrow to sint8
const int8x8_t i8hi = vmovn_s16(vcombine_s16(vmovn_s32(ints3), vmovn_s32(ints4))); // narrow to sint16, combine, narrow to sint8
vst1q_s8(mmdst, vcombine_s8(i8lo, i8hi)); // combine to int8x16_t, store out
i -= 16;
src += 16;
mmdst += 16;
}
dst = (Sint8 *)mmdst;
}
// Finish off any leftovers with scalar operations.
while (i) {
const float sample = *src;
if (sample >= 1.0f) {
*dst = 127;
} else if (sample <= -1.0f) {
*dst = -128;
} else {
*dst = (Sint8)(sample * 127.0f);
}
i--;
src++;
dst++;
}
}
static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using NEON)");
// Get dst aligned to 16 bytes
for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) {
const float sample = *src;
if (sample >= 1.0f) {
*dst = 255;
} else if (sample <= -1.0f) {
*dst = 0;
} else {
*dst = (Uint8)((sample + 1.0f) * 127.0f);
}
}
SDL_assert(!i || !(((size_t)dst) & 15));
// Make sure src is aligned too.
if (!(((size_t)src) & 15)) {
// Aligned! Do NEON blocks as long as we have 16 bytes available.
const float32x4_t one = vdupq_n_f32(1.0f);
const float32x4_t negone = vdupq_n_f32(-1.0f);
const float32x4_t mulby127 = vdupq_n_f32(127.0f);
uint8_t *mmdst = (uint8_t *)dst;
while (i >= 16) { // 16 * float32
const uint32x4_t uints1 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), one), mulby127)); // load 4 floats, clamp, convert to uint32
const uint32x4_t uints2 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 4)), one), one), mulby127)); // load 4 floats, clamp, convert to uint32
const uint32x4_t uints3 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 8)), one), one), mulby127)); // load 4 floats, clamp, convert to uint32
const uint32x4_t uints4 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 12)), one), one), mulby127)); // load 4 floats, clamp, convert to uint32
const uint8x8_t ui8lo = vmovn_u16(vcombine_u16(vmovn_u32(uints1), vmovn_u32(uints2))); // narrow to uint16, combine, narrow to uint8
const uint8x8_t ui8hi = vmovn_u16(vcombine_u16(vmovn_u32(uints3), vmovn_u32(uints4))); // narrow to uint16, combine, narrow to uint8
vst1q_u8(mmdst, vcombine_u8(ui8lo, ui8hi)); // combine to uint8x16_t, store out
i -= 16;
src += 16;
mmdst += 16;
}
dst = (Uint8 *)mmdst;
}
// Finish off any leftovers with scalar operations.
while (i) {
const float sample = *src;
if (sample >= 1.0f) {
*dst = 255;
} else if (sample <= -1.0f) {
*dst = 0;
} else {
*dst = (Uint8)((sample + 1.0f) * 127.0f);
}
i--;
src++;
dst++;
}
}
static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using NEON)");
// Get dst aligned to 16 bytes
for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) {
const float sample = *src;
if (sample >= 1.0f) {
*dst = 32767;
} else if (sample <= -1.0f) {
*dst = -32768;
} else {
*dst = (Sint16)(sample * 32767.0f);
}
}
SDL_assert(!i || !(((size_t)dst) & 15));
// Make sure src is aligned too.
if (!(((size_t)src) & 15)) {
// Aligned! Do NEON blocks as long as we have 16 bytes available.
const float32x4_t one = vdupq_n_f32(1.0f);
const float32x4_t negone = vdupq_n_f32(-1.0f);
const float32x4_t mulby32767 = vdupq_n_f32(32767.0f);
int16_t *mmdst = (int16_t *)dst;
while (i >= 8) { // 8 * float32
const int32x4_t ints1 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), mulby32767)); // load 4 floats, clamp, convert to sint32
const int32x4_t ints2 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 4)), one), mulby32767)); // load 4 floats, clamp, convert to sint32
vst1q_s16(mmdst, vcombine_s16(vmovn_s32(ints1), vmovn_s32(ints2))); // narrow to sint16, combine, store out.
i -= 8;
src += 8;
mmdst += 8;
}
dst = (Sint16 *)mmdst;
}
// Finish off any leftovers with scalar operations.
while (i) {
const float sample = *src;
if (sample >= 1.0f) {
*dst = 32767;
} else if (sample <= -1.0f) {
*dst = -32768;
} else {
*dst = (Sint16)(sample * 32767.0f);
}
i--;
src++;
dst++;
}
}
static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_samples)
{
int i;
LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using NEON)");
// Get dst aligned to 16 bytes
for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) {
const float sample = *src;
if (sample >= 1.0f) {
*dst = 2147483647;
} else if (sample <= -1.0f) {
*dst = (-2147483647) - 1;
} else {
*dst = ((Sint32)(sample * 8388607.0f)) << 8;
}
}
SDL_assert(!i || !(((size_t)dst) & 15));
SDL_assert(!i || !(((size_t)src) & 15));
{
// Aligned! Do NEON blocks as long as we have 16 bytes available.
const float32x4_t one = vdupq_n_f32(1.0f);
const float32x4_t negone = vdupq_n_f32(-1.0f);
const float32x4_t mulby8388607 = vdupq_n_f32(8388607.0f);
int32_t *mmdst = (int32_t *)dst;
while (i >= 4) { // 4 * float32
vst1q_s32(mmdst, vshlq_n_s32(vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), mulby8388607)), 8));
i -= 4;
src += 4;
mmdst += 4;
}
dst = (Sint32 *)mmdst;
}
// Finish off any leftovers with scalar operations.
while (i) {
const float sample = *src;
if (sample >= 1.0f) {
*dst = 2147483647;
} else if (sample <= -1.0f) {
*dst = (-2147483647) - 1;
} else {
*dst = ((Sint32)(sample * 8388607.0f)) << 8;
}
i--;
src++;
dst++;
}
}
#endif
// Function pointers set to a CPU-specific implementation.
void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples) = NULL;
void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples) = NULL;
void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples) = NULL;
void (*SDL_Convert_S32_to_F32)(float *dst, const Sint32 *src, int num_samples) = NULL;
void (*SDL_Convert_F32_to_S8)(Sint8 *dst, const float *src, int num_samples) = NULL;
void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples) = NULL;
void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples) = NULL;
void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples) = NULL;
void SDL_ChooseAudioConverters(void)
{
static SDL_bool converters_chosen = SDL_FALSE;
if (converters_chosen) {
return;
}
#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; \
converters_chosen = SDL_TRUE
#ifdef SDL_SSE2_INTRINSICS
if (SDL_HasSSE2()) {
SET_CONVERTER_FUNCS(SSE2);
return;
}
#endif
#ifdef SDL_NEON_INTRINSICS
if (SDL_HasNEON()) {
SET_CONVERTER_FUNCS(NEON);
return;
}
#endif
SET_CONVERTER_FUNCS(Scalar);
#undef SET_CONVERTER_FUNCS
SDL_assert(converters_chosen == SDL_TRUE);
}
+293
View File
@@ -0,0 +1,293 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 ADJUST_VOLUME(type, s, v) ((s) = (type)(((s) * (v)) / SDL_MIX_MAXVOLUME))
#define ADJUST_VOLUME_U8(s, v) ((s) = (Uint8)(((((s) - 128) * (v)) / SDL_MIX_MAXVOLUME) + 128))
// !!! FIXME: this needs some SIMD magic.
int SDL_MixAudioFormat(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format,
Uint32 len, int volume)
{
if (volume == 0) {
return 0;
}
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_SwapLE16(*(Sint16 *)src);
ADJUST_VOLUME(Sint16, src1, volume);
src2 = SDL_SwapLE16(*(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_SwapLE16((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_SwapBE16(*(Sint16 *)src);
ADJUST_VOLUME(Sint16, src1, volume);
src2 = SDL_SwapBE16(*(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_SwapBE16((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_SwapLE32(*src32));
src32++;
ADJUST_VOLUME(Sint64, src1, volume);
src2 = (Sint64)((Sint32)SDL_SwapLE32(*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_SwapLE32((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_SwapBE32(*src32));
src32++;
ADJUST_VOLUME(Sint64, src1, volume);
src2 = (Sint64)((Sint32)SDL_SwapBE32(*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_SwapBE32((Uint32)((Sint32)dst_sample));
}
} break;
case SDL_AUDIO_F32LE:
{
const float fmaxvolume = 1.0f / ((float)SDL_MIX_MAXVOLUME);
const float fvolume = (float)volume;
const float *src32 = (float *)src;
float *dst32 = (float *)dst;
float src1, src2;
double dst_sample;
// !!! FIXME: are these right?
const double max_audioval = 3.402823466e+38F;
const double min_audioval = -3.402823466e+38F;
len /= 4;
while (len--) {
src1 = ((SDL_SwapFloatLE(*src32) * fvolume) * fmaxvolume);
src2 = SDL_SwapFloatLE(*dst32);
src32++;
dst_sample = ((double)src1) + ((double)src2);
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(dst32++) = SDL_SwapFloatLE((float)dst_sample);
}
} break;
case SDL_AUDIO_F32BE:
{
const float fmaxvolume = 1.0f / ((float)SDL_MIX_MAXVOLUME);
const float fvolume = (float)volume;
const float *src32 = (float *)src;
float *dst32 = (float *)dst;
float src1, src2;
double dst_sample;
// !!! FIXME: are these right?
const double max_audioval = 3.402823466e+38F;
const double min_audioval = -3.402823466e+38F;
len /= 4;
while (len--) {
src1 = ((SDL_SwapFloatBE(*src32) * fvolume) * fmaxvolume);
src2 = SDL_SwapFloatBE(*dst32);
src32++;
dst_sample = ((double)src1) + ((double)src2);
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(dst32++) = SDL_SwapFloatBE((float)dst_sample);
}
} break;
default: // If this happens... FIXME!
return SDL_SetError("SDL_MixAudioFormat(): unknown audio format");
}
return 0;
}
+362
View File
@@ -0,0 +1,362 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_
#include "../SDL_hashtable.h"
#define DEBUG_AUDIOSTREAM 0
#define DEBUG_AUDIO_CONVERT 0
#if DEBUG_AUDIO_CONVERT
#define LOG_DEBUG_AUDIO_CONVERT(from, to) SDL_Log("SDL_AUDIO_CONVERT: Converting %s to %s.\n", from, to);
#else
#define LOG_DEBUG_AUDIO_CONVERT(from, to)
#endif
// These pointers get set during SDL_ChooseAudioConverters() to various SIMD implementations.
extern void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples);
extern void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples);
extern void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples);
extern void (*SDL_Convert_S32_to_F32)(float *dst, const Sint32 *src, int num_samples);
extern void (*SDL_Convert_F32_to_S8)(Sint8 *dst, const float *src, int num_samples);
extern void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples);
extern void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples);
extern void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples);
// !!! FIXME: These are wordy and unlocalized...
#define DEFAULT_OUTPUT_DEVNAME "System audio output device"
#define DEFAULT_INPUT_DEVNAME "System audio capture device"
// these are used when no better specifics are known. We default to CD audio quality.
#define DEFAULT_AUDIO_OUTPUT_FORMAT SDL_AUDIO_S16
#define DEFAULT_AUDIO_OUTPUT_CHANNELS 2
#define DEFAULT_AUDIO_OUTPUT_FREQUENCY 44100
#define DEFAULT_AUDIO_CAPTURE_FORMAT SDL_AUDIO_S16
#define DEFAULT_AUDIO_CAPTURE_CHANNELS 1
#define DEFAULT_AUDIO_CAPTURE_FREQUENCY 44100
#define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq))
typedef struct SDL_AudioDevice SDL_AudioDevice;
typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice;
// Used by src/SDL.c to initialize a particular audio driver.
extern int SDL_InitAudio(const char *driver_name);
// 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(const SDL_bool iscapture, 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 int SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
// Same as above, but assume the device is already locked.
extern int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
// Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. DOES NOT LOCK THE DEVICE.
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle);
// Find an SDL_AudioDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(SDL_bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata);
// Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct.
extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device);
// 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_OutputAudioThreadSetup(SDL_AudioDevice *device);
extern SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device);
extern void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device);
extern void SDL_CaptureAudioThreadSetup(SDL_AudioDevice *device);
extern SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device);
extern void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device);
extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device);
// 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,
void *dst, SDL_AudioFormat dst_format, int dst_channels, void* scratch);
// 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);
typedef struct SDL_AudioDriverImpl
{
void (*DetectDevices)(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture);
int (*OpenDevice)(SDL_AudioDevice *device);
void (*ThreadInit)(SDL_AudioDevice *device); // Called by audio thread at start
void (*ThreadDeinit)(SDL_AudioDevice *device); // Called by audio thread at end
int (*WaitDevice)(SDL_AudioDevice *device);
int (*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);
int (*WaitCaptureDevice)(SDL_AudioDevice *device);
int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
void (*FlushCapture)(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.
SDL_bool ProvidesOwnCallbackThread; // !!! FIXME: rename this, it's not a callback thread anymore.
SDL_bool HasCaptureSupport;
SDL_bool OnlyHasDefaultOutputDevice;
SDL_bool OnlyHasDefaultCaptureDevice; // !!! FIXME: is there ever a time where you'd have a default output and not a default capture (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 (capture, playback, logical and physical!)
SDL_AudioStream *existing_streams; // a list of all existing SDL_AudioStreams.
SDL_AudioDeviceID default_output_device_id;
SDL_AudioDeviceID default_capture_device_id;
SDL_PendingAudioDeviceEvent pending_events;
SDL_PendingAudioDeviceEvent *pending_events_tail;
// !!! FIXME: most (all?) of these don't have to be atomic.
SDL_AtomicInt output_device_count;
SDL_AtomicInt capture_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;
float freq_ratio;
struct SDL_AudioQueue* queue;
Uint64 total_bytes_queued;
SDL_AudioSpec input_spec; // The spec of input data currently being processed
Sint64 resample_offset;
Uint8 *work_buffer; // used for scratch space during data conversion/resampling.
size_t work_buffer_allocation;
Uint8 *history_buffer; // history for left padding and future sample rate changes.
size_t history_buffer_allocation;
SDL_bool simplified; // SDL_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;
// double-linked list of all audio streams currently bound to this opened device.
SDL_AudioStream *bound_streams;
// SDL_TRUE if this was opened as a default device.
SDL_bool opened_as_default;
// SDL_TRUE if device was opened with SDL_OpenAudioDeviceStream (so it forbids binding changes, etc).
SDL_bool simplified;
// If non-NULL, callback into the app that lets them access the final postmix buffer.
SDL_AudioPostmixCallback postmix;
// App-supplied pointer for postmix callback.
void *postmix_userdata;
// double-linked list of opened devices on the same physical device.
SDL_LogicalAudioDevice *next;
SDL_LogicalAudioDevice *prev;
};
struct SDL_AudioDevice
{
// A mutex for locking access to this struct
SDL_Mutex *lock;
// A condition variable to protect device close, where we can't hold the device lock forever.
SDL_Condition *close_cond;
// Reference count of the device; logical devices, device threads, etc, add to this.
SDL_AtomicInt refcount;
// These are, initially, set from current_audio, but we might swap them out with Zombie versions on disconnect/failure.
int (*WaitDevice)(SDL_AudioDevice *device);
int (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen);
Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
int (*WaitCaptureDevice)(SDL_AudioDevice *device);
int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
void (*FlushCapture)(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;
int buffer_size;
// 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;
// SDL_TRUE if this is a capture device instead of an output device
SDL_bool iscapture;
// SDL_TRUE if audio thread can skip silence/mix/convert stages and just do a basic memcpy.
SDL_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;
// SDL_TRUE if this physical device is currently opened by the backend.
SDL_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;
SDL_bool (*init)(SDL_AudioDriverImpl *impl);
SDL_bool demand_only; // if SDL_TRUE: request explicitly, or it won't be available.
} AudioBootStrap;
// Not all of these are available in a given build. Use #ifdefs, etc.
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 ANDROIDAUDIO_bootstrap;
extern AudioBootStrap PS2AUDIO_bootstrap;
extern AudioBootStrap PSPAUDIO_bootstrap;
extern AudioBootStrap VITAAUD_bootstrap;
extern AudioBootStrap N3DSAUDIO_bootstrap;
extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
extern AudioBootStrap QSAAUDIO_bootstrap;
#endif // SDL_sysaudio_h_
File diff suppressed because it is too large Load Diff
+151
View File
@@ -0,0 +1,151 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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;
+553
View File
@@ -0,0 +1,553 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 <stdbool.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;
SDL_bool resume; // Resume device if it was paused automatically
};
// Debug
#if 0
#define LOGI(...) SDL_Log(__VA_ARGS__);
#else
#define LOGI(...)
#endif
#define LIB_AAUDIO_SO "libaaudio.so"
typedef struct AAUDIO_Data
{
void *handle;
#define SDL_PROC(ret, func, params) ret (*func) params;
#include "SDL_aaudiofuncs.h"
} AAUDIO_Data;
static AAUDIO_Data ctx;
static int 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 0;
}
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_AtomicSet(&device->hidden->error_callback_triggered, (int) error); // AAUDIO_OK is zero, so !triggered means no error.
SDL_PostSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice.
}
static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames)
{
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->iscapture) {
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)\n", 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\n", (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)\n", 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\n", (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_PostSemaphore(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 int AAUDIO_WaitDevice(SDL_AudioDevice *device)
{
SDL_WaitSemaphore(device->hidden->semaphore);
return 0;
}
static int BuildAAudioStream(SDL_AudioDevice *device);
static int 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) < 0) {
return -1; // 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) < 0) {
return -1; // ugh
}
return 0;
}
static int 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_AtomicGet(&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) < 0) {
return -1; // oh well, we went down hard.
}
} else {
SDL_MemoryBarrierRelease();
hidden->processed_bytes += buflen;
}
return 0;
}
static int AAUDIO_CaptureFromDevice(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_AtomicGet(&hidden->error_callback_triggered)) {
SDL_AtomicSet(&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 int BuildAAudioStream(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
const SDL_bool iscapture = device->iscapture;
aaudio_result_t res;
SDL_AtomicSet(&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;
#ifdef SET_AUDIO_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);
#endif
const aaudio_direction_t direction = (iscapture ? 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_ANDROID_LOW_LATENCY_AUDIO", SDL_TRUE)) {
ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
}
LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u",
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", 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 double buffered mixing buffer
hidden->num_buffers = 2;
hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size);
hidden->mixbuf = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), hidden->mixbuf_bytes);
if (!hidden->mixbuf) {
return -1;
}
hidden->processed_bytes = 0;
hidden->callback_bytes = 0;
hidden->semaphore = SDL_CreateSemaphore(iscapture ? 0 : hidden->num_buffers);
if (!hidden->semaphore) {
LOGI("SDL Failed SDL_CreateSemaphore %s iscapture:%d", SDL_GetError(), iscapture);
return -1;
}
LOGI("AAudio Actually opened %u hz %u bit chan %u %s samples %u, buffers %d",
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames, hidden->num_buffers);
res = ctx.AAudioStream_requestStart(hidden->stream);
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStream_requestStart %d iscapture:%d", res, iscapture);
return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
}
LOGI("SDL AAudioStream_requestStart OK");
return 0;
}
// !!! FIXME: make this non-blocking!
static void SDLCALL AndroidRequestPermissionBlockingCallback(void *userdata, const char *permission, SDL_bool granted)
{
SDL_AtomicSet((SDL_AtomicInt *) userdata, granted ? 1 : -1);
}
static int 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->iscapture) {
// !!! FIXME: make this non-blocking!
SDL_AtomicInt permission_response;
SDL_AtomicSet(&permission_response, 0);
if (SDL_AndroidRequestPermission("android.permission.RECORD_AUDIO", AndroidRequestPermissionBlockingCallback, &permission_response) == -1) {
return -1;
}
while (SDL_AtomicGet(&permission_response) == 0) {
SDL_Delay(10);
}
if (SDL_AtomicGet(&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 -1;
}
return BuildAAudioStream(device);
}
static SDL_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->iscapture) {
// Pause() isn't implemented for 'capture', use Stop()
res = ctx.AAudioStream_requestStop(hidden->stream);
} else {
res = ctx.AAudioStream_requestPause(hidden->stream);
}
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStream_requestPause %d", res);
SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
}
SDL_LockMutex(device->lock);
hidden->resume = SDL_TRUE;
}
}
return SDL_FALSE; // keep enumerating.
}
// Pause (block) all non already paused audio devices by taking their mixer lock
void AAUDIO_PauseDevices(void)
{
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 SDL_bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
if (hidden) {
if (hidden->resume) {
hidden->resume = SDL_FALSE;
SDL_UnlockMutex(device->lock);
}
if (hidden->stream) {
aaudio_result_t res = ctx.AAudioStream_requestStart(hidden->stream);
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStream_requestStart %d", res);
SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
}
}
}
return SDL_FALSE; // keep enumerating.
}
void AAUDIO_ResumeDevices(void)
{
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 SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl)
{
LOGI(__func__);
/* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release,
* so don't use it until 8.1.
*
* See https://github.com/google/oboe/issues/40 for more information.
*/
if (SDL_GetAndroidSDKVersion() < 27) {
return SDL_FALSE;
}
SDL_zero(ctx);
ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO);
if (!ctx.handle) {
LOGI("SDL couldn't find " LIB_AAUDIO_SO);
return SDL_FALSE;
}
if (AAUDIO_LoadFunctions(&ctx) < 0) {
SDL_UnloadObject(ctx.handle);
SDL_zero(ctx);
return SDL_FALSE;
}
impl->ThreadInit = Android_AudioThreadInit;
impl->Deinitialize = AAUDIO_Deinitialize;
impl->OpenDevice = AAUDIO_OpenDevice;
impl->CloseDevice = AAUDIO_CloseDevice;
impl->WaitDevice = AAUDIO_WaitDevice;
impl->PlayDevice = AAUDIO_PlayDevice;
impl->GetDeviceBuf = AAUDIO_GetDeviceBuf;
impl->WaitCaptureDevice = AAUDIO_WaitDevice;
impl->CaptureFromDevice = AAUDIO_CaptureFromDevice;
impl->HasCaptureSupport = SDL_TRUE;
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
impl->DetectDevices = Android_StartAudioHotplug;
#else
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
#endif
LOGI("SDL AAUDIO_Init OK");
return SDL_TRUE;
}
AudioBootStrap AAUDIO_bootstrap = {
"AAudio", "AAudio audio driver", AAUDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_AAUDIO
+38
View File
@@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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
void AAUDIO_ResumeDevices(void);
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-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#define SDL_PROC_UNUSED(ret, func, params)
SDL_PROC(const char *, AAudio_convertResultToText, (aaudio_result_t returnCode))
SDL_PROC(const char *, AAudio_convertStreamStateToText, (aaudio_stream_state_t state))
SDL_PROC(aaudio_result_t, AAudio_createStreamBuilder, (AAudioStreamBuilder * *builder))
SDL_PROC(void, AAudioStreamBuilder_setDeviceId, (AAudioStreamBuilder * builder, int32_t deviceId))
SDL_PROC(void, AAudioStreamBuilder_setSampleRate, (AAudioStreamBuilder * builder, int32_t sampleRate))
SDL_PROC(void, AAudioStreamBuilder_setChannelCount, (AAudioStreamBuilder * builder, int32_t channelCount))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSamplesPerFrame, (AAudioStreamBuilder * builder, int32_t samplesPerFrame))
SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aaudio_format_t format))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode))
SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames))
SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) // API 29
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) // API 28
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) // API 30
SDL_PROC(void, AAudioStreamBuilder_setDataCallback, (AAudioStreamBuilder * builder, AAudioStream_dataCallback callback, void *userData))
SDL_PROC(void, AAudioStreamBuilder_setFramesPerDataCallback, (AAudioStreamBuilder * builder, int32_t numFrames))
SDL_PROC(void, AAudioStreamBuilder_setErrorCallback, (AAudioStreamBuilder * builder, AAudioStream_errorCallback callback, void *userData))
SDL_PROC(aaudio_result_t, AAudioStreamBuilder_openStream, (AAudioStreamBuilder * builder, AAudioStream **stream))
SDL_PROC(aaudio_result_t, AAudioStreamBuilder_delete, (AAudioStreamBuilder * builder))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_release, (AAudioStream * stream)) // API 30
SDL_PROC(aaudio_result_t, AAudioStream_close, (AAudioStream * stream))
SDL_PROC(aaudio_result_t, AAudioStream_requestStart, (AAudioStream * stream))
SDL_PROC(aaudio_result_t, AAudioStream_requestPause, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_requestFlush, (AAudioStream * stream))
SDL_PROC(aaudio_result_t, AAudioStream_requestStop, (AAudioStream * stream))
SDL_PROC(aaudio_stream_state_t, AAudioStream_getState, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_waitForStateChange, (AAudioStream * stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_read, (AAudioStream * stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_write, (AAudioStream * stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_setBufferSizeInFrames, (AAudioStream * stream, int32_t numFrames))
SDL_PROC_UNUSED(int32_t, AAudioStream_getBufferSizeInFrames, (AAudioStream * stream))
SDL_PROC_UNUSED(int32_t, AAudioStream_getFramesPerBurst, (AAudioStream * stream))
SDL_PROC(int32_t, AAudioStream_getBufferCapacityInFrames, (AAudioStream * stream))
SDL_PROC(int32_t, AAudioStream_getFramesPerDataCallback, (AAudioStream * stream))
SDL_PROC_UNUSED(int32_t, AAudioStream_getXRunCount, (AAudioStream * stream))
SDL_PROC(int32_t, AAudioStream_getSampleRate, (AAudioStream * stream))
SDL_PROC(int32_t, AAudioStream_getChannelCount, (AAudioStream * stream))
SDL_PROC_UNUSED(int32_t, AAudioStream_getSamplesPerFrame, (AAudioStream * stream))
SDL_PROC_UNUSED(int32_t, AAudioStream_getDeviceId, (AAudioStream * stream))
SDL_PROC(aaudio_format_t, AAudioStream_getFormat, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_sharing_mode_t, AAudioStream_getSharingMode, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_performance_mode_t, AAudioStream_getPerformanceMode, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_direction_t, AAudioStream_getDirection, (AAudioStream * stream))
SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesWritten, (AAudioStream * stream))
SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesRead, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_session_id_t, AAudioStream_getSessionId, (AAudioStream * stream)) // API 28
SDL_PROC(aaudio_result_t, AAudioStream_getTimestamp, (AAudioStream * stream, clockid_t clockid, int64_t *framePosition, int64_t *timeNanoseconds))
SDL_PROC_UNUSED(aaudio_usage_t, AAudioStream_getUsage, (AAudioStream * stream)) // API 28
SDL_PROC_UNUSED(aaudio_content_type_t, AAudioStream_getContentType, (AAudioStream * stream)) // API 28
SDL_PROC_UNUSED(aaudio_input_preset_t, AAudioStream_getInputPreset, (AAudioStream * stream)) // API 28
SDL_PROC_UNUSED(aaudio_allowed_capture_policy_t, AAudioStream_getAllowedCapturePolicy, (AAudioStream * stream)) // API 29
SDL_PROC_UNUSED(bool, AAudioStream_isPrivacySensitive, (AAudioStream * stream)) // API 30
#undef SDL_PROC
#undef SDL_PROC_UNUSED
+994
View File
@@ -0,0 +1,994 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_ALSA
#ifndef SDL_ALSA_NON_BLOCKING
#define SDL_ALSA_NON_BLOCKING 0
#endif
// without the thread, you will detect devices on startup, but will not get further hotplug events. But that might be okay.
#ifndef SDL_ALSA_HOTPLUG_THREAD
#define SDL_ALSA_HOTPLUG_THREAD 1
#endif
// Allow access to a raw mixing buffer
#include <sys/types.h>
#include <signal.h> // For kill()
#include <string.h>
#include "../SDL_sysaudio.h"
#include "SDL_alsa_audio.h"
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
#endif
static int (*ALSA_snd_pcm_open)(snd_pcm_t **, const char *, snd_pcm_stream_t, int);
static int (*ALSA_snd_pcm_close)(snd_pcm_t *pcm);
static int (*ALSA_snd_pcm_start)(snd_pcm_t *pcm);
static snd_pcm_sframes_t (*ALSA_snd_pcm_writei)(snd_pcm_t *, const void *, snd_pcm_uframes_t);
static snd_pcm_sframes_t (*ALSA_snd_pcm_readi)(snd_pcm_t *, void *, snd_pcm_uframes_t);
static int (*ALSA_snd_pcm_recover)(snd_pcm_t *, int, int);
static int (*ALSA_snd_pcm_prepare)(snd_pcm_t *);
static int (*ALSA_snd_pcm_drain)(snd_pcm_t *);
static const char *(*ALSA_snd_strerror)(int);
static size_t (*ALSA_snd_pcm_hw_params_sizeof)(void);
static size_t (*ALSA_snd_pcm_sw_params_sizeof)(void);
static void (*ALSA_snd_pcm_hw_params_copy)(snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *);
static int (*ALSA_snd_pcm_hw_params_any)(snd_pcm_t *, snd_pcm_hw_params_t *);
static int (*ALSA_snd_pcm_hw_params_set_access)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
static int (*ALSA_snd_pcm_hw_params_set_format)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
static int (*ALSA_snd_pcm_hw_params_set_channels)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int);
static int (*ALSA_snd_pcm_hw_params_get_channels)(const snd_pcm_hw_params_t *, unsigned int *);
static int (*ALSA_snd_pcm_hw_params_set_rate_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
static int (*ALSA_snd_pcm_hw_params_set_period_size_near)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
static int (*ALSA_snd_pcm_hw_params_get_period_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
static int (*ALSA_snd_pcm_hw_params_set_periods_min)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
static int (*ALSA_snd_pcm_hw_params_set_periods_first)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
static int (*ALSA_snd_pcm_hw_params_get_periods)(const snd_pcm_hw_params_t *, unsigned int *, int *);
static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
static int (*ALSA_snd_pcm_hw_params_get_buffer_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
static int (*ALSA_snd_pcm_hw_params)(snd_pcm_t *, snd_pcm_hw_params_t *);
static int (*ALSA_snd_pcm_sw_params_current)(snd_pcm_t *,
snd_pcm_sw_params_t *);
static int (*ALSA_snd_pcm_sw_params_set_start_threshold)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
static int (*ALSA_snd_pcm_sw_params)(snd_pcm_t *, snd_pcm_sw_params_t *);
static int (*ALSA_snd_pcm_nonblock)(snd_pcm_t *, int);
static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int);
static int (*ALSA_snd_pcm_sw_params_set_avail_min)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
static int (*ALSA_snd_pcm_reset)(snd_pcm_t *);
static int (*ALSA_snd_device_name_hint)(int, const char *, void ***);
static char *(*ALSA_snd_device_name_get_hint)(const void *, const char *);
static int (*ALSA_snd_device_name_free_hint)(void **);
static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *);
#ifdef SND_CHMAP_API_VERSION
static snd_pcm_chmap_t *(*ALSA_snd_pcm_get_chmap)(snd_pcm_t *);
static int (*ALSA_snd_pcm_chmap_print)(const snd_pcm_chmap_t *map, size_t maxlen, char *buf);
#endif
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
#define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
#define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
static void *alsa_handle = NULL;
static int load_alsa_sym(const char *fn, void **addr)
{
*addr = SDL_LoadFunction(alsa_handle, fn);
if (!*addr) {
// Don't call SDL_SetError(): SDL_LoadFunction already did.
return 0;
}
return 1;
}
// cast funcs to char* first, to please GCC's strict aliasing rules.
#define SDL_ALSA_SYM(x) \
if (!load_alsa_sym(#x, (void **)(char *)&ALSA_##x)) \
return -1
#else
#define SDL_ALSA_SYM(x) ALSA_##x = x
#endif
static int load_alsa_syms(void)
{
SDL_ALSA_SYM(snd_pcm_open);
SDL_ALSA_SYM(snd_pcm_close);
SDL_ALSA_SYM(snd_pcm_start);
SDL_ALSA_SYM(snd_pcm_writei);
SDL_ALSA_SYM(snd_pcm_readi);
SDL_ALSA_SYM(snd_pcm_recover);
SDL_ALSA_SYM(snd_pcm_prepare);
SDL_ALSA_SYM(snd_pcm_drain);
SDL_ALSA_SYM(snd_strerror);
SDL_ALSA_SYM(snd_pcm_hw_params_sizeof);
SDL_ALSA_SYM(snd_pcm_sw_params_sizeof);
SDL_ALSA_SYM(snd_pcm_hw_params_copy);
SDL_ALSA_SYM(snd_pcm_hw_params_any);
SDL_ALSA_SYM(snd_pcm_hw_params_set_access);
SDL_ALSA_SYM(snd_pcm_hw_params_set_format);
SDL_ALSA_SYM(snd_pcm_hw_params_set_channels);
SDL_ALSA_SYM(snd_pcm_hw_params_get_channels);
SDL_ALSA_SYM(snd_pcm_hw_params_set_rate_near);
SDL_ALSA_SYM(snd_pcm_hw_params_set_period_size_near);
SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size);
SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_min);
SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_first);
SDL_ALSA_SYM(snd_pcm_hw_params_get_periods);
SDL_ALSA_SYM(snd_pcm_hw_params_set_buffer_size_near);
SDL_ALSA_SYM(snd_pcm_hw_params_get_buffer_size);
SDL_ALSA_SYM(snd_pcm_hw_params);
SDL_ALSA_SYM(snd_pcm_sw_params_current);
SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold);
SDL_ALSA_SYM(snd_pcm_sw_params);
SDL_ALSA_SYM(snd_pcm_nonblock);
SDL_ALSA_SYM(snd_pcm_wait);
SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
SDL_ALSA_SYM(snd_pcm_reset);
SDL_ALSA_SYM(snd_device_name_hint);
SDL_ALSA_SYM(snd_device_name_get_hint);
SDL_ALSA_SYM(snd_device_name_free_hint);
SDL_ALSA_SYM(snd_pcm_avail);
#ifdef SND_CHMAP_API_VERSION
SDL_ALSA_SYM(snd_pcm_get_chmap);
SDL_ALSA_SYM(snd_pcm_chmap_print);
#endif
return 0;
}
#undef SDL_ALSA_SYM
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
static void UnloadALSALibrary(void)
{
if (alsa_handle) {
SDL_UnloadObject(alsa_handle);
alsa_handle = NULL;
}
}
static int LoadALSALibrary(void)
{
int retval = 0;
if (!alsa_handle) {
alsa_handle = SDL_LoadObject(alsa_library);
if (!alsa_handle) {
retval = -1;
// Don't call SDL_SetError(): SDL_LoadObject already did.
} else {
retval = load_alsa_syms();
if (retval < 0) {
UnloadALSALibrary();
}
}
}
return retval;
}
#else
static void UnloadALSALibrary(void)
{
}
static int LoadALSALibrary(void)
{
load_alsa_syms();
return 0;
}
#endif // SDL_AUDIO_DRIVER_ALSA_DYNAMIC
typedef struct ALSA_Device
{
char *name;
SDL_bool iscapture;
struct ALSA_Device *next;
} ALSA_Device;
static const ALSA_Device default_output_handle = {
"default",
SDL_FALSE,
NULL
};
static const ALSA_Device default_capture_handle = {
"default",
SDL_TRUE,
NULL
};
static const char *get_audio_device(void *handle, const int channels)
{
SDL_assert(handle != NULL); // SDL2 used NULL to mean "default" but that's not true in SDL3.
ALSA_Device *dev = (ALSA_Device *)handle;
if (SDL_strcmp(dev->name, "default") == 0) {
const char *device = SDL_getenv("AUDIODEV"); // Is there a standard variable name?
if (device) {
return device;
} else if (channels == 6) {
return "plug:surround51";
} else if (channels == 4) {
return "plug:surround40";
}
return "default";
}
return dev->name;
}
// !!! FIXME: is there a channel swizzler in alsalib instead?
// https://bugzilla.libsdl.org/show_bug.cgi?id=110
// "For Linux ALSA, this is FL-FR-RL-RR-C-LFE
// and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR"
#define SWIZ6(T) \
static void swizzle_alsa_channels_6_##T(void *buffer, const Uint32 bufferlen) \
{ \
T *ptr = (T *)buffer; \
Uint32 i; \
for (i = 0; i < bufferlen; i++, ptr += 6) { \
T tmp; \
tmp = ptr[2]; \
ptr[2] = ptr[4]; \
ptr[4] = tmp; \
tmp = ptr[3]; \
ptr[3] = ptr[5]; \
ptr[5] = tmp; \
} \
}
// !!! FIXME: is there a channel swizzler in alsalib instead?
// !!! FIXME: this screams for a SIMD shuffle operation.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/mapping-stream-formats-to-speaker-configurations
// For Linux ALSA, this appears to be FL-FR-RL-RR-C-LFE-SL-SR
// and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-SL-SR-RL-RR"
#define SWIZ8(T) \
static void swizzle_alsa_channels_8_##T(void *buffer, const Uint32 bufferlen) \
{ \
T *ptr = (T *)buffer; \
Uint32 i; \
for (i = 0; i < bufferlen; i++, ptr += 6) { \
const T center = ptr[2]; \
const T subwoofer = ptr[3]; \
const T side_left = ptr[4]; \
const T side_right = ptr[5]; \
const T rear_left = ptr[6]; \
const T rear_right = ptr[7]; \
ptr[2] = rear_left; \
ptr[3] = rear_right; \
ptr[4] = center; \
ptr[5] = subwoofer; \
ptr[6] = side_left; \
ptr[7] = side_right; \
} \
}
#define CHANNEL_SWIZZLE(x) \
x(Uint64) \
x(Uint32) \
x(Uint16) \
x(Uint8)
CHANNEL_SWIZZLE(SWIZ6)
CHANNEL_SWIZZLE(SWIZ8)
#undef CHANNEL_SWIZZLE
#undef SWIZ6
#undef SWIZ8
// Called right before feeding device->hidden->mixbuf to the hardware. Swizzle
// channels from Windows/Mac order to the format alsalib will want.
static void swizzle_alsa_channels(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen)
{
switch (device->spec.channels) {
#define CHANSWIZ(chans) \
case chans: \
switch ((device->spec.format & (0xFF))) { \
case 8: \
swizzle_alsa_channels_##chans##_Uint8(buffer, bufferlen); \
break; \
case 16: \
swizzle_alsa_channels_##chans##_Uint16(buffer, bufferlen); \
break; \
case 32: \
swizzle_alsa_channels_##chans##_Uint32(buffer, bufferlen); \
break; \
case 64: \
swizzle_alsa_channels_##chans##_Uint64(buffer, bufferlen); \
break; \
default: \
SDL_assert(!"unhandled bitsize"); \
break; \
} \
return;
CHANSWIZ(6);
CHANSWIZ(8);
#undef CHANSWIZ
default:
break;
}
}
#ifdef SND_CHMAP_API_VERSION
// Some devices have the right channel map, no swizzling necessary
static void no_swizzle(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen)
{
}
#endif // SND_CHMAP_API_VERSION
// This function waits until it is possible to write a full sound buffer
static int ALSA_WaitDevice(SDL_AudioDevice *device)
{
const int fulldelay = (int) ((((Uint64) device->sample_frames) * 1000) / device->spec.freq);
const int delay = SDL_max(fulldelay, 10);
while (!SDL_AtomicGet(&device->shutdown)) {
const int rc = ALSA_snd_pcm_wait(device->hidden->pcm_handle, delay);
if (rc < 0 && (rc != -EAGAIN)) {
const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0);
if (status < 0) {
// Hmm, not much we can do - abort
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA: snd_pcm_wait failed (unrecoverable): %s", ALSA_snd_strerror(rc));
return -1;
}
continue;
}
if (rc > 0) {
break; // ready to go!
}
// Timed out! Make sure we aren't shutting down and then wait again.
}
return 0;
}
static int ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
SDL_assert(buffer == device->hidden->mixbuf);
Uint8 *sample_buf = (Uint8 *) buffer; // !!! FIXME: deal with this without casting away constness
const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
snd_pcm_uframes_t frames_left = (snd_pcm_uframes_t) (buflen / frame_size);
device->hidden->swizzle_func(device, sample_buf, frames_left);
while ((frames_left > 0) && !SDL_AtomicGet(&device->shutdown)) {
const int rc = ALSA_snd_pcm_writei(device->hidden->pcm_handle, sample_buf, frames_left);
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA PLAYDEVICE: WROTE %d of %d bytes", (rc >= 0) ? ((int) (rc * frame_size)) : rc, (int) (frames_left * frame_size));
SDL_assert(rc != 0); // assuming this can't happen if we used snd_pcm_wait and queried for available space.
if (rc < 0) {
SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it!
const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0);
if (status < 0) {
// Hmm, not much we can do - abort
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(rc));
return -1;
}
continue;
}
sample_buf += rc * frame_size;
frames_left -= rc;
}
return 0;
}
static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
if (rc <= 0) {
// Wait a bit and try again, maybe the hardware isn't quite ready yet?
SDL_Delay(1);
rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
if (rc <= 0) {
// We'll catch it next time
*buffer_size = 0;
return NULL;
}
}
const int requested_frames = SDL_min(device->sample_frames, rc);
const int requested_bytes = requested_frames * SDL_AUDIO_FRAMESIZE(device->spec);
SDL_assert(requested_bytes <= *buffer_size);
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA GETDEVICEBUF: NEED %d BYTES", requested_bytes);
*buffer_size = requested_bytes;
return device->hidden->mixbuf;
}
static int ALSA_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
SDL_assert((buflen % frame_size) == 0);
const snd_pcm_sframes_t total_available = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
const int total_frames = SDL_min(buflen / frame_size, total_available);
const int rc = ALSA_snd_pcm_readi(device->hidden->pcm_handle, buffer, total_frames);
SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it!
if (rc < 0) {
const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0);
if (status < 0) {
// Hmm, not much we can do - abort
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA read failed (unrecoverable): %s", ALSA_snd_strerror(rc));
return -1;
}
return 0; // go back to WaitDevice and try again.
} else if (rc > 0) {
device->hidden->swizzle_func(device, buffer, total_frames - rc);
}
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: captured %d bytes", rc * frame_size);
return rc * frame_size;
}
static void ALSA_FlushCapture(SDL_AudioDevice *device)
{
ALSA_snd_pcm_reset(device->hidden->pcm_handle);
}
static void ALSA_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->pcm_handle) {
// Wait for the submitted audio to drain. ALSA_snd_pcm_drop() can hang, so don't use that.
SDL_Delay(((device->sample_frames * 1000) / device->spec.freq) * 2);
ALSA_snd_pcm_close(device->hidden->pcm_handle);
}
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
}
}
static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *params)
{
int status;
snd_pcm_hw_params_t *hwparams;
snd_pcm_uframes_t persize;
unsigned int periods;
// Copy the hardware parameters for this setup
snd_pcm_hw_params_alloca(&hwparams);
ALSA_snd_pcm_hw_params_copy(hwparams, params);
// Attempt to match the period size to the requested buffer size
persize = device->sample_frames;
status = ALSA_snd_pcm_hw_params_set_period_size_near(
device->hidden->pcm_handle, hwparams, &persize, NULL);
if (status < 0) {
return -1;
}
// Need to at least double buffer
periods = 2;
status = ALSA_snd_pcm_hw_params_set_periods_min(
device->hidden->pcm_handle, hwparams, &periods, NULL);
if (status < 0) {
return -1;
}
status = ALSA_snd_pcm_hw_params_set_periods_first(
device->hidden->pcm_handle, hwparams, &periods, NULL);
if (status < 0) {
return -1;
}
// "set" the hardware with the desired parameters
status = ALSA_snd_pcm_hw_params(device->hidden->pcm_handle, hwparams);
if (status < 0) {
return -1;
}
device->sample_frames = persize;
// This is useful for debugging
if (SDL_getenv("SDL_AUDIO_ALSA_DEBUG")) {
snd_pcm_uframes_t bufsize;
ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize);
SDL_LogError(SDL_LOG_CATEGORY_AUDIO,
"ALSA: period size = %ld, periods = %u, buffer size = %lu",
persize, periods, bufsize);
}
return 0;
}
static int ALSA_OpenDevice(SDL_AudioDevice *device)
{
const SDL_bool iscapture = device->iscapture;
int status = 0;
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return -1;
}
// Open the audio device
// Name of device should depend on # channels in spec
snd_pcm_t *pcm_handle = NULL;
status = ALSA_snd_pcm_open(&pcm_handle,
get_audio_device(device->handle, device->spec.channels),
iscapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
SND_PCM_NONBLOCK);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't open audio device: %s", ALSA_snd_strerror(status));
}
device->hidden->pcm_handle = pcm_handle;
// Figure out what the hardware is capable of
snd_pcm_hw_params_t *hwparams = NULL;
snd_pcm_hw_params_alloca(&hwparams);
status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't get hardware config: %s", ALSA_snd_strerror(status));
}
// SDL only uses interleaved sample output
status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't set interleaved access: %s", ALSA_snd_strerror(status));
}
// Try for a closest match on audio format
snd_pcm_format_t format = 0;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
SDL_AudioFormat test_format;
while ((test_format = *(closefmts++)) != 0) {
switch (test_format) {
case SDL_AUDIO_U8:
format = SND_PCM_FORMAT_U8;
break;
case SDL_AUDIO_S8:
format = SND_PCM_FORMAT_S8;
break;
case SDL_AUDIO_S16LE:
format = SND_PCM_FORMAT_S16_LE;
break;
case SDL_AUDIO_S16BE:
format = SND_PCM_FORMAT_S16_BE;
break;
case SDL_AUDIO_S32LE:
format = SND_PCM_FORMAT_S32_LE;
break;
case SDL_AUDIO_S32BE:
format = SND_PCM_FORMAT_S32_BE;
break;
case SDL_AUDIO_F32LE:
format = SND_PCM_FORMAT_FLOAT_LE;
break;
case SDL_AUDIO_F32BE:
format = SND_PCM_FORMAT_FLOAT_BE;
break;
default:
continue;
}
if (ALSA_snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) >= 0) {
break;
}
}
if (!test_format) {
return SDL_SetError("ALSA: Unsupported audio format");
}
device->spec.format = test_format;
// Validate number of channels and determine if swizzling is necessary.
// Assume original swizzling, until proven otherwise.
device->hidden->swizzle_func = swizzle_alsa_channels;
#ifdef SND_CHMAP_API_VERSION
snd_pcm_chmap_t *chmap = ALSA_snd_pcm_get_chmap(pcm_handle);
if (chmap) {
char chmap_str[64];
if (ALSA_snd_pcm_chmap_print(chmap, sizeof(chmap_str), chmap_str) > 0) {
if (SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0 ||
SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0) {
device->hidden->swizzle_func = no_swizzle;
}
}
free(chmap); // This should NOT be SDL_free()
}
#endif // SND_CHMAP_API_VERSION
// Set the number of channels
status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams,
device->spec.channels);
unsigned int channels = device->spec.channels;
if (status < 0) {
status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't set audio channels");
}
device->spec.channels = channels;
}
// Set the audio rate
unsigned int rate = device->spec.freq;
status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams,
&rate, NULL);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't set audio frequency: %s", ALSA_snd_strerror(status));
}
device->spec.freq = rate;
// Set the buffer size, in samples
status = ALSA_set_buffer_size(device, hwparams);
if (status < 0) {
return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status));
}
// Set the software parameters
snd_pcm_sw_params_t *swparams = NULL;
snd_pcm_sw_params_alloca(&swparams);
status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't get software config: %s", ALSA_snd_strerror(status));
}
status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, device->sample_frames);
if (status < 0) {
return SDL_SetError("Couldn't set minimum available samples: %s", ALSA_snd_strerror(status));
}
status =
ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't set start threshold: %s", ALSA_snd_strerror(status));
}
status = ALSA_snd_pcm_sw_params(pcm_handle, swparams);
if (status < 0) {
return SDL_SetError("Couldn't set software audio parameters: %s", ALSA_snd_strerror(status));
}
// Calculate the final parameters for this audio specification
SDL_UpdatedAudioDeviceFormat(device);
// Allocate mixing buffer
if (!iscapture) {
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (!device->hidden->mixbuf) {
return -1;
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
}
#if !SDL_ALSA_NON_BLOCKING
if (!iscapture) {
ALSA_snd_pcm_nonblock(pcm_handle, 0);
}
#endif
ALSA_snd_pcm_start(pcm_handle);
return 0; // We're ready to rock and roll. :-)
}
static void add_device(const SDL_bool iscapture, const char *name, void *hint, ALSA_Device **pSeen)
{
ALSA_Device *dev = SDL_malloc(sizeof(ALSA_Device));
char *desc;
char *ptr;
if (!dev) {
return;
}
// Not all alsa devices are enumerable via snd_device_name_get_hint
// (i.e. bluetooth devices). Therefore if hint is passed in to this
// function as NULL, assume name contains desc.
// Make sure not to free the storage associated with desc in this case
if (hint) {
desc = ALSA_snd_device_name_get_hint(hint, "DESC");
if (!desc) {
SDL_free(dev);
return;
}
} else {
desc = (char *)name;
}
SDL_assert(name != NULL);
// some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output".
// just chop the extra lines off, this seems to get a reasonable device
// name without extra details.
ptr = SDL_strchr(desc, '\n');
if (ptr) {
*ptr = '\0';
}
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: adding %s device '%s' (%s)", iscapture ? "capture" : "output", name, desc);
dev->name = SDL_strdup(name);
if (!dev->name) {
if (hint) {
free(desc); // This should NOT be SDL_free()
}
SDL_free(dev->name);
SDL_free(dev);
return;
}
// Note that spec is NULL, because we are required to open the device before
// acquiring the mix format, making this information inaccessible at
// enumeration time
SDL_AddAudioDevice(iscapture, desc, NULL, dev);
if (hint) {
free(desc); // This should NOT be SDL_free()
}
dev->iscapture = iscapture;
dev->next = *pSeen;
*pSeen = dev;
}
static ALSA_Device *hotplug_devices = NULL;
static void ALSA_HotplugIteration(SDL_bool *has_default_output, SDL_bool *has_default_capture)
{
void **hints = NULL;
ALSA_Device *unseen = NULL;
ALSA_Device *seen = NULL;
if (ALSA_snd_device_name_hint(-1, "pcm", &hints) == 0) {
const char *match = NULL;
int bestmatch = 0xFFFF;
int has_default = -1;
size_t match_len = 0;
static const char *const prefixes[] = {
"hw:", "sysdefault:", "default:", NULL
};
unseen = hotplug_devices;
seen = NULL;
// Apparently there are several different ways that ALSA lists
// actual hardware. It could be prefixed with "hw:" or "default:"
// or "sysdefault:" and maybe others. Go through the list and see
// if we can find a preferred prefix for the system.
for (int i = 0; hints[i]; i++) {
char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
if (!name) {
continue;
}
if (SDL_strcmp(name, "default") == 0) {
if (has_default < 0) {
has_default = i;
}
} else {
for (int j = 0; prefixes[j]; j++) {
const char *prefix = prefixes[j];
const size_t prefixlen = SDL_strlen(prefix);
if (SDL_strncmp(name, prefix, prefixlen) == 0) {
if (j < bestmatch) {
bestmatch = j;
match = prefix;
match_len = prefixlen;
}
}
}
free(name); // This should NOT be SDL_free()
}
}
// look through the list of device names to find matches
if (match || (has_default >= 0)) { // did we find a device name prefix we like at all...?
for (int i = 0; hints[i]; i++) {
char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
if (!name) {
continue;
}
// only want physical hardware interfaces
const SDL_bool is_default = (has_default == i);
if (is_default || (match && SDL_strncmp(name, match, match_len) == 0)) {
char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID");
const SDL_bool isoutput = (!ioid) || (SDL_strcmp(ioid, "Output") == 0);
const SDL_bool isinput = (!ioid) || (SDL_strcmp(ioid, "Input") == 0);
SDL_bool have_output = SDL_FALSE;
SDL_bool have_input = SDL_FALSE;
free(ioid); // This should NOT be SDL_free()
if (!isoutput && !isinput) {
free(name); // This should NOT be SDL_free()
continue;
}
if (is_default) {
if (has_default_output && isoutput) {
*has_default_output = SDL_TRUE;
} else if (has_default_capture && isinput) {
*has_default_capture = SDL_TRUE;
}
free(name); // This should NOT be SDL_free()
continue;
}
ALSA_Device *prev = NULL;
ALSA_Device *next;
for (ALSA_Device *dev = unseen; dev; dev = next) {
next = dev->next;
if ((SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->iscapture) || ((isoutput) && !dev->iscapture))) {
if (prev) {
prev->next = next;
} else {
unseen = next;
}
dev->next = seen;
seen = dev;
if (isinput) {
have_input = SDL_TRUE;
}
if (isoutput) {
have_output = SDL_TRUE;
}
} else {
prev = dev;
}
}
if (isinput && !have_input) {
add_device(SDL_TRUE, name, hints[i], &seen);
}
if (isoutput && !have_output) {
add_device(SDL_FALSE, name, hints[i], &seen);
}
}
free(name); // This should NOT be SDL_free()
}
}
ALSA_snd_device_name_free_hint(hints);
hotplug_devices = seen; // now we have a known-good list of attached devices.
// report anything still in unseen as removed.
ALSA_Device *next = NULL;
for (ALSA_Device *dev = unseen; dev; dev = next) {
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: removing %s device '%s'", dev->iscapture ? "capture" : "output", dev->name);
next = dev->next;
SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(dev));
SDL_free(dev->name);
SDL_free(dev);
}
}
}
#if SDL_ALSA_HOTPLUG_THREAD
static SDL_AtomicInt ALSA_hotplug_shutdown;
static SDL_Thread *ALSA_hotplug_thread;
static int SDLCALL ALSA_HotplugThread(void *arg)
{
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW);
while (!SDL_AtomicGet(&ALSA_hotplug_shutdown)) {
// Block awhile before checking again, unless we're told to stop.
const Uint64 ticks = SDL_GetTicks() + 5000;
while (!SDL_AtomicGet(&ALSA_hotplug_shutdown) && SDL_GetTicks() < ticks) {
SDL_Delay(100);
}
ALSA_HotplugIteration(NULL, NULL); // run the check.
}
return 0;
}
#endif
static void ALSA_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
// ALSA doesn't have a concept of a changeable default device, afaik, so we expose a generic default
// device here. It's the best we can do at this level.
SDL_bool has_default_output = SDL_FALSE, has_default_capture = SDL_FALSE;
ALSA_HotplugIteration(&has_default_output, &has_default_capture); // run once now before a thread continues to check.
if (has_default_output) {
*default_output = SDL_AddAudioDevice(/*iscapture=*/SDL_FALSE, "ALSA default output device", NULL, (void*)&default_output_handle);
}
if (has_default_capture) {
*default_capture = SDL_AddAudioDevice(/*iscapture=*/SDL_TRUE, "ALSA default capture device", NULL, (void*)&default_capture_handle);
}
#if SDL_ALSA_HOTPLUG_THREAD
SDL_AtomicSet(&ALSA_hotplug_shutdown, 0);
ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL);
// if the thread doesn't spin, oh well, you just don't get further hotplug events.
#endif
}
static void ALSA_DeinitializeStart(void)
{
ALSA_Device *dev;
ALSA_Device *next;
#if SDL_ALSA_HOTPLUG_THREAD
if (ALSA_hotplug_thread) {
SDL_AtomicSet(&ALSA_hotplug_shutdown, 1);
SDL_WaitThread(ALSA_hotplug_thread, NULL);
ALSA_hotplug_thread = NULL;
}
#endif
// Shutting down! Clean up any data we've gathered.
for (dev = hotplug_devices; dev; dev = next) {
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: at shutdown, removing %s device '%s'", dev->iscapture ? "capture" : "output", dev->name);
next = dev->next;
SDL_free(dev->name);
SDL_free(dev);
}
hotplug_devices = NULL;
}
static void ALSA_Deinitialize(void)
{
UnloadALSALibrary();
}
static SDL_bool ALSA_Init(SDL_AudioDriverImpl *impl)
{
if (LoadALSALibrary() < 0) {
return SDL_FALSE;
}
impl->DetectDevices = ALSA_DetectDevices;
impl->OpenDevice = ALSA_OpenDevice;
impl->WaitDevice = ALSA_WaitDevice;
impl->GetDeviceBuf = ALSA_GetDeviceBuf;
impl->PlayDevice = ALSA_PlayDevice;
impl->CloseDevice = ALSA_CloseDevice;
impl->DeinitializeStart = ALSA_DeinitializeStart;
impl->Deinitialize = ALSA_Deinitialize;
impl->WaitCaptureDevice = ALSA_WaitDevice;
impl->CaptureFromDevice = ALSA_CaptureFromDevice;
impl->FlushCapture = ALSA_FlushCapture;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap ALSA_bootstrap = {
"alsa", "ALSA PCM audio", ALSA_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_ALSA
+42
View File
@@ -0,0 +1,42 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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"
struct SDL_PrivateAudioData
{
// The audio device handle
snd_pcm_t *pcm_handle;
// Raw mixing buffer
Uint8 *mixbuf;
// swizzle function
void (*swizzle_func)(SDL_AudioDevice *_this, void *buffer, Uint32 bufferlen);
};
#endif // SDL_ALSA_audio_h_
@@ -0,0 +1,191 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_ANDROID
// Output audio to Android (legacy interface)
#include "../SDL_sysaudio.h"
#include "SDL_androidaudio.h"
#include "../../core/android/SDL_android.h"
#include <android/log.h>
struct SDL_PrivateAudioData
{
int resume; // Resume device if it was paused automatically
};
static SDL_AudioDevice *audioDevice = NULL;
static SDL_AudioDevice *captureDevice = NULL;
static int ANDROIDAUDIO_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return -1;
}
const SDL_bool iscapture = device->iscapture;
if (iscapture) {
if (captureDevice) {
return SDL_SetError("An audio capture device is already opened");
}
captureDevice = device;
} else {
if (audioDevice) {
return SDL_SetError("An audio playback device is already opened");
}
audioDevice = device;
}
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
if ((test_format == SDL_AUDIO_U8) ||
(test_format == SDL_AUDIO_S16) ||
(test_format == SDL_AUDIO_F32)) {
device->spec.format = test_format;
break;
}
}
if (!test_format) {
return SDL_SetError("android: Unsupported audio format");
}
if (Android_JNI_OpenAudioDevice(device) < 0) {
return -1;
}
SDL_UpdatedAudioDeviceFormat(device);
return 0;
}
// !!! FIXME: this needs a WaitDevice implementation.
static int ANDROIDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
Android_JNI_WriteAudioBuffer();
return 0;
}
static Uint8 *ANDROIDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return Android_JNI_GetAudioBuffer();
}
static int ANDROIDAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
return Android_JNI_CaptureAudioBuffer(buffer, buflen);
}
static void ANDROIDAUDIO_FlushCapture(SDL_AudioDevice *device)
{
Android_JNI_FlushCapturedAudio();
}
static void ANDROIDAUDIO_CloseDevice(SDL_AudioDevice *device)
{
/* At this point SDL_CloseAudioDevice via close_audio_device took care of terminating the audio thread
so it's safe to terminate the Java side buffer and AudioTrack
*/
if (device->hidden) {
Android_JNI_CloseAudioDevice(device->iscapture);
if (device->iscapture) {
SDL_assert(captureDevice == device);
captureDevice = NULL;
} else {
SDL_assert(audioDevice == device);
audioDevice = NULL;
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
// Pause (block) all non already paused audio devices by taking their mixer lock
void ANDROIDAUDIO_PauseDevices(void)
{
// TODO: Handle multiple devices?
struct SDL_PrivateAudioData *hidden;
if (audioDevice && audioDevice->hidden) {
hidden = (struct SDL_PrivateAudioData *)audioDevice->hidden;
SDL_LockMutex(audioDevice->lock);
hidden->resume = SDL_TRUE;
}
if (captureDevice && captureDevice->hidden) {
hidden = (struct SDL_PrivateAudioData *)captureDevice->hidden;
SDL_LockMutex(captureDevice->lock);
hidden->resume = SDL_TRUE;
}
}
// Resume (unblock) all non already paused audio devices by releasing their mixer lock
void ANDROIDAUDIO_ResumeDevices(void)
{
// TODO: Handle multiple devices?
struct SDL_PrivateAudioData *hidden;
if (audioDevice && audioDevice->hidden) {
hidden = (struct SDL_PrivateAudioData *)audioDevice->hidden;
if (hidden->resume) {
hidden->resume = SDL_FALSE;
SDL_UnlockMutex(audioDevice->lock);
}
}
if (captureDevice && captureDevice->hidden) {
hidden = (struct SDL_PrivateAudioData *)captureDevice->hidden;
if (hidden->resume) {
hidden->resume = SDL_FALSE;
SDL_UnlockMutex(captureDevice->lock);
}
}
}
static SDL_bool ANDROIDAUDIO_Init(SDL_AudioDriverImpl *impl)
{
// !!! FIXME: if on Android API < 24, DetectDevices and Deinitialize should be NULL and OnlyHasDefaultOutputDevice and OnlyHasDefaultCaptureDevice should be SDL_TRUE, since audio device enum and hotplug appears to require Android 7.0+.
impl->ThreadInit = Android_AudioThreadInit;
impl->DetectDevices = Android_StartAudioHotplug;
impl->DeinitializeStart = Android_StopAudioHotplug;
impl->OpenDevice = ANDROIDAUDIO_OpenDevice;
impl->PlayDevice = ANDROIDAUDIO_PlayDevice;
impl->GetDeviceBuf = ANDROIDAUDIO_GetDeviceBuf;
impl->CloseDevice = ANDROIDAUDIO_CloseDevice;
impl->CaptureFromDevice = ANDROIDAUDIO_CaptureFromDevice;
impl->FlushCapture = ANDROIDAUDIO_FlushCapture;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap ANDROIDAUDIO_bootstrap = {
"android", "SDL Android audio driver", ANDROIDAUDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_ANDROID
@@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_androidaudio_h_
#define SDL_androidaudio_h_
#ifdef SDL_AUDIO_DRIVER_ANDROID
void ANDROIDAUDIO_ResumeDevices(void);
void ANDROIDAUDIO_PauseDevices(void);
#else
static void ANDROIDAUDIO_ResumeDevices(void) {}
static void ANDROIDAUDIO_PauseDevices(void) {}
#endif
#endif // SDL_androidaudio_h_
@@ -0,0 +1,68 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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
SDL_bool interrupted;
CFTypeRef interruption_listener;
#endif
};
#endif // SDL_coreaudio_h_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,680 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 SDL_bool SupportsIMMDevice = SDL_FALSE;
#endif
// DirectX function pointers for audio
static void *DSoundDLL = NULL;
typedef HRESULT(WINAPI *fnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
typedef HRESULT(WINAPI *fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
typedef HRESULT(WINAPI *fnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN);
typedef HRESULT(WINAPI *fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
typedef HRESULT(WINAPI *fnGetDeviceID)(LPCGUID, LPGUID);
static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL;
static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL;
static fnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL;
static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL;
static fnGetDeviceID pGetDeviceID = NULL;
#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 int DSOUND_Load(void)
{
int loaded = 0;
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 = 0; \
}
loaded = 1; // 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 int 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
{
SDL_bool iscapture;
SDL_AudioDevice **default_device;
LPCGUID default_device_guid;
} FindAllDevsData;
static BOOL CALLBACK FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID userdata)
{
FindAllDevsData *data = (FindAllDevsData *) userdata;
if (guid != NULL) { // skip default device
char *str = WIN_LookupAudioDeviceName(desc, guid);
if (str) {
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->iscapture, str, NULL, cpyguid);
if (device && data->default_device && data->default_device_guid) {
if (SDL_memcmp(cpyguid, data->default_device_guid, sizeof (GUID)) == 0) {
*data->default_device = device;
}
}
}
SDL_free(str); // SDL_AddAudioDevice() makes a copy of this string.
}
}
return TRUE; // keep enumerating.
}
static void DSOUND_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
#ifdef HAVE_MMDEVICEAPI_H
if (SupportsIMMDevice) {
SDL_IMMDevice_EnumerateEndpoints(default_output, default_capture);
} else
#endif
{
// Without IMMDevice, you can enumerate devices and figure out the default devices,
// but you won't get device hotplug or default device change notifications. But this is
// only for WinXP; Windows Vista and later should be using IMMDevice.
FindAllDevsData data;
GUID guid;
data.iscapture = SDL_TRUE;
data.default_device = default_capture;
data.default_device_guid = (pGetDeviceID(&SDL_DSDEVID_DefaultCapture, &guid) == DS_OK) ? &guid : NULL;
pDirectSoundCaptureEnumerateW(FindAllDevs, &data);
data.iscapture = SDL_FALSE;
data.default_device = default_output;
data.default_device_guid = (pGetDeviceID(&SDL_DSDEVID_DefaultPlayback, &guid) == DS_OK) ? &guid : NULL;
pDirectSoundEnumerateW(FindAllDevs, &data);
}
}
static int 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_AtomicGet(&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 -1;
}
SDL_Delay(1); // not ready yet; sleep a bit.
}
return 0;
}
static int 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 -1;
}
return 0;
}
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 int DSOUND_WaitCaptureDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *h = device->hidden;
while (!SDL_AtomicGet(&device->shutdown)) {
DWORD junk, cursor;
if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) {
return -1;
} else if ((cursor / device->buffer_size) != h->lastchunk) {
break;
}
SDL_Delay(1);
}
return 0;
}
static int DSOUND_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
struct SDL_PrivateAudioData *h = device->hidden;
DWORD ptr1len, ptr2len;
VOID *ptr1, *ptr2;
SDL_assert(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_FlushCapture(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 capture.
*/
static int 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 0; // We're ready to go
}
/* This function tries to create a capture buffer, and returns the
number of audio chunks available in the created buffer. This is for
capture devices, not playback.
*/
static int CreateCaptureBuffer(SDL_AudioDevice *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 0;
}
static int 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 -1;
}
// Open the audio device
LPGUID guid;
#ifdef HAVE_MMDEVICEAPI_H
if (SupportsIMMDevice) {
guid = SDL_IMMDevice_GetDirectSoundGUID(device);
} else
#endif
{
guid = (LPGUID) device->handle;
}
SDL_assert(guid != NULL);
HRESULT result;
if (device->iscapture) {
result = pDirectSoundCaptureCreate8(guid, &device->hidden->capture, NULL);
if (result != DS_OK) {
return SetDSerror("DirectSoundCaptureCreate8", result);
}
} else {
result = pDirectSoundCreate8(guid, &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;
SDL_bool tried_format = SDL_FALSE;
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
switch (test_format) {
case SDL_AUDIO_U8:
case SDL_AUDIO_S16:
case SDL_AUDIO_S32:
case SDL_AUDIO_F32:
tried_format = SDL_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 int rc = device->iscapture ? CreateCaptureBuffer(device, bufsize, (WAVEFORMATEX *)&wfmt) : CreateSecondary(device, bufsize, (WAVEFORMATEX *)&wfmt);
if (rc == 0) {
device->hidden->num_buffers = numchunks;
break;
}
}
continue;
default:
continue;
}
break;
}
if (!test_format) {
if (tried_format) {
return -1; // CreateSecondary() should have called SDL_SetError().
}
return SDL_SetError("%s: Unsupported audio format", "directsound");
}
// Playback buffers will auto-start playing in DSOUND_WaitDevice()
return 0; // 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 = SDL_FALSE;
#endif
}
static SDL_bool DSOUND_Init(SDL_AudioDriverImpl *impl)
{
if (!DSOUND_Load()) {
return SDL_FALSE;
}
#ifdef HAVE_MMDEVICEAPI_H
SupportsIMMDevice = !(SDL_IMMDevice_Init(NULL) < 0);
#endif
impl->DetectDevices = DSOUND_DetectDevices;
impl->OpenDevice = DSOUND_OpenDevice;
impl->PlayDevice = DSOUND_PlayDevice;
impl->WaitDevice = DSOUND_WaitDevice;
impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
impl->WaitCaptureDevice = DSOUND_WaitCaptureDevice;
impl->CaptureFromDevice = DSOUND_CaptureFromDevice;
impl->FlushCapture = DSOUND_FlushCapture;
impl->CloseDevice = DSOUND_CloseDevice;
impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle;
impl->DeinitializeStart = DSOUND_DeinitializeStart;
impl->Deinitialize = DSOUND_Deinitialize;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap DSOUND_bootstrap = {
"directsound", "DirectSound", DSOUND_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_DSOUND
@@ -0,0 +1,43 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_
+173
View File
@@ -0,0 +1,173 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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"
// !!! FIXME: these should be SDL hints, not environment variables.
// environment variables and defaults.
#define DISKENVR_OUTFILE "SDL_DISKAUDIOFILE"
#define DISKDEFAULT_OUTFILE "sdlaudio.raw"
#define DISKENVR_INFILE "SDL_DISKAUDIOFILEIN"
#define DISKDEFAULT_INFILE "sdlaudio-in.raw"
#define DISKENVR_IODELAY "SDL_DISKAUDIODELAY"
static int DISKAUDIO_WaitDevice(SDL_AudioDevice *device)
{
SDL_Delay(device->hidden->io_delay);
return 0;
}
static int 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 -1;
}
#ifdef DEBUG_AUDIO
SDL_Log("DISKAUDIO: Wrote %d bytes of audio data", (int) written);
#endif
return 0;
}
static Uint8 *DISKAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int DISKAUDIO_CaptureFromDevice(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_FlushCapture(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 SDL_bool iscapture)
{
const char *devname = SDL_getenv(iscapture ? DISKENVR_INFILE : DISKENVR_OUTFILE);
if (!devname) {
devname = iscapture ? DISKDEFAULT_INFILE : DISKDEFAULT_OUTFILE;
}
return devname;
}
static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device)
{
SDL_bool iscapture = device->iscapture;
const char *fname = get_filename(iscapture);
const char *envr = SDL_getenv(DISKENVR_IODELAY);
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return -1;
}
if (envr) {
device->hidden->io_delay = SDL_atoi(envr);
} else {
device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq);
}
// Open the "audio device"
device->hidden->io = SDL_IOFromFile(fname, iscapture ? "rb" : "wb");
if (!device->hidden->io) {
return -1;
}
// Allocate mixing buffer
if (!iscapture) {
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (!device->hidden->mixbuf) {
return -1;
}
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].\n", iscapture ? "Reading from" : "Writing to", fname);
return 0; // We're ready to rock and roll. :-)
}
static void DISKAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
*default_output = SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1);
*default_capture = SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2);
}
static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = DISKAUDIO_OpenDevice;
impl->WaitDevice = DISKAUDIO_WaitDevice;
impl->WaitCaptureDevice = DISKAUDIO_WaitDevice;
impl->PlayDevice = DISKAUDIO_PlayDevice;
impl->GetDeviceBuf = DISKAUDIO_GetDeviceBuf;
impl->CaptureFromDevice = DISKAUDIO_CaptureFromDevice;
impl->FlushCapture = DISKAUDIO_FlushCapture;
impl->CloseDevice = DISKAUDIO_CloseDevice;
impl->DetectDevices = DISKAUDIO_DetectDevices;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap DISKAUDIO_bootstrap = {
"disk", "direct-to-disk audio", DISKAUDIO_Init, SDL_TRUE
};
#endif // SDL_AUDIO_DRIVER_DISK
+36
View File
@@ -0,0 +1,36 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_
+304
View File
@@ -0,0 +1,304 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_utils_c.h"
#include "SDL_dspaudio.h"
static void DSP_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
SDL_EnumUnixAudioDevices(SDL_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 int DSP_OpenDevice(SDL_AudioDevice *device)
{
// Make sure fragment size stays a power of 2, or OSS fails.
// (I don't know which of these are actually legal values, though...)
if (device->spec.channels > 8) {
device->spec.channels = 8;
} else if (device->spec.channels > 4) {
device->spec.channels = 4;
} else if (device->spec.channels > 2) {
device->spec.channels = 2;
}
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return -1;
}
// Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
const int flags = ((device->iscapture) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT);
device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC, 0);
if (device->hidden->audio_fd < 0) {
return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno));
}
// Make the file descriptor use blocking i/o with fcntl()
{
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->iscapture) {
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (!device->hidden->mixbuf) {
return -1;
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
}
return 0; // We're ready to rock and roll. :-)
}
static int DSP_WaitDevice(SDL_AudioDevice *device)
{
const unsigned long ioctlreq = device->iscapture ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE;
struct SDL_PrivateAudioData *h = device->hidden;
while (!SDL_AtomicGet(&device->shutdown)) {
audio_buf_info info;
const int rc = ioctl(h->audio_fd, ioctlreq, &info);
if (rc < 0) {
if (errno == EAGAIN) {
continue;
}
// Hmm, not much we can do - abort
fprintf(stderr, "dsp WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno));
return -1;
} else if (info.bytes < device->buffer_size) {
SDL_Delay(10);
} else {
break; // ready to go!
}
}
return 0;
}
static int 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 -1;
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", h->mixlen);
#endif
return 0;
}
static Uint8 *DSP_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int DSP_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
return (int)read(device->hidden->audio_fd, buffer, buflen);
}
static void DSP_FlushCapture(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 SDL_bool InitTimeDevicesExist = SDL_FALSE;
static SDL_bool look_for_devices_test(int fd)
{
InitTimeDevicesExist = SDL_TRUE; // note that _something_ exists.
// Don't add to the device list, we're just seeing if any devices exist.
return SDL_FALSE;
}
static SDL_bool DSP_Init(SDL_AudioDriverImpl *impl)
{
InitTimeDevicesExist = SDL_FALSE;
SDL_EnumUnixAudioDevices(SDL_FALSE, look_for_devices_test);
if (!InitTimeDevicesExist) {
SDL_SetError("dsp: No such audio device");
return SDL_FALSE; // maybe try a different backend.
}
impl->DetectDevices = DSP_DetectDevices;
impl->OpenDevice = DSP_OpenDevice;
impl->WaitDevice = DSP_WaitDevice;
impl->PlayDevice = DSP_PlayDevice;
impl->GetDeviceBuf = DSP_GetDeviceBuf;
impl->CloseDevice = DSP_CloseDevice;
impl->WaitCaptureDevice = DSP_WaitDevice;
impl->CaptureFromDevice = DSP_CaptureFromDevice;
impl->FlushCapture = DSP_FlushCapture;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap DSP_bootstrap = {
"dsp", "Open Sound System (/dev/dsp)", DSP_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_OSS
+37
View File
@@ -0,0 +1,37 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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,97 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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"
// !!! FIXME: this should be an SDL hint, not an environment variable.
#define DUMMYENVR_IODELAY "SDL_DUMMYAUDIODELAY"
static int DUMMYAUDIO_WaitDevice(SDL_AudioDevice *device)
{
SDL_Delay(device->hidden->io_delay);
return 0;
}
static int DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device)
{
const char *envr = SDL_getenv(DUMMYENVR_IODELAY);
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return -1;
}
if (!device->iscapture) {
device->hidden->mixbuf = (Uint8 *) SDL_malloc(device->buffer_size);
if (!device->hidden->mixbuf) {
return -1;
}
}
device->hidden->io_delay = (Uint32) (envr ? SDL_atoi(envr) : ((device->sample_frames * 1000) / device->spec.freq));
return 0; // we're good; don't change reported device format.
}
static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static Uint8 *DUMMYAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int DUMMYAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
// always return a full buffer of silence.
SDL_memset(buffer, device->silence_value, buflen);
return buflen;
}
static SDL_bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = DUMMYAUDIO_OpenDevice;
impl->CloseDevice = DUMMYAUDIO_CloseDevice;
impl->WaitDevice = DUMMYAUDIO_WaitDevice;
impl->GetDeviceBuf = DUMMYAUDIO_GetDeviceBuf;
impl->WaitCaptureDevice = DUMMYAUDIO_WaitDevice;
impl->CaptureFromDevice = DUMMYAUDIO_CaptureFromDevice;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap DUMMYAUDIO_bootstrap = {
"dummy", "SDL dummy audio driver", DUMMYAUDIO_Init, SDL_TRUE
};
@@ -0,0 +1,34 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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; // miliseconds to sleep in WaitDevice.
};
#endif // SDL_dummyaudio_h_
@@ -0,0 +1,351 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 int EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
{
const int framelen = SDL_AUDIO_FRAMESIZE(device->spec);
MAIN_THREAD_EM_ASM({
var SDL3 = Module['SDL3'];
var numChannels = SDL3.audio.currentOutputBuffer['numberOfChannels'];
for (var c = 0; c < numChannels; ++c) {
var channelData = SDL3.audio.currentOutputBuffer['getChannelData'](c);
if (channelData.length != $1) {
throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
}
for (var j = 0; j < $1; ++j) {
channelData[j] = HEAPF32[$0 + ((j*numChannels + c) << 2) >> 2]; // !!! FIXME: why are these shifts here?
}
}
}, buffer, buffer_size / framelen);
return 0;
}
static void EMSCRIPTENAUDIO_FlushCapture(SDL_AudioDevice *device)
{
// Do nothing, the new data will just be dropped.
}
static int EMSCRIPTENAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
MAIN_THREAD_EM_ASM({
var SDL3 = Module['SDL3'];
var numChannels = SDL3.capture.currentCaptureBuffer.numberOfChannels;
for (var c = 0; c < numChannels; ++c) {
var channelData = SDL3.capture.currentCaptureBuffer.getChannelData(c);
if (channelData.length != $1) {
throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
}
if (numChannels == 1) { // fastpath this a little for the common (mono) case.
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.capture.silenceTimer !== undefined) {
clearInterval(SDL3.capture.silenceTimer);
}
if (SDL3.capture.stream !== undefined) {
var tracks = SDL3.capture.stream.getAudioTracks();
for (var i = 0; i < tracks.length; i++) {
SDL3.capture.stream.removeTrack(tracks[i]);
}
}
if (SDL3.capture.scriptProcessorNode !== undefined) {
SDL3.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {};
SDL3.capture.scriptProcessorNode.disconnect();
}
if (SDL3.capture.mediaStreamNode !== undefined) {
SDL3.capture.mediaStreamNode.disconnect();
}
SDL3.capture = undefined;
} else {
if (SDL3.audio.scriptProcessorNode != undefined) {
SDL3.audio.scriptProcessorNode.disconnect();
}
if (SDL3.audio.silenceTimer !== undefined) {
clearInterval(SDL3.audio.silenceTimer);
}
SDL3.audio = undefined;
}
if ((SDL3.audioContext !== undefined) && (SDL3.audio === undefined) && (SDL3.capture === undefined)) {
SDL3.audioContext.close();
SDL3.audioContext = undefined;
}
}, device->iscapture);
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
device->hidden = NULL;
SDL_AudioThreadFinalize(device);
}
EM_JS_DEPS(sdlaudio, "$autoResumeAudioContext,$dynCall");
static int EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device)
{
// based on parts of library_sdl.js
// create context
const int result = MAIN_THREAD_EM_ASM_INT({
if (typeof(Module['SDL3']) === 'undefined') {
Module['SDL3'] = {};
}
var SDL3 = Module['SDL3'];
if (!$0) {
SDL3.audio = {};
} else {
SDL3.capture = {};
}
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') { // Firefox doesn't have this (as of August 2023), use autoResumeAudioContext instead.
autoResumeAudioContext(SDL3.audioContext);
}
}
}
return SDL3.audioContext === undefined ? -1 : 0;
}, device->iscapture);
if (result < 0) {
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 -1;
}
// limit to native freq
device->spec.freq = EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; });
SDL_UpdatedAudioDeviceFormat(device);
if (!device->iscapture) {
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (!device->hidden->mixbuf) {
return -1;
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
}
if (device->iscapture) {
/* The idea is to take the capture media stream, hook it up to an
audio graph where we can pass it through a ScriptProcessorNode
to access the raw PCM samples and push them to the SDL app's
callback. From there, we "process" the audio data into silence
and forget about it.
This should, strictly speaking, use MediaRecorder for capture, but
this API is cleaner to use and better supported, and fires a
callback whenever there's enough data to fire down into the app.
The downside is that we are spending CPU time silencing a buffer
that the audiocontext uselessly mixes into any output. 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 capture: we have a microphone! Replacing silence callback.');
if (SDL3.capture.silenceTimer !== undefined) {
clearInterval(SDL3.capture.silenceTimer);
SDL3.capture.silenceTimer = undefined;
SDL3.capture.silenceBuffer = undefined
}
SDL3.capture.mediaStreamNode = SDL3.audioContext.createMediaStreamSource(stream);
SDL3.capture.scriptProcessorNode = SDL3.audioContext.createScriptProcessor($1, $0, 1);
SDL3.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {
if ((SDL3 === undefined) || (SDL3.capture === undefined)) { return; }
audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0);
SDL3.capture.currentCaptureBuffer = audioProcessingEvent.inputBuffer;
dynCall('vi', $2, [$3]);
};
SDL3.capture.mediaStreamNode.connect(SDL3.capture.scriptProcessorNode);
SDL3.capture.scriptProcessorNode.connect(SDL3.audioContext.destination);
SDL3.capture.stream = stream;
};
var no_microphone = function(error) {
//console.log('SDL audio capture: we DO NOT have a microphone! (' + error.name + ')...leaving silence callback running.');
};
// we write silence to the audio callback until the microphone is available (user approves use, etc).
SDL3.capture.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
SDL3.capture.silenceBuffer.getChannelData(0).fill(0.0);
var silence_callback = function() {
SDL3.capture.currentCaptureBuffer = SDL3.capture.silenceBuffer;
dynCall('vi', $2, [$3]);
};
SDL3.capture.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_CaptureAudioThreadIterate, device);
} else {
// setup a ScriptProcessorNode
MAIN_THREAD_EM_ASM({
var SDL3 = Module['SDL3'];
SDL3.audio.scriptProcessorNode = SDL3.audioContext['createScriptProcessor']($1, 0, $0);
SDL3.audio.scriptProcessorNode['onaudioprocess'] = function (e) {
if ((SDL3 === undefined) || (SDL3.audio === undefined)) { return; }
// if we're actually running the node, we don't need the fake callback anymore, so kill it.
if (SDL3.audio.silenceTimer !== undefined) {
clearInterval(SDL3.audio.silenceTimer);
SDL3.audio.silenceTimer = undefined;
SDL3.audio.silenceBuffer = undefined;
}
SDL3.audio.currentOutputBuffer = e['outputBuffer'];
dynCall('vi', $2, [$3]);
};
SDL3.audio.scriptProcessorNode['connect'](SDL3.audioContext['destination']);
if (SDL3.audioContext.state === 'suspended') { // uhoh, autoplay is blocked.
SDL3.audio.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
SDL3.audio.silenceBuffer.getChannelData(0).fill(0.0);
var silence_callback = function() {
if ((typeof navigator.userActivation) !== 'undefined') { // Almost everything modern except Firefox (as of August 2023)
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.currentOutputBuffer = SDL3.audio.silenceBuffer;
dynCall('vi', $2, [$3]);
SDL3.audio.currentOutputBuffer = undefined;
};
SDL3.audio.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
}
}, device->spec.channels, device->sample_frames, SDL_OutputAudioThreadIterate, device);
}
return 0;
}
static SDL_bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl)
{
SDL_bool available, capture_available;
impl->OpenDevice = EMSCRIPTENAUDIO_OpenDevice;
impl->CloseDevice = EMSCRIPTENAUDIO_CloseDevice;
impl->GetDeviceBuf = EMSCRIPTENAUDIO_GetDeviceBuf;
impl->PlayDevice = EMSCRIPTENAUDIO_PlayDevice;
impl->FlushCapture = EMSCRIPTENAUDIO_FlushCapture;
impl->CaptureFromDevice = EMSCRIPTENAUDIO_CaptureFromDevice;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
// technically, this is just runs in idle time in the main thread, but it's close enough to a "thread" for our purposes.
impl->ProvidesOwnCallbackThread = SDL_TRUE;
// 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");
}
capture_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->HasCaptureSupport = capture_available;
impl->OnlyHasDefaultCaptureDevice = capture_available;
return available;
}
AudioBootStrap EMSCRIPTENAUDIO_bootstrap = {
"emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, SDL_FALSE
};
/* *INDENT-ON* */ /* clang-format on */
#endif // SDL_AUDIO_DRIVER_EMSCRIPTEN
@@ -0,0 +1,33 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_
+222
View File
@@ -0,0 +1,222 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_HAIKU
// Allow access to the audio stream on Haiku
#include <SoundPlayer.h>
#include <signal.h>
#include "../../core/haiku/SDL_BeApp.h"
extern "C"
{
#include "../SDL_sysaudio.h"
#include "SDL_haikuaudio.h"
}
static Uint8 *HAIKUAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
SDL_assert(device->hidden->current_buffer != NULL);
SDL_assert(device->hidden->current_buffer_len > 0);
*buffer_size = device->hidden->current_buffer_len;
return device->hidden->current_buffer;
}
static int HAIKUAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
{
// We already wrote our output right into the BSoundPlayer's callback's stream. Just clean up our stuff.
SDL_assert(device->hidden->current_buffer != NULL);
SDL_assert(device->hidden->current_buffer_len > 0);
device->hidden->current_buffer = NULL;
device->hidden->current_buffer_len = 0;
return 0;
}
// The Haiku callback for handling the audio buffer
static void FillSound(void *data, void *stream, size_t len, const media_raw_audio_format & format)
{
SDL_AudioDevice *device = (SDL_AudioDevice *)data;
SDL_assert(device->hidden->current_buffer == NULL);
SDL_assert(device->hidden->current_buffer_len == 0);
device->hidden->current_buffer = (Uint8 *) stream;
device->hidden->current_buffer_len = (int) len;
SDL_OutputAudioThreadIterate(device);
}
static void HAIKUAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->audio_obj) {
device->hidden->audio_obj->Stop();
delete device->hidden->audio_obj;
}
delete device->hidden;
device->hidden = NULL;
SDL_AudioThreadFinalize(device);
}
}
static const int sig_list[] = {
SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGALRM, SIGTERM, SIGWINCH, 0
};
static inline void MaskSignals(sigset_t * omask)
{
sigset_t mask;
int i;
sigemptyset(&mask);
for (i = 0; sig_list[i]; ++i) {
sigaddset(&mask, sig_list[i]);
}
sigprocmask(SIG_BLOCK, &mask, omask);
}
static inline void UnmaskSignals(sigset_t * omask)
{
sigprocmask(SIG_SETMASK, omask, NULL);
}
static int HAIKUAUDIO_OpenDevice(SDL_AudioDevice *device)
{
// Initialize all variables that we clean on shutdown
device->hidden = new SDL_PrivateAudioData;
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
SDL_zerop(device->hidden);
// Parse the audio format and fill the Be raw audio format
media_raw_audio_format format;
SDL_zero(format);
format.byte_order = B_MEDIA_LITTLE_ENDIAN;
format.frame_rate = (float) device->spec.freq;
format.channel_count = device->spec.channels; // !!! FIXME: support > 2?
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
switch (test_format) {
case SDL_AUDIO_S8:
format.format = media_raw_audio_format::B_AUDIO_CHAR;
break;
case SDL_AUDIO_U8:
format.format = media_raw_audio_format::B_AUDIO_UCHAR;
break;
case SDL_AUDIO_S16LE:
format.format = media_raw_audio_format::B_AUDIO_SHORT;
break;
case SDL_AUDIO_S16BE:
format.format = media_raw_audio_format::B_AUDIO_SHORT;
format.byte_order = B_MEDIA_BIG_ENDIAN;
break;
case SDL_AUDIO_S32LE:
format.format = media_raw_audio_format::B_AUDIO_INT;
break;
case SDL_AUDIO_S32BE:
format.format = media_raw_audio_format::B_AUDIO_INT;
format.byte_order = B_MEDIA_BIG_ENDIAN;
break;
case SDL_AUDIO_F32LE:
format.format = media_raw_audio_format::B_AUDIO_FLOAT;
break;
case SDL_AUDIO_F32BE:
format.format = media_raw_audio_format::B_AUDIO_FLOAT;
format.byte_order = B_MEDIA_BIG_ENDIAN;
break;
default:
continue;
}
break;
}
if (!test_format) { // shouldn't happen, but just in case...
return SDL_SetError("HAIKU: Unsupported audio format");
}
device->spec.format = test_format;
// Calculate the final parameters for this audio specification
SDL_UpdatedAudioDeviceFormat(device);
format.buffer_size = device->buffer_size;
// Subscribe to the audio stream (creates a new thread)
sigset_t omask;
MaskSignals(&omask);
device->hidden->audio_obj = new BSoundPlayer(&format, "SDL Audio",
FillSound, NULL, device);
UnmaskSignals(&omask);
if (device->hidden->audio_obj->Start() == B_NO_ERROR) {
device->hidden->audio_obj->SetHasData(true);
} else {
return SDL_SetError("Unable to start Haiku audio");
}
return 0; // We're running!
}
static void HAIKUAUDIO_Deinitialize(void)
{
SDL_QuitBeApp();
}
static SDL_bool HAIKUAUDIO_Init(SDL_AudioDriverImpl *impl)
{
if (SDL_InitBeApp() < 0) {
return SDL_FALSE;
}
// Set the function pointers
impl->OpenDevice = HAIKUAUDIO_OpenDevice;
impl->GetDeviceBuf = HAIKUAUDIO_GetDeviceBuf;
impl->PlayDevice = HAIKUAUDIO_PlayDevice;
impl->CloseDevice = HAIKUAUDIO_CloseDevice;
impl->Deinitialize = HAIKUAUDIO_Deinitialize;
impl->ProvidesOwnCallbackThread = SDL_TRUE;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
return SDL_TRUE;
}
extern "C" { extern AudioBootStrap HAIKUAUDIO_bootstrap; }
AudioBootStrap HAIKUAUDIO_bootstrap = {
"haiku", "Haiku BSoundPlayer", HAIKUAUDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_HAIKU
@@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_
+444
View File
@@ -0,0 +1,444 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 int load_jack_syms(void);
#ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC
static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC;
static void *jack_handle = NULL;
// !!! FIXME: this is copy/pasted in several places now
static int 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 0;
}
return 1;
}
// 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 -1
static void UnloadJackLibrary(void)
{
if (jack_handle) {
SDL_UnloadObject(jack_handle);
jack_handle = NULL;
}
}
static int LoadJackLibrary(void)
{
int retval = 0;
if (!jack_handle) {
jack_handle = SDL_LoadObject(jack_library);
if (!jack_handle) {
retval = -1;
// Don't call SDL_SetError(): SDL_LoadObject already did.
} else {
retval = load_jack_syms();
if (retval < 0) {
UnloadJackLibrary();
}
}
}
return retval;
}
#else
#define SDL_JACK_SYM(x) JACK_##x = x
static void UnloadJackLibrary(void)
{
}
static int LoadJackLibrary(void)
{
load_jack_syms();
return 0;
}
#endif // SDL_AUDIO_DRIVER_JACK_DYNAMIC
static int 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 0;
}
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) < 0) {
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) < 0) {
SDL_AudioDeviceDisconnected(device);
}
return 0;
}
static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg)
{
SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
SDL_OutputAudioThreadIterate((SDL_AudioDevice *)arg);
return 0;
}
static int 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 0;
}
static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return (Uint8 *)device->hidden->iobuffer;
}
static int jackProcessCaptureCallback(jack_nframes_t nframes, void *arg)
{
SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
SDL_CaptureAudioThreadIterate((SDL_AudioDevice *)arg);
return 0;
}
static int JACK_CaptureFromDevice(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_FlushCapture(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)
{
const char *retval = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME);
if (retval && *retval) {
return retval;
}
retval = SDL_GetHint(SDL_HINT_APP_NAME);
if (retval && *retval) {
return retval;
}
return "SDL Application";
}
static int JACK_OpenDevice(SDL_AudioDevice *device)
{
/* Note that JACK uses "output" for capture devices (they output audio
data to us) and "input" for playback (we input audio data to them).
Likewise, SDL's playback port will be "output" (we write data out)
and capture will be "input" (we read data in). */
SDL_bool iscapture = device->iscapture;
const unsigned long sysportflags = iscapture ? JackPortIsOutput : JackPortIsInput;
const unsigned long sdlportflags = iscapture ? JackPortIsInput : JackPortIsOutput;
const JackProcessCallback callback = iscapture ? jackProcessCaptureCallback : jackProcessPlaybackCallback;
const char *sdlportstr = iscapture ? "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 -1;
}
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 (!device->iscapture) {
device->hidden->iobuffer = (float *)SDL_calloc(1, device->buffer_size);
if (!device->hidden->iobuffer) {
SDL_free(audio_ports);
return -1;
}
}
// 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 -1;
}
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 = iscapture ? devports[audio_ports[i]] : sdlport;
const char *dstport = iscapture ? 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 0;
}
static void JACK_Deinitialize(void)
{
UnloadJackLibrary();
}
static SDL_bool JACK_Init(SDL_AudioDriverImpl *impl)
{
if (LoadJackLibrary() < 0) {
return SDL_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();
SDL_SetError("Can't open JACK client");
return SDL_FALSE;
}
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->CaptureFromDevice = JACK_CaptureFromDevice;
impl->FlushCapture = JACK_FlushCapture;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
impl->HasCaptureSupport = SDL_TRUE;
impl->ProvidesOwnCallbackThread = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap JACK_bootstrap = {
"jack", "JACK Audio Connection Kit", JACK_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_JACK
+35
View File
@@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_
+287
View File
@@ -0,0 +1,287 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 = SDL_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 = SDL_TRUE;
}
}
if (shouldBroadcast) {
CondVar_Broadcast(&device->hidden->cv);
}
contextUnlock(device);
}
static int 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 -1;
}
// 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)) {
SDL_SetError("DSP init failed: dspfirm.cdc missing!");
} else {
SDL_SetError("DSP init failed. Error code: 0x%lX", ndsp_init_res);
}
return -1;
}
// 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 -1;
}
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 0;
}
static int 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 0; // !!! FIXME: is this a fatal error? If so, this should return -1.
}
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 0;
}
static int N3DSAUDIO_WaitDevice(SDL_AudioDevice *device)
{
contextLock(device);
while (!device->hidden->isCancelled && !SDL_AtomicGet(&device->shutdown) &&
device->hidden->waveBuf[device->hidden->nextbuf].status != NDSP_WBUF_FREE) {
CondVar_Wait(&device->hidden->cv, &device->hidden->lock);
}
contextUnlock(device);
return 0;
}
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 SDL_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->OnlyHasDefaultOutputDevice = SDL_TRUE;
// Should be possible, but micInit would fail
impl->HasCaptureSupport = SDL_FALSE;
return SDL_TRUE;
}
AudioBootStrap N3DSAUDIO_bootstrap = {
N3DSAUDIO_DRIVER_NAME,
"SDL N3DS audio driver",
N3DSAUDIO_Init,
0
};
#endif // SDL_AUDIO_DRIVER_N3DS
+40
View File
@@ -0,0 +1,40 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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;
SDL_bool isCancelled;
};
#endif // SDL_n3dsaudio_h
@@ -0,0 +1,328 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_output, SDL_AudioDevice **default_capture)
{
SDL_EnumUnixAudioDevices(SDL_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->iscapture ? &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->iscapture ? "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 int NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device)
{
const SDL_bool iscapture = device->iscapture;
while (!SDL_AtomicGet(&device->shutdown)) {
audio_info_t info;
const int rc = ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info);
if (rc < 0) {
if (errno == EAGAIN) {
continue;
}
// Hmm, not much we can do - abort
fprintf(stderr, "netbsdaudio WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno));
return -1;
}
const size_t remain = (size_t)((iscapture ? info.record.seek : info.play.seek) * SDL_AUDIO_BYTESIZE(device->spec.format));
if (!iscapture && (remain >= device->buffer_size)) {
SDL_Delay(10);
} else if (iscapture && (remain < device->buffer_size)) {
SDL_Delay(10);
} else {
break; // ready to go!
}
}
return 0;
}
static int 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 -1;
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", written);
#endif
return 0;
}
static Uint8 *NETBSDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int NETBSDAUDIO_CaptureFromDevice(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, "Captured %d bytes of audio data\n", br);
#endif
return br;
}
static void NETBSDAUDIO_FlushCapture(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 int NETBSDAUDIO_OpenDevice(SDL_AudioDevice *device)
{
const SDL_bool iscapture = device->iscapture;
int encoding = AUDIO_ENCODING_NONE;
audio_info_t info, hwinfo;
struct audio_prinfo *prinfo = iscapture ? &info.record : &info.play;
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return -1;
}
// Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
const int flags = ((device->iscapture) ? O_RDONLY : O_WRONLY);
device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC);
if (device->hidden->audio_fd < 0) {
return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno));
}
AUDIO_INITINFO(&info);
#ifdef AUDIO_GETFORMAT // Introduced in NetBSD 9.0
if (ioctl(device->hidden->audio_fd, AUDIO_GETFORMAT, &hwinfo) != -1) {
// Use the device's native sample rate so the kernel doesn't have to resample.
device->spec.freq = iscapture ? hwinfo.record.sample_rate : hwinfo.play.sample_rate;
}
#endif
prinfo->sample_rate = 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 (!iscapture) {
// Allocate mixing buffer
device->hidden->mixlen = device->buffer_size;
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->hidden->mixlen);
if (!device->hidden->mixbuf) {
return -1;
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
}
NETBSDAUDIO_Status(device);
return 0; // We're ready to rock and roll. :-)
}
static SDL_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->WaitCaptureDevice = NETBSDAUDIO_WaitDevice;
impl->CaptureFromDevice = NETBSDAUDIO_CaptureFromDevice;
impl->FlushCapture = NETBSDAUDIO_FlushCapture;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap NETBSDAUDIO_bootstrap = {
"netbsd", "NetBSD audio", NETBSDAUDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_NETBSD
@@ -0,0 +1,44 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_
+801
View File
@@ -0,0 +1,801 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 iscapture )
{
LOGI( "openSLES_DetectDevices()" );
if ( iscapture )
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 int 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 1;
error:
OPENSLES_DestroyEngine();
return 0;
}
// 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_PostSemaphore(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 AndroidRequestPermissionBlockingCallback(void *userdata, const char *permission, SDL_bool granted)
{
SDL_AtomicSet((SDL_AtomicInt *) userdata, granted ? 1 : -1);
}
static int 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_AtomicSet(&permission_response, 0);
if (SDL_AndroidRequestPermission("android.permission.RECORD_AUDIO", AndroidRequestPermissionBlockingCallback, &permission_response) == -1) {
return -1;
}
while (SDL_AtomicGet(&permission_response) == 0) {
SDL_Delay(10);
}
if (SDL_AtomicGet(&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 chan %u %s samples %u",
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
// 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 0;
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_PostSemaphore(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 int 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 chan %u %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 0;
failed:
return -1;
}
static int OPENSLES_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return -1;
}
if (device->iscapture) {
LOGI("OPENSLES_OpenDevice() for capture");
return OPENSLES_CreatePCMRecorder(device);
} else {
int ret;
LOGI("OPENSLES_OpenDevice() for playing");
ret = OPENSLES_CreatePCMPlayer(device);
if (ret < 0) {
// Another attempt to open the device with a lower frequency
if (device->spec.freq > 48000) {
OPENSLES_DestroyPCMPlayer(device);
device->spec.freq = 48000;
ret = OPENSLES_CreatePCMPlayer(device);
}
}
if (ret != 0) {
return SDL_SetError("Open device failed!");
}
}
return 0;
}
static int OPENSLES_WaitDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
LOGV("OPENSLES_WaitDevice()");
// Wait for an audio chunk to finish
return SDL_WaitSemaphore(audiodata->playsem);
}
static int 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_PostSemaphore(audiodata->playsem);
}
return 0;
}
/// 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_CaptureFromDevice(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->iscapture) {
LOGI("OPENSLES_CloseDevice() for capture");
OPENSLES_DestroyPCMRecorder(device);
} else {
LOGI("OPENSLES_CloseDevice() for playing");
OPENSLES_DestroyPCMPlayer(device);
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static SDL_bool OPENSLES_Init(SDL_AudioDriverImpl *impl)
{
LOGI("OPENSLES_Init() called");
if (!OPENSLES_CreateEngine()) {
return SDL_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->WaitCaptureDevice = OPENSLES_WaitDevice;
impl->CaptureFromDevice = OPENSLES_CaptureFromDevice;
impl->CloseDevice = OPENSLES_CloseDevice;
impl->Deinitialize = OPENSLES_DestroyEngine;
// and the capabilities
impl->HasCaptureSupport = SDL_TRUE;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
LOGI("OPENSLES_Init() - success");
// this audio target is available.
return SDL_TRUE;
}
AudioBootStrap OPENSLES_bootstrap = {
"openslES", "OpenSL ES audio driver", OPENSLES_Init, SDL_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-2024 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
void OPENSLES_ResumeDevices(void);
void OPENSLES_PauseDevices(void);
#else
static void OPENSLES_ResumeDevices(void) {}
static void OPENSLES_PauseDevices(void) {}
#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-2024 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_
+159
View File
@@ -0,0 +1,159 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 int PS2AUDIO_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return -1;
}
// 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 0;
}
static int 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) ? -1 : 0;
}
static int PS2AUDIO_WaitDevice(SDL_AudioDevice *device)
{
audsrv_wait_audio(device->buffer_size);
return 0;
}
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 SDL_bool PS2AUDIO_Init(SDL_AudioDriverImpl *impl)
{
if (init_audio_driver() < 0) {
return SDL_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->OnlyHasDefaultOutputDevice = SDL_TRUE;
return SDL_TRUE; // this audio target is available.
}
AudioBootStrap PS2AUDIO_bootstrap = {
"ps2", "PS2 audio driver", PS2AUDIO_Init, SDL_FALSE
};
+42
View File
@@ -0,0 +1,42 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_
+183
View File
@@ -0,0 +1,183 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 inline SDL_bool isBasicAudioConfig(const SDL_AudioSpec *spec)
{
return spec->freq == 44100;
}
static int PSPAUDIO_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return -1;
}
// 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 0;
}
static int 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) ? 0 : -1;
}
static int PSPAUDIO_WaitDevice(SDL_AudioDevice *device)
{
return 0; // 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 SDL_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->OnlyHasDefaultOutputDevice = SDL_TRUE;
//impl->HasCaptureSupport = SDL_TRUE;
//impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap PSPAUDIO_bootstrap = {
"psp", "PSP audio driver", PSPAUDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_PSP
+41
View File
@@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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-2024 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 *capturebuf;
int capturelen;
};
#endif // SDL_pulseaudio_h_
+450
View File
@@ -0,0 +1,450 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 int QSA_SetError(const char *fn, int status)
{
return SDL_SetError("QSA: %s() failed: %s", fn, snd_strerror(status));
}
// !!! FIXME: does this need to be here? Does the SDL version not work?
static void QSA_ThreadInit(SDL_AudioDevice *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 int 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->iscapture ? 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 -1;
case 0:
device->hidden->timeout_on_wait = SDL_TRUE; // !!! FIXME: Should we just disconnect the device in this case?
break;
default:
device->hidden->timeout_on_wait = SDL_FALSE;
break;
}
return 0;
}
static int QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
if (SDL_AtomicGet(&device->shutdown) || !device->hidden) {
return 0;
}
int towrite = buflen;
// Write the audio data, checking for EAGAIN (buffer full) and underrun
while ((towrite > 0) && !SDL_AtomicGet(&device->shutdown));
const int bw = snd_pcm_plugin_write(device->hidden->audio_handle, buffer, towrite);
if (bw != towrite) {
// Check if samples playback got stuck somewhere in hardware or in the audio device driver
if ((errno == EAGAIN) && (bw == 0)) {
if (device->hidden->timeout_on_wait) {
return 0; // 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->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
int status = snd_pcm_plugin_status(device->hidden->audio_handle, &cstatus);
if (status < 0) {
QSA_SetError("snd_pcm_plugin_status", status);
return -1;
} else if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY)) {
status = snd_pcm_plugin_prepare(device->hidden->audio_handle, device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
if (status < 0) {
QSA_SetError("snd_pcm_plugin_prepare", status);
return -1;
}
}
continue;
} else {
return -1;
}
} 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) ? -1 : 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 capture
snd_pcm_plugin_flush(device->hidden->audio_handle, device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
#endif
snd_pcm_close(device->hidden->audio_handle);
}
SDL_free(device->hidden->pcm_buf);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static int QSA_OpenDevice(SDL_AudioDevice *device)
{
if (device->iscapture) {
return SDL_SetError("SDL capture support isn't available on QNX atm"); // !!! FIXME: most of this code has support for capture devices, but there's no CaptureFromDevice, etc functions. Fill them in!
}
SDL_assert(device->handle != NULL); // NULL used to mean "system default device" in SDL2; it does not mean that in SDL3.
const Uint32 sdlhandle = (Uint32) ((size_t) device->handle);
const uint32_t cardno = (uint32_t) (sdlhandle & 0xFFFF);
const uint32_t deviceno = (uint32_t) ((sdlhandle >> 16) & 0xFFFF);
const SDL_bool iscapture = device->iscapture;
int status = 0;
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, (sizeof (struct SDL_PrivateAudioData)));
if (device->hidden == NULL) {
return -1;
}
// 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, iscapture ? 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 = iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) {
return SDL_SetError("QSA: Unable to setup channel");
}
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 -1;
}
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 0; // 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_output, SDL_AudioDevice **default_capture)
{
// Detect amount of available devices
// this value can be changed in the runtime
int num_cards = 0;
(void) snd_cards_list(NULL, 0, &alloc_num_cards);
SDL_bool isstack = SDL_FALSE;
int *cards = SDL_small_alloc(int, num_cards, &isstack);
if (!cards) {
return; // we're in trouble.
}
int overflow_cards = 0;
const int total_num_cards = snd_cards_list(cards, num_cards, &overflow_cards);
// if overflow_cards > 0 or total_num_cards > num_cards, it changed at the last moment; oh well, we lost some.
num_cards = SDL_min(num_cards, total_num_cards); // ...but make sure it didn't _shrink_.
// If io-audio manager is not running we will get 0 as number of available audio devices
if (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
SDL_bool iscapture = SDL_FALSE;
status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_PLAYBACK);
if (status != EOK) { // no? See if it's a capture device instead.
#if 0 // !!! FIXME: most of this code has support for capture devices, but there's no CaptureFromDevice, etc functions. Fill them in!
status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_CAPTURE);
if (status == EOK) {
iscapture = SDL_TRUE;
}
#endif
}
if (status == EOK) {
SDL_AudioSpec spec;
SDL_AudioSpec *pspec = &spec;
snd_pcm_channel_setup_t csetup;
SDL_zero(csetup);
csetup.channel = iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) {
pspec = NULL; // go on without spec info.
} else {
spec.format = QnxFormatToSDLFormat(csetup.format.format);
spec.channels = csetup.format.channels;
spec.freq = csetup.format.rate;
}
status = snd_pcm_close(handle);
if (status == EOK) {
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
SDL_assert(card <= 0xFFFF);
SDL_assert(deviceno <= 0xFFFF);
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
SDL_AddAudioDevice(iscapture, fullname, pspec, (void *) ((size_t) sdlhandle));
}
} else {
// 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_output = 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_capture = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
}
}
static void QSA_Deinitialize(void)
{
// nothing to do here atm.
}
static SDL_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 capture devices, but there's no CaptureFromDevice, etc functions. Fill them in!
//impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap QSAAUDIO_bootstrap = {
"qsa", "QNX QSA Audio", QSA_Init, 0
};
#endif // SDL_AUDIO_DRIVER_QNX
+40
View File
@@ -0,0 +1,40 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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
SDL_bool timeout_on_wait; // Select timeout status
Uint8 *pcm_buf; // Raw mixing buffer
};
#endif // __SDL_QSA_AUDIO_H__
+359
View File
@@ -0,0 +1,359 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 void *sndio_handle = NULL;
static int load_sndio_sym(const char *fn, void **addr)
{
*addr = SDL_LoadFunction(sndio_handle, fn);
if (!*addr) {
return 0; // Don't call SDL_SetError(): SDL_LoadFunction already did.
}
return 1;
}
// cast funcs to char* first, to please GCC's strict aliasing rules.
#define SDL_SNDIO_SYM(x) \
if (!load_sndio_sym(#x, (void **)(char *)&SNDIO_##x)) \
return -1
#else
#define SDL_SNDIO_SYM(x) SNDIO_##x = x
#endif
static int 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 0;
}
#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 int LoadSNDIOLibrary(void)
{
int retval = 0;
if (!sndio_handle) {
sndio_handle = SDL_LoadObject(sndio_library);
if (!sndio_handle) {
retval = -1; // Don't call SDL_SetError(): SDL_LoadObject already did.
} else {
retval = load_sndio_syms();
if (retval < 0) {
UnloadSNDIOLibrary();
}
}
}
return retval;
}
#else
static void UnloadSNDIOLibrary(void)
{
}
static int LoadSNDIOLibrary(void)
{
load_sndio_syms();
return 0;
}
#endif // SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
static int SNDIO_WaitDevice(SDL_AudioDevice *device)
{
const SDL_bool iscapture = device->iscapture;
while (!SDL_AtomicGet(&device->shutdown)) {
if (SNDIO_sio_eof(device->hidden->dev)) {
return -1;
}
const int nfds = SNDIO_sio_pollfd(device->hidden->dev, device->hidden->pfd, iscapture ? POLLIN : POLLOUT);
if (nfds <= 0 || poll(device->hidden->pfd, nfds, 10) < 0) {
return -1;
}
const int revents = SNDIO_sio_revents(device->hidden->dev, device->hidden->pfd);
if (iscapture && (revents & POLLIN)) {
break;
} else if (!iscapture && (revents & POLLOUT)) {
break;
} else if (revents & POLLHUP) {
return -1;
}
}
return 0;
}
static int 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 -1; // 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 0;
}
static int SNDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
// We set capture devices non-blocking; this can safely return 0 in SDL3, but we'll check for EOF to cause a device disconnect.
const size_t br = SNDIO_sio_read(device->hidden->dev, buffer, buflen);
if ((br == 0) && SNDIO_sio_eof(device->hidden->dev)) {
return -1;
}
return (int) br;
}
static void SNDIO_FlushCapture(SDL_AudioDevice *device)
{
char buf[512];
while (!SDL_AtomicGet(&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 int SNDIO_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return -1;
}
// !!! FIXME: we really should standardize this on a specific SDL hint.
const char *audiodev = SDL_getenv("AUDIODEV");
// Capture devices must be non-blocking for SNDIO_FlushCapture
device->hidden->dev = SNDIO_sio_open(audiodev ? audiodev : SIO_DEVANY,
device->iscapture ? SIO_REC : SIO_PLAY, device->iscapture);
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 -1;
}
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 -1;
}
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 0; // We're ready to rock and roll. :-)
}
static void SNDIO_Deinitialize(void)
{
UnloadSNDIOLibrary();
}
static void SNDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
*default_output = SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1);
*default_capture = SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2);
}
static SDL_bool SNDIO_Init(SDL_AudioDriverImpl *impl)
{
if (LoadSNDIOLibrary() < 0) {
return SDL_FALSE;
}
impl->OpenDevice = SNDIO_OpenDevice;
impl->WaitDevice = SNDIO_WaitDevice;
impl->PlayDevice = SNDIO_PlayDevice;
impl->GetDeviceBuf = SNDIO_GetDeviceBuf;
impl->CloseDevice = SNDIO_CloseDevice;
impl->WaitCaptureDevice = SNDIO_WaitDevice;
impl->CaptureFromDevice = SNDIO_CaptureFromDevice;
impl->FlushCapture = SNDIO_FlushCapture;
impl->Deinitialize = SNDIO_Deinitialize;
impl->DetectDevices = SNDIO_DetectDevices;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap SNDIO_bootstrap = {
"sndio", "OpenBSD sndio", SNDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_SNDIO
@@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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_
+237
View File
@@ -0,0 +1,237 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 int VITAAUD_OpenCaptureDevice(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 0;
}
static int VITAAUD_OpenDevice(SDL_AudioDevice *device)
{
int format, mixlen, i, port = SCE_AUDIO_OUT_PORT_TYPE_MAIN;
int vols[2] = { SCE_AUDIO_MAX_VOLUME, SCE_AUDIO_MAX_VOLUME };
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts;
device->hidden = (struct SDL_PrivateAudioData *)
SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return -1;
}
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->iscapture) {
return VITAAUD_OpenCaptureDevice(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 0;
}
static int VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
{
return (sceAudioOutOutput(device->hidden->port, buffer) == 0) ? 0 : -1;
}
// This function waits until it is possible to write a full sound buffer
static int 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_AtomicGet(&device->shutdown) && (sceAudioOutGetRestSample(device->hidden->port) >= device->buffer_size)) {
SDL_Delay(1);
}
return 0;
}
static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
Uint8 *retval = device->hidden->mixbufs[device->hidden->next_buffer];
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
return retval;
}
static void VITAAUD_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->port >= 0) {
if (device->iscapture) {
sceAudioInReleasePort(device->hidden->port);
} else {
sceAudioOutReleasePort(device->hidden->port);
}
device->hidden->port = -1;
}
if (!device->iscapture && 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 int VITAAUD_WaitCaptureDevice(SDL_AudioDevice *device)
{
// there's only a blocking call to obtain more data, so we'll just sleep as
// long as a buffer would run.
const Uint64 endticks = SDL_GetTicks() + ((device->sample_frames * 1000) / device->spec.freq);
while (!SDL_AtomicGet(&device->shutdown) && (SDL_GetTicks() < endticks)) {
SDL_Delay(1);
}
return 0;
}
static int VITAAUD_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
int ret;
SDL_assert(buflen == device->buffer_size);
ret = sceAudioInInput(device->hidden->port, buffer);
if (ret < 0) {
return SDL_SetError("Failed to capture from device: %x", ret);
}
return device->buffer_size;
}
static void VITAAUD_FlushCapture(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 SDL_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->WaitCaptureDevice = VITAAUD_WaitCaptureDevice;
impl->FlushCapture = VITAAUD_FlushCapture;
impl->CaptureFromDevice = VITAAUD_CaptureFromDevice;
impl->HasCaptureSupport = SDL_TRUE;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap VITAAUD_bootstrap = {
"vita", "VITA audio driver", VITAAUD_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_VITA
+41
View File
@@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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
+796
View File
@@ -0,0 +1,796 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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
// 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 } };
// 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;
int result;
SDL_Semaphore *task_complete_sem;
char *errorstr;
struct ManagementThreadPendingTask *next;
} ManagementThreadPendingTask;
static SDL_Thread *ManagementThread = NULL;
static ManagementThreadPendingTask *ManagementThreadPendingTasks = NULL;
static SDL_Mutex *ManagementThreadLock = NULL;
static SDL_Condition *ManagementThreadCondition = NULL;
static SDL_AtomicInt ManagementThreadShutdown;
static void ManagementThreadMainloop(void)
{
SDL_LockMutex(ManagementThreadLock);
ManagementThreadPendingTask *task;
while (((task = (ManagementThreadPendingTask *)SDL_AtomicGetPtr((void **)&ManagementThreadPendingTasks)) != NULL) || !SDL_AtomicGet(&ManagementThreadShutdown)) {
if (!task) {
SDL_WaitCondition(ManagementThreadCondition, ManagementThreadLock); // block until there's something to do.
} else {
SDL_AtomicSetPtr((void **) &ManagementThreadPendingTasks, task->next); // take task off the pending list.
SDL_UnlockMutex(ManagementThreadLock); // let other things add to the list while we chew on this task.
task->result = task->fn(task->userdata); // run this task.
if (task->task_complete_sem) { // something waiting on result?
task->errorstr = SDL_strdup(SDL_GetError());
SDL_PostSemaphore(task->task_complete_sem);
} else { // nothing waiting, we're done, free it.
SDL_free(task);
}
SDL_LockMutex(ManagementThreadLock); // regrab the lock so we can get the next task; if nothing to do, we'll release the lock in SDL_WaitCondition.
}
}
SDL_UnlockMutex(ManagementThreadLock); // told to shut down and out of tasks, let go of the lock and return.
}
int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_on_result)
{
// We want to block for a result, but we are already running from the management thread! Just run the task now so we don't deadlock.
if ((wait_on_result) && (SDL_GetCurrentThreadID() == SDL_GetThreadID(ManagementThread))) {
*wait_on_result = task(userdata);
return 0; // completed!
}
if (SDL_AtomicGet(&ManagementThreadShutdown)) {
return SDL_SetError("Can't add task, we're shutting down");
}
ManagementThreadPendingTask *pending = (ManagementThreadPendingTask *)SDL_calloc(1, sizeof(ManagementThreadPendingTask));
if (!pending) {
return -1;
}
pending->fn = task;
pending->userdata = userdata;
if (wait_on_result) {
pending->task_complete_sem = SDL_CreateSemaphore(0);
if (!pending->task_complete_sem) {
SDL_free(pending);
return -1;
}
}
pending->next = NULL;
SDL_LockMutex(ManagementThreadLock);
// add to end of task list.
ManagementThreadPendingTask *prev = NULL;
for (ManagementThreadPendingTask *i = (ManagementThreadPendingTask *)SDL_AtomicGetPtr((void **)&ManagementThreadPendingTasks); i; i = i->next) {
prev = i;
}
if (prev) {
prev->next = pending;
} else {
SDL_AtomicSetPtr((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 0; // successfully added (and possibly executed)!
}
static int ManagementThreadPrepare(void)
{
if (WASAPI_PlatformInit() == -1) {
return -1;
}
ManagementThreadLock = SDL_CreateMutex();
if (!ManagementThreadLock) {
WASAPI_PlatformDeinit();
return -1;
}
ManagementThreadCondition = SDL_CreateCondition();
if (!ManagementThreadCondition) {
SDL_DestroyMutex(ManagementThreadLock);
ManagementThreadLock = NULL;
WASAPI_PlatformDeinit();
return -1;
}
return 0;
}
typedef struct
{
char *errorstr;
SDL_Semaphore *ready_sem;
} ManagementThreadEntryData;
static int ManagementThreadEntry(void *userdata)
{
ManagementThreadEntryData *data = (ManagementThreadEntryData *)userdata;
if (ManagementThreadPrepare() < 0) {
data->errorstr = SDL_strdup(SDL_GetError());
SDL_PostSemaphore(data->ready_sem); // unblock calling thread.
return 0;
}
SDL_PostSemaphore(data->ready_sem); // unblock calling thread.
ManagementThreadMainloop();
WASAPI_PlatformDeinit();
return 0;
}
static int InitManagementThread(void)
{
ManagementThreadEntryData mgmtdata;
SDL_zero(mgmtdata);
mgmtdata.ready_sem = SDL_CreateSemaphore(0);
if (!mgmtdata.ready_sem) {
return -1;
}
SDL_AtomicSetPtr((void **) &ManagementThreadPendingTasks, NULL);
SDL_AtomicSet(&ManagementThreadShutdown, 0);
ManagementThread = SDL_CreateThreadInternal(ManagementThreadEntry, "SDLWASAPIMgmt", 256 * 1024, &mgmtdata); // !!! FIXME: maybe even smaller stack size?
if (!ManagementThread) {
return -1;
}
SDL_WaitSemaphore(mgmtdata.ready_sem);
SDL_DestroySemaphore(mgmtdata.ready_sem);
if (mgmtdata.errorstr) {
SDL_WaitThread(ManagementThread, NULL);
ManagementThread = NULL;
SDL_SetError("%s", mgmtdata.errorstr);
SDL_free(mgmtdata.errorstr);
return -1;
}
return 0;
}
static void DeinitManagementThread(void)
{
if (ManagementThread) {
SDL_AtomicSet(&ManagementThreadShutdown, 1);
SDL_LockMutex(ManagementThreadLock);
SDL_SignalCondition(ManagementThreadCondition);
SDL_UnlockMutex(ManagementThreadLock);
SDL_WaitThread(ManagementThread, NULL);
ManagementThread = NULL;
}
SDL_assert(SDL_AtomicGetPtr((void **) &ManagementThreadPendingTasks) == NULL);
SDL_DestroyCondition(ManagementThreadCondition);
SDL_DestroyMutex(ManagementThreadLock);
ManagementThreadCondition = NULL;
ManagementThreadLock = NULL;
SDL_AtomicSet(&ManagementThreadShutdown, 0);
}
typedef struct
{
SDL_AudioDevice **default_output;
SDL_AudioDevice **default_capture;
} mgmtthrtask_DetectDevicesData;
static int mgmtthrtask_DetectDevices(void *userdata)
{
mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata;
WASAPI_EnumerateEndpoints(data->default_output, data->default_capture);
return 0;
}
static void WASAPI_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
int rc;
// this blocks because it needs to finish before the audio subsystem inits
mgmtthrtask_DetectDevicesData data;
data.default_output = default_output;
data.default_capture = default_capture;
WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc);
}
static int mgmtthrtask_DisconnectDevice(void *userdata)
{
SDL_AudioDeviceDisconnected((SDL_AudioDevice *)userdata);
return 0;
}
void WASAPI_DisconnectDevice(SDL_AudioDevice *device)
{
int rc; // block on this; don't disconnect while holding the device lock!
WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, &rc);
}
static SDL_bool WasapiFailed(SDL_AudioDevice *device, const HRESULT err)
{
if (err == S_OK) {
return SDL_FALSE;
} else if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
device->hidden->device_lost = SDL_TRUE;
} else {
device->hidden->device_dead = SDL_TRUE;
}
return SDL_TRUE;
}
static int mgmtthrtask_StopAndReleaseClient(void *userdata)
{
IAudioClient *client = (IAudioClient *) userdata;
IAudioClient_Stop(client);
IAudioClient_Release(client);
return 0;
}
static int mgmtthrtask_ReleaseCaptureClient(void *userdata)
{
IAudioCaptureClient_Release((IAudioCaptureClient *)userdata);
return 0;
}
static int mgmtthrtask_ReleaseRenderClient(void *userdata)
{
IAudioRenderClient_Release((IAudioRenderClient *)userdata);
return 0;
}
static int mgmtthrtask_CoTaskMemFree(void *userdata)
{
CoTaskMemFree(userdata);
return 0;
}
static int mgmtthrtask_PlatformDeleteActivationHandler(void *userdata)
{
WASAPI_PlatformDeleteActivationHandler(userdata);
return 0;
}
static int mgmtthrtask_CloseHandle(void *userdata)
{
CloseHandle((HANDLE) userdata);
return 0;
}
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->activation_handler) {
void *activation_handler = device->hidden->activation_handler;
device->hidden->activation_handler = NULL;
WASAPI_ProxyToManagementThread(mgmtthrtask_PlatformDeleteActivationHandler, activation_handler, NULL);
}
if (device->hidden->event) {
HANDLE event = device->hidden->event;
device->hidden->event = NULL;
WASAPI_ProxyToManagementThread(mgmtthrtask_CloseHandle, (void *) event, NULL);
}
}
static int mgmtthrtask_ActivateDevice(void *userdata)
{
return WASAPI_ActivateDevice((SDL_AudioDevice *)userdata);
}
static int 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.
int rc = -1;
return ((WASAPI_ProxyToManagementThread(mgmtthrtask_ActivateDevice, device, &rc) < 0) || (rc < 0)) ? -1 : 0;
}
// do not call when holding the device lock!
static SDL_bool RecoverWasapiDevice(SDL_AudioDevice *device)
{
ResetWasapiDevice(device); // dump the lost device's handles.
// This handles a non-default device that simply had its format changed in the Windows Control Panel.
if (ActivateWasapiDevice(device) == -1) {
WASAPI_DisconnectDevice(device);
return SDL_FALSE;
}
device->hidden->device_lost = SDL_FALSE;
return SDL_TRUE; // okay, carry on with new device details!
}
// do not call when holding the device lock!
static SDL_bool RecoverWasapiIfLost(SDL_AudioDevice *device)
{
if (SDL_AtomicGet(&device->shutdown)) {
return SDL_FALSE; // already failed.
} 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_AtomicGet(&device->shutdown)); // so we don't come back through here.
return SDL_FALSE; // already failed.
} else if (SDL_AtomicGet(&device->zombie)) {
return SDL_FALSE; // we're already dead, so just leave and let the Zombie implementations take over.
} else if (!device->hidden->client) {
return SDL_TRUE; // still waiting for activation.
}
return device->hidden->device_lost ? RecoverWasapiDevice(device) : SDL_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 int WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
if (device->hidden->render) { // definitely activated?
// WasapiFailed() will mark the device for reacquisition or removal elsewhere.
WasapiFailed(device, IAudioRenderClient_ReleaseBuffer(device->hidden->render, device->sample_frames, 0));
}
return 0;
}
static int 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) {
DWORD waitResult = WaitForSingleObjectEx(device->hidden->event, 200, FALSE);
if (waitResult == WAIT_OBJECT_0) {
const UINT32 maxpadding = device->sample_frames;
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 (device->iscapture && (padding > 0)) {
break;
} else if (!device->iscapture && (padding <= maxpadding)) {
break;
}
}
} else if (waitResult != WAIT_TIMEOUT) {
//SDL_Log("WASAPI FAILED EVENT!");*/
IAudioClient_Stop(device->hidden->client);
return -1;
}
}
return 0;
}
static int WASAPI_CaptureFromDevice(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 SDL_bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? SDL_TRUE : SDL_FALSE;
SDL_assert(leftover == 0); // according to MSDN, this isn't everything available, just one "packet" of data per-GetBuffer call.
if (silent) {
SDL_memset(buffer, device->silence_value, cpy);
} else {
SDL_memcpy(buffer, ptr, cpy);
}
WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames));
return cpy;
}
}
return -1; // unrecoverable error.
}
static void WASAPI_FlushCapture(SDL_AudioDevice *device)
{
BYTE *ptr = NULL;
UINT32 frames = 0;
DWORD flags = 0;
// just read until we stop getting packets, throwing them away.
while (!SDL_AtomicGet(&device->shutdown) && device->hidden->capture) {
const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
if (ret == AUDCLNT_S_BUFFER_EMPTY) {
break; // no more buffered data; we're done.
} else if (WasapiFailed(device, ret)) {
break; // failed for some other reason, abort.
} else if (WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames))) {
break; // something broke.
}
}
}
static void WASAPI_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
ResetWasapiDevice(device);
SDL_free(device->hidden->devid);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static int mgmtthrtask_PrepDevice(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *)userdata;
/* !!! FIXME: we could request an exclusive mode stream, which is lower latency;
!!! it will write into the kernel's audio buffer directly instead of
!!! shared memory that a user-mode mixer then writes to the kernel with
!!! 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);
#if defined(SDL_PLATFORM_WINRT) || defined(SDL_PLATFORM_GDK) // CreateEventEx() arrived in Vista, so we need an #ifdef for XP.
device->hidden->event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
#else
device->hidden->event = CreateEventW(NULL, 0, 0, NULL);
#endif
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;
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
const float period_millis = default_period / 10000.0f;
const float period_frames = period_millis * newspec.freq / 1000.0f;
int 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) < 0) {
return -1;
}
device->hidden->framesize = SDL_AUDIO_FRAMESIZE(device->spec);
if (device->iscapture) {
IAudioCaptureClient *capture = NULL;
ret = IAudioClient_GetService(client, &SDL_IID_IAudioCaptureClient, (void **)&capture);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't get capture client service", ret);
}
SDL_assert(capture != NULL);
device->hidden->capture = capture;
ret = IAudioClient_Start(client);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't start capture", ret);
}
WASAPI_FlushCapture(device); // MSDN says you should flush capture endpoint right after startup.
} else {
IAudioRenderClient *render = NULL;
ret = IAudioClient_GetService(client, &SDL_IID_IAudioRenderClient, (void **)&render);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't get render client service", ret);
}
SDL_assert(render != NULL);
device->hidden->render = render;
ret = IAudioClient_Start(client);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't start playback", ret);
}
}
return 0; // good to go.
}
// This is called once a device is activated, possibly asynchronously.
int WASAPI_PrepDevice(SDL_AudioDevice *device)
{
int rc = 0;
return (WASAPI_ProxyToManagementThread(mgmtthrtask_PrepDevice, device, &rc) < 0) ? -1 : rc;
}
static int WASAPI_OpenDevice(SDL_AudioDevice *device)
{
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return -1;
} else if (ActivateWasapiDevice(device) < 0) {
return -1; // already set error.
}
/* Ready, but possibly waiting for async device activation.
Until activation is successful, we will report silence from capture
devices and ignore data on playback devices. Upon activation, we'll make
sure any bound audio streams are adjusted for the final device format. */
return 0;
}
static void WASAPI_ThreadInit(SDL_AudioDevice *device)
{
WASAPI_PlatformThreadInit(device);
}
static void WASAPI_ThreadDeinit(SDL_AudioDevice *device)
{
WASAPI_PlatformThreadDeinit(device);
}
static int mgmtthrtask_FreeDeviceHandle(void *userdata)
{
WASAPI_PlatformFreeDeviceHandle((SDL_AudioDevice *)userdata);
return 0;
}
static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device)
{
int rc;
WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc);
}
static int mgmtthrtask_DeinitializeStart(void *userdata)
{
WASAPI_PlatformDeinitializeStart();
return 0;
}
static void WASAPI_DeinitializeStart(void)
{
int rc;
WASAPI_ProxyToManagementThread(mgmtthrtask_DeinitializeStart, NULL, &rc);
}
static void WASAPI_Deinitialize(void)
{
DeinitManagementThread();
}
static SDL_bool WASAPI_Init(SDL_AudioDriverImpl *impl)
{
if (InitManagementThread() < 0) {
return SDL_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->WaitCaptureDevice = WASAPI_WaitDevice;
impl->CaptureFromDevice = WASAPI_CaptureFromDevice;
impl->FlushCapture = WASAPI_FlushCapture;
impl->CloseDevice = WASAPI_CloseDevice;
impl->DeinitializeStart = WASAPI_DeinitializeStart;
impl->Deinitialize = WASAPI_Deinitialize;
impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap WASAPI_bootstrap = {
"wasapi", "WASAPI", WASAPI_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_WASAPI
+73
View File
@@ -0,0 +1,73 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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;
SDL_bool coinitialized;
int framesize;
SDL_bool device_lost;
SDL_bool device_dead;
void *activation_handler;
};
// win32 and winrt implementations call into these.
int 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 int (*ManagementThreadTask)(void *userdata);
int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_until_complete);
// These are functions that are implemented differently for Windows vs WinRT.
// UNLESS OTHERWISE NOTED THESE ALL HAPPEN ON THE MANAGEMENT THREAD.
int WASAPI_PlatformInit(void);
void WASAPI_PlatformDeinit(void);
void WASAPI_PlatformDeinitializeStart(void);
void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture);
int WASAPI_ActivateDevice(SDL_AudioDevice *device);
void WASAPI_PlatformThreadInit(SDL_AudioDevice *device); // this happens on the audio device thread, not the management thread.
void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device); // this happens on the audio device thread, not the management thread.
void WASAPI_PlatformDeleteActivationHandler(void *handler);
void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device);
#ifdef __cplusplus
}
#endif
#endif // SDL_wasapi_h_
@@ -0,0 +1,205 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 code that Windows uses to talk to WASAPI-related system APIs.
This is for non-WinRT desktop apps. The C++/CX implementation of these
functions, exclusive to WinRT, are in SDL_wasapi_winrt.cpp.
The code in SDL_wasapi.c is used by both standard Windows and WinRT builds
to deal with audio and calls into these functions. */
#if defined(SDL_AUDIO_DRIVER_WASAPI) && !defined(SDL_PLATFORM_WINRT)
#include "../../core/windows/SDL_windows.h"
#include "../../core/windows/SDL_immdevice.h"
#include "../SDL_sysaudio.h"
#include <audioclient.h>
#include "SDL_wasapi.h"
// 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;
static SDL_bool immdevice_initialized = SDL_FALSE;
// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
static int mgmtthrtask_AudioDeviceDisconnected(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
SDL_AudioDeviceDisconnected(device);
UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
return 0;
}
static void WASAPI_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 int mgmtthrtask_DefaultAudioDeviceChanged(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
SDL_DefaultAudioDeviceChanged(device);
UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
return 0;
}
static void WASAPI_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);
}
}
int WASAPI_PlatformInit(void)
{
const SDL_IMMDevice_callbacks callbacks = { WASAPI_AudioDeviceDisconnected, WASAPI_DefaultAudioDeviceChanged };
if (FAILED(WIN_CoInitialize())) {
return SDL_SetError("CoInitialize() failed");
} else if (SDL_IMMDevice_Init(&callbacks) < 0) {
return -1; // Error string is set by SDL_IMMDevice_Init
}
immdevice_initialized = SDL_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");
}
return 0;
}
static void StopWasapiHotplug(void)
{
if (immdevice_initialized) {
SDL_IMMDevice_Quit();
immdevice_initialized = SDL_FALSE;
}
}
void WASAPI_PlatformDeinit(void)
{
if (libavrt) {
FreeLibrary(libavrt);
libavrt = NULL;
}
pAvSetMmThreadCharacteristicsW = NULL;
pAvRevertMmThreadCharacteristics = NULL;
StopWasapiHotplug();
WIN_CoUninitialize();
}
void WASAPI_PlatformDeinitializeStart(void)
{
StopWasapiHotplug();
}
void WASAPI_PlatformThreadInit(SDL_AudioDevice *device)
{
// this thread uses COM.
if (SUCCEEDED(WIN_CoInitialize())) { // can't report errors, hope it worked!
device->hidden->coinitialized = SDL_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_SetThreadPriority(device->iscapture ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
}
}
void WASAPI_PlatformThreadDeinit(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 = SDL_FALSE;
}
}
int WASAPI_ActivateDevice(SDL_AudioDevice *device)
{
IMMDevice *immdevice = NULL;
if (SDL_IMMDevice_Get(device, &immdevice, device->iscapture) < 0) {
device->hidden->client = NULL;
return -1; // This is already set by SDL_IMMDevice_Get
}
// this is _not_ async in standard win32, yay!
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) == -1) { // not async, fire it right away.
return -1;
}
return 0; // good to go.
}
void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
SDL_IMMDevice_EnumerateEndpoints(default_output, default_capture);
}
void WASAPI_PlatformDeleteActivationHandler(void *handler)
{
// not asynchronous.
SDL_assert(!"This function should have only been called on WinRT.");
}
void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device)
{
SDL_IMMDevice_FreeDeviceHandle(device);
}
#endif // SDL_AUDIO_DRIVER_WASAPI && !defined(SDL_PLATFORM_WINRT)
@@ -0,0 +1,360 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 C++/CX code that the WinRT port uses to talk to WASAPI-related
// system APIs. The C implementation of these functions, for non-WinRT apps,
// is in SDL_wasapi_win32.c. The code in SDL_wasapi.c is used by both standard
// Windows and WinRT builds to deal with audio and calls into these functions.
#if defined(SDL_AUDIO_DRIVER_WASAPI) && defined(SDL_PLATFORM_WINRT)
#include <Windows.h>
#include <windows.ui.core.h>
#include <windows.devices.enumeration.h>
#include <windows.media.devices.h>
#include <wrl/implements.h>
#include <collection.h>
extern "C" {
#include "../../core/windows/SDL_windows.h"
#include "../SDL_sysaudio.h"
}
#define COBJMACROS
#include <mmdeviceapi.h>
#include <audioclient.h>
#include "SDL_wasapi.h"
using namespace Windows::Devices::Enumeration;
using namespace Windows::Media::Devices;
using namespace Windows::Foundation;
using namespace Microsoft::WRL;
static Platform::String ^ SDL_PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0";
static SDL_bool FindWinRTAudioDeviceCallback(SDL_AudioDevice *device, void *userdata)
{
return (SDL_wcscmp((LPCWSTR) device->handle, (LPCWSTR) userdata) == 0);
}
static SDL_AudioDevice *FindWinRTAudioDevice(LPCWSTR devid)
{
return SDL_FindPhysicalAudioDeviceByCallback(FindWinRTAudioDeviceCallback, (void *) devid);
}
class SDL_WasapiDeviceEventHandler
{
public:
SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture);
~SDL_WasapiDeviceEventHandler();
void OnDeviceAdded(DeviceWatcher ^ sender, DeviceInformation ^ args);
void OnDeviceRemoved(DeviceWatcher ^ sender, DeviceInformationUpdate ^ args);
void OnDeviceUpdated(DeviceWatcher ^ sender, DeviceInformationUpdate ^ args);
void OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args);
void OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args);
void OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args);
void WaitForCompletion();
private:
SDL_Semaphore *completed_semaphore;
const SDL_bool iscapture;
DeviceWatcher ^ watcher;
Windows::Foundation::EventRegistrationToken added_handler;
Windows::Foundation::EventRegistrationToken removed_handler;
Windows::Foundation::EventRegistrationToken updated_handler;
Windows::Foundation::EventRegistrationToken completed_handler;
Windows::Foundation::EventRegistrationToken default_changed_handler;
};
SDL_WasapiDeviceEventHandler::SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture)
: iscapture(_iscapture), completed_semaphore(SDL_CreateSemaphore(0))
{
if (!completed_semaphore) {
return; // uhoh.
}
Platform::String ^ selector = _iscapture ? MediaDevice::GetAudioCaptureSelector() : MediaDevice::GetAudioRenderSelector();
Platform::Collections::Vector<Platform::String ^> properties;
properties.Append(SDL_PKEY_AudioEngine_DeviceFormat);
watcher = DeviceInformation::CreateWatcher(selector, properties.GetView());
if (!watcher)
return; // uhoh.
// !!! FIXME: this doesn't need a lambda here, I think, if I make SDL_WasapiDeviceEventHandler a proper C++/CX class. --ryan.
added_handler = watcher->Added += ref new TypedEventHandler<DeviceWatcher ^, DeviceInformation ^>([this](DeviceWatcher ^ sender, DeviceInformation ^ args) { OnDeviceAdded(sender, args); });
removed_handler = watcher->Removed += ref new TypedEventHandler<DeviceWatcher ^, DeviceInformationUpdate ^>([this](DeviceWatcher ^ sender, DeviceInformationUpdate ^ args) { OnDeviceRemoved(sender, args); });
updated_handler = watcher->Updated += ref new TypedEventHandler<DeviceWatcher ^, DeviceInformationUpdate ^>([this](DeviceWatcher ^ sender, DeviceInformationUpdate ^ args) { OnDeviceUpdated(sender, args); });
completed_handler = watcher->EnumerationCompleted += ref new TypedEventHandler<DeviceWatcher ^, Platform::Object ^>([this](DeviceWatcher ^ sender, Platform::Object ^ args) { OnEnumerationCompleted(sender, args); });
if (iscapture) {
default_changed_handler = MediaDevice::DefaultAudioCaptureDeviceChanged += ref new TypedEventHandler<Platform::Object ^, DefaultAudioCaptureDeviceChangedEventArgs ^>([this](Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args) { OnDefaultCaptureDeviceChanged(sender, args); });
} else {
default_changed_handler = MediaDevice::DefaultAudioRenderDeviceChanged += ref new TypedEventHandler<Platform::Object ^, DefaultAudioRenderDeviceChangedEventArgs ^>([this](Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args) { OnDefaultRenderDeviceChanged(sender, args); });
}
watcher->Start();
}
SDL_WasapiDeviceEventHandler::~SDL_WasapiDeviceEventHandler()
{
if (watcher) {
watcher->Added -= added_handler;
watcher->Removed -= removed_handler;
watcher->Updated -= updated_handler;
watcher->EnumerationCompleted -= completed_handler;
watcher->Stop();
watcher = nullptr;
}
if (completed_semaphore) {
SDL_DestroySemaphore(completed_semaphore);
completed_semaphore = nullptr;
}
if (iscapture) {
MediaDevice::DefaultAudioCaptureDeviceChanged -= default_changed_handler;
} else {
MediaDevice::DefaultAudioRenderDeviceChanged -= default_changed_handler;
}
}
void SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher ^ sender, DeviceInformation ^ info)
{
/* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever).
In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for
phones and tablets, where you might have an internal speaker and a headphone jack and expect both to be
available and switch automatically. (!!! FIXME...?) */
SDL_assert(sender == this->watcher);
char *utf8dev = WIN_StringToUTF8(info->Name->Data());
if (utf8dev) {
SDL_AudioSpec spec;
SDL_zero(spec);
Platform::Object ^ obj = info->Properties->Lookup(SDL_PKEY_AudioEngine_DeviceFormat);
if (obj) {
IPropertyValue ^ property = (IPropertyValue ^) obj;
Platform::Array<unsigned char> ^ data;
property->GetUInt8Array(&data);
WAVEFORMATEXTENSIBLE fmt;
SDL_zero(fmt);
SDL_memcpy(&fmt, data->Data, SDL_min(data->Length, sizeof(WAVEFORMATEXTENSIBLE)));
spec.channels = (Uint8)fmt.Format.nChannels;
spec.freq = fmt.Format.nSamplesPerSec;
spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)&fmt);
}
LPWSTR devid = SDL_wcsdup(info->Id->Data());
if (devid) {
SDL_AddAudioDevice(this->iscapture, utf8dev, spec.channels ? &spec : NULL, devid);
}
SDL_free(utf8dev);
}
}
void SDL_WasapiDeviceEventHandler::OnDeviceRemoved(DeviceWatcher ^ sender, DeviceInformationUpdate ^ info)
{
SDL_assert(sender == this->watcher);
WASAPI_DisconnectDevice(FindWinRTAudioDevice(info->Id->Data()));
}
void SDL_WasapiDeviceEventHandler::OnDeviceUpdated(DeviceWatcher ^ sender, DeviceInformationUpdate ^ args)
{
SDL_assert(sender == this->watcher);
}
void SDL_WasapiDeviceEventHandler::OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args)
{
SDL_assert(sender == this->watcher);
if (this->completed_semaphore) {
SDL_PostSemaphore(this->completed_semaphore);
}
}
void SDL_WasapiDeviceEventHandler::OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args)
{
SDL_assert(!this->iscapture);
SDL_DefaultAudioDeviceChanged(FindWinRTAudioDevice(args->Id->Data()));
}
void SDL_WasapiDeviceEventHandler::OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args)
{
SDL_assert(this->iscapture);
SDL_DefaultAudioDeviceChanged(FindWinRTAudioDevice(args->Id->Data()));
}
void SDL_WasapiDeviceEventHandler::WaitForCompletion()
{
if (this->completed_semaphore) {
SDL_WaitSemaphore(this->completed_semaphore);
SDL_DestroySemaphore(this->completed_semaphore);
this->completed_semaphore = nullptr;
}
}
static SDL_WasapiDeviceEventHandler *playback_device_event_handler;
static SDL_WasapiDeviceEventHandler *capture_device_event_handler;
int WASAPI_PlatformInit(void)
{
return 0;
}
static void StopWasapiHotplug(void)
{
delete playback_device_event_handler;
playback_device_event_handler = nullptr;
delete capture_device_event_handler;
capture_device_event_handler = nullptr;
}
void WASAPI_PlatformDeinit(void)
{
StopWasapiHotplug();
}
void WASAPI_PlatformDeinitializeStart(void)
{
StopWasapiHotplug();
}
void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
Platform::String ^ defdevid;
// DeviceWatchers will fire an Added event for each existing device at
// startup, so we don't need to enumerate them separately before
// listening for updates.
playback_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_FALSE);
playback_device_event_handler->WaitForCompletion();
defdevid = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
if (defdevid) {
*default_output = FindWinRTAudioDevice(defdevid->Data());
}
capture_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_TRUE);
capture_device_event_handler->WaitForCompletion();
defdevid = MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default);
if (defdevid) {
*default_capture = FindWinRTAudioDevice(defdevid->Data());
}
}
class SDL_WasapiActivationHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>, FtmBase, IActivateAudioInterfaceCompletionHandler>
{
public:
SDL_WasapiActivationHandler() : completion_semaphore(SDL_CreateSemaphore(0)) { SDL_assert(completion_semaphore != NULL); }
STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation *operation);
void WaitForCompletion();
private:
SDL_Semaphore *completion_semaphore;
};
void SDL_WasapiActivationHandler::WaitForCompletion()
{
if (completion_semaphore) {
SDL_WaitSemaphore(completion_semaphore);
SDL_DestroySemaphore(completion_semaphore);
completion_semaphore = NULL;
}
}
HRESULT
SDL_WasapiActivationHandler::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *async)
{
// Just set a flag, since we're probably in a different thread. We'll pick it up and init everything on our own thread to prevent races.
SDL_PostSemaphore(completion_semaphore);
return S_OK;
}
void WASAPI_PlatformDeleteActivationHandler(void *handler)
{
((SDL_WasapiActivationHandler *)handler)->Release();
}
int WASAPI_ActivateDevice(SDL_AudioDevice *device)
{
LPCWSTR devid = (LPCWSTR) device->handle;
SDL_assert(devid != NULL);
ComPtr<SDL_WasapiActivationHandler> handler = Make<SDL_WasapiActivationHandler>();
if (handler == nullptr) {
return SDL_SetError("Failed to allocate WASAPI activation handler");
}
handler.Get()->AddRef(); // we hold a reference after ComPtr destructs on return, causing a Release, and Release ourselves in WASAPI_PlatformDeleteActivationHandler(), etc.
device->hidden->activation_handler = handler.Get();
IActivateAudioInterfaceAsyncOperation *async = nullptr;
const HRESULT ret = ActivateAudioInterfaceAsync(devid, __uuidof(IAudioClient), nullptr, handler.Get(), &async);
if (FAILED(ret) || async == nullptr) {
if (async != nullptr) {
async->Release();
}
handler.Get()->Release();
return WIN_SetErrorFromHRESULT("WASAPI can't activate requested audio endpoint", ret);
}
// !!! FIXME: the problems in SDL2 that needed this to be synchronous are _probably_ solved by SDL3, and this can block indefinitely if a user prompt is shown to get permission to use a microphone.
handler.Get()->WaitForCompletion(); // block here until we have an answer, so this is synchronous to us after all.
HRESULT activateRes = S_OK;
IUnknown *iunknown = nullptr;
const HRESULT getActivateRes = async->GetActivateResult(&activateRes, &iunknown);
async->Release();
if (FAILED(getActivateRes)) {
return WIN_SetErrorFromHRESULT("Failed to get WASAPI activate result", getActivateRes);
} else if (FAILED(activateRes)) {
return WIN_SetErrorFromHRESULT("Failed to activate WASAPI device", activateRes);
}
iunknown->QueryInterface(IID_PPV_ARGS(&device->hidden->client));
if (!device->hidden->client) {
return SDL_SetError("Failed to query WASAPI client interface");
}
if (WASAPI_PrepDevice(device) == -1) {
return -1;
}
return 0;
}
void WASAPI_PlatformThreadInit(SDL_AudioDevice *device)
{
// !!! FIXME: set this thread to "Pro Audio" priority.
SDL_SetThreadPriority(device->iscapture ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
}
void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device)
{
// !!! FIXME: set this thread to "Pro Audio" priority.
}
void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device)
{
SDL_free(device->handle);
}
#endif // SDL_AUDIO_DRIVER_WASAPI && defined(SDL_PLATFORM_WINRT)
File diff suppressed because it is too large Load Diff
+35
View File
@@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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
int SDL_CameraInit(const char *driver_name);
// Shutdown the camera subsystem
void SDL_QuitCamera(void);
// "Pump" the event queue.
extern void SDL_UpdateCamera(void);
#endif // SDL_camera_c_h_
+213
View File
@@ -0,0 +1,213 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 "../SDL_hashtable.h"
#define DEBUG_CAMERA 0
typedef struct SDL_CameraDevice SDL_CameraDevice;
/* 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_CameraDevice *SDL_AddCameraDevice(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_CameraDeviceDisconnected(SDL_CameraDevice *device);
// Find an SDL_CameraDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
extern SDL_CameraDevice *SDL_FindPhysicalCameraDeviceByCallback(SDL_bool (*callback)(SDL_CameraDevice *device, void *userdata), void *userdata);
// Backends should call this when the user has approved/denied access to a camera.
extern void SDL_CameraDevicePermissionOutcome(SDL_CameraDevice *device, SDL_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_CameraDevice *device, char *buf, size_t buflen);
// Backends can call these to change a device's refcount.
extern void RefPhysicalCameraDevice(SDL_CameraDevice *device);
extern void UnrefPhysicalCameraDevice(SDL_CameraDevice *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_CameraDevice *device);
extern SDL_bool SDL_CameraThreadIterate(SDL_CameraDevice *device);
extern void SDL_CameraThreadShutdown(SDL_CameraDevice *device);
// common utility functionality to gather up camera specs. Not required!
typedef struct CameraFormatAddData
{
SDL_CameraSpec *specs;
int num_specs;
int allocated_specs;
} CameraFormatAddData;
int SDL_AddCameraFormat(CameraFormatAddData *data, SDL_PixelFormatEnum fmt, int w, int h, int interval_numerator, int interval_denominator);
typedef struct SurfaceList
{
SDL_Surface *surface;
Uint64 timestampNS;
struct SurfaceList *next;
} SurfaceList;
// Define the SDL camera driver structure
struct SDL_CameraDevice
{
// 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.
int (*WaitDevice)(SDL_CameraDevice *device);
int (*AcquireFrame)(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS);
void (*ReleaseFrame)(SDL_CameraDevice *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_CameraDeviceID 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
// SDL_TRUE if acquire_surface needs to be converted for final output.
SDL_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);
int (*OpenDevice)(SDL_CameraDevice *device, const SDL_CameraSpec *spec);
void (*CloseDevice)(SDL_CameraDevice *device);
int (*WaitDevice)(SDL_CameraDevice *device);
int (*AcquireFrame)(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS); // set frame->pixels, frame->pitch, and *timestampNS!
void (*ReleaseFrame)(SDL_CameraDevice *device, SDL_Surface *frame); // Reclaim frame->pixels and frame->pitch!
void (*FreeDeviceHandle)(SDL_CameraDevice *device); // SDL is done with this device; free the handle from SDL_AddCameraDevice()
void (*Deinitialize)(void);
SDL_bool ProvidesOwnCallbackThread;
} SDL_CameraDriverImpl;
typedef struct SDL_PendingCameraDeviceEvent
{
Uint32 type;
SDL_CameraDeviceID devid;
struct SDL_PendingCameraDeviceEvent *next;
} SDL_PendingCameraDeviceEvent;
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`
SDL_HashTable *device_hash; // the collection of currently-available camera devices
SDL_PendingCameraDeviceEvent pending_events;
SDL_PendingCameraDeviceEvent *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;
SDL_bool (*init)(SDL_CameraDriverImpl *impl);
SDL_bool demand_only; // if SDL_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 V4L2_bootstrap;
extern CameraBootStrap COREMEDIA_bootstrap;
extern CameraBootStrap ANDROIDCAMERA_bootstrap;
extern CameraBootStrap EMSCRIPTENCAMERA_bootstrap;
extern CameraBootStrap MEDIAFOUNDATION_bootstrap;
#endif // SDL_syscamera_h_
@@ -0,0 +1,891 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 "../../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 int 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 int 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 "insuffient 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 int SetMediaError(const char *what, const media_status_t rc)
{
return SetErrorStr(what, MediaStatusStr(rc), (int) rc);
}
static ACameraManager *cameraMgr = NULL;
static int CreateCameraManager(void)
{
SDL_assert(cameraMgr == NULL);
cameraMgr = pACameraManager_create();
if (!cameraMgr) {
return SDL_SetError("Error creating ACameraManager");
}
return 0;
}
static void DestroyCameraManager(void)
{
if (cameraMgr) {
pACameraManager_delete(cameraMgr);
cameraMgr = NULL;
}
}
static Uint32 format_android_to_sdl(Uint32 fmt)
{
switch (fmt) {
#define CASE(x, y) case x: return y
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);
//CASE(AIMAGE_FORMAT_RGBA_FP16, SDL_PIXELFORMAT_UNKNOWN); // 64bits
//CASE(AIMAGE_FORMAT_RAW_PRIVATE, SDL_PIXELFORMAT_UNKNOWN);
//CASE(AIMAGE_FORMAT_JPEG, SDL_PIXELFORMAT_UNKNOWN);
#undef CASE
default: break;
}
#if DEBUG_CAMERA
//SDL_Log("Unknown format AIMAGE_FORMAT '%d'", fmt);
#endif
return SDL_PIXELFORMAT_UNKNOWN;
}
static Uint32 format_sdl_to_android(Uint32 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 int ANDROIDCAMERA_WaitDevice(SDL_CameraDevice *device)
{
return 0; // this isn't used atm, since we run our own thread via onImageAvailable callbacks.
}
static int ANDROIDCAMERA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
{
int retval = 1;
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) {
return SetMediaError("Error AImageReader_acquireNextImage", res);
}
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.
}
// !!! FIXME: we have an open issue in SDL3 to allow SDL_Surface to support non-contiguous planar data, but we don't have it yet.
size_t buflen = 0;
for (int i = 0; (i < num_planes) && (i < 3); i++) {
uint8_t *data = NULL;
int32_t datalen = 0;
pAImage_getPlaneData(image, i, &data, &datalen);
buflen += (int) datalen;
}
frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
if (frame->pixels == NULL) {
retval = -1;
} else {
int32_t row_stride = 0;
Uint8 *dst = frame->pixels;
pAImage_getPlaneRowStride(image, 0, &row_stride);
frame->pitch = (int) row_stride; // this is what SDL3 currently expects, probably incorrectly.
for (int i = 0; (i < num_planes) && (i < 3); i++) {
uint8_t *data = NULL;
int32_t datalen = 0;
pAImage_getPlaneData(image, i, &data, &datalen);
const void *src = data;
SDL_memcpy(dst, src, datalen);
dst += datalen;
}
}
pAImage_delete(image);
return retval;
}
static void ANDROIDCAMERA_ReleaseFrame(SDL_CameraDevice *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_CameraDevice *device = (SDL_CameraDevice *) context;
SDL_CameraThreadIterate(device);
}
static void onDisconnected(void *context, ACameraDevice *device)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onDisconnected");
#endif
SDL_CameraDeviceDisconnected((SDL_CameraDevice *) context);
}
static void onError(void *context, ACameraDevice *device, int error)
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onError");
#endif
SDL_CameraDeviceDisconnected((SDL_CameraDevice *) context);
}
static void onClosed(void* context, ACameraCaptureSession *session)
{
// SDL_CameraDevice *_this = (SDL_CameraDevice *) context;
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onClosed");
#endif
}
static void onReady(void* context, ACameraCaptureSession *session)
{
// SDL_CameraDevice *_this = (SDL_CameraDevice *) context;
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onReady");
#endif
}
static void onActive(void* context, ACameraCaptureSession *session)
{
// SDL_CameraDevice *_this = (SDL_CameraDevice *) context;
#if DEBUG_CAMERA
SDL_Log("CAMERA: CB onActive");
#endif
}
static void ANDROIDCAMERA_CloseDevice(SDL_CameraDevice *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 int PrepareCamera(SDL_CameraDevice *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_OpenCameraDevice 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 0;
}
static void SDLCALL CameraPermissionCallback(void *userdata, const char *permission, SDL_bool granted)
{
SDL_CameraDevice *device = (SDL_CameraDevice *) userdata;
if (device->hidden != NULL) { // if device was already closed, don't send an event.
if (!granted) {
SDL_CameraDevicePermissionOutcome(device, SDL_FALSE); // sorry, permission denied.
} else if (PrepareCamera(device) < 0) { // 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_CameraDeviceDisconnected(device);
} else {
// okay! We have permission to use the camera _and_ opening the hardware worked out, report that the camera is usable!
SDL_CameraDevicePermissionOutcome(device, SDL_TRUE); // go go go!
}
}
UnrefPhysicalCameraDevice(device); // we ref'd this in OpenDevice, release the extra reference.
}
static int ANDROIDCAMERA_OpenDevice(SDL_CameraDevice *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 -1;
}
RefPhysicalCameraDevice(device); // ref'd until permission callback fires.
// just in case SDL_OpenCameraDevice is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy.
SDL_copyp(&device->hidden->requested_spec, spec);
if (SDL_AndroidRequestPermission("android.permission.CAMERA", CameraPermissionCallback, device) < 0) {
UnrefPhysicalCameraDevice(device);
return -1;
}
return 0; // we don't open the camera until permission is granted, so always succeed for now.
}
static void ANDROIDCAMERA_FreeDeviceHandle(SDL_CameraDevice *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 = (int) i32ptr[1];
const int h = (int) i32ptr[2];
const int32_t type = i32ptr[3];
Uint32 sdlfmt;
if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) {
continue;
} else if ((w <= 0) || (h <= 0)) {
continue;
} else if ((sdlfmt = format_android_to_sdl(fmt)) == 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(format_android_to_sdl(fpsfmt)), fpsw, fpsh, duration);
if ((duration > 0) && (fpsfmt == fmt) && (fpsw == w) && (fpsh == h)) {
SDL_AddCameraFormat(add_data, sdlfmt, w, h, duration, 1000000000);
}
}
#else
SDL_AddCameraFormat(add_data, sdlfmt, w, h, 1, 30);
#endif
}
pACameraMetadata_free(metadata);
}
static SDL_bool FindAndroidCameraDeviceByID(SDL_CameraDevice *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_FindPhysicalCameraDeviceByCallback(FindAndroidCameraDeviceByID, (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_CameraDevice *device = SDL_AddCameraDevice(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_CameraDevice *device = SDL_FindPhysicalCameraDeviceByCallback(FindAndroidCameraDeviceByID, (void *) cameraId);
if (device && !device->hidden) {
SDL_CameraDeviceDisconnected(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 SDL_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 SDL_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 SDL_FALSE;
}
SDL_bool okay = SDL_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 = SDL_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() < 0) {
dlclose(libmedia);
dlclose(libcamera2);
return SDL_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 = SDL_TRUE;
return SDL_TRUE;
}
CameraBootStrap ANDROIDCAMERA_bootstrap = {
"android", "SDL Android camera driver", ANDROIDCAMERA_Init, SDL_FALSE
};
#endif
@@ -0,0 +1,479 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_CAMERA_DRIVER_COREMEDIA
#include "../SDL_syscamera.h"
#include "../SDL_camera_c.h"
#include "../../thread/SDL_systhread.h"
#import <AVFoundation/AVFoundation.h>
#import <CoreMedia/CoreMedia.h>
/*
* Need to link with:: CoreMedia CoreVideo
*
* Add in pInfo.list:
* <key>NSCameraUsageDescription</key> <string>Access camera</string>
*
*
* MACOSX:
* Add to the Code Sign Entitlement file:
* <key>com.apple.security.device.camera</key> <true/>
*/
static Uint32 CoreMediaFormatToSDL(FourCharCode fmt)
{
switch (fmt) {
#define CASE(x, y) case x: return y
// the 16LE ones should use 16BE if we're on a Bigendian system like PowerPC,
// but at current time there is no bigendian Apple platform that has CoreMedia.
CASE(kCMPixelFormat_16LE555, SDL_PIXELFORMAT_RGB555);
CASE(kCMPixelFormat_16LE5551, SDL_PIXELFORMAT_RGBA5551);
CASE(kCMPixelFormat_16LE565, SDL_PIXELFORMAT_RGB565);
CASE(kCMPixelFormat_24RGB, SDL_PIXELFORMAT_RGB24);
CASE(kCMPixelFormat_32ARGB, SDL_PIXELFORMAT_ARGB32);
CASE(kCMPixelFormat_32BGRA, SDL_PIXELFORMAT_BGRA32);
CASE(kCMPixelFormat_422YpCbCr8, SDL_PIXELFORMAT_YUY2);
CASE(kCMPixelFormat_422YpCbCr8_yuvs, SDL_PIXELFORMAT_UYVY);
#undef CASE
default:
#if DEBUG_CAMERA
SDL_Log("CAMERA: Unknown format FourCharCode '%d'", (int) fmt);
#endif
break;
}
return SDL_PIXELFORMAT_UNKNOWN;
}
@class SDLCaptureVideoDataOutputSampleBufferDelegate;
// just a simple wrapper to help ARC manage memory...
@interface SDLPrivateCameraData : NSObject
@property(nonatomic, retain) AVCaptureSession *session;
@property(nonatomic, retain) SDLCaptureVideoDataOutputSampleBufferDelegate *delegate;
@property(nonatomic, assign) CMSampleBufferRef current_sample;
@end
@implementation SDLPrivateCameraData
@end
static SDL_bool CheckCameraPermissions(SDL_CameraDevice *device)
{
if (device->permission == 0) { // still expecting a permission result.
if (@available(macOS 14, *)) {
const AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (status != AVAuthorizationStatusNotDetermined) { // NotDetermined == still waiting for an answer from the user.
SDL_CameraDevicePermissionOutcome(device, (status == AVAuthorizationStatusAuthorized) ? SDL_TRUE : SDL_FALSE);
}
} else {
SDL_CameraDevicePermissionOutcome(device, SDL_TRUE); // always allowed (or just unqueryable...?) on older macOS.
}
}
return (device->permission > 0);
}
// this delegate just receives new video frames on a Grand Central Dispatch queue, and fires off the
// main device thread iterate function directly to consume it.
@interface SDLCaptureVideoDataOutputSampleBufferDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
@property SDL_CameraDevice *device;
-(id) init:(SDL_CameraDevice *) dev;
-(void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
@end
@implementation SDLCaptureVideoDataOutputSampleBufferDelegate
-(id) init:(SDL_CameraDevice *) dev {
if ( self = [super init] ) {
_device = dev;
}
return self;
}
- (void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
SDL_CameraDevice *device = self.device;
if (!device || !device->hidden) {
return; // oh well.
}
if (!CheckCameraPermissions(device)) {
return; // nothing to do right now, dump what is probably a completely black frame.
}
SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
hidden.current_sample = sampleBuffer;
SDL_CameraThreadIterate(device);
hidden.current_sample = NULL;
}
- (void)captureOutput:(AVCaptureOutput *)output didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
#if DEBUG_CAMERA
SDL_Log("CAMERA: Drop frame.");
#endif
}
@end
static int COREMEDIA_WaitDevice(SDL_CameraDevice *device)
{
return 0; // this isn't used atm, since we run our own thread out of Grand Central Dispatch.
}
static int COREMEDIA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
{
int retval = 1;
SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
CMSampleBufferRef sample_buffer = hidden.current_sample;
hidden.current_sample = NULL;
SDL_assert(sample_buffer != NULL); // should only have been called from our delegate with a new frame.
CMSampleTimingInfo timinginfo;
if (CMSampleBufferGetSampleTimingInfo(sample_buffer, 0, &timinginfo) == noErr) {
*timestampNS = (Uint64) (CMTimeGetSeconds(timinginfo.presentationTimeStamp) * ((Float64) SDL_NS_PER_SECOND));
} else {
SDL_assert(!"this shouldn't happen, I think.");
*timestampNS = 0;
}
CVImageBufferRef image = CMSampleBufferGetImageBuffer(sample_buffer); // does not retain `image` (and we don't want it to).
const int numPlanes = (int) CVPixelBufferGetPlaneCount(image);
const int planar = (int) CVPixelBufferIsPlanar(image);
#if DEBUG_CAMERA
const int w = (int) CVPixelBufferGetWidth(image);
const int h = (int) CVPixelBufferGetHeight(image);
const int sz = (int) CVPixelBufferGetDataSize(image);
const int pitch = (int) CVPixelBufferGetBytesPerRow(image);
SDL_Log("CAMERA: buffer planar=%d numPlanes=%d %d x %d sz=%d pitch=%d", planar, numPlanes, w, h, sz, pitch);
#endif
// !!! FIXME: this currently copies the data to the surface (see FIXME about non-contiguous planar surfaces, but in theory we could just keep this locked until ReleaseFrame...
CVPixelBufferLockBaseAddress(image, 0);
if ((planar == 0) && (numPlanes == 0)) {
const int pitch = (int) CVPixelBufferGetBytesPerRow(image);
const size_t buflen = pitch * frame->h;
frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
if (frame->pixels == NULL) {
retval = -1;
} else {
frame->pitch = pitch;
SDL_memcpy(frame->pixels, CVPixelBufferGetBaseAddress(image), buflen);
}
} else {
// !!! FIXME: we have an open issue in SDL3 to allow SDL_Surface to support non-contiguous planar data, but we don't have it yet.
size_t buflen = 0;
for (int i = 0; (i < numPlanes) && (i < 3); i++) {
buflen += CVPixelBufferGetBytesPerRowOfPlane(image, i);
}
buflen *= frame->h;
frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
if (frame->pixels == NULL) {
retval = -1;
} else {
Uint8 *dst = frame->pixels;
frame->pitch = (int) CVPixelBufferGetBytesPerRowOfPlane(image, 0); // this is what SDL3 currently expects, probably incorrectly.
for (int i = 0; (i < numPlanes) && (i < 3); i++) {
const void *src = CVPixelBufferGetBaseAddressOfPlane(image, i);
const size_t pitch = CVPixelBufferGetBytesPerRowOfPlane(image, i);
SDL_memcpy(dst, src, pitch * frame->h);
dst += pitch * frame->h;
}
}
}
CVPixelBufferUnlockBaseAddress(image, 0);
return retval;
}
static void COREMEDIA_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
{
// !!! FIXME: this currently copies the data to the surface, but in theory we could just keep this locked until ReleaseFrame...
SDL_aligned_free(frame->pixels);
}
static void COREMEDIA_CloseDevice(SDL_CameraDevice *device)
{
if (device && device->hidden) {
SDLPrivateCameraData *hidden = (SDLPrivateCameraData *) CFBridgingRelease(device->hidden);
device->hidden = NULL;
AVCaptureSession *session = hidden.session;
if (session) {
hidden.session = nil;
[session stopRunning];
[session removeInput:[session.inputs objectAtIndex:0]];
[session removeOutput:(AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0]];
session = nil;
}
hidden.delegate = NULL;
hidden.current_sample = NULL;
}
}
static int COREMEDIA_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
{
AVCaptureDevice *avdevice = (__bridge AVCaptureDevice *) device->handle;
// Pick format that matches the spec
const Uint32 sdlfmt = spec->format;
const int w = spec->width;
const int h = spec->height;
const int rate = spec->interval_denominator;
AVCaptureDeviceFormat *spec_format = nil;
NSArray<AVCaptureDeviceFormat *> *formats = [avdevice formats];
for (AVCaptureDeviceFormat *format in formats) {
CMFormatDescriptionRef formatDescription = [format formatDescription];
if (CoreMediaFormatToSDL(CMFormatDescriptionGetMediaSubType(formatDescription)) != sdlfmt) {
continue;
}
const CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDescription);
if ( ((int) dim.width != w) || (((int) dim.height) != h) ) {
continue;
}
for (AVFrameRateRange *framerate in format.videoSupportedFrameRateRanges) {
if ((rate == (int) SDL_ceil((double) framerate.minFrameRate)) || (rate == (int) SDL_floor((double) framerate.maxFrameRate))) {
spec_format = format;
break;
}
}
if (spec_format != nil) {
break;
}
}
if (spec_format == nil) {
return SDL_SetError("camera spec format not available");
} else if (![avdevice lockForConfiguration:NULL]) {
return SDL_SetError("Cannot lockForConfiguration");
}
avdevice.activeFormat = spec_format;
[avdevice unlockForConfiguration];
AVCaptureSession *session = [[AVCaptureSession alloc] init];
if (session == nil) {
return SDL_SetError("Failed to allocate/init AVCaptureSession");
}
session.sessionPreset = AVCaptureSessionPresetHigh;
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:avdevice error:&error];
if (!input) {
return SDL_SetError("Cannot create AVCaptureDeviceInput");
}
AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
if (!output) {
return SDL_SetError("Cannot create AVCaptureVideoDataOutput");
}
char threadname[64];
SDL_GetCameraThreadName(device, threadname, sizeof (threadname));
dispatch_queue_t queue = dispatch_queue_create(threadname, NULL);
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
if (!queue) {
return SDL_SetError("dispatch_queue_create() failed");
}
SDLCaptureVideoDataOutputSampleBufferDelegate *delegate = [[SDLCaptureVideoDataOutputSampleBufferDelegate alloc] init:device];
if (delegate == nil) {
return SDL_SetError("Cannot create SDLCaptureVideoDataOutputSampleBufferDelegate");
}
[output setSampleBufferDelegate:delegate queue:queue];
if (![session canAddInput:input]) {
return SDL_SetError("Cannot add AVCaptureDeviceInput");
}
[session addInput:input];
if (![session canAddOutput:output]) {
return SDL_SetError("Cannot add AVCaptureVideoDataOutput");
}
[session addOutput:output];
[session commitConfiguration];
SDLPrivateCameraData *hidden = [[SDLPrivateCameraData alloc] init];
if (hidden == nil) {
return SDL_SetError("Cannot create SDLPrivateCameraData");
}
hidden.session = session;
hidden.delegate = delegate;
hidden.current_sample = NULL;
device->hidden = (struct SDL_PrivateCameraData *)CFBridgingRetain(hidden);
[session startRunning]; // !!! FIXME: docs say this can block while camera warms up and shouldn't be done on main thread. Maybe push through `queue`?
CheckCameraPermissions(device); // check right away, in case the process is already granted permission.
return 0;
}
static void COREMEDIA_FreeDeviceHandle(SDL_CameraDevice *device)
{
if (device && device->handle) {
CFBridgingRelease(device->handle);
}
}
static void GatherCameraSpecs(AVCaptureDevice *device, CameraFormatAddData *add_data)
{
SDL_zerop(add_data);
for (AVCaptureDeviceFormat *fmt in device.formats) {
if (CMFormatDescriptionGetMediaType(fmt.formatDescription) != kCMMediaType_Video) {
continue;
}
const Uint32 sdlfmt = CoreMediaFormatToSDL(CMFormatDescriptionGetMediaSubType(fmt.formatDescription));
if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) {
continue;
}
const CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(fmt.formatDescription);
const int w = (int) dims.width;
const int h = (int) dims.height;
for (AVFrameRateRange *framerate in fmt.videoSupportedFrameRateRanges) {
int rate;
rate = (int) SDL_ceil((double) framerate.minFrameRate);
if (rate) {
SDL_AddCameraFormat(add_data, sdlfmt, w, h, 1, rate);
}
rate = (int) SDL_floor((double) framerate.maxFrameRate);
if (rate) {
SDL_AddCameraFormat(add_data, sdlfmt, w, h, 1, rate);
}
}
}
}
static SDL_bool FindCoreMediaCameraDeviceByUniqueID(SDL_CameraDevice *device, void *userdata)
{
NSString *uniqueid = (__bridge NSString *) userdata;
AVCaptureDevice *avdev = (__bridge AVCaptureDevice *) device->handle;
return ([uniqueid isEqualToString:avdev.uniqueID]) ? SDL_TRUE : SDL_FALSE;
}
static void MaybeAddDevice(AVCaptureDevice *avdevice)
{
if (!avdevice.connected) {
return; // not connected.
} else if (![avdevice hasMediaType:AVMediaTypeVideo]) {
return; // not a camera.
} else if (SDL_FindPhysicalCameraDeviceByCallback(FindCoreMediaCameraDeviceByUniqueID, (__bridge void *) avdevice.uniqueID)) {
return; // already have this one.
}
CameraFormatAddData add_data;
GatherCameraSpecs(avdevice, &add_data);
if (add_data.num_specs > 0) {
SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
if (avdevice.position == AVCaptureDevicePositionFront) {
position = SDL_CAMERA_POSITION_FRONT_FACING;
} else if (avdevice.position == AVCaptureDevicePositionBack) {
position = SDL_CAMERA_POSITION_BACK_FACING;
}
SDL_AddCameraDevice(avdevice.localizedName.UTF8String, position, add_data.num_specs, add_data.specs, (void *) CFBridgingRetain(avdevice));
}
SDL_free(add_data.specs);
}
static void COREMEDIA_DetectDevices(void)
{
NSArray<AVCaptureDevice *> *devices = nil;
if (@available(macOS 10.15, iOS 13, *)) {
// kind of annoying that there isn't a "give me anything that looks like a camera" option,
// so this list will need to be updated when Apple decides to add
// AVCaptureDeviceTypeBuiltInQuadrupleCamera some day.
NSArray *device_types = @[
#ifdef SDL_PLATFORM_IOS
AVCaptureDeviceTypeBuiltInTelephotoCamera,
AVCaptureDeviceTypeBuiltInDualCamera,
AVCaptureDeviceTypeBuiltInDualWideCamera,
AVCaptureDeviceTypeBuiltInTripleCamera,
AVCaptureDeviceTypeBuiltInUltraWideCamera,
#else
AVCaptureDeviceTypeExternalUnknown,
#endif
AVCaptureDeviceTypeBuiltInWideAngleCamera
];
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
discoverySessionWithDeviceTypes:device_types
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionUnspecified];
devices = discoverySession.devices;
// !!! FIXME: this can use Key Value Observation to get hotplug events.
} else {
// this is deprecated but works back to macOS 10.7; 10.15 added AVCaptureDeviceDiscoverySession as a replacement.
devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
// !!! FIXME: this can use AVCaptureDeviceWasConnectedNotification and AVCaptureDeviceWasDisconnectedNotification with NSNotificationCenter to get hotplug events.
}
for (AVCaptureDevice *device in devices) {
MaybeAddDevice(device);
}
}
static void COREMEDIA_Deinitialize(void)
{
// !!! FIXME: disable hotplug.
}
static SDL_bool COREMEDIA_Init(SDL_CameraDriverImpl *impl)
{
impl->DetectDevices = COREMEDIA_DetectDevices;
impl->OpenDevice = COREMEDIA_OpenDevice;
impl->CloseDevice = COREMEDIA_CloseDevice;
impl->WaitDevice = COREMEDIA_WaitDevice;
impl->AcquireFrame = COREMEDIA_AcquireFrame;
impl->ReleaseFrame = COREMEDIA_ReleaseFrame;
impl->FreeDeviceHandle = COREMEDIA_FreeDeviceHandle;
impl->Deinitialize = COREMEDIA_Deinitialize;
impl->ProvidesOwnCallbackThread = SDL_TRUE;
return SDL_TRUE;
}
CameraBootStrap COREMEDIA_bootstrap = {
"coremedia", "SDL Apple CoreMedia camera driver", COREMEDIA_Init, SDL_FALSE
};
#endif // SDL_CAMERA_DRIVER_COREMEDIA
@@ -0,0 +1,80 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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 int DUMMYCAMERA_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
{
return SDL_Unsupported();
}
static void DUMMYCAMERA_CloseDevice(SDL_CameraDevice *device)
{
}
static int DUMMYCAMERA_WaitDevice(SDL_CameraDevice *device)
{
return SDL_Unsupported();
}
static int DUMMYCAMERA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
{
return SDL_Unsupported();
}
static void DUMMYCAMERA_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
{
}
static void DUMMYCAMERA_DetectDevices(void)
{
}
static void DUMMYCAMERA_FreeDeviceHandle(SDL_CameraDevice *device)
{
}
static void DUMMYCAMERA_Deinitialize(void)
{
}
static SDL_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 SDL_TRUE;
}
CameraBootStrap DUMMYCAMERA_bootstrap = {
"dummy", "SDL dummy camera driver", DUMMYCAMERA_Init, SDL_TRUE
};
#endif // SDL_CAMERA_DRIVER_DUMMY
@@ -0,0 +1,265 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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 <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 int EMSCRIPTENCAMERA_WaitDevice(SDL_CameraDevice *device)
{
SDL_assert(!"This shouldn't be called"); // we aren't using SDL's internal thread.
return -1;
}
static int EMSCRIPTENCAMERA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
{
void *rgba = SDL_malloc(device->actual_spec.width * device->actual_spec.height * 4);
if (!rgba) {
return SDL_OutOfMemory();
}
*timestampNS = SDL_GetTicksNS(); // best we can do here.
const int rc = MAIN_THREAD_EM_ASM_INT({
const w = $0;
const h = $1;
const rgba = $2;
const SDL3 = Module['SDL3'];
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.ctx2d) === 'undefined')) {
return 0; // don't have something we need, oh well.
}
SDL3.camera.ctx2d.drawImage(SDL3.camera.video, 0, 0, w, h);
const imgrgba = SDL3.camera.ctx2d.getImageData(0, 0, w, h).data;
Module.HEAPU8.set(imgrgba, rgba);
return 1;
}, device->actual_spec.width, device->actual_spec.height, rgba);
if (!rc) {
SDL_free(rgba);
return 0; // something went wrong, maybe shutting down; just don't return a frame.
}
frame->pixels = rgba;
frame->pitch = device->actual_spec.width * 4;
return 1;
}
static void EMSCRIPTENCAMERA_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
{
SDL_free(frame->pixels);
}
static void EMSCRIPTENCAMERA_CloseDevice(SDL_CameraDevice *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.
_SDL_free(SDL3.camera.rgba);
SDL3.camera = {}; // dump our references to everything.
});
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static void SDLEmscriptenCameraDevicePermissionOutcome(SDL_CameraDevice *device, int approved, int w, int h, int fps)
{
device->spec.width = device->actual_spec.width = w;
device->spec.height = device->actual_spec.height = h;
device->spec.interval_numerator = device->actual_spec.interval_numerator = 1;
device->spec.interval_denominator = device->actual_spec.interval_denominator = fps;
SDL_CameraDevicePermissionOutcome(device, approved ? SDL_TRUE : SDL_FALSE);
}
static int EMSCRIPTENCAMERA_OpenDevice(SDL_CameraDevice *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 interval_numerator = $3;
const interval_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 ((interval_numerator > 0) && (interval_denominator > 0)) {
var fps = interval_denominator / interval_numerator;
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);
dynCall('viiiii', 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.rgba = 0;
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('viiiii', outcome, [device, 0, 0, 0, 0]); // we call this a permission error, because it probably is.
});
}, device, spec->width, spec->height, spec->interval_numerator, spec->interval_denominator, SDLEmscriptenCameraDevicePermissionOutcome, SDL_CameraThreadIterate);
return 0; // the real work waits until the user approves a camera.
}
static void EMSCRIPTENCAMERA_FreeDeviceHandle(SDL_CameraDevice *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_AddCameraDevice("Web browser's camera", SDL_CAMERA_POSITION_UNKNOWN, 0, NULL, (void *) (size_t) 0x1);
}
}
static SDL_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 = SDL_TRUE;
return SDL_TRUE;
}
CameraBootStrap EMSCRIPTENCAMERA_bootstrap = {
"emscripten", "SDL Emscripten MediaStream camera driver", EMSCRIPTENCAMERA_Init, SDL_FALSE
};
/* *INDENT-ON* */ /* clang-format on */
#endif // SDL_CAMERA_DRIVER_EMSCRIPTEN
@@ -0,0 +1,933 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
// the Windows Media Foundation API
#ifdef SDL_CAMERA_DRIVER_MEDIAFOUNDATION
#define COBJMACROS
// this seems to be a bug in mfidl.h, just define this to avoid the problem section.
#define __IMFVideoProcessorControl3_INTERFACE_DEFINED__
#include "../../core/windows/SDL_windows.h"
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include "../SDL_syscamera.h"
#include "../SDL_camera_c.h"
static const IID SDL_IID_IMFMediaSource = { 0x279a808d, 0xaec7, 0x40c8, { 0x9c, 0x6b, 0xa6, 0xb4, 0x92, 0xc7, 0x8a, 0x66 } };
static const IID SDL_IID_IMF2DBuffer = { 0x7dc9d5f9, 0x9ed9, 0x44ec, { 0x9b, 0xbf, 0x06, 0x00, 0xbb, 0x58, 0x9f, 0xbb } };
static const IID SDL_IID_IMF2DBuffer2 = { 0x33ae5ea6, 0x4316, 0x436f, { 0x8d, 0xdd, 0xd7, 0x3d, 0x22, 0xf8, 0x29, 0xec } };
static const GUID SDL_MF_MT_DEFAULT_STRIDE = { 0x644b4e48, 0x1e02, 0x4516, { 0xb0, 0xeb, 0xc0, 0x1c, 0xa9, 0xd4, 0x9a, 0xc6 } };
static const GUID SDL_MF_MT_MAJOR_TYPE = { 0x48eba18e, 0xf8c9, 0x4687, { 0xbf, 0x11, 0x0a, 0x74, 0xc9, 0xf9, 0x6a, 0x8f } };
static const GUID SDL_MF_MT_SUBTYPE = { 0xf7e34c9a, 0x42e8, 0x4714, { 0xb7, 0x4b, 0xcb, 0x29, 0xd7, 0x2c, 0x35, 0xe5 } };
static const GUID SDL_MF_MT_FRAME_SIZE = { 0x1652c33d, 0xd6b2, 0x4012, { 0xb8, 0x34, 0x72, 0x03, 0x08, 0x49, 0xa3, 0x7d } };
static const GUID SDL_MF_MT_FRAME_RATE = { 0xc459a2e8, 0x3d2c, 0x4e44, { 0xb1, 0x32, 0xfe, 0xe5, 0x15, 0x6c, 0x7b, 0xb0 } };
static const GUID SDL_MFMediaType_Video = { 0x73646976, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } };
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmultichar"
#endif
#define SDL_DEFINE_MEDIATYPE_GUID(name, fmt) static const GUID SDL_##name = { fmt, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB555, 24);
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB565, 23);
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB24, 20);
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB32, 22);
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_ARGB32, 21);
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_A2R10G10B10, 31);
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YV12, FCC('YV12'));
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_IYUV, FCC('IYUV'));
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YUY2, FCC('YUY2'));
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_UYVY, FCC('UYVY'));
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YVYU, FCC('YVYU'));
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_NV12, FCC('NV12'));
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_NV21, FCC('NV21'));
#undef SDL_DEFINE_MEDIATYPE_GUID
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
static const struct
{
const GUID *guid;
const SDL_PixelFormatEnum sdlfmt;
} fmtmappings[] = {
// This is not every possible format, just popular ones that SDL can reasonably handle.
// (and we should probably trim this list more.)
{ &SDL_MFVideoFormat_RGB555, SDL_PIXELFORMAT_XRGB1555 },
{ &SDL_MFVideoFormat_RGB565, SDL_PIXELFORMAT_RGB565 },
{ &SDL_MFVideoFormat_RGB24, SDL_PIXELFORMAT_RGB24 },
{ &SDL_MFVideoFormat_RGB32, SDL_PIXELFORMAT_XRGB8888 },
{ &SDL_MFVideoFormat_ARGB32, SDL_PIXELFORMAT_ARGB8888 },
{ &SDL_MFVideoFormat_A2R10G10B10, SDL_PIXELFORMAT_ARGB2101010 },
{ &SDL_MFVideoFormat_YV12, SDL_PIXELFORMAT_YV12 },
{ &SDL_MFVideoFormat_IYUV, SDL_PIXELFORMAT_IYUV },
{ &SDL_MFVideoFormat_YUY2, SDL_PIXELFORMAT_YUY2 },
{ &SDL_MFVideoFormat_UYVY, SDL_PIXELFORMAT_UYVY },
{ &SDL_MFVideoFormat_YVYU, SDL_PIXELFORMAT_YVYU },
{ &SDL_MFVideoFormat_NV12, SDL_PIXELFORMAT_NV12 },
{ &SDL_MFVideoFormat_NV21, SDL_PIXELFORMAT_NV21 }
};
static SDL_PixelFormatEnum MFVidFmtGuidToSDLFmt(const GUID *guid)
{
for (size_t i = 0; i < SDL_arraysize(fmtmappings); i++) {
if (WIN_IsEqualGUID(guid, fmtmappings[i].guid)) {
return fmtmappings[i].sdlfmt;
}
}
return SDL_PIXELFORMAT_UNKNOWN;
}
static const GUID *SDLFmtToMFVidFmtGuid(SDL_PixelFormatEnum sdlfmt)
{
for (size_t i = 0; i < SDL_arraysize(fmtmappings); i++) {
if (fmtmappings[i].sdlfmt == sdlfmt) {
return fmtmappings[i].guid;
}
}
return NULL;
}
// handle to Media Foundation libs--Vista and later!--for access to the Media Foundation API.
// mf.dll ...
static HMODULE libmf = NULL;
typedef HRESULT(WINAPI *pfnMFEnumDeviceSources)(IMFAttributes *,IMFActivate ***,UINT32 *);
typedef HRESULT(WINAPI *pfnMFCreateDeviceSource)(IMFAttributes *, IMFMediaSource **);
static pfnMFEnumDeviceSources pMFEnumDeviceSources = NULL;
static pfnMFCreateDeviceSource pMFCreateDeviceSource = NULL;
// mfplat.dll ...
static HMODULE libmfplat = NULL;
typedef HRESULT(WINAPI *pfnMFStartup)(ULONG, DWORD);
typedef HRESULT(WINAPI *pfnMFShutdown)(void);
typedef HRESULT(WINAPI *pfnMFCreateAttributes)(IMFAttributes **, UINT32);
typedef HRESULT(WINAPI *pfnMFCreateMediaType)(IMFMediaType **);
typedef HRESULT(WINAPI *pfnMFGetStrideForBitmapInfoHeader)(DWORD, DWORD, LONG *);
static pfnMFStartup pMFStartup = NULL;
static pfnMFShutdown pMFShutdown = NULL;
static pfnMFCreateAttributes pMFCreateAttributes = NULL;
static pfnMFCreateMediaType pMFCreateMediaType = NULL;
static pfnMFGetStrideForBitmapInfoHeader pMFGetStrideForBitmapInfoHeader = NULL;
// mfreadwrite.dll ...
static HMODULE libmfreadwrite = NULL;
typedef HRESULT(WINAPI *pfnMFCreateSourceReaderFromMediaSource)(IMFMediaSource *, IMFAttributes *, IMFSourceReader **);
static pfnMFCreateSourceReaderFromMediaSource pMFCreateSourceReaderFromMediaSource = NULL;
typedef struct SDL_PrivateCameraData
{
IMFSourceReader *srcreader;
IMFSample *current_sample;
int pitch;
} SDL_PrivateCameraData;
static int MEDIAFOUNDATION_WaitDevice(SDL_CameraDevice *device)
{
SDL_assert(device->hidden->current_sample == NULL);
IMFSourceReader *srcreader = device->hidden->srcreader;
IMFSample *sample = NULL;
while (!SDL_AtomicGet(&device->shutdown)) {
DWORD stream_flags = 0;
const HRESULT ret = IMFSourceReader_ReadSample(srcreader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, &stream_flags, NULL, &sample);
if (FAILED(ret)) {
return -1; // ruh roh.
}
// we currently ignore stream_flags format changes, but my _hope_ is that IMFSourceReader is handling this and
// will continue to give us the explictly-specified format we requested when opening the device, though, and
// we don't have to manually deal with it.
if (sample != NULL) {
break;
} else if (stream_flags & (MF_SOURCE_READERF_ERROR | MF_SOURCE_READERF_ENDOFSTREAM)) {
return -1; // apparently this camera has gone down. :/
}
// otherwise, there was some minor burp, probably; just try again.
}
device->hidden->current_sample = sample;
return 0;
}
#ifdef KEEP_ACQUIRED_BUFFERS_LOCKED
#define PROP_SURFACE_IMFOBJS_POINTER "SDL.camera.mediafoundation.imfobjs"
typedef struct SDL_IMFObjects
{
IMF2DBuffer2 *buffer2d2;
IMF2DBuffer *buffer2d;
IMFMediaBuffer *buffer;
IMFSample *sample;
} SDL_IMFObjects;
static void SDLCALL CleanupIMF2DBuffer2(void *userdata, void *value)
{
SDL_IMFObjects *objs = (SDL_IMFObjects *)value;
IMF2DBuffer2_Unlock2D(objs->buffer2d2);
IMF2DBuffer2_Release(objs->buffer2d2);
IMFMediaBuffer_Release(objs->buffer);
IMFSample_Release(objs->sample);
SDL_free(objs);
}
static void SDLCALL CleanupIMF2DBuffer(void *userdata, void *value)
{
SDL_IMFObjects *objs = (SDL_IMFObjects *)value;
IMF2DBuffer_Unlock2D(objs->buffer2d);
IMF2DBuffer_Release(objs->buffer2d);
IMFMediaBuffer_Release(objs->buffer);
IMFSample_Release(objs->sample);
SDL_free(objs);
}
static void SDLCALL CleanupIMFMediaBuffer(void *userdata, void *value)
{
SDL_IMFObjects *objs = (SDL_IMFObjects *)value;
IMFMediaBuffer_Unlock(objs->buffer);
IMFMediaBuffer_Release(objs->buffer);
IMFSample_Release(objs->sample);
SDL_free(objs);
}
static int MEDIAFOUNDATION_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
{
SDL_assert(device->hidden->current_sample != NULL);
int retval = 1;
HRESULT ret;
LONGLONG timestamp100NS = 0;
SDL_IMFObjects *objs = (SDL_IMFObjects *) SDL_calloc(1, sizeof (SDL_IMFObjects));
if (objs == NULL) {
return -1;
}
objs->sample = device->hidden->current_sample;
device->hidden->current_sample = NULL;
const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
if (!surfprops) {
retval = -1;
} else {
ret = IMFSample_GetSampleTime(objs->sample, &timestamp100NS);
if (FAILED(ret)) {
retval = -1;
}
*timestampNS = timestamp100NS * 100; // the timestamps are in 100-nanosecond increments; move to full nanoseconds.
}
ret = (retval < 0) ? E_FAIL : IMFSample_ConvertToContiguousBuffer(objs->sample, &objs->buffer); /*IMFSample_GetBufferByIndex(objs->sample, 0, &objs->buffer);*/
if (FAILED(ret)) {
SDL_free(objs);
retval = -1;
} else {
BYTE *pixels = NULL;
LONG pitch = 0;
if (SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer2, (void **)&objs->buffer2d2))) {
BYTE *bufstart = NULL;
DWORD buflen = 0;
ret = IMF2DBuffer2_Lock2DSize(objs->buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen);
if (FAILED(ret)) {
retval = -1;
CleanupIMF2DBuffer2(NULL, objs);
} else {
frame->pixels = pixels;
frame->pitch = (int) pitch;
if (SDL_SetPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer2, NULL) == -1) {
CleanupIMF2DBuffer2(NULL, objs);
retval = -1;
}
}
} else if (SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer, (void **)&objs->buffer2d))) {
ret = IMF2DBuffer_Lock2D(objs->buffer2d, &pixels, &pitch);
if (FAILED(ret)) {
CleanupIMF2DBuffer(NULL, objs);
retval = -1;
} else {
frame->pixels = pixels;
frame->pitch = (int) pitch;
if (SDL_SetPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer, NULL) == -1) {
CleanupIMF2DBuffer(NULL, objs);
retval = -1;
}
}
} else {
DWORD maxlen = 0, currentlen = 0;
ret = IMFMediaBuffer_Lock(objs->buffer, &pixels, &maxlen, &currentlen);
if (FAILED(ret)) {
CleanupIMFMediaBuffer(NULL, objs);
retval = -1;
} else {
pitch = (LONG) device->hidden->pitch;
if (pitch < 0) { // image rows are reversed.
pixels += -pitch * (frame->h - 1);
}
frame->pixels = pixels;
frame->pitch = (int) pitch;
if (SDL_SetPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMFMediaBuffer, NULL) == -1) {
CleanupIMFMediaBuffer(NULL, objs);
retval = -1;
}
}
}
}
if (retval < 0) {
*timestampNS = 0;
}
return retval;
}
static void MEDIAFOUNDATION_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
{
const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
if (surfprops) {
// this will release the IMFBuffer and IMFSample objects for this frame.
SDL_ClearProperty(surfprops, PROP_SURFACE_IMFOBJS_POINTER);
}
}
#else
static int MEDIAFOUNDATION_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
{
SDL_assert(device->hidden->current_sample != NULL);
int retval = 1;
HRESULT ret;
LONGLONG timestamp100NS = 0;
IMFSample *sample = device->hidden->current_sample;
device->hidden->current_sample = NULL;
const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
if (!surfprops) {
retval = -1;
} else {
ret = IMFSample_GetSampleTime(sample, &timestamp100NS);
if (FAILED(ret)) {
retval = -1;
}
*timestampNS = timestamp100NS * 100; // the timestamps are in 100-nanosecond increments; move to full nanoseconds.
}
IMFMediaBuffer *buffer = NULL;
ret = (retval < 0) ? E_FAIL : IMFSample_ConvertToContiguousBuffer(sample, &buffer); /*IMFSample_GetBufferByIndex(sample, 0, &buffer);*/
if (FAILED(ret)) {
retval = -1;
} else {
IMF2DBuffer *buffer2d = NULL;
IMF2DBuffer2 *buffer2d2 = NULL;
BYTE *pixels = NULL;
LONG pitch = 0;
if (SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer2, (void **)&buffer2d2))) {
BYTE *bufstart = NULL;
DWORD buflen = 0;
ret = IMF2DBuffer2_Lock2DSize(buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen);
if (FAILED(ret)) {
retval = -1;
} else {
frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
if (frame->pixels == NULL) {
retval = -1;
} else {
SDL_memcpy(frame->pixels, pixels, buflen);
frame->pitch = (int)pitch;
}
IMF2DBuffer2_Unlock2D(buffer2d2);
}
IMF2DBuffer2_Release(buffer2d2);
} else if (SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer, (void **)&buffer2d))) {
ret = IMF2DBuffer_Lock2D(buffer2d, &pixels, &pitch);
if (FAILED(ret)) {
retval = -1;
} else {
BYTE *bufstart = pixels;
const DWORD buflen = (SDL_abs((int)pitch) * frame->w) * frame->h;
if (pitch < 0) { // image rows are reversed.
bufstart += -pitch * (frame->h - 1);
}
frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
if (frame->pixels == NULL) {
retval = -1;
} else {
SDL_memcpy(frame->pixels, bufstart, buflen);
frame->pitch = (int)pitch;
}
IMF2DBuffer_Unlock2D(buffer2d);
}
IMF2DBuffer_Release(buffer2d);
} else {
DWORD maxlen = 0, currentlen = 0;
ret = IMFMediaBuffer_Lock(buffer, &pixels, &maxlen, &currentlen);
if (FAILED(ret)) {
retval = -1;
} else {
BYTE *bufstart = pixels;
pitch = (LONG)device->hidden->pitch;
const DWORD buflen = (SDL_abs((int)pitch) * frame->w) * frame->h;
if (pitch < 0) { // image rows are reversed.
bufstart += -pitch * (frame->h - 1);
}
frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
if (frame->pixels == NULL) {
retval = -1;
} else {
SDL_memcpy(frame->pixels, bufstart, buflen);
frame->pitch = (int)pitch;
}
IMFMediaBuffer_Unlock(buffer);
}
}
IMFMediaBuffer_Release(buffer);
}
IMFSample_Release(sample);
if (retval < 0) {
*timestampNS = 0;
}
return retval;
}
static void MEDIAFOUNDATION_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
{
SDL_aligned_free(frame->pixels);
}
#endif
static void MEDIAFOUNDATION_CloseDevice(SDL_CameraDevice *device)
{
if (device && device->hidden) {
if (device->hidden->srcreader) {
IMFSourceReader_Release(device->hidden->srcreader);
}
if (device->hidden->current_sample) {
IMFSample_Release(device->hidden->current_sample);
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
// this function is from https://learn.microsoft.com/en-us/windows/win32/medfound/uncompressed-video-buffers
static HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride)
{
LONG lStride = 0;
// Try to get the default stride from the media type.
HRESULT ret = IMFMediaType_GetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32*)&lStride);
if (FAILED(ret)) {
// Attribute not set. Try to calculate the default stride.
GUID subtype = GUID_NULL;
UINT32 width = 0;
/* UINT32 height = 0; */
UINT64 val = 0;
// Get the subtype and the image size.
ret = IMFMediaType_GetGUID(pType, &SDL_MF_MT_SUBTYPE, &subtype);
if (FAILED(ret)) {
goto done;
}
ret = IMFMediaType_GetUINT64(pType, &SDL_MF_MT_FRAME_SIZE, &val);
if (FAILED(ret)) {
goto done;
}
width = (UINT32) (val >> 32);
/* height = (UINT32) val; */
ret = pMFGetStrideForBitmapInfoHeader(subtype.Data1, width, &lStride);
if (FAILED(ret)) {
goto done;
}
// Set the attribute for later reference.
IMFMediaType_SetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32) lStride);
}
if (SUCCEEDED(ret)) {
*plStride = lStride;
}
done:
return ret;
}
static int MEDIAFOUNDATION_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
{
const char *utf8symlink = (const char *) device->handle;
IMFAttributes *attrs = NULL;
LPWSTR wstrsymlink = NULL;
IMFMediaSource *source = NULL;
IMFMediaType *mediatype = NULL;
IMFSourceReader *srcreader = NULL;
#if 0
DWORD num_streams = 0;
#endif
LONG lstride = 0;
//PROPVARIANT var;
HRESULT ret;
#if 0
IMFStreamDescriptor *streamdesc = NULL;
IMFPresentationDescriptor *presentdesc = NULL;
IMFMediaTypeHandler *handler = NULL;
#endif
#if DEBUG_CAMERA
SDL_Log("CAMERA: opening device with symlink of '%s'", utf8symlink);
#endif
wstrsymlink = WIN_UTF8ToString(utf8symlink);
if (!wstrsymlink) {
goto failed;
}
#define CHECK_HRESULT(what, r) if (FAILED(r)) { WIN_SetErrorFromHRESULT(what " failed", r); goto failed; }
ret = pMFCreateAttributes(&attrs, 1);
CHECK_HRESULT("MFCreateAttributes", ret);
ret = IMFAttributes_SetGUID(attrs, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
CHECK_HRESULT("IMFAttributes_SetGUID(srctype)", ret);
ret = IMFAttributes_SetString(attrs, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, wstrsymlink);
CHECK_HRESULT("IMFAttributes_SetString(symlink)", ret);
ret = pMFCreateDeviceSource(attrs, &source);
CHECK_HRESULT("MFCreateDeviceSource", ret);
IMFAttributes_Release(attrs);
SDL_free(wstrsymlink);
attrs = NULL;
wstrsymlink = NULL;
// !!! FIXME: I think it'd be nice to do this without an IMFSourceReader,
// since it's just utility code that has to handle more complex media streams
// than we're dealing with, but this will do for now. The docs are slightly
// insistent that you should use one, though...Maybe it's extremely hard
// to handle directly at the IMFMediaSource layer...?
ret = pMFCreateSourceReaderFromMediaSource(source, NULL, &srcreader);
CHECK_HRESULT("MFCreateSourceReaderFromMediaSource", ret);
// !!! FIXME: do we actually have to find the media type object in the source reader or can we just roll our own like this?
ret = pMFCreateMediaType(&mediatype);
CHECK_HRESULT("MFCreateMediaType", ret);
ret = IMFMediaType_SetGUID(mediatype, &SDL_MF_MT_MAJOR_TYPE, &SDL_MFMediaType_Video);
CHECK_HRESULT("IMFMediaType_SetGUID(major_type)", ret);
ret = IMFMediaType_SetGUID(mediatype, &SDL_MF_MT_SUBTYPE, SDLFmtToMFVidFmtGuid(spec->format));
CHECK_HRESULT("IMFMediaType_SetGUID(subtype)", ret);
ret = IMFMediaType_SetUINT64(mediatype, &SDL_MF_MT_FRAME_SIZE, (((UINT64)spec->width) << 32) | ((UINT64)spec->height));
CHECK_HRESULT("MFSetAttributeSize(frame_size)", ret);
ret = IMFMediaType_SetUINT64(mediatype, &SDL_MF_MT_FRAME_RATE, (((UINT64)spec->interval_numerator) << 32) | ((UINT64)spec->interval_denominator));
CHECK_HRESULT("MFSetAttributeRatio(frame_rate)", ret);
ret = IMFSourceReader_SetCurrentMediaType(srcreader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mediatype);
CHECK_HRESULT("IMFSourceReader_SetCurrentMediaType", ret);
#if 0 // this (untested thing) is what we would do to get started with a IMFMediaSource that _doesn't_ use IMFSourceReader...
ret = IMFMediaSource_CreatePresentationDescriptor(source, &presentdesc);
CHECK_HRESULT("IMFMediaSource_CreatePresentationDescriptor", ret);
ret = IMFPresentationDescriptor_GetStreamDescriptorCount(presentdesc, &num_streams);
CHECK_HRESULT("IMFPresentationDescriptor_GetStreamDescriptorCount", ret);
for (DWORD i = 0; i < num_streams; i++) {
BOOL selected = FALSE;
ret = IMFPresentationDescriptor_GetStreamDescriptorByIndex(presentdesc, i, &selected, &streamdesc);
CHECK_HRESULT("IMFPresentationDescriptor_GetStreamDescriptorByIndex", ret);
if (selected) {
ret = IMFStreamDescriptor_GetMediaTypeHandler(streamdesc, &handler);
CHECK_HRESULT("IMFStreamDescriptor_GetMediaTypeHandler", ret);
IMFMediaTypeHandler_SetCurrentMediaType(handler, mediatype);
IMFMediaTypeHandler_Release(handler);
handler = NULL;
}
IMFStreamDescriptor_Release(streamdesc);
streamdesc = NULL;
}
PropVariantInit(&var);
var.vt = VT_EMPTY;
ret = IMFMediaSource_Start(source, presentdesc, NULL, &var);
PropVariantClear(&var);
CHECK_HRESULT("IMFMediaSource_Start", ret);
IMFPresentationDescriptor_Release(presentdesc);
presentdesc = NULL;
#endif
ret = GetDefaultStride(mediatype, &lstride);
CHECK_HRESULT("GetDefaultStride", ret);
IMFMediaType_Release(mediatype);
mediatype = NULL;
device->hidden = (SDL_PrivateCameraData *) SDL_calloc(1, sizeof (SDL_PrivateCameraData));
if (!device->hidden) {
goto failed;
}
device->hidden->pitch = (int) lstride;
device->hidden->srcreader = srcreader;
IMFMediaSource_Release(source); // srcreader is holding a reference to this.
// There is no user permission prompt for camera access (I think?)
SDL_CameraDevicePermissionOutcome(device, SDL_TRUE);
#undef CHECK_HRESULT
return 0;
failed:
if (srcreader) {
IMFSourceReader_Release(srcreader);
}
#if 0
if (handler) {
IMFMediaTypeHandler_Release(handler);
}
if (streamdesc) {
IMFStreamDescriptor_Release(streamdesc);
}
if (presentdesc) {
IMFPresentationDescriptor_Release(presentdesc);
}
#endif
if (source) {
IMFMediaSource_Shutdown(source);
IMFMediaSource_Release(source);
}
if (mediatype) {
IMFMediaType_Release(mediatype);
}
if (attrs) {
IMFAttributes_Release(attrs);
}
SDL_free(wstrsymlink);
return -1;
}
static void MEDIAFOUNDATION_FreeDeviceHandle(SDL_CameraDevice *device)
{
if (device) {
SDL_free(device->handle); // the device's symlink string.
}
}
static char *QueryActivationObjectString(IMFActivate *activation, const GUID *pguid)
{
LPWSTR wstr = NULL;
UINT32 wlen = 0;
HRESULT ret = IMFActivate_GetAllocatedString(activation, pguid, &wstr, &wlen);
if (FAILED(ret)) {
return NULL;
}
char *utf8str = WIN_StringToUTF8(wstr);
CoTaskMemFree(wstr);
return utf8str;
}
static void GatherCameraSpecs(IMFMediaSource *source, CameraFormatAddData *add_data)
{
HRESULT ret;
// this has like a thousand steps. :/
SDL_zerop(add_data);
IMFPresentationDescriptor *presentdesc = NULL;
ret = IMFMediaSource_CreatePresentationDescriptor(source, &presentdesc);
if (FAILED(ret) || !presentdesc) {
return;
}
DWORD num_streams = 0;
ret = IMFPresentationDescriptor_GetStreamDescriptorCount(presentdesc, &num_streams);
if (FAILED(ret)) {
num_streams = 0;
}
for (DWORD i = 0; i < num_streams; i++) {
IMFStreamDescriptor *streamdesc = NULL;
BOOL selected = FALSE;
ret = IMFPresentationDescriptor_GetStreamDescriptorByIndex(presentdesc, i, &selected, &streamdesc);
if (FAILED(ret) || !streamdesc) {
continue;
}
if (selected) {
IMFMediaTypeHandler *handler = NULL;
ret = IMFStreamDescriptor_GetMediaTypeHandler(streamdesc, &handler);
if (SUCCEEDED(ret) && handler) {
DWORD num_mediatype = 0;
ret = IMFMediaTypeHandler_GetMediaTypeCount(handler, &num_mediatype);
if (FAILED(ret)) {
num_mediatype = 0;
}
for (DWORD j = 0; j < num_mediatype; j++) {
IMFMediaType *mediatype = NULL;
ret = IMFMediaTypeHandler_GetMediaTypeByIndex(handler, j, &mediatype);
if (SUCCEEDED(ret) && mediatype) {
GUID type;
ret = IMFMediaType_GetGUID(mediatype, &SDL_MF_MT_MAJOR_TYPE, &type);
if (SUCCEEDED(ret) && WIN_IsEqualGUID(&type, &SDL_MFMediaType_Video)) {
ret = IMFMediaType_GetGUID(mediatype, &SDL_MF_MT_SUBTYPE, &type);
if (SUCCEEDED(ret)) {
const SDL_PixelFormatEnum sdlfmt = MFVidFmtGuidToSDLFmt(&type);
if (sdlfmt != SDL_PIXELFORMAT_UNKNOWN) {
UINT64 val = 0;
UINT32 w = 0, h = 0;
ret = IMFMediaType_GetUINT64(mediatype, &SDL_MF_MT_FRAME_SIZE, &val);
w = (UINT32)(val >> 32);
h = (UINT32)val;
if (SUCCEEDED(ret) && w && h) {
UINT32 interval_numerator = 0, interval_denominator = 0;
ret = IMFMediaType_GetUINT64(mediatype, &SDL_MF_MT_FRAME_RATE, &val);
interval_numerator = (UINT32)(val >> 32);
interval_denominator = (UINT32)val;
if (SUCCEEDED(ret) && interval_numerator && interval_denominator) {
SDL_AddCameraFormat(add_data, sdlfmt, (int) w, (int) h, (int) interval_numerator, (int) interval_denominator); // whew.
}
}
}
}
}
IMFMediaType_Release(mediatype);
}
}
IMFMediaTypeHandler_Release(handler);
}
}
IMFStreamDescriptor_Release(streamdesc);
}
IMFPresentationDescriptor_Release(presentdesc);
}
static SDL_bool FindMediaFoundationCameraDeviceBySymlink(SDL_CameraDevice *device, void *userdata)
{
return (SDL_strcmp((const char *) device->handle, (const char *) userdata) == 0);
}
static void MaybeAddDevice(IMFActivate *activation)
{
char *symlink = QueryActivationObjectString(activation, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK);
if (SDL_FindPhysicalCameraDeviceByCallback(FindMediaFoundationCameraDeviceBySymlink, symlink)) {
SDL_free(symlink);
return; // already have this one.
}
char *name = QueryActivationObjectString(activation, &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME);
if (name && symlink) {
IMFMediaSource *source = NULL;
// "activating" here only creates an object, it doesn't open the actual camera hardware or start recording.
HRESULT ret = IMFActivate_ActivateObject(activation, &SDL_IID_IMFMediaSource, (void**)&source);
if (SUCCEEDED(ret) && source) {
CameraFormatAddData add_data;
GatherCameraSpecs(source, &add_data);
if (add_data.num_specs > 0) {
SDL_AddCameraDevice(name, SDL_CAMERA_POSITION_UNKNOWN, add_data.num_specs, add_data.specs, symlink);
}
SDL_free(add_data.specs);
IMFActivate_ShutdownObject(activation);
IMFMediaSource_Release(source);
}
}
SDL_free(name);
}
static void MEDIAFOUNDATION_DetectDevices(void)
{
// !!! FIXME: use CM_Register_Notification (Win8+) to get device notifications.
// !!! FIXME: Earlier versions can use RegisterDeviceNotification, but I'm not bothering: no hotplug for you!
HRESULT ret;
IMFAttributes *attrs = NULL;
ret = pMFCreateAttributes(&attrs, 1);
if (FAILED(ret)) {
return; // oh well, no cameras for you.
}
// !!! FIXME: We need these GUIDs hardcoded in this file.
ret = IMFAttributes_SetGUID(attrs, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(ret)) {
IMFAttributes_Release(attrs);
return; // oh well, no cameras for you.
}
IMFActivate **activations = NULL;
UINT32 total = 0;
ret = pMFEnumDeviceSources(attrs, &activations, &total);
IMFAttributes_Release(attrs);
if (FAILED(ret)) {
return; // oh well, no cameras for you.
}
for (UINT32 i = 0; i < total; i++) {
MaybeAddDevice(activations[i]);
IMFActivate_Release(activations[i]);
}
CoTaskMemFree(activations);
}
static void MEDIAFOUNDATION_Deinitialize(void)
{
pMFShutdown();
FreeLibrary(libmfreadwrite);
libmfreadwrite = NULL;
FreeLibrary(libmfplat);
libmfplat = NULL;
FreeLibrary(libmf);
libmf = NULL;
pMFEnumDeviceSources = NULL;
pMFCreateDeviceSource = NULL;
pMFStartup = NULL;
pMFShutdown = NULL;
pMFCreateAttributes = NULL;
pMFCreateMediaType = NULL;
pMFCreateSourceReaderFromMediaSource = NULL;
pMFGetStrideForBitmapInfoHeader = NULL;
}
static SDL_bool MEDIAFOUNDATION_Init(SDL_CameraDriverImpl *impl)
{
// !!! FIXME: slide this off into a subroutine
HMODULE mf = LoadLibrary(TEXT("Mf.dll")); // this library is available in Vista and later, but also can be on XP with service packs and Windows
if (!mf) {
return SDL_FALSE;
}
HMODULE mfplat = LoadLibrary(TEXT("Mfplat.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
if (!mfplat) {
FreeLibrary(mf);
return SDL_FALSE;
}
HMODULE mfreadwrite = LoadLibrary(TEXT("Mfreadwrite.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
if (!mfreadwrite) {
FreeLibrary(mfplat);
FreeLibrary(mf);
return SDL_FALSE;
}
SDL_bool okay = SDL_TRUE;
#define LOADSYM(lib, fn) if (okay) { p##fn = (pfn##fn) GetProcAddress(lib, #fn); if (!p##fn) { okay = SDL_FALSE; } }
LOADSYM(mf, MFEnumDeviceSources);
LOADSYM(mf, MFCreateDeviceSource);
LOADSYM(mfplat, MFStartup);
LOADSYM(mfplat, MFShutdown);
LOADSYM(mfplat, MFCreateAttributes);
LOADSYM(mfplat, MFCreateMediaType);
LOADSYM(mfplat, MFGetStrideForBitmapInfoHeader);
LOADSYM(mfreadwrite, MFCreateSourceReaderFromMediaSource);
#undef LOADSYM
if (!okay) {
FreeLibrary(mfreadwrite);
FreeLibrary(mfplat);
FreeLibrary(mf);
}
libmf = mf;
libmfplat = mfplat;
libmfreadwrite = mfreadwrite;
const HRESULT ret = pMFStartup(MF_VERSION, MFSTARTUP_LITE);
if (FAILED(ret)) {
FreeLibrary(libmfplat);
libmfplat = NULL;
FreeLibrary(libmf);
libmf = NULL;
return SDL_FALSE;
}
impl->DetectDevices = MEDIAFOUNDATION_DetectDevices;
impl->OpenDevice = MEDIAFOUNDATION_OpenDevice;
impl->CloseDevice = MEDIAFOUNDATION_CloseDevice;
impl->WaitDevice = MEDIAFOUNDATION_WaitDevice;
impl->AcquireFrame = MEDIAFOUNDATION_AcquireFrame;
impl->ReleaseFrame = MEDIAFOUNDATION_ReleaseFrame;
impl->FreeDeviceHandle = MEDIAFOUNDATION_FreeDeviceHandle;
impl->Deinitialize = MEDIAFOUNDATION_Deinitialize;
return SDL_TRUE;
}
CameraBootStrap MEDIAFOUNDATION_bootstrap = {
"mediafoundation", "SDL Windows Media Foundation camera driver", MEDIAFOUNDATION_Init, SDL_FALSE
};
#endif // SDL_CAMERA_DRIVER_MEDIAFOUNDATION
+890
View File
@@ -0,0 +1,890 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 <stddef.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h> // low-level i/o
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.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 "../../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 int V4L2_WaitDevice(SDL_CameraDevice *device)
{
const int fd = device->hidden->fd;
int retval;
do {
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100 * 1000;
retval = select(fd + 1, &fds, NULL, NULL, &tv);
if ((retval == -1) && (errno == EINTR)) {
retval = 0; // pretend it was a timeout, keep looping.
}
} while (retval == 0);
return retval;
}
static int V4L2_AcquireFrame(SDL_CameraDevice *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;
switch (io) {
case IO_METHOD_READ:
if (read(fd, device->hidden->buffers[0].start, size) == -1) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
// Could ignore EIO, see spec.
// fall through
default:
return SDL_SetError("read");
}
}
*timestampNS = SDL_GetTicksNS(); // oh well, close enough.
frame->pixels = device->hidden->buffers[0].start;
frame->pitch = device->hidden->driver_pitch;
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 0;
case EIO:
// Could ignore EIO, see spec.
// fall through
default:
return SDL_SetError("VIDIOC_DQBUF: %d", errno);
}
}
if ((int)buf.index < 0 || (int)buf.index >= device->hidden->nb_buffers) {
return SDL_SetError("invalid buffer index");
}
frame->pixels = device->hidden->buffers[buf.index].start;
frame->pitch = device->hidden->driver_pitch;
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 0;
case EIO:
// Could ignore EIO, see spec.
// fall through
default:
return SDL_SetError("VIDIOC_DQBUF");
}
}
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) {
return SDL_SetError("invalid buffer index");
}
frame->pixels = (void*)buf.m.userptr;
frame->pitch = device->hidden->driver_pitch;
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 1;
}
static void V4L2_ReleaseFrame(SDL_CameraDevice *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 int EnqueueBuffers(SDL_CameraDevice *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 0;
}
static int AllocBufferRead(SDL_CameraDevice *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 ? 0 : -1;
}
static int AllocBufferMmap(SDL_CameraDevice *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 0;
}
static int AllocBufferUserPtr(SDL_CameraDevice *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 -1;
}
}
return 0;
}
static Uint32 format_v4l2_to_sdl(Uint32 fmt)
{
switch (fmt) {
#define CASE(x, y) case x: return y
CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2);
CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN);
#undef CASE
default:
#if DEBUG_CAMERA
SDL_Log("CAMERA: Unknown format V4L2_PIX_FORMAT '%d'", fmt);
#endif
return SDL_PIXELFORMAT_UNKNOWN;
}
}
static Uint32 format_sdl_to_v4l2(Uint32 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_UNKNOWN);
#undef CASE
default:
return 0;
}
}
static void V4L2_CloseDevice(SDL_CameraDevice *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 int V4L2_OpenDevice(SDL_CameraDevice *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 -1;
}
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->interval_numerator && spec->interval_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.numerator != spec->interval_numerator) ||
(setfps.parm.capture.timeperframe.denominator = spec->interval_denominator) ) {
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
setfps.parm.capture.timeperframe.numerator = spec->interval_numerator;
setfps.parm.capture.timeperframe.denominator = spec->interval_denominator;
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 -1;
}
size_t size, pitch;
SDL_CalculateSurfaceSize(device->spec.format, device->spec.width, device->spec.height, &size, &pitch, SDL_FALSE);
int rc = 0;
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 < 0) {
return -1;
} else if (EnqueueBuffers(device) < 0) {
return -1;
} 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_CameraDevicePermissionOutcome(device, SDL_TRUE);
return 0;
}
static SDL_bool FindV4L2CameraDeviceByBusInfoCallback(SDL_CameraDevice *device, void *userdata)
{
const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
return (SDL_strcmp(handle->bus_info, (const char *) userdata) == 0);
}
static int AddCameraFormat(const int fd, CameraFormatAddData *data, Uint32 sdlfmt, 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, w, h, numerator, denominator) == -1) {
return -1; // 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
if (SDL_AddCameraFormat(data, sdlfmt, w, h, n, d) == -1) {
return -1; // Probably out of memory; we'll go with what we have, if anything.
}
d += (int) frmivalenum.stepwise.step.denominator;
}
break;
}
}
return 0;
}
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_FindPhysicalCameraDeviceByCallback(FindV4L2CameraDeviceByBusInfoCallback, 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) {
const Uint32 sdlfmt = format_v4l2_to_sdl(fmtdesc.pixelformat);
#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, fmtdesc.pixelformat, w, h) == -1) {
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, fmtdesc.pixelformat, w, h) == -1) {
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_AddCameraDevice((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_CameraDevice *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 SDL_bool FindV4L2CameraDeviceByPathCallback(SDL_CameraDevice *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_CameraDeviceDisconnected(SDL_FindPhysicalCameraDeviceByCallback(FindV4L2CameraDeviceByPathCallback, (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() == 0) {
if (SDL_UDEV_AddCallback(CameraUdevCallback) == 0) {
SDL_UDEV_Scan(); // Force a scan to build the initial device list
}
}
#else
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);
}
#endif // SDL_USE_LIBUDEV
}
static SDL_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 SDL_TRUE;
}
CameraBootStrap V4L2_bootstrap = {
"v4l2", "SDL Video4Linux2 camera driver", V4L2_Init, SDL_FALSE
};
#endif // SDL_CAMERA_DRIVER_V4L2
+237
View File
@@ -0,0 +1,237 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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
DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata)
{
}
#endif
#ifndef SDL_PLATFORM_LINUX
DECLSPEC int SDLCALL SDL_LinuxSetThreadPriority(Sint64 threadID, int priority);
int SDL_LinuxSetThreadPriority(Sint64 threadID, int priority)
{
(void)threadID;
(void)priority;
return SDL_Unsupported();
}
DECLSPEC int SDLCALL SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy);
int SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy)
{
(void)threadID;
(void)sdlPriority;
(void)schedPolicy;
return SDL_Unsupported();
}
#endif
#ifndef SDL_PLATFORM_GDK
DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void);
void SDL_GDKSuspendComplete(void)
{
SDL_Unsupported();
}
DECLSPEC int SDLCALL SDL_GDKGetDefaultUser(void *outUserHandle); /* XUserHandle *outUserHandle */
int SDL_GDKGetDefaultUser(void *outUserHandle)
{
return SDL_Unsupported();
}
#endif
#if !(defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINRT) || defined(SDL_PLATFORM_GDK))
DECLSPEC int SDLCALL SDL_RegisterApp(const char *name, Uint32 style, void *hInst);
int SDL_RegisterApp(const char *name, Uint32 style, void *hInst)
{
(void)name;
(void)style;
(void)hInst;
return SDL_Unsupported();
}
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();
}
DECLSPEC void SDLCALL SDL_UnregisterApp(void);
void SDL_UnregisterApp(void)
{
SDL_Unsupported();
}
#endif
#ifndef SDL_PLATFORM_WINRT
/* Returns SDL_WinRT_DeviceFamily enum */
DECLSPEC int SDLCALL SDL_WinRTGetDeviceFamily(void);
int SDL_WinRTGetDeviceFamily()
{
SDL_Unsupported();
return 0; /* SDL_WINRT_DEVICEFAMILY_UNKNOWN */
}
DECLSPEC const wchar_t *SDLCALL SDL_WinRTGetFSPathUNICODE(int pathType); /* SDL_WinRT_Path pathType */
const wchar_t *SDL_WinRTGetFSPathUNICODE(int pathType)
{
(void)pathType;
SDL_Unsupported();
return NULL;
}
DECLSPEC const char *SDLCALL SDL_WinRTGetFSPathUTF8(int pathType); /* SDL_WinRT_Path pathType */
const char *SDL_WinRTGetFSPathUTF8(int pathType)
{
(void)pathType;
SDL_Unsupported();
return NULL;
}
#endif
#ifndef SDL_PLATFORM_ANDROID
DECLSPEC void SDLCALL SDL_AndroidBackButton(void);
void SDL_AndroidBackButton()
{
SDL_Unsupported();
}
DECLSPEC void *SDLCALL SDL_AndroidGetActivity(void);
void *SDL_AndroidGetActivity()
{
SDL_Unsupported();
return NULL;
}
DECLSPEC const char *SDLCALL SDL_AndroidGetExternalStoragePath(void);
const char* SDL_AndroidGetExternalStoragePath()
{
SDL_Unsupported();
return NULL;
}
DECLSPEC int SDLCALL SDL_AndroidGetExternalStorageState(Uint32 *state);
int SDL_AndroidGetExternalStorageState(Uint32 *state)
{
(void)state;
return SDL_Unsupported();
}
DECLSPEC const char *SDLCALL SDL_AndroidGetInternalStoragePath(void);
const char *SDL_AndroidGetInternalStoragePath()
{
SDL_Unsupported();
return NULL;
}
DECLSPEC void *SDLCALL SDL_AndroidGetJNIEnv(void);
void *SDL_AndroidGetJNIEnv()
{
SDL_Unsupported();
return NULL;
}
typedef void (SDLCALL *SDL_AndroidRequestPermissionCallback)(void *userdata, const char *permission, SDL_bool granted);
DECLSPEC int SDLCALL SDL_AndroidRequestPermission(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata);
int SDL_AndroidRequestPermission(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata)
{
(void)permission;
(void)cb;
(void)userdata;
return SDL_Unsupported();
}
DECLSPEC int SDLCALL SDL_AndroidSendMessage(Uint32 command, int param);
int SDL_AndroidSendMessage(Uint32 command, int param)
{
(void)command;
(void)param;
return SDL_Unsupported();
}
DECLSPEC int SDLCALL SDL_AndroidShowToast(const char* message, int duration, int gravity, int xoffset, int yoffset);
int SDL_AndroidShowToast(const char* message, int duration, int gravity, int xoffset, int yoffset)
{
(void)message;
(void)duration;
(void)gravity;
(void)xoffset;
(void)yoffset;
return SDL_Unsupported();
}
DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void);
int SDL_GetAndroidSDKVersion()
{
return SDL_Unsupported();
}
DECLSPEC SDL_bool SDLCALL SDL_IsAndroidTV(void);
SDL_bool SDL_IsAndroidTV()
{
SDL_Unsupported();
return SDL_FALSE;
}
DECLSPEC SDL_bool SDLCALL SDL_IsChromebook(void);
SDL_bool SDL_IsChromebook()
{
SDL_Unsupported();
return SDL_FALSE;
}
DECLSPEC SDL_bool SDLCALL SDL_IsDeXMode(void);
SDL_bool SDL_IsDeXMode(void)
{
SDL_Unsupported();
return SDL_FALSE;
}
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
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
char *SDL_GetUserFolder(SDL_Folder folder)
{
(void)folder;
SDL_Unsupported();
return NULL;
}
#endif
+44
View File
@@ -0,0 +1,44 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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"
/* Most platforms that use/need SDL_main have their own SDL_RunApp() implementation.
* If not, you can special case it here by appending || defined(__YOUR_PLATFORM__) */
#if ( !defined(SDL_MAIN_NEEDED) && !defined(SDL_MAIN_AVAILABLE) ) || defined(SDL_PLATFORM_ANDROID)
DECLSPEC int
SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved)
{
(void)reserved;
if(!argv)
{
/* make sure argv isn't NULL, in case some user code doesn't like that */
static char dummyargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' };
static char* argvdummy[2] = { dummyargv0, NULL };
argc = 1;
argv = argvdummy;
}
return mainFunction(argc, argv);
}
#endif
File diff suppressed because it is too large Load Diff
+149
View File
@@ -0,0 +1,149 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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"
/* 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
/* Interface from the SDL library into the Android Java activity */
extern void Android_JNI_SetActivityTitle(const char *title);
extern void Android_JNI_SetWindowStyle(SDL_bool fullscreen);
extern void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint);
extern void Android_JNI_MinizeWindow(void);
extern SDL_bool Android_JNI_ShouldMinimizeOnFocusLoss(void);
extern SDL_bool Android_JNI_GetAccelerometerValues(float values[3]);
extern void Android_JNI_ShowScreenKeyboard(SDL_Rect *inputRect);
extern void Android_JNI_HideScreenKeyboard(void);
extern SDL_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_output, SDL_AudioDevice **default_capture);
void Android_StopAudioHotplug(void);
extern void Android_AudioThreadInit(SDL_AudioDevice *device);
extern int Android_JNI_OpenAudioDevice(SDL_AudioDevice *device);
extern void *Android_JNI_GetAudioBuffer(void);
extern void Android_JNI_WriteAudioBuffer(void);
extern int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen);
extern void Android_JNI_FlushCapturedAudio(void);
extern void Android_JNI_CloseAudioDevice(const int iscapture);
/* Detecting device type */
extern SDL_bool Android_IsDeXMode(void);
extern SDL_bool Android_IsChromebook(void);
int 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, int 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);
int Android_JNI_FileClose(void *userdata);
/* Environment support */
void Android_JNI_GetManifestEnvironmentVariables(void);
/* Clipboard support */
int Android_JNI_SetClipboardText(const char *text);
char *Android_JNI_GetClipboardText(void);
SDL_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_HapticStop(int device_id);
/* Video */
int Android_JNI_SuspendScreenSaver(SDL_bool suspend);
/* Touch support */
void Android_JNI_InitTouch(void);
/* Threads */
#include <jni.h>
JNIEnv *Android_JNI_GetEnv(void);
int Android_JNI_SetupThread(void);
/* Locale */
int Android_JNI_GetLocale(char *buf, size_t buflen);
/* Generic messages */
int Android_JNI_SendMessage(int command, int param);
/* Init */
JNIEXPORT void JNICALL SDL_Android_Init(JNIEnv *mEnv, jclass cls);
/* MessageBox */
int 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);
SDL_bool Android_JNI_SetCustomCursor(int cursorID);
SDL_bool Android_JNI_SetSystemCursor(int cursorID);
/* Relative mouse support */
SDL_bool Android_JNI_SupportsRelativeMouse(void);
SDL_bool Android_JNI_SetRelativeMouseEnabled(SDL_bool enabled);
/* Show toast notification */
int Android_JNI_ShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset);
int Android_JNI_OpenURL(const char *url);
int SDL_GetAndroidSDKVersion(void);
SDL_bool SDL_IsAndroidTablet(void);
SDL_bool SDL_IsAndroidTV(void);
SDL_bool SDL_IsChromebook(void);
SDL_bool SDL_IsDeXMode(void);
void Android_ActivityMutex_Lock(void);
void Android_ActivityMutex_Unlock(void);
void Android_ActivityMutex_Lock_Running(void);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
/* *INDENT-OFF* */
}
/* *INDENT-ON* */
#endif
@@ -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-2024 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.. */
SDL_bool dead_key_next;
int npadch; /* -1 or number assembled on pad */
accentmap_t *accents;
unsigned int diacr;
SDL_bool rep; /* flag telling character repeat */
unsigned char lockstate;
unsigned char ledflagstate;
char shift_state;
char text[128];
unsigned int text_len;
};
static int 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()
{
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(sizeof(accentmap_t), 1);
kbd->key_map = SDL_calloc(sizeof(keymap_t), 1);
kbd->kbInfo = SDL_calloc(sizeof(keyboard_info_t), 1);
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;
}
/* Allow inhibiting keyboard mute with env. variable for debugging etc. */
if (SDL_getenv("SDL_INPUT_FREEBSD_KEEP_KBD") == NULL) {
/* 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, SDL_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, SDL_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 int 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 = SDL_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 */
+236
View File
@@ -0,0 +1,236 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 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 <shellapi.h> /* CommandLineToArgvW() */
#include <appnotify.h>
static XTaskQueueHandle GDK_GlobalTaskQueue;
PAPPSTATE_REGISTRATION hPLM = {};
HANDLE plmSuspendComplete = nullptr;
extern "C" DECLSPEC int
SDL_GDKGetTaskQueue(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 0;
}
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))
;
}
}
/* Pop up an out of memory message, returns to Windows */
extern "C" static BOOL
OutOfMemory(void)
{
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Out of memory - aborting", NULL);
return FALSE;
}
/* Gets the arguments with GetCommandLine, converts them to argc and argv
and calls SDL_main */
extern "C" DECLSPEC int
SDL_RunApp(int, char**, SDL_main_func mainFunction, void *reserved)
{
LPWSTR *argvw;
char **argv;
int i, argc, result;
HRESULT hr;
XTaskQueueHandle taskQueue;
argvw = CommandLineToArgvW(GetCommandLineW(), &argc);
if (argvw == NULL) {
return OutOfMemory();
}
/* Note that we need to be careful about how we allocate/free memory here.
* If the application calls SDL_SetMemoryFunctions(), we can't rely on
* SDL_free() to use the same allocator after SDL_main() returns.
*/
/* Parse it into argv and argc */
argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv));
if (argv == NULL) {
return OutOfMemory();
}
for (i = 0; i < argc; ++i) {
DWORD len;
char *arg = WIN_StringToUTF8W(argvw[i]);
if (arg == NULL) {
return OutOfMemory();
}
len = (DWORD)SDL_strlen(arg);
argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len + 1);
if (!argv[i]) {
return OutOfMemory();
}
SDL_memcpy(argv[i], arg, len);
SDL_free(arg);
}
argv[i] = NULL;
LocalFree(argvw);
hr = XGameRuntimeInitialize();
if (SUCCEEDED(hr) && SDL_GDKGetTaskQueue(&taskQueue) == 0) {
Uint32 titleid = 0;
char scidBuffer[64];
XblInitArgs xblArgs;
XTaskQueueSetCurrentProcessTaskQueue(taskQueue);
/* Try to get the title ID and initialize Xbox Live */
hr = XGameGetXboxTitleId(&titleid);
if (SUCCEEDED(hr)) {
SDL_zero(xblArgs);
xblArgs.queue = taskQueue;
SDL_snprintf(scidBuffer, 64, "00000000-0000-0000-0000-0000%08X", titleid);
xblArgs.scid = scidBuffer;
hr = XblInitialize(&xblArgs);
} else {
SDL_SetError("[GDK] Unable to get titleid. Will not call XblInitialize. Check MicrosoftGame.config!");
}
SDL_SetMainReady();
/* Register suspend/resume handling */
plmSuspendComplete = CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
if (!plmSuspendComplete) {
SDL_SetError("[GDK] Unable to create plmSuspendComplete event");
return -1;
}
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)) {
SDL_SetError("[GDK] Unable to call RegisterAppStateChangeNotification");
return -1;
}
/* Run the application main() code */
result = mainFunction(argc, argv);
/* Unregister suspend/resume handling */
UnregisterAppStateChangeNotification(hPLM);
CloseHandle(plmSuspendComplete);
/* !!! FIXME: This follows the docs exactly, but for some reason still leaks handles on exit? */
/* Terminate the task queue and dispatch any pending tasks */
XTaskQueueTerminate(taskQueue, false, nullptr, nullptr);
while (XTaskQueueDispatch(taskQueue, XTaskQueuePort::Completion, 0))
;
XTaskQueueCloseHandle(taskQueue);
XGameRuntimeUninitialize();
} else {
#ifdef SDL_PLATFORM_WINGDK
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "[GDK] Could not initialize - aborting", NULL);
#else
SDL_assert_always(0 && "[GDK] Could not initialize - aborting");
#endif
result = -1;
}
/* Free argv, to avoid memory leak */
for (i = 0; i < argc; ++i) {
HeapFree(GetProcessHeap(), 0, argv[i]);
}
HeapFree(GetProcessHeap(), 0, argv);
return result;
}
extern "C" DECLSPEC void
SDL_GDKSuspendComplete()
{
if (plmSuspendComplete) {
SetEvent(plmSuspendComplete);
}
}
extern "C" DECLSPEC int
SDL_GDKGetDefaultUser(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 0;
}

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