Merge commit '411251c6242b04119edc41ce83f09f0714e2d32b' as 'external/SDL'
This commit is contained in:
Vendored
+914
@@ -0,0 +1,914 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
#include "SDL3/SDL_revision.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_WINDOWS)
|
||||
#include "core/windows/SDL_windows.h"
|
||||
#else
|
||||
#include <unistd.h> // _exit(), etc.
|
||||
#endif
|
||||
|
||||
// this checks for HAVE_DBUS_DBUS_H internally.
|
||||
#include "core/linux/SDL_dbus.h"
|
||||
|
||||
#ifdef SDL_PLATFORM_EMSCRIPTEN
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
// Initialization code for SDL
|
||||
|
||||
#include "SDL_assert_c.h"
|
||||
#include "SDL_hints_c.h"
|
||||
#include "SDL_log_c.h"
|
||||
#include "SDL_properties_c.h"
|
||||
#include "audio/SDL_sysaudio.h"
|
||||
#include "camera/SDL_camera_c.h"
|
||||
#include "cpuinfo/SDL_cpuinfo_c.h"
|
||||
#include "events/SDL_events_c.h"
|
||||
#include "haptic/SDL_haptic_c.h"
|
||||
#include "joystick/SDL_gamepad_c.h"
|
||||
#include "joystick/SDL_joystick_c.h"
|
||||
#include "render/SDL_sysrender.h"
|
||||
#include "sensor/SDL_sensor_c.h"
|
||||
#include "stdlib/SDL_getenv_c.h"
|
||||
#include "thread/SDL_thread_c.h"
|
||||
#include "tray/SDL_tray_utils.h"
|
||||
#include "video/SDL_pixels_c.h"
|
||||
#include "video/SDL_surface_c.h"
|
||||
#include "video/SDL_video_c.h"
|
||||
#include "filesystem/SDL_filesystem_c.h"
|
||||
#include "io/SDL_asyncio_c.h"
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
#include "core/android/SDL_android.h"
|
||||
#endif
|
||||
|
||||
#define SDL_ALL_SUBSYSTEM_FLAGS ~0U
|
||||
|
||||
// Initialization/Cleanup routines
|
||||
#include "timer/SDL_timer_c.h"
|
||||
#ifdef SDL_PLATFORM_WINDOWS
|
||||
extern bool SDL_HelperWindowCreate(void);
|
||||
extern void SDL_HelperWindowDestroy(void);
|
||||
#endif
|
||||
|
||||
#ifdef SDL_BUILD_MAJOR_VERSION
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MAJOR_VERSION,
|
||||
SDL_MAJOR_VERSION == SDL_BUILD_MAJOR_VERSION);
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MINOR_VERSION,
|
||||
SDL_MINOR_VERSION == SDL_BUILD_MINOR_VERSION);
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MICRO_VERSION,
|
||||
SDL_MICRO_VERSION == SDL_BUILD_MICRO_VERSION);
|
||||
#endif
|
||||
|
||||
// Limited by its encoding in SDL_VERSIONNUM
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_MAJOR_VERSION_min, SDL_MAJOR_VERSION >= 0);
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_MAJOR_VERSION_max, SDL_MAJOR_VERSION <= 10);
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_MINOR_VERSION_min, SDL_MINOR_VERSION >= 0);
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_MINOR_VERSION_max, SDL_MINOR_VERSION <= 999);
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_MICRO_VERSION_min, SDL_MICRO_VERSION >= 0);
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_MICRO_VERSION_max, SDL_MICRO_VERSION <= 999);
|
||||
|
||||
/* This is not declared in any header, although it is shared between some
|
||||
parts of SDL, because we don't want anything calling it without an
|
||||
extremely good reason. */
|
||||
extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
|
||||
SDL_NORETURN void SDL_ExitProcess(int exitcode)
|
||||
{
|
||||
#if defined(SDL_PLATFORM_WINDOWS)
|
||||
/* "if you do not know the state of all threads in your process, it is
|
||||
better to call TerminateProcess than ExitProcess"
|
||||
https://msdn.microsoft.com/en-us/library/windows/desktop/ms682658(v=vs.85).aspx */
|
||||
TerminateProcess(GetCurrentProcess(), exitcode);
|
||||
/* MingW doesn't have TerminateProcess marked as noreturn, so add an
|
||||
ExitProcess here that will never be reached but make MingW happy. */
|
||||
ExitProcess(exitcode);
|
||||
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
|
||||
emscripten_cancel_main_loop(); // this should "kill" the app.
|
||||
emscripten_force_exit(exitcode); // this should "kill" the app.
|
||||
exit(exitcode);
|
||||
#elif defined(SDL_PLATFORM_HAIKU) // Haiku has _Exit, but it's not marked noreturn.
|
||||
_exit(exitcode);
|
||||
#elif defined(HAVE__EXIT) // Upper case _Exit()
|
||||
_Exit(exitcode);
|
||||
#else
|
||||
_exit(exitcode);
|
||||
#endif
|
||||
}
|
||||
|
||||
// App metadata
|
||||
|
||||
bool SDL_SetAppMetadata(const char *appname, const char *appversion, const char *appidentifier)
|
||||
{
|
||||
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING, appname);
|
||||
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING, appversion);
|
||||
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, appidentifier);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SDL_ValidMetadataProperty(const char *name)
|
||||
{
|
||||
if (!name || !*name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0 ||
|
||||
SDL_strcmp(name, SDL_PROP_APP_METADATA_VERSION_STRING) == 0 ||
|
||||
SDL_strcmp(name, SDL_PROP_APP_METADATA_IDENTIFIER_STRING) == 0 ||
|
||||
SDL_strcmp(name, SDL_PROP_APP_METADATA_CREATOR_STRING) == 0 ||
|
||||
SDL_strcmp(name, SDL_PROP_APP_METADATA_COPYRIGHT_STRING) == 0 ||
|
||||
SDL_strcmp(name, SDL_PROP_APP_METADATA_URL_STRING) == 0 ||
|
||||
SDL_strcmp(name, SDL_PROP_APP_METADATA_TYPE_STRING) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDL_SetAppMetadataProperty(const char *name, const char *value)
|
||||
{
|
||||
CHECK_PARAM(!SDL_ValidMetadataProperty(name)) {
|
||||
return SDL_InvalidParamError("name");
|
||||
}
|
||||
|
||||
return SDL_SetStringProperty(SDL_GetGlobalProperties(), name, value);
|
||||
}
|
||||
|
||||
const char *SDL_GetAppMetadataProperty(const char *name)
|
||||
{
|
||||
CHECK_PARAM(!SDL_ValidMetadataProperty(name)) {
|
||||
SDL_InvalidParamError("name");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *value = NULL;
|
||||
if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0) {
|
||||
value = SDL_GetHint(SDL_HINT_APP_NAME);
|
||||
} else if (SDL_strcmp(name, SDL_PROP_APP_METADATA_IDENTIFIER_STRING) == 0) {
|
||||
value = SDL_GetHint(SDL_HINT_APP_ID);
|
||||
}
|
||||
if (!value || !*value) {
|
||||
value = SDL_GetStringProperty(SDL_GetGlobalProperties(), name, NULL);
|
||||
}
|
||||
if (!value || !*value) {
|
||||
if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0) {
|
||||
value = "SDL Application";
|
||||
} else if (SDL_strcmp(name, SDL_PROP_APP_METADATA_TYPE_STRING) == 0) {
|
||||
value = "application";
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
// The initialized subsystems
|
||||
#ifdef SDL_MAIN_NEEDED
|
||||
static bool SDL_MainIsReady = false;
|
||||
#else
|
||||
static bool SDL_MainIsReady = true;
|
||||
#endif
|
||||
static SDL_ThreadID SDL_MainThreadID = 0;
|
||||
static SDL_ThreadID SDL_EventsThreadID = 0;
|
||||
static SDL_ThreadID SDL_VideoThreadID = 0;
|
||||
static bool SDL_bInMainQuit = false;
|
||||
static Uint8 SDL_SubsystemRefCount[32];
|
||||
|
||||
// Private helper to increment a subsystem's ref counter.
|
||||
static void SDL_IncrementSubsystemRefCount(Uint32 subsystem)
|
||||
{
|
||||
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
|
||||
SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255));
|
||||
if (subsystem_index >= 0) {
|
||||
++SDL_SubsystemRefCount[subsystem_index];
|
||||
}
|
||||
}
|
||||
|
||||
// Private helper to decrement a subsystem's ref counter.
|
||||
static void SDL_DecrementSubsystemRefCount(Uint32 subsystem)
|
||||
{
|
||||
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
|
||||
if ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] > 0)) {
|
||||
if (SDL_bInMainQuit) {
|
||||
SDL_SubsystemRefCount[subsystem_index] = 0;
|
||||
} else {
|
||||
--SDL_SubsystemRefCount[subsystem_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Private helper to check if a system needs init.
|
||||
static bool SDL_ShouldInitSubsystem(Uint32 subsystem)
|
||||
{
|
||||
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
|
||||
SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255));
|
||||
return ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 0));
|
||||
}
|
||||
|
||||
// Private helper to check if a system needs to be quit.
|
||||
static bool SDL_ShouldQuitSubsystem(Uint32 subsystem)
|
||||
{
|
||||
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
|
||||
if ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If we're in SDL_Quit, we shut down every subsystem, even if refcount
|
||||
* isn't zero.
|
||||
*/
|
||||
return (((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 1)) || SDL_bInMainQuit);
|
||||
}
|
||||
|
||||
#if !defined(SDL_VIDEO_DISABLED) || !defined(SDL_AUDIO_DISABLED) || !defined(SDL_JOYSTICK_DISABLED)
|
||||
/* Private helper to either increment's existing ref counter,
|
||||
* or fully init a new subsystem. */
|
||||
static bool SDL_InitOrIncrementSubsystem(Uint32 subsystem)
|
||||
{
|
||||
int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
|
||||
SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255));
|
||||
if (subsystem_index < 0) {
|
||||
return false;
|
||||
}
|
||||
if (SDL_SubsystemRefCount[subsystem_index] > 0) {
|
||||
++SDL_SubsystemRefCount[subsystem_index];
|
||||
return true;
|
||||
}
|
||||
return SDL_InitSubSystem(subsystem);
|
||||
}
|
||||
#endif // !SDL_VIDEO_DISABLED || !SDL_AUDIO_DISABLED || !SDL_JOYSTICK_DISABLED
|
||||
|
||||
void SDL_SetMainReady(void)
|
||||
{
|
||||
SDL_MainIsReady = true;
|
||||
|
||||
if (SDL_MainThreadID == 0) {
|
||||
SDL_MainThreadID = SDL_GetCurrentThreadID();
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL_IsMainThread(void)
|
||||
{
|
||||
if (SDL_VideoThreadID) {
|
||||
return (SDL_GetCurrentThreadID() == SDL_VideoThreadID);
|
||||
}
|
||||
if (SDL_EventsThreadID) {
|
||||
return (SDL_GetCurrentThreadID() == SDL_EventsThreadID);
|
||||
}
|
||||
if (SDL_MainThreadID) {
|
||||
return (SDL_GetCurrentThreadID() == SDL_MainThreadID);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SDL_IsVideoThread(void)
|
||||
{
|
||||
return (SDL_GetCurrentThreadID() == SDL_VideoThreadID);
|
||||
}
|
||||
|
||||
// Initialize all the subsystems that require initialization before threads start
|
||||
void SDL_InitMainThread(void)
|
||||
{
|
||||
static bool done_info = false;
|
||||
|
||||
// If we haven't done it by now, mark this as the main thread
|
||||
if (SDL_MainThreadID == 0) {
|
||||
SDL_MainThreadID = SDL_GetCurrentThreadID();
|
||||
}
|
||||
|
||||
SDL_InitTLSData();
|
||||
SDL_InitEnvironment();
|
||||
SDL_InitTicks();
|
||||
SDL_InitFilesystem();
|
||||
|
||||
if (!done_info) {
|
||||
const char *value;
|
||||
|
||||
value = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "App name: %s", value ? value : "<unspecified>");
|
||||
value = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "App version: %s", value ? value : "<unspecified>");
|
||||
value = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "App ID: %s", value ? value : "<unspecified>");
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "SDL revision: %s", SDL_REVISION);
|
||||
|
||||
done_info = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_QuitMainThread(void)
|
||||
{
|
||||
SDL_QuitFilesystem();
|
||||
SDL_QuitTicks();
|
||||
SDL_QuitEnvironment();
|
||||
SDL_QuitTLSData();
|
||||
}
|
||||
|
||||
bool SDL_InitSubSystem(SDL_InitFlags flags)
|
||||
{
|
||||
Uint32 flags_initialized = 0;
|
||||
|
||||
if (!SDL_MainIsReady) {
|
||||
return SDL_SetError("Application didn't initialize properly, did you include SDL_main.h in the file containing your main() function?");
|
||||
}
|
||||
|
||||
#ifdef SDL_PLATFORM_EMSCRIPTEN
|
||||
MAIN_THREAD_EM_ASM({
|
||||
// make sure this generic table to hang SDL-specific Javascript stuff is available at init time.
|
||||
if (typeof(Module['SDL3']) === 'undefined') {
|
||||
Module['SDL3'] = {};
|
||||
}
|
||||
|
||||
var SDL3 = Module['SDL3'];
|
||||
#if defined(__wasm32__)
|
||||
if (typeof(SDL3.JSVarToCPtr) === 'undefined') { SDL3.JSVarToCPtr = function(v) { return v; }; }
|
||||
if (typeof(SDL3.CPtrToHeap32Index) === 'undefined') { SDL3.CPtrToHeap32Index = function(ptr) { return ptr >>> 2; }; }
|
||||
#elif defined(__wasm64__)
|
||||
if (typeof(SDL3.JSVarToCPtr) === 'undefined') { SDL3.JSVarToCPtr = function(v) { return BigInt(v); }; }
|
||||
if (typeof(SDL3.CPtrToHeap32Index) === 'undefined') { SDL3.CPtrToHeap32Index = function(ptr) { return Number(ptr / 4n); }; }
|
||||
#else
|
||||
#error Please define your platform.
|
||||
#endif
|
||||
});
|
||||
#endif
|
||||
|
||||
SDL_InitMainThread();
|
||||
|
||||
#ifdef SDL_USE_LIBDBUS
|
||||
SDL_DBus_Init();
|
||||
#endif
|
||||
|
||||
#ifdef SDL_PLATFORM_WINDOWS
|
||||
if (flags & (SDL_INIT_HAPTIC | SDL_INIT_JOYSTICK)) {
|
||||
if (!SDL_HelperWindowCreate()) {
|
||||
goto quit_and_error;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Initialize the event subsystem
|
||||
if (flags & SDL_INIT_EVENTS) {
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_EVENTS)) {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS);
|
||||
|
||||
// Note which thread initialized events
|
||||
// This is the thread which should be pumping events
|
||||
SDL_EventsThreadID = SDL_GetCurrentThreadID();
|
||||
|
||||
if (!SDL_InitEvents()) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS);
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_EVENTS;
|
||||
}
|
||||
|
||||
// Initialize the video subsystem
|
||||
if (flags & SDL_INIT_VIDEO) {
|
||||
#ifndef SDL_VIDEO_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_VIDEO)) {
|
||||
// video implies events
|
||||
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
|
||||
goto quit_and_error;
|
||||
}
|
||||
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO);
|
||||
|
||||
// We initialize video on the main thread
|
||||
// On Apple platforms this is a requirement.
|
||||
// On other platforms, this is the definition.
|
||||
SDL_VideoThreadID = SDL_GetCurrentThreadID();
|
||||
#ifdef SDL_PLATFORM_APPLE
|
||||
SDL_assert(SDL_VideoThreadID == SDL_MainThreadID);
|
||||
#endif
|
||||
|
||||
if (!SDL_VideoInit(NULL)) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);
|
||||
SDL_PushError();
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
SDL_PopError();
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_VIDEO;
|
||||
#else
|
||||
SDL_SetError("SDL not built with video support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Initialize the audio subsystem
|
||||
if (flags & SDL_INIT_AUDIO) {
|
||||
#ifndef SDL_AUDIO_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_AUDIO)) {
|
||||
// audio implies events
|
||||
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
|
||||
goto quit_and_error;
|
||||
}
|
||||
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_AUDIO);
|
||||
if (!SDL_InitAudio(NULL)) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO);
|
||||
SDL_PushError();
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
SDL_PopError();
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_AUDIO);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_AUDIO;
|
||||
#else
|
||||
SDL_SetError("SDL not built with audio support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Initialize the joystick subsystem
|
||||
if (flags & SDL_INIT_JOYSTICK) {
|
||||
#ifndef SDL_JOYSTICK_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_JOYSTICK)) {
|
||||
// joystick implies events
|
||||
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
|
||||
goto quit_and_error;
|
||||
}
|
||||
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_JOYSTICK);
|
||||
if (!SDL_InitJoysticks()) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK);
|
||||
SDL_PushError();
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
SDL_PopError();
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_JOYSTICK;
|
||||
#else
|
||||
SDL_SetError("SDL not built with joystick support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (flags & SDL_INIT_GAMEPAD) {
|
||||
#ifndef SDL_JOYSTICK_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_GAMEPAD)) {
|
||||
// game controller implies joystick
|
||||
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_JOYSTICK)) {
|
||||
goto quit_and_error;
|
||||
}
|
||||
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_GAMEPAD);
|
||||
if (!SDL_InitGamepads()) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD);
|
||||
SDL_PushError();
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
SDL_PopError();
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_GAMEPAD);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_GAMEPAD;
|
||||
#else
|
||||
SDL_SetError("SDL not built with joystick support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Initialize the haptic subsystem
|
||||
if (flags & SDL_INIT_HAPTIC) {
|
||||
#ifndef SDL_HAPTIC_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_HAPTIC)) {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_HAPTIC);
|
||||
if (!SDL_InitHaptics()) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_HAPTIC);
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_HAPTIC);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_HAPTIC;
|
||||
#else
|
||||
SDL_SetError("SDL not built with haptic (force feedback) support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Initialize the sensor subsystem
|
||||
if (flags & SDL_INIT_SENSOR) {
|
||||
#ifndef SDL_SENSOR_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_SENSOR)) {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_SENSOR);
|
||||
if (!SDL_InitSensors()) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_SENSOR);
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_SENSOR);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_SENSOR;
|
||||
#else
|
||||
SDL_SetError("SDL not built with sensor support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Initialize the camera subsystem
|
||||
if (flags & SDL_INIT_CAMERA) {
|
||||
#ifndef SDL_CAMERA_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_CAMERA)) {
|
||||
// camera implies events
|
||||
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
|
||||
goto quit_and_error;
|
||||
}
|
||||
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA);
|
||||
if (!SDL_CameraInit(NULL)) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
|
||||
SDL_PushError();
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
SDL_PopError();
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_CAMERA;
|
||||
#else
|
||||
SDL_SetError("SDL not built with camera support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
(void)flags_initialized; // make static analysis happy, since this only gets used in error cases.
|
||||
|
||||
return SDL_ClearError();
|
||||
|
||||
quit_and_error:
|
||||
{
|
||||
SDL_PushError();
|
||||
SDL_QuitSubSystem(flags_initialized);
|
||||
SDL_PopError();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDL_Init(SDL_InitFlags flags)
|
||||
{
|
||||
return SDL_InitSubSystem(flags);
|
||||
}
|
||||
|
||||
void SDL_QuitSubSystem(SDL_InitFlags flags)
|
||||
{
|
||||
// Shut down requested initialized subsystems
|
||||
|
||||
#ifndef SDL_CAMERA_DISABLED
|
||||
if (flags & SDL_INIT_CAMERA) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_CAMERA)) {
|
||||
SDL_QuitCamera();
|
||||
// camera implies events
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SDL_SENSOR_DISABLED
|
||||
if (flags & SDL_INIT_SENSOR) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_SENSOR)) {
|
||||
SDL_QuitSensors();
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_SENSOR);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SDL_JOYSTICK_DISABLED
|
||||
if (flags & SDL_INIT_GAMEPAD) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_GAMEPAD)) {
|
||||
SDL_QuitGamepads();
|
||||
// game controller implies joystick
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD);
|
||||
}
|
||||
|
||||
if (flags & SDL_INIT_JOYSTICK) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_JOYSTICK)) {
|
||||
SDL_QuitJoysticks();
|
||||
// joystick implies events
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HAPTIC_DISABLED
|
||||
if (flags & SDL_INIT_HAPTIC) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_HAPTIC)) {
|
||||
SDL_QuitHaptics();
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_HAPTIC);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SDL_AUDIO_DISABLED
|
||||
if (flags & SDL_INIT_AUDIO) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_AUDIO)) {
|
||||
SDL_QuitAudio();
|
||||
// audio implies events
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SDL_VIDEO_DISABLED
|
||||
if (flags & SDL_INIT_VIDEO) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_VIDEO)) {
|
||||
SDL_QuitRender();
|
||||
SDL_VideoQuit();
|
||||
SDL_VideoThreadID = 0;
|
||||
// video implies events
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (flags & SDL_INIT_EVENTS) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_EVENTS)) {
|
||||
SDL_QuitEvents();
|
||||
SDL_EventsThreadID = 0;
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS);
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 SDL_WasInit(SDL_InitFlags flags)
|
||||
{
|
||||
int i;
|
||||
int num_subsystems = SDL_arraysize(SDL_SubsystemRefCount);
|
||||
Uint32 initialized = 0;
|
||||
|
||||
// Fast path for checking one flag
|
||||
if (SDL_HasExactlyOneBitSet32(flags)) {
|
||||
int subsystem_index = SDL_MostSignificantBitIndex32(flags);
|
||||
return SDL_SubsystemRefCount[subsystem_index] ? flags : 0;
|
||||
}
|
||||
|
||||
if (!flags) {
|
||||
flags = SDL_ALL_SUBSYSTEM_FLAGS;
|
||||
}
|
||||
|
||||
num_subsystems = SDL_min(num_subsystems, SDL_MostSignificantBitIndex32(flags) + 1);
|
||||
|
||||
// Iterate over each bit in flags, and check the matching subsystem.
|
||||
for (i = 0; i < num_subsystems; ++i) {
|
||||
if ((flags & 1) && SDL_SubsystemRefCount[i] > 0) {
|
||||
initialized |= (1 << i);
|
||||
}
|
||||
|
||||
flags >>= 1;
|
||||
}
|
||||
|
||||
return initialized;
|
||||
}
|
||||
|
||||
void SDL_Quit(void)
|
||||
{
|
||||
SDL_bInMainQuit = true;
|
||||
|
||||
// Quit all subsystems
|
||||
#ifdef SDL_PLATFORM_WINDOWS
|
||||
SDL_HelperWindowDestroy();
|
||||
#endif
|
||||
SDL_QuitSubSystem(SDL_ALL_SUBSYSTEM_FLAGS);
|
||||
SDL_CleanupTrays();
|
||||
|
||||
#ifdef SDL_USE_LIBDBUS
|
||||
SDL_DBus_Quit();
|
||||
#endif
|
||||
|
||||
SDL_QuitTimers();
|
||||
SDL_QuitAsyncIO();
|
||||
|
||||
SDL_SetObjectsInvalid();
|
||||
SDL_AssertionsQuit();
|
||||
|
||||
SDL_QuitPixelFormatDetails();
|
||||
|
||||
SDL_QuitCPUInfo();
|
||||
|
||||
/* Now that every subsystem has been quit, we reset the subsystem refcount
|
||||
* and the list of initialized subsystems.
|
||||
*/
|
||||
SDL_zeroa(SDL_SubsystemRefCount);
|
||||
|
||||
SDL_QuitLog();
|
||||
SDL_QuitHints();
|
||||
SDL_QuitProperties();
|
||||
|
||||
SDL_QuitMainThread();
|
||||
|
||||
SDL_bInMainQuit = false;
|
||||
}
|
||||
|
||||
// Get the library version number
|
||||
int SDL_GetVersion(void)
|
||||
{
|
||||
return SDL_VERSION;
|
||||
}
|
||||
|
||||
// Get the library source revision
|
||||
const char *SDL_GetRevision(void)
|
||||
{
|
||||
return SDL_REVISION;
|
||||
}
|
||||
|
||||
// Get the name of the platform
|
||||
const char *SDL_GetPlatform(void)
|
||||
{
|
||||
#if defined(SDL_PLATFORM_PRIVATE)
|
||||
return SDL_PLATFORM_PRIVATE_NAME;
|
||||
#elif defined(SDL_PLATFORM_AIX)
|
||||
return "AIX";
|
||||
#elif defined(SDL_PLATFORM_ANDROID)
|
||||
return "Android";
|
||||
#elif defined(SDL_PLATFORM_BSDI)
|
||||
return "BSDI";
|
||||
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
|
||||
return "Emscripten";
|
||||
#elif defined(SDL_PLATFORM_FREEBSD)
|
||||
return "FreeBSD";
|
||||
#elif defined(SDL_PLATFORM_HAIKU)
|
||||
return "Haiku";
|
||||
#elif defined(SDL_PLATFORM_HPUX)
|
||||
return "HP-UX";
|
||||
#elif defined(SDL_PLATFORM_IRIX)
|
||||
return "Irix";
|
||||
#elif defined(SDL_PLATFORM_LINUX)
|
||||
return "Linux";
|
||||
#elif defined(__MINT__)
|
||||
return "Atari MiNT";
|
||||
#elif defined(SDL_PLATFORM_MSDOS)
|
||||
return "MS-DOS";
|
||||
#elif defined(SDL_PLATFORM_MACOS)
|
||||
return "macOS";
|
||||
#elif defined(SDL_PLATFORM_NETBSD)
|
||||
return "NetBSD";
|
||||
#elif defined(SDL_PLATFORM_NGAGE)
|
||||
return "Nokia N-Gage";
|
||||
#elif defined(SDL_PLATFORM_OPENBSD)
|
||||
return "OpenBSD";
|
||||
#elif defined(SDL_PLATFORM_OS2)
|
||||
return "OS/2";
|
||||
#elif defined(SDL_PLATFORM_OSF)
|
||||
return "OSF/1";
|
||||
#elif defined(SDL_PLATFORM_QNXNTO)
|
||||
return "QNX Neutrino";
|
||||
#elif defined(SDL_PLATFORM_RISCOS)
|
||||
return "RISC OS";
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
return "Solaris";
|
||||
#elif defined(SDL_PLATFORM_WIN32)
|
||||
return "Windows";
|
||||
#elif defined(SDL_PLATFORM_WINGDK)
|
||||
return "WinGDK";
|
||||
#elif defined(SDL_PLATFORM_XBOXONE)
|
||||
return "Xbox One";
|
||||
#elif defined(SDL_PLATFORM_XBOXSERIES)
|
||||
return "Xbox Series X|S";
|
||||
#elif defined(SDL_PLATFORM_VISIONOS)
|
||||
return "visionOS";
|
||||
#elif defined(SDL_PLATFORM_IOS)
|
||||
return "iOS";
|
||||
#elif defined(SDL_PLATFORM_TVOS)
|
||||
return "tvOS";
|
||||
#elif defined(SDL_PLATFORM_PS2)
|
||||
return "PlayStation 2";
|
||||
#elif defined(SDL_PLATFORM_PSP)
|
||||
return "PlayStation Portable";
|
||||
#elif defined(SDL_PLATFORM_VITA)
|
||||
return "PlayStation Vita";
|
||||
#elif defined(SDL_PLATFORM_3DS)
|
||||
return "Nintendo 3DS";
|
||||
#elif defined(SDL_PLATFORM_HURD)
|
||||
return "GNU/Hurd";
|
||||
#elif defined(__managarm__)
|
||||
return "Managarm";
|
||||
#else
|
||||
return "Unknown (see SDL_platform.h)";
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SDL_IsPhone(void)
|
||||
{
|
||||
#if defined(SDL_PLATFORM_ANDROID) || \
|
||||
(defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_VISIONOS))
|
||||
if (!SDL_IsTablet() && !SDL_IsTV()) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDL_IsTablet(void)
|
||||
{
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
return SDL_IsAndroidTablet();
|
||||
#elif defined(SDL_PLATFORM_IOS)
|
||||
extern bool SDL_IsIPad(void);
|
||||
return SDL_IsIPad();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SDL_IsTV(void)
|
||||
{
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
return SDL_IsAndroidTV();
|
||||
#elif defined(SDL_PLATFORM_IOS)
|
||||
extern bool SDL_IsAppleTV(void);
|
||||
return SDL_IsAppleTV();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static SDL_Sandbox SDL_DetectSandbox(void)
|
||||
{
|
||||
#if defined(SDL_PLATFORM_LINUX)
|
||||
if (access("/.flatpak-info", F_OK) == 0) {
|
||||
return SDL_SANDBOX_FLATPAK;
|
||||
}
|
||||
|
||||
/* For Snap, we check multiple variables because they might be set for
|
||||
* unrelated reasons. This is the same thing WebKitGTK does. */
|
||||
if (SDL_getenv("SNAP") && SDL_getenv("SNAP_NAME") && SDL_getenv("SNAP_REVISION")) {
|
||||
return SDL_SANDBOX_SNAP;
|
||||
}
|
||||
|
||||
if (access("/run/host/container-manager", F_OK) == 0) {
|
||||
return SDL_SANDBOX_UNKNOWN_CONTAINER;
|
||||
}
|
||||
|
||||
#elif defined(SDL_PLATFORM_MACOS)
|
||||
if (SDL_getenv("APP_SANDBOX_CONTAINER_ID")) {
|
||||
return SDL_SANDBOX_MACOS;
|
||||
}
|
||||
#endif
|
||||
|
||||
return SDL_SANDBOX_NONE;
|
||||
}
|
||||
|
||||
SDL_Sandbox SDL_GetSandbox(void)
|
||||
{
|
||||
static SDL_Sandbox sandbox;
|
||||
static bool sandbox_initialized;
|
||||
|
||||
if (!sandbox_initialized) {
|
||||
sandbox = SDL_DetectSandbox();
|
||||
sandbox_initialized = true;
|
||||
}
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
#ifdef SDL_PLATFORM_WIN32
|
||||
|
||||
#if !defined(HAVE_LIBC) && !defined(SDL_STATIC_LIB)
|
||||
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)
|
||||
Vendored
+441
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_WINDOWS)
|
||||
#include "core/windows/SDL_windows.h"
|
||||
#endif
|
||||
|
||||
#include "SDL_assert_c.h"
|
||||
#include "video/SDL_sysvideo.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_WINDOWS)
|
||||
#ifndef WS_OVERLAPPEDWINDOW
|
||||
#define WS_OVERLAPPEDWINDOW 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef SDL_PLATFORM_EMSCRIPTEN
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
// The size of the stack buffer to use for rendering assert messages.
|
||||
#define SDL_MAX_ASSERT_MESSAGE_STACK 256
|
||||
|
||||
static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata);
|
||||
|
||||
/*
|
||||
* We keep all triggered assertions in a singly-linked list so we can
|
||||
* generate a report later.
|
||||
*/
|
||||
static SDL_AssertData *triggered_assertions = NULL;
|
||||
|
||||
#ifndef SDL_THREADS_DISABLED
|
||||
static SDL_Mutex *assertion_mutex = NULL;
|
||||
#endif
|
||||
|
||||
static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion;
|
||||
static void *assertion_userdata = NULL;
|
||||
|
||||
#ifdef __GNUC__
|
||||
static void debug_print(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
#endif
|
||||
|
||||
static void debug_print(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_WARN, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static void SDL_AddAssertionToReport(SDL_AssertData *data)
|
||||
{
|
||||
/* (data) is always a static struct defined with the assert macros, so
|
||||
we don't have to worry about copying or allocating them. */
|
||||
data->trigger_count++;
|
||||
if (data->trigger_count == 1) { // not yet added?
|
||||
data->next = triggered_assertions;
|
||||
triggered_assertions = data;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(SDL_PLATFORM_WINDOWS)
|
||||
#define ENDLINE "\r\n"
|
||||
#else
|
||||
#define ENDLINE "\n"
|
||||
#endif
|
||||
|
||||
static int SDL_RenderAssertMessage(char *buf, size_t buf_len, const SDL_AssertData *data)
|
||||
{
|
||||
return SDL_snprintf(buf, buf_len,
|
||||
"Assertion failure at %s (%s:%d), triggered %u %s:" ENDLINE " '%s'",
|
||||
data->function, data->filename, data->linenum,
|
||||
data->trigger_count, (data->trigger_count == 1) ? "time" : "times",
|
||||
data->condition);
|
||||
}
|
||||
|
||||
static void SDL_GenerateAssertionReport(void)
|
||||
{
|
||||
const SDL_AssertData *item = triggered_assertions;
|
||||
|
||||
// only do this if the app hasn't assigned an assertion handler.
|
||||
if ((item) && (assertion_handler != SDL_PromptAssertion)) {
|
||||
debug_print("\n\nSDL assertion report.\n");
|
||||
debug_print("All SDL assertions between last init/quit:\n\n");
|
||||
|
||||
while (item) {
|
||||
debug_print(
|
||||
"'%s'\n"
|
||||
" * %s (%s:%d)\n"
|
||||
" * triggered %u time%s.\n"
|
||||
" * always ignore: %s.\n",
|
||||
item->condition, item->function, item->filename,
|
||||
item->linenum, item->trigger_count,
|
||||
(item->trigger_count == 1) ? "" : "s",
|
||||
item->always_ignore ? "yes" : "no");
|
||||
item = item->next;
|
||||
}
|
||||
debug_print("\n");
|
||||
|
||||
SDL_ResetAssertionReport();
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_NORETURN void SDL_AbortAssertion(void)
|
||||
{
|
||||
SDL_Quit();
|
||||
SDL_abort();
|
||||
}
|
||||
|
||||
static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata)
|
||||
{
|
||||
SDL_AssertState state = SDL_ASSERTION_ABORT;
|
||||
SDL_Window *window;
|
||||
SDL_MessageBoxData messagebox;
|
||||
SDL_MessageBoxButtonData buttons[] = {
|
||||
{ 0, SDL_ASSERTION_RETRY, "Retry" },
|
||||
{ 0, SDL_ASSERTION_BREAK, "Break" },
|
||||
{ 0, SDL_ASSERTION_ABORT, "Abort" },
|
||||
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
|
||||
SDL_ASSERTION_IGNORE, "Ignore" },
|
||||
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
|
||||
SDL_ASSERTION_ALWAYS_IGNORE, "Always Ignore" }
|
||||
};
|
||||
int selected;
|
||||
|
||||
char stack_buf[SDL_MAX_ASSERT_MESSAGE_STACK];
|
||||
char *message = stack_buf;
|
||||
size_t buf_len = sizeof(stack_buf);
|
||||
int len;
|
||||
|
||||
(void)userdata; // unused in default handler.
|
||||
|
||||
// Assume the output will fit...
|
||||
len = SDL_RenderAssertMessage(message, buf_len, data);
|
||||
|
||||
// .. and if it didn't, try to allocate as much room as we actually need.
|
||||
if (len >= (int)buf_len) {
|
||||
if (SDL_size_add_check_overflow(len, 1, &buf_len)) {
|
||||
message = (char *)SDL_malloc(buf_len);
|
||||
if (message) {
|
||||
len = SDL_RenderAssertMessage(message, buf_len, data);
|
||||
} else {
|
||||
message = stack_buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Something went very wrong
|
||||
if (len < 0) {
|
||||
if (message != stack_buf) {
|
||||
SDL_free(message);
|
||||
}
|
||||
return SDL_ASSERTION_ABORT;
|
||||
}
|
||||
|
||||
debug_print("\n\n%s\n\n", message);
|
||||
|
||||
// let env. variable override, so unit tests won't block in a GUI.
|
||||
const char *hint = SDL_GetHint(SDL_HINT_ASSERT);
|
||||
if (hint) {
|
||||
if (message != stack_buf) {
|
||||
SDL_free(message);
|
||||
}
|
||||
|
||||
if (SDL_strcmp(hint, "abort") == 0) {
|
||||
return SDL_ASSERTION_ABORT;
|
||||
} else if (SDL_strcmp(hint, "break") == 0) {
|
||||
return SDL_ASSERTION_BREAK;
|
||||
} else if (SDL_strcmp(hint, "retry") == 0) {
|
||||
return SDL_ASSERTION_RETRY;
|
||||
} else if (SDL_strcmp(hint, "ignore") == 0) {
|
||||
return SDL_ASSERTION_IGNORE;
|
||||
} else if (SDL_strcmp(hint, "always_ignore") == 0) {
|
||||
return SDL_ASSERTION_ALWAYS_IGNORE;
|
||||
} else {
|
||||
return SDL_ASSERTION_ABORT; // oh well.
|
||||
}
|
||||
}
|
||||
|
||||
// Leave fullscreen mode, if possible (scary!)
|
||||
window = SDL_GetToplevelForKeyboardFocus();
|
||||
if (window) {
|
||||
if (window->fullscreen_exclusive) {
|
||||
SDL_MinimizeWindow(window);
|
||||
} else {
|
||||
// !!! FIXME: ungrab the input if we're not fullscreen?
|
||||
// No need to mess with the window
|
||||
window = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Show a messagebox if we can, otherwise fall back to stdio
|
||||
SDL_zero(messagebox);
|
||||
messagebox.flags = SDL_MESSAGEBOX_WARNING;
|
||||
messagebox.window = window;
|
||||
messagebox.title = "Assertion Failed";
|
||||
messagebox.message = message;
|
||||
messagebox.numbuttons = SDL_arraysize(buttons);
|
||||
messagebox.buttons = buttons;
|
||||
|
||||
if (SDL_ShowMessageBox(&messagebox, &selected)) {
|
||||
if (selected == -1) {
|
||||
state = SDL_ASSERTION_IGNORE;
|
||||
} else {
|
||||
state = (SDL_AssertState)selected;
|
||||
}
|
||||
} else {
|
||||
#ifdef SDL_PLATFORM_PRIVATE_ASSERT
|
||||
SDL_PRIVATE_PROMPTASSERTION();
|
||||
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
|
||||
// This is nasty, but we can't block on a custom UI.
|
||||
for (;;) {
|
||||
bool okay = true;
|
||||
/* *INDENT-OFF* */ // clang-format off
|
||||
int reply = MAIN_THREAD_EM_ASM_INT({
|
||||
var str =
|
||||
UTF8ToString($0) + '\n\n' +
|
||||
'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
|
||||
var reply = window.prompt(str, "i");
|
||||
if (reply === null) {
|
||||
reply = "i";
|
||||
}
|
||||
return reply.length === 1 ? reply.charCodeAt(0) : -1;
|
||||
}, message);
|
||||
/* *INDENT-ON* */ // clang-format on
|
||||
|
||||
switch (reply) {
|
||||
case 'a':
|
||||
state = SDL_ASSERTION_ABORT;
|
||||
break;
|
||||
#if 0 // (currently) no break functionality on Emscripten
|
||||
case 'b':
|
||||
state = SDL_ASSERTION_BREAK;
|
||||
break;
|
||||
#endif
|
||||
case 'r':
|
||||
state = SDL_ASSERTION_RETRY;
|
||||
break;
|
||||
case 'i':
|
||||
state = SDL_ASSERTION_IGNORE;
|
||||
break;
|
||||
case 'A':
|
||||
state = SDL_ASSERTION_ALWAYS_IGNORE;
|
||||
break;
|
||||
default:
|
||||
okay = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (okay) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#elif defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_3DS)
|
||||
// this is a little hacky.
|
||||
for (;;) {
|
||||
char buf[32];
|
||||
(void)fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : ");
|
||||
(void)fflush(stderr);
|
||||
if (fgets(buf, sizeof(buf), stdin) == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (SDL_strncmp(buf, "a", 1) == 0) {
|
||||
state = SDL_ASSERTION_ABORT;
|
||||
break;
|
||||
} else if (SDL_strncmp(buf, "b", 1) == 0) {
|
||||
state = SDL_ASSERTION_BREAK;
|
||||
break;
|
||||
} else if (SDL_strncmp(buf, "r", 1) == 0) {
|
||||
state = SDL_ASSERTION_RETRY;
|
||||
break;
|
||||
} else if (SDL_strncmp(buf, "i", 1) == 0) {
|
||||
state = SDL_ASSERTION_IGNORE;
|
||||
break;
|
||||
} else if (SDL_strncmp(buf, "A", 1) == 0) {
|
||||
state = SDL_ASSERTION_ALWAYS_IGNORE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Assertion Failed", message, window);
|
||||
#endif // SDL_PLATFORM_PRIVATE_ASSERT
|
||||
}
|
||||
|
||||
// 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_abort();
|
||||
} else {
|
||||
while (1) { // do nothing but spin; what else can you do?!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!data->always_ignore) {
|
||||
state = assertion_handler(data, assertion_userdata);
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case SDL_ASSERTION_ALWAYS_IGNORE:
|
||||
state = SDL_ASSERTION_IGNORE;
|
||||
data->always_ignore = true;
|
||||
break;
|
||||
|
||||
case SDL_ASSERTION_IGNORE:
|
||||
case SDL_ASSERTION_RETRY:
|
||||
case SDL_ASSERTION_BREAK:
|
||||
break; // macro handles these.
|
||||
|
||||
case SDL_ASSERTION_ABORT:
|
||||
SDL_AbortAssertion();
|
||||
// break; ...shouldn't return, but oh well.
|
||||
}
|
||||
|
||||
assertion_running--;
|
||||
|
||||
#ifndef SDL_THREADS_DISABLED
|
||||
SDL_UnlockMutex(assertion_mutex);
|
||||
#endif
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void SDL_AssertionsQuit(void)
|
||||
{
|
||||
#if SDL_ASSERT_LEVEL > 0
|
||||
SDL_GenerateAssertionReport();
|
||||
#ifndef SDL_THREADS_DISABLED
|
||||
if (assertion_mutex) {
|
||||
SDL_DestroyMutex(assertion_mutex);
|
||||
assertion_mutex = NULL;
|
||||
}
|
||||
#endif
|
||||
#endif // SDL_ASSERT_LEVEL > 0
|
||||
}
|
||||
|
||||
void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata)
|
||||
{
|
||||
if (handler != NULL) {
|
||||
assertion_handler = handler;
|
||||
assertion_userdata = userdata;
|
||||
} else {
|
||||
assertion_handler = SDL_PromptAssertion;
|
||||
assertion_userdata = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const SDL_AssertData *SDL_GetAssertionReport(void)
|
||||
{
|
||||
return triggered_assertions;
|
||||
}
|
||||
|
||||
void SDL_ResetAssertionReport(void)
|
||||
{
|
||||
SDL_AssertData *next = NULL;
|
||||
SDL_AssertData *item;
|
||||
for (item = triggered_assertions; item; item = next) {
|
||||
next = (SDL_AssertData *)item->next;
|
||||
item->always_ignore = false;
|
||||
item->trigger_count = 0;
|
||||
item->next = NULL;
|
||||
}
|
||||
|
||||
triggered_assertions = NULL;
|
||||
}
|
||||
|
||||
SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
|
||||
{
|
||||
return SDL_PromptAssertion;
|
||||
}
|
||||
|
||||
SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata)
|
||||
{
|
||||
if (userdata) {
|
||||
*userdata = assertion_userdata;
|
||||
}
|
||||
return assertion_handler;
|
||||
}
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_
|
||||
Vendored
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "stdlib/SDL_vacopy.h"
|
||||
|
||||
// Simple error handling in SDL
|
||||
|
||||
#include "SDL_error_c.h"
|
||||
|
||||
bool SDL_SetError(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
bool result;
|
||||
|
||||
va_start(ap, fmt);
|
||||
result = SDL_SetErrorV(fmt, ap);
|
||||
va_end(ap);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SDL_SetErrorV(SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)
|
||||
{
|
||||
// Ignore call if invalid format pointer was passed
|
||||
if (fmt) {
|
||||
int result;
|
||||
SDL_error *error = SDL_GetErrBuf(true);
|
||||
|
||||
// use the other slot for the new error, so if this does
|
||||
// SDL_SetError("%s", SDL_GetError()), we don't have a problem.
|
||||
const int current = error->current ? 0 : 1;
|
||||
SDL_ErrorInfo *errinfo = &error->info[current];
|
||||
error->current = current;
|
||||
|
||||
errinfo->error = SDL_ErrorCodeGeneric;
|
||||
|
||||
va_list ap2;
|
||||
va_copy(ap2, ap);
|
||||
result = SDL_vsnprintf(errinfo->str, errinfo->len, fmt, ap2);
|
||||
va_end(ap2);
|
||||
|
||||
if (result >= 0 && (size_t)result >= errinfo->len && error->realloc_func) {
|
||||
size_t len = (size_t)result + 1;
|
||||
char *str = (char *)error->realloc_func(errinfo->str, len);
|
||||
if (str) {
|
||||
errinfo->str = str;
|
||||
errinfo->len = len;
|
||||
va_copy(ap2, ap);
|
||||
(void)SDL_vsnprintf(errinfo->str, errinfo->len, fmt, ap2);
|
||||
va_end(ap2);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable this if you want to see all errors printed as they occur.
|
||||
// Note that there are many recoverable errors that may happen internally and
|
||||
// can be safely ignored if the public API doesn't return an error code.
|
||||
#if 0
|
||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s", errinfo->str);
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *SDL_GetError(void)
|
||||
{
|
||||
const SDL_error *error = SDL_GetErrBuf(false);
|
||||
|
||||
if (!error) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const SDL_ErrorInfo *errinfo = &error->info[error->current];
|
||||
switch (errinfo->error) {
|
||||
case SDL_ErrorCodeGeneric:
|
||||
return errinfo->str;
|
||||
case SDL_ErrorCodeOutOfMemory:
|
||||
return "Out of memory";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL_ClearError(void)
|
||||
{
|
||||
SDL_error *error = SDL_GetErrBuf(false);
|
||||
|
||||
if (error) {
|
||||
SDL_ErrorInfo *errinfo = &error->info[error->current];
|
||||
errinfo->error = SDL_ErrorCodeNone;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SDL_OutOfMemory(void)
|
||||
{
|
||||
SDL_error *error = SDL_GetErrBuf(true);
|
||||
|
||||
if (error) {
|
||||
SDL_ErrorInfo *errinfo = &error->info[error->current];
|
||||
errinfo->error = SDL_ErrorCodeOutOfMemory;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_ErrorInfo
|
||||
{
|
||||
SDL_ErrorCode error;
|
||||
char *str;
|
||||
size_t len;
|
||||
} SDL_ErrorInfo;
|
||||
|
||||
typedef struct SDL_error
|
||||
{
|
||||
SDL_ErrorInfo info[2]; // there are two, so you can do SDL_SetError("%s", SDL_GetError()) without stomping the buffer.
|
||||
int current;
|
||||
SDL_realloc_func realloc_func;
|
||||
SDL_free_func free_func;
|
||||
} SDL_error;
|
||||
|
||||
// Defined in SDL_thread.c
|
||||
extern SDL_error *SDL_GetErrBuf(bool create);
|
||||
|
||||
#endif // SDL_error_c_h_
|
||||
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// convert the guid to a printable string
|
||||
void SDL_GUIDToString(SDL_GUID guid, char *pszGUID, int cbGUID)
|
||||
{
|
||||
static const char k_rgchHexToASCII[] = "0123456789abcdef";
|
||||
int i;
|
||||
|
||||
if ((!pszGUID) || (cbGUID <= 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < sizeof(guid.data) && i < (cbGUID - 1) / 2; i++) {
|
||||
// each input byte writes 2 ascii chars, and might write a null byte.
|
||||
// If we don't have room for next input byte, stop
|
||||
unsigned char c = guid.data[i];
|
||||
|
||||
*pszGUID++ = k_rgchHexToASCII[c >> 4];
|
||||
*pszGUID++ = k_rgchHexToASCII[c & 0x0F];
|
||||
}
|
||||
*pszGUID = '\0';
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Purpose: Returns the 4 bit nibble for a hex character
|
||||
* Input : c -
|
||||
* Output : unsigned char
|
||||
*-----------------------------------------------------------------------------*/
|
||||
static unsigned char nibble(unsigned char c)
|
||||
{
|
||||
if ((c >= '0') && (c <= '9')) {
|
||||
return c - '0';
|
||||
}
|
||||
|
||||
if ((c >= 'A') && (c <= 'F')) {
|
||||
return c - 'A' + 0x0a;
|
||||
}
|
||||
|
||||
if ((c >= 'a') && (c <= 'f')) {
|
||||
return c - 'a' + 0x0a;
|
||||
}
|
||||
|
||||
// received an invalid character, and no real way to return an error
|
||||
// AssertMsg1(false, "Q_nibble invalid hex character '%c' ", c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// convert the string version of a guid to the struct
|
||||
SDL_GUID SDL_StringToGUID(const char *pchGUID)
|
||||
{
|
||||
SDL_GUID guid;
|
||||
int maxoutputbytes = sizeof(guid);
|
||||
size_t len = SDL_strlen(pchGUID);
|
||||
Uint8 *p;
|
||||
size_t i;
|
||||
|
||||
// Make sure it's even
|
||||
len = (len) & ~0x1;
|
||||
|
||||
SDL_zero(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;
|
||||
}
|
||||
Vendored
+544
@@ -0,0 +1,544 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
typedef struct SDL_HashItem
|
||||
{
|
||||
// TODO: Splitting off values into a separate array might be more cache-friendly
|
||||
const void *key;
|
||||
const void *value;
|
||||
Uint32 hash;
|
||||
Uint32 probe_len : 31;
|
||||
Uint32 live : 1;
|
||||
} SDL_HashItem;
|
||||
|
||||
// Must be a power of 2 >= sizeof(SDL_HashItem)
|
||||
#define MAX_HASHITEM_SIZEOF 32u
|
||||
SDL_COMPILE_TIME_ASSERT(sizeof_SDL_HashItem, sizeof(SDL_HashItem) <= MAX_HASHITEM_SIZEOF);
|
||||
|
||||
// Anything larger than this will cause integer overflows
|
||||
#define MAX_HASHTABLE_SIZE (0x80000000u / (MAX_HASHITEM_SIZEOF))
|
||||
|
||||
struct SDL_HashTable
|
||||
{
|
||||
SDL_RWLock *lock; // NULL if not created threadsafe
|
||||
SDL_HashItem *table;
|
||||
SDL_HashCallback hash;
|
||||
SDL_HashKeyMatchCallback keymatch;
|
||||
SDL_HashDestroyCallback destroy;
|
||||
void *userdata;
|
||||
Uint32 hash_mask;
|
||||
Uint32 max_probe_len;
|
||||
Uint32 num_occupied_slots;
|
||||
};
|
||||
|
||||
|
||||
static Uint32 CalculateHashBucketsFromEstimate(int estimated_capacity)
|
||||
{
|
||||
if (estimated_capacity <= 0) {
|
||||
return 4; // start small, grow as necessary.
|
||||
}
|
||||
|
||||
const Uint32 estimated32 = (Uint32) estimated_capacity;
|
||||
Uint32 buckets = ((Uint32) 1) << SDL_MostSignificantBitIndex32(estimated32);
|
||||
if (!SDL_HasExactlyOneBitSet32(estimated32)) {
|
||||
buckets <<= 1; // need next power of two up to fit overflow capacity bits.
|
||||
}
|
||||
|
||||
return SDL_min(buckets, MAX_HASHTABLE_SIZE);
|
||||
}
|
||||
|
||||
SDL_HashTable *SDL_CreateHashTable(int estimated_capacity, bool threadsafe, SDL_HashCallback hash,
|
||||
SDL_HashKeyMatchCallback keymatch,
|
||||
SDL_HashDestroyCallback destroy, void *userdata)
|
||||
{
|
||||
const Uint32 num_buckets = CalculateHashBucketsFromEstimate(estimated_capacity);
|
||||
SDL_HashTable *table = (SDL_HashTable *)SDL_calloc(1, sizeof(SDL_HashTable));
|
||||
if (!table) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (threadsafe) {
|
||||
table->lock = SDL_CreateRWLock();
|
||||
if (!table->lock) {
|
||||
SDL_DestroyHashTable(table);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
table->table = (SDL_HashItem *)SDL_calloc(num_buckets, sizeof(SDL_HashItem));
|
||||
if (!table->table) {
|
||||
SDL_DestroyHashTable(table);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
table->hash_mask = num_buckets - 1;
|
||||
table->userdata = userdata;
|
||||
table->hash = hash;
|
||||
table->keymatch = keymatch;
|
||||
table->destroy = destroy;
|
||||
return table;
|
||||
}
|
||||
|
||||
static SDL_INLINE Uint32 calc_hash(const SDL_HashTable *table, const void *key)
|
||||
{
|
||||
const Uint32 BitMixer = 0x9E3779B1u;
|
||||
return table->hash(table->userdata, key) * BitMixer;
|
||||
}
|
||||
|
||||
static SDL_INLINE Uint32 get_probe_length(Uint32 zero_idx, Uint32 actual_idx, Uint32 num_buckets)
|
||||
{
|
||||
// returns the probe sequence length from zero_idx to actual_idx
|
||||
if (actual_idx < zero_idx) {
|
||||
return num_buckets - zero_idx + actual_idx;
|
||||
}
|
||||
|
||||
return actual_idx - zero_idx;
|
||||
}
|
||||
|
||||
static SDL_HashItem *find_item(const SDL_HashTable *ht, const void *key, Uint32 hash, Uint32 *i, Uint32 *probe_len)
|
||||
{
|
||||
Uint32 hash_mask = ht->hash_mask;
|
||||
Uint32 max_probe_len = ht->max_probe_len;
|
||||
|
||||
SDL_HashItem *table = ht->table;
|
||||
|
||||
while (true) {
|
||||
SDL_HashItem *item = table + *i;
|
||||
Uint32 item_hash = item->hash;
|
||||
|
||||
if (!item->live) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (item_hash == hash && ht->keymatch(ht->userdata, item->key, key)) {
|
||||
return item;
|
||||
}
|
||||
|
||||
Uint32 item_probe_len = item->probe_len;
|
||||
SDL_assert(item_probe_len == get_probe_length(item_hash & hash_mask, (Uint32)(item - table), hash_mask + 1));
|
||||
|
||||
if (*probe_len > item_probe_len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (++*probe_len > max_probe_len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*i = (*i + 1) & hash_mask;
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_HashItem *find_first_item(const SDL_HashTable *ht, const void *key, Uint32 hash)
|
||||
{
|
||||
Uint32 i = hash & ht->hash_mask;
|
||||
Uint32 probe_len = 0;
|
||||
return find_item(ht, key, hash, &i, &probe_len);
|
||||
}
|
||||
|
||||
static SDL_HashItem *insert_item(SDL_HashItem *item_to_insert, SDL_HashItem *table, Uint32 hash_mask, Uint32 *max_probe_len_ptr)
|
||||
{
|
||||
const Uint32 num_buckets = hash_mask + 1;
|
||||
Uint32 idx = item_to_insert->hash & hash_mask;
|
||||
SDL_HashItem *target = NULL;
|
||||
SDL_HashItem temp_item;
|
||||
|
||||
while (true) {
|
||||
SDL_HashItem *candidate = table + idx;
|
||||
|
||||
if (!candidate->live) {
|
||||
// Found an empty slot. Put it here and we're done.
|
||||
*candidate = *item_to_insert;
|
||||
|
||||
if (target == NULL) {
|
||||
target = candidate;
|
||||
}
|
||||
|
||||
const Uint32 probe_len = get_probe_length(candidate->hash & hash_mask, idx, num_buckets);
|
||||
candidate->probe_len = probe_len;
|
||||
|
||||
if (*max_probe_len_ptr < probe_len) {
|
||||
*max_probe_len_ptr = probe_len;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const Uint32 candidate_probe_len = candidate->probe_len;
|
||||
SDL_assert(candidate_probe_len == get_probe_length(candidate->hash & hash_mask, idx, num_buckets));
|
||||
const Uint32 new_probe_len = get_probe_length(item_to_insert->hash & hash_mask, idx, num_buckets);
|
||||
|
||||
if (candidate_probe_len < new_probe_len) {
|
||||
// Robin Hood hashing: the item at idx has a better probe length than our item would at this position.
|
||||
// Evict it and put our item in its place, then continue looking for a new spot for the displaced item.
|
||||
// This algorithm significantly reduces clustering in the table, making lookups take very few probes.
|
||||
|
||||
temp_item = *candidate;
|
||||
*candidate = *item_to_insert;
|
||||
|
||||
if (target == NULL) {
|
||||
target = candidate;
|
||||
}
|
||||
|
||||
*item_to_insert = temp_item;
|
||||
|
||||
SDL_assert(new_probe_len == get_probe_length(candidate->hash & hash_mask, idx, num_buckets));
|
||||
candidate->probe_len = new_probe_len;
|
||||
|
||||
if (*max_probe_len_ptr < new_probe_len) {
|
||||
*max_probe_len_ptr = new_probe_len;
|
||||
}
|
||||
}
|
||||
|
||||
idx = (idx + 1) & hash_mask;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
static void delete_item(SDL_HashTable *ht, SDL_HashItem *item)
|
||||
{
|
||||
const Uint32 hash_mask = ht->hash_mask;
|
||||
SDL_HashItem *table = ht->table;
|
||||
|
||||
if (ht->destroy) {
|
||||
ht->destroy(ht->userdata, item->key, item->value);
|
||||
}
|
||||
|
||||
SDL_assert(ht->num_occupied_slots > 0);
|
||||
ht->num_occupied_slots--;
|
||||
|
||||
Uint32 idx = (Uint32)(item - ht->table);
|
||||
|
||||
while (true) {
|
||||
idx = (idx + 1) & hash_mask;
|
||||
SDL_HashItem *next_item = table + idx;
|
||||
|
||||
if (next_item->probe_len < 1) {
|
||||
SDL_zerop(item);
|
||||
return;
|
||||
}
|
||||
|
||||
*item = *next_item;
|
||||
item->probe_len -= 1;
|
||||
SDL_assert(item->probe_len < ht->max_probe_len);
|
||||
item = next_item;
|
||||
}
|
||||
}
|
||||
|
||||
static bool resize(SDL_HashTable *ht, Uint32 new_size)
|
||||
{
|
||||
const Uint32 new_hash_mask = new_size - 1;
|
||||
SDL_HashItem *new_table = SDL_calloc(new_size, sizeof(*new_table));
|
||||
|
||||
if (!new_table) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_HashItem *old_table = ht->table;
|
||||
const Uint32 old_size = ht->hash_mask + 1;
|
||||
|
||||
ht->max_probe_len = 0;
|
||||
ht->hash_mask = new_hash_mask;
|
||||
ht->table = new_table;
|
||||
|
||||
for (Uint32 i = 0; i < old_size; ++i) {
|
||||
SDL_HashItem *item = old_table + i;
|
||||
if (item->live) {
|
||||
insert_item(item, new_table, new_hash_mask, &ht->max_probe_len);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(old_table);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool maybe_resize(SDL_HashTable *ht)
|
||||
{
|
||||
const Uint32 capacity = ht->hash_mask + 1;
|
||||
|
||||
if (capacity >= MAX_HASHTABLE_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Uint32 max_load_factor = 217; // range: 0-255; 217 is roughly 85%
|
||||
const Uint32 resize_threshold = (Uint32)((max_load_factor * (Uint64)capacity) >> 8);
|
||||
|
||||
if (ht->num_occupied_slots > resize_threshold) {
|
||||
return resize(ht, capacity * 2);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value, bool replace)
|
||||
{
|
||||
CHECK_PARAM(!table) {
|
||||
return SDL_InvalidParamError("table");
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
|
||||
SDL_LockRWLockForWriting(table->lock);
|
||||
|
||||
const Uint32 hash = calc_hash(table, key);
|
||||
SDL_HashItem *item = find_first_item(table, key, hash);
|
||||
bool do_insert = true;
|
||||
|
||||
if (item) {
|
||||
if (replace) {
|
||||
delete_item(table, item);
|
||||
} else {
|
||||
SDL_SetError("key already exists and replace is disabled");
|
||||
do_insert = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (do_insert) {
|
||||
SDL_HashItem new_item;
|
||||
new_item.key = key;
|
||||
new_item.value = value;
|
||||
new_item.hash = hash;
|
||||
new_item.live = true;
|
||||
new_item.probe_len = 0;
|
||||
|
||||
table->num_occupied_slots++;
|
||||
|
||||
if (!maybe_resize(table)) {
|
||||
table->num_occupied_slots--;
|
||||
} else {
|
||||
// This never returns NULL
|
||||
insert_item(&new_item, table->table, table->hash_mask, &table->max_probe_len);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UnlockRWLock(table->lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **value)
|
||||
{
|
||||
CHECK_PARAM(!table) {
|
||||
if (value) {
|
||||
*value = NULL;
|
||||
}
|
||||
return SDL_InvalidParamError("table");
|
||||
}
|
||||
|
||||
SDL_LockRWLockForReading(table->lock);
|
||||
|
||||
bool result = false;
|
||||
const Uint32 hash = calc_hash(table, key);
|
||||
SDL_HashItem *i = find_first_item(table, key, hash);
|
||||
if (i) {
|
||||
if (value) {
|
||||
*value = i->value;
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
|
||||
SDL_UnlockRWLock(table->lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key)
|
||||
{
|
||||
CHECK_PARAM(!table) {
|
||||
return SDL_InvalidParamError("table");
|
||||
}
|
||||
|
||||
SDL_LockRWLockForWriting(table->lock);
|
||||
|
||||
bool result = false;
|
||||
const Uint32 hash = calc_hash(table, key);
|
||||
SDL_HashItem *item = find_first_item(table, key, hash);
|
||||
if (item) {
|
||||
delete_item(table, item);
|
||||
result = true;
|
||||
}
|
||||
|
||||
SDL_UnlockRWLock(table->lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SDL_IterateHashTable(const SDL_HashTable *table, SDL_HashTableIterateCallback callback, void *userdata)
|
||||
{
|
||||
CHECK_PARAM(!table) {
|
||||
return SDL_InvalidParamError("table");
|
||||
}
|
||||
CHECK_PARAM(!callback) {
|
||||
return SDL_InvalidParamError("callback");
|
||||
}
|
||||
|
||||
SDL_LockRWLockForReading(table->lock);
|
||||
SDL_HashItem *end = table->table + (table->hash_mask + 1);
|
||||
Uint32 num_iterated = 0;
|
||||
|
||||
for (SDL_HashItem *item = table->table; item < end; item++) {
|
||||
if (item->live) {
|
||||
if (!callback(userdata, table, item->key, item->value)) {
|
||||
break; // callback requested iteration stop.
|
||||
} else if (++num_iterated >= table->num_occupied_slots) {
|
||||
break; // we can drop out early because we've seen all the live items.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UnlockRWLock(table->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SDL_HashTableEmpty(SDL_HashTable *table)
|
||||
{
|
||||
CHECK_PARAM(!table) {
|
||||
return SDL_InvalidParamError("table");
|
||||
}
|
||||
|
||||
SDL_LockRWLockForReading(table->lock);
|
||||
const bool retval = (table->num_occupied_slots == 0);
|
||||
SDL_UnlockRWLock(table->lock);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static void destroy_all(SDL_HashTable *table)
|
||||
{
|
||||
SDL_HashDestroyCallback destroy = table->destroy;
|
||||
if (destroy) {
|
||||
void *userdata = table->userdata;
|
||||
SDL_HashItem *end = table->table + (table->hash_mask + 1);
|
||||
for (SDL_HashItem *i = table->table; i < end; ++i) {
|
||||
if (i->live) {
|
||||
i->live = false;
|
||||
destroy(userdata, i->key, i->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_ClearHashTable(SDL_HashTable *table)
|
||||
{
|
||||
if (table) {
|
||||
SDL_LockRWLockForWriting(table->lock);
|
||||
{
|
||||
destroy_all(table);
|
||||
SDL_memset(table->table, 0, sizeof(*table->table) * (table->hash_mask + 1));
|
||||
table->num_occupied_slots = 0;
|
||||
}
|
||||
SDL_UnlockRWLock(table->lock);
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_DestroyHashTable(SDL_HashTable *table)
|
||||
{
|
||||
if (table) {
|
||||
destroy_all(table);
|
||||
if (table->lock) {
|
||||
SDL_DestroyRWLock(table->lock);
|
||||
}
|
||||
SDL_free(table->table);
|
||||
SDL_free(table);
|
||||
}
|
||||
}
|
||||
|
||||
// this is djb's xor hashing function.
|
||||
static SDL_INLINE Uint32 hash_string_djbxor(const char *str, size_t len)
|
||||
{
|
||||
Uint32 hash = 5381;
|
||||
while (len--) {
|
||||
hash = ((hash << 5) + hash) ^ *(str++);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
Uint32 SDL_HashPointer(void *unused, const void *key)
|
||||
{
|
||||
(void)unused;
|
||||
return SDL_murmur3_32(&key, sizeof(key), 0);
|
||||
}
|
||||
|
||||
bool SDL_KeyMatchPointer(void *unused, const void *a, const void *b)
|
||||
{
|
||||
(void)unused;
|
||||
return (a == b);
|
||||
}
|
||||
|
||||
Uint32 SDL_HashString(void *unused, const void *key)
|
||||
{
|
||||
(void)unused;
|
||||
const char *str = (const char *)key;
|
||||
return hash_string_djbxor(str, SDL_strlen(str));
|
||||
}
|
||||
|
||||
bool SDL_KeyMatchString(void *unused, const void *a, const void *b)
|
||||
{
|
||||
const char *a_string = (const char *)a;
|
||||
const char *b_string = (const char *)b;
|
||||
|
||||
(void)unused;
|
||||
if (a == b) {
|
||||
return true; // same pointer, must match.
|
||||
} else if (!a || !b) {
|
||||
return false; // one pointer is NULL (and first test shows they aren't the same pointer), must not match.
|
||||
} else if (a_string[0] != b_string[0]) {
|
||||
return false; // we know they don't match
|
||||
}
|
||||
return (SDL_strcmp(a_string, b_string) == 0); // Check against actual string contents.
|
||||
}
|
||||
|
||||
// We assume we can fit the ID in the key directly
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_HashID_KeySize, sizeof(Uint32) <= sizeof(const void *));
|
||||
|
||||
Uint32 SDL_HashID(void *unused, const void *key)
|
||||
{
|
||||
(void)unused;
|
||||
return (Uint32)(uintptr_t)key;
|
||||
}
|
||||
|
||||
bool SDL_KeyMatchID(void *unused, const void *a, const void *b)
|
||||
{
|
||||
(void)unused;
|
||||
return (a == b);
|
||||
}
|
||||
|
||||
void SDL_DestroyHashKeyAndValue(void *unused, const void *key, const void *value)
|
||||
{
|
||||
(void)unused;
|
||||
SDL_free((void *)key);
|
||||
SDL_free((void *)value);
|
||||
}
|
||||
|
||||
void SDL_DestroyHashKey(void *unused, const void *key, const void *value)
|
||||
{
|
||||
(void)value;
|
||||
(void)unused;
|
||||
SDL_free((void *)key);
|
||||
}
|
||||
|
||||
void SDL_DestroyHashValue(void *unused, const void *key, const void *value)
|
||||
{
|
||||
(void)key;
|
||||
(void)unused;
|
||||
SDL_free((void *)value);
|
||||
}
|
||||
Vendored
+633
@@ -0,0 +1,633 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
/* this is over-documented because it was almost a public API. Leaving the
|
||||
full docs here in case it _does_ become public some day. */
|
||||
|
||||
/* WIKI CATEGORY: HashTable */
|
||||
|
||||
/**
|
||||
* # CategoryHashTable
|
||||
*
|
||||
* SDL offers a hash table implementation, as a convenience for C code that
|
||||
* needs efficient organization and access of arbitrary data.
|
||||
*
|
||||
* Hash tables are a popular data structure, designed to make it quick to
|
||||
* store and look up arbitrary data. Data is stored with an associated "key."
|
||||
* While one would look up an element of an array with an index, a hash table
|
||||
* uses a unique key to find an element later.
|
||||
*
|
||||
* A key can be anything, as long as its unique and in a format that the table
|
||||
* understands. For example, it's popular to use strings as keys: the key
|
||||
* might be a username, and it is used to lookup account information for that
|
||||
* user, etc.
|
||||
*
|
||||
* Hash tables are named because they "hash" their keys down into simple
|
||||
* integers that can be used to efficiently organize and access the associated
|
||||
* data.
|
||||
*
|
||||
* As this is a C API, there is one generic interface that is intended to work
|
||||
* with different data types. This can be a little awkward to set up, but is
|
||||
* easy to use after that.
|
||||
*
|
||||
* Hashtables are generated by a call to SDL_CreateHashTable(). This function
|
||||
* requires several callbacks to be provided (for hashing keys, comparing
|
||||
* entries, and cleaning up entries when removed). These are necessary to
|
||||
* allow the hash to manage any arbitrary data type.
|
||||
*
|
||||
* Once a hash table is created, the common tasks are inserting data into the
|
||||
* table, (SDL_InsertIntoHashTable), looking up previously inserted data
|
||||
* (SDL_FindInHashTable), and removing data (SDL_RemoveFromHashTable and
|
||||
* SDL_ClearHashTable). Less common but still useful is the ability to
|
||||
* iterate through all the items in the table (SDL_IterateHashTable).
|
||||
*
|
||||
* The underlying hash table implementation is always subject to change, but
|
||||
* at the time of writing, it uses open addressing and Robin Hood hashing.
|
||||
* The technical details are explained [here](https://github.com/libsdl-org/SDL/pull/10897).
|
||||
*
|
||||
* Hashtables keep an SDL_RWLock internally, so multiple threads can perform
|
||||
* hash lookups in parallel, while changes to the table will safely serialize
|
||||
* access between threads.
|
||||
*
|
||||
* SDL provides a layer on top of this hash table implementation that might be
|
||||
* more pleasant to use. SDL_PropertiesID maps a string to arbitrary data of
|
||||
* various types in the same table, which could be both easier to use and more
|
||||
* flexible. Refer to [CategoryProperties](CategoryProperties) for details.
|
||||
*/
|
||||
|
||||
#ifndef SDL_hashtable_h_
|
||||
#define SDL_hashtable_h_
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
#include <SDL3/SDL_begin_code.h>
|
||||
/* Set up for C function definitions, even when using C++ */
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The opaque type that represents a hash table.
|
||||
*
|
||||
* This is hidden behind an opaque pointer because not only does the table
|
||||
* need to store arbitrary data types, but the hash table implementation may
|
||||
* change in the future.
|
||||
*
|
||||
* \since This struct is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
*/
|
||||
typedef struct SDL_HashTable SDL_HashTable;
|
||||
|
||||
/**
|
||||
* A function pointer representing a hash table hashing callback.
|
||||
*
|
||||
* This is called by SDL_HashTable when it needs to look up a key in
|
||||
* its dataset. It generates a hash value from that key, and then uses that
|
||||
* value as a basis for an index into an internal array.
|
||||
*
|
||||
* There are no rules on what hashing algorithm is used, so long as it
|
||||
* can produce a reliable 32-bit value from `key`, and ideally distributes
|
||||
* those values well across the 32-bit value space. The quality of a
|
||||
* hashing algorithm is directly related to how well a hash table performs.
|
||||
*
|
||||
* Hashing can be a complicated subject, and often times what works best
|
||||
* for one dataset will be suboptimal for another. There is a good discussion
|
||||
* of the field [on Wikipedia](https://en.wikipedia.org/wiki/Hash_function).
|
||||
*
|
||||
* Also: do you _need_ to write a hashing function? SDL provides generic
|
||||
* functions for strings (SDL_HashString), generic integer IDs (SDL_HashID),
|
||||
* and generic pointers (SDL_HashPointer). Often you should use one of these
|
||||
* before writing your own.
|
||||
*
|
||||
* \param userdata what was passed as `userdata` to SDL_CreateHashTable().
|
||||
* \param key the key to be hashed.
|
||||
* \returns a 32-bit value that represents a hash of `key`.
|
||||
*
|
||||
* \threadsafety This function must be thread safe if the hash table is used
|
||||
* from multiple threads at the same time.
|
||||
*
|
||||
* \since This datatype is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
* \sa SDL_HashString
|
||||
* \sa SDL_HashID
|
||||
* \sa SDL_HashPointer
|
||||
*/
|
||||
typedef Uint32 (SDLCALL *SDL_HashCallback)(void *userdata, const void *key);
|
||||
|
||||
|
||||
/**
|
||||
* A function pointer representing a hash table matching callback.
|
||||
*
|
||||
* This is called by SDL_HashTable when it needs to look up a key in its
|
||||
* dataset. After hashing the key, it looks for items stored in relation to
|
||||
* that hash value. Since there can be more than one item found through the
|
||||
* same hash value, this function verifies a specific value is actually
|
||||
* correct before choosing it.
|
||||
*
|
||||
* So this function needs to compare the keys at `a` and `b` and decide if
|
||||
* they are actually the same.
|
||||
*
|
||||
* For example, if the keys are C strings, this function might just be:
|
||||
*
|
||||
* ```c
|
||||
* return (SDL_strcmp((const char *) a, const char *b) == 0);`
|
||||
* ```
|
||||
*
|
||||
* Also: do you _need_ to write a matching function? SDL provides generic
|
||||
* functions for strings (SDL_KeyMatchString), generic integer IDs
|
||||
* (SDL_KeyMatchID), and generic pointers (SDL_KeyMatchPointer). Often you
|
||||
* should use one of these before writing your own.
|
||||
*
|
||||
* \param userdata what was passed as `userdata` to SDL_CreateHashTable().
|
||||
* \param a the first key to be compared.
|
||||
* \param b the second key to be compared.
|
||||
* \returns true if two keys are identical, false otherwise.
|
||||
*
|
||||
* \threadsafety This function must be thread safe if the hash table is used
|
||||
* from multiple threads at the same time.
|
||||
*
|
||||
* \since This datatype is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
*/
|
||||
typedef bool (SDLCALL *SDL_HashKeyMatchCallback)(void *userdata, const void *a, const void *b);
|
||||
|
||||
|
||||
/**
|
||||
* A function pointer representing a hash table cleanup callback.
|
||||
*
|
||||
* This is called by SDL_HashTable when removing items from the hash, or
|
||||
* destroying the hash table. It is used to optionally deallocate the
|
||||
* key/value pairs.
|
||||
*
|
||||
* This is not required to do anything, if all the data in the table is
|
||||
* static or POD data, but it can also do more than a simple free: for
|
||||
* example, if the hash table is storing open files, it can close them here.
|
||||
* It can also free only the key or only the value; it depends on what the
|
||||
* hash table contains.
|
||||
*
|
||||
* \param userdata what was passed as `userdata` to SDL_CreateHashTable().
|
||||
* \param key the key to deallocate.
|
||||
* \param value the value to deallocate.
|
||||
*
|
||||
* \threadsafety This function must be thread safe if the hash table is used
|
||||
* from multiple threads at the same time.
|
||||
*
|
||||
* \since This datatype is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
*/
|
||||
typedef void (SDLCALL *SDL_HashDestroyCallback)(void *userdata, const void *key, const void *value);
|
||||
|
||||
|
||||
/**
|
||||
* A function pointer representing a hash table iterator callback.
|
||||
*
|
||||
* This function is called once for each key/value pair to be considered
|
||||
* when iterating a hash table.
|
||||
*
|
||||
* Iteration continues as long as there are more items to examine and this
|
||||
* callback continues to return true.
|
||||
*
|
||||
* Do not attempt to modify the hash table during this callback, as it will
|
||||
* cause incorrect behavior and possibly crashes.
|
||||
*
|
||||
* \param userdata what was passed as `userdata` to an iterator function.
|
||||
* \param table the hash table being iterated.
|
||||
* \param key the current key being iterated.
|
||||
* \param value the current value being iterated.
|
||||
* \returns true to keep iterating, false to stop iteration.
|
||||
*
|
||||
* \threadsafety A read lock is held during iteration, so other threads can
|
||||
* still access the hash table, but threads attempting to make
|
||||
* changes will be blocked until iteration completes. If this
|
||||
* is a concern, do as little in the callback as possible and
|
||||
* finish iteration quickly.
|
||||
*
|
||||
* \since This datatype is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_IterateHashTable
|
||||
*/
|
||||
typedef bool (SDLCALL *SDL_HashTableIterateCallback)(void *userdata, const SDL_HashTable *table, const void *key, const void *value);
|
||||
|
||||
|
||||
/**
|
||||
* Create a new hash table.
|
||||
*
|
||||
* To deal with different datatypes and needs of the caller, hash tables
|
||||
* require several callbacks that deal with some specifics: how to hash a key,
|
||||
* how to compare a key for equality, and how to clean up keys and values.
|
||||
* SDL provides a few generic functions that can be used for these callbacks:
|
||||
*
|
||||
* - SDL_HashString and SDL_KeyMatchString for C strings.
|
||||
* - SDL_HashPointer and SDL_KeyMatchPointer for generic pointers.
|
||||
* - SDL_HashID and SDL_KeyMatchID for generic (possibly small) integers.
|
||||
*
|
||||
* Oftentimes, these are all you need for any hash table, but depending on
|
||||
* your dataset, custom implementations might make more sense.
|
||||
*
|
||||
* You can specify an estimate of the number of items expected to be stored
|
||||
* in the table, which can help make the table run more efficiently. The table
|
||||
* will preallocate resources to accommodate this number of items, which is
|
||||
* most useful if you intend to fill the table with a lot of data right after
|
||||
* creating it. Otherwise, it might make more sense to specify the _minimum_
|
||||
* you expect the table to hold and let it grow as necessary from there. This
|
||||
* number is only a hint, and the table will be able to handle any amount of
|
||||
* data--as long as the system doesn't run out of resources--so a perfect
|
||||
* answer is not required. A value of 0 signifies no guess at all, and the
|
||||
* table will start small and reallocate as necessary; often this is the
|
||||
* correct thing to do.
|
||||
*
|
||||
* !!! FIXME: add note about `threadsafe` here. And update `threadsafety` tags.
|
||||
* !!! FIXME: note that `threadsafe` tables can't be recursively locked, so
|
||||
* !!! FIXME: you can't use `destroy` callbacks that might end up relocking.
|
||||
*
|
||||
* Note that SDL provides a higher-level option built on its hash tables:
|
||||
* SDL_PropertiesID lets you map strings to various datatypes, and this
|
||||
* might be easier to use. It only allows strings for keys, however. Those are
|
||||
* created with SDL_CreateProperties().
|
||||
*
|
||||
* The returned hash table should be destroyed with SDL_DestroyHashTable()
|
||||
* when no longer needed.
|
||||
*
|
||||
* \param estimated_capacity the approximate maximum number of items to be held
|
||||
* in the hash table, or 0 for no estimate.
|
||||
* \param threadsafe true to create an internal rwlock for this table.
|
||||
* \param hash the function to use to hash keys.
|
||||
* \param keymatch the function to use to compare keys.
|
||||
* \param destroy the function to use to clean up keys and values, may be NULL.
|
||||
* \param userdata a pointer that is passed to the callbacks.
|
||||
* \returns a newly-created hash table, or NULL if there was an error; call
|
||||
* SDL_GetError() for more information.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_DestroyHashTable
|
||||
*/
|
||||
extern SDL_HashTable * SDL_CreateHashTable(int estimated_capacity,
|
||||
bool threadsafe,
|
||||
SDL_HashCallback hash,
|
||||
SDL_HashKeyMatchCallback keymatch,
|
||||
SDL_HashDestroyCallback destroy,
|
||||
void *userdata);
|
||||
|
||||
|
||||
/**
|
||||
* Destroy a hash table.
|
||||
*
|
||||
* This will call the hash table's SDL_HashDestroyCallback for each item in
|
||||
* the table, removing all inserted items, before deallocating the table
|
||||
* itself.
|
||||
*
|
||||
* The table becomes invalid once this function is called, and no other thread
|
||||
* should be accessing this table once this function has started.
|
||||
*
|
||||
* \param table the hash table to destroy.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*/
|
||||
extern void SDL_DestroyHashTable(SDL_HashTable *table);
|
||||
|
||||
/**
|
||||
* Add an item to a hash table.
|
||||
*
|
||||
* All keys in the table must be unique. If attempting to insert a key that
|
||||
* already exists in the hash table, what will be done depends on the
|
||||
* `replace` value:
|
||||
*
|
||||
* - If `replace` is false, this function will return false without modifying
|
||||
* the table.
|
||||
* - If `replace` is true, SDL will remove the previous item first, so the new
|
||||
* value is the only one associated with that key. This will call the hash
|
||||
* table's SDL_HashDestroyCallback for the previous item.
|
||||
*
|
||||
* \param table the hash table to insert into.
|
||||
* \param key the key of the new item to insert.
|
||||
* \param value the value of the new item to insert.
|
||||
* \param replace true if a duplicate key should replace the previous value.
|
||||
* \returns true if the new item was inserted, false otherwise.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*/
|
||||
extern bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value, bool replace);
|
||||
|
||||
/**
|
||||
* Look up an item in a hash table.
|
||||
*
|
||||
* On return, the value associated with `key` is stored to `*value`.
|
||||
* If the key does not exist in the table, `*value` will be set to NULL.
|
||||
*
|
||||
* It is legal for `value` to be NULL, to not retrieve the key's value. In
|
||||
* this case, the return value is still useful for reporting if the key exists
|
||||
* in the table at all.
|
||||
*
|
||||
* \param table the hash table to search.
|
||||
* \param key the key to search for in the table.
|
||||
* \param value the found value will be stored here. Can be NULL.
|
||||
* \returns true if key exists in the table, false otherwise.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_InsertIntoHashTable
|
||||
*/
|
||||
extern bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **value);
|
||||
|
||||
/**
|
||||
* Remove an item from a hash table.
|
||||
*
|
||||
* If there is an item that matches `key`, it is removed from the table. This
|
||||
* will call the hash table's SDL_HashDestroyCallback for the item to be
|
||||
* removed.
|
||||
*
|
||||
* \param table the hash table to remove from.
|
||||
* \param key the key of the item to remove from the table.
|
||||
* \returns true if a key was removed, false if the key was not found.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*/
|
||||
extern bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key);
|
||||
|
||||
/**
|
||||
* Remove all items in a hash table.
|
||||
*
|
||||
* This will call the hash table's SDL_HashDestroyCallback for each item in
|
||||
* the table, removing all inserted items.
|
||||
*
|
||||
* When this function returns, the hash table will be empty.
|
||||
*
|
||||
* \param table the hash table to clear.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*/
|
||||
extern void SDL_ClearHashTable(SDL_HashTable *table);
|
||||
|
||||
/**
|
||||
* Check if any items are currently stored in a hash table.
|
||||
*
|
||||
* If there are no items stored (the table is completely empty), this will
|
||||
* return true.
|
||||
*
|
||||
* \param table the hash table to check.
|
||||
* \returns true if the table is completely empty, false otherwise.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_ClearHashTable
|
||||
*/
|
||||
extern bool SDL_HashTableEmpty(SDL_HashTable *table);
|
||||
|
||||
/**
|
||||
* Iterate all key/value pairs in a hash table.
|
||||
*
|
||||
* This function will call `callback` once for each key/value pair in the
|
||||
* table, until either all pairs have been presented to the callback, or the
|
||||
* callback has returned false to signal it is done.
|
||||
*
|
||||
* There is no guarantee what order results will be returned in.
|
||||
*
|
||||
* \param table the hash table to iterate.
|
||||
* \param callback the function pointer to call for each value.
|
||||
* \param userdata a pointer that is passed to `callback`.
|
||||
* \returns true if iteration happened, false if not (bogus parameter, etc.).
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*/
|
||||
extern bool SDL_IterateHashTable(const SDL_HashTable *table, SDL_HashTableIterateCallback callback, void *userdata);
|
||||
|
||||
|
||||
/* Helper functions for SDL_CreateHashTable callbacks... */
|
||||
|
||||
/**
|
||||
* Generate a hash from a generic pointer.
|
||||
*
|
||||
* The key is intended to be a unique pointer to any datatype.
|
||||
*
|
||||
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
|
||||
* if this is useful to the type of keys to be used with the hash table.
|
||||
*
|
||||
* Note that the implementation may change in the future; do not expect
|
||||
* the results to be stable vs future SDL releases. Use this in a hash table
|
||||
* in the current process and don't store them to disk for the future.
|
||||
*
|
||||
* \param unused this parameter is ignored.
|
||||
* \param key the key to hash as a generic pointer.
|
||||
* \returns a 32-bit hash of the key.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
*/
|
||||
extern Uint32 SDL_HashPointer(void *unused, const void *key);
|
||||
|
||||
/**
|
||||
* Compare two generic pointers as hash table keys.
|
||||
*
|
||||
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
|
||||
* if this is useful to the type of keys to be used with the hash table.
|
||||
*
|
||||
* \param unused this parameter is ignored.
|
||||
* \param a the first generic pointer to compare.
|
||||
* \param b the second generic pointer to compare.
|
||||
* \returns true if the pointers are the same, false otherwise.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
*/
|
||||
extern bool SDL_KeyMatchPointer(void *unused, const void *a, const void *b);
|
||||
|
||||
/**
|
||||
* Generate a hash from a C string.
|
||||
*
|
||||
* The key is intended to be a NULL-terminated string, in UTF-8 format.
|
||||
*
|
||||
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
|
||||
* if this is useful to the type of keys to be used with the hash table.
|
||||
*
|
||||
* Note that the implementation may change in the future; do not expect
|
||||
* the results to be stable vs future SDL releases. Use this in a hash table
|
||||
* in the current process and don't store them to disk for the future.
|
||||
*
|
||||
* \param unused this parameter is ignored.
|
||||
* \param key the key to hash as a generic pointer.
|
||||
* \returns a 32-bit hash of the key.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
*/
|
||||
extern Uint32 SDL_HashString(void *unused, const void *key);
|
||||
|
||||
/**
|
||||
* Compare two C strings as hash table keys.
|
||||
*
|
||||
* Strings will be compared in a case-sensitive manner. More specifically,
|
||||
* they'll be compared as NULL-terminated arrays of bytes.
|
||||
*
|
||||
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
|
||||
* if this is useful to the type of keys to be used with the hash table.
|
||||
*
|
||||
* \param unused this parameter is ignored.
|
||||
* \param a the first string to compare.
|
||||
* \param b the second string to compare.
|
||||
* \returns true if the strings are the same, false otherwise.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
*/
|
||||
extern bool SDL_KeyMatchString(void *unused, const void *a, const void *b);
|
||||
|
||||
/**
|
||||
* Generate a hash from an integer ID.
|
||||
*
|
||||
* The key is intended to a unique integer, possibly within a small range.
|
||||
*
|
||||
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
|
||||
* if this is useful to the type of keys to be used with the hash table.
|
||||
*
|
||||
* Note that the implementation may change in the future; do not expect
|
||||
* the results to be stable vs future SDL releases. Use this in a hash table
|
||||
* in the current process and don't store them to disk for the future.
|
||||
*
|
||||
* \param unused this parameter is ignored.
|
||||
* \param key the key to hash as a generic pointer.
|
||||
* \returns a 32-bit hash of the key.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
*/
|
||||
extern Uint32 SDL_HashID(void *unused, const void *key);
|
||||
|
||||
/**
|
||||
* Compare two integer IDs as hash table keys.
|
||||
*
|
||||
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
|
||||
* if this is useful to the type of keys to be used with the hash table.
|
||||
*
|
||||
* \param unused this parameter is ignored.
|
||||
* \param a the first ID to compare.
|
||||
* \param b the second ID to compare.
|
||||
* \returns true if the IDs are the same, false otherwise.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
*/
|
||||
extern bool SDL_KeyMatchID(void *unused, const void *a, const void *b);
|
||||
|
||||
/**
|
||||
* Free both the key and value pointers of a hash table item.
|
||||
*
|
||||
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
|
||||
* if this is useful to the type of data to be used with the hash table.
|
||||
*
|
||||
* This literally calls `SDL_free(key);` and `SDL_free(value);`.
|
||||
*
|
||||
* \param unused this parameter is ignored.
|
||||
* \param key the key to be destroyed.
|
||||
* \param value the value to be destroyed.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
*/
|
||||
extern void SDL_DestroyHashKeyAndValue(void *unused, const void *key, const void *value);
|
||||
|
||||
/**
|
||||
* Free just the value pointer of a hash table item.
|
||||
*
|
||||
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
|
||||
* if this is useful to the type of data to be used with the hash table.
|
||||
*
|
||||
* This literally calls `SDL_free(key);` and leaves `value` alone.
|
||||
*
|
||||
* \param unused this parameter is ignored.
|
||||
* \param key the key to be destroyed.
|
||||
* \param value the value to be destroyed.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
*/
|
||||
extern void SDL_DestroyHashKey(void *unused, const void *key, const void *value);
|
||||
|
||||
/**
|
||||
* Free just the value pointer of a hash table item.
|
||||
*
|
||||
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
|
||||
* if this is useful to the type of data to be used with the hash table.
|
||||
*
|
||||
* This literally calls `SDL_free(value);` and leaves `key` alone.
|
||||
*
|
||||
* \param unused this parameter is ignored.
|
||||
* \param key the key to be destroyed.
|
||||
* \param value the value to be destroyed.
|
||||
*
|
||||
* \threadsafety It is safe to call this function from any thread.
|
||||
*
|
||||
* \since This function is available since SDL 3.4.0.
|
||||
*
|
||||
* \sa SDL_CreateHashTable
|
||||
*/
|
||||
extern void SDL_DestroyHashValue(void *unused, const void *key, const void *value);
|
||||
|
||||
|
||||
/* Ends C function definitions when using C++ */
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#include <SDL3/SDL_close_code.h>
|
||||
|
||||
#endif /* SDL_hashtable_h_ */
|
||||
Vendored
+404
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_hints_c.h"
|
||||
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
#include "core/android/SDL_android.h"
|
||||
#endif
|
||||
|
||||
typedef struct SDL_HintWatch
|
||||
{
|
||||
SDL_HintCallback callback;
|
||||
void *userdata;
|
||||
struct SDL_HintWatch *next;
|
||||
} SDL_HintWatch;
|
||||
|
||||
typedef struct SDL_Hint
|
||||
{
|
||||
char *value;
|
||||
SDL_HintPriority priority;
|
||||
SDL_HintWatch *callbacks;
|
||||
} SDL_Hint;
|
||||
|
||||
static SDL_AtomicU32 SDL_hint_props;
|
||||
|
||||
|
||||
void SDL_InitHints(void)
|
||||
{
|
||||
}
|
||||
|
||||
void SDL_QuitHints(void)
|
||||
{
|
||||
SDL_PropertiesID props;
|
||||
do {
|
||||
props = SDL_GetAtomicU32(&SDL_hint_props);
|
||||
} while (!SDL_CompareAndSwapAtomicU32(&SDL_hint_props, props, 0));
|
||||
|
||||
if (props) {
|
||||
SDL_DestroyProperties(props);
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_PropertiesID GetHintProperties(bool create)
|
||||
{
|
||||
SDL_PropertiesID props = SDL_GetAtomicU32(&SDL_hint_props);
|
||||
if (!props && create) {
|
||||
props = SDL_CreateProperties();
|
||||
if (!SDL_CompareAndSwapAtomicU32(&SDL_hint_props, 0, props)) {
|
||||
// Somebody else created hint properties before us, just use those
|
||||
SDL_DestroyProperties(props);
|
||||
props = SDL_GetAtomicU32(&SDL_hint_props);
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
static void SDLCALL CleanupHintProperty(void *userdata, void *value)
|
||||
{
|
||||
SDL_Hint *hint = (SDL_Hint *) value;
|
||||
SDL_free(hint->value);
|
||||
|
||||
SDL_HintWatch *entry = hint->callbacks;
|
||||
while (entry) {
|
||||
SDL_HintWatch *freeable = entry;
|
||||
entry = entry->next;
|
||||
SDL_free(freeable);
|
||||
}
|
||||
SDL_free(hint);
|
||||
}
|
||||
|
||||
static const char *GetHintEnvironmentVariable(const char *name)
|
||||
{
|
||||
const char *result = SDL_getenv(name);
|
||||
if (!result && name && *name) {
|
||||
// fall back to old (SDL2) names of environment variables that
|
||||
// are important to users (e.g. many use SDL_VIDEODRIVER=wayland)
|
||||
if (SDL_strcmp(name, SDL_HINT_VIDEO_DRIVER) == 0) {
|
||||
result = SDL_getenv("SDL_VIDEODRIVER");
|
||||
} else if (SDL_strcmp(name, SDL_HINT_AUDIO_DRIVER) == 0) {
|
||||
result = SDL_getenv("SDL_AUDIODRIVER");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriority priority)
|
||||
{
|
||||
CHECK_PARAM(!name || !*name) {
|
||||
return SDL_InvalidParamError("name");
|
||||
}
|
||||
|
||||
const char *env = GetHintEnvironmentVariable(name);
|
||||
if (env && (priority < SDL_HINT_OVERRIDE)) {
|
||||
return SDL_SetError("An environment variable is taking priority");
|
||||
}
|
||||
|
||||
const SDL_PropertiesID hints = GetHintProperties(true);
|
||||
if (!hints) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
|
||||
SDL_LockProperties(hints);
|
||||
|
||||
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
|
||||
if (hint) {
|
||||
if (priority >= hint->priority) {
|
||||
if (hint->value != value && (!value || !hint->value || SDL_strcmp(hint->value, value) != 0)) {
|
||||
char *old_value = hint->value;
|
||||
|
||||
hint->value = value ? SDL_strdup(value) : NULL;
|
||||
SDL_HintWatch *entry = hint->callbacks;
|
||||
while (entry) {
|
||||
// Save the next entry in case this one is deleted
|
||||
SDL_HintWatch *next = entry->next;
|
||||
entry->callback(entry->userdata, name, old_value, value);
|
||||
entry = next;
|
||||
}
|
||||
SDL_free(old_value);
|
||||
}
|
||||
hint->priority = priority;
|
||||
result = true;
|
||||
}
|
||||
} else { // Couldn't find the hint? Add a new one.
|
||||
hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
|
||||
if (hint) {
|
||||
hint->value = value ? SDL_strdup(value) : NULL;
|
||||
hint->priority = priority;
|
||||
hint->callbacks = NULL;
|
||||
result = SDL_SetPointerPropertyWithCleanup(hints, name, hint, CleanupHintProperty, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) {
|
||||
// Special handling for this hint, which needs to persist outside the normal application flow
|
||||
Android_SetAllowRecreateActivity(SDL_GetStringBoolean(value, false));
|
||||
}
|
||||
#endif // SDL_PLATFORM_ANDROID
|
||||
|
||||
SDL_UnlockProperties(hints);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SDL_ResetHint(const char *name)
|
||||
{
|
||||
CHECK_PARAM(!name || !*name) {
|
||||
return SDL_InvalidParamError("name");
|
||||
}
|
||||
|
||||
const char *env = GetHintEnvironmentVariable(name);
|
||||
|
||||
const SDL_PropertiesID hints = GetHintProperties(false);
|
||||
if (!hints) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
|
||||
SDL_LockProperties(hints);
|
||||
|
||||
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
|
||||
if (hint) {
|
||||
if ((!env && hint->value) || (env && !hint->value) || (env && SDL_strcmp(env, hint->value) != 0)) {
|
||||
for (SDL_HintWatch *entry = hint->callbacks; entry;) {
|
||||
// Save the next entry in case this one is deleted
|
||||
SDL_HintWatch *next = entry->next;
|
||||
entry->callback(entry->userdata, name, hint->value, env);
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
SDL_free(hint->value);
|
||||
hint->value = NULL;
|
||||
hint->priority = SDL_HINT_DEFAULT;
|
||||
result = true;
|
||||
}
|
||||
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) {
|
||||
// Special handling for this hint, which needs to persist outside the normal application flow
|
||||
if (env) {
|
||||
Android_SetAllowRecreateActivity(SDL_GetStringBoolean(env, false));
|
||||
} else {
|
||||
Android_SetAllowRecreateActivity(false);
|
||||
}
|
||||
}
|
||||
#endif // SDL_PLATFORM_ANDROID
|
||||
|
||||
SDL_UnlockProperties(hints);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void SDLCALL ResetHintsCallback(void *userdata, SDL_PropertiesID hints, const char *name)
|
||||
{
|
||||
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
|
||||
if (!hint) {
|
||||
return; // uh...okay.
|
||||
}
|
||||
|
||||
const char *env = GetHintEnvironmentVariable(name);
|
||||
if ((!env && hint->value) || (env && !hint->value) || (env && SDL_strcmp(env, hint->value) != 0)) {
|
||||
SDL_HintWatch *entry = hint->callbacks;
|
||||
while (entry) {
|
||||
// Save the next entry in case this one is deleted
|
||||
SDL_HintWatch *next = entry->next;
|
||||
entry->callback(entry->userdata, name, hint->value, env);
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
SDL_free(hint->value);
|
||||
hint->value = NULL;
|
||||
hint->priority = SDL_HINT_DEFAULT;
|
||||
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) {
|
||||
// Special handling for this hint, which needs to persist outside the normal application flow
|
||||
if (env) {
|
||||
Android_SetAllowRecreateActivity(SDL_GetStringBoolean(env, false));
|
||||
} else {
|
||||
Android_SetAllowRecreateActivity(false);
|
||||
}
|
||||
}
|
||||
#endif // SDL_PLATFORM_ANDROID
|
||||
}
|
||||
|
||||
void SDL_ResetHints(void)
|
||||
{
|
||||
SDL_EnumerateProperties(GetHintProperties(false), ResetHintsCallback, NULL);
|
||||
}
|
||||
|
||||
bool SDL_SetHint(const char *name, const char *value)
|
||||
{
|
||||
return SDL_SetHintWithPriority(name, value, SDL_HINT_NORMAL);
|
||||
}
|
||||
|
||||
const char *SDL_GetHint(const char *name)
|
||||
{
|
||||
if (!name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *result = GetHintEnvironmentVariable(name);
|
||||
|
||||
const SDL_PropertiesID hints = GetHintProperties(false);
|
||||
if (hints) {
|
||||
SDL_LockProperties(hints);
|
||||
|
||||
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
|
||||
if (hint) {
|
||||
if (!result || hint->priority == SDL_HINT_OVERRIDE) {
|
||||
result = SDL_GetPersistentString(hint->value);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UnlockProperties(hints);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int SDL_GetStringInteger(const char *value, int default_value)
|
||||
{
|
||||
if (!value || !*value) {
|
||||
return default_value;
|
||||
}
|
||||
if (SDL_strcasecmp(value, "false") == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (SDL_strcasecmp(value, "true") == 0) {
|
||||
return 1;
|
||||
}
|
||||
if (*value == '-' || SDL_isdigit(*value)) {
|
||||
return SDL_atoi(value);
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
bool SDL_GetStringBoolean(const char *value, bool default_value)
|
||||
{
|
||||
if (!value || !*value) {
|
||||
return default_value;
|
||||
}
|
||||
if (*value == '0' || SDL_strcasecmp(value, "false") == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SDL_GetHintBoolean(const char *name, bool default_value)
|
||||
{
|
||||
const char *hint = SDL_GetHint(name);
|
||||
return SDL_GetStringBoolean(hint, default_value);
|
||||
}
|
||||
|
||||
bool SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
CHECK_PARAM(!name || !*name) {
|
||||
return SDL_InvalidParamError("name");
|
||||
}
|
||||
CHECK_PARAM(!callback) {
|
||||
return SDL_InvalidParamError("callback");
|
||||
}
|
||||
|
||||
const SDL_PropertiesID hints = GetHintProperties(true);
|
||||
if (!hints) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_HintWatch *entry = (SDL_HintWatch *)SDL_malloc(sizeof(*entry));
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
entry->callback = callback;
|
||||
entry->userdata = userdata;
|
||||
|
||||
bool result = false;
|
||||
|
||||
SDL_LockProperties(hints);
|
||||
|
||||
SDL_RemoveHintCallback(name, callback, userdata);
|
||||
|
||||
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
|
||||
if (hint) {
|
||||
result = true;
|
||||
} else { // Need to add a hint entry for this watcher
|
||||
hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
|
||||
if (!hint) {
|
||||
SDL_free(entry);
|
||||
SDL_UnlockProperties(hints);
|
||||
return false;
|
||||
} else {
|
||||
hint->value = NULL;
|
||||
hint->priority = SDL_HINT_DEFAULT;
|
||||
hint->callbacks = NULL;
|
||||
result = SDL_SetPointerPropertyWithCleanup(hints, name, hint, CleanupHintProperty, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Add it to the callbacks for this hint
|
||||
entry->next = hint->callbacks;
|
||||
hint->callbacks = entry;
|
||||
|
||||
// Now call it with the current value
|
||||
const char *value = SDL_GetHint(name);
|
||||
callback(userdata, name, value, value);
|
||||
|
||||
SDL_UnlockProperties(hints);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void SDL_RemoveHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
if (!name || !*name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const SDL_PropertiesID hints = GetHintProperties(false);
|
||||
if (!hints) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_LockProperties(hints);
|
||||
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
|
||||
if (hint) {
|
||||
SDL_HintWatch *prev = NULL;
|
||||
for (SDL_HintWatch *entry = hint->callbacks; entry; entry = entry->next) {
|
||||
if ((callback == entry->callback) && (userdata == entry->userdata)) {
|
||||
if (prev) {
|
||||
prev->next = entry->next;
|
||||
} else {
|
||||
hint->callbacks = entry->next;
|
||||
}
|
||||
SDL_free(entry);
|
||||
break;
|
||||
}
|
||||
prev = entry;
|
||||
}
|
||||
}
|
||||
SDL_UnlockProperties(hints);
|
||||
}
|
||||
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// This file defines useful function for working with SDL hints
|
||||
|
||||
#ifndef SDL_hints_c_h_
|
||||
#define SDL_hints_c_h_
|
||||
|
||||
extern void SDL_InitHints(void);
|
||||
extern bool SDL_GetStringBoolean(const char *value, bool default_value);
|
||||
extern int SDL_GetStringInteger(const char *value, int default_value);
|
||||
extern void SDL_QuitHints(void);
|
||||
|
||||
#endif // SDL_hints_c_h_
|
||||
Vendored
+325
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#ifndef SDL_internal_h_
|
||||
#define SDL_internal_h_
|
||||
|
||||
// Many of SDL's features require _GNU_SOURCE on various platforms
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
// Need this so Linux systems define fseek64o, ftell64o and off64_t
|
||||
#ifndef _LARGEFILE64_SOURCE
|
||||
#define _LARGEFILE64_SOURCE 1
|
||||
#endif
|
||||
|
||||
/* This is for a variable-length array at the end of a struct:
|
||||
struct x { int y; char z[SDL_VARIABLE_LENGTH_ARRAY]; };
|
||||
Use this because GCC 2 needs different magic than other compilers. */
|
||||
#if (defined(__GNUC__) && (__GNUC__ <= 2)) || defined(__CC_ARM) || defined(__cplusplus)
|
||||
#define SDL_VARIABLE_LENGTH_ARRAY 1
|
||||
#else
|
||||
#define SDL_VARIABLE_LENGTH_ARRAY
|
||||
#endif
|
||||
|
||||
#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) || defined(__clang__)
|
||||
#define HAVE_GCC_DIAGNOSTIC_PRAGMA 1
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER // We use constant comparison for generated code
|
||||
#pragma warning(disable : 6326)
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER // SDL_MAX_SMALL_ALLOC_STACKSIZE is smaller than _ALLOCA_S_THRESHOLD and should be generally safe
|
||||
#pragma warning(disable : 6255)
|
||||
#endif
|
||||
#define SDL_MAX_SMALL_ALLOC_STACKSIZE 128
|
||||
#define SDL_small_alloc(type, count, pisstack) ((*(pisstack) = ((sizeof(type) * (count)) < SDL_MAX_SMALL_ALLOC_STACKSIZE)), (*(pisstack) ? SDL_stack_alloc(type, count) : (type *)SDL_malloc(sizeof(type) * (count))))
|
||||
#define SDL_small_free(ptr, isstack) \
|
||||
if ((isstack)) { \
|
||||
SDL_stack_free(ptr); \
|
||||
} else { \
|
||||
SDL_free(ptr); \
|
||||
}
|
||||
|
||||
#include "SDL_build_config.h"
|
||||
|
||||
#include "dynapi/SDL_dynapi.h"
|
||||
|
||||
#if SDL_DYNAMIC_API
|
||||
#include "dynapi/SDL_dynapi_overrides.h"
|
||||
/* force SDL_DECLSPEC off...it's all internal symbols now.
|
||||
These will have actual #defines during SDL_dynapi.c only */
|
||||
#ifdef SDL_DECLSPEC
|
||||
#undef SDL_DECLSPEC
|
||||
#endif
|
||||
#define SDL_DECLSPEC
|
||||
#endif
|
||||
|
||||
#ifdef SDL_PLATFORM_APPLE
|
||||
#ifndef _DARWIN_C_SOURCE
|
||||
#define _DARWIN_C_SOURCE 1 // for memset_pattern4()
|
||||
#endif
|
||||
#include <Availability.h>
|
||||
|
||||
#ifndef __IPHONE_OS_VERSION_MAX_ALLOWED
|
||||
#define __IPHONE_OS_VERSION_MAX_ALLOWED 0
|
||||
#endif
|
||||
#ifndef __APPLETV_OS_VERSION_MAX_ALLOWED
|
||||
#define __APPLETV_OS_VERSION_MAX_ALLOWED 0
|
||||
#endif
|
||||
#ifndef __MAC_OS_X_VERSION_MAX_ALLOWED
|
||||
#define __MAC_OS_X_VERSION_MAX_ALLOWED 0
|
||||
#endif
|
||||
#endif // SDL_PLATFORM_APPLE
|
||||
|
||||
#ifdef HAVE_SYS_TYPES_H
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDIO_H
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#elif defined(HAVE_MALLOC_H)
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDDEF_H
|
||||
#include <stddef.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDARG_H
|
||||
#include <stdarg.h>
|
||||
#endif
|
||||
#ifdef HAVE_STRING_H
|
||||
#ifdef HAVE_MEMORY_H
|
||||
#include <memory.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#endif
|
||||
#ifdef HAVE_STRINGS_H
|
||||
#include <strings.h>
|
||||
#endif
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif
|
||||
#ifdef HAVE_INTTYPES_H
|
||||
#include <inttypes.h>
|
||||
#elif defined(HAVE_STDINT_H)
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
#ifdef HAVE_MATH_H
|
||||
#include <math.h>
|
||||
#endif
|
||||
#ifdef HAVE_FLOAT_H
|
||||
#include <float.h>
|
||||
#endif
|
||||
|
||||
// If you run into a warning that O_CLOEXEC is redefined, update the SDL configuration header for your platform to add HAVE_O_CLOEXEC
|
||||
#ifndef HAVE_O_CLOEXEC
|
||||
#define O_CLOEXEC 0
|
||||
#endif
|
||||
|
||||
/* A few #defines to reduce SDL footprint.
|
||||
Only effective when library is statically linked. */
|
||||
|
||||
/* Optimized functions from 'SDL_blit_0.c'
|
||||
- blit with source bits_per_pixel < 8, palette */
|
||||
#if !defined(SDL_HAVE_BLIT_0) && !defined(SDL_LEAN_AND_MEAN)
|
||||
#define SDL_HAVE_BLIT_0 1
|
||||
#endif
|
||||
|
||||
/* Optimized functions from 'SDL_blit_1.c'
|
||||
- blit with source bytes_per_pixel == 1, palette */
|
||||
#if !defined(SDL_HAVE_BLIT_1) && !defined(SDL_LEAN_AND_MEAN)
|
||||
#define SDL_HAVE_BLIT_1 1
|
||||
#endif
|
||||
|
||||
/* Optimized functions from 'SDL_blit_A.c'
|
||||
- blit with 'SDL_BLENDMODE_BLEND' blending mode */
|
||||
#if !defined(SDL_HAVE_BLIT_A) && !defined(SDL_LEAN_AND_MEAN)
|
||||
#define SDL_HAVE_BLIT_A 1
|
||||
#endif
|
||||
|
||||
/* Optimized functions from 'SDL_blit_N.c'
|
||||
- blit with COLORKEY mode, or nothing */
|
||||
#if !defined(SDL_HAVE_BLIT_N) && !defined(SDL_LEAN_AND_MEAN)
|
||||
#define SDL_HAVE_BLIT_N 1
|
||||
#endif
|
||||
|
||||
/* Optimized functions from 'SDL_blit_N.c'
|
||||
- RGB565 conversion with Lookup tables */
|
||||
#if !defined(SDL_HAVE_BLIT_N_RGB565) && !defined(SDL_LEAN_AND_MEAN)
|
||||
#define SDL_HAVE_BLIT_N_RGB565 1
|
||||
#endif
|
||||
|
||||
/* Optimized functions from 'SDL_blit_AUTO.c'
|
||||
- blit with modulate color, modulate alpha, any blending mode
|
||||
- scaling or not */
|
||||
#if !defined(SDL_HAVE_BLIT_AUTO) && !defined(SDL_LEAN_AND_MEAN)
|
||||
#define SDL_HAVE_BLIT_AUTO 1
|
||||
#endif
|
||||
|
||||
/* Run-Length-Encoding
|
||||
- SDL_SetSurfaceColorKey() called with SDL_RLEACCEL flag */
|
||||
#if !defined(SDL_HAVE_RLE) && !defined(SDL_LEAN_AND_MEAN)
|
||||
#define SDL_HAVE_RLE 1
|
||||
#endif
|
||||
|
||||
/* Software SDL_Renderer
|
||||
- creation of software renderer
|
||||
- *not* general blitting functions
|
||||
- {blend,draw}{fillrect,line,point} internal functions */
|
||||
#if !defined(SDL_VIDEO_RENDER_SW) && !defined(SDL_LEAN_AND_MEAN)
|
||||
#define SDL_VIDEO_RENDER_SW 1
|
||||
#endif
|
||||
|
||||
/* YUV formats
|
||||
- handling of YUV surfaces
|
||||
- blitting and conversion functions */
|
||||
#if !defined(SDL_HAVE_YUV) && !defined(SDL_LEAN_AND_MEAN)
|
||||
#define SDL_HAVE_YUV 1
|
||||
#endif
|
||||
|
||||
#ifdef SDL_CAMERA_DISABLED
|
||||
#undef SDL_CAMERA_DRIVER_ANDROID
|
||||
#undef SDL_CAMERA_DRIVER_COREMEDIA
|
||||
#undef SDL_CAMERA_DRIVER_DUMMY
|
||||
#undef SDL_CAMERA_DRIVER_EMSCRIPTEN
|
||||
#undef SDL_CAMERA_DRIVER_MEDIAFOUNDATION
|
||||
#undef SDL_CAMERA_DRIVER_PIPEWIRE
|
||||
#undef SDL_CAMERA_DRIVER_V4L2
|
||||
#undef SDL_CAMERA_DRIVER_VITA
|
||||
#endif
|
||||
|
||||
#ifdef SDL_RENDER_DISABLED
|
||||
#undef SDL_VIDEO_RENDER_SW
|
||||
#undef SDL_VIDEO_RENDER_D3D
|
||||
#undef SDL_VIDEO_RENDER_D3D11
|
||||
#undef SDL_VIDEO_RENDER_D3D12
|
||||
#undef SDL_VIDEO_RENDER_GPU
|
||||
#undef SDL_VIDEO_RENDER_METAL
|
||||
#undef SDL_VIDEO_RENDER_OGL
|
||||
#undef SDL_VIDEO_RENDER_OGL_ES2
|
||||
#undef SDL_VIDEO_RENDER_PS2
|
||||
#undef SDL_VIDEO_RENDER_PSP
|
||||
#undef SDL_VIDEO_RENDER_VITA_GXM
|
||||
#undef SDL_VIDEO_RENDER_VULKAN
|
||||
#endif // SDL_RENDER_DISABLED
|
||||
|
||||
#ifdef SDL_GPU_DISABLED
|
||||
#undef SDL_GPU_D3D12
|
||||
#undef SDL_GPU_METAL
|
||||
#undef SDL_GPU_VULKAN
|
||||
#undef HAVE_GPU_OPENXR
|
||||
#undef SDL_VIDEO_RENDER_GPU
|
||||
#endif // SDL_GPU_DISABLED
|
||||
|
||||
#if !defined(HAVE_LIBC)
|
||||
// If not using _any_ C runtime, these have to be defined before SDL_thread.h
|
||||
// gets included, so internal SDL_CreateThread calls will not try to reference
|
||||
// the (unavailable and unneeded) _beginthreadex/_endthreadex functions.
|
||||
#define SDL_BeginThreadFunction NULL
|
||||
#define SDL_EndThreadFunction NULL
|
||||
#endif
|
||||
|
||||
#ifdef SDL_NOLONGLONG
|
||||
#error We cannot build a valid SDL3 library without long long support
|
||||
#endif
|
||||
|
||||
/* Enable internal definitions in SDL API headers */
|
||||
#define SDL_INTERNAL
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_intrin.h>
|
||||
|
||||
#define SDL_MAIN_NOIMPL // don't drag in header-only implementation of SDL_main
|
||||
#include <SDL3/SDL_main.h>
|
||||
|
||||
// Set up for C function definitions, even when using C++
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "SDL_utils_c.h"
|
||||
#include "SDL_hashtable.h"
|
||||
|
||||
|
||||
/* SDL_ExitProcess is not declared in any public 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);
|
||||
|
||||
#ifdef HAVE_LIBC
|
||||
#define SDL_abort() abort()
|
||||
#else
|
||||
#define SDL_abort() do { \
|
||||
SDL_TriggerBreakpoint(); \
|
||||
SDL_ExitProcess(42); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
// Macros to save and restore error values
|
||||
#define SDL_PushError() do { \
|
||||
char *saved_error = SDL_strdup(SDL_GetError())
|
||||
|
||||
#define SDL_PopError() \
|
||||
if (saved_error) { \
|
||||
SDL_SetError("%s", saved_error); \
|
||||
SDL_free(saved_error); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#if defined(SDL_DISABLE_INVALID_PARAMS)
|
||||
#ifdef DEBUG
|
||||
// If you define SDL_DISABLE_INVALID_PARAMS, you're promising that you'll
|
||||
// never pass an invalid parameter to SDL, since it may crash or lead to
|
||||
// hard to diagnose bugs. Let's assert that this is true in debug builds.
|
||||
#define OBJECT_VALIDATION_REQUIRED
|
||||
#define CHECK_PARAM(invalid) SDL_assert_always(!(invalid)); if (false)
|
||||
#else
|
||||
#define CHECK_PARAM(invalid) if (false)
|
||||
#endif
|
||||
#elif defined(SDL_ASSERT_INVALID_PARAMS)
|
||||
#define OBJECT_VALIDATION_REQUIRED
|
||||
#define CHECK_PARAM(invalid) SDL_assert_always(!(invalid)); if (invalid)
|
||||
#else
|
||||
#define CHECK_PARAM(invalid) if (invalid)
|
||||
#endif
|
||||
|
||||
// Do any initialization that needs to happen before threads are started
|
||||
extern void SDL_InitMainThread(void);
|
||||
|
||||
// Return true if this thread has initialized video
|
||||
extern bool SDL_IsVideoThread(void);
|
||||
|
||||
/* The internal implementations of these functions have up to nanosecond precision.
|
||||
We can expose these functions as part of the API if we want to later.
|
||||
*/
|
||||
extern bool SDLCALL SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS);
|
||||
extern bool SDLCALL SDL_WaitConditionTimeoutNS(SDL_Condition *cond, SDL_Mutex *mutex, Sint64 timeoutNS);
|
||||
extern bool SDLCALL SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS);
|
||||
|
||||
// Ends C function definitions when using C++
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SDL_internal_h_
|
||||
Vendored
+173
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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"
|
||||
|
||||
// Append
|
||||
bool SDL_ListAppend(SDL_ListNode **head, void *ent)
|
||||
{
|
||||
SDL_ListNode *cursor;
|
||||
SDL_ListNode *node;
|
||||
|
||||
if (!head) {
|
||||
return false;
|
||||
}
|
||||
|
||||
node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
node->entry = ent;
|
||||
node->next = NULL;
|
||||
|
||||
if (*head) {
|
||||
cursor = *head;
|
||||
while (cursor->next) {
|
||||
cursor = cursor->next;
|
||||
}
|
||||
cursor->next = node;
|
||||
} else {
|
||||
*head = node;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SDL_ListInsertAtPosition(SDL_ListNode **head, int pos, void *ent)
|
||||
{
|
||||
SDL_ListNode *cursor;
|
||||
SDL_ListNode *node;
|
||||
int i;
|
||||
|
||||
if (pos == -1) {
|
||||
return SDL_ListAppend(head, ent);
|
||||
}
|
||||
|
||||
if (!pos) {
|
||||
node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
node->entry = ent;
|
||||
|
||||
if (*head) {
|
||||
node->next = *head;
|
||||
} else {
|
||||
node->next = NULL;
|
||||
}
|
||||
|
||||
*head = node;
|
||||
}
|
||||
|
||||
cursor = *head;
|
||||
for (i = 1; i < pos - 1 && cursor; i++) {
|
||||
cursor = cursor->next;
|
||||
}
|
||||
|
||||
if (!cursor) {
|
||||
return SDL_ListAppend(head, ent);
|
||||
}
|
||||
|
||||
node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
node->entry = ent;
|
||||
node->next = cursor->next;
|
||||
cursor->next = node;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Push
|
||||
bool SDL_ListAdd(SDL_ListNode **head, void *ent)
|
||||
{
|
||||
SDL_ListNode *node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
|
||||
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
node->entry = ent;
|
||||
node->next = *head;
|
||||
*head = node;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pop from end as a FIFO (if add with SDL_ListAdd)
|
||||
void SDL_ListPop(SDL_ListNode **head, void **ent)
|
||||
{
|
||||
SDL_ListNode **ptr = head;
|
||||
|
||||
// Invalid or empty
|
||||
if (!head || !*head) {
|
||||
return;
|
||||
}
|
||||
|
||||
while ((*ptr)->next) {
|
||||
ptr = &(*ptr)->next;
|
||||
}
|
||||
|
||||
if (ent) {
|
||||
*ent = (*ptr)->entry;
|
||||
}
|
||||
|
||||
SDL_free(*ptr);
|
||||
*ptr = NULL;
|
||||
}
|
||||
|
||||
void SDL_ListRemove(SDL_ListNode **head, void *ent)
|
||||
{
|
||||
SDL_ListNode **ptr = head;
|
||||
|
||||
while (*ptr) {
|
||||
if ((*ptr)->entry == ent) {
|
||||
SDL_ListNode *tmp = *ptr;
|
||||
*ptr = (*ptr)->next;
|
||||
SDL_free(tmp);
|
||||
return;
|
||||
}
|
||||
ptr = &(*ptr)->next;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_ListClear(SDL_ListNode **head)
|
||||
{
|
||||
SDL_ListNode *l = *head;
|
||||
*head = NULL;
|
||||
while (l) {
|
||||
SDL_ListNode *tmp = l;
|
||||
l = l->next;
|
||||
SDL_free(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
int SDL_ListCountEntries(SDL_ListNode **head)
|
||||
{
|
||||
SDL_ListNode *node;
|
||||
int count = 0;
|
||||
|
||||
for (node = *head; node; node = node->next) {
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_list_h_
|
||||
#define SDL_list_h_
|
||||
|
||||
typedef struct SDL_ListNode
|
||||
{
|
||||
void *entry;
|
||||
struct SDL_ListNode *next;
|
||||
} SDL_ListNode;
|
||||
|
||||
bool SDL_ListAppend(SDL_ListNode **head, void *ent);
|
||||
bool SDL_ListInsertAtPosition(SDL_ListNode **head, int pos, void *ent);
|
||||
bool SDL_ListAdd(SDL_ListNode **head, void *ent);
|
||||
void SDL_ListPop(SDL_ListNode **head, void **ent);
|
||||
void SDL_ListRemove(SDL_ListNode **head, void *ent);
|
||||
void SDL_ListClear(SDL_ListNode **head);
|
||||
int SDL_ListCountEntries(SDL_ListNode **head);
|
||||
|
||||
#endif // SDL_list_h_
|
||||
Vendored
+839
@@ -0,0 +1,839 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_WINDOWS)
|
||||
#include "core/windows/SDL_windows.h"
|
||||
#endif
|
||||
|
||||
#if defined(SDL_PLATFORM_NGAGE)
|
||||
#include "core/ngage/SDL_ngage.h"
|
||||
#endif
|
||||
|
||||
// Simple log messages in SDL
|
||||
|
||||
#include "SDL_log_c.h"
|
||||
|
||||
#ifdef HAVE_STDIO_H
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
#include "stdlib/SDL_vacopy.h"
|
||||
|
||||
// The size of the stack buffer to use for rendering log messages.
|
||||
#define SDL_MAX_LOG_MESSAGE_STACK 256
|
||||
|
||||
#define DEFAULT_CATEGORY -1
|
||||
|
||||
typedef struct SDL_LogLevel
|
||||
{
|
||||
int category;
|
||||
SDL_LogPriority priority;
|
||||
struct SDL_LogLevel *next;
|
||||
} SDL_LogLevel;
|
||||
|
||||
|
||||
// The default log output function
|
||||
static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority, const char *message);
|
||||
|
||||
static void CleanupLogPriorities(void);
|
||||
static void CleanupLogPrefixes(void);
|
||||
|
||||
static SDL_InitState SDL_log_init;
|
||||
static SDL_Mutex *SDL_log_lock;
|
||||
static SDL_Mutex *SDL_log_function_lock;
|
||||
static SDL_LogLevel *SDL_loglevels SDL_GUARDED_BY(SDL_log_lock);
|
||||
static SDL_LogPriority SDL_log_priorities[SDL_LOG_CATEGORY_CUSTOM] SDL_GUARDED_BY(SDL_log_lock);
|
||||
static SDL_LogPriority SDL_log_default_priority SDL_GUARDED_BY(SDL_log_lock);
|
||||
static SDL_LogOutputFunction SDL_log_function SDL_GUARDED_BY(SDL_log_function_lock) = SDL_LogOutput;
|
||||
static void *SDL_log_userdata SDL_GUARDED_BY(SDL_log_function_lock) = NULL;
|
||||
|
||||
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
#endif
|
||||
|
||||
// If this list changes, update the documentation for SDL_HINT_LOGGING
|
||||
static const char * const SDL_priority_names[] = {
|
||||
NULL,
|
||||
"TRACE",
|
||||
"VERBOSE",
|
||||
"DEBUG",
|
||||
"INFO",
|
||||
"WARN",
|
||||
"ERROR",
|
||||
"CRITICAL"
|
||||
};
|
||||
SDL_COMPILE_TIME_ASSERT(priority_names, SDL_arraysize(SDL_priority_names) == SDL_LOG_PRIORITY_COUNT);
|
||||
|
||||
// This is guarded by SDL_log_function_lock because it's the logging function that calls GetLogPriorityPrefix()
|
||||
static char *SDL_priority_prefixes[SDL_LOG_PRIORITY_COUNT] SDL_GUARDED_BY(SDL_log_function_lock);
|
||||
|
||||
// If this list changes, update the documentation for SDL_HINT_LOGGING
|
||||
static const char * const SDL_category_names[] = {
|
||||
"APP",
|
||||
"ERROR",
|
||||
"ASSERT",
|
||||
"SYSTEM",
|
||||
"AUDIO",
|
||||
"VIDEO",
|
||||
"RENDER",
|
||||
"INPUT",
|
||||
"TEST",
|
||||
"GPU"
|
||||
};
|
||||
SDL_COMPILE_TIME_ASSERT(category_names, SDL_arraysize(SDL_category_names) == SDL_LOG_CATEGORY_RESERVED2);
|
||||
|
||||
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
static int SDL_android_priority[] = {
|
||||
ANDROID_LOG_UNKNOWN,
|
||||
ANDROID_LOG_VERBOSE,
|
||||
ANDROID_LOG_VERBOSE,
|
||||
ANDROID_LOG_DEBUG,
|
||||
ANDROID_LOG_INFO,
|
||||
ANDROID_LOG_WARN,
|
||||
ANDROID_LOG_ERROR,
|
||||
ANDROID_LOG_FATAL
|
||||
};
|
||||
SDL_COMPILE_TIME_ASSERT(android_priority, SDL_arraysize(SDL_android_priority) == SDL_LOG_PRIORITY_COUNT);
|
||||
#endif // SDL_PLATFORM_ANDROID
|
||||
|
||||
static void SDLCALL SDL_LoggingChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
|
||||
{
|
||||
SDL_ResetLogPriorities();
|
||||
}
|
||||
|
||||
void SDL_InitLog(void)
|
||||
{
|
||||
if (!SDL_ShouldInit(&SDL_log_init)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If these fail we'll continue without them.
|
||||
SDL_log_lock = SDL_CreateMutex();
|
||||
SDL_log_function_lock = SDL_CreateMutex();
|
||||
|
||||
SDL_AddHintCallback(SDL_HINT_LOGGING, SDL_LoggingChanged, NULL);
|
||||
|
||||
SDL_SetInitialized(&SDL_log_init, true);
|
||||
}
|
||||
|
||||
void SDL_QuitLog(void)
|
||||
{
|
||||
if (!SDL_ShouldQuit(&SDL_log_init)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_RemoveHintCallback(SDL_HINT_LOGGING, SDL_LoggingChanged, NULL);
|
||||
|
||||
CleanupLogPriorities();
|
||||
CleanupLogPrefixes();
|
||||
|
||||
if (SDL_log_lock) {
|
||||
SDL_DestroyMutex(SDL_log_lock);
|
||||
SDL_log_lock = NULL;
|
||||
}
|
||||
if (SDL_log_function_lock) {
|
||||
SDL_DestroyMutex(SDL_log_function_lock);
|
||||
SDL_log_function_lock = NULL;
|
||||
}
|
||||
|
||||
SDL_SetInitialized(&SDL_log_init, false);
|
||||
}
|
||||
|
||||
static void SDL_CheckInitLog(void)
|
||||
{
|
||||
int status = SDL_GetAtomicInt(&SDL_log_init.status);
|
||||
if (status == SDL_INIT_STATUS_INITIALIZED ||
|
||||
(status == SDL_INIT_STATUS_INITIALIZING && SDL_log_init.thread == SDL_GetCurrentThreadID())) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_InitLog();
|
||||
}
|
||||
|
||||
static void CleanupLogPriorities(void)
|
||||
{
|
||||
while (SDL_loglevels) {
|
||||
SDL_LogLevel *entry = SDL_loglevels;
|
||||
SDL_loglevels = entry->next;
|
||||
SDL_free(entry);
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_SetLogPriorities(SDL_LogPriority priority)
|
||||
{
|
||||
SDL_CheckInitLog();
|
||||
|
||||
SDL_LockMutex(SDL_log_lock);
|
||||
{
|
||||
CleanupLogPriorities();
|
||||
|
||||
SDL_log_default_priority = priority;
|
||||
for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
|
||||
SDL_log_priorities[i] = priority;
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(SDL_log_lock);
|
||||
}
|
||||
|
||||
void SDL_SetLogPriority(int category, SDL_LogPriority priority)
|
||||
{
|
||||
SDL_LogLevel *entry;
|
||||
|
||||
SDL_CheckInitLog();
|
||||
|
||||
SDL_LockMutex(SDL_log_lock);
|
||||
{
|
||||
if (category >= 0 && category < SDL_arraysize(SDL_log_priorities)) {
|
||||
SDL_log_priorities[category] = priority;
|
||||
} else {
|
||||
for (entry = SDL_loglevels; entry; entry = entry->next) {
|
||||
if (entry->category == category) {
|
||||
entry->priority = priority;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entry) {
|
||||
entry = (SDL_LogLevel *)SDL_malloc(sizeof(*entry));
|
||||
if (entry) {
|
||||
entry->category = category;
|
||||
entry->priority = priority;
|
||||
entry->next = SDL_loglevels;
|
||||
SDL_loglevels = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(SDL_log_lock);
|
||||
}
|
||||
|
||||
SDL_LogPriority SDL_GetLogPriority(int category)
|
||||
{
|
||||
SDL_LogLevel *entry;
|
||||
SDL_LogPriority priority = SDL_LOG_PRIORITY_INVALID;
|
||||
|
||||
SDL_CheckInitLog();
|
||||
|
||||
// Bypass the lock for known categories
|
||||
// Technically if the priority was set on a different CPU the value might not
|
||||
// be visible on this CPU for a while, but in practice it's fast enough that
|
||||
// this performance improvement is worthwhile.
|
||||
if (category >= 0 && category < SDL_arraysize(SDL_log_priorities)) {
|
||||
return SDL_log_priorities[category];
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_log_lock);
|
||||
{
|
||||
if (category >= 0 && category < SDL_arraysize(SDL_log_priorities)) {
|
||||
priority = SDL_log_priorities[category];
|
||||
} else {
|
||||
for (entry = SDL_loglevels; entry; entry = entry->next) {
|
||||
if (entry->category == category) {
|
||||
priority = entry->priority;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (priority == SDL_LOG_PRIORITY_INVALID) {
|
||||
priority = SDL_log_default_priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(SDL_log_lock);
|
||||
|
||||
return priority;
|
||||
}
|
||||
|
||||
static bool ParseLogCategory(const char *string, size_t length, int *category)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (SDL_isdigit(*string)) {
|
||||
*category = SDL_atoi(string);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (*string == '*') {
|
||||
*category = DEFAULT_CATEGORY;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (i = 0; i < SDL_arraysize(SDL_category_names); ++i) {
|
||||
if (SDL_strncasecmp(string, SDL_category_names[i], length) == 0) {
|
||||
*category = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ParseLogPriority(const char *string, size_t length, SDL_LogPriority *priority)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (SDL_isdigit(*string)) {
|
||||
i = SDL_atoi(string);
|
||||
if (i == 0) {
|
||||
// 0 has a special meaning of "disable this category"
|
||||
*priority = SDL_LOG_PRIORITY_COUNT;
|
||||
return true;
|
||||
}
|
||||
if (i > SDL_LOG_PRIORITY_INVALID && i < SDL_LOG_PRIORITY_COUNT) {
|
||||
*priority = (SDL_LogPriority)i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SDL_strncasecmp(string, "quiet", length) == 0) {
|
||||
*priority = SDL_LOG_PRIORITY_COUNT;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (i = SDL_LOG_PRIORITY_INVALID + 1; i < SDL_LOG_PRIORITY_COUNT; ++i) {
|
||||
if (SDL_strncasecmp(string, SDL_priority_names[i], length) == 0) {
|
||||
*priority = (SDL_LogPriority)i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ParseLogPriorities(const char *hint)
|
||||
{
|
||||
const char *name, *next;
|
||||
int category = DEFAULT_CATEGORY;
|
||||
SDL_LogPriority priority = SDL_LOG_PRIORITY_INVALID;
|
||||
|
||||
if (SDL_strchr(hint, '=') == NULL) {
|
||||
if (ParseLogPriority(hint, SDL_strlen(hint), &priority)) {
|
||||
SDL_SetLogPriorities(priority);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (name = hint; name; name = next) {
|
||||
const char *sep = SDL_strchr(name, '=');
|
||||
if (!sep) {
|
||||
break;
|
||||
}
|
||||
next = SDL_strchr(sep, ',');
|
||||
if (next) {
|
||||
++next;
|
||||
}
|
||||
|
||||
if (ParseLogCategory(name, (sep - name), &category)) {
|
||||
const char *value = sep + 1;
|
||||
size_t len;
|
||||
if (next) {
|
||||
len = (next - value - 1);
|
||||
} else {
|
||||
len = SDL_strlen(value);
|
||||
}
|
||||
if (ParseLogPriority(value, len, &priority)) {
|
||||
if (category == DEFAULT_CATEGORY) {
|
||||
for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
|
||||
if (SDL_log_priorities[i] == SDL_LOG_PRIORITY_INVALID) {
|
||||
SDL_log_priorities[i] = priority;
|
||||
}
|
||||
}
|
||||
SDL_log_default_priority = priority;
|
||||
} else {
|
||||
SDL_SetLogPriority(category, priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_ResetLogPriorities(void)
|
||||
{
|
||||
SDL_CheckInitLog();
|
||||
|
||||
SDL_LockMutex(SDL_log_lock);
|
||||
{
|
||||
const char *env = SDL_getenv("DEBUG_INVOCATION");
|
||||
bool debug = (env && *env && *env != '0');
|
||||
|
||||
CleanupLogPriorities();
|
||||
|
||||
SDL_log_default_priority = SDL_LOG_PRIORITY_INVALID;
|
||||
for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
|
||||
SDL_log_priorities[i] = SDL_LOG_PRIORITY_INVALID;
|
||||
}
|
||||
|
||||
const char *hint = SDL_GetHint(SDL_HINT_LOGGING);
|
||||
if (hint) {
|
||||
ParseLogPriorities(hint);
|
||||
}
|
||||
|
||||
if (SDL_log_default_priority == SDL_LOG_PRIORITY_INVALID) {
|
||||
SDL_log_default_priority = SDL_LOG_PRIORITY_ERROR;
|
||||
}
|
||||
for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
|
||||
if (SDL_log_priorities[i] != SDL_LOG_PRIORITY_INVALID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (i) {
|
||||
case SDL_LOG_CATEGORY_APPLICATION:
|
||||
if (debug) {
|
||||
SDL_log_priorities[i] = SDL_LOG_PRIORITY_DEBUG;
|
||||
} else {
|
||||
SDL_log_priorities[i] = SDL_LOG_PRIORITY_INFO;
|
||||
}
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_ASSERT:
|
||||
SDL_log_priorities[i] = SDL_LOG_PRIORITY_WARN;
|
||||
break;
|
||||
case SDL_LOG_CATEGORY_TEST:
|
||||
SDL_log_priorities[i] = SDL_LOG_PRIORITY_VERBOSE;
|
||||
break;
|
||||
default:
|
||||
if (debug) {
|
||||
SDL_log_priorities[i] = SDL_LOG_PRIORITY_DEBUG;
|
||||
} else {
|
||||
SDL_log_priorities[i] = SDL_LOG_PRIORITY_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(SDL_log_lock);
|
||||
}
|
||||
|
||||
static void CleanupLogPrefixes(void)
|
||||
{
|
||||
for (int i = 0; i < SDL_arraysize(SDL_priority_prefixes); ++i) {
|
||||
if (SDL_priority_prefixes[i]) {
|
||||
SDL_free(SDL_priority_prefixes[i]);
|
||||
SDL_priority_prefixes[i] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char *GetLogPriorityPrefix(SDL_LogPriority priority)
|
||||
{
|
||||
if (priority <= SDL_LOG_PRIORITY_INVALID || priority >= SDL_LOG_PRIORITY_COUNT) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (SDL_priority_prefixes[priority]) {
|
||||
return SDL_priority_prefixes[priority];
|
||||
}
|
||||
|
||||
switch (priority) {
|
||||
case SDL_LOG_PRIORITY_WARN:
|
||||
return "WARNING: ";
|
||||
case SDL_LOG_PRIORITY_ERROR:
|
||||
return "ERROR: ";
|
||||
case SDL_LOG_PRIORITY_CRITICAL:
|
||||
return "ERROR: ";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL_SetLogPriorityPrefix(SDL_LogPriority priority, const char *prefix)
|
||||
{
|
||||
char *prefix_copy;
|
||||
|
||||
CHECK_PARAM(priority <= SDL_LOG_PRIORITY_INVALID || priority >= SDL_LOG_PRIORITY_COUNT) {
|
||||
return SDL_InvalidParamError("priority");
|
||||
}
|
||||
|
||||
if (!prefix || !*prefix) {
|
||||
prefix_copy = SDL_strdup("");
|
||||
} else {
|
||||
prefix_copy = SDL_strdup(prefix);
|
||||
}
|
||||
if (!prefix_copy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_log_function_lock);
|
||||
{
|
||||
if (SDL_priority_prefixes[priority]) {
|
||||
SDL_free(SDL_priority_prefixes[priority]);
|
||||
}
|
||||
SDL_priority_prefixes[priority] = prefix_copy;
|
||||
}
|
||||
SDL_UnlockMutex(SDL_log_function_lock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDL_Log(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogTrace(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_TRACE, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogVerbose(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_VERBOSE, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogDebug(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_DEBUG, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogInfo(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_INFO, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogWarn(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_WARN, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogError(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_ERROR, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogCritical(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_CRITICAL, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogMessage(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, priority, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
static const char *GetCategoryPrefix(int category)
|
||||
{
|
||||
if (category < SDL_LOG_CATEGORY_RESERVED2) {
|
||||
return SDL_category_names[category];
|
||||
}
|
||||
if (category < SDL_LOG_CATEGORY_CUSTOM) {
|
||||
return "RESERVED";
|
||||
}
|
||||
return "CUSTOM";
|
||||
}
|
||||
#endif // SDL_PLATFORM_ANDROID
|
||||
|
||||
void SDL_LogMessageV(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)
|
||||
{
|
||||
char *message = NULL;
|
||||
char stack_buf[SDL_MAX_LOG_MESSAGE_STACK];
|
||||
size_t len_plus_term;
|
||||
int len;
|
||||
va_list aq;
|
||||
|
||||
// Nothing to do if we don't have an output function
|
||||
if (!SDL_log_function) {
|
||||
return;
|
||||
}
|
||||
|
||||
// See if we want to do anything with this message
|
||||
if (priority < SDL_GetLogPriority(category)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Render into stack buffer
|
||||
va_copy(aq, ap);
|
||||
len = SDL_vsnprintf(stack_buf, sizeof(stack_buf), fmt, aq);
|
||||
va_end(aq);
|
||||
|
||||
if (len < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If message truncated, allocate and re-render
|
||||
if (len >= sizeof(stack_buf)) {
|
||||
if (SDL_size_add_check_overflow(len, 1, &len_plus_term)) {
|
||||
// Allocate exactly what we need, including the zero-terminator
|
||||
message = (char *)SDL_malloc(len_plus_term);
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
va_copy(aq, ap);
|
||||
len = SDL_vsnprintf(message, len_plus_term, fmt, aq);
|
||||
va_end(aq);
|
||||
} else {
|
||||
// Allocation would overflow, use truncated message
|
||||
message = stack_buf;
|
||||
len = sizeof(stack_buf);
|
||||
}
|
||||
} else {
|
||||
message = stack_buf;
|
||||
}
|
||||
|
||||
// Chop off final endline.
|
||||
if ((len > 0) && (message[len - 1] == '\n')) {
|
||||
message[--len] = '\0';
|
||||
if ((len > 0) && (message[len - 1] == '\r')) { // catch "\r\n", too.
|
||||
message[--len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_log_function_lock);
|
||||
{
|
||||
SDL_log_function(SDL_log_userdata, category, priority, message);
|
||||
}
|
||||
SDL_UnlockMutex(SDL_log_function_lock);
|
||||
|
||||
// Free only if dynamically allocated
|
||||
if (message != stack_buf) {
|
||||
SDL_free(message);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(SDL_PLATFORM_WIN32) && !defined(SDL_PLATFORM_GDK)
|
||||
enum {
|
||||
CONSOLE_UNATTACHED = 0,
|
||||
CONSOLE_ATTACHED_CONSOLE = 1,
|
||||
CONSOLE_ATTACHED_FILE = 2,
|
||||
CONSOLE_ATTACHED_ERROR = -1,
|
||||
} consoleAttached = CONSOLE_UNATTACHED;
|
||||
|
||||
// Handle to stderr output of console.
|
||||
static HANDLE stderrHandle = NULL;
|
||||
#endif
|
||||
|
||||
static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority,
|
||||
const char *message)
|
||||
{
|
||||
#if defined(SDL_PLATFORM_WINDOWS)
|
||||
// Way too many allocations here, urgh
|
||||
// Note: One can't call SDL_SetError here, since that function itself logs.
|
||||
{
|
||||
char *output;
|
||||
size_t length;
|
||||
LPTSTR tstr;
|
||||
bool isstack;
|
||||
|
||||
#if !defined(SDL_PLATFORM_GDK)
|
||||
BOOL attachResult;
|
||||
DWORD attachError;
|
||||
DWORD consoleMode;
|
||||
DWORD charsWritten;
|
||||
|
||||
// Maybe attach console and get stderr handle
|
||||
if (consoleAttached == CONSOLE_UNATTACHED) {
|
||||
attachResult = AttachConsole(ATTACH_PARENT_PROCESS);
|
||||
if (!attachResult) {
|
||||
attachError = GetLastError();
|
||||
if (attachError == ERROR_INVALID_HANDLE) {
|
||||
// This is expected when running from Visual Studio
|
||||
// OutputDebugString(TEXT("Parent process has no console\r\n"));
|
||||
consoleAttached = CONSOLE_ATTACHED_ERROR;
|
||||
} else if (attachError == ERROR_GEN_FAILURE) {
|
||||
OutputDebugString(TEXT("Could not attach to console of parent process\r\n"));
|
||||
consoleAttached = CONSOLE_ATTACHED_ERROR;
|
||||
} else if (attachError == ERROR_ACCESS_DENIED) {
|
||||
// Already attached
|
||||
consoleAttached = CONSOLE_ATTACHED_CONSOLE;
|
||||
} else {
|
||||
OutputDebugString(TEXT("Error attaching console\r\n"));
|
||||
consoleAttached = CONSOLE_ATTACHED_ERROR;
|
||||
}
|
||||
} else {
|
||||
// Newly attached
|
||||
consoleAttached = CONSOLE_ATTACHED_CONSOLE;
|
||||
}
|
||||
|
||||
if (consoleAttached == CONSOLE_ATTACHED_CONSOLE) {
|
||||
stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
|
||||
|
||||
if (GetConsoleMode(stderrHandle, &consoleMode) == 0) {
|
||||
// WriteConsole fails if the output is redirected to a file. Must use WriteFile instead.
|
||||
consoleAttached = CONSOLE_ATTACHED_FILE;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !defined(SDL_PLATFORM_GDK)
|
||||
length = SDL_strlen(GetLogPriorityPrefix(priority)) + SDL_strlen(message) + 1 + 1 + 1;
|
||||
output = SDL_small_alloc(char, length, &isstack);
|
||||
if (!output) {
|
||||
return;
|
||||
}
|
||||
(void)SDL_snprintf(output, length, "%s%s\r\n", GetLogPriorityPrefix(priority), message);
|
||||
tstr = WIN_UTF8ToString(output);
|
||||
|
||||
// Output to debugger
|
||||
OutputDebugString(tstr);
|
||||
|
||||
#if !defined(SDL_PLATFORM_GDK)
|
||||
// Screen output to stderr, if console was attached.
|
||||
if (consoleAttached == CONSOLE_ATTACHED_CONSOLE) {
|
||||
if (!WriteConsole(stderrHandle, tstr, (DWORD)SDL_tcslen(tstr), &charsWritten, NULL)) {
|
||||
OutputDebugString(TEXT("Error calling WriteConsole\r\n"));
|
||||
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) {
|
||||
OutputDebugString(TEXT("Insufficient heap memory to write message\r\n"));
|
||||
}
|
||||
}
|
||||
|
||||
} else if (consoleAttached == CONSOLE_ATTACHED_FILE) {
|
||||
if (!WriteFile(stderrHandle, output, (DWORD)SDL_strlen(output), &charsWritten, NULL)) {
|
||||
OutputDebugString(TEXT("Error calling WriteFile\r\n"));
|
||||
}
|
||||
}
|
||||
#endif // !defined(SDL_PLATFORM_GDK)
|
||||
|
||||
SDL_free(tstr);
|
||||
SDL_small_free(output, isstack);
|
||||
}
|
||||
#elif defined(SDL_PLATFORM_ANDROID)
|
||||
{
|
||||
char tag[32];
|
||||
|
||||
SDL_snprintf(tag, SDL_arraysize(tag), "SDL/%s", GetCategoryPrefix(category));
|
||||
__android_log_write(SDL_android_priority[priority], tag, message);
|
||||
}
|
||||
#elif defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))
|
||||
/* Technically we don't need Cocoa/UIKit, but that's where this function is defined for now.
|
||||
*/
|
||||
extern void SDL_NSLog(const char *prefix, const char *text);
|
||||
{
|
||||
SDL_NSLog(GetLogPriorityPrefix(priority), message);
|
||||
return;
|
||||
}
|
||||
#elif defined(SDL_PLATFORM_PSP) || defined(SDL_PLATFORM_PS2)
|
||||
{
|
||||
FILE *pFile;
|
||||
pFile = fopen("SDL_Log.txt", "a");
|
||||
if (pFile) {
|
||||
(void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
|
||||
(void)fclose(pFile);
|
||||
}
|
||||
}
|
||||
#elif defined(SDL_PLATFORM_VITA)
|
||||
{
|
||||
FILE *pFile;
|
||||
pFile = fopen("ux0:/data/SDL_Log.txt", "a");
|
||||
if (pFile) {
|
||||
(void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
|
||||
(void)fclose(pFile);
|
||||
}
|
||||
}
|
||||
#elif defined(SDL_PLATFORM_3DS)
|
||||
{
|
||||
FILE *pFile;
|
||||
pFile = fopen("sdmc:/3ds/SDL_Log.txt", "a");
|
||||
if (pFile) {
|
||||
(void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
|
||||
(void)fclose(pFile);
|
||||
}
|
||||
}
|
||||
#elif defined(SDL_PLATFORM_NGAGE)
|
||||
{
|
||||
NGAGE_DebugPrintf("%s%s", GetLogPriorityPrefix(priority), message);
|
||||
#ifdef ENABLE_FILE_LOG
|
||||
FILE *pFile;
|
||||
pFile = fopen("E:/SDL_Log.txt", "a");
|
||||
if (pFile) {
|
||||
(void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
|
||||
(void)fclose(pFile);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#if defined(HAVE_STDIO_H) && \
|
||||
!(defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))) && \
|
||||
!(defined(SDL_PLATFORM_NGAGE)) && \
|
||||
!(defined(SDL_PLATFORM_WIN32))
|
||||
(void)fprintf(stderr, "%s%s\n", GetLogPriorityPrefix(priority), message);
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_LogOutputFunction SDL_GetDefaultLogOutputFunction(void)
|
||||
{
|
||||
return SDL_LogOutput;
|
||||
}
|
||||
|
||||
void SDL_GetLogOutputFunction(SDL_LogOutputFunction *callback, void **userdata)
|
||||
{
|
||||
SDL_LockMutex(SDL_log_function_lock);
|
||||
{
|
||||
if (callback) {
|
||||
*callback = SDL_log_function;
|
||||
}
|
||||
if (userdata) {
|
||||
*userdata = SDL_log_userdata;
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(SDL_log_function_lock);
|
||||
}
|
||||
|
||||
void SDL_SetLogOutputFunction(SDL_LogOutputFunction callback, void *userdata)
|
||||
{
|
||||
SDL_LockMutex(SDL_log_function_lock);
|
||||
{
|
||||
SDL_log_function = callback;
|
||||
SDL_log_userdata = userdata;
|
||||
}
|
||||
SDL_UnlockMutex(SDL_log_function_lock);
|
||||
}
|
||||
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_
|
||||
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_menu_h_
|
||||
#define SDL_menu_h_
|
||||
|
||||
#include "./SDL_list.h"
|
||||
|
||||
typedef enum SDL_MenuItemType
|
||||
{
|
||||
SDL_MENU_ITEM_TYPE_NORMAL,
|
||||
SDL_MENU_ITEM_TYPE_SEPERATOR,
|
||||
SDL_MENU_ITEM_TYPE_CHECKBOX
|
||||
} SDL_MenuItemType;
|
||||
|
||||
typedef enum SDL_MenuItemFlags
|
||||
{
|
||||
SDL_MENU_ITEM_FLAGS_NONE = 0,
|
||||
SDL_MENU_ITEM_FLAGS_DISABLED = 1 << 0,
|
||||
SDL_MENU_ITEM_FLAGS_CHECKED = 1 << 1,
|
||||
SDL_MENU_ITEM_FLAGS_BAR_ITEM = 1 << 2
|
||||
} SDL_MenuItemFlags;
|
||||
|
||||
/* Do not create this struct directly, users of this structure like the DBUSMENU layer use extended versions of it which need to be allocated by specfic functions. */
|
||||
/* This struct is meant to be in an SDL_List just like sub_menu */
|
||||
typedef struct SDL_MenuItem
|
||||
{
|
||||
/* Basic properties */
|
||||
const char *utf8;
|
||||
SDL_MenuItemType type;
|
||||
SDL_MenuItemFlags flags;
|
||||
|
||||
/* Callback */
|
||||
void *cb_data;
|
||||
void (*cb)(struct SDL_MenuItem *, void *);
|
||||
|
||||
/* Submenu, set to NULL if none */
|
||||
SDL_ListNode *sub_menu;
|
||||
|
||||
/* User data slots */
|
||||
void *udata;
|
||||
void *udata2;
|
||||
void *udata3;
|
||||
} SDL_MenuItem;
|
||||
|
||||
#endif // SDL_menu_h_
|
||||
Vendored
+824
@@ -0,0 +1,824 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_hints_c.h"
|
||||
#include "SDL_properties_c.h"
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SDL_PropertyType type;
|
||||
|
||||
union {
|
||||
void *pointer_value;
|
||||
char *string_value;
|
||||
Sint64 number_value;
|
||||
float float_value;
|
||||
bool boolean_value;
|
||||
} value;
|
||||
|
||||
char *string_storage;
|
||||
|
||||
SDL_CleanupPropertyCallback cleanup;
|
||||
void *userdata;
|
||||
} SDL_Property;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SDL_HashTable *props;
|
||||
SDL_Mutex *lock;
|
||||
} SDL_Properties;
|
||||
|
||||
static SDL_InitState SDL_properties_init;
|
||||
static SDL_HashTable *SDL_properties;
|
||||
static SDL_AtomicU32 SDL_last_properties_id;
|
||||
static SDL_AtomicU32 SDL_global_properties;
|
||||
|
||||
|
||||
static void SDL_FreePropertyWithCleanup(const void *key, const void *value, void *data, bool cleanup)
|
||||
{
|
||||
SDL_Property *property = (SDL_Property *)value;
|
||||
if (property) {
|
||||
switch (property->type) {
|
||||
case SDL_PROPERTY_TYPE_POINTER:
|
||||
if (property->cleanup && cleanup) {
|
||||
property->cleanup(property->userdata, property->value.pointer_value);
|
||||
}
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_STRING:
|
||||
SDL_free(property->value.string_value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
SDL_free(property->string_storage);
|
||||
}
|
||||
SDL_free((void *)key);
|
||||
SDL_free((void *)value);
|
||||
}
|
||||
|
||||
static void SDLCALL SDL_FreeProperty(void *data, const void *key, const void *value)
|
||||
{
|
||||
SDL_FreePropertyWithCleanup(key, value, data, true);
|
||||
}
|
||||
|
||||
static void SDL_FreeProperties(SDL_Properties *properties)
|
||||
{
|
||||
if (properties) {
|
||||
SDL_DestroyHashTable(properties->props);
|
||||
SDL_DestroyMutex(properties->lock);
|
||||
SDL_free(properties);
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL_InitProperties(void)
|
||||
{
|
||||
if (!SDL_ShouldInit(&SDL_properties_init)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SDL_properties = SDL_CreateHashTable(0, true, SDL_HashID, SDL_KeyMatchID, NULL, NULL);
|
||||
const bool initialized = (SDL_properties != NULL);
|
||||
SDL_SetInitialized(&SDL_properties_init, initialized);
|
||||
return initialized;
|
||||
}
|
||||
|
||||
static bool SDLCALL FreeOneProperties(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
|
||||
{
|
||||
SDL_FreeProperties((SDL_Properties *)value);
|
||||
return true; // keep iterating.
|
||||
}
|
||||
|
||||
void SDL_QuitProperties(void)
|
||||
{
|
||||
if (!SDL_ShouldQuit(&SDL_properties_init)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_PropertiesID props;
|
||||
do {
|
||||
props = SDL_GetAtomicU32(&SDL_global_properties);
|
||||
} while (!SDL_CompareAndSwapAtomicU32(&SDL_global_properties, props, 0));
|
||||
|
||||
if (props) {
|
||||
SDL_DestroyProperties(props);
|
||||
}
|
||||
|
||||
// this can't just DestroyHashTable with SDL_FreeProperties as the destructor, because
|
||||
// other destructors under this might cause use to attempt a recursive lock on SDL_properties,
|
||||
// which isn't allowed with rwlocks. So manually iterate and free everything.
|
||||
SDL_HashTable *properties = SDL_properties;
|
||||
SDL_properties = NULL;
|
||||
SDL_IterateHashTable(properties, FreeOneProperties, NULL);
|
||||
SDL_DestroyHashTable(properties);
|
||||
|
||||
SDL_SetInitialized(&SDL_properties_init, false);
|
||||
}
|
||||
|
||||
static bool SDL_CheckInitProperties(void)
|
||||
{
|
||||
return SDL_InitProperties();
|
||||
}
|
||||
|
||||
SDL_PropertiesID SDL_GetGlobalProperties(void)
|
||||
{
|
||||
SDL_PropertiesID props = SDL_GetAtomicU32(&SDL_global_properties);
|
||||
if (!props) {
|
||||
props = SDL_CreateProperties();
|
||||
if (!SDL_CompareAndSwapAtomicU32(&SDL_global_properties, 0, props)) {
|
||||
// Somebody else created global properties before us, just use those
|
||||
SDL_DestroyProperties(props);
|
||||
props = SDL_GetAtomicU32(&SDL_global_properties);
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
SDL_PropertiesID SDL_CreateProperties(void)
|
||||
{
|
||||
if (!SDL_CheckInitProperties()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_Properties *properties = (SDL_Properties *)SDL_calloc(1, sizeof(*properties));
|
||||
if (!properties) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
properties->lock = SDL_CreateMutex();
|
||||
if (!properties->lock) {
|
||||
SDL_free(properties);
|
||||
return 0;
|
||||
}
|
||||
|
||||
properties->props = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_FreeProperty, NULL);
|
||||
if (!properties->props) {
|
||||
SDL_DestroyMutex(properties->lock);
|
||||
SDL_free(properties);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_PropertiesID props = 0;
|
||||
while (true) {
|
||||
props = (SDL_GetAtomicU32(&SDL_last_properties_id) + 1);
|
||||
if (props == 0) {
|
||||
continue;
|
||||
} else if (SDL_CompareAndSwapAtomicU32(&SDL_last_properties_id, props - 1, props)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_assert(!SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, NULL)); // should NOT be in the hash table already.
|
||||
|
||||
if (!SDL_InsertIntoHashTable(SDL_properties, (const void *)(uintptr_t)props, properties, false)) {
|
||||
SDL_FreeProperties(properties);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return props; // All done!
|
||||
}
|
||||
|
||||
typedef struct CopyOnePropertyData
|
||||
{
|
||||
SDL_Properties *dst_properties;
|
||||
bool result;
|
||||
} CopyOnePropertyData;
|
||||
|
||||
static bool SDLCALL CopyOneProperty(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
|
||||
{
|
||||
const SDL_Property *src_property = (const SDL_Property *)value;
|
||||
if (src_property->cleanup) {
|
||||
// Can't copy properties with cleanup functions, we don't know how to duplicate the data
|
||||
return true; // keep iterating.
|
||||
}
|
||||
|
||||
CopyOnePropertyData *data = (CopyOnePropertyData *) userdata;
|
||||
SDL_Properties *dst_properties = data->dst_properties;
|
||||
const char *src_name = (const char *)key;
|
||||
SDL_Property *dst_property;
|
||||
|
||||
char *dst_name = SDL_strdup(src_name);
|
||||
if (!dst_name) {
|
||||
data->result = false;
|
||||
return true; // keep iterating (I guess...?)
|
||||
}
|
||||
|
||||
dst_property = (SDL_Property *)SDL_malloc(sizeof(*dst_property));
|
||||
if (!dst_property) {
|
||||
SDL_free(dst_name);
|
||||
data->result = false;
|
||||
return true; // keep iterating (I guess...?)
|
||||
}
|
||||
|
||||
SDL_copyp(dst_property, src_property);
|
||||
if (src_property->type == SDL_PROPERTY_TYPE_STRING) {
|
||||
dst_property->value.string_value = SDL_strdup(src_property->value.string_value);
|
||||
if (!dst_property->value.string_value) {
|
||||
SDL_free(dst_name);
|
||||
SDL_free(dst_property);
|
||||
data->result = false;
|
||||
return true; // keep iterating (I guess...?)
|
||||
}
|
||||
}
|
||||
|
||||
if (!SDL_InsertIntoHashTable(dst_properties->props, dst_name, dst_property, true)) {
|
||||
SDL_FreePropertyWithCleanup(dst_name, dst_property, NULL, false);
|
||||
data->result = false;
|
||||
}
|
||||
|
||||
return true; // keep iterating.
|
||||
}
|
||||
|
||||
bool SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst)
|
||||
{
|
||||
CHECK_PARAM(!src) {
|
||||
return SDL_InvalidParamError("src");
|
||||
}
|
||||
CHECK_PARAM(!dst) {
|
||||
return SDL_InvalidParamError("dst");
|
||||
}
|
||||
|
||||
SDL_Properties *src_properties = NULL;
|
||||
SDL_Properties *dst_properties = NULL;
|
||||
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)src, (const void **)&src_properties);
|
||||
CHECK_PARAM(!src_properties) {
|
||||
return SDL_InvalidParamError("src");
|
||||
}
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)dst, (const void **)&dst_properties);
|
||||
CHECK_PARAM(!dst_properties) {
|
||||
return SDL_InvalidParamError("dst");
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
SDL_LockMutex(src_properties->lock);
|
||||
SDL_LockMutex(dst_properties->lock);
|
||||
{
|
||||
CopyOnePropertyData data = { dst_properties, true };
|
||||
SDL_IterateHashTable(src_properties->props, CopyOneProperty, &data);
|
||||
result = data.result;
|
||||
}
|
||||
SDL_UnlockMutex(dst_properties->lock);
|
||||
SDL_UnlockMutex(src_properties->lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SDL_LockProperties(SDL_PropertiesID props)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
|
||||
CHECK_PARAM(!props) {
|
||||
return SDL_InvalidParamError("props");
|
||||
}
|
||||
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
CHECK_PARAM(!properties) {
|
||||
return SDL_InvalidParamError("props");
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDL_UnlockProperties(SDL_PropertiesID props)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
|
||||
if (!props) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
if (!properties) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
}
|
||||
|
||||
static bool SDL_PrivateSetProperty(SDL_PropertiesID props, const char *name, SDL_Property *property)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
bool result = true;
|
||||
|
||||
CHECK_PARAM(!props) {
|
||||
SDL_FreePropertyWithCleanup(NULL, property, NULL, true);
|
||||
return SDL_InvalidParamError("props");
|
||||
}
|
||||
CHECK_PARAM(!name || !*name) {
|
||||
SDL_FreePropertyWithCleanup(NULL, property, NULL, true);
|
||||
return SDL_InvalidParamError("name");
|
||||
}
|
||||
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
CHECK_PARAM(!properties) {
|
||||
SDL_FreePropertyWithCleanup(NULL, property, NULL, true);
|
||||
return SDL_InvalidParamError("props");
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_RemoveFromHashTable(properties->props, name);
|
||||
if (property) {
|
||||
char *key = SDL_strdup(name);
|
||||
if (!key || !SDL_InsertIntoHashTable(properties->props, key, property, false)) {
|
||||
SDL_FreePropertyWithCleanup(key, property, NULL, true);
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SDL_SetPointerPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, SDL_CleanupPropertyCallback cleanup, void *userdata)
|
||||
{
|
||||
SDL_Property *property;
|
||||
|
||||
if (!value) {
|
||||
if (cleanup) {
|
||||
cleanup(userdata, value);
|
||||
}
|
||||
return SDL_ClearProperty(props, name);
|
||||
}
|
||||
|
||||
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
|
||||
if (!property) {
|
||||
if (cleanup) {
|
||||
cleanup(userdata, value);
|
||||
}
|
||||
SDL_FreePropertyWithCleanup(NULL, property, NULL, false);
|
||||
return false;
|
||||
}
|
||||
property->type = SDL_PROPERTY_TYPE_POINTER;
|
||||
property->value.pointer_value = value;
|
||||
property->cleanup = cleanup;
|
||||
property->userdata = userdata;
|
||||
return SDL_PrivateSetProperty(props, name, property);
|
||||
}
|
||||
|
||||
bool SDL_SetPointerProperty(SDL_PropertiesID props, const char *name, void *value)
|
||||
{
|
||||
SDL_Property *property;
|
||||
|
||||
if (!value) {
|
||||
return SDL_ClearProperty(props, name);
|
||||
}
|
||||
|
||||
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
|
||||
if (!property) {
|
||||
return false;
|
||||
}
|
||||
property->type = SDL_PROPERTY_TYPE_POINTER;
|
||||
property->value.pointer_value = value;
|
||||
return SDL_PrivateSetProperty(props, name, property);
|
||||
}
|
||||
|
||||
static void SDLCALL CleanupFreeableProperty(void *userdata, void *value)
|
||||
{
|
||||
SDL_free(value);
|
||||
}
|
||||
|
||||
bool SDL_SetFreeableProperty(SDL_PropertiesID props, const char *name, void *value)
|
||||
{
|
||||
return SDL_SetPointerPropertyWithCleanup(props, name, value, CleanupFreeableProperty, NULL);
|
||||
}
|
||||
|
||||
static void SDLCALL CleanupSurface(void *userdata, void *value)
|
||||
{
|
||||
SDL_Surface *surface = (SDL_Surface *)value;
|
||||
|
||||
SDL_DestroySurface(surface);
|
||||
}
|
||||
|
||||
bool SDL_SetSurfaceProperty(SDL_PropertiesID props, const char *name, SDL_Surface *surface)
|
||||
{
|
||||
return SDL_SetPointerPropertyWithCleanup(props, name, surface, CleanupSurface, NULL);
|
||||
}
|
||||
|
||||
bool SDL_SetStringProperty(SDL_PropertiesID props, const char *name, const char *value)
|
||||
{
|
||||
SDL_Property *property;
|
||||
|
||||
if (!value) {
|
||||
return SDL_ClearProperty(props, name);
|
||||
}
|
||||
|
||||
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
|
||||
if (!property) {
|
||||
return false;
|
||||
}
|
||||
property->type = SDL_PROPERTY_TYPE_STRING;
|
||||
property->value.string_value = SDL_strdup(value);
|
||||
if (!property->value.string_value) {
|
||||
SDL_free(property);
|
||||
return false;
|
||||
}
|
||||
return SDL_PrivateSetProperty(props, name, property);
|
||||
}
|
||||
|
||||
bool SDL_SetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 value)
|
||||
{
|
||||
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
|
||||
if (!property) {
|
||||
return false;
|
||||
}
|
||||
property->type = SDL_PROPERTY_TYPE_NUMBER;
|
||||
property->value.number_value = value;
|
||||
return SDL_PrivateSetProperty(props, name, property);
|
||||
}
|
||||
|
||||
bool SDL_SetFloatProperty(SDL_PropertiesID props, const char *name, float value)
|
||||
{
|
||||
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
|
||||
if (!property) {
|
||||
return false;
|
||||
}
|
||||
property->type = SDL_PROPERTY_TYPE_FLOAT;
|
||||
property->value.float_value = value;
|
||||
return SDL_PrivateSetProperty(props, name, property);
|
||||
}
|
||||
|
||||
bool SDL_SetBooleanProperty(SDL_PropertiesID props, const char *name, bool value)
|
||||
{
|
||||
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
|
||||
if (!property) {
|
||||
return false;
|
||||
}
|
||||
property->type = SDL_PROPERTY_TYPE_BOOLEAN;
|
||||
property->value.boolean_value = value ? true : false;
|
||||
return SDL_PrivateSetProperty(props, name, property);
|
||||
}
|
||||
|
||||
bool SDL_HasProperty(SDL_PropertiesID props, const char *name)
|
||||
{
|
||||
return (SDL_GetPropertyType(props, name) != SDL_PROPERTY_TYPE_INVALID);
|
||||
}
|
||||
|
||||
SDL_PropertyType SDL_GetPropertyType(SDL_PropertiesID props, const char *name)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
SDL_PropertyType type = SDL_PROPERTY_TYPE_INVALID;
|
||||
|
||||
if (!props) {
|
||||
return SDL_PROPERTY_TYPE_INVALID;
|
||||
}
|
||||
if (!name || !*name) {
|
||||
return SDL_PROPERTY_TYPE_INVALID;
|
||||
}
|
||||
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
if (!properties) {
|
||||
return SDL_PROPERTY_TYPE_INVALID;
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_Property *property = NULL;
|
||||
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
|
||||
type = property->type;
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
void *SDL_GetPointerProperty(SDL_PropertiesID props, const char *name, void *default_value)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
void *value = default_value;
|
||||
|
||||
if (!props) {
|
||||
return value;
|
||||
}
|
||||
if (!name || !*name) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
if (!properties) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Note that taking the lock here only guarantees that we won't read the
|
||||
// hashtable while it's being modified. The value itself can easily be
|
||||
// freed from another thread after it is returned here.
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_Property *property = NULL;
|
||||
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
|
||||
if (property->type == SDL_PROPERTY_TYPE_POINTER) {
|
||||
value = property->value.pointer_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
const char *SDL_GetStringProperty(SDL_PropertiesID props, const char *name, const char *default_value)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
const char *value = default_value;
|
||||
|
||||
if (!props) {
|
||||
return value;
|
||||
}
|
||||
if (!name || !*name) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
if (!properties) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_Property *property = NULL;
|
||||
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
|
||||
switch (property->type) {
|
||||
case SDL_PROPERTY_TYPE_STRING:
|
||||
value = property->value.string_value;
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_NUMBER:
|
||||
if (property->string_storage) {
|
||||
value = property->string_storage;
|
||||
} else {
|
||||
SDL_asprintf(&property->string_storage, "%" SDL_PRIs64, property->value.number_value);
|
||||
if (property->string_storage) {
|
||||
value = property->string_storage;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_FLOAT:
|
||||
if (property->string_storage) {
|
||||
value = property->string_storage;
|
||||
} else {
|
||||
SDL_asprintf(&property->string_storage, "%f", property->value.float_value);
|
||||
if (property->string_storage) {
|
||||
value = property->string_storage;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_BOOLEAN:
|
||||
value = property->value.boolean_value ? "true" : "false";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
Sint64 SDL_GetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 default_value)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
Sint64 value = default_value;
|
||||
|
||||
if (!props) {
|
||||
return value;
|
||||
}
|
||||
if (!name || !*name) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
if (!properties) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_Property *property = NULL;
|
||||
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
|
||||
switch (property->type) {
|
||||
case SDL_PROPERTY_TYPE_STRING:
|
||||
value = (Sint64)SDL_strtoll(property->value.string_value, NULL, 0);
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_NUMBER:
|
||||
value = property->value.number_value;
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_FLOAT:
|
||||
value = (Sint64)SDL_round((double)property->value.float_value);
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_BOOLEAN:
|
||||
value = property->value.boolean_value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
float SDL_GetFloatProperty(SDL_PropertiesID props, const char *name, float default_value)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
float value = default_value;
|
||||
|
||||
if (!props) {
|
||||
return value;
|
||||
}
|
||||
if (!name || !*name) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
if (!properties) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_Property *property = NULL;
|
||||
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
|
||||
switch (property->type) {
|
||||
case SDL_PROPERTY_TYPE_STRING:
|
||||
value = (float)SDL_atof(property->value.string_value);
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_NUMBER:
|
||||
value = (float)property->value.number_value;
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_FLOAT:
|
||||
value = property->value.float_value;
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_BOOLEAN:
|
||||
value = (float)property->value.boolean_value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
bool SDL_GetBooleanProperty(SDL_PropertiesID props, const char *name, bool default_value)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
bool value = default_value ? true : false;
|
||||
|
||||
if (!props) {
|
||||
return value;
|
||||
}
|
||||
if (!name || !*name) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
if (!properties) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_Property *property = NULL;
|
||||
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
|
||||
switch (property->type) {
|
||||
case SDL_PROPERTY_TYPE_STRING:
|
||||
value = SDL_GetStringBoolean(property->value.string_value, default_value);
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_NUMBER:
|
||||
value = (property->value.number_value != 0);
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_FLOAT:
|
||||
value = (property->value.float_value != 0.0f);
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_BOOLEAN:
|
||||
value = property->value.boolean_value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
bool SDL_ClearProperty(SDL_PropertiesID props, const char *name)
|
||||
{
|
||||
return SDL_PrivateSetProperty(props, name, NULL);
|
||||
}
|
||||
|
||||
typedef struct EnumerateOnePropertyData
|
||||
{
|
||||
SDL_EnumeratePropertiesCallback callback;
|
||||
void *userdata;
|
||||
SDL_PropertiesID props;
|
||||
} EnumerateOnePropertyData;
|
||||
|
||||
|
||||
static bool SDLCALL EnumerateOneProperty(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
|
||||
{
|
||||
(void) table;
|
||||
(void) value;
|
||||
const EnumerateOnePropertyData *data = (const EnumerateOnePropertyData *) userdata;
|
||||
data->callback(data->userdata, data->props, (const char *)key);
|
||||
return true; // keep iterating.
|
||||
}
|
||||
|
||||
bool SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCallback callback, void *userdata)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
|
||||
CHECK_PARAM(!props) {
|
||||
return SDL_InvalidParamError("props");
|
||||
}
|
||||
CHECK_PARAM(!callback) {
|
||||
return SDL_InvalidParamError("callback");
|
||||
}
|
||||
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
CHECK_PARAM(!properties) {
|
||||
return SDL_InvalidParamError("props");
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
EnumerateOnePropertyData data = { callback, userdata, props };
|
||||
SDL_IterateHashTable(properties->props, EnumerateOneProperty, &data);
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void SDLCALL SDL_DumpPropertiesCallback(void *userdata, SDL_PropertiesID props, const char *name)
|
||||
{
|
||||
switch (SDL_GetPropertyType(props, name)) {
|
||||
case SDL_PROPERTY_TYPE_POINTER:
|
||||
SDL_Log("%s: %p", name, SDL_GetPointerProperty(props, name, NULL));
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_STRING:
|
||||
SDL_Log("%s: \"%s\"", name, SDL_GetStringProperty(props, name, ""));
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_NUMBER:
|
||||
{
|
||||
Sint64 value = SDL_GetNumberProperty(props, name, 0);
|
||||
SDL_Log("%s: %" SDL_PRIs64 " (%" SDL_PRIx64 ")", name, value, value);
|
||||
}
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_FLOAT:
|
||||
SDL_Log("%s: %g", name, SDL_GetFloatProperty(props, name, 0.0f));
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_BOOLEAN:
|
||||
SDL_Log("%s: %s", name, SDL_GetBooleanProperty(props, name, false) ? "true" : "false");
|
||||
break;
|
||||
default:
|
||||
SDL_Log("%s UNKNOWN TYPE", name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL_DumpProperties(SDL_PropertiesID props)
|
||||
{
|
||||
return SDL_EnumerateProperties(props, SDL_DumpPropertiesCallback, NULL);
|
||||
}
|
||||
|
||||
void SDL_DestroyProperties(SDL_PropertiesID props)
|
||||
{
|
||||
if (props) {
|
||||
// this can't just use RemoveFromHashTable with SDL_FreeProperties as the destructor, because
|
||||
// other destructors under this might cause use to attempt a recursive lock on SDL_properties,
|
||||
// which isn't allowed with rwlocks. So manually look it up and remove/free it.
|
||||
SDL_Properties *properties = NULL;
|
||||
if (SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties)) {
|
||||
SDL_FreeProperties(properties);
|
||||
SDL_RemoveFromHashTable(SDL_properties, (const void *)(uintptr_t)props);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
extern bool SDL_InitProperties(void);
|
||||
extern bool SDL_SetFreeableProperty(SDL_PropertiesID props, const char *name, void *value);
|
||||
extern bool SDL_SetSurfaceProperty(SDL_PropertiesID props, const char *name, SDL_Surface *surface);
|
||||
extern bool SDL_DumpProperties(SDL_PropertiesID props);
|
||||
extern void SDL_QuitProperties(void);
|
||||
Vendored
+625
@@ -0,0 +1,625 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "joystick/SDL_joystick_c.h" // For SDL_GetGamepadTypeFromVIDPID()
|
||||
|
||||
#ifdef SDL_PLATFORM_EMSCRIPTEN
|
||||
#include <emscripten.h>
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE void Emscripten_force_free(void *ptr)
|
||||
{
|
||||
free(ptr); // This should NOT be SDL_free()
|
||||
}
|
||||
#endif
|
||||
|
||||
// Common utility functions that aren't in the public API
|
||||
|
||||
int SDL_powerof2(int x)
|
||||
{
|
||||
int value;
|
||||
|
||||
if (x <= 0) {
|
||||
// Return some sane value - we shouldn't hit this in our use cases
|
||||
return 1;
|
||||
}
|
||||
|
||||
// This trick works for 32-bit values
|
||||
{
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_powerof2, sizeof(x) == sizeof(Uint32));
|
||||
}
|
||||
value = x;
|
||||
value -= 1;
|
||||
value |= value >> 1;
|
||||
value |= value >> 2;
|
||||
value |= value >> 4;
|
||||
value |= value >> 8;
|
||||
value |= value >> 16;
|
||||
value += 1;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
Uint32 SDL_CalculateGCD(Uint32 a, Uint32 b)
|
||||
{
|
||||
if (b == 0) {
|
||||
return a;
|
||||
}
|
||||
return SDL_CalculateGCD(b, (a % b));
|
||||
}
|
||||
|
||||
// Algorithm adapted with thanks from John Cook's blog post:
|
||||
// http://www.johndcook.com/blog/2010/10/20/best-rational-approximation
|
||||
void SDL_CalculateFraction(float x, int *numerator, int *denominator)
|
||||
{
|
||||
const int N = 1000;
|
||||
int a = 0, b = 1;
|
||||
int c = 1, d = 0;
|
||||
|
||||
while (b <= N && d <= N) {
|
||||
float mediant = (float)(a + c) / (b + d);
|
||||
if (x == mediant) {
|
||||
if (b + d <= N) {
|
||||
*numerator = a + c;
|
||||
*denominator = b + d;
|
||||
} else if (d > b) {
|
||||
*numerator = c;
|
||||
*denominator = d;
|
||||
} else {
|
||||
*numerator = a;
|
||||
*denominator = b;
|
||||
}
|
||||
return;
|
||||
} else if (x > mediant) {
|
||||
a = a + c;
|
||||
b = b + d;
|
||||
} else {
|
||||
c = a + c;
|
||||
d = b + d;
|
||||
}
|
||||
}
|
||||
if (b > N) {
|
||||
*numerator = c;
|
||||
*denominator = d;
|
||||
} else {
|
||||
*numerator = a;
|
||||
*denominator = b;
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL_startswith(const char *string, const char *prefix)
|
||||
{
|
||||
if (string && prefix &&
|
||||
SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDL_endswith(const char *string, const char *suffix)
|
||||
{
|
||||
size_t string_length = string ? SDL_strlen(string) : 0;
|
||||
size_t suffix_length = suffix ? SDL_strlen(suffix) : 0;
|
||||
|
||||
if (suffix_length > 0 && suffix_length <= string_length) {
|
||||
if (SDL_memcmp(string + string_length - suffix_length, suffix, suffix_length) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_COMPILE_TIME_ASSERT(sizeof_object_id, sizeof(int) == sizeof(Uint32));
|
||||
|
||||
Uint32 SDL_GetNextObjectID(void)
|
||||
{
|
||||
static SDL_AtomicInt last_id;
|
||||
|
||||
Uint32 id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
|
||||
if (id == 0) {
|
||||
id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
static SDL_InitState SDL_objects_init;
|
||||
static SDL_HashTable *SDL_objects;
|
||||
bool SDL_object_validation = true;
|
||||
|
||||
static void SDLCALL SDL_InvalidParamChecksChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
|
||||
{
|
||||
bool validation_enabled = true;
|
||||
|
||||
#ifndef OBJECT_VALIDATION_REQUIRED
|
||||
if (hint) {
|
||||
switch (*hint) {
|
||||
case '0':
|
||||
case '1':
|
||||
validation_enabled = false;
|
||||
break;
|
||||
case '2':
|
||||
validation_enabled = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif // !OBJECT_VALIDATION_REQUIRED
|
||||
|
||||
SDL_object_validation = validation_enabled;
|
||||
}
|
||||
|
||||
static Uint32 SDLCALL SDL_HashObject(void *unused, const void *key)
|
||||
{
|
||||
return (Uint32)(uintptr_t)key;
|
||||
}
|
||||
|
||||
static bool SDL_KeyMatchObject(void *unused, const void *a, const void *b)
|
||||
{
|
||||
return (a == b);
|
||||
}
|
||||
|
||||
void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid)
|
||||
{
|
||||
SDL_assert(object != NULL);
|
||||
|
||||
if (SDL_ShouldInit(&SDL_objects_init)) {
|
||||
SDL_objects = SDL_CreateHashTable(0, true, SDL_HashObject, SDL_KeyMatchObject, NULL, NULL);
|
||||
const bool initialized = (SDL_objects != NULL);
|
||||
SDL_SetInitialized(&SDL_objects_init, initialized);
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
SDL_AddHintCallback(SDL_HINT_INVALID_PARAM_CHECKS, SDL_InvalidParamChecksChanged, NULL);
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
SDL_InsertIntoHashTable(SDL_objects, object, (void *)(uintptr_t)type, true);
|
||||
} else {
|
||||
SDL_RemoveFromHashTable(SDL_objects, object);
|
||||
}
|
||||
}
|
||||
|
||||
bool SDL_FindObject(void *object, SDL_ObjectType type)
|
||||
{
|
||||
const void *object_type;
|
||||
if (!SDL_FindInHashTable(SDL_objects, object, &object_type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (((SDL_ObjectType)(uintptr_t)object_type) == type);
|
||||
}
|
||||
|
||||
typedef struct GetOneObjectData
|
||||
{
|
||||
const SDL_ObjectType type;
|
||||
void **objects;
|
||||
const int count;
|
||||
int num_objects;
|
||||
} GetOneObjectData;
|
||||
|
||||
static bool SDLCALL GetOneObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type)
|
||||
{
|
||||
GetOneObjectData *data = (GetOneObjectData *) userdata;
|
||||
if ((SDL_ObjectType)(uintptr_t)object_type == data->type) {
|
||||
if (data->num_objects < data->count) {
|
||||
data->objects[data->num_objects] = (void *)object;
|
||||
}
|
||||
++data->num_objects;
|
||||
}
|
||||
return true; // keep iterating.
|
||||
}
|
||||
|
||||
|
||||
int SDL_GetObjects(SDL_ObjectType type, void **objects, int count)
|
||||
{
|
||||
GetOneObjectData data = { type, objects, count, 0 };
|
||||
SDL_IterateHashTable(SDL_objects, GetOneObject, &data);
|
||||
return data.num_objects;
|
||||
}
|
||||
|
||||
static bool SDLCALL LogOneLeakedObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type)
|
||||
{
|
||||
const char *type = "unknown object";
|
||||
switch ((SDL_ObjectType)(uintptr_t)object_type) {
|
||||
#define SDLOBJTYPECASE(typ, name) case SDL_OBJECT_TYPE_##typ: type = name; break
|
||||
SDLOBJTYPECASE(WINDOW, "SDL_Window");
|
||||
SDLOBJTYPECASE(RENDERER, "SDL_Renderer");
|
||||
SDLOBJTYPECASE(TEXTURE, "SDL_Texture");
|
||||
SDLOBJTYPECASE(JOYSTICK, "SDL_Joystick");
|
||||
SDLOBJTYPECASE(GAMEPAD, "SDL_Gamepad");
|
||||
SDLOBJTYPECASE(HAPTIC, "SDL_Haptic");
|
||||
SDLOBJTYPECASE(SENSOR, "SDL_Sensor");
|
||||
SDLOBJTYPECASE(HIDAPI_DEVICE, "hidapi device");
|
||||
SDLOBJTYPECASE(HIDAPI_JOYSTICK, "hidapi joystick");
|
||||
SDLOBJTYPECASE(THREAD, "thread");
|
||||
SDLOBJTYPECASE(TRAY, "SDL_Tray");
|
||||
#undef SDLOBJTYPECASE
|
||||
default: break;
|
||||
}
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Leaked %s (%p)", type, object);
|
||||
return true; // keep iterating.
|
||||
}
|
||||
|
||||
void SDL_SetObjectsInvalid(void)
|
||||
{
|
||||
if (SDL_ShouldQuit(&SDL_objects_init)) {
|
||||
// Log any leaked objects
|
||||
SDL_IterateHashTable(SDL_objects, LogOneLeakedObject, NULL);
|
||||
SDL_DestroyHashTable(SDL_objects);
|
||||
SDL_objects = NULL;
|
||||
SDL_SetInitialized(&SDL_objects_init, false);
|
||||
SDL_RemoveHintCallback(SDL_HINT_INVALID_PARAM_CHECKS, SDL_InvalidParamChecksChanged, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static int SDL_URIDecode(const char *src, char *dst, int len)
|
||||
{
|
||||
int ri, wi, di;
|
||||
char decode = '\0';
|
||||
if (!src || !dst || len < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (len == 0) {
|
||||
len = (int)SDL_strlen(src);
|
||||
}
|
||||
for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) {
|
||||
if (di == 0) {
|
||||
// start decoding
|
||||
if (src[ri] == '%') {
|
||||
decode = '\0';
|
||||
di += 1;
|
||||
continue;
|
||||
}
|
||||
// normal write
|
||||
dst[wi] = src[ri];
|
||||
wi += 1;
|
||||
} else if (di == 1 || di == 2) {
|
||||
char off = '\0';
|
||||
char isa = src[ri] >= 'a' && src[ri] <= 'f';
|
||||
char isA = src[ri] >= 'A' && src[ri] <= 'F';
|
||||
char isn = src[ri] >= '0' && src[ri] <= '9';
|
||||
if (!(isa || isA || isn)) {
|
||||
// not a hexadecimal
|
||||
int sri;
|
||||
for (sri = ri - di; sri <= ri; sri += 1) {
|
||||
dst[wi] = src[sri];
|
||||
wi += 1;
|
||||
}
|
||||
di = 0;
|
||||
continue;
|
||||
}
|
||||
// itsy bitsy magicsy
|
||||
if (isn) {
|
||||
off = 0 - '0';
|
||||
} else if (isa) {
|
||||
off = 10 - 'a';
|
||||
} else if (isA) {
|
||||
off = 10 - 'A';
|
||||
}
|
||||
decode |= (src[ri] + off) << (2 - di) * 4;
|
||||
if (di == 2) {
|
||||
dst[wi] = decode;
|
||||
wi += 1;
|
||||
di = 0;
|
||||
} else {
|
||||
di += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
dst[wi] = '\0';
|
||||
return wi;
|
||||
}
|
||||
|
||||
int SDL_URIToLocal(const char *src, char *dst)
|
||||
{
|
||||
if (SDL_memcmp(src, "file:/", 6) == 0) {
|
||||
src += 6; // local file?
|
||||
} else if (SDL_strstr(src, ":/") != NULL) {
|
||||
return -1; // wrong scheme
|
||||
}
|
||||
|
||||
bool local = src[0] != '/' || (src[0] != '\0' && src[1] == '/');
|
||||
|
||||
// Check the hostname, if present. RFC 3986 states that the hostname component of a URI is not case-sensitive.
|
||||
if (!local && src[0] == '/' && src[2] != '/') {
|
||||
char *hostname_end = SDL_strchr(src + 1, '/');
|
||||
if (hostname_end) {
|
||||
const size_t src_len = hostname_end - (src + 1);
|
||||
size_t hostname_len;
|
||||
|
||||
#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS)
|
||||
char hostname[257];
|
||||
if (gethostname(hostname, 255) == 0) {
|
||||
hostname[256] = '\0';
|
||||
hostname_len = SDL_strlen(hostname);
|
||||
if (hostname_len == src_len && SDL_strncasecmp(src + 1, hostname, src_len) == 0) {
|
||||
src = hostname_end + 1;
|
||||
local = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!local) {
|
||||
static const char *localhost = "localhost";
|
||||
hostname_len = SDL_strlen(localhost);
|
||||
if (hostname_len == src_len && SDL_strncasecmp(src + 1, localhost, src_len) == 0) {
|
||||
src = hostname_end + 1;
|
||||
local = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (local) {
|
||||
// Convert URI escape sequences to real characters
|
||||
if (src[0] == '/') {
|
||||
src++;
|
||||
} else {
|
||||
src--;
|
||||
}
|
||||
return SDL_URIDecode(src, dst, 0);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool SDL_IsURI(const char *uri)
|
||||
{
|
||||
/* A valid URI begins with a letter and is followed by any sequence of
|
||||
* letters, digits, '+', '.', or '-'.
|
||||
*/
|
||||
if (!uri) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The first character of the scheme must be a letter.
|
||||
if (!((*uri >= 'a' && *uri <= 'z') || (*uri >= 'A' && *uri <= 'Z'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the colon is found before encountering the end of the string or
|
||||
* any invalid characters, the scheme can be considered valid.
|
||||
*/
|
||||
while (*uri) {
|
||||
if (!((*uri >= 'a' && *uri <= 'z') ||
|
||||
(*uri >= 'A' && *uri <= 'Z') ||
|
||||
(*uri >= '0' && *uri <= '9') ||
|
||||
*uri == '+' || *uri == '-' || *uri == '.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*++uri == ':') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is a set of per-thread persistent strings that we can return from the SDL API.
|
||||
// This is used for short strings that might persist past the lifetime of the object
|
||||
// they are related to.
|
||||
|
||||
static SDL_TLSID SDL_string_storage;
|
||||
|
||||
static void SDL_FreePersistentStrings( void *value )
|
||||
{
|
||||
SDL_HashTable *strings = (SDL_HashTable *)value;
|
||||
SDL_DestroyHashTable(strings);
|
||||
}
|
||||
|
||||
const char *SDL_GetPersistentString(const char *string)
|
||||
{
|
||||
if (!string) {
|
||||
return NULL;
|
||||
}
|
||||
if (!*string) {
|
||||
return "";
|
||||
}
|
||||
|
||||
SDL_HashTable *strings = (SDL_HashTable *)SDL_GetTLS(&SDL_string_storage);
|
||||
if (!strings) {
|
||||
strings = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_DestroyHashValue, NULL);
|
||||
if (!strings) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_SetTLS(&SDL_string_storage, strings, SDL_FreePersistentStrings);
|
||||
}
|
||||
|
||||
const char *result;
|
||||
if (!SDL_FindInHashTable(strings, string, (const void **)&result)) {
|
||||
char *new_string = SDL_strdup(string);
|
||||
if (!new_string) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// If the hash table insert fails, at least we can return the string we allocated
|
||||
SDL_InsertIntoHashTable(strings, new_string, new_string, false);
|
||||
result = new_string;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int PrefixMatch(const char *a, const char *b)
|
||||
{
|
||||
int matchlen = 0;
|
||||
// Fixes the "HORI HORl Taiko No Tatsujin Drum Controller"
|
||||
if (SDL_strncmp(a, "HORI ", 5) == 0 && SDL_strncmp(b, "HORl ", 5) == 0) {
|
||||
return 5;
|
||||
}
|
||||
while (*a && *b) {
|
||||
if (SDL_tolower((unsigned char)*a++) == SDL_tolower((unsigned char)*b++)) {
|
||||
++matchlen;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return matchlen;
|
||||
}
|
||||
|
||||
char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name)
|
||||
{
|
||||
static struct
|
||||
{
|
||||
const char *prefix;
|
||||
const char *replacement;
|
||||
} replacements[] = {
|
||||
{ "(Standard system devices) ", "" },
|
||||
{ "8BitDo Tech Ltd", "8BitDo" },
|
||||
{ "ASTRO Gaming", "ASTRO" },
|
||||
{ "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" },
|
||||
{ "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" },
|
||||
{ "HORI CO.,LTD.", "HORI" },
|
||||
{ "HORI CO.,LTD", "HORI" },
|
||||
{ "Mad Catz Inc.", "Mad Catz" },
|
||||
{ "Nintendo Co., Ltd.", "Nintendo" },
|
||||
{ "NVIDIA Corporation ", "" },
|
||||
{ "Performance Designed Products", "PDP" },
|
||||
{ "QANBA USA, LLC", "Qanba" },
|
||||
{ "QANBA USA,LLC", "Qanba" },
|
||||
{ "Unknown ", "" },
|
||||
};
|
||||
char *name = NULL;
|
||||
size_t i, len;
|
||||
|
||||
if (!vendor_name) {
|
||||
vendor_name = "";
|
||||
}
|
||||
if (!product_name) {
|
||||
product_name = "";
|
||||
}
|
||||
|
||||
while (*vendor_name == ' ') {
|
||||
++vendor_name;
|
||||
}
|
||||
while (*product_name == ' ') {
|
||||
++product_name;
|
||||
}
|
||||
|
||||
if (*vendor_name && *product_name) {
|
||||
len = (SDL_strlen(vendor_name) + 1 + SDL_strlen(product_name) + 1);
|
||||
name = (char *)SDL_malloc(len);
|
||||
if (name) {
|
||||
(void)SDL_snprintf(name, len, "%s %s", vendor_name, product_name);
|
||||
}
|
||||
} else if (*product_name) {
|
||||
name = SDL_strdup(product_name);
|
||||
} else if (vendor || product) {
|
||||
// Couldn't find a controller name, try to give it one based on device type
|
||||
switch (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, true)) {
|
||||
case SDL_GAMEPAD_TYPE_XBOX360:
|
||||
name = SDL_strdup("Xbox 360 Controller");
|
||||
break;
|
||||
case SDL_GAMEPAD_TYPE_XBOXONE:
|
||||
name = SDL_strdup("Xbox One Controller");
|
||||
break;
|
||||
case SDL_GAMEPAD_TYPE_PS3:
|
||||
name = SDL_strdup("PS3 Controller");
|
||||
break;
|
||||
case SDL_GAMEPAD_TYPE_PS4:
|
||||
name = SDL_strdup("PS4 Controller");
|
||||
break;
|
||||
case SDL_GAMEPAD_TYPE_PS5:
|
||||
name = SDL_strdup("DualSense Wireless Controller");
|
||||
break;
|
||||
case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
|
||||
name = SDL_strdup("Nintendo Switch Pro Controller");
|
||||
break;
|
||||
default:
|
||||
len = (6 + 1 + 6 + 1);
|
||||
name = (char *)SDL_malloc(len);
|
||||
if (name) {
|
||||
(void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (default_name) {
|
||||
name = SDL_strdup(default_name);
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Trim trailing whitespace
|
||||
for (len = SDL_strlen(name); (len > 0 && name[len - 1] == ' '); --len) {
|
||||
// continue
|
||||
}
|
||||
name[len] = '\0';
|
||||
|
||||
// Compress duplicate spaces
|
||||
for (i = 0; i < (len - 1);) {
|
||||
if (name[i] == ' ' && name[i + 1] == ' ') {
|
||||
SDL_memmove(&name[i], &name[i + 1], (len - i));
|
||||
--len;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform any manufacturer replacements
|
||||
for (i = 0; i < SDL_arraysize(replacements); ++i) {
|
||||
size_t prefixlen = SDL_strlen(replacements[i].prefix);
|
||||
if (SDL_strncasecmp(name, replacements[i].prefix, prefixlen) == 0) {
|
||||
size_t replacementlen = SDL_strlen(replacements[i].replacement);
|
||||
if (replacementlen <= prefixlen) {
|
||||
SDL_memcpy(name, replacements[i].replacement, replacementlen);
|
||||
SDL_memmove(name + replacementlen, name + prefixlen, (len - prefixlen) + 1);
|
||||
len -= (prefixlen - replacementlen);
|
||||
} else {
|
||||
// FIXME: Need to handle the expand case by reallocating the string
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove duplicate manufacturer or product in the name
|
||||
* e.g. Razer Razer Raiju Tournament Edition Wired
|
||||
*/
|
||||
for (i = 1; i < (len - 1); ++i) {
|
||||
int matchlen = PrefixMatch(name, &name[i]);
|
||||
while (matchlen > 0) {
|
||||
if (name[matchlen] == ' ' || name[matchlen] == '-') {
|
||||
SDL_memmove(name, name + matchlen + 1, len - matchlen);
|
||||
break;
|
||||
}
|
||||
--matchlen;
|
||||
}
|
||||
if (matchlen > 0) {
|
||||
// We matched the manufacturer's name and removed it
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
void SDL_DebugLogBackend(const char *subsystem, const char *backend)
|
||||
{
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "SDL chose %s backend '%s'", subsystem, backend);
|
||||
}
|
||||
|
||||
Vendored
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
// This is included in SDL_internal.h
|
||||
//#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_utils_h_
|
||||
#define SDL_utils_h_
|
||||
|
||||
// Common utility functions that aren't in the public API
|
||||
|
||||
// Return the smallest power of 2 greater than or equal to 'x'
|
||||
extern int SDL_powerof2(int x);
|
||||
|
||||
extern Uint32 SDL_CalculateGCD(Uint32 a, Uint32 b);
|
||||
extern void SDL_CalculateFraction(float x, int *numerator, int *denominator);
|
||||
|
||||
extern bool SDL_startswith(const char *string, const char *prefix);
|
||||
extern bool SDL_endswith(const char *string, const char *suffix);
|
||||
|
||||
/** Convert URI to a local filename, stripping the "file://"
|
||||
* preamble and hostname if present, and writes the result
|
||||
* to the dst buffer. Since URI-encoded characters take
|
||||
* three times the space of normal characters, src and dst
|
||||
* can safely point to the same buffer for in situ conversion.
|
||||
*
|
||||
* Returns the number of decoded bytes that wound up in
|
||||
* the destination buffer, excluding the terminating NULL byte.
|
||||
*
|
||||
* On error, -1 is returned.
|
||||
*/
|
||||
extern int SDL_URIToLocal(const char *src, char *dst);
|
||||
|
||||
/// Determine if a URI is valid by validating the scheme.
|
||||
extern bool SDL_IsURI(const char *uri);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SDL_OBJECT_TYPE_UNKNOWN,
|
||||
SDL_OBJECT_TYPE_WINDOW,
|
||||
SDL_OBJECT_TYPE_RENDERER,
|
||||
SDL_OBJECT_TYPE_TEXTURE,
|
||||
SDL_OBJECT_TYPE_JOYSTICK,
|
||||
SDL_OBJECT_TYPE_GAMEPAD,
|
||||
SDL_OBJECT_TYPE_HAPTIC,
|
||||
SDL_OBJECT_TYPE_SENSOR,
|
||||
SDL_OBJECT_TYPE_HIDAPI_DEVICE,
|
||||
SDL_OBJECT_TYPE_HIDAPI_JOYSTICK,
|
||||
SDL_OBJECT_TYPE_THREAD,
|
||||
SDL_OBJECT_TYPE_TRAY,
|
||||
|
||||
} SDL_ObjectType;
|
||||
|
||||
extern Uint32 SDL_GetNextObjectID(void);
|
||||
extern void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid);
|
||||
extern bool SDL_FindObject(void *object, SDL_ObjectType type);
|
||||
extern int SDL_GetObjects(SDL_ObjectType type, void **objects, int count);
|
||||
extern void SDL_SetObjectsInvalid(void);
|
||||
|
||||
extern bool SDL_object_validation;
|
||||
|
||||
SDL_FORCE_INLINE bool SDL_ObjectValid(void *object, SDL_ObjectType type)
|
||||
{
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SDL_object_validation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return SDL_FindObject(object, type);
|
||||
}
|
||||
|
||||
extern const char *SDL_GetPersistentString(const char *string);
|
||||
|
||||
extern char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name);
|
||||
|
||||
// Log what backend a subsystem chose, if a hint was set to do so. Useful for debugging.
|
||||
extern void SDL_DebugLogBackend(const char *subsystem, const char *backend);
|
||||
|
||||
#ifdef SDL_PLATFORM_EMSCRIPTEN
|
||||
// even though we reference the C runtime's free() in other places, it appears
|
||||
// to be inlined more aggressively in Emscripten 4, so we need a reference to
|
||||
// it here, too, so inlined Javascript doesn't fail to find it.
|
||||
extern void Emscripten_force_free(void *ptr);
|
||||
#endif
|
||||
|
||||
#endif // SDL_utils_h_
|
||||
Vendored
+390
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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 intrinsics showed up in different times for different compilers.
|
||||
#if (defined(__GNUC__) && (__GNUC__ >= 5)) || (defined(__clang__) && defined(HAVE_GCC_ATOMICS))
|
||||
#define HAVE_ATOMIC_LOAD_N 1
|
||||
#define HAVE_ATOMIC_EXCHANGE_N 1
|
||||
#else
|
||||
#if SDL_HAS_BUILTIN(__atomic_load_n)
|
||||
#define HAVE_ATOMIC_LOAD_N 1
|
||||
#endif
|
||||
#if SDL_HAS_BUILTIN(__atomic_exchange_n)
|
||||
#define HAVE_ATOMIC_EXCHANGE_N 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
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)
|
||||
#define EMULATE_CAS 1
|
||||
#endif
|
||||
|
||||
#ifdef EMULATE_CAS
|
||||
static SDL_SpinLock locks[32];
|
||||
|
||||
static SDL_INLINE void enterLock(void *a)
|
||||
{
|
||||
uintptr_t index = ((((uintptr_t)a) >> 3) & 0x1f);
|
||||
|
||||
SDL_LockSpinlock(&locks[index]);
|
||||
}
|
||||
|
||||
static SDL_INLINE void leaveLock(void *a)
|
||||
{
|
||||
uintptr_t index = ((((uintptr_t)a) >> 3) & 0x1f);
|
||||
|
||||
SDL_UnlockSpinlock(&locks[index]);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool SDL_CompareAndSwapAtomicInt(SDL_AtomicInt *a, int oldval, int newval)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value));
|
||||
return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval;
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_bool_compare_and_swap(&a->value, oldval, newval);
|
||||
#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
|
||||
return OSAtomicCompareAndSwap32Barrier(oldval, newval, &a->value);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(uint_t) == sizeof(a->value));
|
||||
return ((int)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval);
|
||||
#elif defined(EMULATE_CAS)
|
||||
bool result = false;
|
||||
|
||||
enterLock(a);
|
||||
if (a->value == oldval) {
|
||||
a->value = newval;
|
||||
result = true;
|
||||
}
|
||||
leaveLock(a);
|
||||
|
||||
return result;
|
||||
#else
|
||||
#error Please define your platform.
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SDL_CompareAndSwapAtomicU32(SDL_AtomicU32 *a, Uint32 oldval, Uint32 newval)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value));
|
||||
return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval;
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_bool_compare_and_swap(&a->value, oldval, newval);
|
||||
#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
|
||||
return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t *)&a->value);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(uint_t) == sizeof(a->value));
|
||||
return ((Uint32)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval);
|
||||
#elif defined(EMULATE_CAS)
|
||||
bool result = false;
|
||||
|
||||
enterLock(a);
|
||||
if (a->value == oldval) {
|
||||
a->value = newval;
|
||||
result = true;
|
||||
}
|
||||
leaveLock(a);
|
||||
|
||||
return result;
|
||||
#else
|
||||
#error Please define your platform.
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SDL_CompareAndSwapAtomicPointer(void **a, void *oldval, void *newval)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
return _InterlockedCompareExchangePointer(a, newval, oldval) == oldval;
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_bool_compare_and_swap(a, oldval, newval);
|
||||
#elif defined(SDL_PLATFORM_MACOS) && defined(__LP64__) // this is deprecated in 10.12 sdk; favor gcc atomics.
|
||||
return OSAtomicCompareAndSwap64Barrier((int64_t)oldval, (int64_t)newval, (int64_t *)a);
|
||||
#elif defined(SDL_PLATFORM_MACOS) && !defined(__LP64__) // this is deprecated in 10.12 sdk; favor gcc atomics.
|
||||
return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t *)a);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
return (atomic_cas_ptr(a, oldval, newval) == oldval);
|
||||
#elif defined(EMULATE_CAS)
|
||||
bool result = false;
|
||||
|
||||
enterLock(a);
|
||||
if (*a == oldval) {
|
||||
*a = newval;
|
||||
result = true;
|
||||
}
|
||||
leaveLock(a);
|
||||
|
||||
return result;
|
||||
#else
|
||||
#error Please define your platform.
|
||||
#endif
|
||||
}
|
||||
|
||||
int SDL_SetAtomicInt(SDL_AtomicInt *a, int v)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value));
|
||||
return _InterlockedExchange((long *)&a->value, v);
|
||||
#elif defined(HAVE_ATOMIC_EXCHANGE_N)
|
||||
return __atomic_exchange_n(&a->value, v, __ATOMIC_SEQ_CST);
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
// __sync_lock_test_and_set() is designed for locking rather than a
|
||||
// generic atomic exchange, so it only provides an acquire barrier
|
||||
// and may not store the exact value on all architectures. We prefer
|
||||
// __atomic_exchange_n() instead on all modern compilers.
|
||||
__sync_synchronize();
|
||||
return __sync_lock_test_and_set(&a->value, v);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(uint_t) == sizeof(a->value));
|
||||
return (int)atomic_swap_uint((volatile uint_t *)&a->value, v);
|
||||
#else
|
||||
int value;
|
||||
do {
|
||||
value = a->value;
|
||||
} while (!SDL_CompareAndSwapAtomicInt(a, value, v));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
Uint32 SDL_SetAtomicU32(SDL_AtomicU32 *a, Uint32 v)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value));
|
||||
return _InterlockedExchange((long *)&a->value, v);
|
||||
#elif defined(HAVE_ATOMIC_EXCHANGE_N)
|
||||
return __atomic_exchange_n(&a->value, v, __ATOMIC_SEQ_CST);
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
// __sync_lock_test_and_set() is designed for locking rather than a
|
||||
// generic atomic exchange, so it only provides an acquire barrier
|
||||
// and may not store the exact value on all architectures. We prefer
|
||||
// __atomic_exchange_n() instead on all modern compilers.
|
||||
__sync_synchronize();
|
||||
return __sync_lock_test_and_set(&a->value, v);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(uint_t) == sizeof(a->value));
|
||||
return (Uint32)atomic_swap_uint((volatile uint_t *)&a->value, v);
|
||||
#else
|
||||
Uint32 value;
|
||||
do {
|
||||
value = a->value;
|
||||
} while (!SDL_CompareAndSwapAtomicU32(a, value, v));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
void *SDL_SetAtomicPointer(void **a, void *v)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
return _InterlockedExchangePointer(a, v);
|
||||
#elif defined(HAVE_ATOMIC_EXCHANGE_N)
|
||||
return __atomic_exchange_n(a, v, __ATOMIC_SEQ_CST);
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
// __sync_lock_test_and_set() is designed for locking rather than a
|
||||
// generic atomic exchange, so it only provides an acquire barrier
|
||||
// and may not store the exact value on all architectures. We prefer
|
||||
// __atomic_exchange_n() instead on all modern compilers.
|
||||
__sync_synchronize();
|
||||
return __sync_lock_test_and_set(a, v);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
return atomic_swap_ptr(a, v);
|
||||
#else
|
||||
void *value;
|
||||
do {
|
||||
value = *a;
|
||||
} while (!SDL_CompareAndSwapAtomicPointer(a, value, v));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
int SDL_AddAtomicInt(SDL_AtomicInt *a, int v)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(long) == sizeof(a->value));
|
||||
return _InterlockedExchangeAdd((long *)&a->value, v);
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_fetch_and_add(&a->value, v);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
int pv = a->value;
|
||||
membar_consumer();
|
||||
atomic_add_int((volatile uint_t *)&a->value, v);
|
||||
return pv;
|
||||
#else
|
||||
int value;
|
||||
do {
|
||||
value = a->value;
|
||||
} while (!SDL_CompareAndSwapAtomicInt(a, value, (value + v)));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
Uint32 SDL_AddAtomicU32(SDL_AtomicU32 *a, int v)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(long) == sizeof(a->value));
|
||||
return (Uint32)_InterlockedExchangeAdd((long *)&a->value, v);
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_fetch_and_add(&a->value, v);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
Uint32 pv = a->value;
|
||||
membar_consumer();
|
||||
atomic_add_int((volatile uint_t *)&a->value, v);
|
||||
return pv;
|
||||
#else
|
||||
Uint32 value;
|
||||
do {
|
||||
value = a->value;
|
||||
} while (!SDL_CompareAndSwapAtomicU32(a, value, (value + v)));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
int SDL_GetAtomicInt(SDL_AtomicInt *a)
|
||||
{
|
||||
#ifdef HAVE_ATOMIC_LOAD_N
|
||||
return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST);
|
||||
#elif defined(HAVE_MSC_ATOMICS) && (defined(_M_ARM64) || defined(_M_ARM64EC))
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_get_int, sizeof(__int32) == sizeof(a->value));
|
||||
return (int)__ldar32((unsigned __int32 *)&a->value);
|
||||
#elif defined(HAVE_MSC_ATOMICS) && (defined(_M_X64) || defined(_M_IX86))
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_get_int, sizeof(int) == sizeof(a->value));
|
||||
SDL_CompilerBarrier();
|
||||
int value = *(volatile int *)&a->value;
|
||||
SDL_CompilerBarrier();
|
||||
return value;
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_or_and_fetch(&a->value, 0);
|
||||
#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
|
||||
return sizeof(a->value) == sizeof(uint32_t) ? OSAtomicOr32Barrier(0, (volatile uint32_t *)&a->value) : OSAtomicAdd64Barrier(0, (volatile int64_t *)&a->value);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
return atomic_or_uint_nv((volatile uint_t *)&a->value, 0);
|
||||
#else
|
||||
int value;
|
||||
do {
|
||||
value = a->value;
|
||||
} while (!SDL_CompareAndSwapAtomicInt(a, value, value));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
Uint32 SDL_GetAtomicU32(SDL_AtomicU32 *a)
|
||||
{
|
||||
#ifdef HAVE_ATOMIC_LOAD_N
|
||||
return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST);
|
||||
#elif defined(HAVE_MSC_ATOMICS) && (defined(_M_ARM64) || defined(_M_ARM64EC))
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_get_u32, sizeof(__int32) == sizeof(a->value));
|
||||
return __ldar32((unsigned __int32 *)&a->value);
|
||||
#elif defined(HAVE_MSC_ATOMICS) && (defined(_M_X64) || defined(_M_IX86))
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_get_u32, sizeof(Uint32) == sizeof(a->value));
|
||||
SDL_CompilerBarrier();
|
||||
Uint32 value = *(volatile Uint32 *)&a->value;
|
||||
SDL_CompilerBarrier();
|
||||
return value;
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_or_and_fetch(&a->value, 0);
|
||||
#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
|
||||
return OSAtomicOr32Barrier(0, (volatile uint32_t *)&a->value);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(uint_t) == sizeof(a->value));
|
||||
return (Uint32)atomic_or_uint_nv((volatile uint_t *)&a->value, 0);
|
||||
#else
|
||||
Uint32 value;
|
||||
do {
|
||||
value = a->value;
|
||||
} while (!SDL_CompareAndSwapAtomicU32(a, value, value));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
void *SDL_GetAtomicPointer(void **a)
|
||||
{
|
||||
#ifdef HAVE_ATOMIC_LOAD_N
|
||||
return __atomic_load_n(a, __ATOMIC_SEQ_CST);
|
||||
#elif defined(HAVE_MSC_ATOMICS) && (defined(_M_ARM64) || defined(_M_ARM64EC))
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_get_ptr, sizeof(__int64) == sizeof(*a));
|
||||
return (void *)__ldar64((unsigned __int64 *)a);
|
||||
#elif defined(HAVE_MSC_ATOMICS) && (defined(_M_X64) || defined(_M_IX86))
|
||||
SDL_CompilerBarrier();
|
||||
void *value = *(void * volatile *)a;
|
||||
SDL_CompilerBarrier();
|
||||
return value;
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_val_compare_and_swap(a, (void *)0, (void *)0);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
return atomic_cas_ptr(a, (void *)0, (void *)0);
|
||||
#else
|
||||
void *value;
|
||||
do {
|
||||
value = *a;
|
||||
} while (!SDL_CompareAndSwapAtomicPointer(a, value, value));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef SDL_MEMORY_BARRIER_USES_FUNCTION
|
||||
#error This file should be built in arm mode so the mcr instruction is available for memory barriers
|
||||
#endif
|
||||
|
||||
void SDL_MemoryBarrierReleaseFunction(void)
|
||||
{
|
||||
SDL_MemoryBarrierRelease();
|
||||
}
|
||||
|
||||
void SDL_MemoryBarrierAcquireFunction(void)
|
||||
{
|
||||
SDL_MemoryBarrierAcquire();
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_WINDOWS)
|
||||
#include "../core/windows/SDL_windows.h"
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_SOLARIS)
|
||||
#include <atomic.h>
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_RISCOS)
|
||||
#include <unixlib/local.h>
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#ifdef PS2
|
||||
#include <kernel.h>
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_MACOS)
|
||||
#include <libkern/OSAtomic.h>
|
||||
#endif
|
||||
|
||||
// This function is where all the magic happens...
|
||||
bool SDL_TryLockSpinlock(SDL_SpinLock *lock)
|
||||
{
|
||||
#if defined(HAVE_GCC_ATOMICS) || defined(HAVE_GCC_SYNC_LOCK_TEST_AND_SET)
|
||||
return __sync_lock_test_and_set(lock, 1) == 0;
|
||||
|
||||
#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC))
|
||||
SDL_COMPILE_TIME_ASSERT(locksize, sizeof(*lock) == sizeof(long));
|
||||
return _InterlockedExchange_acq((long *)lock, 1) == 0;
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
SDL_COMPILE_TIME_ASSERT(locksize, sizeof(*lock) == sizeof(long));
|
||||
return InterlockedExchange((long *)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(__TINYC__)) && (defined(__i386__) || defined(__x86_64__))
|
||||
int result;
|
||||
__asm__ __volatile__(
|
||||
"lock ; xchgl %0, (%1)\n"
|
||||
: "=r"(result)
|
||||
: "r"(lock), "0"(1)
|
||||
: "cc", "memory");
|
||||
return result == 0;
|
||||
|
||||
#elif defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)
|
||||
// Maybe used for PowerPC, but the Intel asm or gcc atomics are favored.
|
||||
return OSAtomicCompareAndSwap32Barrier(0, 1, lock);
|
||||
|
||||
#elif defined(SDL_PLATFORM_SOLARIS) && defined(_LP64)
|
||||
// Used for Solaris with non-gcc compilers.
|
||||
return ((int)atomic_cas_64((volatile uint64_t *)lock, 0, 1) == 0);
|
||||
|
||||
#elif defined(SDL_PLATFORM_SOLARIS) && !defined(_LP64)
|
||||
// Used for Solaris with non-gcc compilers.
|
||||
return ((int)atomic_cas_32((volatile uint32_t *)lock, 0, 1) == 0);
|
||||
#elif defined(PS2)
|
||||
uint32_t oldintr;
|
||||
bool res = false;
|
||||
// disable interruption
|
||||
oldintr = DIntr();
|
||||
|
||||
if (*lock == 0) {
|
||||
*lock = 1;
|
||||
res = true;
|
||||
}
|
||||
// enable interruption
|
||||
if (oldintr) {
|
||||
EIntr();
|
||||
}
|
||||
return res;
|
||||
#else
|
||||
// Terrible terrible damage
|
||||
static SDL_Mutex *_spinlock_mutex;
|
||||
|
||||
if (!_spinlock_mutex) {
|
||||
// Race condition on first lock...
|
||||
_spinlock_mutex = SDL_CreateMutex();
|
||||
}
|
||||
SDL_LockMutex(_spinlock_mutex);
|
||||
if (*lock == 0) {
|
||||
*lock = 1;
|
||||
SDL_UnlockMutex(_spinlock_mutex);
|
||||
return true;
|
||||
} else {
|
||||
SDL_UnlockMutex(_spinlock_mutex);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SDL_LockSpinlock(SDL_SpinLock *lock)
|
||||
{
|
||||
int iterations = 0;
|
||||
// FIXME: Should we have an eventual timeout?
|
||||
while (!SDL_TryLockSpinlock(lock)) {
|
||||
if (iterations < 32) {
|
||||
iterations++;
|
||||
SDL_CPUPauseInstruction();
|
||||
} else {
|
||||
// !!! FIXME: this doesn't definitely give up the current timeslice, it does different things on various platforms.
|
||||
SDL_Delay(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_UnlockSpinlock(SDL_SpinLock *lock)
|
||||
{
|
||||
#if defined(HAVE_GCC_ATOMICS) || defined(HAVE_GCC_SYNC_LOCK_TEST_AND_SET)
|
||||
__sync_lock_release(lock);
|
||||
|
||||
#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC))
|
||||
SDL_COMPILE_TIME_ASSERT(locksize, sizeof(*lock) == sizeof(long));
|
||||
_InterlockedExchange_rel((long *)lock, 0);
|
||||
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
// Used for Solaris when not using gcc.
|
||||
membar_producer();
|
||||
*lock = 0;
|
||||
|
||||
#else
|
||||
SDL_MemoryBarrierRelease();
|
||||
*lock = 0;
|
||||
#endif
|
||||
}
|
||||
Vendored
+2640
File diff suppressed because it is too large
Load Diff
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_
|
||||
+1068
File diff suppressed because it is too large
Load Diff
+1590
File diff suppressed because it is too large
Load Diff
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// Get the name of the audio device we use for output
|
||||
|
||||
#if defined(SDL_AUDIO_DRIVER_NETBSD) || defined(SDL_AUDIO_DRIVER_OSS)
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h> // For close()
|
||||
|
||||
#include "SDL_audiodev_c.h"
|
||||
|
||||
#ifndef SDL_PATH_DEV_DSP
|
||||
#if defined(SDL_PLATFORM_NETBSD) || defined(SDL_PLATFORM_OPENBSD)
|
||||
#define SDL_PATH_DEV_DSP "/dev/audio"
|
||||
#else
|
||||
#define SDL_PATH_DEV_DSP "/dev/dsp"
|
||||
#endif
|
||||
#endif
|
||||
#ifndef SDL_PATH_DEV_DSP24
|
||||
#define SDL_PATH_DEV_DSP24 "/dev/sound/dsp"
|
||||
#endif
|
||||
#ifndef SDL_PATH_DEV_AUDIO
|
||||
#define SDL_PATH_DEV_AUDIO "/dev/audio"
|
||||
#endif
|
||||
|
||||
static void test_device(const bool recording, const char *fname, int flags, bool (*test)(int fd))
|
||||
{
|
||||
struct stat sb;
|
||||
const int audio_fd = open(fname, flags | O_CLOEXEC, 0);
|
||||
if (audio_fd >= 0) {
|
||||
if ((fstat(audio_fd, &sb) == 0) && (S_ISCHR(sb.st_mode))) {
|
||||
const bool okay = test(audio_fd);
|
||||
close(audio_fd);
|
||||
if (okay) {
|
||||
static size_t dummyhandle = 0;
|
||||
dummyhandle++;
|
||||
SDL_assert(dummyhandle != 0);
|
||||
|
||||
/* Note that spec is NULL; while we are opening the device
|
||||
* endpoint here, the endpoint does not provide any mix format
|
||||
* information, making this information inaccessible at
|
||||
* enumeration time
|
||||
*/
|
||||
SDL_AddAudioDevice(recording, fname, NULL, (void *)(uintptr_t)dummyhandle);
|
||||
}
|
||||
} else {
|
||||
close(audio_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool test_stub(int fd)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void SDL_EnumUnixAudioDevices_Internal(const bool recording, const bool classic, bool (*test)(int))
|
||||
{
|
||||
const int flags = recording ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT;
|
||||
const char *audiodev;
|
||||
char audiopath[1024];
|
||||
|
||||
if (!test) {
|
||||
test = test_stub;
|
||||
}
|
||||
|
||||
// Figure out what our audio device is
|
||||
audiodev = SDL_getenv("AUDIODEV");
|
||||
if (!audiodev) {
|
||||
if (classic) {
|
||||
audiodev = SDL_PATH_DEV_AUDIO;
|
||||
} else {
|
||||
struct stat sb;
|
||||
|
||||
// Added support for /dev/sound/\* in Linux 2.4
|
||||
if (((stat("/dev/sound", &sb) == 0) && S_ISDIR(sb.st_mode)) && ((stat(SDL_PATH_DEV_DSP24, &sb) == 0) && S_ISCHR(sb.st_mode))) {
|
||||
audiodev = SDL_PATH_DEV_DSP24;
|
||||
} else {
|
||||
audiodev = SDL_PATH_DEV_DSP;
|
||||
}
|
||||
}
|
||||
}
|
||||
test_device(recording, audiodev, flags, test);
|
||||
|
||||
if (SDL_strlen(audiodev) < (sizeof(audiopath) - 3)) {
|
||||
int instance = 0;
|
||||
while (instance <= 64) {
|
||||
(void)SDL_snprintf(audiopath, SDL_arraysize(audiopath),
|
||||
"%s%d", audiodev, instance);
|
||||
instance++;
|
||||
test_device(recording, audiopath, flags, test);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_EnumUnixAudioDevices(const bool classic, bool (*test)(int))
|
||||
{
|
||||
SDL_EnumUnixAudioDevices_Internal(true, classic, test);
|
||||
SDL_EnumUnixAudioDevices_Internal(false, classic, test);
|
||||
}
|
||||
|
||||
#endif // Audio device selection
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_audiodev_c_h_
|
||||
#define SDL_audiodev_c_h_
|
||||
|
||||
#include "SDL_internal.h"
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
// Open the audio device for playback, and don't block if busy
|
||||
//#define USE_BLOCKING_WRITES
|
||||
|
||||
#ifdef USE_BLOCKING_WRITES
|
||||
#define OPEN_FLAGS_OUTPUT O_WRONLY
|
||||
#define OPEN_FLAGS_INPUT O_RDONLY
|
||||
#else
|
||||
#define OPEN_FLAGS_OUTPUT (O_WRONLY | O_NONBLOCK)
|
||||
#define OPEN_FLAGS_INPUT (O_RDONLY | O_NONBLOCK)
|
||||
#endif
|
||||
|
||||
extern void SDL_EnumUnixAudioDevices(const bool classic, bool (*test)(int));
|
||||
|
||||
#endif // SDL_audiodev_c_h_
|
||||
+652
@@ -0,0 +1,652 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_audioqueue.h"
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
typedef struct SDL_MemoryPool SDL_MemoryPool;
|
||||
|
||||
struct SDL_MemoryPool
|
||||
{
|
||||
void *free_blocks;
|
||||
size_t block_size;
|
||||
size_t num_free;
|
||||
size_t max_free;
|
||||
};
|
||||
|
||||
struct SDL_AudioTrack
|
||||
{
|
||||
SDL_AudioSpec spec;
|
||||
int *chmap;
|
||||
bool flushed;
|
||||
SDL_AudioTrack *next;
|
||||
|
||||
void *userdata;
|
||||
SDL_ReleaseAudioBufferCallback callback;
|
||||
|
||||
Uint8 *data;
|
||||
size_t head;
|
||||
size_t tail;
|
||||
size_t capacity;
|
||||
|
||||
int chmap_storage[SDL_MAX_CHANNELMAP_CHANNELS]; // !!! FIXME: this needs to grow if SDL ever supports more channels. But if it grows, we should probably be more clever about allocations.
|
||||
};
|
||||
|
||||
struct SDL_AudioQueue
|
||||
{
|
||||
SDL_AudioTrack *head;
|
||||
SDL_AudioTrack *tail;
|
||||
|
||||
Uint8 *history_buffer;
|
||||
size_t history_length;
|
||||
size_t history_capacity;
|
||||
|
||||
SDL_MemoryPool track_pool;
|
||||
SDL_MemoryPool chunk_pool;
|
||||
};
|
||||
|
||||
// Allocate a new block, avoiding checking for ones already in the pool
|
||||
static void *AllocNewMemoryPoolBlock(const SDL_MemoryPool *pool)
|
||||
{
|
||||
return SDL_malloc(pool->block_size);
|
||||
}
|
||||
|
||||
// Allocate a new block, first checking if there are any in the pool
|
||||
static void *AllocMemoryPoolBlock(SDL_MemoryPool *pool)
|
||||
{
|
||||
if (pool->num_free == 0) {
|
||||
return AllocNewMemoryPoolBlock(pool);
|
||||
}
|
||||
|
||||
void *block = pool->free_blocks;
|
||||
pool->free_blocks = *(void **)block;
|
||||
--pool->num_free;
|
||||
return block;
|
||||
}
|
||||
|
||||
// Free a block, or add it to the pool if there's room
|
||||
static void FreeMemoryPoolBlock(SDL_MemoryPool *pool, void *block)
|
||||
{
|
||||
if (pool->num_free < pool->max_free) {
|
||||
*(void **)block = pool->free_blocks;
|
||||
pool->free_blocks = block;
|
||||
++pool->num_free;
|
||||
} else {
|
||||
SDL_free(block);
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy a pool and all of its blocks
|
||||
static void DestroyMemoryPool(SDL_MemoryPool *pool)
|
||||
{
|
||||
void *block = pool->free_blocks;
|
||||
pool->free_blocks = NULL;
|
||||
pool->num_free = 0;
|
||||
|
||||
while (block) {
|
||||
void *next = *(void **)block;
|
||||
SDL_free(block);
|
||||
block = next;
|
||||
}
|
||||
}
|
||||
|
||||
// Keeping a list of free chunks reduces memory allocations,
|
||||
// But also increases the amount of work to perform when freeing the track.
|
||||
static void InitMemoryPool(SDL_MemoryPool *pool, size_t block_size, size_t max_free)
|
||||
{
|
||||
SDL_zerop(pool);
|
||||
|
||||
SDL_assert(block_size >= sizeof(void *));
|
||||
pool->block_size = block_size;
|
||||
pool->max_free = max_free;
|
||||
}
|
||||
|
||||
// Allocates a number of blocks and adds them to the pool
|
||||
static bool ReserveMemoryPoolBlocks(SDL_MemoryPool *pool, size_t num_blocks)
|
||||
{
|
||||
for (; num_blocks; --num_blocks) {
|
||||
void *block = AllocNewMemoryPoolBlock(pool);
|
||||
|
||||
if (block == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*(void **)block = pool->free_blocks;
|
||||
pool->free_blocks = block;
|
||||
++pool->num_free;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDL_DestroyAudioQueue(SDL_AudioQueue *queue)
|
||||
{
|
||||
SDL_ClearAudioQueue(queue);
|
||||
|
||||
DestroyMemoryPool(&queue->track_pool);
|
||||
DestroyMemoryPool(&queue->chunk_pool);
|
||||
SDL_aligned_free(queue->history_buffer);
|
||||
|
||||
SDL_free(queue);
|
||||
}
|
||||
|
||||
SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size)
|
||||
{
|
||||
SDL_AudioQueue *queue = (SDL_AudioQueue *)SDL_calloc(1, sizeof(*queue));
|
||||
|
||||
if (!queue) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
InitMemoryPool(&queue->track_pool, sizeof(SDL_AudioTrack), 8);
|
||||
InitMemoryPool(&queue->chunk_pool, chunk_size, 4);
|
||||
|
||||
if (!ReserveMemoryPoolBlocks(&queue->track_pool, 2)) {
|
||||
SDL_DestroyAudioQueue(queue);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
static void DestroyAudioTrack(SDL_AudioQueue *queue, SDL_AudioTrack *track)
|
||||
{
|
||||
track->callback(track->userdata, track->data, (int)track->capacity);
|
||||
|
||||
FreeMemoryPoolBlock(&queue->track_pool, track);
|
||||
}
|
||||
|
||||
void SDL_ClearAudioQueue(SDL_AudioQueue *queue)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
queue->head = NULL;
|
||||
queue->tail = NULL;
|
||||
queue->history_length = 0;
|
||||
|
||||
while (track) {
|
||||
SDL_AudioTrack *next = track->next;
|
||||
DestroyAudioTrack(queue, track);
|
||||
track = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void FlushAudioTrack(SDL_AudioTrack *track)
|
||||
{
|
||||
track->flushed = true;
|
||||
}
|
||||
|
||||
void SDL_FlushAudioQueue(SDL_AudioQueue *queue)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->tail;
|
||||
|
||||
if (track) {
|
||||
FlushAudioTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
for (;;) {
|
||||
bool flushed = track->flushed;
|
||||
|
||||
SDL_AudioTrack *next = track->next;
|
||||
DestroyAudioTrack(queue, track);
|
||||
track = next;
|
||||
|
||||
if (flushed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
queue->head = track;
|
||||
queue->history_length = 0;
|
||||
|
||||
if (!track) {
|
||||
queue->tail = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_AudioTrack *SDL_CreateAudioTrack(
|
||||
SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap,
|
||||
Uint8 *data, size_t len, size_t capacity,
|
||||
SDL_ReleaseAudioBufferCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AudioTrack *track = (SDL_AudioTrack *)AllocMemoryPoolBlock(&queue->track_pool);
|
||||
|
||||
if (!track) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_zerop(track);
|
||||
|
||||
if (chmap) {
|
||||
SDL_assert(SDL_arraysize(track->chmap_storage) >= spec->channels);
|
||||
SDL_memcpy(track->chmap_storage, chmap, sizeof (*chmap) * spec->channels);
|
||||
track->chmap = track->chmap_storage;
|
||||
}
|
||||
|
||||
SDL_copyp(&track->spec, spec);
|
||||
|
||||
track->userdata = userdata;
|
||||
track->callback = callback;
|
||||
track->data = data;
|
||||
track->head = 0;
|
||||
track->tail = len;
|
||||
track->capacity = capacity;
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
static void SDLCALL FreeChunkedAudioBuffer(void *userdata, const void *buf, int len)
|
||||
{
|
||||
SDL_AudioQueue *queue = (SDL_AudioQueue *)userdata;
|
||||
|
||||
FreeMemoryPoolBlock(&queue->chunk_pool, (void *)buf);
|
||||
}
|
||||
|
||||
static SDL_AudioTrack *CreateChunkedAudioTrack(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap)
|
||||
{
|
||||
Uint8 *chunk = (Uint8 *)AllocMemoryPoolBlock(&queue->chunk_pool);
|
||||
|
||||
if (!chunk) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t capacity = queue->chunk_pool.block_size;
|
||||
capacity -= capacity % SDL_AUDIO_FRAMESIZE(*spec);
|
||||
|
||||
SDL_AudioTrack *track = SDL_CreateAudioTrack(queue, spec, chmap, chunk, 0, capacity, FreeChunkedAudioBuffer, queue);
|
||||
|
||||
if (!track) {
|
||||
FreeMemoryPoolBlock(&queue->chunk_pool, chunk);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track)
|
||||
{
|
||||
SDL_AudioTrack *tail = queue->tail;
|
||||
|
||||
if (tail) {
|
||||
// If the spec has changed, make sure to flush the previous track
|
||||
if (!SDL_AudioSpecsEqual(&tail->spec, &track->spec, tail->chmap, track->chmap)) {
|
||||
FlushAudioTrack(tail);
|
||||
}
|
||||
|
||||
tail->next = track;
|
||||
} else {
|
||||
queue->head = track;
|
||||
}
|
||||
|
||||
queue->tail = track;
|
||||
}
|
||||
|
||||
static size_t WriteToAudioTrack(SDL_AudioTrack *track, const Uint8 *data, size_t len)
|
||||
{
|
||||
if (track->flushed || track->tail >= track->capacity) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
len = SDL_min(len, track->capacity - track->tail);
|
||||
SDL_memcpy(&track->data[track->tail], data, len);
|
||||
track->tail += len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
bool SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap, const Uint8 *data, size_t len)
|
||||
{
|
||||
if (len == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SDL_AudioTrack *track = queue->tail;
|
||||
|
||||
if (track) {
|
||||
if (!SDL_AudioSpecsEqual(&track->spec, spec, track->chmap, chmap)) {
|
||||
FlushAudioTrack(track);
|
||||
}
|
||||
} else {
|
||||
SDL_assert(!queue->head);
|
||||
track = CreateChunkedAudioTrack(queue, spec, chmap);
|
||||
|
||||
if (!track) {
|
||||
return false;
|
||||
}
|
||||
|
||||
queue->head = track;
|
||||
queue->tail = track;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
const size_t written = WriteToAudioTrack(track, data, len);
|
||||
data += written;
|
||||
len -= written;
|
||||
|
||||
if (len == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_AudioTrack *new_track = CreateChunkedAudioTrack(queue, spec, chmap);
|
||||
|
||||
if (!new_track) {
|
||||
return false;
|
||||
}
|
||||
|
||||
track->next = new_track;
|
||||
queue->tail = new_track;
|
||||
track = new_track;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue)
|
||||
{
|
||||
return queue->head;
|
||||
}
|
||||
|
||||
size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed)
|
||||
{
|
||||
SDL_AudioTrack *iter = (SDL_AudioTrack *)(*inout_iter);
|
||||
SDL_assert(iter != NULL);
|
||||
|
||||
SDL_copyp(out_spec, &iter->spec);
|
||||
*out_chmap = iter->chmap;
|
||||
|
||||
bool flushed = false;
|
||||
size_t queued_bytes = 0;
|
||||
|
||||
while (iter) {
|
||||
SDL_AudioTrack *track = iter;
|
||||
iter = iter->next;
|
||||
|
||||
size_t avail = track->tail - track->head;
|
||||
|
||||
if (avail >= SDL_SIZE_MAX - queued_bytes) {
|
||||
queued_bytes = SDL_SIZE_MAX;
|
||||
flushed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
queued_bytes += avail;
|
||||
flushed = track->flushed;
|
||||
|
||||
if (flushed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*inout_iter = iter;
|
||||
*out_flushed = flushed;
|
||||
|
||||
return queued_bytes;
|
||||
}
|
||||
|
||||
static const Uint8 *PeekIntoAudioQueuePast(SDL_AudioQueue *queue, Uint8 *data, size_t len)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
if (track->head >= len) {
|
||||
return &track->data[track->head - len];
|
||||
}
|
||||
|
||||
size_t past = len - track->head;
|
||||
|
||||
if (past > queue->history_length) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_memcpy(data, &queue->history_buffer[queue->history_length - past], past);
|
||||
SDL_memcpy(&data[past], track->data, track->head);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void UpdateAudioQueueHistory(SDL_AudioQueue *queue,
|
||||
const Uint8 *data, size_t len)
|
||||
{
|
||||
Uint8 *history_buffer = queue->history_buffer;
|
||||
size_t history_bytes = queue->history_length;
|
||||
|
||||
if (len >= history_bytes) {
|
||||
SDL_memcpy(history_buffer, &data[len - history_bytes], history_bytes);
|
||||
} else {
|
||||
size_t preserve = history_bytes - len;
|
||||
SDL_memmove(history_buffer, &history_buffer[len], preserve);
|
||||
SDL_memcpy(&history_buffer[preserve], data, len);
|
||||
}
|
||||
}
|
||||
|
||||
static const Uint8 *ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
if (track->tail - track->head >= len) {
|
||||
const Uint8 *ptr = &track->data[track->head];
|
||||
track->head += len;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
|
||||
for (;;) {
|
||||
size_t avail = SDL_min(len - total, track->tail - track->head);
|
||||
SDL_memcpy(&data[total], &track->data[track->head], avail);
|
||||
track->head += avail;
|
||||
total += avail;
|
||||
|
||||
if (total == len) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (track->flushed) {
|
||||
SDL_SetError("Reading past end of flushed track");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_AudioTrack *next = track->next;
|
||||
|
||||
if (!next) {
|
||||
SDL_SetError("Reading past end of incomplete track");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
UpdateAudioQueueHistory(queue, track->data, track->tail);
|
||||
|
||||
queue->head = next;
|
||||
DestroyAudioTrack(queue, track);
|
||||
track = next;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static const Uint8 *PeekIntoAudioQueueFuture(SDL_AudioQueue *queue, Uint8 *data, size_t len)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
if (track->tail - track->head >= len) {
|
||||
return &track->data[track->head];
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
|
||||
for (;;) {
|
||||
size_t avail = SDL_min(len - total, track->tail - track->head);
|
||||
SDL_memcpy(&data[total], &track->data[track->head], avail);
|
||||
total += avail;
|
||||
|
||||
if (total == len) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (track->flushed) {
|
||||
// If we have run out of data, fill the rest with silence.
|
||||
SDL_memset(&data[total], SDL_GetSilenceValueForFormat(track->spec.format), len - total);
|
||||
break;
|
||||
}
|
||||
|
||||
track = track->next;
|
||||
|
||||
if (!track) {
|
||||
SDL_SetError("Peeking past end of incomplete track");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
|
||||
Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
|
||||
int past_frames, int present_frames, int future_frames,
|
||||
Uint8 *scratch, float gain)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
if (!track) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_AudioFormat src_format = track->spec.format;
|
||||
int src_channels = track->spec.channels;
|
||||
const int *src_map = track->chmap;
|
||||
|
||||
size_t src_frame_size = SDL_AUDIO_BYTESIZE(src_format) * src_channels;
|
||||
size_t dst_frame_size = SDL_AUDIO_BYTESIZE(dst_format) * dst_channels;
|
||||
|
||||
size_t src_past_bytes = past_frames * src_frame_size;
|
||||
size_t src_present_bytes = present_frames * src_frame_size;
|
||||
size_t src_future_bytes = future_frames * src_frame_size;
|
||||
|
||||
size_t dst_past_bytes = past_frames * dst_frame_size;
|
||||
size_t dst_present_bytes = present_frames * dst_frame_size;
|
||||
size_t dst_future_bytes = future_frames * dst_frame_size;
|
||||
|
||||
const bool convert = (src_format != dst_format) || (src_channels != dst_channels) || (gain != 1.0f);
|
||||
|
||||
if (convert && !dst) {
|
||||
// The user didn't ask for the data to be copied, but we need to convert it, so store it in the scratch buffer
|
||||
dst = scratch;
|
||||
}
|
||||
|
||||
// Can we get all of the data straight from this track?
|
||||
if ((track->head >= src_past_bytes) && ((track->tail - track->head) >= (src_present_bytes + src_future_bytes))) {
|
||||
const Uint8 *ptr = &track->data[track->head - src_past_bytes];
|
||||
track->head += src_present_bytes;
|
||||
|
||||
// Do we still need to copy/convert the data?
|
||||
if (dst) {
|
||||
ConvertAudio(past_frames + present_frames + future_frames, ptr,
|
||||
src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
|
||||
ptr = dst;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
if (!dst) {
|
||||
// The user didn't ask for the data to be copied, but we need to, so store it in the scratch buffer
|
||||
dst = scratch;
|
||||
} else if (!convert) {
|
||||
// We are only copying, not converting, so copy straight into the dst buffer
|
||||
scratch = dst;
|
||||
}
|
||||
|
||||
Uint8 *ptr = dst;
|
||||
|
||||
if (src_past_bytes) {
|
||||
ConvertAudio(past_frames, PeekIntoAudioQueuePast(queue, scratch, src_past_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
|
||||
dst += dst_past_bytes;
|
||||
scratch += dst_past_bytes;
|
||||
}
|
||||
|
||||
if (src_present_bytes) {
|
||||
ConvertAudio(present_frames, ReadFromAudioQueue(queue, scratch, src_present_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
|
||||
dst += dst_present_bytes;
|
||||
scratch += dst_present_bytes;
|
||||
}
|
||||
|
||||
if (src_future_bytes) {
|
||||
ConvertAudio(future_frames, PeekIntoAudioQueueFuture(queue, scratch, src_future_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
|
||||
dst += dst_future_bytes;
|
||||
scratch += dst_future_bytes;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue)
|
||||
{
|
||||
size_t total = 0;
|
||||
void *iter = SDL_BeginAudioQueueIter(queue);
|
||||
|
||||
while (iter) {
|
||||
SDL_AudioSpec src_spec;
|
||||
int *src_chmap;
|
||||
bool flushed;
|
||||
|
||||
size_t avail = SDL_NextAudioQueueIter(queue, &iter, &src_spec, &src_chmap, &flushed);
|
||||
|
||||
if (avail >= SDL_SIZE_MAX - total) {
|
||||
total = SDL_SIZE_MAX;
|
||||
break;
|
||||
}
|
||||
|
||||
total += avail;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
bool SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
if (!track) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t length = num_frames * SDL_AUDIO_FRAMESIZE(track->spec);
|
||||
Uint8 *history_buffer = queue->history_buffer;
|
||||
|
||||
if (queue->history_capacity < length) {
|
||||
history_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), length);
|
||||
if (!history_buffer) {
|
||||
return false;
|
||||
}
|
||||
SDL_aligned_free(queue->history_buffer);
|
||||
queue->history_buffer = history_buffer;
|
||||
queue->history_capacity = length;
|
||||
}
|
||||
|
||||
queue->history_length = length;
|
||||
SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(track->spec.format), length);
|
||||
|
||||
return true;
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_audioqueue_h_
|
||||
#define SDL_audioqueue_h_
|
||||
|
||||
// Internal functions used by SDL_AudioStream for queueing audio.
|
||||
|
||||
typedef SDL_AudioStreamDataCompleteCallback SDL_ReleaseAudioBufferCallback;
|
||||
|
||||
typedef struct SDL_AudioQueue SDL_AudioQueue;
|
||||
typedef struct SDL_AudioTrack SDL_AudioTrack;
|
||||
|
||||
// Create a new audio queue
|
||||
extern SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size);
|
||||
|
||||
// Destroy an audio queue
|
||||
extern void SDL_DestroyAudioQueue(SDL_AudioQueue *queue);
|
||||
|
||||
// Completely clear the queue
|
||||
extern void SDL_ClearAudioQueue(SDL_AudioQueue *queue);
|
||||
|
||||
// Mark the last track as flushed
|
||||
extern void SDL_FlushAudioQueue(SDL_AudioQueue *queue);
|
||||
|
||||
// Pop the current head track
|
||||
// REQUIRES: The head track must exist, and must have been flushed
|
||||
extern void SDL_PopAudioQueueHead(SDL_AudioQueue *queue);
|
||||
|
||||
// Write data to the end of queue
|
||||
// REQUIRES: If the spec has changed, the last track must have been flushed
|
||||
extern bool SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap, const Uint8 *data, size_t len);
|
||||
|
||||
// Create a track where the input data is owned by the caller
|
||||
extern SDL_AudioTrack *SDL_CreateAudioTrack(SDL_AudioQueue *queue,
|
||||
const SDL_AudioSpec *spec, const int *chmap, Uint8 *data, size_t len, size_t capacity,
|
||||
SDL_ReleaseAudioBufferCallback callback, void *userdata);
|
||||
|
||||
// Add a track to the end of the queue
|
||||
// REQUIRES: `track != NULL`
|
||||
extern void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track);
|
||||
|
||||
// Iterate over the tracks in the queue
|
||||
extern void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue);
|
||||
|
||||
// Query and update the track iterator
|
||||
// REQUIRES: `*inout_iter != NULL` (a valid iterator)
|
||||
extern size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed);
|
||||
|
||||
extern const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
|
||||
Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
|
||||
int past_frames, int present_frames, int future_frames,
|
||||
Uint8 *scratch, float gain);
|
||||
|
||||
// Get the total number of bytes currently queued
|
||||
extern size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue);
|
||||
|
||||
extern bool SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames);
|
||||
|
||||
#endif // SDL_audioqueue_h_
|
||||
+706
@@ -0,0 +1,706 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
#include "SDL_audioresample.h"
|
||||
|
||||
// SDL's resampler uses a "bandlimited interpolation" algorithm:
|
||||
// https://ccrma.stanford.edu/~jos/resample/
|
||||
|
||||
// TODO: Support changing this at runtime?
|
||||
#if defined(SDL_SSE_INTRINSICS) || defined(SDL_NEON_INTRINSICS)
|
||||
// In <current year>, SSE is basically mandatory anyway
|
||||
// We want RESAMPLER_SAMPLES_PER_FRAME to be a multiple of 4, to make SIMD easier
|
||||
#define RESAMPLER_ZERO_CROSSINGS 6
|
||||
#else
|
||||
#define RESAMPLER_ZERO_CROSSINGS 5
|
||||
#endif
|
||||
|
||||
#define RESAMPLER_SAMPLES_PER_FRAME (RESAMPLER_ZERO_CROSSINGS * 2)
|
||||
|
||||
// For a given srcpos, `srcpos + frame` are sampled, where `-RESAMPLER_ZERO_CROSSINGS < frame <= RESAMPLER_ZERO_CROSSINGS`.
|
||||
// Note, when upsampling, it is also possible to start sampling from `srcpos = -1`.
|
||||
#define RESAMPLER_MAX_PADDING_FRAMES (RESAMPLER_ZERO_CROSSINGS + 1)
|
||||
|
||||
// More bits gives more precision, at the cost of a larger table.
|
||||
#define RESAMPLER_BITS_PER_ZERO_CROSSING 3
|
||||
#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << RESAMPLER_BITS_PER_ZERO_CROSSING)
|
||||
#define RESAMPLER_FILTER_INTERP_BITS (32 - RESAMPLER_BITS_PER_ZERO_CROSSING)
|
||||
#define RESAMPLER_FILTER_INTERP_RANGE (1 << RESAMPLER_FILTER_INTERP_BITS)
|
||||
|
||||
// ResampleFrame is just a vector/matrix/matrix multiplication.
|
||||
// It performs cubic interpolation of the filter, then multiplies that with the input.
|
||||
// dst = [1, frac, frac^2, frac^3] * filter * src
|
||||
|
||||
// Cubic Polynomial
|
||||
typedef union Cubic
|
||||
{
|
||||
float v[4];
|
||||
|
||||
#ifdef SDL_SSE_INTRINSICS
|
||||
// Aligned loads can be used directly as memory operands for mul/add
|
||||
__m128 v128;
|
||||
#endif
|
||||
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
float32x4_t v128;
|
||||
#endif
|
||||
|
||||
} Cubic;
|
||||
|
||||
static void ResampleFrame_Generic(const float *src, float *dst, const Cubic *filter, float frac, int chans)
|
||||
{
|
||||
const float frac2 = frac * frac;
|
||||
const float frac3 = frac * frac2;
|
||||
|
||||
int i, chan;
|
||||
float scales[RESAMPLER_SAMPLES_PER_FRAME];
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i, ++filter) {
|
||||
scales[i] = filter->v[0] + (filter->v[1] * frac) + (filter->v[2] * frac2) + (filter->v[3] * frac3);
|
||||
}
|
||||
|
||||
for (chan = 0; chan < chans; ++chan) {
|
||||
float out = 0.0f;
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i) {
|
||||
out += src[i * chans + chan] * scales[i];
|
||||
}
|
||||
|
||||
dst[chan] = out;
|
||||
}
|
||||
}
|
||||
|
||||
static void ResampleFrame_Mono(const float *src, float *dst, const Cubic *filter, float frac, int chans)
|
||||
{
|
||||
const float frac2 = frac * frac;
|
||||
const float frac3 = frac * frac2;
|
||||
|
||||
int i;
|
||||
float out = 0.0f;
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i, ++filter) {
|
||||
// Interpolate between the nearest two filters
|
||||
const float scale = filter->v[0] + (filter->v[1] * frac) + (filter->v[2] * frac2) + (filter->v[3] * frac3);
|
||||
|
||||
out += src[i] * scale;
|
||||
}
|
||||
|
||||
dst[0] = out;
|
||||
}
|
||||
|
||||
static void ResampleFrame_Stereo(const float *src, float *dst, const Cubic *filter, float frac, int chans)
|
||||
{
|
||||
const float frac2 = frac * frac;
|
||||
const float frac3 = frac * frac2;
|
||||
|
||||
int i;
|
||||
float out0 = 0.0f;
|
||||
float out1 = 0.0f;
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i, ++filter) {
|
||||
// Interpolate between the nearest two filters
|
||||
const float scale = filter->v[0] + (filter->v[1] * frac) + (filter->v[2] * frac2) + (filter->v[3] * frac3);
|
||||
|
||||
out0 += src[i * 2 + 0] * scale;
|
||||
out1 += src[i * 2 + 1] * scale;
|
||||
}
|
||||
|
||||
dst[0] = out0;
|
||||
dst[1] = out1;
|
||||
}
|
||||
|
||||
#ifdef SDL_SSE_INTRINSICS
|
||||
#define sdl_madd_ps(a, b, c) _mm_add_ps(a, _mm_mul_ps(b, c)) // Not-so-fused multiply-add
|
||||
|
||||
static void SDL_TARGETING("sse") ResampleFrame_Generic_SSE(const float *src, float *dst, const Cubic *filter, float frac, int chans)
|
||||
{
|
||||
#if RESAMPLER_SAMPLES_PER_FRAME != 12
|
||||
#error Invalid samples per frame
|
||||
#endif
|
||||
|
||||
__m128 f0, f1, f2;
|
||||
|
||||
{
|
||||
const __m128 frac1 = _mm_set1_ps(frac);
|
||||
const __m128 frac2 = _mm_mul_ps(frac1, frac1);
|
||||
const __m128 frac3 = _mm_mul_ps(frac1, frac2);
|
||||
|
||||
// Transposed in SetupAudioResampler
|
||||
// Explicitly use _mm_load_ps to workaround ICE in GCC 4.9.4 accessing Cubic.v128
|
||||
#define X(out) \
|
||||
out = _mm_load_ps(filter[0].v); \
|
||||
out = sdl_madd_ps(out, frac1, _mm_load_ps(filter[1].v)); \
|
||||
out = sdl_madd_ps(out, frac2, _mm_load_ps(filter[2].v)); \
|
||||
out = sdl_madd_ps(out, frac3, _mm_load_ps(filter[3].v)); \
|
||||
filter += 4
|
||||
|
||||
X(f0);
|
||||
X(f1);
|
||||
X(f2);
|
||||
|
||||
#undef X
|
||||
}
|
||||
|
||||
if (chans == 2) {
|
||||
// Duplicate each of the filter elements and multiply by the input
|
||||
// Use two accumulators to improve throughput
|
||||
__m128 out0 = _mm_mul_ps(_mm_loadu_ps(src + 0), _mm_unpacklo_ps(f0, f0));
|
||||
__m128 out1 = _mm_mul_ps(_mm_loadu_ps(src + 4), _mm_unpackhi_ps(f0, f0));
|
||||
out0 = sdl_madd_ps(out0, _mm_loadu_ps(src + 8), _mm_unpacklo_ps(f1, f1));
|
||||
out1 = sdl_madd_ps(out1, _mm_loadu_ps(src + 12), _mm_unpackhi_ps(f1, f1));
|
||||
out0 = sdl_madd_ps(out0, _mm_loadu_ps(src + 16), _mm_unpacklo_ps(f2, f2));
|
||||
out1 = sdl_madd_ps(out1, _mm_loadu_ps(src + 20), _mm_unpackhi_ps(f2, f2));
|
||||
|
||||
// Add the accumulators together
|
||||
__m128 out = _mm_add_ps(out0, out1);
|
||||
|
||||
// Add the lower and upper pairs together
|
||||
out = _mm_add_ps(out, _mm_movehl_ps(out, out));
|
||||
|
||||
// Store the result
|
||||
_mm_storel_pi((__m64 *)dst, out);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chans == 1) {
|
||||
// Multiply the filter by the input
|
||||
__m128 out = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
|
||||
out = sdl_madd_ps(out, f1, _mm_loadu_ps(src + 4));
|
||||
out = sdl_madd_ps(out, f2, _mm_loadu_ps(src + 8));
|
||||
|
||||
// Horizontal sum
|
||||
__m128 shuf = _mm_shuffle_ps(out, out, _MM_SHUFFLE(2, 3, 0, 1));
|
||||
out = _mm_add_ps(out, shuf);
|
||||
out = _mm_add_ss(out, _mm_movehl_ps(shuf, out));
|
||||
|
||||
_mm_store_ss(dst, out);
|
||||
return;
|
||||
}
|
||||
|
||||
int chan = 0;
|
||||
|
||||
// Process 4 channels at once
|
||||
for (; chan + 4 <= chans; chan += 4) {
|
||||
const float *in = &src[chan];
|
||||
__m128 out0 = _mm_setzero_ps();
|
||||
__m128 out1 = _mm_setzero_ps();
|
||||
|
||||
#define X(a, b, out) \
|
||||
out = sdl_madd_ps(out, _mm_loadu_ps(in), _mm_shuffle_ps(a, a, _MM_SHUFFLE(b, b, b, b))); \
|
||||
in += chans
|
||||
|
||||
#define Y(a) \
|
||||
X(a, 0, out0); \
|
||||
X(a, 1, out1); \
|
||||
X(a, 2, out0); \
|
||||
X(a, 3, out1)
|
||||
|
||||
Y(f0);
|
||||
Y(f1);
|
||||
Y(f2);
|
||||
|
||||
#undef X
|
||||
#undef Y
|
||||
|
||||
// Add the accumulators together
|
||||
__m128 out = _mm_add_ps(out0, out1);
|
||||
|
||||
_mm_storeu_ps(&dst[chan], out);
|
||||
}
|
||||
|
||||
// Process the remaining channels one at a time.
|
||||
// Channel counts 1,2,4,8 are already handled above, leaving 3,5,6,7 to deal with (looping 3,1,2,3 times).
|
||||
// Without vgatherdps (AVX2), this gets quite messy.
|
||||
for (; chan < chans; ++chan) {
|
||||
const float *in = &src[chan];
|
||||
__m128 v0, v1, v2;
|
||||
|
||||
#define X(x) \
|
||||
x = _mm_unpacklo_ps(_mm_load_ss(in), _mm_load_ss(in + chans)); \
|
||||
in += chans + chans; \
|
||||
x = _mm_movelh_ps(x, _mm_unpacklo_ps(_mm_load_ss(in), _mm_load_ss(in + chans))); \
|
||||
in += chans + chans
|
||||
|
||||
X(v0);
|
||||
X(v1);
|
||||
X(v2);
|
||||
|
||||
#undef X
|
||||
|
||||
__m128 out = _mm_mul_ps(f0, v0);
|
||||
out = sdl_madd_ps(out, f1, v1);
|
||||
out = sdl_madd_ps(out, f2, v2);
|
||||
|
||||
// Horizontal sum
|
||||
__m128 shuf = _mm_shuffle_ps(out, out, _MM_SHUFFLE(2, 3, 0, 1));
|
||||
out = _mm_add_ps(out, shuf);
|
||||
out = _mm_add_ss(out, _mm_movehl_ps(shuf, out));
|
||||
|
||||
_mm_store_ss(&dst[chan], out);
|
||||
}
|
||||
}
|
||||
|
||||
#undef sdl_madd_ps
|
||||
#endif
|
||||
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
static void ResampleFrame_Generic_NEON(const float *src, float *dst, const Cubic *filter, float frac, int chans)
|
||||
{
|
||||
#if RESAMPLER_SAMPLES_PER_FRAME != 12
|
||||
#error Invalid samples per frame
|
||||
#endif
|
||||
|
||||
float32x4_t f0, f1, f2;
|
||||
|
||||
{
|
||||
const float32x4_t frac1 = vdupq_n_f32(frac);
|
||||
const float32x4_t frac2 = vmulq_f32(frac1, frac1);
|
||||
const float32x4_t frac3 = vmulq_f32(frac1, frac2);
|
||||
|
||||
// Transposed in SetupAudioResampler
|
||||
#define X(out) \
|
||||
out = vmlaq_f32(vmlaq_f32(vmlaq_f32(filter[0].v128, filter[1].v128, frac1), filter[2].v128, frac2), filter[3].v128, frac3); \
|
||||
filter += 4
|
||||
|
||||
X(f0);
|
||||
X(f1);
|
||||
X(f2);
|
||||
|
||||
#undef X
|
||||
}
|
||||
|
||||
if (chans == 2) {
|
||||
float32x4x2_t g0 = vzipq_f32(f0, f0);
|
||||
float32x4x2_t g1 = vzipq_f32(f1, f1);
|
||||
float32x4x2_t g2 = vzipq_f32(f2, f2);
|
||||
|
||||
// Duplicate each of the filter elements and multiply by the input
|
||||
// Use two accumulators to improve throughput
|
||||
float32x4_t out0 = vmulq_f32(vld1q_f32(src + 0), g0.val[0]);
|
||||
float32x4_t out1 = vmulq_f32(vld1q_f32(src + 4), g0.val[1]);
|
||||
out0 = vmlaq_f32(out0, vld1q_f32(src + 8), g1.val[0]);
|
||||
out1 = vmlaq_f32(out1, vld1q_f32(src + 12), g1.val[1]);
|
||||
out0 = vmlaq_f32(out0, vld1q_f32(src + 16), g2.val[0]);
|
||||
out1 = vmlaq_f32(out1, vld1q_f32(src + 20), g2.val[1]);
|
||||
|
||||
// Add the accumulators together
|
||||
out0 = vaddq_f32(out0, out1);
|
||||
|
||||
// Add the lower and upper pairs together
|
||||
float32x2_t out = vadd_f32(vget_low_f32(out0), vget_high_f32(out0));
|
||||
|
||||
// Store the result
|
||||
vst1_f32(dst, out);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chans == 1) {
|
||||
// Multiply the filter by the input
|
||||
float32x4_t out = vmulq_f32(f0, vld1q_f32(src + 0));
|
||||
out = vmlaq_f32(out, f1, vld1q_f32(src + 4));
|
||||
out = vmlaq_f32(out, f2, vld1q_f32(src + 8));
|
||||
|
||||
// Horizontal sum
|
||||
float32x2_t sum = vadd_f32(vget_low_f32(out), vget_high_f32(out));
|
||||
sum = vpadd_f32(sum, sum);
|
||||
|
||||
vst1_lane_f32(dst, sum, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int chan = 0;
|
||||
|
||||
// Process 4 channels at once
|
||||
for (; chan + 4 <= chans; chan += 4) {
|
||||
const float *in = &src[chan];
|
||||
float32x4_t out0 = vdupq_n_f32(0);
|
||||
float32x4_t out1 = vdupq_n_f32(0);
|
||||
|
||||
#define X(a, b, out) \
|
||||
out = vmlaq_f32(out, vld1q_f32(in), vdupq_lane_f32(a, b)); \
|
||||
in += chans
|
||||
|
||||
#define Y(a) \
|
||||
X(vget_low_f32(a), 0, out0); \
|
||||
X(vget_low_f32(a), 1, out1); \
|
||||
X(vget_high_f32(a), 0, out0); \
|
||||
X(vget_high_f32(a), 1, out1)
|
||||
|
||||
Y(f0);
|
||||
Y(f1);
|
||||
Y(f2);
|
||||
|
||||
#undef X
|
||||
#undef Y
|
||||
|
||||
// Add the accumulators together
|
||||
float32x4_t out = vaddq_f32(out0, out1);
|
||||
|
||||
vst1q_f32(&dst[chan], out);
|
||||
}
|
||||
|
||||
// Process the remaining channels one at a time.
|
||||
// Channel counts 1,2,4,8 are already handled above, leaving 3,5,6,7 to deal with (looping 3,1,2,3 times).
|
||||
for (; chan < chans; ++chan) {
|
||||
const float *in = &src[chan];
|
||||
float32x4_t v0, v1, v2;
|
||||
|
||||
#define X(x) \
|
||||
x = vld1q_dup_f32(in); \
|
||||
in += chans; \
|
||||
x = vld1q_lane_f32(in, x, 1); \
|
||||
in += chans; \
|
||||
x = vld1q_lane_f32(in, x, 2); \
|
||||
in += chans; \
|
||||
x = vld1q_lane_f32(in, x, 3); \
|
||||
in += chans
|
||||
|
||||
X(v0);
|
||||
X(v1);
|
||||
X(v2);
|
||||
|
||||
#undef X
|
||||
|
||||
float32x4_t out = vmulq_f32(f0, v0);
|
||||
out = vmlaq_f32(out, f1, v1);
|
||||
out = vmlaq_f32(out, f2, v2);
|
||||
|
||||
// Horizontal sum
|
||||
float32x2_t sum = vadd_f32(vget_low_f32(out), vget_high_f32(out));
|
||||
sum = vpadd_f32(sum, sum);
|
||||
|
||||
vst1_lane_f32(&dst[chan], sum, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Calculate the cubic equation which passes through all four points.
|
||||
// https://en.wikipedia.org/wiki/Ordinary_least_squares
|
||||
// https://en.wikipedia.org/wiki/Polynomial_regression
|
||||
static void CubicLeastSquares(Cubic *coeffs, float y0, float y1, float y2, float y3)
|
||||
{
|
||||
// Least squares matrix for xs = [0, 1/3, 2/3, 1]
|
||||
// [ 1.0 0.0 0.0 0.0 ]
|
||||
// [ -5.5 9.0 -4.5 1.0 ]
|
||||
// [ 9.0 -22.5 18.0 -4.5 ]
|
||||
// [ -4.5 13.5 -13.5 4.5 ]
|
||||
|
||||
coeffs->v[0] = y0;
|
||||
coeffs->v[1] = -5.5f * y0 + 9.0f * y1 - 4.5f * y2 + y3;
|
||||
coeffs->v[2] = 9.0f * y0 - 22.5f * y1 + 18.0f * y2 - 4.5f * y3;
|
||||
coeffs->v[3] = -4.5f * y0 + 13.5f * y1 - 13.5f * y2 + 4.5f * y3;
|
||||
}
|
||||
|
||||
// Zeroth-order modified Bessel function of the first kind
|
||||
// https://mathworld.wolfram.com/ModifiedBesselFunctionoftheFirstKind.html
|
||||
static float BesselI0(float x)
|
||||
{
|
||||
float sum = 0.0f;
|
||||
float i = 1.0f;
|
||||
float t = 1.0f;
|
||||
x *= x * 0.25f;
|
||||
|
||||
while (t >= sum * SDL_FLT_EPSILON) {
|
||||
sum += t;
|
||||
t *= x / (i * i);
|
||||
++i;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Pre-calculate 180 degrees of sin(pi * x) / pi
|
||||
// The speedup from this isn't huge, but it also avoids precision issues.
|
||||
// If sinf isn't available, SDL_sinf just calls SDL_sin.
|
||||
// Know what SDL_sin(SDL_PI_F) equals? Not quite zero.
|
||||
static void SincTable(float *table, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
table[i] = SDL_sinf(i * (SDL_PI_F / len)) / SDL_PI_F;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate Sinc(x/y), using a lookup table
|
||||
static float Sinc(const float *table, int x, int y)
|
||||
{
|
||||
float s = table[x % y];
|
||||
s = ((x / y) & 1) ? -s : s;
|
||||
return (s * y) / x;
|
||||
}
|
||||
|
||||
static Cubic ResamplerFilter[RESAMPLER_SAMPLES_PER_ZERO_CROSSING][RESAMPLER_SAMPLES_PER_FRAME];
|
||||
|
||||
static void GenerateResamplerFilter(void)
|
||||
{
|
||||
enum
|
||||
{
|
||||
// Generate samples at 3x the target resolution, so that we have samples at [0, 1/3, 2/3, 1] of each position
|
||||
TABLE_SAMPLES_PER_ZERO_CROSSING = RESAMPLER_SAMPLES_PER_ZERO_CROSSING * 3,
|
||||
TABLE_SIZE = RESAMPLER_ZERO_CROSSINGS * TABLE_SAMPLES_PER_ZERO_CROSSING,
|
||||
};
|
||||
|
||||
// if dB > 50, beta=(0.1102 * (dB - 8.7)), according to Matlab.
|
||||
const float dB = 80.0f;
|
||||
const float beta = 0.1102f * (dB - 8.7f);
|
||||
const float bessel_beta = BesselI0(beta);
|
||||
const float lensqr = TABLE_SIZE * TABLE_SIZE;
|
||||
|
||||
int i, j;
|
||||
|
||||
float sinc[TABLE_SAMPLES_PER_ZERO_CROSSING];
|
||||
SincTable(sinc, TABLE_SAMPLES_PER_ZERO_CROSSING);
|
||||
|
||||
// Generate one wing of the filter
|
||||
// https://en.wikipedia.org/wiki/Kaiser_window
|
||||
// https://en.wikipedia.org/wiki/Whittaker%E2%80%93Shannon_interpolation_formula
|
||||
float filter[TABLE_SIZE + 1];
|
||||
filter[0] = 1.0f;
|
||||
|
||||
for (i = 1; i <= TABLE_SIZE; ++i) {
|
||||
float b = BesselI0(beta * SDL_sqrtf((lensqr - (i * i)) / lensqr)) / bessel_beta;
|
||||
float s = Sinc(sinc, i, TABLE_SAMPLES_PER_ZERO_CROSSING);
|
||||
filter[i] = b * s;
|
||||
}
|
||||
|
||||
// Generate the coefficients for each point
|
||||
// When interpolating, the fraction represents how far we are between input samples,
|
||||
// so we need to align the filter by "moving" it to the right.
|
||||
//
|
||||
// For the left wing, this means interpolating "forwards" (away from the center)
|
||||
// For the right wing, this means interpolating "backwards" (towards the center)
|
||||
//
|
||||
// The center of the filter is at the end of the left wing (RESAMPLER_ZERO_CROSSINGS - 1)
|
||||
// The left wing is the filter, but reversed
|
||||
// The right wing is the filter, but offset by 1
|
||||
//
|
||||
// Since the right wing is offset by 1, this just means we interpolate backwards
|
||||
// between the same points, instead of forwards
|
||||
// interp(p[n], p[n+1], t) = interp(p[n+1], p[n+1-1], 1 - t) = interp(p[n+1], p[n], 1 - t)
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
|
||||
for (j = 0; j < RESAMPLER_ZERO_CROSSINGS; ++j) {
|
||||
const float *ys = &filter[((j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING) + i) * 3];
|
||||
|
||||
Cubic *fwd = &ResamplerFilter[i][RESAMPLER_ZERO_CROSSINGS - j - 1];
|
||||
Cubic *rev = &ResamplerFilter[RESAMPLER_SAMPLES_PER_ZERO_CROSSING - i - 1][RESAMPLER_ZERO_CROSSINGS + j];
|
||||
|
||||
// Calculate the cubic equation of the 4 points
|
||||
CubicLeastSquares(fwd, ys[0], ys[1], ys[2], ys[3]);
|
||||
CubicLeastSquares(rev, ys[3], ys[2], ys[1], ys[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef void (*ResampleFrameFunc)(const float *src, float *dst, const Cubic *filter, float frac, int chans);
|
||||
static ResampleFrameFunc ResampleFrame[8];
|
||||
|
||||
// Transpose 4x4 floats
|
||||
static void Transpose4x4(Cubic *data)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
Cubic temp[4] = { data[0], data[1], data[2], data[3] };
|
||||
|
||||
for (i = 0; i < 4; ++i) {
|
||||
for (j = 0; j < 4; ++j) {
|
||||
data[i].v[j] = temp[j].v[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void SetupAudioResampler(void)
|
||||
{
|
||||
int i, j;
|
||||
bool transpose = false;
|
||||
|
||||
GenerateResamplerFilter();
|
||||
|
||||
#ifdef SDL_SSE_INTRINSICS
|
||||
if (SDL_HasSSE()) {
|
||||
for (i = 0; i < 8; ++i) {
|
||||
ResampleFrame[i] = ResampleFrame_Generic_SSE;
|
||||
}
|
||||
transpose = true;
|
||||
} else
|
||||
#endif
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
if (SDL_HasNEON()) {
|
||||
for (i = 0; i < 8; ++i) {
|
||||
ResampleFrame[i] = ResampleFrame_Generic_NEON;
|
||||
}
|
||||
transpose = true;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
for (i = 0; i < 8; ++i) {
|
||||
ResampleFrame[i] = ResampleFrame_Generic;
|
||||
}
|
||||
|
||||
ResampleFrame[0] = ResampleFrame_Mono;
|
||||
ResampleFrame[1] = ResampleFrame_Stereo;
|
||||
}
|
||||
|
||||
if (transpose) {
|
||||
// Transpose each set of 4 coefficients, to reduce work when resampling
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
|
||||
for (j = 0; j + 4 <= RESAMPLER_SAMPLES_PER_FRAME; j += 4) {
|
||||
Transpose4x4(&ResamplerFilter[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_SetupAudioResampler(void)
|
||||
{
|
||||
static SDL_InitState init;
|
||||
|
||||
if (SDL_ShouldInit(&init)) {
|
||||
SetupAudioResampler();
|
||||
SDL_SetInitialized(&init, true);
|
||||
}
|
||||
}
|
||||
|
||||
Sint64 SDL_GetResampleRate(int src_rate, int dst_rate)
|
||||
{
|
||||
SDL_assert(src_rate > 0);
|
||||
SDL_assert(dst_rate > 0);
|
||||
|
||||
Sint64 numerator = (Sint64)src_rate << 32;
|
||||
Sint64 denominator = (Sint64)dst_rate;
|
||||
|
||||
// Generally it's expected that `dst_frames = (src_frames * dst_rate) / src_rate`
|
||||
// To match this as closely as possible without infinite precision, always round up the resample rate.
|
||||
// For example, without rounding up, a sample ratio of 2:3 would have `sample_rate = 0xAAAAAAAA`
|
||||
// After 3 frames, the position would be 0x1.FFFFFFFE, meaning we haven't fully consumed the second input frame.
|
||||
// By rounding up to 0xAAAAAAAB, we would instead reach 0x2.00000001, fulling consuming the second frame.
|
||||
// Technically you could say this is kicking the can 0x100000000 steps down the road, but I'm fine with that :)
|
||||
// sample_rate = div_ceil(numerator, denominator)
|
||||
Sint64 sample_rate = ((numerator - 1) / denominator) + 1;
|
||||
|
||||
SDL_assert(sample_rate > 0);
|
||||
|
||||
return sample_rate;
|
||||
}
|
||||
|
||||
int SDL_GetResamplerHistoryFrames(void)
|
||||
{
|
||||
// Even if we aren't currently resampling, make sure to keep enough history in case we need to later.
|
||||
|
||||
return RESAMPLER_MAX_PADDING_FRAMES;
|
||||
}
|
||||
|
||||
int SDL_GetResamplerPaddingFrames(Sint64 resample_rate)
|
||||
{
|
||||
// This must always be <= SDL_GetResamplerHistoryFrames()
|
||||
|
||||
return resample_rate ? RESAMPLER_MAX_PADDING_FRAMES : 0;
|
||||
}
|
||||
|
||||
// These are not general purpose. They do not check for all possible underflow/overflow
|
||||
SDL_FORCE_INLINE bool ResamplerAdd(Sint64 a, Sint64 b, Sint64 *ret)
|
||||
{
|
||||
if ((b > 0) && (a > SDL_MAX_SINT64 - b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*ret = a + b;
|
||||
return true;
|
||||
}
|
||||
|
||||
SDL_FORCE_INLINE bool ResamplerMul(Sint64 a, Sint64 b, Sint64 *ret)
|
||||
{
|
||||
if ((b > 0) && (a > SDL_MAX_SINT64 / b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*ret = a * b;
|
||||
return true;
|
||||
}
|
||||
|
||||
Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset)
|
||||
{
|
||||
// Calculate the index of the last input frame, then add 1.
|
||||
// ((((output_frames - 1) * resample_rate) + resample_offset) >> 32) + 1
|
||||
|
||||
Sint64 output_offset;
|
||||
if (!ResamplerMul(output_frames, resample_rate, &output_offset) ||
|
||||
!ResamplerAdd(output_offset, -resample_rate + resample_offset + 0x100000000, &output_offset)) {
|
||||
output_offset = SDL_MAX_SINT64;
|
||||
}
|
||||
|
||||
Sint64 input_frames = (Sint64)(Sint32)(output_offset >> 32);
|
||||
input_frames = SDL_max(input_frames, 0);
|
||||
|
||||
return input_frames;
|
||||
}
|
||||
|
||||
Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset)
|
||||
{
|
||||
Sint64 resample_offset = *inout_resample_offset;
|
||||
|
||||
// input_offset = (input_frames << 32) - resample_offset;
|
||||
Sint64 input_offset;
|
||||
if (!ResamplerMul(input_frames, 0x100000000, &input_offset) ||
|
||||
!ResamplerAdd(input_offset, -resample_offset, &input_offset)) {
|
||||
input_offset = SDL_MAX_SINT64;
|
||||
}
|
||||
|
||||
// output_frames = div_ceil(input_offset, resample_rate)
|
||||
Sint64 output_frames = (input_offset > 0) ? ((input_offset - 1) / resample_rate) + 1 : 0;
|
||||
|
||||
*inout_resample_offset = (output_frames * resample_rate) - input_offset;
|
||||
|
||||
return output_frames;
|
||||
}
|
||||
|
||||
void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
|
||||
Sint64 resample_rate, Sint64 *inout_resample_offset)
|
||||
{
|
||||
int i;
|
||||
Sint64 srcpos = *inout_resample_offset;
|
||||
ResampleFrameFunc resample_frame = ResampleFrame[chans - 1];
|
||||
|
||||
SDL_assert(resample_rate > 0);
|
||||
|
||||
src -= (RESAMPLER_ZERO_CROSSINGS - 1) * chans;
|
||||
|
||||
for (i = 0; i < outframes; ++i) {
|
||||
int srcindex = (int)(Sint32)(srcpos >> 32);
|
||||
Uint32 srcfraction = (Uint32)(srcpos & 0xFFFFFFFF);
|
||||
srcpos += resample_rate;
|
||||
|
||||
SDL_assert(srcindex >= -1 && srcindex < inframes);
|
||||
|
||||
const Cubic *filter = ResamplerFilter[srcfraction >> RESAMPLER_FILTER_INTERP_BITS];
|
||||
const float frac = (float)(srcfraction & (RESAMPLER_FILTER_INTERP_RANGE - 1)) * (1.0f / RESAMPLER_FILTER_INTERP_RANGE);
|
||||
|
||||
const float *frame = &src[srcindex * chans];
|
||||
resample_frame(frame, dst, filter, frac, chans);
|
||||
|
||||
dst += chans;
|
||||
}
|
||||
|
||||
*inout_resample_offset = srcpos - ((Sint64)inframes << 32);
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_
|
||||
+986
@@ -0,0 +1,986 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
#include <fenv.h>
|
||||
#endif
|
||||
|
||||
#define DIVBY2147483648 0.0000000004656612873077392578125f // 0x1p-31f
|
||||
|
||||
// start fallback scalar converters
|
||||
|
||||
// This code requires that floats are in the IEEE-754 binary32 format
|
||||
SDL_COMPILE_TIME_ASSERT(float_bits, sizeof(float) == sizeof(Uint32));
|
||||
|
||||
union float_bits {
|
||||
Uint32 u32;
|
||||
float f32;
|
||||
};
|
||||
|
||||
static void SDL_Convert_S8_to_F32_Scalar(float *dst, const Sint8 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S8", "F32");
|
||||
|
||||
for (i = num_samples - 1; i >= 0; --i) {
|
||||
/* 1) Construct a float in the range [65536.0, 65538.0)
|
||||
* 2) Shift the float range to [-1.0, 1.0) */
|
||||
union float_bits x;
|
||||
x.u32 = (Uint8)src[i] ^ 0x47800080u;
|
||||
dst[i] = x.f32 - 65537.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_U8_to_F32_Scalar(float *dst, const Uint8 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("U8", "F32");
|
||||
|
||||
for (i = num_samples - 1; i >= 0; --i) {
|
||||
/* 1) Construct a float in the range [65536.0, 65538.0)
|
||||
* 2) Shift the float range to [-1.0, 1.0) */
|
||||
union float_bits x;
|
||||
x.u32 = src[i] ^ 0x47800000u;
|
||||
dst[i] = x.f32 - 65537.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_S16_to_F32_Scalar(float *dst, const Sint16 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S16", "F32");
|
||||
|
||||
for (i = num_samples - 1; i >= 0; --i) {
|
||||
/* 1) Construct a float in the range [256.0, 258.0)
|
||||
* 2) Shift the float range to [-1.0, 1.0) */
|
||||
union float_bits x;
|
||||
x.u32 = (Uint16)src[i] ^ 0x43808000u;
|
||||
dst[i] = x.f32 - 257.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_S32_to_F32_Scalar(float *dst, const Sint32 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S32", "F32");
|
||||
|
||||
for (i = num_samples - 1; i >= 0; --i) {
|
||||
dst[i] = (float)src[i] * DIVBY2147483648;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a bit-mask based on the sign-bit. Should optimize to a single arithmetic-shift-right
|
||||
#define SIGNMASK(x) (Uint32)(0u - ((Uint32)(x) >> 31))
|
||||
|
||||
static void SDL_Convert_F32_to_S8_Scalar(Sint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S8");
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
|
||||
* 2) Shift the integer range from [0x47BFFF80, 0x47C00080] to [-128, 128]
|
||||
* 3) Clamp the value to [-128, 127] */
|
||||
union float_bits x;
|
||||
x.f32 = src[i] + 98304.0f;
|
||||
|
||||
Uint32 y = x.u32 - 0x47C00000u;
|
||||
Uint32 z = 0x7Fu - (y ^ SIGNMASK(y));
|
||||
y = y ^ (z & SIGNMASK(z));
|
||||
|
||||
dst[i] = (Sint8)(y & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_U8_Scalar(Uint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "U8");
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
|
||||
* 2) Shift the integer range from [0x47BFFF80, 0x47C00080] to [-128, 128]
|
||||
* 3) Clamp the value to [-128, 127]
|
||||
* 4) Shift the integer range from [-128, 127] to [0, 255] */
|
||||
union float_bits x;
|
||||
x.f32 = src[i] + 98304.0f;
|
||||
|
||||
Uint32 y = x.u32 - 0x47C00000u;
|
||||
Uint32 z = 0x7Fu - (y ^ SIGNMASK(y));
|
||||
y = (y ^ 0x80u) ^ (z & SIGNMASK(z));
|
||||
|
||||
dst[i] = (Uint8)(y & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S16_Scalar(Sint16 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S16");
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [383.0, 385.0]
|
||||
* 2) Shift the integer range from [0x43BF8000, 0x43C08000] to [-32768, 32768]
|
||||
* 3) Clamp values outside the [-32768, 32767] range */
|
||||
union float_bits x;
|
||||
x.f32 = src[i] + 384.0f;
|
||||
|
||||
Uint32 y = x.u32 - 0x43C00000u;
|
||||
Uint32 z = 0x7FFFu - (y ^ SIGNMASK(y));
|
||||
y = y ^ (z & SIGNMASK(z));
|
||||
|
||||
dst[i] = (Sint16)(y & 0xFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S32_Scalar(Sint32 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S32");
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [-2147483648.0, 2147483648.0]
|
||||
* 2) Set values outside the [-2147483648.0, 2147483647.0] range to -2147483648.0
|
||||
* 3) Convert the float to an integer, and fixup values outside the valid range */
|
||||
union float_bits x;
|
||||
x.f32 = src[i];
|
||||
|
||||
Uint32 y = x.u32 + 0x0F800000u;
|
||||
Uint32 z = y - 0xCF000000u;
|
||||
z &= SIGNMASK(y ^ z);
|
||||
x.u32 = y - z;
|
||||
|
||||
dst[i] = (Sint32)x.f32 ^ (Sint32)SIGNMASK(z);
|
||||
}
|
||||
}
|
||||
|
||||
#undef SIGNMASK
|
||||
|
||||
static void SDL_Convert_Swap16_Scalar(Uint16 *dst, const Uint16 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
dst[i] = SDL_Swap16(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_Swap32_Scalar(Uint32 *dst, const Uint32 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
dst[i] = SDL_Swap32(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// end fallback scalar converters
|
||||
|
||||
// Convert forwards, when sizeof(*src) >= sizeof(*dst)
|
||||
#define CONVERT_16_FWD(CVT1, CVT16) \
|
||||
int i = 0; \
|
||||
if (num_samples >= 16) { \
|
||||
while ((uintptr_t)(&dst[i]) & 15) { CVT1 ++i; } \
|
||||
while ((i + 16) <= num_samples) { CVT16 i += 16; } \
|
||||
} \
|
||||
while (i < num_samples) { CVT1 ++i; }
|
||||
|
||||
// Convert backwards, when sizeof(*src) <= sizeof(*dst)
|
||||
#define CONVERT_16_REV(CVT1, CVT16) \
|
||||
int i = num_samples; \
|
||||
if (i >= 16) { \
|
||||
while ((uintptr_t)(&dst[i]) & 15) { --i; CVT1 } \
|
||||
while (i >= 16) { i -= 16; CVT16 } \
|
||||
} \
|
||||
while (i > 0) { --i; CVT1 }
|
||||
|
||||
#ifdef SDL_SSE2_INTRINSICS
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_S8_to_F32_SSE2(float *dst, const Sint8 *src, int num_samples)
|
||||
{
|
||||
/* 1) Flip the sign bit to convert from S8 to U8 format
|
||||
* 2) Construct a float in the range [65536.0, 65538.0)
|
||||
* 3) Shift the float range to [-1.0, 1.0)
|
||||
* dst[i] = i2f((src[i] ^ 0x80) | 0x47800000) - 65537.0 */
|
||||
const __m128i zero = _mm_setzero_si128();
|
||||
const __m128i flipper = _mm_set1_epi8(-0x80);
|
||||
const __m128i caster = _mm_set1_epi16(0x4780 /* 0x47800000 = f2i(65536.0) */);
|
||||
const __m128 offset = _mm_set1_ps(-65537.0);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using SSE2)");
|
||||
|
||||
CONVERT_16_REV({
|
||||
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint8)src[i] ^ 0x47800080u)), offset));
|
||||
}, {
|
||||
const __m128i bytes = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i]), flipper);
|
||||
|
||||
const __m128i shorts0 = _mm_unpacklo_epi8(bytes, zero);
|
||||
const __m128i shorts1 = _mm_unpackhi_epi8(bytes, zero);
|
||||
|
||||
const __m128 floats0 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts0, caster)), offset);
|
||||
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts0, caster)), offset);
|
||||
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
|
||||
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
|
||||
|
||||
_mm_store_ps(&dst[i], floats0);
|
||||
_mm_store_ps(&dst[i + 4], floats1);
|
||||
_mm_store_ps(&dst[i + 8], floats2);
|
||||
_mm_store_ps(&dst[i + 12], floats3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_U8_to_F32_SSE2(float *dst, const Uint8 *src, int num_samples)
|
||||
{
|
||||
/* 1) Construct a float in the range [65536.0, 65538.0)
|
||||
* 2) Shift the float range to [-1.0, 1.0)
|
||||
* dst[i] = i2f(src[i] | 0x47800000) - 65537.0 */
|
||||
const __m128i zero = _mm_setzero_si128();
|
||||
const __m128i caster = _mm_set1_epi16(0x4780 /* 0x47800000 = f2i(65536.0) */);
|
||||
const __m128 offset = _mm_set1_ps(-65537.0);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using SSE2)");
|
||||
|
||||
CONVERT_16_REV({
|
||||
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint8)src[i] ^ 0x47800000u)), offset));
|
||||
}, {
|
||||
const __m128i bytes = _mm_loadu_si128((const __m128i *)&src[i]);
|
||||
|
||||
const __m128i shorts0 = _mm_unpacklo_epi8(bytes, zero);
|
||||
const __m128i shorts1 = _mm_unpackhi_epi8(bytes, zero);
|
||||
|
||||
const __m128 floats0 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts0, caster)), offset);
|
||||
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts0, caster)), offset);
|
||||
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
|
||||
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
|
||||
|
||||
_mm_store_ps(&dst[i], floats0);
|
||||
_mm_store_ps(&dst[i + 4], floats1);
|
||||
_mm_store_ps(&dst[i + 8], floats2);
|
||||
_mm_store_ps(&dst[i + 12], floats3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_S16_to_F32_SSE2(float *dst, const Sint16 *src, int num_samples)
|
||||
{
|
||||
/* 1) Flip the sign bit to convert from S16 to U16 format
|
||||
* 2) Construct a float in the range [256.0, 258.0)
|
||||
* 3) Shift the float range to [-1.0, 1.0)
|
||||
* dst[i] = i2f((src[i] ^ 0x8000) | 0x43800000) - 257.0 */
|
||||
const __m128i flipper = _mm_set1_epi16(-0x8000);
|
||||
const __m128i caster = _mm_set1_epi16(0x4380 /* 0x43800000 = f2i(256.0) */);
|
||||
const __m128 offset = _mm_set1_ps(-257.0f);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using SSE2)");
|
||||
|
||||
CONVERT_16_REV({
|
||||
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint16)src[i] ^ 0x43808000u)), offset));
|
||||
}, {
|
||||
const __m128i shorts0 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i]), flipper);
|
||||
const __m128i shorts1 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i + 8]), flipper);
|
||||
|
||||
const __m128 floats0 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts0, caster)), offset);
|
||||
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts0, caster)), offset);
|
||||
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
|
||||
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
|
||||
|
||||
_mm_store_ps(&dst[i], floats0);
|
||||
_mm_store_ps(&dst[i + 4], floats1);
|
||||
_mm_store_ps(&dst[i + 8], floats2);
|
||||
_mm_store_ps(&dst[i + 12], floats3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_S32_to_F32_SSE2(float *dst, const Sint32 *src, int num_samples)
|
||||
{
|
||||
// dst[i] = f32(src[i]) / f32(0x80000000)
|
||||
const __m128 scaler = _mm_set1_ps(DIVBY2147483648);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using SSE2)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
_mm_store_ss(&dst[i], _mm_mul_ss(_mm_cvt_si2ss(_mm_setzero_ps(), src[i]), scaler));
|
||||
}, {
|
||||
const __m128i ints0 = _mm_loadu_si128((const __m128i *)&src[i]);
|
||||
const __m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i + 4]);
|
||||
const __m128i ints2 = _mm_loadu_si128((const __m128i *)&src[i + 8]);
|
||||
const __m128i ints3 = _mm_loadu_si128((const __m128i *)&src[i + 12]);
|
||||
|
||||
const __m128 floats0 = _mm_mul_ps(_mm_cvtepi32_ps(ints0), scaler);
|
||||
const __m128 floats1 = _mm_mul_ps(_mm_cvtepi32_ps(ints1), scaler);
|
||||
const __m128 floats2 = _mm_mul_ps(_mm_cvtepi32_ps(ints2), scaler);
|
||||
const __m128 floats3 = _mm_mul_ps(_mm_cvtepi32_ps(ints3), scaler);
|
||||
|
||||
_mm_store_ps(&dst[i], floats0);
|
||||
_mm_store_ps(&dst[i + 4], floats1);
|
||||
_mm_store_ps(&dst[i + 8], floats2);
|
||||
_mm_store_ps(&dst[i + 12], floats3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S8_SSE2(Sint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
|
||||
* 2) Extract the lowest 16 bits and clamp to [-128, 127]
|
||||
* Overflow is correctly handled for inputs between roughly [-255.0, 255.0]
|
||||
* dst[i] = clamp(i16(f2i(src[i] + 98304.0) & 0xFFFF), -128, 127) */
|
||||
const __m128 offset = _mm_set1_ps(98304.0f);
|
||||
const __m128i mask = _mm_set1_epi16(0xFF);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using SSE2)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
const __m128i ints = _mm_castps_si128(_mm_add_ss(_mm_load_ss(&src[i]), offset));
|
||||
dst[i] = (Sint8)(_mm_cvtsi128_si32(_mm_packs_epi16(ints, ints)) & 0xFF);
|
||||
}, {
|
||||
const __m128 floats0 = _mm_loadu_ps(&src[i]);
|
||||
const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
|
||||
const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
|
||||
const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
|
||||
|
||||
const __m128i ints0 = _mm_castps_si128(_mm_add_ps(floats0, offset));
|
||||
const __m128i ints1 = _mm_castps_si128(_mm_add_ps(floats1, offset));
|
||||
const __m128i ints2 = _mm_castps_si128(_mm_add_ps(floats2, offset));
|
||||
const __m128i ints3 = _mm_castps_si128(_mm_add_ps(floats3, offset));
|
||||
|
||||
const __m128i shorts0 = _mm_and_si128(_mm_packs_epi16(ints0, ints1), mask);
|
||||
const __m128i shorts1 = _mm_and_si128(_mm_packs_epi16(ints2, ints3), mask);
|
||||
|
||||
const __m128i bytes = _mm_packus_epi16(shorts0, shorts1);
|
||||
|
||||
_mm_store_si128((__m128i *)&dst[i], bytes);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_U8_SSE2(Uint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [98304.0, 98306.0]
|
||||
* 2) Extract the lowest 16 bits and clamp to [0, 255]
|
||||
* Overflow is correctly handled for inputs between roughly [-254.0, 254.0]
|
||||
* dst[i] = clamp(i16(f2i(src[i] + 98305.0) & 0xFFFF), 0, 255) */
|
||||
const __m128 offset = _mm_set1_ps(98305.0f);
|
||||
const __m128i mask = _mm_set1_epi16(0xFF);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using SSE2)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
const __m128i ints = _mm_castps_si128(_mm_add_ss(_mm_load_ss(&src[i]), offset));
|
||||
dst[i] = (Uint8)(_mm_cvtsi128_si32(_mm_packus_epi16(ints, ints)) & 0xFF);
|
||||
}, {
|
||||
const __m128 floats0 = _mm_loadu_ps(&src[i]);
|
||||
const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
|
||||
const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
|
||||
const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
|
||||
|
||||
const __m128i ints0 = _mm_castps_si128(_mm_add_ps(floats0, offset));
|
||||
const __m128i ints1 = _mm_castps_si128(_mm_add_ps(floats1, offset));
|
||||
const __m128i ints2 = _mm_castps_si128(_mm_add_ps(floats2, offset));
|
||||
const __m128i ints3 = _mm_castps_si128(_mm_add_ps(floats3, offset));
|
||||
|
||||
const __m128i shorts0 = _mm_and_si128(_mm_packus_epi16(ints0, ints1), mask);
|
||||
const __m128i shorts1 = _mm_and_si128(_mm_packus_epi16(ints2, ints3), mask);
|
||||
|
||||
const __m128i bytes = _mm_packus_epi16(shorts0, shorts1);
|
||||
|
||||
_mm_store_si128((__m128i *)&dst[i], bytes);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S16_SSE2(Sint16 *dst, const float *src, int num_samples)
|
||||
{
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [256.0, 258.0]
|
||||
* 2) Shift the int range from [0x43800000, 0x43810000] to [-32768,32768]
|
||||
* 3) Clamp to range [-32768,32767]
|
||||
* Overflow is correctly handled for inputs between roughly [-257.0, +inf)
|
||||
* dst[i] = clamp(f2i(src[i] + 257.0) - 0x43808000, -32768, 32767) */
|
||||
const __m128 offset = _mm_set1_ps(257.0f);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using SSE2)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
const __m128i ints = _mm_sub_epi32(_mm_castps_si128(_mm_add_ss(_mm_load_ss(&src[i]), offset)), _mm_castps_si128(offset));
|
||||
dst[i] = (Sint16)(_mm_cvtsi128_si32(_mm_packs_epi32(ints, ints)) & 0xFFFF);
|
||||
}, {
|
||||
const __m128 floats0 = _mm_loadu_ps(&src[i]);
|
||||
const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
|
||||
const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
|
||||
const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
|
||||
|
||||
const __m128i ints0 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats0, offset)), _mm_castps_si128(offset));
|
||||
const __m128i ints1 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats1, offset)), _mm_castps_si128(offset));
|
||||
const __m128i ints2 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats2, offset)), _mm_castps_si128(offset));
|
||||
const __m128i ints3 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats3, offset)), _mm_castps_si128(offset));
|
||||
|
||||
const __m128i shorts0 = _mm_packs_epi32(ints0, ints1);
|
||||
const __m128i shorts1 = _mm_packs_epi32(ints2, ints3);
|
||||
|
||||
_mm_store_si128((__m128i *)&dst[i], shorts0);
|
||||
_mm_store_si128((__m128i *)&dst[i + 8], shorts1);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S32_SSE2(Sint32 *dst, const float *src, int num_samples)
|
||||
{
|
||||
/* 1) Scale the float range from [-1.0, 1.0] to [-2147483648.0, 2147483648.0]
|
||||
* 2) Convert to integer (values too small/large become 0x80000000 = -2147483648)
|
||||
* 3) Fixup values which were too large (0x80000000 ^ 0xFFFFFFFF = 2147483647)
|
||||
* dst[i] = i32(src[i] * 2147483648.0) ^ ((src[i] >= 2147483648.0) ? 0xFFFFFFFF : 0x00000000) */
|
||||
const __m128 limit = _mm_set1_ps(2147483648.0f);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using SSE2)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
const __m128 floats = _mm_load_ss(&src[i]);
|
||||
const __m128 values = _mm_mul_ss(floats, limit);
|
||||
const __m128i ints = _mm_xor_si128(_mm_cvttps_epi32(values), _mm_castps_si128(_mm_cmpge_ss(values, limit)));
|
||||
dst[i] = (Sint32)_mm_cvtsi128_si32(ints);
|
||||
}, {
|
||||
const __m128 floats0 = _mm_loadu_ps(&src[i]);
|
||||
const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
|
||||
const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
|
||||
const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
|
||||
|
||||
const __m128 values1 = _mm_mul_ps(floats0, limit);
|
||||
const __m128 values2 = _mm_mul_ps(floats1, limit);
|
||||
const __m128 values3 = _mm_mul_ps(floats2, limit);
|
||||
const __m128 values4 = _mm_mul_ps(floats3, limit);
|
||||
|
||||
const __m128i ints0 = _mm_xor_si128(_mm_cvttps_epi32(values1), _mm_castps_si128(_mm_cmpge_ps(values1, limit)));
|
||||
const __m128i ints1 = _mm_xor_si128(_mm_cvttps_epi32(values2), _mm_castps_si128(_mm_cmpge_ps(values2, limit)));
|
||||
const __m128i ints2 = _mm_xor_si128(_mm_cvttps_epi32(values3), _mm_castps_si128(_mm_cmpge_ps(values3, limit)));
|
||||
const __m128i ints3 = _mm_xor_si128(_mm_cvttps_epi32(values4), _mm_castps_si128(_mm_cmpge_ps(values4, limit)));
|
||||
|
||||
_mm_store_si128((__m128i *)&dst[i], ints0);
|
||||
_mm_store_si128((__m128i *)&dst[i + 4], ints1);
|
||||
_mm_store_si128((__m128i *)&dst[i + 8], ints2);
|
||||
_mm_store_si128((__m128i *)&dst[i + 12], ints3);
|
||||
})
|
||||
}
|
||||
#endif
|
||||
|
||||
// FIXME: SDL doesn't have SSSE3 detection, so use the next one up
|
||||
#ifdef SDL_SSE4_1_INTRINSICS
|
||||
static void SDL_TARGETING("ssse3") SDL_Convert_Swap16_SSSE3(Uint16 *dst, const Uint16 *src, int num_samples)
|
||||
{
|
||||
const __m128i shuffle = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);
|
||||
|
||||
CONVERT_16_FWD({
|
||||
dst[i] = SDL_Swap16(src[i]);
|
||||
}, {
|
||||
__m128i ints0 = _mm_loadu_si128((const __m128i *)&src[i]);
|
||||
__m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i + 8]);
|
||||
|
||||
ints0 = _mm_shuffle_epi8(ints0, shuffle);
|
||||
ints1 = _mm_shuffle_epi8(ints1, shuffle);
|
||||
|
||||
_mm_store_si128((__m128i *)&dst[i], ints0);
|
||||
_mm_store_si128((__m128i *)&dst[i + 8], ints1);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32 *dst, const Uint32 *src, int num_samples)
|
||||
{
|
||||
const __m128i shuffle = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
|
||||
|
||||
CONVERT_16_FWD({
|
||||
dst[i] = SDL_Swap32(src[i]);
|
||||
}, {
|
||||
__m128i ints0 = _mm_loadu_si128((const __m128i *)&src[i]);
|
||||
__m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i + 4]);
|
||||
__m128i ints2 = _mm_loadu_si128((const __m128i *)&src[i + 8]);
|
||||
__m128i ints3 = _mm_loadu_si128((const __m128i *)&src[i + 12]);
|
||||
|
||||
ints0 = _mm_shuffle_epi8(ints0, shuffle);
|
||||
ints1 = _mm_shuffle_epi8(ints1, shuffle);
|
||||
ints2 = _mm_shuffle_epi8(ints2, shuffle);
|
||||
ints3 = _mm_shuffle_epi8(ints3, shuffle);
|
||||
|
||||
_mm_store_si128((__m128i *)&dst[i], ints0);
|
||||
_mm_store_si128((__m128i *)&dst[i + 4], ints1);
|
||||
_mm_store_si128((__m128i *)&dst[i + 8], ints2);
|
||||
_mm_store_si128((__m128i *)&dst[i + 12], ints3);
|
||||
})
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
|
||||
// C99 requires that all code modifying floating point environment should
|
||||
// be guarded by the STDC FENV_ACCESS pragma; otherwise, it's undefined
|
||||
// behavior. However, the compiler support for this pragma is bad.
|
||||
#if defined(__clang__)
|
||||
#if __clang_major__ >= 12
|
||||
#if defined(__aarch64__)
|
||||
#pragma STDC FENV_ACCESS ON
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma fenv_access (on)
|
||||
#elif defined(__GNUC__)
|
||||
// GCC does not support the pragma at all
|
||||
#else
|
||||
#pragma STDC FENV_ACCESS ON
|
||||
#endif
|
||||
|
||||
static void SDL_Convert_S8_to_F32_NEON(float *dst, const Sint8 *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using NEON)");
|
||||
fenv_t fenv;
|
||||
feholdexcept(&fenv);
|
||||
|
||||
CONVERT_16_REV({
|
||||
vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32(src[i]), 7), 0);
|
||||
}, {
|
||||
int8x16_t bytes = vld1q_s8(&src[i]);
|
||||
|
||||
int16x8_t shorts0 = vmovl_s8(vget_low_s8(bytes));
|
||||
int16x8_t shorts1 = vmovl_s8(vget_high_s8(bytes));
|
||||
|
||||
float32x4_t floats0 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts0)), 7);
|
||||
float32x4_t floats1 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts0)), 7);
|
||||
float32x4_t floats2 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts1)), 7);
|
||||
float32x4_t floats3 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts1)), 7);
|
||||
|
||||
vst1q_f32(&dst[i], floats0);
|
||||
vst1q_f32(&dst[i + 4], floats1);
|
||||
vst1q_f32(&dst[i + 8], floats2);
|
||||
vst1q_f32(&dst[i + 12], floats3);
|
||||
})
|
||||
fesetenv(&fenv);
|
||||
}
|
||||
|
||||
static void SDL_Convert_U8_to_F32_NEON(float *dst, const Uint8 *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using NEON)");
|
||||
fenv_t fenv;
|
||||
feholdexcept(&fenv);
|
||||
|
||||
uint8x16_t flipper = vdupq_n_u8(0x80);
|
||||
|
||||
CONVERT_16_REV({
|
||||
vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32((Sint8)(src[i] ^ 0x80)), 7), 0);
|
||||
}, {
|
||||
int8x16_t bytes = vreinterpretq_s8_u8(veorq_u8(vld1q_u8(&src[i]), flipper));
|
||||
|
||||
int16x8_t shorts0 = vmovl_s8(vget_low_s8(bytes));
|
||||
int16x8_t shorts1 = vmovl_s8(vget_high_s8(bytes));
|
||||
|
||||
float32x4_t floats0 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts0)), 7);
|
||||
float32x4_t floats1 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts0)), 7);
|
||||
float32x4_t floats2 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts1)), 7);
|
||||
float32x4_t floats3 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts1)), 7);
|
||||
|
||||
vst1q_f32(&dst[i], floats0);
|
||||
vst1q_f32(&dst[i + 4], floats1);
|
||||
vst1q_f32(&dst[i + 8], floats2);
|
||||
vst1q_f32(&dst[i + 12], floats3);
|
||||
})
|
||||
fesetenv(&fenv);
|
||||
}
|
||||
|
||||
static void SDL_Convert_S16_to_F32_NEON(float *dst, const Sint16 *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using NEON)");
|
||||
fenv_t fenv;
|
||||
feholdexcept(&fenv);
|
||||
|
||||
CONVERT_16_REV({
|
||||
vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32(src[i]), 15), 0);
|
||||
}, {
|
||||
int16x8_t shorts0 = vld1q_s16(&src[i]);
|
||||
int16x8_t shorts1 = vld1q_s16(&src[i + 8]);
|
||||
|
||||
float32x4_t floats0 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts0)), 15);
|
||||
float32x4_t floats1 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts0)), 15);
|
||||
float32x4_t floats2 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts1)), 15);
|
||||
float32x4_t floats3 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts1)), 15);
|
||||
|
||||
vst1q_f32(&dst[i], floats0);
|
||||
vst1q_f32(&dst[i + 4], floats1);
|
||||
vst1q_f32(&dst[i + 8], floats2);
|
||||
vst1q_f32(&dst[i + 12], floats3);
|
||||
})
|
||||
fesetenv(&fenv);
|
||||
}
|
||||
|
||||
static void SDL_Convert_S32_to_F32_NEON(float *dst, const Sint32 *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using NEON)");
|
||||
fenv_t fenv;
|
||||
feholdexcept(&fenv);
|
||||
|
||||
CONVERT_16_FWD({
|
||||
vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vld1_dup_s32(&src[i]), 31), 0);
|
||||
}, {
|
||||
int32x4_t ints0 = vld1q_s32(&src[i]);
|
||||
int32x4_t ints1 = vld1q_s32(&src[i + 4]);
|
||||
int32x4_t ints2 = vld1q_s32(&src[i + 8]);
|
||||
int32x4_t ints3 = vld1q_s32(&src[i + 12]);
|
||||
|
||||
float32x4_t floats0 = vcvtq_n_f32_s32(ints0, 31);
|
||||
float32x4_t floats1 = vcvtq_n_f32_s32(ints1, 31);
|
||||
float32x4_t floats2 = vcvtq_n_f32_s32(ints2, 31);
|
||||
float32x4_t floats3 = vcvtq_n_f32_s32(ints3, 31);
|
||||
|
||||
vst1q_f32(&dst[i], floats0);
|
||||
vst1q_f32(&dst[i + 4], floats1);
|
||||
vst1q_f32(&dst[i + 8], floats2);
|
||||
vst1q_f32(&dst[i + 12], floats3);
|
||||
})
|
||||
fesetenv(&fenv);
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using NEON)");
|
||||
fenv_t fenv;
|
||||
feholdexcept(&fenv);
|
||||
|
||||
CONVERT_16_FWD({
|
||||
vst1_lane_s8(&dst[i], vreinterpret_s8_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)), 3);
|
||||
}, {
|
||||
float32x4_t floats0 = vld1q_f32(&src[i]);
|
||||
float32x4_t floats1 = vld1q_f32(&src[i + 4]);
|
||||
float32x4_t floats2 = vld1q_f32(&src[i + 8]);
|
||||
float32x4_t floats3 = vld1q_f32(&src[i + 12]);
|
||||
|
||||
int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
|
||||
int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
|
||||
int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
|
||||
int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
|
||||
|
||||
int16x8_t shorts0 = vcombine_s16(vshrn_n_s32(ints0, 16), vshrn_n_s32(ints1, 16));
|
||||
int16x8_t shorts1 = vcombine_s16(vshrn_n_s32(ints2, 16), vshrn_n_s32(ints3, 16));
|
||||
|
||||
int8x16_t bytes = vcombine_s8(vshrn_n_s16(shorts0, 8), vshrn_n_s16(shorts1, 8));
|
||||
|
||||
vst1q_s8(&dst[i], bytes);
|
||||
})
|
||||
fesetenv(&fenv);
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using NEON)");
|
||||
fenv_t fenv;
|
||||
feholdexcept(&fenv);
|
||||
|
||||
uint8x16_t flipper = vdupq_n_u8(0x80);
|
||||
|
||||
CONVERT_16_FWD({
|
||||
vst1_lane_u8(&dst[i],
|
||||
veor_u8(vreinterpret_u8_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)),
|
||||
vget_low_u8(flipper)), 3);
|
||||
}, {
|
||||
float32x4_t floats0 = vld1q_f32(&src[i]);
|
||||
float32x4_t floats1 = vld1q_f32(&src[i + 4]);
|
||||
float32x4_t floats2 = vld1q_f32(&src[i + 8]);
|
||||
float32x4_t floats3 = vld1q_f32(&src[i + 12]);
|
||||
|
||||
int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
|
||||
int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
|
||||
int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
|
||||
int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
|
||||
|
||||
int16x8_t shorts0 = vcombine_s16(vshrn_n_s32(ints0, 16), vshrn_n_s32(ints1, 16));
|
||||
int16x8_t shorts1 = vcombine_s16(vshrn_n_s32(ints2, 16), vshrn_n_s32(ints3, 16));
|
||||
|
||||
uint8x16_t bytes = veorq_u8(vreinterpretq_u8_s8(
|
||||
vcombine_s8(vshrn_n_s16(shorts0, 8), vshrn_n_s16(shorts1, 8))),
|
||||
flipper);
|
||||
|
||||
vst1q_u8(&dst[i], bytes);
|
||||
})
|
||||
fesetenv(&fenv);
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using NEON)");
|
||||
fenv_t fenv;
|
||||
feholdexcept(&fenv);
|
||||
|
||||
CONVERT_16_FWD({
|
||||
vst1_lane_s16(&dst[i], vreinterpret_s16_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)), 1);
|
||||
}, {
|
||||
float32x4_t floats0 = vld1q_f32(&src[i]);
|
||||
float32x4_t floats1 = vld1q_f32(&src[i + 4]);
|
||||
float32x4_t floats2 = vld1q_f32(&src[i + 8]);
|
||||
float32x4_t floats3 = vld1q_f32(&src[i + 12]);
|
||||
|
||||
int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
|
||||
int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
|
||||
int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
|
||||
int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
|
||||
|
||||
int16x8_t shorts0 = vcombine_s16(vshrn_n_s32(ints0, 16), vshrn_n_s32(ints1, 16));
|
||||
int16x8_t shorts1 = vcombine_s16(vshrn_n_s32(ints2, 16), vshrn_n_s32(ints3, 16));
|
||||
|
||||
vst1q_s16(&dst[i], shorts0);
|
||||
vst1q_s16(&dst[i + 8], shorts1);
|
||||
})
|
||||
fesetenv(&fenv);
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using NEON)");
|
||||
fenv_t fenv;
|
||||
feholdexcept(&fenv);
|
||||
|
||||
CONVERT_16_FWD({
|
||||
vst1_lane_s32(&dst[i], vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31), 0);
|
||||
}, {
|
||||
float32x4_t floats0 = vld1q_f32(&src[i]);
|
||||
float32x4_t floats1 = vld1q_f32(&src[i + 4]);
|
||||
float32x4_t floats2 = vld1q_f32(&src[i + 8]);
|
||||
float32x4_t floats3 = vld1q_f32(&src[i + 12]);
|
||||
|
||||
int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
|
||||
int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
|
||||
int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
|
||||
int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
|
||||
|
||||
vst1q_s32(&dst[i], ints0);
|
||||
vst1q_s32(&dst[i + 4], ints1);
|
||||
vst1q_s32(&dst[i + 8], ints2);
|
||||
vst1q_s32(&dst[i + 12], ints3);
|
||||
})
|
||||
fesetenv(&fenv);
|
||||
}
|
||||
|
||||
static void SDL_Convert_Swap16_NEON(Uint16 *dst, const Uint16 *src, int num_samples)
|
||||
{
|
||||
CONVERT_16_FWD({
|
||||
dst[i] = SDL_Swap16(src[i]);
|
||||
}, {
|
||||
uint8x16_t ints0 = vld1q_u8((const Uint8 *)&src[i]);
|
||||
uint8x16_t ints1 = vld1q_u8((const Uint8 *)&src[i + 8]);
|
||||
|
||||
ints0 = vrev16q_u8(ints0);
|
||||
ints1 = vrev16q_u8(ints1);
|
||||
|
||||
vst1q_u8((Uint8 *)&dst[i], ints0);
|
||||
vst1q_u8((Uint8 *)&dst[i + 8], ints1);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_Convert_Swap32_NEON(Uint32 *dst, const Uint32 *src, int num_samples)
|
||||
{
|
||||
CONVERT_16_FWD({
|
||||
dst[i] = SDL_Swap32(src[i]);
|
||||
}, {
|
||||
uint8x16_t ints0 = vld1q_u8((const Uint8 *)&src[i]);
|
||||
uint8x16_t ints1 = vld1q_u8((const Uint8 *)&src[i + 4]);
|
||||
uint8x16_t ints2 = vld1q_u8((const Uint8 *)&src[i + 8]);
|
||||
uint8x16_t ints3 = vld1q_u8((const Uint8 *)&src[i + 12]);
|
||||
|
||||
ints0 = vrev32q_u8(ints0);
|
||||
ints1 = vrev32q_u8(ints1);
|
||||
ints2 = vrev32q_u8(ints2);
|
||||
ints3 = vrev32q_u8(ints3);
|
||||
|
||||
vst1q_u8((Uint8 *)&dst[i], ints0);
|
||||
vst1q_u8((Uint8 *)&dst[i + 4], ints1);
|
||||
vst1q_u8((Uint8 *)&dst[i + 8], ints2);
|
||||
vst1q_u8((Uint8 *)&dst[i + 12], ints3);
|
||||
})
|
||||
}
|
||||
|
||||
#if defined(__clang__)
|
||||
#if __clang_major__ >= 12
|
||||
#if defined(__aarch64__)
|
||||
#pragma STDC FENV_ACCESS DEFAULT
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma fenv_access (off)
|
||||
#elif defined(__GNUC__)
|
||||
//
|
||||
#else
|
||||
#pragma STDC FENV_ACCESS DEFAULT
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#undef CONVERT_16_FWD
|
||||
#undef CONVERT_16_REV
|
||||
|
||||
// Function pointers set to a CPU-specific implementation.
|
||||
static void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_S32_to_F32)(float *dst, const Sint32 *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_F32_to_S8)(Sint8 *dst, const float *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples) = NULL;
|
||||
|
||||
static void (*SDL_Convert_Swap16)(Uint16 *dst, const Uint16 *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_Swap32)(Uint32 *dst, const Uint32 *src, int num_samples) = NULL;
|
||||
|
||||
void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt)
|
||||
{
|
||||
switch (src_fmt) {
|
||||
case SDL_AUDIO_S8:
|
||||
SDL_Convert_S8_to_F32(dst, (const Sint8 *) src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_U8:
|
||||
SDL_Convert_U8_to_F32(dst, (const Uint8 *) src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16:
|
||||
SDL_Convert_S16_to_F32(dst, (const Sint16 *) src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
|
||||
SDL_Convert_Swap16((Uint16 *)dst, (const Uint16 *)src, num_samples);
|
||||
SDL_Convert_S16_to_F32(dst, (const Sint16 *) dst, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32:
|
||||
SDL_Convert_S32_to_F32(dst, (const Sint32 *) src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
|
||||
SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples);
|
||||
SDL_Convert_S32_to_F32(dst, (const Sint32 *) dst, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
|
||||
SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples);
|
||||
break;
|
||||
|
||||
default: SDL_assert(!"Unexpected audio format!"); break;
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt)
|
||||
{
|
||||
switch (dst_fmt) {
|
||||
case SDL_AUDIO_S8:
|
||||
SDL_Convert_F32_to_S8((Sint8 *) dst, src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_U8:
|
||||
SDL_Convert_F32_to_U8((Uint8 *) dst, src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16:
|
||||
SDL_Convert_F32_to_S16((Sint16 *) dst, src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
|
||||
SDL_Convert_F32_to_S16((Sint16 *) dst, src, num_samples);
|
||||
SDL_Convert_Swap16((Uint16 *)dst, (const Uint16 *)dst, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32:
|
||||
SDL_Convert_F32_to_S32((Sint32 *) dst, src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
|
||||
SDL_Convert_F32_to_S32((Sint32 *) dst, src, num_samples);
|
||||
SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)dst, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
|
||||
SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples);
|
||||
break;
|
||||
|
||||
default: SDL_assert(!"Unexpected audio format!"); break;
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertAudioSwapEndian(void *dst, const void *src, int num_samples, int bitsize)
|
||||
{
|
||||
switch (bitsize) {
|
||||
case 16: SDL_Convert_Swap16((Uint16 *)dst, (const Uint16 *)src, num_samples); break;
|
||||
case 32: SDL_Convert_Swap32((Uint32 *)dst, (const Uint32 *)src, num_samples); break;
|
||||
default: SDL_assert(!"Unexpected audio format!"); break;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_ChooseAudioConverters(void)
|
||||
{
|
||||
static bool converters_chosen = false;
|
||||
if (converters_chosen) {
|
||||
return;
|
||||
}
|
||||
|
||||
#define SET_CONVERTER_FUNCS(fntype) \
|
||||
SDL_Convert_Swap16 = SDL_Convert_Swap16_##fntype; \
|
||||
SDL_Convert_Swap32 = SDL_Convert_Swap32_##fntype
|
||||
|
||||
#ifdef SDL_SSE4_1_INTRINSICS
|
||||
if (SDL_HasSSE41()) {
|
||||
SET_CONVERTER_FUNCS(SSSE3);
|
||||
} else
|
||||
#endif
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
if (SDL_HasNEON()) {
|
||||
SET_CONVERTER_FUNCS(NEON);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
SET_CONVERTER_FUNCS(Scalar);
|
||||
}
|
||||
|
||||
#undef SET_CONVERTER_FUNCS
|
||||
|
||||
#define SET_CONVERTER_FUNCS(fntype) \
|
||||
SDL_Convert_S8_to_F32 = SDL_Convert_S8_to_F32_##fntype; \
|
||||
SDL_Convert_U8_to_F32 = SDL_Convert_U8_to_F32_##fntype; \
|
||||
SDL_Convert_S16_to_F32 = SDL_Convert_S16_to_F32_##fntype; \
|
||||
SDL_Convert_S32_to_F32 = SDL_Convert_S32_to_F32_##fntype; \
|
||||
SDL_Convert_F32_to_S8 = SDL_Convert_F32_to_S8_##fntype; \
|
||||
SDL_Convert_F32_to_U8 = SDL_Convert_F32_to_U8_##fntype; \
|
||||
SDL_Convert_F32_to_S16 = SDL_Convert_F32_to_S16_##fntype; \
|
||||
SDL_Convert_F32_to_S32 = SDL_Convert_F32_to_S32_##fntype \
|
||||
|
||||
#ifdef SDL_SSE2_INTRINSICS
|
||||
if (SDL_HasSSE2()) {
|
||||
SET_CONVERTER_FUNCS(SSE2);
|
||||
} else
|
||||
#endif
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
if (SDL_HasNEON()) {
|
||||
SET_CONVERTER_FUNCS(NEON);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
SET_CONVERTER_FUNCS(Scalar);
|
||||
}
|
||||
|
||||
#undef SET_CONVERTER_FUNCS
|
||||
|
||||
converters_chosen = true;
|
||||
}
|
||||
Vendored
+290
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// This provides the default mixing callback for the SDL audio routines
|
||||
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
/* This table is used to add two sound values together and pin
|
||||
* the value to avoid overflow. (used with permission from ARDI)
|
||||
*/
|
||||
static const Uint8 mix8[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03,
|
||||
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
|
||||
0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24,
|
||||
0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
|
||||
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
|
||||
0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
|
||||
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B,
|
||||
0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
|
||||
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71,
|
||||
0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C,
|
||||
0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
||||
0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92,
|
||||
0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
|
||||
0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8,
|
||||
0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3,
|
||||
0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
|
||||
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9,
|
||||
0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4,
|
||||
0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
|
||||
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA,
|
||||
0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5,
|
||||
0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
||||
};
|
||||
|
||||
// The volume ranges from 0 - 128
|
||||
#define MIX_MAXVOLUME 128
|
||||
#define ADJUST_VOLUME(type, s, v) ((s) = (type)(((s) * (v)) / MIX_MAXVOLUME))
|
||||
#define ADJUST_VOLUME_U8(s, v) ((s) = (Uint8)(((((s) - 128) * (v)) / MIX_MAXVOLUME) + 128))
|
||||
|
||||
// !!! FIXME: This needs some SIMD magic.
|
||||
// !!! FIXME: Add fast-path for volume = 1
|
||||
// !!! FIXME: Use larger scales for 16-bit/32-bit integers
|
||||
|
||||
bool SDL_MixAudio(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, Uint32 len, float fvolume)
|
||||
{
|
||||
int volume = (int)SDL_roundf(fvolume * MIX_MAXVOLUME);
|
||||
|
||||
if (volume == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
|
||||
case SDL_AUDIO_U8:
|
||||
{
|
||||
Uint8 src_sample;
|
||||
|
||||
while (len--) {
|
||||
src_sample = *src;
|
||||
ADJUST_VOLUME_U8(src_sample, volume);
|
||||
*dst = mix8[*dst + src_sample];
|
||||
++dst;
|
||||
++src;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S8:
|
||||
{
|
||||
Sint8 *dst8, *src8;
|
||||
Sint8 src_sample;
|
||||
int dst_sample;
|
||||
const int max_audioval = SDL_MAX_SINT8;
|
||||
const int min_audioval = SDL_MIN_SINT8;
|
||||
|
||||
src8 = (Sint8 *)src;
|
||||
dst8 = (Sint8 *)dst;
|
||||
while (len--) {
|
||||
src_sample = *src8;
|
||||
ADJUST_VOLUME(Sint8, src_sample, volume);
|
||||
dst_sample = *dst8 + src_sample;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*dst8 = (Sint8)dst_sample;
|
||||
++dst8;
|
||||
++src8;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S16LE:
|
||||
{
|
||||
Sint16 src1, src2;
|
||||
int dst_sample;
|
||||
const int max_audioval = SDL_MAX_SINT16;
|
||||
const int min_audioval = SDL_MIN_SINT16;
|
||||
|
||||
len /= 2;
|
||||
while (len--) {
|
||||
src1 = SDL_Swap16LE(*(Sint16 *)src);
|
||||
ADJUST_VOLUME(Sint16, src1, volume);
|
||||
src2 = SDL_Swap16LE(*(Sint16 *)dst);
|
||||
src += 2;
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(Sint16 *)dst = SDL_Swap16LE((Sint16)dst_sample);
|
||||
dst += 2;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S16BE:
|
||||
{
|
||||
Sint16 src1, src2;
|
||||
int dst_sample;
|
||||
const int max_audioval = SDL_MAX_SINT16;
|
||||
const int min_audioval = SDL_MIN_SINT16;
|
||||
|
||||
len /= 2;
|
||||
while (len--) {
|
||||
src1 = SDL_Swap16BE(*(Sint16 *)src);
|
||||
ADJUST_VOLUME(Sint16, src1, volume);
|
||||
src2 = SDL_Swap16BE(*(Sint16 *)dst);
|
||||
src += 2;
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(Sint16 *)dst = SDL_Swap16BE((Sint16)dst_sample);
|
||||
dst += 2;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S32LE:
|
||||
{
|
||||
const Uint32 *src32 = (Uint32 *)src;
|
||||
Uint32 *dst32 = (Uint32 *)dst;
|
||||
Sint64 src1, src2;
|
||||
Sint64 dst_sample;
|
||||
const Sint64 max_audioval = SDL_MAX_SINT32;
|
||||
const Sint64 min_audioval = SDL_MIN_SINT32;
|
||||
|
||||
len /= 4;
|
||||
while (len--) {
|
||||
src1 = (Sint64)((Sint32)SDL_Swap32LE(*src32));
|
||||
src32++;
|
||||
ADJUST_VOLUME(Sint64, src1, volume);
|
||||
src2 = (Sint64)((Sint32)SDL_Swap32LE(*dst32));
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(dst32++) = SDL_Swap32LE((Uint32)((Sint32)dst_sample));
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S32BE:
|
||||
{
|
||||
const Uint32 *src32 = (Uint32 *)src;
|
||||
Uint32 *dst32 = (Uint32 *)dst;
|
||||
Sint64 src1, src2;
|
||||
Sint64 dst_sample;
|
||||
const Sint64 max_audioval = SDL_MAX_SINT32;
|
||||
const Sint64 min_audioval = SDL_MIN_SINT32;
|
||||
|
||||
len /= 4;
|
||||
while (len--) {
|
||||
src1 = (Sint64)((Sint32)SDL_Swap32BE(*src32));
|
||||
src32++;
|
||||
ADJUST_VOLUME(Sint64, src1, volume);
|
||||
src2 = (Sint64)((Sint32)SDL_Swap32BE(*dst32));
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(dst32++) = SDL_Swap32BE((Uint32)((Sint32)dst_sample));
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_F32LE:
|
||||
{
|
||||
const float *src32 = (float *)src;
|
||||
float *dst32 = (float *)dst;
|
||||
float src1, src2;
|
||||
float dst_sample;
|
||||
const float max_audioval = 1.0f;
|
||||
const float min_audioval = -1.0f;
|
||||
|
||||
len /= 4;
|
||||
while (len--) {
|
||||
src1 = SDL_SwapFloatLE(*src32) * fvolume;
|
||||
src2 = SDL_SwapFloatLE(*dst32);
|
||||
src32++;
|
||||
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(dst32++) = SDL_SwapFloatLE(dst_sample);
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_F32BE:
|
||||
{
|
||||
const float *src32 = (float *)src;
|
||||
float *dst32 = (float *)dst;
|
||||
float src1, src2;
|
||||
float dst_sample;
|
||||
const float max_audioval = 1.0f;
|
||||
const float min_audioval = -1.0f;
|
||||
|
||||
len /= 4;
|
||||
while (len--) {
|
||||
src1 = SDL_SwapFloatBE(*src32) * fvolume;
|
||||
src2 = SDL_SwapFloatBE(*dst32);
|
||||
src32++;
|
||||
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(dst32++) = SDL_SwapFloatBE(dst_sample);
|
||||
}
|
||||
} break;
|
||||
|
||||
default: // If this happens... FIXME!
|
||||
return SDL_SetError("SDL_MixAudio(): unknown audio format");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_sysaudio_h_
|
||||
#define SDL_sysaudio_h_
|
||||
|
||||
#define DEBUG_AUDIOSTREAM 0
|
||||
#define DEBUG_AUDIO_CONVERT 0
|
||||
|
||||
#if DEBUG_AUDIO_CONVERT
|
||||
#define LOG_DEBUG_AUDIO_CONVERT(from, to) SDL_Log("SDL_AUDIO_CONVERT: Converting %s to %s.", from, to);
|
||||
#else
|
||||
#define LOG_DEBUG_AUDIO_CONVERT(from, to)
|
||||
#endif
|
||||
|
||||
// !!! FIXME: These are wordy and unlocalized...
|
||||
#define DEFAULT_PLAYBACK_DEVNAME "System audio playback device"
|
||||
#define DEFAULT_RECORDING_DEVNAME "System audio recording device"
|
||||
|
||||
// these are used when no better specifics are known. We default to CD audio quality.
|
||||
#define DEFAULT_AUDIO_PLAYBACK_FORMAT SDL_AUDIO_S16
|
||||
#define DEFAULT_AUDIO_PLAYBACK_CHANNELS 2
|
||||
#define DEFAULT_AUDIO_PLAYBACK_FREQUENCY 44100
|
||||
|
||||
#define DEFAULT_AUDIO_RECORDING_FORMAT SDL_AUDIO_S16
|
||||
#define DEFAULT_AUDIO_RECORDING_CHANNELS 1
|
||||
#define DEFAULT_AUDIO_RECORDING_FREQUENCY 44100
|
||||
|
||||
#define SDL_MAX_CHANNELMAP_CHANNELS 8 // !!! FIXME: if SDL ever supports more channels, clean this out and make those parts dynamic.
|
||||
|
||||
typedef struct SDL_AudioDevice SDL_AudioDevice;
|
||||
typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice;
|
||||
|
||||
// Used by src/SDL.c to initialize a particular audio driver.
|
||||
extern bool SDL_InitAudio(const char *driver_name);
|
||||
|
||||
// Used by src/SDL.c to shut down previously-initialized audio.
|
||||
extern void SDL_QuitAudio(void);
|
||||
|
||||
// Function to get a list of audio formats, ordered most similar to `format` to least, 0-terminated. Don't free results.
|
||||
const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format);
|
||||
|
||||
// Must be called at least once before using converters.
|
||||
extern void SDL_ChooseAudioConverters(void);
|
||||
extern void SDL_SetupAudioResampler(void);
|
||||
|
||||
/* Backends should call this as devices are added to the system (such as
|
||||
a USB headset being plugged in), and should also be called for
|
||||
for every device found during DetectDevices(). */
|
||||
extern SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_AudioSpec *spec, void *handle);
|
||||
|
||||
/* Backends should call this if an opened audio device is lost.
|
||||
This can happen due to i/o errors, or a device being unplugged, etc. */
|
||||
extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device);
|
||||
|
||||
// Backends should call this if the system default device changes.
|
||||
extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device);
|
||||
|
||||
// Backends should call this if a device's format is changing (opened or not); SDL will update state and carry on with the new format.
|
||||
extern bool SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
|
||||
|
||||
// Same as above, but assume the device is already locked.
|
||||
extern bool SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
|
||||
|
||||
// Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. DOES NOT LOCK THE DEVICE.
|
||||
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle);
|
||||
|
||||
// Find an SDL_AudioDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
|
||||
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata);
|
||||
|
||||
// Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct.
|
||||
extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device);
|
||||
|
||||
// Backends can call this to get a reasonable default sample frame count for a device's sample rate.
|
||||
int SDL_GetDefaultSampleFramesFromFreq(const int freq);
|
||||
|
||||
// Backends can call this to get a standardized name for a thread to power a specific audio device.
|
||||
extern char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen);
|
||||
|
||||
// Backends can call these to change a device's refcount.
|
||||
extern void RefPhysicalAudioDevice(SDL_AudioDevice *device);
|
||||
extern void UnrefPhysicalAudioDevice(SDL_AudioDevice *device);
|
||||
|
||||
// These functions are the heart of the audio threads. Backends can call them directly if they aren't using the SDL-provided thread.
|
||||
extern void SDL_PlaybackAudioThreadSetup(SDL_AudioDevice *device);
|
||||
extern bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device);
|
||||
extern void SDL_PlaybackAudioThreadShutdown(SDL_AudioDevice *device);
|
||||
extern void SDL_RecordingAudioThreadSetup(SDL_AudioDevice *device);
|
||||
extern bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device);
|
||||
extern void SDL_RecordingAudioThreadShutdown(SDL_AudioDevice *device);
|
||||
extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device);
|
||||
|
||||
extern void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt);
|
||||
extern void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt);
|
||||
extern void ConvertAudioSwapEndian(void *dst, const void *src, int num_samples, int bitsize);
|
||||
|
||||
extern bool SDL_ChannelMapIsDefault(const int *map, int channels);
|
||||
extern bool SDL_ChannelMapIsBogus(const int *map, int channels);
|
||||
|
||||
// this gets used from the audio device threads. It has rules, don't use this if you don't know how to use it!
|
||||
extern void ConvertAudio(int num_frames,
|
||||
const void *src, SDL_AudioFormat src_format, int src_channels, const int *src_map,
|
||||
void *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
|
||||
void *scratch, float gain);
|
||||
|
||||
// Compare two SDL_AudioSpecs, return true if they match exactly.
|
||||
// Using SDL_memcmp directly isn't safe, since potential padding might not be initialized.
|
||||
// either channel map can be NULL for the default (and both should be if you don't care about them).
|
||||
extern bool SDL_AudioSpecsEqual(const SDL_AudioSpec *a, const SDL_AudioSpec *b, const int *channel_map_a, const int *channel_map_b);
|
||||
|
||||
// See if two channel maps match
|
||||
// either channel map can be NULL for the default (and both should be if you don't care about them).
|
||||
extern bool SDL_AudioChannelMapsEqual(int channels, const int *channel_map_a, const int *channel_map_b);
|
||||
|
||||
// allocate+copy a channel map.
|
||||
extern int *SDL_ChannelMapDup(const int *origchmap, int channels);
|
||||
|
||||
// Special case to let something in SDL_audiocvt.c access something in SDL_audio.c. Don't use this.
|
||||
extern void OnAudioStreamCreated(SDL_AudioStream *stream);
|
||||
extern void OnAudioStreamDestroy(SDL_AudioStream *stream);
|
||||
|
||||
// This just lets audio playback apply logical device gain at the same time as audiostream gain, so it's one multiplication instead of thousands.
|
||||
extern int SDL_GetAudioStreamDataAdjustGain(SDL_AudioStream *stream, void *voidbuf, int len, float extra_gain);
|
||||
|
||||
// This is the bulk of `SDL_SetAudioStream*putChannelMap`'s work, but it lets you skip the check about changing the device end of a stream if isinput==-1.
|
||||
extern bool SetAudioStreamChannelMap(SDL_AudioStream *stream, const SDL_AudioSpec *spec, int **stream_chmap, const int *chmap, int channels, int isinput);
|
||||
|
||||
|
||||
typedef struct SDL_AudioDriverImpl
|
||||
{
|
||||
void (*DetectDevices)(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording);
|
||||
bool (*OpenDevice)(SDL_AudioDevice *device);
|
||||
void (*ThreadInit)(SDL_AudioDevice *device); // Called by audio thread at start
|
||||
void (*ThreadDeinit)(SDL_AudioDevice *device); // Called by audio thread at end
|
||||
bool (*WaitDevice)(SDL_AudioDevice *device);
|
||||
bool (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen); // buffer and buflen are always from GetDeviceBuf, passed here for convenience.
|
||||
Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
|
||||
bool (*WaitRecordingDevice)(SDL_AudioDevice *device);
|
||||
int (*RecordDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
|
||||
void (*FlushRecording)(SDL_AudioDevice *device);
|
||||
void (*CloseDevice)(SDL_AudioDevice *device);
|
||||
void (*FreeDeviceHandle)(SDL_AudioDevice *device); // SDL is done with this device; free the handle from SDL_AddAudioDevice()
|
||||
void (*DeinitializeStart)(void); // SDL calls this, then starts destroying objects, then calls Deinitialize. This is a good place to stop hotplug detection.
|
||||
void (*Deinitialize)(void);
|
||||
|
||||
// Some flags to push duplicate code into the core and reduce #ifdefs.
|
||||
bool ProvidesOwnCallbackThread; // !!! FIXME: rename this, it's not a callback thread anymore.
|
||||
bool HasRecordingSupport;
|
||||
bool OnlyHasDefaultPlaybackDevice;
|
||||
bool OnlyHasDefaultRecordingDevice; // !!! FIXME: is there ever a time where you'd have a default playback and not a default recording (or vice versa)?
|
||||
} SDL_AudioDriverImpl;
|
||||
|
||||
|
||||
typedef struct SDL_PendingAudioDeviceEvent
|
||||
{
|
||||
Uint32 type;
|
||||
SDL_AudioDeviceID devid;
|
||||
struct SDL_PendingAudioDeviceEvent *next;
|
||||
} SDL_PendingAudioDeviceEvent;
|
||||
|
||||
typedef struct SDL_AudioDriver
|
||||
{
|
||||
const char *name; // The name of this audio driver
|
||||
const char *desc; // The description of this audio driver
|
||||
SDL_AudioDriverImpl impl; // the backend's interface
|
||||
SDL_RWLock *subsystem_rwlock; // A rwlock that protects several things in the audio subsystem (device hashtables, etc).
|
||||
SDL_HashTable *device_hash_physical; // the collection of currently-available audio devices (recording and playback), for mapping SDL_AudioDeviceID to an SDL_AudioDevice*.
|
||||
SDL_HashTable *device_hash_logical; // the collection of currently-available audio devices (recording and playback), for mapping SDL_AudioDeviceID to an SDL_LogicalAudioDevice*.
|
||||
SDL_AudioStream *existing_streams; // a list of all existing SDL_AudioStreams.
|
||||
SDL_AudioDeviceID default_playback_device_id;
|
||||
SDL_AudioDeviceID default_recording_device_id;
|
||||
SDL_PendingAudioDeviceEvent pending_events;
|
||||
SDL_PendingAudioDeviceEvent *pending_events_tail;
|
||||
|
||||
// !!! FIXME: most (all?) of these don't have to be atomic.
|
||||
SDL_AtomicInt playback_device_count;
|
||||
SDL_AtomicInt recording_device_count;
|
||||
SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs.
|
||||
} SDL_AudioDriver;
|
||||
|
||||
struct SDL_AudioQueue; // forward decl.
|
||||
|
||||
struct SDL_AudioStream
|
||||
{
|
||||
SDL_Mutex *lock;
|
||||
|
||||
SDL_PropertiesID props;
|
||||
|
||||
SDL_AudioStreamCallback get_callback;
|
||||
void *get_callback_userdata;
|
||||
SDL_AudioStreamCallback put_callback;
|
||||
void *put_callback_userdata;
|
||||
|
||||
SDL_AudioSpec src_spec;
|
||||
SDL_AudioSpec dst_spec;
|
||||
int *src_chmap;
|
||||
int *dst_chmap;
|
||||
float freq_ratio;
|
||||
float gain;
|
||||
|
||||
struct SDL_AudioQueue *queue;
|
||||
|
||||
SDL_AudioSpec input_spec; // The spec of input data currently being processed
|
||||
int *input_chmap;
|
||||
int input_chmap_storage[SDL_MAX_CHANNELMAP_CHANNELS]; // !!! FIXME: this needs to grow if SDL ever supports more channels. But if it grows, we should probably be more clever about allocations.
|
||||
Sint64 resample_offset;
|
||||
|
||||
Uint8 *work_buffer; // used for scratch space during data conversion/resampling.
|
||||
size_t work_buffer_allocation;
|
||||
|
||||
bool simplified; // true if created via SDL_OpenAudioDeviceStream
|
||||
|
||||
SDL_LogicalAudioDevice *bound_device;
|
||||
SDL_AudioStream *next_binding;
|
||||
SDL_AudioStream *prev_binding;
|
||||
|
||||
SDL_AudioStream *prev; // linked list of all existing streams (so we can free them on shutdown).
|
||||
SDL_AudioStream *next; // linked list of all existing streams (so we can free them on shutdown).
|
||||
};
|
||||
|
||||
/* Logical devices are an abstraction in SDL3; you can open the same physical
|
||||
device multiple times, and each will result in an object with its own set
|
||||
of bound audio streams, etc, even though internally these are all processed
|
||||
as a group when mixing the final output for the physical device. */
|
||||
struct SDL_LogicalAudioDevice
|
||||
{
|
||||
// the unique instance ID of this device.
|
||||
SDL_AudioDeviceID instance_id;
|
||||
|
||||
// The physical device associated with this opened device.
|
||||
SDL_AudioDevice *physical_device;
|
||||
|
||||
// If whole logical device is paused (process no streams bound to this device).
|
||||
SDL_AtomicInt paused;
|
||||
|
||||
// Volume of the device output.
|
||||
float gain;
|
||||
|
||||
// double-linked list of all audio streams currently bound to this opened device.
|
||||
SDL_AudioStream *bound_streams;
|
||||
|
||||
// true if this was opened as a default device.
|
||||
bool opened_as_default;
|
||||
|
||||
// true if device was opened with SDL_OpenAudioDeviceStream (so it forbids binding changes, etc).
|
||||
bool simplified;
|
||||
|
||||
// If non-NULL, callback into the app that lets them access the final postmix buffer.
|
||||
SDL_AudioPostmixCallback postmix;
|
||||
|
||||
// App-supplied pointer for postmix callback.
|
||||
void *postmix_userdata;
|
||||
|
||||
// double-linked list of opened devices on the same physical device.
|
||||
SDL_LogicalAudioDevice *next;
|
||||
SDL_LogicalAudioDevice *prev;
|
||||
};
|
||||
|
||||
struct SDL_AudioDevice
|
||||
{
|
||||
// A mutex for locking access to this struct
|
||||
SDL_Mutex *lock;
|
||||
|
||||
// A condition variable to protect device close, where we can't hold the device lock forever.
|
||||
SDL_Condition *close_cond;
|
||||
|
||||
// Reference count of the device; logical devices, device threads, etc, add to this.
|
||||
SDL_AtomicInt refcount;
|
||||
|
||||
// These are, initially, set from current_audio, but we might swap them out with Zombie versions on disconnect/failure.
|
||||
bool (*WaitDevice)(SDL_AudioDevice *device);
|
||||
bool (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen);
|
||||
Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
|
||||
bool (*WaitRecordingDevice)(SDL_AudioDevice *device);
|
||||
int (*RecordDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
|
||||
void (*FlushRecording)(SDL_AudioDevice *device);
|
||||
|
||||
// human-readable name of the device. ("SoundBlaster Pro 16")
|
||||
char *name;
|
||||
|
||||
// the unique instance ID of this device.
|
||||
SDL_AudioDeviceID instance_id;
|
||||
|
||||
// a way for the backend to identify this device _when not opened_
|
||||
void *handle;
|
||||
|
||||
// The device's current audio specification
|
||||
SDL_AudioSpec spec;
|
||||
|
||||
// The size, in bytes, of the device's playback/recording buffer.
|
||||
int buffer_size;
|
||||
|
||||
// The device's channel map, or NULL for SDL default layout.
|
||||
int *chmap;
|
||||
|
||||
// The device's default audio specification
|
||||
SDL_AudioSpec default_spec;
|
||||
|
||||
// Number of sample frames the devices wants per-buffer.
|
||||
int sample_frames;
|
||||
|
||||
// Value to use for SDL_memset to silence a buffer in this device's format
|
||||
int silence_value;
|
||||
|
||||
// non-zero if we are signaling the audio thread to end.
|
||||
SDL_AtomicInt shutdown;
|
||||
|
||||
// non-zero if this was a disconnected device and we're waiting for it to be decommissioned.
|
||||
SDL_AtomicInt zombie;
|
||||
|
||||
// true if this is a recording device instead of an playback device
|
||||
bool recording;
|
||||
|
||||
// true if audio thread can skip silence/mix/convert stages and just do a basic memcpy.
|
||||
bool simple_copy;
|
||||
|
||||
// Scratch buffers used for mixing.
|
||||
Uint8 *work_buffer;
|
||||
Uint8 *mix_buffer;
|
||||
float *postmix_buffer;
|
||||
|
||||
// Size of work_buffer (and mix_buffer) in bytes.
|
||||
int work_buffer_size;
|
||||
|
||||
// A thread to feed the audio device
|
||||
SDL_Thread *thread;
|
||||
|
||||
// true if this physical device is currently opened by the backend.
|
||||
bool currently_opened;
|
||||
|
||||
// Data private to this driver
|
||||
struct SDL_PrivateAudioData *hidden;
|
||||
|
||||
// All logical devices associated with this physical device.
|
||||
SDL_LogicalAudioDevice *logical_devices;
|
||||
};
|
||||
|
||||
typedef struct AudioBootStrap
|
||||
{
|
||||
const char *name;
|
||||
const char *desc;
|
||||
bool (*init)(SDL_AudioDriverImpl *impl);
|
||||
bool demand_only; // if true: request explicitly, or it won't be available.
|
||||
bool is_preferred;
|
||||
} AudioBootStrap;
|
||||
|
||||
// Not all of these are available in a given build. Use #ifdefs, etc.
|
||||
extern AudioBootStrap PRIVATEAUDIO_bootstrap;
|
||||
extern AudioBootStrap PIPEWIRE_PREFERRED_bootstrap;
|
||||
extern AudioBootStrap PIPEWIRE_bootstrap;
|
||||
extern AudioBootStrap PULSEAUDIO_bootstrap;
|
||||
extern AudioBootStrap ALSA_bootstrap;
|
||||
extern AudioBootStrap JACK_bootstrap;
|
||||
extern AudioBootStrap SNDIO_bootstrap;
|
||||
extern AudioBootStrap NETBSDAUDIO_bootstrap;
|
||||
extern AudioBootStrap DSP_bootstrap;
|
||||
extern AudioBootStrap WASAPI_bootstrap;
|
||||
extern AudioBootStrap DSOUND_bootstrap;
|
||||
extern AudioBootStrap WINMM_bootstrap;
|
||||
extern AudioBootStrap HAIKUAUDIO_bootstrap;
|
||||
extern AudioBootStrap COREAUDIO_bootstrap;
|
||||
extern AudioBootStrap DISKAUDIO_bootstrap;
|
||||
extern AudioBootStrap DUMMYAUDIO_bootstrap;
|
||||
extern AudioBootStrap AAUDIO_bootstrap;
|
||||
extern AudioBootStrap OPENSLES_bootstrap;
|
||||
extern AudioBootStrap PS2AUDIO_bootstrap;
|
||||
extern AudioBootStrap PSPAUDIO_bootstrap;
|
||||
extern AudioBootStrap VITAAUD_bootstrap;
|
||||
extern AudioBootStrap N3DSAUDIO_bootstrap;
|
||||
extern AudioBootStrap NGAGEAUDIO_bootstrap;
|
||||
extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
|
||||
extern AudioBootStrap QSAAUDIO_bootstrap;
|
||||
extern AudioBootStrap DOSSOUNDBLASTER_bootstrap;
|
||||
|
||||
#endif // SDL_sysaudio_h_
|
||||
Vendored
+2163
File diff suppressed because it is too large
Load Diff
Vendored
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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;
|
||||
+603
@@ -0,0 +1,603 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_AAUDIO
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_aaudio.h"
|
||||
|
||||
#include "../../core/android/SDL_android.h"
|
||||
#include <aaudio/AAudio.h>
|
||||
|
||||
#if __ANDROID_API__ < 31
|
||||
#define AAUDIO_FORMAT_PCM_I32 4
|
||||
#endif
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
AAudioStream *stream;
|
||||
int num_buffers;
|
||||
Uint8 *mixbuf; // Raw mixing buffer
|
||||
size_t mixbuf_bytes; // num_buffers * device->buffer_size
|
||||
size_t callback_bytes;
|
||||
size_t processed_bytes;
|
||||
SDL_Semaphore *semaphore;
|
||||
SDL_AtomicInt error_callback_triggered;
|
||||
};
|
||||
|
||||
// Debug
|
||||
#if 0
|
||||
#define LOGI(...) SDL_Log(__VA_ARGS__);
|
||||
#else
|
||||
#define LOGI(...)
|
||||
#endif
|
||||
|
||||
#define LIB_AAUDIO_SO "libaaudio.so"
|
||||
|
||||
SDL_ELF_NOTE_DLOPEN(
|
||||
"audio-aaudio",
|
||||
"Support for audio through AAudio",
|
||||
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
|
||||
LIB_AAUDIO_SO
|
||||
)
|
||||
|
||||
typedef struct AAUDIO_Data
|
||||
{
|
||||
SDL_SharedObject *handle;
|
||||
#define SDL_PROC(ret, func, params) ret (*func) params;
|
||||
#include "SDL_aaudiofuncs.h"
|
||||
} AAUDIO_Data;
|
||||
static AAUDIO_Data ctx;
|
||||
|
||||
static bool AAUDIO_LoadFunctions(AAUDIO_Data *data)
|
||||
{
|
||||
#define SDL_PROC(ret, func, params) \
|
||||
do { \
|
||||
data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \
|
||||
if (!data->func) { \
|
||||
return SDL_SetError("Couldn't load AAUDIO function %s: %s", #func, SDL_GetError()); \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
#define SDL_PROC_OPTIONAL(ret, func, params) \
|
||||
do { \
|
||||
data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); /* if it fails, okay. */ \
|
||||
} while (0);
|
||||
#include "SDL_aaudiofuncs.h"
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error)
|
||||
{
|
||||
LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error));
|
||||
|
||||
// You MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here.
|
||||
// Just flag the device so we can kill it in PlayDevice instead.
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
|
||||
SDL_SetAtomicInt(&device->hidden->error_callback_triggered, (int) error); // AAUDIO_OK is zero, so !triggered means no error.
|
||||
SDL_SignalSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice.
|
||||
}
|
||||
|
||||
static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
size_t framesize = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
size_t callback_bytes = numFrames * framesize;
|
||||
size_t old_buffer_index = hidden->callback_bytes / device->buffer_size;
|
||||
|
||||
if (device->recording) {
|
||||
const Uint8 *input = (const Uint8 *)audioData;
|
||||
size_t available_bytes = hidden->mixbuf_bytes - (hidden->callback_bytes - hidden->processed_bytes);
|
||||
size_t size = SDL_min(available_bytes, callback_bytes);
|
||||
size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
|
||||
size_t end = (offset + size) % hidden->mixbuf_bytes;
|
||||
SDL_assert(size <= hidden->mixbuf_bytes);
|
||||
|
||||
//LOGI("Recorded %zu frames, %zu available, %zu max (%zu written, %zu read)", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->callback_bytes / framesize, hidden->processed_bytes / framesize);
|
||||
|
||||
if (offset <= end) {
|
||||
SDL_memcpy(&hidden->mixbuf[offset], input, size);
|
||||
} else {
|
||||
size_t partial = (hidden->mixbuf_bytes - offset);
|
||||
SDL_memcpy(&hidden->mixbuf[offset], &input[0], partial);
|
||||
SDL_memcpy(&hidden->mixbuf[0], &input[partial], end);
|
||||
}
|
||||
|
||||
SDL_MemoryBarrierRelease();
|
||||
hidden->callback_bytes += size;
|
||||
|
||||
if (size < callback_bytes) {
|
||||
LOGI("Audio recording overflow, dropped %zu frames", (callback_bytes - size) / framesize);
|
||||
}
|
||||
} else {
|
||||
Uint8 *output = (Uint8 *)audioData;
|
||||
size_t available_bytes = (hidden->processed_bytes - hidden->callback_bytes);
|
||||
size_t size = SDL_min(available_bytes, callback_bytes);
|
||||
size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
|
||||
size_t end = (offset + size) % hidden->mixbuf_bytes;
|
||||
SDL_assert(size <= hidden->mixbuf_bytes);
|
||||
|
||||
//LOGI("Playing %zu frames, %zu available, %zu max (%zu written, %zu read)", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->processed_bytes / framesize, hidden->callback_bytes / framesize);
|
||||
|
||||
SDL_MemoryBarrierAcquire();
|
||||
if (offset <= end) {
|
||||
SDL_memcpy(output, &hidden->mixbuf[offset], size);
|
||||
} else {
|
||||
size_t partial = (hidden->mixbuf_bytes - offset);
|
||||
SDL_memcpy(&output[0], &hidden->mixbuf[offset], partial);
|
||||
SDL_memcpy(&output[partial], &hidden->mixbuf[0], end);
|
||||
}
|
||||
hidden->callback_bytes += size;
|
||||
|
||||
if (size < callback_bytes) {
|
||||
LOGI("Audio playback underflow, missed %zu frames", (callback_bytes - size) / framesize);
|
||||
SDL_memset(&output[size], device->silence_value, (callback_bytes - size));
|
||||
}
|
||||
}
|
||||
|
||||
size_t new_buffer_index = hidden->callback_bytes / device->buffer_size;
|
||||
while (old_buffer_index < new_buffer_index) {
|
||||
// Trigger audio processing
|
||||
SDL_SignalSemaphore(hidden->semaphore);
|
||||
++old_buffer_index;
|
||||
}
|
||||
|
||||
return AAUDIO_CALLBACK_RESULT_CONTINUE;
|
||||
}
|
||||
|
||||
static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
|
||||
return &hidden->mixbuf[offset];
|
||||
}
|
||||
|
||||
static bool AAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
// this semaphore won't fire when the app is in the background (AAUDIO_PauseDevices was called).
|
||||
if (SDL_WaitSemaphoreTimeout(device->hidden->semaphore, 100)) {
|
||||
return true; // semaphore was signaled, let's go!
|
||||
}
|
||||
// Still waiting on the semaphore (or the system), check other things then wait again.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool BuildAAudioStream(SDL_AudioDevice *device);
|
||||
|
||||
static bool RecoverAAudioDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
|
||||
// attempt to build a new stream, in case there's a new default device.
|
||||
ctx.AAudioStream_requestStop(hidden->stream);
|
||||
ctx.AAudioStream_close(hidden->stream);
|
||||
hidden->stream = NULL;
|
||||
|
||||
SDL_aligned_free(hidden->mixbuf);
|
||||
hidden->mixbuf = NULL;
|
||||
|
||||
SDL_DestroySemaphore(hidden->semaphore);
|
||||
hidden->semaphore = NULL;
|
||||
|
||||
const int prev_sample_frames = device->sample_frames;
|
||||
SDL_AudioSpec prevspec;
|
||||
SDL_copyp(&prevspec, &device->spec);
|
||||
|
||||
if (!BuildAAudioStream(device)) {
|
||||
return false; // oh well, we tried.
|
||||
}
|
||||
|
||||
// we don't know the new device spec until we open the new device, so we saved off the old one and force it back
|
||||
// so SDL_AudioDeviceFormatChanged can set up all the important state if necessary and then set it back to the new spec.
|
||||
const int new_sample_frames = device->sample_frames;
|
||||
SDL_AudioSpec newspec;
|
||||
SDL_copyp(&newspec, &device->spec);
|
||||
|
||||
device->sample_frames = prev_sample_frames;
|
||||
SDL_copyp(&device->spec, &prevspec);
|
||||
if (!SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames)) {
|
||||
return false; // ugh
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
|
||||
// AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
|
||||
const aaudio_result_t err = (aaudio_result_t) SDL_GetAtomicInt(&hidden->error_callback_triggered);
|
||||
if (err) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "aaudio: Audio device triggered error %d (%s)", (int) err, ctx.AAudio_convertResultToText(err));
|
||||
|
||||
if (!RecoverAAudioDevice(device)) {
|
||||
return false; // oh well, we went down hard.
|
||||
}
|
||||
} else {
|
||||
SDL_MemoryBarrierRelease();
|
||||
hidden->processed_bytes += buflen;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int AAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
|
||||
// AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
|
||||
if (SDL_GetAtomicInt(&hidden->error_callback_triggered)) {
|
||||
SDL_SetAtomicInt(&hidden->error_callback_triggered, 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_assert(buflen == device->buffer_size); // If this isn't true, we need to change semaphore trigger logic and account for wrapping copies here
|
||||
size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
|
||||
SDL_MemoryBarrierAcquire();
|
||||
SDL_memcpy(buffer, &hidden->mixbuf[offset], buflen);
|
||||
hidden->processed_bytes += buflen;
|
||||
return buflen;
|
||||
}
|
||||
|
||||
static void AAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
LOGI(SDL_FUNCTION);
|
||||
|
||||
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 void SetOptionalStreamUsage(AAudioStreamBuilder *builder)
|
||||
{
|
||||
if (ctx.AAudioStreamBuilder_setUsage) { // optional API: requires Android 28
|
||||
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE);
|
||||
if (hint) {
|
||||
aaudio_usage_t usage = AAUDIO_USAGE_MEDIA; // covers most things, and is the system default.
|
||||
if ((SDL_strcasecmp(hint, "Communications") == 0) || (SDL_strcasecmp(hint, "GameChat") == 0)) {
|
||||
usage = AAUDIO_USAGE_VOICE_COMMUNICATION;
|
||||
} else if (SDL_strcasecmp(hint, "Game") == 0) {
|
||||
usage = AAUDIO_USAGE_GAME;
|
||||
}
|
||||
ctx.AAudioStreamBuilder_setUsage(builder, usage);
|
||||
|
||||
// !!! FIXME: I _think_ this is okay with the current set of usages we support, but the docs
|
||||
// !!! FIXME: say you need to dip down into Java to call android.app.Activity.setVolumeControlStream(usage)
|
||||
// !!! FIXME: so the physical volume buttons control this stream, but that might be more for special cases
|
||||
// !!! FIXME: like notification sounds, etc, and it's possible you _don't_ want to override this for those
|
||||
// !!! FIXME: special cases, too! We'll revisit if there are bug reports.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool BuildAAudioStream(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
const bool recording = device->recording;
|
||||
aaudio_result_t res;
|
||||
|
||||
SDL_SetAtomicInt(&hidden->error_callback_triggered, 0);
|
||||
|
||||
AAudioStreamBuilder *builder = NULL;
|
||||
res = ctx.AAudio_createStreamBuilder(&builder);
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudio_createStreamBuilder %d", res);
|
||||
return SDL_SetError("SDL Failed AAudio_createStreamBuilder %d", res);
|
||||
} else if (!builder) {
|
||||
LOGI("SDL Failed AAudio_createStreamBuilder - builder NULL");
|
||||
return SDL_SetError("SDL Failed AAudio_createStreamBuilder - builder NULL");
|
||||
}
|
||||
|
||||
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||
const int aaudio_device_id = (int) ((size_t) device->handle);
|
||||
LOGI("Opening device id %d", aaudio_device_id);
|
||||
ctx.AAudioStreamBuilder_setDeviceId(builder, aaudio_device_id);
|
||||
#endif
|
||||
|
||||
aaudio_format_t format;
|
||||
if ((device->spec.format == SDL_AUDIO_S32) && (SDL_GetAndroidSDKVersion() >= 31)) {
|
||||
format = AAUDIO_FORMAT_PCM_I32;
|
||||
} else if (device->spec.format == SDL_AUDIO_F32) {
|
||||
format = AAUDIO_FORMAT_PCM_FLOAT;
|
||||
} else {
|
||||
format = AAUDIO_FORMAT_PCM_I16; // sint16 is a safe bet for everything else.
|
||||
}
|
||||
ctx.AAudioStreamBuilder_setFormat(builder, format);
|
||||
ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq);
|
||||
ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels);
|
||||
|
||||
int32_t sample_frames;
|
||||
if (SDL_GetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES)) {
|
||||
sample_frames = device->sample_frames;
|
||||
} else {
|
||||
// Use 20 ms for the default audio buffer size
|
||||
sample_frames = (device->spec.freq / 50);
|
||||
}
|
||||
ctx.AAudioStreamBuilder_setBufferCapacityInFrames(builder, 2 * sample_frames); // AAudio requires that the buffer capacity is at least
|
||||
ctx.AAudioStreamBuilder_setFramesPerDataCallback(builder, sample_frames); // twice the size of the data callback buffer size
|
||||
|
||||
const aaudio_direction_t direction = (recording ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT);
|
||||
ctx.AAudioStreamBuilder_setDirection(builder, direction);
|
||||
ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, device);
|
||||
ctx.AAudioStreamBuilder_setDataCallback(builder, AAUDIO_dataCallback, device);
|
||||
// Some devices have flat sounding audio when low latency mode is enabled, but this is a better experience for most people
|
||||
if (SDL_GetHintBoolean(SDL_HINT_ANDROID_LOW_LATENCY_AUDIO, true)) {
|
||||
SDL_Log("Low latency audio enabled");
|
||||
ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
|
||||
} else {
|
||||
SDL_Log("Low latency audio disabled");
|
||||
}
|
||||
|
||||
SetOptionalStreamUsage(builder);
|
||||
|
||||
if (recording && ctx.AAudioStreamBuilder_setInputPreset) { // optional API: requires Android 28
|
||||
// try to use a microphone that is for recording external audio. Otherwise Android might choose the mic used for talking
|
||||
// on the telephone when held to the user's ear, which is often not useful at any distance from the device.
|
||||
ctx.AAudioStreamBuilder_setInputPreset(builder, AAUDIO_INPUT_PRESET_CAMCORDER);
|
||||
}
|
||||
|
||||
LOGI("AAudio Try to open %u hz %s %u channels samples %u",
|
||||
device->spec.freq, SDL_GetAudioFormatName(device->spec.format),
|
||||
device->spec.channels, device->sample_frames);
|
||||
|
||||
res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream);
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res);
|
||||
ctx.AAudioStreamBuilder_delete(builder);
|
||||
return SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res));
|
||||
}
|
||||
ctx.AAudioStreamBuilder_delete(builder);
|
||||
|
||||
device->sample_frames = (int)ctx.AAudioStream_getFramesPerDataCallback(hidden->stream);
|
||||
if (device->sample_frames == AAUDIO_UNSPECIFIED) {
|
||||
// We'll get variable frames in the callback, make sure we have at least half a buffer available
|
||||
device->sample_frames = (int)ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 2;
|
||||
}
|
||||
|
||||
device->spec.freq = ctx.AAudioStream_getSampleRate(hidden->stream);
|
||||
device->spec.channels = ctx.AAudioStream_getChannelCount(hidden->stream);
|
||||
|
||||
format = ctx.AAudioStream_getFormat(hidden->stream);
|
||||
if (format == AAUDIO_FORMAT_PCM_I16) {
|
||||
device->spec.format = SDL_AUDIO_S16;
|
||||
} else if (format == AAUDIO_FORMAT_PCM_I32) {
|
||||
device->spec.format = SDL_AUDIO_S32;
|
||||
} else if (format == AAUDIO_FORMAT_PCM_FLOAT) {
|
||||
device->spec.format = SDL_AUDIO_F32;
|
||||
} else {
|
||||
return SDL_SetError("Got unexpected audio format %d from AAudioStream_getFormat", (int) format);
|
||||
}
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
// Allocate a triple buffered mixing buffer
|
||||
// Two buffers can be in the process of being filled while the third is being read
|
||||
hidden->num_buffers = 3;
|
||||
hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size);
|
||||
hidden->mixbuf = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), hidden->mixbuf_bytes);
|
||||
if (!hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
hidden->processed_bytes = 0;
|
||||
hidden->callback_bytes = 0;
|
||||
|
||||
hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers - 1);
|
||||
if (!hidden->semaphore) {
|
||||
LOGI("SDL Failed SDL_CreateSemaphore %s recording:%d", SDL_GetError(), recording);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("AAudio Actually opened %u hz %s %u channels samples %u, buffers %d",
|
||||
device->spec.freq, SDL_GetAudioFormatName(device->spec.format),
|
||||
device->spec.channels, device->sample_frames, hidden->num_buffers);
|
||||
|
||||
res = ctx.AAudioStream_requestStart(hidden->stream);
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudioStream_requestStart %d recording:%d", res, recording);
|
||||
return SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res));
|
||||
}
|
||||
|
||||
LOGI("SDL AAudioStream_requestStart OK");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// !!! FIXME: make this non-blocking!
|
||||
static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted)
|
||||
{
|
||||
SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1);
|
||||
}
|
||||
|
||||
static bool AAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||
SDL_assert(device->handle); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero.
|
||||
#endif
|
||||
|
||||
LOGI(SDL_FUNCTION);
|
||||
|
||||
if (device->recording) {
|
||||
// !!! FIXME: make this non-blocking!
|
||||
SDL_AtomicInt permission_response;
|
||||
SDL_SetAtomicInt(&permission_response, 0);
|
||||
if (!SDL_RequestAndroidPermission("android.permission.RECORD_AUDIO", RequestAndroidPermissionBlockingCallback, &permission_response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (SDL_GetAtomicInt(&permission_response) == 0) {
|
||||
SDL_Delay(10);
|
||||
}
|
||||
|
||||
if (SDL_GetAtomicInt(&permission_response) < 0) {
|
||||
LOGI("This app doesn't have RECORD_AUDIO permission");
|
||||
return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
|
||||
}
|
||||
}
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return BuildAAudioStream(device);
|
||||
}
|
||||
|
||||
static bool PauseOneDevice(SDL_AudioDevice *device, void *userdata)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden;
|
||||
if (hidden) {
|
||||
if (hidden->stream) {
|
||||
aaudio_result_t res;
|
||||
|
||||
if (device->recording) {
|
||||
// Pause() isn't implemented for recording, use Stop()
|
||||
res = ctx.AAudioStream_requestStop(hidden->stream);
|
||||
} else {
|
||||
res = ctx.AAudioStream_requestPause(hidden->stream);
|
||||
}
|
||||
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudioStream_requestPause %d", res);
|
||||
SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res));
|
||||
}
|
||||
}
|
||||
}
|
||||
return false; // keep enumerating.
|
||||
}
|
||||
|
||||
// Pause (block) all non already paused audio devices by taking their mixer lock
|
||||
void AAUDIO_PauseDevices(void)
|
||||
{
|
||||
if (ctx.handle) { // AAUDIO driver is used?
|
||||
(void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneDevice, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Resume (unblock) all non already paused audio devices by releasing their mixer lock
|
||||
static bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
if (hidden) {
|
||||
if (hidden->stream) {
|
||||
aaudio_result_t res = ctx.AAudioStream_requestStart(hidden->stream);
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudioStream_requestStart %d", res);
|
||||
SDL_SetError("%s : %s", SDL_FUNCTION, ctx.AAudio_convertResultToText(res));
|
||||
}
|
||||
}
|
||||
}
|
||||
return false; // keep enumerating.
|
||||
}
|
||||
|
||||
void AAUDIO_ResumeDevices(void)
|
||||
{
|
||||
if (ctx.handle) { // AAUDIO driver is used?
|
||||
(void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneDevice, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void AAUDIO_Deinitialize(void)
|
||||
{
|
||||
Android_StopAudioHotplug();
|
||||
|
||||
LOGI(SDL_FUNCTION);
|
||||
if (ctx.handle) {
|
||||
SDL_UnloadObject(ctx.handle);
|
||||
}
|
||||
SDL_zero(ctx);
|
||||
LOGI("End AAUDIO %s", SDL_GetError());
|
||||
}
|
||||
|
||||
|
||||
static bool AAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
LOGI(SDL_FUNCTION);
|
||||
|
||||
/* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release,
|
||||
* so don't use it until 8.1.
|
||||
*
|
||||
* See https://github.com/google/oboe/issues/40 for more information.
|
||||
*/
|
||||
if (SDL_GetAndroidSDKVersion() < 27) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_zero(ctx);
|
||||
|
||||
ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO);
|
||||
if (!ctx.handle) {
|
||||
LOGI("SDL couldn't find " LIB_AAUDIO_SO);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AAUDIO_LoadFunctions(&ctx)) {
|
||||
SDL_UnloadObject(ctx.handle);
|
||||
SDL_zero(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
impl->ThreadInit = Android_AudioThreadInit;
|
||||
impl->Deinitialize = AAUDIO_Deinitialize;
|
||||
impl->OpenDevice = AAUDIO_OpenDevice;
|
||||
impl->CloseDevice = AAUDIO_CloseDevice;
|
||||
impl->WaitDevice = AAUDIO_WaitDevice;
|
||||
impl->PlayDevice = AAUDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = AAUDIO_GetDeviceBuf;
|
||||
impl->WaitRecordingDevice = AAUDIO_WaitDevice;
|
||||
impl->RecordDevice = AAUDIO_RecordDevice;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||
impl->DetectDevices = Android_StartAudioHotplug;
|
||||
#else
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
impl->OnlyHasDefaultRecordingDevice = true;
|
||||
#endif
|
||||
|
||||
LOGI("SDL AAUDIO_Init OK");
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap AAUDIO_bootstrap = {
|
||||
"AAudio", "AAudio audio driver", AAUDIO_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_AAUDIO
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_aaudio_h_
|
||||
#define SDL_aaudio_h_
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_AAUDIO
|
||||
|
||||
extern void AAUDIO_ResumeDevices(void);
|
||||
extern void AAUDIO_PauseDevices(void);
|
||||
|
||||
#else
|
||||
|
||||
#define AAUDIO_ResumeDevices()
|
||||
#define AAUDIO_PauseDevices()
|
||||
|
||||
#endif
|
||||
|
||||
#endif // SDL_aaudio_h_
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright , (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_PROC_OPTIONAL
|
||||
#define SDL_PROC_OPTIONAL(ret, func, params) SDL_PROC(ret, func, params)
|
||||
#endif
|
||||
|
||||
#define SDL_PROC_UNUSED(ret, func, params)
|
||||
|
||||
SDL_PROC(const char *, AAudio_convertResultToText, (aaudio_result_t returnCode))
|
||||
SDL_PROC(const char *, AAudio_convertStreamStateToText, (aaudio_stream_state_t state))
|
||||
SDL_PROC(aaudio_result_t, AAudio_createStreamBuilder, (AAudioStreamBuilder * *builder))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setDeviceId, (AAudioStreamBuilder * builder, int32_t deviceId))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setSampleRate, (AAudioStreamBuilder * builder, int32_t sampleRate))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setChannelCount, (AAudioStreamBuilder * builder, int32_t channelCount))
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSamplesPerFrame, (AAudioStreamBuilder * builder, int32_t samplesPerFrame))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aaudio_format_t format))
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode))
|
||||
SDL_PROC_OPTIONAL(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_OPTIONAL(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
|
||||
#undef SDL_PROC_OPTIONAL
|
||||
+1609
File diff suppressed because it is too large
Load Diff
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_ALSA_audio_h_
|
||||
#define SDL_ALSA_audio_h_
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#define SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX 8
|
||||
#define SDL_AUDIO_ALSA__SDL_CHMAPS_N 9 // from 0 channels to 8 channels
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The audio device handle
|
||||
snd_pcm_t *pcm;
|
||||
|
||||
// Raw mixing buffer
|
||||
Uint8 *mixbuf;
|
||||
};
|
||||
|
||||
#endif // SDL_ALSA_audio_h_
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_coreaudio_h_
|
||||
#define SDL_coreaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#ifndef SDL_PLATFORM_IOS
|
||||
#define MACOSX_COREAUDIO
|
||||
#endif
|
||||
|
||||
#ifdef MACOSX_COREAUDIO
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
#else
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <UIKit/UIApplication.h>
|
||||
#endif
|
||||
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
|
||||
// Things named "Master" were renamed to "Main" in macOS 12.0's SDK.
|
||||
#ifdef MACOSX_COREAUDIO
|
||||
#include <AvailabilityMacros.h>
|
||||
#ifndef MAC_OS_VERSION_12_0
|
||||
#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
SDL_Thread *thread;
|
||||
AudioQueueRef audioQueue;
|
||||
int numAudioBuffers;
|
||||
AudioQueueBufferRef *audioBuffer;
|
||||
AudioQueueBufferRef current_buffer;
|
||||
AudioStreamBasicDescription strdesc;
|
||||
SDL_Semaphore *ready_semaphore;
|
||||
char *thread_error;
|
||||
#ifdef MACOSX_COREAUDIO
|
||||
AudioDeviceID deviceID;
|
||||
#else
|
||||
bool interrupted;
|
||||
CFTypeRef interruption_listener;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // SDL_coreaudio_h_
|
||||
+1045
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,681 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_DSOUND
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_directsound.h"
|
||||
#include <mmreg.h>
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
#include "../../core/windows/SDL_immdevice.h"
|
||||
#endif
|
||||
|
||||
#ifndef WAVE_FORMAT_IEEE_FLOAT
|
||||
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
|
||||
#endif
|
||||
|
||||
// For Vista+, we can enumerate DSound devices with IMMDevice
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
static bool SupportsIMMDevice = false;
|
||||
#endif
|
||||
|
||||
// DirectX function pointers for audio
|
||||
static SDL_SharedObject *DSoundDLL = NULL;
|
||||
typedef HRESULT (WINAPI *pfnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
|
||||
typedef HRESULT (WINAPI *pfnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
|
||||
typedef HRESULT (WINAPI *pfnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN);
|
||||
typedef HRESULT (WINAPI *pfnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
|
||||
typedef HRESULT (WINAPI *pfnGetDeviceID)(LPCGUID, LPGUID);
|
||||
static pfnDirectSoundCreate8 pDirectSoundCreate8 = NULL;
|
||||
static pfnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL;
|
||||
static pfnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL;
|
||||
static pfnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL;
|
||||
static pfnGetDeviceID pGetDeviceID = NULL;
|
||||
|
||||
#include <initguid.h>
|
||||
DEFINE_GUID(SDL_DSDEVID_DefaultPlayback, 0xdef00000, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
|
||||
DEFINE_GUID(SDL_DSDEVID_DefaultCapture, 0xdef00001, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
|
||||
|
||||
static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
|
||||
static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
|
||||
|
||||
static void DSOUND_Unload(void)
|
||||
{
|
||||
pDirectSoundCreate8 = NULL;
|
||||
pDirectSoundEnumerateW = NULL;
|
||||
pDirectSoundCaptureCreate8 = NULL;
|
||||
pDirectSoundCaptureEnumerateW = NULL;
|
||||
pGetDeviceID = NULL;
|
||||
|
||||
if (DSoundDLL) {
|
||||
SDL_UnloadObject(DSoundDLL);
|
||||
DSoundDLL = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool DSOUND_Load(void)
|
||||
{
|
||||
bool loaded = false;
|
||||
|
||||
DSOUND_Unload();
|
||||
|
||||
DSoundDLL = SDL_LoadObject("DSOUND.DLL");
|
||||
if (!DSoundDLL) {
|
||||
SDL_SetError("DirectSound: failed to load DSOUND.DLL");
|
||||
} else {
|
||||
// Now make sure we have DirectX 8 or better...
|
||||
#define DSOUNDLOAD(f) \
|
||||
{ \
|
||||
p##f = (pfn##f)SDL_LoadFunction(DSoundDLL, #f); \
|
||||
if (!p##f) \
|
||||
loaded = false; \
|
||||
}
|
||||
loaded = true; // will reset if necessary.
|
||||
DSOUNDLOAD(DirectSoundCreate8);
|
||||
DSOUNDLOAD(DirectSoundEnumerateW);
|
||||
DSOUNDLOAD(DirectSoundCaptureCreate8);
|
||||
DSOUNDLOAD(DirectSoundCaptureEnumerateW);
|
||||
DSOUNDLOAD(GetDeviceID);
|
||||
#undef DSOUNDLOAD
|
||||
|
||||
if (!loaded) {
|
||||
SDL_SetError("DirectSound: System doesn't appear to have DX8.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
DSOUND_Unload();
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
static bool SetDSerror(const char *function, int code)
|
||||
{
|
||||
const char *error;
|
||||
|
||||
switch (code) {
|
||||
case E_NOINTERFACE:
|
||||
error = "Unsupported interface -- Is DirectX 8.0 or later installed?";
|
||||
break;
|
||||
case DSERR_ALLOCATED:
|
||||
error = "Audio device in use";
|
||||
break;
|
||||
case DSERR_BADFORMAT:
|
||||
error = "Unsupported audio format";
|
||||
break;
|
||||
case DSERR_BUFFERLOST:
|
||||
error = "Mixing buffer was lost";
|
||||
break;
|
||||
case DSERR_CONTROLUNAVAIL:
|
||||
error = "Control requested is not available";
|
||||
break;
|
||||
case DSERR_INVALIDCALL:
|
||||
error = "Invalid call for the current state";
|
||||
break;
|
||||
case DSERR_INVALIDPARAM:
|
||||
error = "Invalid parameter";
|
||||
break;
|
||||
case DSERR_NODRIVER:
|
||||
error = "No audio device found";
|
||||
break;
|
||||
case DSERR_OUTOFMEMORY:
|
||||
error = "Out of memory";
|
||||
break;
|
||||
case DSERR_PRIOLEVELNEEDED:
|
||||
error = "Caller doesn't have priority";
|
||||
break;
|
||||
case DSERR_UNSUPPORTED:
|
||||
error = "Function not supported";
|
||||
break;
|
||||
default:
|
||||
error = "Unknown DirectSound error";
|
||||
break;
|
||||
}
|
||||
|
||||
return SDL_SetError("%s: %s (0x%x)", function, error, code);
|
||||
}
|
||||
|
||||
static void DSOUND_FreeDeviceHandle(SDL_AudioDevice *device)
|
||||
{
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
if (SupportsIMMDevice) {
|
||||
SDL_IMMDevice_FreeDeviceHandle(device);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
SDL_free(device->handle);
|
||||
}
|
||||
}
|
||||
|
||||
// FindAllDevs is presumably only used on WinXP; Vista and later can use IMMDevice for better results.
|
||||
typedef struct FindAllDevsData
|
||||
{
|
||||
bool recording;
|
||||
SDL_AudioDevice **default_device;
|
||||
LPCGUID default_device_guid;
|
||||
} FindAllDevsData;
|
||||
|
||||
static BOOL CALLBACK FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID userdata)
|
||||
{
|
||||
FindAllDevsData *data = (FindAllDevsData *) userdata;
|
||||
if (guid != NULL) { // skip default device
|
||||
char *str = WIN_LookupAudioDeviceName(desc, guid);
|
||||
if (str) {
|
||||
LPGUID cpyguid = (LPGUID)SDL_malloc(sizeof(GUID));
|
||||
if (cpyguid) {
|
||||
SDL_copyp(cpyguid, guid);
|
||||
|
||||
/* Note that spec is NULL, because we are required to connect to the
|
||||
* device before getting the channel mask and output format, making
|
||||
* this information inaccessible at enumeration time
|
||||
*/
|
||||
SDL_AudioDevice *device = SDL_AddAudioDevice(data->recording, str, NULL, cpyguid);
|
||||
if (device && data->default_device && data->default_device_guid) {
|
||||
if (SDL_memcmp(cpyguid, data->default_device_guid, sizeof (GUID)) == 0) {
|
||||
*data->default_device = device;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_free(str); // SDL_AddAudioDevice() makes a copy of this string.
|
||||
}
|
||||
}
|
||||
return TRUE; // keep enumerating.
|
||||
}
|
||||
|
||||
static void DSOUND_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
if (SupportsIMMDevice) {
|
||||
SDL_IMMDevice_EnumerateEndpoints(default_playback, default_recording, SDL_AUDIO_UNKNOWN, false);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
// Without IMMDevice, you can enumerate devices and figure out the default devices,
|
||||
// but you won't get device hotplug or default device change notifications. But this is
|
||||
// only for WinXP; Windows Vista and later should be using IMMDevice.
|
||||
FindAllDevsData data;
|
||||
GUID guid;
|
||||
|
||||
data.recording = true;
|
||||
data.default_device = default_recording;
|
||||
data.default_device_guid = (pGetDeviceID(&SDL_DSDEVID_DefaultCapture, &guid) == DS_OK) ? &guid : NULL;
|
||||
pDirectSoundCaptureEnumerateW(FindAllDevs, &data);
|
||||
|
||||
data.recording = false;
|
||||
data.default_device = default_playback;
|
||||
data.default_device_guid = (pGetDeviceID(&SDL_DSDEVID_DefaultPlayback, &guid) == DS_OK) ? &guid : NULL;
|
||||
pDirectSoundEnumerateW(FindAllDevs, &data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static bool DSOUND_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
/* Semi-busy wait, since we have no way of getting play notification
|
||||
on a primary mixing buffer located in hardware (DirectX 5.0)
|
||||
*/
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
DWORD status = 0;
|
||||
DWORD cursor = 0;
|
||||
DWORD junk = 0;
|
||||
HRESULT result = DS_OK;
|
||||
|
||||
// Try to restore a lost sound buffer
|
||||
IDirectSoundBuffer_GetStatus(device->hidden->mixbuf, &status);
|
||||
if (status & DSBSTATUS_BUFFERLOST) {
|
||||
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
|
||||
} else if (!(status & DSBSTATUS_PLAYING)) {
|
||||
result = IDirectSoundBuffer_Play(device->hidden->mixbuf, 0, 0, DSBPLAY_LOOPING);
|
||||
} else {
|
||||
// Find out where we are playing
|
||||
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, &junk, &cursor);
|
||||
if ((result == DS_OK) && ((cursor / device->buffer_size) != device->hidden->lastchunk)) {
|
||||
break; // ready for next chunk!
|
||||
}
|
||||
}
|
||||
|
||||
if ((result != DS_OK) && (result != DSERR_BUFFERLOST)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Delay(1); // not ready yet; sleep a bit.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DSOUND_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
// Unlock the buffer, allowing it to play
|
||||
SDL_assert(buflen == device->buffer_size);
|
||||
if (IDirectSoundBuffer_Unlock(device->hidden->mixbuf, (LPVOID) buffer, buflen, NULL, 0) != DS_OK) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *DSOUND_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
DWORD cursor = 0;
|
||||
DWORD junk = 0;
|
||||
HRESULT result = DS_OK;
|
||||
|
||||
SDL_assert(*buffer_size == device->buffer_size);
|
||||
|
||||
// Figure out which blocks to fill next
|
||||
device->hidden->locked_buf = NULL;
|
||||
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf,
|
||||
&junk, &cursor);
|
||||
if (result == DSERR_BUFFERLOST) {
|
||||
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
|
||||
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf,
|
||||
&junk, &cursor);
|
||||
}
|
||||
if (result != DS_OK) {
|
||||
SetDSerror("DirectSound GetCurrentPosition", result);
|
||||
return NULL;
|
||||
}
|
||||
cursor /= device->buffer_size;
|
||||
#ifdef DEBUG_SOUND
|
||||
// Detect audio dropouts
|
||||
{
|
||||
DWORD spot = cursor;
|
||||
if (spot < device->hidden->lastchunk) {
|
||||
spot += device->hidden->num_buffers;
|
||||
}
|
||||
if (spot > device->hidden->lastchunk + 1) {
|
||||
fprintf(stderr, "Audio dropout, missed %d fragments\n",
|
||||
(spot - (device->hidden->lastchunk + 1)));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
device->hidden->lastchunk = cursor;
|
||||
cursor = (cursor + 1) % device->hidden->num_buffers;
|
||||
cursor *= device->buffer_size;
|
||||
|
||||
// Lock the audio buffer
|
||||
DWORD rawlen = 0;
|
||||
result = IDirectSoundBuffer_Lock(device->hidden->mixbuf, cursor,
|
||||
device->buffer_size,
|
||||
(LPVOID *)&device->hidden->locked_buf,
|
||||
&rawlen, NULL, &junk, 0);
|
||||
if (result == DSERR_BUFFERLOST) {
|
||||
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
|
||||
result = IDirectSoundBuffer_Lock(device->hidden->mixbuf, cursor,
|
||||
device->buffer_size,
|
||||
(LPVOID *)&device->hidden->locked_buf, &rawlen, NULL,
|
||||
&junk, 0);
|
||||
}
|
||||
if (result != DS_OK) {
|
||||
SetDSerror("DirectSound Lock", result);
|
||||
return NULL;
|
||||
}
|
||||
return device->hidden->locked_buf;
|
||||
}
|
||||
|
||||
static bool DSOUND_WaitRecordingDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
DWORD junk, cursor;
|
||||
if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) {
|
||||
return false;
|
||||
} else if ((cursor / device->buffer_size) != h->lastchunk) {
|
||||
break;
|
||||
}
|
||||
SDL_Delay(1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int DSOUND_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
DWORD ptr1len, ptr2len;
|
||||
VOID *ptr1, *ptr2;
|
||||
|
||||
SDL_assert(buflen == device->buffer_size);
|
||||
|
||||
if (IDirectSoundCaptureBuffer_Lock(h->capturebuf, h->lastchunk * buflen, buflen, &ptr1, &ptr1len, &ptr2, &ptr2len, 0) != DS_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_assert(ptr1len == (DWORD)buflen);
|
||||
SDL_assert(ptr2 == NULL);
|
||||
SDL_assert(ptr2len == 0);
|
||||
|
||||
SDL_memcpy(buffer, ptr1, ptr1len);
|
||||
|
||||
if (IDirectSoundCaptureBuffer_Unlock(h->capturebuf, ptr1, ptr1len, ptr2, ptr2len) != DS_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
h->lastchunk = (h->lastchunk + 1) % h->num_buffers;
|
||||
|
||||
return (int) ptr1len;
|
||||
}
|
||||
|
||||
static void DSOUND_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
DWORD junk, cursor;
|
||||
if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) == DS_OK) {
|
||||
h->lastchunk = cursor / device->buffer_size;
|
||||
}
|
||||
}
|
||||
|
||||
static void DSOUND_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->mixbuf) {
|
||||
IDirectSoundBuffer_Stop(device->hidden->mixbuf);
|
||||
IDirectSoundBuffer_Release(device->hidden->mixbuf);
|
||||
}
|
||||
if (device->hidden->sound) {
|
||||
IDirectSound_Release(device->hidden->sound);
|
||||
}
|
||||
if (device->hidden->capturebuf) {
|
||||
IDirectSoundCaptureBuffer_Stop(device->hidden->capturebuf);
|
||||
IDirectSoundCaptureBuffer_Release(device->hidden->capturebuf);
|
||||
}
|
||||
if (device->hidden->capture) {
|
||||
IDirectSoundCapture_Release(device->hidden->capture);
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* This function tries to create a secondary audio buffer, and returns the
|
||||
number of audio chunks available in the created buffer. This is for
|
||||
playback devices, not recording.
|
||||
*/
|
||||
static bool CreateSecondary(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt)
|
||||
{
|
||||
LPDIRECTSOUND sndObj = device->hidden->sound;
|
||||
LPDIRECTSOUNDBUFFER *sndbuf = &device->hidden->mixbuf;
|
||||
HRESULT result = DS_OK;
|
||||
DSBUFFERDESC format;
|
||||
LPVOID pvAudioPtr1, pvAudioPtr2;
|
||||
DWORD dwAudioBytes1, dwAudioBytes2;
|
||||
|
||||
// Try to create the secondary buffer
|
||||
SDL_zero(format);
|
||||
format.dwSize = sizeof(format);
|
||||
format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
|
||||
format.dwFlags |= DSBCAPS_GLOBALFOCUS;
|
||||
format.dwBufferBytes = bufsize;
|
||||
format.lpwfxFormat = wfmt;
|
||||
result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSound CreateSoundBuffer", result);
|
||||
}
|
||||
IDirectSoundBuffer_SetFormat(*sndbuf, wfmt);
|
||||
|
||||
// Silence the initial audio buffer
|
||||
result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes,
|
||||
(LPVOID *)&pvAudioPtr1, &dwAudioBytes1,
|
||||
(LPVOID *)&pvAudioPtr2, &dwAudioBytes2,
|
||||
DSBLOCK_ENTIREBUFFER);
|
||||
if (result == DS_OK) {
|
||||
SDL_memset(pvAudioPtr1, device->silence_value, dwAudioBytes1);
|
||||
IDirectSoundBuffer_Unlock(*sndbuf,
|
||||
(LPVOID)pvAudioPtr1, dwAudioBytes1,
|
||||
(LPVOID)pvAudioPtr2, dwAudioBytes2);
|
||||
}
|
||||
|
||||
return true; // We're ready to go
|
||||
}
|
||||
|
||||
/* This function tries to create a capture buffer, and returns the
|
||||
number of audio chunks available in the created buffer. This is for
|
||||
recording devices, not playback.
|
||||
*/
|
||||
static bool CreateCaptureBuffer(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt)
|
||||
{
|
||||
LPDIRECTSOUNDCAPTURE capture = device->hidden->capture;
|
||||
LPDIRECTSOUNDCAPTUREBUFFER *capturebuf = &device->hidden->capturebuf;
|
||||
DSCBUFFERDESC format;
|
||||
HRESULT result;
|
||||
|
||||
SDL_zero(format);
|
||||
format.dwSize = sizeof(format);
|
||||
format.dwFlags = DSCBCAPS_WAVEMAPPED;
|
||||
format.dwBufferBytes = bufsize;
|
||||
format.lpwfxFormat = wfmt;
|
||||
|
||||
result = IDirectSoundCapture_CreateCaptureBuffer(capture, &format, capturebuf, NULL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSound CreateCaptureBuffer", result);
|
||||
}
|
||||
|
||||
result = IDirectSoundCaptureBuffer_Start(*capturebuf, DSCBSTART_LOOPING);
|
||||
if (result != DS_OK) {
|
||||
IDirectSoundCaptureBuffer_Release(*capturebuf);
|
||||
return SetDSerror("DirectSound Start", result);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// presumably this starts at zero, but just in case...
|
||||
result = IDirectSoundCaptureBuffer_GetCurrentPosition(*capturebuf, &junk, &cursor);
|
||||
if (result != DS_OK) {
|
||||
IDirectSoundCaptureBuffer_Stop(*capturebuf);
|
||||
IDirectSoundCaptureBuffer_Release(*capturebuf);
|
||||
return SetDSerror("DirectSound GetCurrentPosition", result);
|
||||
}
|
||||
|
||||
device->hidden->lastchunk = cursor / device->buffer_size;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DSOUND_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the audio device
|
||||
LPGUID guid;
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
if (SupportsIMMDevice) {
|
||||
guid = SDL_IMMDevice_GetDirectSoundGUID(device);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
guid = (LPGUID) device->handle;
|
||||
}
|
||||
|
||||
SDL_assert(guid != NULL);
|
||||
|
||||
HRESULT result;
|
||||
if (device->recording) {
|
||||
result = pDirectSoundCaptureCreate8(guid, &device->hidden->capture, NULL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSoundCaptureCreate8", result);
|
||||
}
|
||||
} else {
|
||||
result = pDirectSoundCreate8(guid, &device->hidden->sound, NULL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSoundCreate8", result);
|
||||
}
|
||||
result = IDirectSound_SetCooperativeLevel(device->hidden->sound,
|
||||
GetDesktopWindow(),
|
||||
DSSCL_NORMAL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSound SetCooperativeLevel", result);
|
||||
}
|
||||
}
|
||||
|
||||
const DWORD numchunks = 8;
|
||||
DWORD bufsize;
|
||||
bool tried_format = false;
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_U8:
|
||||
case SDL_AUDIO_S16:
|
||||
case SDL_AUDIO_S32:
|
||||
case SDL_AUDIO_F32:
|
||||
tried_format = true;
|
||||
|
||||
device->spec.format = test_format;
|
||||
device->spec.freq = SDL_min(DSBFREQUENCY_MAX, device->spec.freq);
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
bufsize = numchunks * device->buffer_size;
|
||||
if ((bufsize < DSBSIZE_MIN) || (bufsize > DSBSIZE_MAX)) {
|
||||
SDL_SetError("Sound buffer size must be between %d and %d",
|
||||
(int)((DSBSIZE_MIN < numchunks) ? 1 : DSBSIZE_MIN / numchunks),
|
||||
(int)(DSBSIZE_MAX / numchunks));
|
||||
} else {
|
||||
WAVEFORMATEXTENSIBLE wfmt;
|
||||
SDL_zero(wfmt);
|
||||
if (device->spec.channels > 2) {
|
||||
wfmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
wfmt.Format.cbSize = sizeof(wfmt) - sizeof(WAVEFORMATEX);
|
||||
|
||||
if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
|
||||
SDL_memcpy(&wfmt.SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID));
|
||||
} else {
|
||||
SDL_memcpy(&wfmt.SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID));
|
||||
}
|
||||
wfmt.Samples.wValidBitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
|
||||
switch (device->spec.channels) {
|
||||
case 3: // 3.0 (or 2.1)
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER;
|
||||
break;
|
||||
case 4: // 4.0
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case 5: // 5.0 (or 4.1)
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case 6: // 5.1
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case 7: // 6.1
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_BACK_CENTER;
|
||||
break;
|
||||
case 8: // 7.1
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
|
||||
break;
|
||||
default:
|
||||
SDL_assert(!"Unsupported channel count!");
|
||||
break;
|
||||
}
|
||||
} else if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
|
||||
wfmt.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
|
||||
} else {
|
||||
wfmt.Format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
}
|
||||
|
||||
wfmt.Format.wBitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
wfmt.Format.nChannels = (WORD)device->spec.channels;
|
||||
wfmt.Format.nSamplesPerSec = device->spec.freq;
|
||||
wfmt.Format.nBlockAlign = wfmt.Format.nChannels * (wfmt.Format.wBitsPerSample / 8);
|
||||
wfmt.Format.nAvgBytesPerSec = wfmt.Format.nSamplesPerSec * wfmt.Format.nBlockAlign;
|
||||
|
||||
const bool rc = device->recording ? CreateCaptureBuffer(device, bufsize, (WAVEFORMATEX *)&wfmt) : CreateSecondary(device, bufsize, (WAVEFORMATEX *)&wfmt);
|
||||
if (rc) {
|
||||
device->hidden->num_buffers = numchunks;
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
if (tried_format) {
|
||||
return false; // CreateSecondary() should have called SDL_SetError().
|
||||
}
|
||||
return SDL_SetError("%s: Unsupported audio format", "directsound");
|
||||
}
|
||||
|
||||
// Playback buffers will auto-start playing in DSOUND_WaitDevice()
|
||||
|
||||
return true; // good to go.
|
||||
}
|
||||
|
||||
static void DSOUND_DeinitializeStart(void)
|
||||
{
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
if (SupportsIMMDevice) {
|
||||
SDL_IMMDevice_Quit();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void DSOUND_Deinitialize(void)
|
||||
{
|
||||
DSOUND_Unload();
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
SupportsIMMDevice = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool DSOUND_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!DSOUND_Load()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
SupportsIMMDevice = SDL_IMMDevice_Init(NULL);
|
||||
#endif
|
||||
|
||||
impl->DetectDevices = DSOUND_DetectDevices;
|
||||
impl->OpenDevice = DSOUND_OpenDevice;
|
||||
impl->PlayDevice = DSOUND_PlayDevice;
|
||||
impl->WaitDevice = DSOUND_WaitDevice;
|
||||
impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
|
||||
impl->WaitRecordingDevice = DSOUND_WaitRecordingDevice;
|
||||
impl->RecordDevice = DSOUND_RecordDevice;
|
||||
impl->FlushRecording = DSOUND_FlushRecording;
|
||||
impl->CloseDevice = DSOUND_CloseDevice;
|
||||
impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle;
|
||||
impl->DeinitializeStart = DSOUND_DeinitializeStart;
|
||||
impl->Deinitialize = DSOUND_Deinitialize;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap DSOUND_bootstrap = {
|
||||
"directsound", "DirectSound", DSOUND_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_DSOUND
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_DISK
|
||||
|
||||
// Output raw audio data to a file.
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_diskaudio.h"
|
||||
|
||||
#define DISKDEFAULT_OUTFILE "sdlaudio.raw"
|
||||
#define DISKDEFAULT_INFILE "sdlaudio-in.raw"
|
||||
|
||||
static bool DISKAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
SDL_Delay(device->hidden->io_delay);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DISKAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
const int written = (int)SDL_WriteIO(device->hidden->io, buffer, (size_t)buffer_size);
|
||||
if (written != buffer_size) { // If we couldn't write, assume fatal error for now
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
SDL_Log("DISKAUDIO: Wrote %d bytes of audio data", (int) written);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *DISKAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int DISKAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
const int origbuflen = buflen;
|
||||
|
||||
if (h->io) {
|
||||
const int br = (int)SDL_ReadIO(h->io, buffer, (size_t)buflen);
|
||||
buflen -= br;
|
||||
buffer = ((Uint8 *)buffer) + br;
|
||||
if (buflen > 0) { // EOF (or error, but whatever).
|
||||
SDL_CloseIO(h->io);
|
||||
h->io = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// if we ran out of file, just write silence.
|
||||
SDL_memset(buffer, device->silence_value, buflen);
|
||||
|
||||
return origbuflen;
|
||||
}
|
||||
|
||||
static void DISKAUDIO_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
// no op...we don't advance the file pointer or anything.
|
||||
}
|
||||
|
||||
static void DISKAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->io) {
|
||||
SDL_CloseIO(device->hidden->io);
|
||||
}
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *get_filename(const bool recording)
|
||||
{
|
||||
const char *devname = SDL_GetHint(recording ? SDL_HINT_AUDIO_DISK_INPUT_FILE : SDL_HINT_AUDIO_DISK_OUTPUT_FILE);
|
||||
if (!devname) {
|
||||
devname = recording ? DISKDEFAULT_INFILE : DISKDEFAULT_OUTFILE;
|
||||
}
|
||||
return devname;
|
||||
}
|
||||
|
||||
static const char *AudioFormatString(SDL_AudioFormat fmt)
|
||||
{
|
||||
const char *str = SDL_GetAudioFormatName(fmt);
|
||||
SDL_assert(str);
|
||||
if (SDL_strncmp(str, "SDL_AUDIO_", 10) == 0) {
|
||||
str += 10; // so we return "S8" instead of "SDL_AUDIO_S8", etc.
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
static bool DISKAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
bool recording = device->recording;
|
||||
const char *fname = get_filename(recording);
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq);
|
||||
|
||||
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DISK_TIMESCALE);
|
||||
if (hint) {
|
||||
double scale = SDL_atof(hint);
|
||||
if (scale >= 0.0) {
|
||||
device->hidden->io_delay = (Uint32)SDL_round(device->hidden->io_delay * scale);
|
||||
}
|
||||
}
|
||||
|
||||
// Open the "audio device"
|
||||
device->hidden->io = SDL_IOFromFile(fname, recording ? "rb" : "wb");
|
||||
if (!device->hidden->io) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate mixing buffer
|
||||
if (!recording) {
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, "You are using the SDL disk i/o audio driver!");
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s], format=%s channels=%d freq=%d.",
|
||||
recording ? "Reading from" : "Writing to", fname,
|
||||
AudioFormatString(device->spec.format), device->spec.channels, device->spec.freq);
|
||||
|
||||
return true; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static void DISKAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
*default_playback = SDL_AddAudioDevice(false, DEFAULT_PLAYBACK_DEVNAME, NULL, (void *)0x1);
|
||||
*default_recording = SDL_AddAudioDevice(true, DEFAULT_RECORDING_DEVNAME, NULL, (void *)0x2);
|
||||
}
|
||||
|
||||
static bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = DISKAUDIO_OpenDevice;
|
||||
impl->WaitDevice = DISKAUDIO_WaitDevice;
|
||||
impl->WaitRecordingDevice = DISKAUDIO_WaitDevice;
|
||||
impl->PlayDevice = DISKAUDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = DISKAUDIO_GetDeviceBuf;
|
||||
impl->RecordDevice = DISKAUDIO_RecordDevice;
|
||||
impl->FlushRecording = DISKAUDIO_FlushRecording;
|
||||
impl->CloseDevice = DISKAUDIO_CloseDevice;
|
||||
impl->DetectDevices = DISKAUDIO_DetectDevices;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap DISKAUDIO_bootstrap = {
|
||||
"disk", "direct-to-disk audio", DISKAUDIO_Init, true, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_DISK
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_
|
||||
+631
@@ -0,0 +1,631 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_DOS_SOUNDBLASTER
|
||||
|
||||
#include "../../core/dos/SDL_dos.h"
|
||||
#include "../../core/dos/SDL_dos_scheduler.h"
|
||||
#include "SDL_dosaudio_sb.h"
|
||||
|
||||
// Set to 1 to force 8-bit mono (pre-SB16) code path even on SB16 hardware.
|
||||
// Useful for testing in DOSBox which always emulates an SB16 (DSP 4.x).
|
||||
#define FORCE_SB_8BIT 0
|
||||
|
||||
static int soundblaster_base_port = -1;
|
||||
static int soundblaster_irq = -1;
|
||||
static int soundblaster_dma_channel = -1;
|
||||
static int soundblaster_highdma_channel = -1;
|
||||
static int soundblaster_version = -1;
|
||||
static int soundblaster_version_minor = -1;
|
||||
static bool soundblaster_is_sb16 = false; // false when FORCE_SB_8BIT or DSP < 4
|
||||
static Uint8 soundblaster_silence_value = 0;
|
||||
|
||||
static void ResetSoundBlasterDSP(void)
|
||||
{
|
||||
// reset the DSP.
|
||||
const int reset_port = soundblaster_base_port + 0x6;
|
||||
outportb(reset_port, 1);
|
||||
SDL_DelayPrecise(3000); // wait at least 3 microseconds for hardware to see it.
|
||||
outportb(reset_port, 0);
|
||||
}
|
||||
|
||||
static bool ReadSoundBlasterReady(void)
|
||||
{
|
||||
const int ready_port = soundblaster_base_port + 0xE;
|
||||
return ((inportb(ready_port) & (1 << 7)) != 0);
|
||||
}
|
||||
|
||||
static void WriteSoundBlasterDSP(const Uint8 val)
|
||||
{
|
||||
const int port = soundblaster_base_port + 0xC;
|
||||
int timeout = 100000;
|
||||
while ((inportb(port) & (1 << 7)) && --timeout > 0) { /* spin until ready or timeout */
|
||||
}
|
||||
outportb(port, val);
|
||||
}
|
||||
|
||||
static Uint8 ReadSoundBlasterDSP(void)
|
||||
{
|
||||
const int query_port = soundblaster_base_port + 0xA;
|
||||
int timeout = 100000;
|
||||
while (!ReadSoundBlasterReady() && --timeout > 0) { /* spin until ready or timeout */
|
||||
}
|
||||
return (Uint8)inportb(query_port);
|
||||
}
|
||||
|
||||
// The ISR copies audio from a pre-allocated ring buffer directly into the
|
||||
// DMA half-buffer. The SDL audio thread fills the ring buffer cooperatively
|
||||
// (using the full SDL pipeline with all its allocations and mutexes), and
|
||||
// the ISR just does a memcpy (no SDL calls, no DPMI, no allocator).
|
||||
|
||||
// Number of DMA half-buffers that fit in the ring. Must be a power of two.
|
||||
// 4 chunks is ~45 ms at 44100 Hz, enough headroom for 22 fps frame times.
|
||||
#define RING_BUFFER_CHUNKS 4
|
||||
|
||||
// All of the following statics are memory-locked, making them safe to access
|
||||
// from the ISR without risking a page fault or DPMI re-entrance.
|
||||
|
||||
// ISR-cached copies of device state (avoids chasing heap pointers in IRQ context).
|
||||
static volatile int isr_irq_ack_port = 0;
|
||||
|
||||
// ISR-visible ring buffer state (all memory-locked).
|
||||
static volatile int isr_ring_read = 0;
|
||||
static volatile int isr_ring_write = 0;
|
||||
static int isr_ring_size = 0; // ring_size (power-of-2 bytes)
|
||||
static int isr_ring_mask = 0; // ring_size - 1
|
||||
static int isr_chunk_size = 0; // one DMA half-buffer, in bytes
|
||||
static Uint8 *isr_ring_buffer = NULL; // the ring itself (allocated and locked)
|
||||
static Uint8 *isr_dma_buffer = NULL; // pointer to the DMA double-buffer
|
||||
static int isr_dma_halfdma = 0; // half the DMA buffer size, in bytes
|
||||
static int isr_dma_channel = 0;
|
||||
static bool isr_is_16bit = false;
|
||||
static Uint8 isr_silence_value = 0;
|
||||
|
||||
// Copy `len` bytes from the ring buffer at position `pos` into `dst`,
|
||||
// handling the power-of-2 wrap. All pointers are memory-locked.
|
||||
static void RingCopyOut(Uint8 *dst, int pos, int len)
|
||||
{
|
||||
const int mask = isr_ring_mask;
|
||||
const int start = pos & mask;
|
||||
const int first = (start + len <= isr_ring_size) ? len : (isr_ring_size - start);
|
||||
SDL_memcpy(dst, isr_ring_buffer + start, first);
|
||||
if (first < len) {
|
||||
SDL_memcpy(dst + first, isr_ring_buffer, len - first);
|
||||
}
|
||||
}
|
||||
static void RingCopyOut_End(void) {}
|
||||
|
||||
// Determine which DMA half-buffer the hardware is NOT currently playing
|
||||
// (i.e. the one we should fill). Uses ISR-cached statics so we don't
|
||||
// chase any heap pointers.
|
||||
static Uint8 *ISR_GetDMAHalf(void)
|
||||
{
|
||||
int count;
|
||||
if (isr_is_16bit) {
|
||||
outportb(0xD8, 0x00);
|
||||
count = (int)inportb(0xC0 + (isr_dma_channel - 4) * 4 + 2);
|
||||
count += (int)inportb(0xC0 + (isr_dma_channel - 4) * 4 + 2) << 8;
|
||||
return isr_dma_buffer + (count < (isr_dma_halfdma / 2) ? 0 : isr_dma_halfdma);
|
||||
} else {
|
||||
outportb(0x0C, 0x00);
|
||||
count = (int)inportb(isr_dma_channel * 2 + 1);
|
||||
count += (int)inportb(isr_dma_channel * 2 + 1) << 8;
|
||||
return isr_dma_buffer + (count < isr_dma_halfdma ? 0 : isr_dma_halfdma);
|
||||
}
|
||||
}
|
||||
static void ISR_GetDMAHalf_End(void) {}
|
||||
|
||||
// The IRQ handler. Copies one chunk from the ring buffer into the DMA
|
||||
// half-buffer that the hardware isn't currently playing. If the ring is
|
||||
// empty it fills with silence (no stutter, just a brief gap).
|
||||
//
|
||||
// This function touches ONLY memory-locked data and does ONLY port I/O and
|
||||
// memcpy. No DPMI, no malloc, no mutex, no FPU.
|
||||
static void SoundBlasterIRQHandler(void)
|
||||
{
|
||||
// Acknowledge hardware first.
|
||||
inportb(isr_irq_ack_port);
|
||||
DOS_EndOfInterrupt(soundblaster_irq);
|
||||
|
||||
Uint8 *dma_dst = ISR_GetDMAHalf();
|
||||
|
||||
// How many bytes are available in the ring?
|
||||
const int avail = isr_ring_write - isr_ring_read; // both are monotonic
|
||||
|
||||
if (avail >= isr_chunk_size) {
|
||||
RingCopyOut(dma_dst, isr_ring_read, isr_chunk_size);
|
||||
isr_ring_read += isr_chunk_size;
|
||||
} else {
|
||||
// Ring underrun: fill with silence so we don't replay stale audio.
|
||||
SDL_memset(dma_dst, isr_silence_value, isr_chunk_size);
|
||||
}
|
||||
}
|
||||
static void SoundBlasterIRQHandler_End(void) {}
|
||||
|
||||
// Wait until the ring buffer has room for one more chunk.
|
||||
// The audio thread keeps yielding so the game's main thread can run while
|
||||
// we wait. Because the ISR is steadily draining the ring, this only blocks
|
||||
// when the ring is completely full, which is a good problem to have.
|
||||
static bool DOSSOUNDBLASTER_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
const int size = hidden->ring_size;
|
||||
|
||||
for (;;) {
|
||||
// Available space = ring_size - (write - read).
|
||||
// ring_write is ours (audio thread only), ring_read is advanced by
|
||||
// the ISR. Read the ISR's copy so we see the latest drain position.
|
||||
const int used = hidden->ring_write - isr_ring_read;
|
||||
if ((size - used) >= hidden->chunk_size) {
|
||||
return true; // room for at least one chunk
|
||||
}
|
||||
DOS_Yield();
|
||||
}
|
||||
}
|
||||
|
||||
static bool DOSSOUNDBLASTER_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const bool is_sb16 = soundblaster_is_sb16;
|
||||
|
||||
if (is_sb16) {
|
||||
// SB16 (DSP >= 4): 16-bit stereo signed
|
||||
device->spec.format = SDL_AUDIO_S16LE;
|
||||
device->spec.channels = 2;
|
||||
} else if (soundblaster_version >= 3) {
|
||||
// SB Pro (DSP 3.x): 8-bit stereo unsigned.
|
||||
// Max 22050 Hz in stereo (hardware interleaves L/R at double the rate).
|
||||
device->spec.format = SDL_AUDIO_U8;
|
||||
device->spec.channels = 2;
|
||||
} else {
|
||||
// SB 2.0 (DSP 2.x) and SB 1.x: 8-bit mono unsigned.
|
||||
device->spec.format = SDL_AUDIO_U8;
|
||||
device->spec.channels = 1;
|
||||
}
|
||||
|
||||
// Accept whatever frequency SDL3's audio layer passes in. For SB16 (DSP >= 4)
|
||||
// the hardware supports 5000–44100 Hz via DSP command 0x41. For pre-SB16,
|
||||
// clamp to hardware limits:
|
||||
// SB 1.x: max ~23 kHz mono
|
||||
// SB 2.0 (DSP 2.x): max 44100 Hz mono (high-speed), ~23 kHz normal
|
||||
// SB Pro (DSP 3.x): max 22050 Hz stereo, max 44100 Hz mono
|
||||
if (!is_sb16 && device->spec.freq > 22050) {
|
||||
device->spec.freq = 22050; // clamp to safe max for pre-SB16
|
||||
}
|
||||
device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq);
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
SDL_Log("SOUNDBLASTER: Opening at %d Hz, %d channels, format 0x%X, %d sample frames (DSP %d.%d, %s)",
|
||||
device->spec.freq, device->spec.channels, device->spec.format, device->sample_frames,
|
||||
soundblaster_version, soundblaster_version_minor, is_sb16 ? "SB16" : "pre-SB16");
|
||||
|
||||
if (device->buffer_size > (32 * 1024)) {
|
||||
return SDL_SetError("Buffer size is too large (choose smaller audio format and/or fewer sample frames)"); // DMA buffer has to fit in 64K segment, so buffer_size has to be half that, as we double it.
|
||||
}
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device->hidden = hidden;
|
||||
hidden->is_16bit = is_sb16;
|
||||
|
||||
ResetSoundBlasterDSP();
|
||||
|
||||
// allocate conventional memory for the DMA buffer.
|
||||
hidden->dma_channel = is_sb16 ? soundblaster_highdma_channel : soundblaster_dma_channel;
|
||||
if (hidden->dma_channel < 0) {
|
||||
SDL_free(hidden);
|
||||
return SDL_SetError("No %s DMA channel configured in BLASTER environment variable",
|
||||
is_sb16 ? "high (16-bit)" : "low (8-bit)");
|
||||
}
|
||||
hidden->dma_buflen = device->buffer_size * 2;
|
||||
hidden->dma_buffer = (Uint8 *)DOS_AllocateDMAMemory(hidden->dma_buflen, &hidden->dma_seginfo);
|
||||
if (!hidden->dma_buffer) {
|
||||
return SDL_SetError("Couldn't allocate Sound Blaster DMA buffer!");
|
||||
}
|
||||
|
||||
SDL_Log("SOUNDBLASTER: Allocated %d bytes of conventional memory at segment %d (ptr=%p)", (int)hidden->dma_buflen, (int)hidden->dma_seginfo.rm_segment, hidden->dma_buffer);
|
||||
|
||||
// silence the DMA buffer to start
|
||||
SDL_memset(hidden->dma_buffer, soundblaster_silence_value, hidden->dma_buflen);
|
||||
|
||||
// set up DMA controller.
|
||||
const Uint32 physical = DOS_LinearToPhysical(hidden->dma_buffer);
|
||||
const Uint8 physical_page = (physical >> 16) & 0xFF;
|
||||
|
||||
if (is_sb16) {
|
||||
// High DMA (16-bit, channels 5-7): ports in 0xC0-0xDF range, counts in words.
|
||||
const int dma_words = (hidden->dma_buflen / 2) - 1;
|
||||
outportb(0xD4, 0x04 | hidden->dma_channel); // mask the DMA channel
|
||||
outportb(0xD6, 0x58 | (hidden->dma_channel - 4)); // mode: single, read, auto-init
|
||||
static const int high_page_ports[] = { 0, 0, 0, 0, 0, 0x8B, 0x89, 0x8A }; // DMA page register ports for channels 5-7
|
||||
outportb(high_page_ports[hidden->dma_channel], physical_page); // page to transfer
|
||||
outportb(0xD8, 0x00); // clear the flip-flop
|
||||
outportb(0xC0 + (hidden->dma_channel - 4) * 4, (Uint8)((physical >> 1) & 0xFF)); // offset low (word address)
|
||||
outportb(0xC0 + (hidden->dma_channel - 4) * 4, (Uint8)((physical >> 9) & 0xFF)); // offset high
|
||||
outportb(0xD8, 0x00); // clear the flip-flop
|
||||
outportb(0xC0 + (hidden->dma_channel - 4) * 4 + 2, (Uint8)(dma_words & 0xFF)); // count low
|
||||
outportb(0xC0 + (hidden->dma_channel - 4) * 4 + 2, (Uint8)((dma_words >> 8) & 0xFF)); // count high
|
||||
outportb(0xD4, hidden->dma_channel & ~4); // unmask the DMA channel
|
||||
} else {
|
||||
// Low DMA (8-bit, channels 0-3): ports in 0x00-0x0F range, counts in bytes.
|
||||
static const int page_ports[] = { 0x87, 0x83, 0x81, 0x82 }; // DMA page register ports for channels 0-3 (yes, they're out of order — that's how the IBM PC DMA controller works)
|
||||
const int dma_bytes = hidden->dma_buflen - 1;
|
||||
outportb(0x0A, 0x04 | hidden->dma_channel); // mask the DMA channel
|
||||
outportb(0x0B, 0x58 | hidden->dma_channel); // mode: single, read, auto-init
|
||||
outportb(page_ports[hidden->dma_channel], physical_page); // page to transfer
|
||||
outportb(0x0C, 0x00); // clear the flip-flop
|
||||
outportb(hidden->dma_channel * 2, (Uint8)(physical & 0xFF)); // offset low (byte address)
|
||||
outportb(hidden->dma_channel * 2, (Uint8)((physical >> 8) & 0xFF)); // offset high
|
||||
outportb(0x0C, 0x00); // clear the flip-flop
|
||||
outportb(hidden->dma_channel * 2 + 1, (Uint8)(dma_bytes & 0xFF)); // count low
|
||||
outportb(hidden->dma_channel * 2 + 1, (Uint8)((dma_bytes >> 8) & 0xFF)); // count high
|
||||
outportb(0x0A, hidden->dma_channel); // unmask the DMA channel (just the channel number, no bit 2)
|
||||
}
|
||||
|
||||
// Cache the IRQ ack port so the ISR doesn't chase pointers.
|
||||
isr_irq_ack_port = is_sb16 ? (soundblaster_base_port + 0x0F) : (soundblaster_base_port + 0x0E);
|
||||
|
||||
// Set up the IRQ-driven ring buffer.
|
||||
hidden->chunk_size = device->buffer_size; // one DMA half-buffer
|
||||
|
||||
// Ring size must be a power of two and hold RING_BUFFER_CHUNKS chunks.
|
||||
hidden->ring_size = hidden->chunk_size * RING_BUFFER_CHUNKS;
|
||||
// Ensure power-of-two (chunk_size itself comes from SDL and may not be).
|
||||
{
|
||||
int rs = hidden->ring_size;
|
||||
rs--;
|
||||
rs |= rs >> 1;
|
||||
rs |= rs >> 2;
|
||||
rs |= rs >> 4;
|
||||
rs |= rs >> 8;
|
||||
rs |= rs >> 16;
|
||||
rs++;
|
||||
hidden->ring_size = rs;
|
||||
}
|
||||
|
||||
hidden->ring_buffer = (Uint8 *)SDL_calloc(1, hidden->ring_size);
|
||||
if (!hidden->ring_buffer) {
|
||||
return SDL_SetError("Couldn't allocate ring buffer for IRQ-driven audio");
|
||||
}
|
||||
hidden->staging_buffer = (Uint8 *)SDL_calloc(1, hidden->chunk_size);
|
||||
if (!hidden->staging_buffer) {
|
||||
return SDL_SetError("Couldn't allocate staging buffer for IRQ-driven audio");
|
||||
}
|
||||
|
||||
hidden->ring_read = 0;
|
||||
hidden->ring_write = 0;
|
||||
|
||||
// Populate ISR-visible statics (all will be memory-locked below).
|
||||
isr_ring_buffer = hidden->ring_buffer;
|
||||
isr_ring_read = 0;
|
||||
isr_ring_write = 0;
|
||||
isr_ring_size = hidden->ring_size;
|
||||
isr_ring_mask = hidden->ring_size - 1;
|
||||
isr_chunk_size = hidden->chunk_size;
|
||||
isr_dma_buffer = hidden->dma_buffer;
|
||||
isr_dma_halfdma = hidden->dma_buflen / 2;
|
||||
isr_dma_channel = hidden->dma_channel;
|
||||
isr_is_16bit = is_sb16;
|
||||
isr_silence_value = soundblaster_silence_value;
|
||||
|
||||
// Lock all ISR code and data to prevent page faults during interrupts.
|
||||
DOS_LockCode(SoundBlasterIRQHandler, SoundBlasterIRQHandler_End);
|
||||
DOS_LockCode(RingCopyOut, RingCopyOut_End);
|
||||
DOS_LockCode(ISR_GetDMAHalf, ISR_GetDMAHalf_End);
|
||||
DOS_LockData(*hidden->ring_buffer, hidden->ring_size);
|
||||
DOS_LockVariable(isr_ring_read);
|
||||
DOS_LockVariable(isr_ring_write);
|
||||
DOS_LockVariable(isr_ring_size);
|
||||
DOS_LockVariable(isr_ring_mask);
|
||||
DOS_LockVariable(isr_chunk_size);
|
||||
DOS_LockVariable(isr_ring_buffer);
|
||||
DOS_LockVariable(isr_dma_buffer);
|
||||
DOS_LockVariable(isr_dma_halfdma);
|
||||
DOS_LockVariable(isr_dma_channel);
|
||||
DOS_LockVariable(isr_is_16bit);
|
||||
DOS_LockVariable(isr_silence_value);
|
||||
DOS_LockVariable(isr_irq_ack_port);
|
||||
DOS_LockVariable(soundblaster_irq);
|
||||
|
||||
DOS_HookInterrupt(soundblaster_irq, SoundBlasterIRQHandler, &hidden->interrupt_hook);
|
||||
|
||||
WriteSoundBlasterDSP(0xD1); // turn on the speaker
|
||||
// The speaker-on command takes up to 112 ms to complete on real hardware.
|
||||
// Poll the DSP write status port (bit 7 clears when the DSP is ready);
|
||||
// in practice — and always in DOSBox — it completes almost instantly.
|
||||
{
|
||||
const int status_port = soundblaster_base_port + 0xC;
|
||||
const Uint64 deadline = SDL_GetTicksNS() + SDL_MS_TO_NS(112);
|
||||
while ((inportb(status_port) & 0x80) && (SDL_GetTicksNS() < deadline)) {
|
||||
SDL_DelayPrecise(SDL_US_TO_NS(100)); // brief yield between polls
|
||||
}
|
||||
}
|
||||
|
||||
if (is_sb16) {
|
||||
// SB16 (DSP >= 4): set output sample rate directly
|
||||
WriteSoundBlasterDSP(0x41); // set output sampling rate
|
||||
WriteSoundBlasterDSP((Uint8)(device->spec.freq >> 8));
|
||||
WriteSoundBlasterDSP((Uint8)(device->spec.freq & 0xFF));
|
||||
|
||||
// start 16-bit auto-initialize DMA mode
|
||||
// half the total buffer per transfer, then convert to samples (divide by 2 because they are 16-bits each).
|
||||
const int block_size = ((hidden->dma_buflen / 2) / sizeof(Sint16)) - 1; // one less than samples to be transferred.
|
||||
WriteSoundBlasterDSP(0xB6); // 16-bit output, auto-init, FIFO on
|
||||
WriteSoundBlasterDSP(0x30); // 16-bit stereo signed PCM
|
||||
WriteSoundBlasterDSP((Uint8)(block_size & 0xFF));
|
||||
WriteSoundBlasterDSP((Uint8)(block_size >> 8));
|
||||
} else {
|
||||
// Pre-SB16 (DSP < 4): set sample rate via Time Constant
|
||||
// Time Constant = 256 - (1000000 / (channels * freq))
|
||||
// In stereo mode the SB Pro interleaves L/R samples, so the effective
|
||||
// hardware rate is channels * freq.
|
||||
const int effective_rate = device->spec.channels * device->spec.freq;
|
||||
const Uint8 time_constant = (Uint8)(256 - (1000000 / effective_rate));
|
||||
WriteSoundBlasterDSP(0x40); // set time constant
|
||||
WriteSoundBlasterDSP(time_constant);
|
||||
|
||||
// SB Pro (DSP 3.x): enable or disable stereo via mixer register 0x0E
|
||||
if (soundblaster_version >= 3) {
|
||||
const int mixer_addr = soundblaster_base_port + 0x04;
|
||||
const int mixer_data = soundblaster_base_port + 0x05;
|
||||
outportb(mixer_addr, 0x0E); // select output/stereo register
|
||||
if (device->spec.channels == 2) {
|
||||
outportb(mixer_data, inportb(mixer_data) | 0x02); // set bit 1 = stereo
|
||||
} else {
|
||||
outportb(mixer_data, inportb(mixer_data) & ~0x02); // clear bit 1 = mono
|
||||
}
|
||||
}
|
||||
|
||||
// start 8-bit auto-initialize DMA mode
|
||||
// block_size is in bytes for 8-bit, and it's the half-buffer size minus 1
|
||||
const int block_size = (hidden->dma_buflen / 2) - 1;
|
||||
WriteSoundBlasterDSP(0x48); // set DSP block transfer size
|
||||
WriteSoundBlasterDSP((Uint8)(block_size & 0xFF));
|
||||
WriteSoundBlasterDSP((Uint8)(block_size >> 8));
|
||||
// NOTE: DSP 1.x does not support auto-init (0x1C). Those cards are extremely
|
||||
// rare and would need single-cycle transfers re-triggered from the ISR.
|
||||
// For now we use 0x1C anyway and hope for the best on DSP 1.x hardware.
|
||||
WriteSoundBlasterDSP(0x1C); // 8-bit auto-init DMA playback
|
||||
}
|
||||
|
||||
SDL_Log("SoundBlaster opened!");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return the staging buffer. The SDL audio pipeline writes the mixed audio
|
||||
// here; PlayDevice then copies it into the ring buffer.
|
||||
static Uint8 *DOSSOUNDBLASTER_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
(void)buffer_size; // unchanged, always one chunk
|
||||
return hidden->staging_buffer;
|
||||
}
|
||||
|
||||
// Commit the staging buffer into the ring buffer.
|
||||
// Called by SDL's audio thread after it has written a full chunk.
|
||||
static bool DOSSOUNDBLASTER_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
const int mask = hidden->ring_size - 1;
|
||||
const int pos = hidden->ring_write & mask;
|
||||
const int first = (pos + buffer_size <= hidden->ring_size) ? buffer_size : (hidden->ring_size - pos);
|
||||
|
||||
SDL_memcpy(hidden->ring_buffer + pos, buffer, first);
|
||||
if (first < buffer_size) {
|
||||
SDL_memcpy(hidden->ring_buffer, buffer + first, buffer_size - first);
|
||||
}
|
||||
|
||||
// Advance the write cursor. Interrupts are disabled around the store so
|
||||
// the ISR never sees a torn write (not strictly necessary on x86 for an
|
||||
// aligned int, but let's be safe).
|
||||
DOS_DisableInterrupts();
|
||||
hidden->ring_write += buffer_size;
|
||||
isr_ring_write = hidden->ring_write;
|
||||
DOS_EnableInterrupts();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void DOSSOUNDBLASTER_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
if (hidden) {
|
||||
// Disable PCM.
|
||||
if (hidden->is_16bit) {
|
||||
WriteSoundBlasterDSP(0xDA); // exit 16-bit auto-init DMA
|
||||
WriteSoundBlasterDSP(0xD3); // turn off the speaker
|
||||
} else {
|
||||
WriteSoundBlasterDSP(0xD0); // halt 8-bit DMA
|
||||
WriteSoundBlasterDSP(0xDA); // exit auto-init DMA
|
||||
WriteSoundBlasterDSP(0xD3); // turn off the speaker
|
||||
|
||||
// SB Pro: reset stereo bit in mixer register 0x0E
|
||||
if (soundblaster_version >= 3) {
|
||||
const int mixer_addr = soundblaster_base_port + 0x04;
|
||||
const int mixer_data = soundblaster_base_port + 0x05;
|
||||
outportb(mixer_addr, 0x0E);
|
||||
outportb(mixer_data, inportb(mixer_data) & ~0x02); // clear stereo bit
|
||||
}
|
||||
}
|
||||
|
||||
DOS_UnhookInterrupt(&hidden->interrupt_hook, true);
|
||||
|
||||
// disable DMA — mask the appropriate DMA channel.
|
||||
if (hidden->dma_buffer) {
|
||||
if (hidden->is_16bit) {
|
||||
outportb(0xD4, 0x04 | hidden->dma_channel); // mask high DMA channel (channels 5-7)
|
||||
} else {
|
||||
outportb(0x0A, 0x04 | hidden->dma_channel); // mask low DMA channel (channels 0-3)
|
||||
}
|
||||
DOS_FreeConventionalMemory(&hidden->dma_seginfo);
|
||||
}
|
||||
|
||||
// Free ring buffer resources.
|
||||
if (hidden->ring_buffer) {
|
||||
SDL_free(hidden->ring_buffer);
|
||||
}
|
||||
if (hidden->staging_buffer) {
|
||||
SDL_free(hidden->staging_buffer);
|
||||
}
|
||||
|
||||
// Clear ISR-visible statics.
|
||||
isr_ring_buffer = NULL;
|
||||
isr_ring_read = 0;
|
||||
isr_ring_write = 0;
|
||||
isr_ring_size = 0;
|
||||
isr_ring_mask = 0;
|
||||
isr_chunk_size = 0;
|
||||
isr_dma_buffer = NULL;
|
||||
isr_dma_halfdma = 0;
|
||||
isr_irq_ack_port = 0;
|
||||
|
||||
SDL_free(hidden);
|
||||
}
|
||||
}
|
||||
|
||||
static bool CheckForSoundBlaster(void)
|
||||
{
|
||||
ResetSoundBlasterDSP();
|
||||
|
||||
// wait for the DSP to say it's ready.
|
||||
bool ready = false;
|
||||
for (int i = 0; i < 300; i++) { // may take up to 100msecs to initialize. We'll give it 300.
|
||||
SDL_DelayPrecise(1000);
|
||||
if (ReadSoundBlasterReady()) {
|
||||
ready = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ready) {
|
||||
return SDL_SetError("No SoundBlaster detected on port 0x%X", soundblaster_base_port); // either no SoundBlaster or it's on a different base port.
|
||||
} else if (ReadSoundBlasterDSP() != 0xAA) {
|
||||
return SDL_SetError("Not a SoundBlaster at port 0x%X", soundblaster_base_port); // either it's not a SoundBlaster or there's a problem.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsSoundBlasterPresent(void)
|
||||
{
|
||||
const char *env = SDL_getenv("BLASTER");
|
||||
if (!env) {
|
||||
return SDL_SetError("No BLASTER environment variable to find Sound Blaster"); // definitely doesn't have a Sound Blaster (or they screwed up).
|
||||
}
|
||||
|
||||
char *copy = SDL_strdup(env);
|
||||
if (!copy) {
|
||||
return false; // oh well.
|
||||
}
|
||||
|
||||
char *str = copy;
|
||||
char *saveptr = NULL;
|
||||
|
||||
char *token;
|
||||
while ((token = SDL_strtok_r(str, " ", &saveptr)) != NULL) {
|
||||
str = NULL; // must be NULL for future calls to tokenize the same string.
|
||||
char *endp = NULL;
|
||||
const int base = (SDL_toupper(*token) == 'A') ? 16 : 10;
|
||||
const int num = (int)SDL_strtol(token + 1, &endp, base);
|
||||
if ((token[1] == 0) || (*endp != 0)) { // bogus num
|
||||
continue;
|
||||
} else if (num < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (SDL_toupper(*token)) {
|
||||
case 'A': // Base i/o port (in hex)
|
||||
soundblaster_base_port = num;
|
||||
break;
|
||||
|
||||
case 'I': // IRQ
|
||||
soundblaster_irq = num;
|
||||
break;
|
||||
|
||||
case 'D': // DMA channel
|
||||
soundblaster_dma_channel = num;
|
||||
break;
|
||||
|
||||
case 'H': // High DMA channel
|
||||
soundblaster_highdma_channel = num;
|
||||
break;
|
||||
|
||||
// don't care about these.
|
||||
// case 'M': // mixer chip base port
|
||||
// case 'P': // MPU-401 base port
|
||||
// case 'T': // type of device
|
||||
// case 'E': // EMU8000 base port: an AWE32 thing
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
SDL_free(copy);
|
||||
|
||||
if (soundblaster_base_port < 0 || soundblaster_irq < 0 || (soundblaster_dma_channel < 0 && soundblaster_highdma_channel < 0)) {
|
||||
return SDL_SetError("BLASTER environment variable is incomplete or incorrect");
|
||||
} else if (!CheckForSoundBlaster()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteSoundBlasterDSP(0xE1); // query DSP version
|
||||
soundblaster_version = (int)ReadSoundBlasterDSP();
|
||||
soundblaster_version_minor = (int)ReadSoundBlasterDSP();
|
||||
|
||||
SDL_Log("SB: BLASTER env='%s'", env);
|
||||
SDL_Log("SB: port=0x%X", soundblaster_base_port);
|
||||
SDL_Log("SB: irq=%d", soundblaster_irq);
|
||||
SDL_Log("SB: dma8=%d", soundblaster_dma_channel);
|
||||
SDL_Log("SB: dma16=%d", soundblaster_highdma_channel);
|
||||
SDL_Log("SB: version=%d.%d", soundblaster_version, soundblaster_version_minor);
|
||||
|
||||
soundblaster_is_sb16 = !FORCE_SB_8BIT && (soundblaster_version >= 4);
|
||||
soundblaster_silence_value = soundblaster_is_sb16 ? 0x00 : 0x80; // S16LE silence is 0x00, U8 silence is 0x80
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DOSSOUNDBLASTER_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!IsSoundBlasterPresent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
impl->OpenDevice = DOSSOUNDBLASTER_OpenDevice;
|
||||
impl->WaitDevice = DOSSOUNDBLASTER_WaitDevice;
|
||||
impl->GetDeviceBuf = DOSSOUNDBLASTER_GetDeviceBuf;
|
||||
impl->PlayDevice = DOSSOUNDBLASTER_PlayDevice;
|
||||
impl->CloseDevice = DOSSOUNDBLASTER_CloseDevice;
|
||||
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap DOSSOUNDBLASTER_bootstrap = {
|
||||
"soundblaster", "Sound Blaster", DOSSOUNDBLASTER_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_DOS_SOUNDBLASTER
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_dosaudio_sb_h_
|
||||
#define SDL_dosaudio_sb_h_
|
||||
|
||||
#include "../../core/dos/SDL_dos.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
Uint8 *dma_buffer;
|
||||
size_t dma_buflen;
|
||||
int dma_channel;
|
||||
bool is_16bit;
|
||||
_go32_dpmi_seginfo dma_seginfo;
|
||||
DOS_InterruptHook interrupt_hook;
|
||||
|
||||
// IRQ-driven ring buffer
|
||||
Uint8 *ring_buffer; // pre-allocated, memory-locked ring buffer
|
||||
volatile int ring_read; // read position (advanced by IRQ handler only)
|
||||
volatile int ring_write; // write position (advanced by audio thread only)
|
||||
int ring_size; // total ring buffer size (power-of-2, multiple of chunk_size)
|
||||
int chunk_size; // == device->buffer_size (one DMA half-buffer worth)
|
||||
Uint8 *staging_buffer; // audio thread writes here, then commits to ring
|
||||
};
|
||||
|
||||
#endif // SDL_dosaudio_sb_h_
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// !!! FIXME: clean out perror and fprintf calls in here.
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_OSS
|
||||
|
||||
#include <stdio.h> // For perror()
|
||||
#include <string.h> // For strerror()
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <sys/soundcard.h>
|
||||
|
||||
#include "../SDL_audiodev_c.h"
|
||||
#include "SDL_dspaudio.h"
|
||||
|
||||
static void DSP_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
SDL_EnumUnixAudioDevices(false, NULL);
|
||||
}
|
||||
|
||||
static void DSP_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->audio_fd >= 0) {
|
||||
close(device->hidden->audio_fd);
|
||||
}
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
}
|
||||
}
|
||||
|
||||
static bool DSP_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Make sure fragment size stays a power of 2, or OSS fails.
|
||||
// (I don't know which of these are actually legal values, though...)
|
||||
if (device->spec.channels > 8) {
|
||||
device->spec.channels = 8;
|
||||
} else if (device->spec.channels > 4) {
|
||||
device->spec.channels = 4;
|
||||
} else if (device->spec.channels > 2) {
|
||||
device->spec.channels = 2;
|
||||
}
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
|
||||
const int flags = ((device->recording) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT);
|
||||
device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC, 0);
|
||||
if (device->hidden->audio_fd < 0) {
|
||||
return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno));
|
||||
}
|
||||
|
||||
// Make the file descriptor use blocking i/o with fcntl()
|
||||
{
|
||||
const long ctlflags = fcntl(device->hidden->audio_fd, F_GETFL) & ~O_NONBLOCK;
|
||||
if (fcntl(device->hidden->audio_fd, F_SETFL, ctlflags) < 0) {
|
||||
return SDL_SetError("Couldn't set audio blocking mode");
|
||||
}
|
||||
}
|
||||
|
||||
// Get a list of supported hardware formats
|
||||
int value;
|
||||
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_GETFMTS, &value) < 0) {
|
||||
perror("SNDCTL_DSP_GETFMTS");
|
||||
return SDL_SetError("Couldn't get audio format list");
|
||||
}
|
||||
|
||||
// Try for a closest match on audio format
|
||||
int format = 0;
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
|
||||
#endif
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_U8:
|
||||
if (value & AFMT_U8) {
|
||||
format = AFMT_U8;
|
||||
}
|
||||
break;
|
||||
case SDL_AUDIO_S16LE:
|
||||
if (value & AFMT_S16_LE) {
|
||||
format = AFMT_S16_LE;
|
||||
}
|
||||
break;
|
||||
case SDL_AUDIO_S16BE:
|
||||
if (value & AFMT_S16_BE) {
|
||||
format = AFMT_S16_BE;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (format == 0) {
|
||||
return SDL_SetError("Couldn't find any hardware audio formats");
|
||||
}
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Set the audio format
|
||||
value = format;
|
||||
if ((ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFMT, &value) < 0) ||
|
||||
(value != format)) {
|
||||
perror("SNDCTL_DSP_SETFMT");
|
||||
return SDL_SetError("Couldn't set audio format");
|
||||
}
|
||||
|
||||
// Set the number of channels of output
|
||||
value = device->spec.channels;
|
||||
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_CHANNELS, &value) < 0) {
|
||||
perror("SNDCTL_DSP_CHANNELS");
|
||||
return SDL_SetError("Cannot set the number of channels");
|
||||
}
|
||||
device->spec.channels = value;
|
||||
|
||||
// Set the DSP frequency
|
||||
value = device->spec.freq;
|
||||
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SPEED, &value) < 0) {
|
||||
perror("SNDCTL_DSP_SPEED");
|
||||
return SDL_SetError("Couldn't set audio frequency");
|
||||
}
|
||||
device->spec.freq = value;
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
/* Determine the power of two of the fragment size
|
||||
Since apps don't control this in SDL3, and this driver only accepts 8, 16
|
||||
bit formats and 1, 2, 4, 8 channels, this should always be a power of 2 already. */
|
||||
SDL_assert(SDL_powerof2(device->buffer_size) == device->buffer_size);
|
||||
|
||||
int frag_spec = 0;
|
||||
while ((0x01U << frag_spec) < device->buffer_size) {
|
||||
frag_spec++;
|
||||
}
|
||||
frag_spec |= 0x00020000; // two fragments, for low latency
|
||||
|
||||
// Set the audio buffering parameters
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Requesting %d fragments of size %d\n",
|
||||
(frag_spec >> 16), 1 << (frag_spec & 0xFFFF));
|
||||
#endif
|
||||
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0) {
|
||||
perror("SNDCTL_DSP_SETFRAGMENT");
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
{
|
||||
audio_buf_info info;
|
||||
ioctl(device->hidden->audio_fd, SNDCTL_DSP_GETOSPACE, &info);
|
||||
fprintf(stderr, "fragments = %d\n", info.fragments);
|
||||
fprintf(stderr, "fragstotal = %d\n", info.fragstotal);
|
||||
fprintf(stderr, "fragsize = %d\n", info.fragsize);
|
||||
fprintf(stderr, "bytes = %d\n", info.bytes);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Allocate mixing buffer
|
||||
if (!device->recording) {
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
return true; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static bool DSP_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const unsigned long ioctlreq = device->recording ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE;
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
audio_buf_info info;
|
||||
const int rc = ioctl(h->audio_fd, ioctlreq, &info);
|
||||
if (rc < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
continue;
|
||||
}
|
||||
// Hmm, not much we can do - abort
|
||||
fprintf(stderr, "dsp WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno));
|
||||
return false;
|
||||
} else if (info.bytes < device->buffer_size) {
|
||||
SDL_Delay(10);
|
||||
} else {
|
||||
break; // ready to go!
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DSP_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
if (write(h->audio_fd, buffer, buflen) == -1) {
|
||||
perror("Audio write");
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Wrote %d bytes of audio data\n", h->mixlen);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *DSP_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int DSP_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
return (int)read(device->hidden->audio_fd, buffer, buflen);
|
||||
}
|
||||
|
||||
static void DSP_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
audio_buf_info info;
|
||||
if (ioctl(h->audio_fd, SNDCTL_DSP_GETISPACE, &info) == 0) {
|
||||
while (info.bytes > 0) {
|
||||
char buf[512];
|
||||
const size_t len = SDL_min(sizeof(buf), info.bytes);
|
||||
const ssize_t br = read(h->audio_fd, buf, len);
|
||||
if (br <= 0) {
|
||||
break;
|
||||
}
|
||||
info.bytes -= br;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool InitTimeDevicesExist = false;
|
||||
static bool look_for_devices_test(int fd)
|
||||
{
|
||||
InitTimeDevicesExist = true; // note that _something_ exists.
|
||||
// Don't add to the device list, we're just seeing if any devices exist.
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool DSP_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
InitTimeDevicesExist = false;
|
||||
SDL_EnumUnixAudioDevices(false, look_for_devices_test);
|
||||
if (!InitTimeDevicesExist) {
|
||||
SDL_SetError("dsp: No such audio device");
|
||||
return false; // maybe try a different backend.
|
||||
}
|
||||
|
||||
impl->DetectDevices = DSP_DetectDevices;
|
||||
impl->OpenDevice = DSP_OpenDevice;
|
||||
impl->WaitDevice = DSP_WaitDevice;
|
||||
impl->PlayDevice = DSP_PlayDevice;
|
||||
impl->GetDeviceBuf = DSP_GetDeviceBuf;
|
||||
impl->CloseDevice = DSP_CloseDevice;
|
||||
impl->WaitRecordingDevice = DSP_WaitDevice;
|
||||
impl->RecordDevice = DSP_RecordDevice;
|
||||
impl->FlushRecording = DSP_FlushRecording;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap DSP_bootstrap = {
|
||||
"dsp", "Open Sound System (/dev/dsp)", DSP_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_OSS
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// Output audio to nowhere...
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_dummyaudio.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_EMSCRIPTEN)
|
||||
#include <emscripten/emscripten.h>
|
||||
#endif
|
||||
|
||||
static bool DUMMYAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
SDL_Delay(device->hidden->io_delay);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!device->recording) {
|
||||
device->hidden->mixbuf = (Uint8 *) SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq);
|
||||
|
||||
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DUMMY_TIMESCALE);
|
||||
if (hint) {
|
||||
double scale = SDL_atof(hint);
|
||||
if (scale >= 0.0) {
|
||||
device->hidden->io_delay = (Uint32)SDL_round(device->hidden->io_delay * scale);
|
||||
}
|
||||
}
|
||||
|
||||
// on Emscripten, we just fire a repeating timer to consume audio.
|
||||
#if defined(SDL_PLATFORM_EMSCRIPTEN)
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var a = Module['SDL3'].dummy_audio;
|
||||
if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); }
|
||||
a.timers[$0] = setInterval(function() { dynCall('vi', $3, [$4]); }, ($1 / $2) * 1000);
|
||||
}, device->recording ? 1 : 0, device->sample_frames, device->spec.freq, device->recording ? SDL_RecordingAudioThreadIterate : SDL_PlaybackAudioThreadIterate, device);
|
||||
#endif
|
||||
|
||||
return true; // we're good; don't change reported device format.
|
||||
}
|
||||
|
||||
static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
// on Emscripten, we just fire a repeating timer to consume audio.
|
||||
#if defined(SDL_PLATFORM_EMSCRIPTEN)
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var a = Module['SDL3'].dummy_audio;
|
||||
if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); }
|
||||
a.timers[$0] = undefined;
|
||||
}, device->recording ? 1 : 0);
|
||||
#endif
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static Uint8 *DUMMYAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int DUMMYAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
// always return a full buffer of silence.
|
||||
SDL_memset(buffer, device->silence_value, buflen);
|
||||
return buflen;
|
||||
}
|
||||
|
||||
static bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = DUMMYAUDIO_OpenDevice;
|
||||
impl->CloseDevice = DUMMYAUDIO_CloseDevice;
|
||||
impl->WaitDevice = DUMMYAUDIO_WaitDevice;
|
||||
impl->GetDeviceBuf = DUMMYAUDIO_GetDeviceBuf;
|
||||
impl->WaitRecordingDevice = DUMMYAUDIO_WaitDevice;
|
||||
impl->RecordDevice = DUMMYAUDIO_RecordDevice;
|
||||
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
impl->OnlyHasDefaultRecordingDevice = true;
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
// on Emscripten, we just fire a repeating timer to consume audio.
|
||||
#if defined(SDL_PLATFORM_EMSCRIPTEN)
|
||||
MAIN_THREAD_EM_ASM({
|
||||
Module['SDL3'].dummy_audio = {};
|
||||
Module['SDL3'].dummy_audio.timers = [];
|
||||
Module['SDL3'].dummy_audio.timers[0] = undefined;
|
||||
Module['SDL3'].dummy_audio.timers[1] = undefined;
|
||||
});
|
||||
impl->ProvidesOwnCallbackThread = true;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap DUMMYAUDIO_bootstrap = {
|
||||
"dummy", "SDL dummy audio driver", DUMMYAUDIO_Init, true, false
|
||||
};
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_dummyaudio_h_
|
||||
#define SDL_dummyaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
Uint8 *mixbuf; // The file descriptor for the audio device
|
||||
Uint32 io_delay; // milliseconds to sleep in WaitDevice.
|
||||
};
|
||||
|
||||
#endif // SDL_dummyaudio_h_
|
||||
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_emscriptenaudio.h"
|
||||
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
// just turn off clang-format for this whole file, this INDENT_OFF stuff on
|
||||
// each EM_ASM section is ugly.
|
||||
/* *INDENT-OFF* */ // clang-format off
|
||||
|
||||
static Uint8 *EMSCRIPTENAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static bool EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
const int framelen = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
MAIN_THREAD_EM_ASM({
|
||||
/* Convert incoming buf pointer to a HEAPF32 offset. */
|
||||
var SDL3 = Module['SDL3'];
|
||||
var buf = SDL3.CPtrToHeap32Index($0);
|
||||
var numChannels = SDL3.audio_playback.currentPlaybackBuffer['numberOfChannels'];
|
||||
for (var c = 0; c < numChannels; ++c) {
|
||||
var channelData = SDL3.audio_playback.currentPlaybackBuffer['getChannelData'](c);
|
||||
if (channelData.length != $1) {
|
||||
throw 'Web Audio playback buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
|
||||
}
|
||||
|
||||
for (var j = 0; j < $1; ++j) {
|
||||
channelData[j] = HEAPF32[buf + (j * numChannels + c)];
|
||||
}
|
||||
}
|
||||
}, buffer, buffer_size / framelen);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void EMSCRIPTENAUDIO_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
// Do nothing, the new data will just be dropped.
|
||||
}
|
||||
|
||||
static int EMSCRIPTENAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
var numChannels = SDL3.audio_recording.currentRecordingBuffer.numberOfChannels;
|
||||
for (var c = 0; c < numChannels; ++c) {
|
||||
var channelData = SDL3.audio_recording.currentRecordingBuffer.getChannelData(c);
|
||||
if (channelData.length != $1) {
|
||||
throw 'Web Audio recording buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
|
||||
}
|
||||
|
||||
if (numChannels == 1) { // fastpath this a little for the common (mono) case.
|
||||
for (var j = 0; j < $1; ++j) {
|
||||
setValue($0 + (j * 4), channelData[j], 'float');
|
||||
}
|
||||
} else {
|
||||
for (var j = 0; j < $1; ++j) {
|
||||
setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, buffer, (buflen / sizeof(float)) / device->spec.channels);
|
||||
|
||||
return buflen;
|
||||
}
|
||||
|
||||
static void EMSCRIPTENAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (!device->hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
if ($0) {
|
||||
if (SDL3.audio_recording.silenceTimer !== undefined) {
|
||||
clearInterval(SDL3.audio_recording.silenceTimer);
|
||||
}
|
||||
if (SDL3.audio_recording.stream !== undefined) {
|
||||
var tracks = SDL3.audio_recording.stream.getAudioTracks();
|
||||
for (var i = 0; i < tracks.length; i++) {
|
||||
SDL3.audio_recording.stream.removeTrack(tracks[i]);
|
||||
}
|
||||
}
|
||||
if (SDL3.audio_recording.scriptProcessorNode !== undefined) {
|
||||
SDL3.audio_recording.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {};
|
||||
SDL3.audio_recording.scriptProcessorNode.disconnect();
|
||||
}
|
||||
if (SDL3.audio_recording.mediaStreamNode !== undefined) {
|
||||
SDL3.audio_recording.mediaStreamNode.disconnect();
|
||||
}
|
||||
SDL3.audio_recording = undefined;
|
||||
} else {
|
||||
if (SDL3.audio_playback.scriptProcessorNode != undefined) {
|
||||
SDL3.audio_playback.scriptProcessorNode.disconnect();
|
||||
}
|
||||
if (SDL3.audio_playback.silenceTimer !== undefined) {
|
||||
clearInterval(SDL3.audio_playback.silenceTimer);
|
||||
}
|
||||
SDL3.audio_playback = undefined;
|
||||
}
|
||||
if ((SDL3.audioContext !== undefined) && (SDL3.audio_playback === undefined) && (SDL3.audio_recording === undefined)) {
|
||||
SDL3.audioContext.close();
|
||||
SDL3.audioContext = undefined;
|
||||
}
|
||||
}, device->recording);
|
||||
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
|
||||
SDL_AudioThreadFinalize(device);
|
||||
}
|
||||
|
||||
EM_JS_DEPS(sdlaudio, "$autoResumeAudioContext,$dynCall");
|
||||
|
||||
static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// based on parts of library_sdl.js
|
||||
|
||||
// create context
|
||||
const bool result = MAIN_THREAD_EM_ASM_INT({
|
||||
var SDL3 = Module['SDL3'];
|
||||
if (typeof(SDL3.audio_playback) === 'undefined') {
|
||||
SDL3.audio_playback = {};
|
||||
}
|
||||
if (typeof(SDL3.audio_recording) === 'undefined') {
|
||||
SDL3.audio_recording = {};
|
||||
}
|
||||
|
||||
if (!SDL3.audioContext) {
|
||||
if (typeof(AudioContext) !== 'undefined') {
|
||||
SDL3.audioContext = new AudioContext();
|
||||
} else if (typeof(webkitAudioContext) !== 'undefined') {
|
||||
SDL3.audioContext = new webkitAudioContext();
|
||||
}
|
||||
if (SDL3.audioContext) {
|
||||
if ((typeof navigator.userActivation) === 'undefined') {
|
||||
autoResumeAudioContext(SDL3.audioContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (SDL3.audioContext !== undefined);
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return SDL_SetError("Web Audio API is not available!");
|
||||
}
|
||||
|
||||
device->spec.format = SDL_AUDIO_F32; // web audio only supports floats
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// limit to native freq
|
||||
device->spec.freq = MAIN_THREAD_EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; });
|
||||
device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq) * 2; // double the buffer size, some browsers need more, and we'll just have to live with the latency.
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
if (!device->recording) {
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
if (device->recording) {
|
||||
/* The idea is to take the recording media stream, hook it up to an
|
||||
audio graph where we can pass it through a ScriptProcessorNode
|
||||
to access the raw PCM samples and push them to the SDL app's
|
||||
callback. From there, we "process" the audio data into silence
|
||||
and forget about it.
|
||||
|
||||
This should, strictly speaking, use MediaRecorder for recording, but
|
||||
this API is cleaner to use and better supported, and fires a
|
||||
callback whenever there's enough data to fire down into the app.
|
||||
The downside is that we are spending CPU time silencing a buffer
|
||||
that the audiocontext uselessly mixes into any playback. On the
|
||||
upside, both of those things are not only run in native code in
|
||||
the browser, they're probably SIMD code, too. MediaRecorder
|
||||
feels like it's a pretty inefficient tapdance in similar ways,
|
||||
to be honest. */
|
||||
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
var have_microphone = function(stream) {
|
||||
//console.log('SDL audio recording: we have a microphone! Replacing silence callback.');
|
||||
if (SDL3.audio_recording.silenceTimer !== undefined) {
|
||||
clearInterval(SDL3.audio_recording.silenceTimer);
|
||||
SDL3.audio_recording.silenceTimer = undefined;
|
||||
SDL3.audio_recording.silenceBuffer = undefined
|
||||
}
|
||||
SDL3.audio_recording.mediaStreamNode = SDL3.audioContext.createMediaStreamSource(stream);
|
||||
SDL3.audio_recording.scriptProcessorNode = SDL3.audioContext.createScriptProcessor($1, $0, 1);
|
||||
SDL3.audio_recording.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {
|
||||
if ((SDL3 === undefined) || (SDL3.audio_recording === undefined)) { return; }
|
||||
audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0);
|
||||
SDL3.audio_recording.currentRecordingBuffer = audioProcessingEvent.inputBuffer;
|
||||
dynCall('ip', $2, [$3]);
|
||||
};
|
||||
SDL3.audio_recording.mediaStreamNode.connect(SDL3.audio_recording.scriptProcessorNode);
|
||||
SDL3.audio_recording.scriptProcessorNode.connect(SDL3.audioContext.destination);
|
||||
SDL3.audio_recording.stream = stream;
|
||||
};
|
||||
|
||||
var no_microphone = function(error) {
|
||||
//console.log('SDL audio recording: we DO NOT have a microphone! (' + error.name + ')...leaving silence callback running.');
|
||||
};
|
||||
|
||||
// we write silence to the audio callback until the microphone is available (user approves use, etc).
|
||||
SDL3.audio_recording.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
|
||||
SDL3.audio_recording.silenceBuffer.getChannelData(0).fill(0.0);
|
||||
var silence_callback = function() {
|
||||
SDL3.audio_recording.currentRecordingBuffer = SDL3.audio_recording.silenceBuffer;
|
||||
dynCall('ip', $2, [$3]);
|
||||
};
|
||||
|
||||
SDL3.audio_recording.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
|
||||
|
||||
if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) {
|
||||
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone);
|
||||
} else if (navigator.webkitGetUserMedia !== undefined) {
|
||||
navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone);
|
||||
}
|
||||
}, device->spec.channels, device->sample_frames, SDL_RecordingAudioThreadIterate, device);
|
||||
} else {
|
||||
// setup a ScriptProcessorNode
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
SDL3.audio_playback.scriptProcessorNode = SDL3.audioContext['createScriptProcessor']($1, 0, $0);
|
||||
SDL3.audio_playback.scriptProcessorNode['onaudioprocess'] = function (e) {
|
||||
if ((SDL3 === undefined) || (SDL3.audio_playback === undefined)) { return; }
|
||||
// if we're actually running the node, we don't need the fake callback anymore, so kill it.
|
||||
if (SDL3.audio_playback.silenceTimer !== undefined) {
|
||||
clearInterval(SDL3.audio_playback.silenceTimer);
|
||||
SDL3.audio_playback.silenceTimer = undefined;
|
||||
SDL3.audio_playback.silenceBuffer = undefined;
|
||||
}
|
||||
SDL3.audio_playback.currentPlaybackBuffer = e['outputBuffer'];
|
||||
dynCall('ip', $2, [$3]);
|
||||
};
|
||||
|
||||
SDL3.audio_playback.scriptProcessorNode['connect'](SDL3.audioContext['destination']);
|
||||
|
||||
if (SDL3.audioContext.state === 'suspended') { // uhoh, autoplay is blocked.
|
||||
SDL3.audio_playback.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
|
||||
SDL3.audio_playback.silenceBuffer.getChannelData(0).fill(0.0);
|
||||
var silence_callback = function() {
|
||||
if ((typeof navigator.userActivation) !== 'undefined') {
|
||||
if (navigator.userActivation.hasBeenActive) {
|
||||
SDL3.audioContext.resume();
|
||||
}
|
||||
}
|
||||
|
||||
// the buffer that gets filled here just gets ignored, so the app can make progress
|
||||
// and/or avoid flooding audio queues until we can actually play audio.
|
||||
SDL3.audio_playback.currentPlaybackBuffer = SDL3.audio_playback.silenceBuffer;
|
||||
dynCall('ip', $2, [$3]);
|
||||
SDL3.audio_playback.currentPlaybackBuffer = undefined;
|
||||
};
|
||||
|
||||
SDL3.audio_playback.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
|
||||
}
|
||||
}, device->spec.channels, device->sample_frames, SDL_PlaybackAudioThreadIterate, device);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
bool available, recording_available;
|
||||
|
||||
impl->OpenDevice = EMSCRIPTENAUDIO_OpenDevice;
|
||||
impl->CloseDevice = EMSCRIPTENAUDIO_CloseDevice;
|
||||
impl->GetDeviceBuf = EMSCRIPTENAUDIO_GetDeviceBuf;
|
||||
impl->PlayDevice = EMSCRIPTENAUDIO_PlayDevice;
|
||||
impl->FlushRecording = EMSCRIPTENAUDIO_FlushRecording;
|
||||
impl->RecordDevice = EMSCRIPTENAUDIO_RecordDevice;
|
||||
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
|
||||
// technically, this is just runs in idle time in the main thread, but it's close enough to a "thread" for our purposes.
|
||||
impl->ProvidesOwnCallbackThread = true;
|
||||
|
||||
// check availability
|
||||
available = MAIN_THREAD_EM_ASM_INT({
|
||||
if (typeof(AudioContext) !== 'undefined') {
|
||||
return true;
|
||||
} else if (typeof(webkitAudioContext) !== 'undefined') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!available) {
|
||||
SDL_SetError("No audio context available");
|
||||
}
|
||||
|
||||
recording_available = available && MAIN_THREAD_EM_ASM_INT({
|
||||
if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) {
|
||||
return true;
|
||||
} else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
impl->HasRecordingSupport = recording_available;
|
||||
impl->OnlyHasDefaultRecordingDevice = recording_available;
|
||||
|
||||
return available;
|
||||
}
|
||||
|
||||
AudioBootStrap EMSCRIPTENAUDIO_bootstrap = {
|
||||
"emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, false, false
|
||||
};
|
||||
|
||||
/* *INDENT-ON* */ // clang-format on
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_EMSCRIPTEN
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_HAIKU
|
||||
|
||||
// Allow access to the audio stream on Haiku
|
||||
|
||||
#include <SoundPlayer.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "../../core/haiku/SDL_BeApp.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_haikuaudio.h"
|
||||
|
||||
}
|
||||
|
||||
static Uint8 *HAIKUAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
SDL_assert(device->hidden->current_buffer != NULL);
|
||||
SDL_assert(device->hidden->current_buffer_len > 0);
|
||||
*buffer_size = device->hidden->current_buffer_len;
|
||||
return device->hidden->current_buffer;
|
||||
}
|
||||
|
||||
static bool HAIKUAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
// We already wrote our output right into the BSoundPlayer's callback's stream. Just clean up our stuff.
|
||||
SDL_assert(device->hidden->current_buffer != NULL);
|
||||
SDL_assert(device->hidden->current_buffer_len > 0);
|
||||
device->hidden->current_buffer = NULL;
|
||||
device->hidden->current_buffer_len = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The Haiku callback for handling the audio buffer
|
||||
static void FillSound(void *data, void *stream, size_t len, const media_raw_audio_format & format)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *)data;
|
||||
SDL_assert(device->hidden->current_buffer == NULL);
|
||||
SDL_assert(device->hidden->current_buffer_len == 0);
|
||||
device->hidden->current_buffer = (Uint8 *) stream;
|
||||
device->hidden->current_buffer_len = (int) len;
|
||||
SDL_PlaybackAudioThreadIterate(device);
|
||||
}
|
||||
|
||||
static void HAIKUAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->audio_obj) {
|
||||
device->hidden->audio_obj->Stop();
|
||||
delete device->hidden->audio_obj;
|
||||
}
|
||||
delete device->hidden;
|
||||
device->hidden = NULL;
|
||||
SDL_AudioThreadFinalize(device);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const int sig_list[] = {
|
||||
SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGALRM, SIGTERM, SIGWINCH, 0
|
||||
};
|
||||
|
||||
static inline void MaskSignals(sigset_t * omask)
|
||||
{
|
||||
sigset_t mask;
|
||||
int i;
|
||||
|
||||
sigemptyset(&mask);
|
||||
for (i = 0; sig_list[i]; ++i) {
|
||||
sigaddset(&mask, sig_list[i]);
|
||||
}
|
||||
sigprocmask(SIG_BLOCK, &mask, omask);
|
||||
}
|
||||
|
||||
static inline void UnmaskSignals(sigset_t * omask)
|
||||
{
|
||||
sigprocmask(SIG_SETMASK, omask, NULL);
|
||||
}
|
||||
|
||||
|
||||
static bool HAIKUAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = new SDL_PrivateAudioData;
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
SDL_zerop(device->hidden);
|
||||
|
||||
// Parse the audio format and fill the Be raw audio format
|
||||
media_raw_audio_format format;
|
||||
SDL_zero(format);
|
||||
format.byte_order = B_MEDIA_LITTLE_ENDIAN;
|
||||
format.frame_rate = (float) device->spec.freq;
|
||||
format.channel_count = device->spec.channels; // !!! FIXME: support > 2?
|
||||
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_S8:
|
||||
format.format = media_raw_audio_format::B_AUDIO_CHAR;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_U8:
|
||||
format.format = media_raw_audio_format::B_AUDIO_UCHAR;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16LE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_SHORT;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16BE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_SHORT;
|
||||
format.byte_order = B_MEDIA_BIG_ENDIAN;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32LE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_INT;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32BE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_INT;
|
||||
format.byte_order = B_MEDIA_BIG_ENDIAN;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_F32LE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_FLOAT;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_F32BE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_FLOAT;
|
||||
format.byte_order = B_MEDIA_BIG_ENDIAN;
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!test_format) { // shouldn't happen, but just in case...
|
||||
return SDL_SetError("HAIKU: Unsupported audio format");
|
||||
}
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
format.buffer_size = device->buffer_size;
|
||||
|
||||
// Subscribe to the audio stream (creates a new thread)
|
||||
sigset_t omask;
|
||||
MaskSignals(&omask);
|
||||
device->hidden->audio_obj = new BSoundPlayer(&format, "SDL Audio",
|
||||
FillSound, NULL, device);
|
||||
UnmaskSignals(&omask);
|
||||
|
||||
if (device->hidden->audio_obj->Start() == B_NO_ERROR) {
|
||||
device->hidden->audio_obj->SetHasData(true);
|
||||
} else {
|
||||
return SDL_SetError("Unable to start Haiku audio");
|
||||
}
|
||||
|
||||
return true; // We're running!
|
||||
}
|
||||
|
||||
static void HAIKUAUDIO_Deinitialize(void)
|
||||
{
|
||||
SDL_QuitBeApp();
|
||||
}
|
||||
|
||||
static bool HAIKUAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!SDL_InitBeApp()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the function pointers
|
||||
impl->OpenDevice = HAIKUAUDIO_OpenDevice;
|
||||
impl->GetDeviceBuf = HAIKUAUDIO_GetDeviceBuf;
|
||||
impl->PlayDevice = HAIKUAUDIO_PlayDevice;
|
||||
impl->CloseDevice = HAIKUAUDIO_CloseDevice;
|
||||
impl->Deinitialize = HAIKUAUDIO_Deinitialize;
|
||||
impl->ProvidesOwnCallbackThread = true;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
extern "C" { extern AudioBootStrap HAIKUAUDIO_bootstrap; }
|
||||
|
||||
AudioBootStrap HAIKUAUDIO_bootstrap = {
|
||||
"haiku", "Haiku BSoundPlayer", HAIKUAUDIO_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_HAIKU
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_
|
||||
+442
@@ -0,0 +1,442 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_JACK
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_jackaudio.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
|
||||
static jack_client_t *(*JACK_jack_client_open)(const char *, jack_options_t, jack_status_t *, ...);
|
||||
static int (*JACK_jack_client_close)(jack_client_t *);
|
||||
static void (*JACK_jack_on_shutdown)(jack_client_t *, JackShutdownCallback, void *);
|
||||
static int (*JACK_jack_activate)(jack_client_t *);
|
||||
static int (*JACK_jack_deactivate)(jack_client_t *);
|
||||
static void *(*JACK_jack_port_get_buffer)(jack_port_t *, jack_nframes_t);
|
||||
static int (*JACK_jack_port_unregister)(jack_client_t *, jack_port_t *);
|
||||
static void (*JACK_jack_free)(void *);
|
||||
static const char **(*JACK_jack_get_ports)(jack_client_t *, const char *, const char *, unsigned long);
|
||||
static jack_nframes_t (*JACK_jack_get_sample_rate)(jack_client_t *);
|
||||
static jack_nframes_t (*JACK_jack_get_buffer_size)(jack_client_t *);
|
||||
static jack_port_t *(*JACK_jack_port_register)(jack_client_t *, const char *, const char *, unsigned long, unsigned long);
|
||||
static jack_port_t *(*JACK_jack_port_by_name)(jack_client_t *, const char *);
|
||||
static const char *(*JACK_jack_port_name)(const jack_port_t *);
|
||||
static const char *(*JACK_jack_port_type)(const jack_port_t *);
|
||||
static int (*JACK_jack_connect)(jack_client_t *, const char *, const char *);
|
||||
static int (*JACK_jack_set_process_callback)(jack_client_t *, JackProcessCallback, void *);
|
||||
static int (*JACK_jack_set_sample_rate_callback)(jack_client_t *, JackSampleRateCallback, void *);
|
||||
static int (*JACK_jack_set_buffer_size_callback)(jack_client_t *, JackBufferSizeCallback, void *);
|
||||
|
||||
static bool load_jack_syms(void);
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC
|
||||
|
||||
SDL_ELF_NOTE_DLOPEN(
|
||||
"audio-libjack",
|
||||
"Support for audio through libjack",
|
||||
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
|
||||
SDL_AUDIO_DRIVER_JACK_DYNAMIC
|
||||
)
|
||||
|
||||
static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC;
|
||||
static SDL_SharedObject *jack_handle = NULL;
|
||||
|
||||
// !!! FIXME: this is copy/pasted in several places now
|
||||
static bool load_jack_sym(const char *fn, void **addr)
|
||||
{
|
||||
*addr = SDL_LoadFunction(jack_handle, fn);
|
||||
if (!*addr) {
|
||||
// Don't call SDL_SetError(): SDL_LoadFunction already did.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// cast funcs to char* first, to please GCC's strict aliasing rules.
|
||||
#define SDL_JACK_SYM(x) \
|
||||
if (!load_jack_sym(#x, (void **)(char *)&JACK_##x)) \
|
||||
return false
|
||||
|
||||
static void UnloadJackLibrary(void)
|
||||
{
|
||||
if (jack_handle) {
|
||||
SDL_UnloadObject(jack_handle);
|
||||
jack_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool LoadJackLibrary(void)
|
||||
{
|
||||
bool result = true;
|
||||
if (!jack_handle) {
|
||||
jack_handle = SDL_LoadObject(jack_library);
|
||||
if (!jack_handle) {
|
||||
result = false;
|
||||
// Don't call SDL_SetError(): SDL_LoadObject already did.
|
||||
} else {
|
||||
result = load_jack_syms();
|
||||
if (!result) {
|
||||
UnloadJackLibrary();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define SDL_JACK_SYM(x) JACK_##x = x
|
||||
|
||||
static void UnloadJackLibrary(void)
|
||||
{
|
||||
}
|
||||
|
||||
static bool LoadJackLibrary(void)
|
||||
{
|
||||
load_jack_syms();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_JACK_DYNAMIC
|
||||
|
||||
static bool load_jack_syms(void)
|
||||
{
|
||||
SDL_JACK_SYM(jack_client_open);
|
||||
SDL_JACK_SYM(jack_client_close);
|
||||
SDL_JACK_SYM(jack_on_shutdown);
|
||||
SDL_JACK_SYM(jack_activate);
|
||||
SDL_JACK_SYM(jack_deactivate);
|
||||
SDL_JACK_SYM(jack_port_get_buffer);
|
||||
SDL_JACK_SYM(jack_port_unregister);
|
||||
SDL_JACK_SYM(jack_free);
|
||||
SDL_JACK_SYM(jack_get_ports);
|
||||
SDL_JACK_SYM(jack_get_sample_rate);
|
||||
SDL_JACK_SYM(jack_get_buffer_size);
|
||||
SDL_JACK_SYM(jack_port_register);
|
||||
SDL_JACK_SYM(jack_port_by_name);
|
||||
SDL_JACK_SYM(jack_port_name);
|
||||
SDL_JACK_SYM(jack_port_type);
|
||||
SDL_JACK_SYM(jack_connect);
|
||||
SDL_JACK_SYM(jack_set_process_callback);
|
||||
SDL_JACK_SYM(jack_set_sample_rate_callback);
|
||||
SDL_JACK_SYM(jack_set_buffer_size_callback);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void jackShutdownCallback(void *arg) // JACK went away; device is lost.
|
||||
{
|
||||
SDL_AudioDeviceDisconnected((SDL_AudioDevice *)arg);
|
||||
}
|
||||
|
||||
static int jackSampleRateCallback(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
//SDL_Log("JACK Sample Rate Callback! %d", (int) nframes);
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) arg;
|
||||
SDL_AudioSpec newspec;
|
||||
SDL_copyp(&newspec, &device->spec);
|
||||
newspec.freq = (int) nframes;
|
||||
if (!SDL_AudioDeviceFormatChanged(device, &newspec, device->sample_frames)) {
|
||||
SDL_AudioDeviceDisconnected(device);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jackBufferSizeCallback(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
//SDL_Log("JACK Buffer Size Callback! %d", (int) nframes);
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) arg;
|
||||
SDL_AudioSpec newspec;
|
||||
SDL_copyp(&newspec, &device->spec);
|
||||
if (!SDL_AudioDeviceFormatChanged(device, &newspec, (int) nframes)) {
|
||||
SDL_AudioDeviceDisconnected(device);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
|
||||
SDL_PlaybackAudioThreadIterate((SDL_AudioDevice *)arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool JACK_PlayDevice(SDL_AudioDevice *device, const Uint8 *ui8buffer, int buflen)
|
||||
{
|
||||
const float *buffer = (float *) ui8buffer;
|
||||
jack_port_t **ports = device->hidden->sdlports;
|
||||
const int total_channels = device->spec.channels;
|
||||
const int total_frames = device->sample_frames;
|
||||
const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames;
|
||||
|
||||
for (int channelsi = 0; channelsi < total_channels; channelsi++) {
|
||||
float *dst = (float *)JACK_jack_port_get_buffer(ports[channelsi], nframes);
|
||||
if (dst) {
|
||||
const float *src = buffer + channelsi;
|
||||
for (int framesi = 0; framesi < total_frames; framesi++) {
|
||||
*(dst++) = *src;
|
||||
src += total_channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return (Uint8 *)device->hidden->iobuffer;
|
||||
}
|
||||
|
||||
static int jackProcessRecordingCallback(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
|
||||
SDL_RecordingAudioThreadIterate((SDL_AudioDevice *)arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int JACK_RecordDevice(SDL_AudioDevice *device, void *vbuffer, int buflen)
|
||||
{
|
||||
float *buffer = (float *) vbuffer;
|
||||
jack_port_t **ports = device->hidden->sdlports;
|
||||
const int total_channels = device->spec.channels;
|
||||
const int total_frames = device->sample_frames;
|
||||
const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames;
|
||||
|
||||
for (int channelsi = 0; channelsi < total_channels; channelsi++) {
|
||||
const float *src = (const float *)JACK_jack_port_get_buffer(ports[channelsi], nframes);
|
||||
if (src) {
|
||||
float *dst = buffer + channelsi;
|
||||
for (int framesi = 0; framesi < total_frames; framesi++) {
|
||||
*dst = *(src++);
|
||||
dst += total_channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buflen;
|
||||
}
|
||||
|
||||
static void JACK_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
// do nothing, the data will just be replaced next callback.
|
||||
}
|
||||
|
||||
static void JACK_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->client) {
|
||||
JACK_jack_deactivate(device->hidden->client);
|
||||
|
||||
if (device->hidden->sdlports) {
|
||||
const int channels = device->spec.channels;
|
||||
int i;
|
||||
for (i = 0; i < channels; i++) {
|
||||
JACK_jack_port_unregister(device->hidden->client, device->hidden->sdlports[i]);
|
||||
}
|
||||
SDL_free(device->hidden->sdlports);
|
||||
}
|
||||
|
||||
JACK_jack_client_close(device->hidden->client);
|
||||
}
|
||||
|
||||
SDL_free(device->hidden->iobuffer);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
|
||||
SDL_AudioThreadFinalize(device);
|
||||
}
|
||||
}
|
||||
|
||||
// !!! FIXME: unify this (PulseAudio has a getAppName, Pipewire has a thing, etc)
|
||||
static const char *GetJackAppName(void)
|
||||
{
|
||||
return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
|
||||
}
|
||||
|
||||
static bool JACK_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
/* Note that JACK uses "output" for recording devices (they output audio
|
||||
data to us) and "input" for playback (we input audio data to them).
|
||||
Likewise, SDL's playback port will be "output" (we write data out)
|
||||
and recording will be "input" (we read data in). */
|
||||
const bool recording = device->recording;
|
||||
const unsigned long sysportflags = recording ? JackPortIsOutput : JackPortIsInput;
|
||||
const unsigned long sdlportflags = recording ? JackPortIsInput : JackPortIsOutput;
|
||||
const JackProcessCallback callback = recording ? jackProcessRecordingCallback : jackProcessPlaybackCallback;
|
||||
const char *sdlportstr = recording ? "input" : "output";
|
||||
const char **devports = NULL;
|
||||
int *audio_ports;
|
||||
jack_client_t *client = NULL;
|
||||
jack_status_t status;
|
||||
int channels = 0;
|
||||
int ports = 0;
|
||||
int i;
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
client = JACK_jack_client_open(GetJackAppName(), JackNoStartServer, &status, NULL);
|
||||
device->hidden->client = client;
|
||||
if (!client) {
|
||||
return SDL_SetError("Can't open JACK client");
|
||||
}
|
||||
|
||||
devports = JACK_jack_get_ports(client, NULL, NULL, JackPortIsPhysical | sysportflags);
|
||||
if (!devports || !devports[0]) {
|
||||
return SDL_SetError("No physical JACK ports available");
|
||||
}
|
||||
|
||||
while (devports[++ports]) {
|
||||
// spin to count devports
|
||||
}
|
||||
|
||||
// Filter out non-audio ports
|
||||
audio_ports = SDL_calloc(ports, sizeof(*audio_ports));
|
||||
for (i = 0; i < ports; i++) {
|
||||
const jack_port_t *dport = JACK_jack_port_by_name(client, devports[i]);
|
||||
const char *type = JACK_jack_port_type(dport);
|
||||
const int len = SDL_strlen(type);
|
||||
// See if type ends with "audio"
|
||||
if (len >= 5 && !SDL_memcmp(type + len - 5, "audio", 5)) {
|
||||
audio_ports[channels++] = i;
|
||||
}
|
||||
}
|
||||
if (channels == 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("No physical JACK ports available");
|
||||
}
|
||||
|
||||
// Jack pretty much demands what it wants.
|
||||
device->spec.format = SDL_AUDIO_F32;
|
||||
device->spec.freq = JACK_jack_get_sample_rate(client);
|
||||
device->spec.channels = channels;
|
||||
device->sample_frames = JACK_jack_get_buffer_size(client);
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
if (!recording) {
|
||||
device->hidden->iobuffer = (float *)SDL_calloc(1, device->buffer_size);
|
||||
if (!device->hidden->iobuffer) {
|
||||
SDL_free(audio_ports);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Build SDL's ports, which we will connect to the device ports.
|
||||
device->hidden->sdlports = (jack_port_t **)SDL_calloc(channels, sizeof(jack_port_t *));
|
||||
if (!device->hidden->sdlports) {
|
||||
SDL_free(audio_ports);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < channels; i++) {
|
||||
char portname[32];
|
||||
(void)SDL_snprintf(portname, sizeof(portname), "sdl_jack_%s_%d", sdlportstr, i);
|
||||
device->hidden->sdlports[i] = JACK_jack_port_register(client, portname, JACK_DEFAULT_AUDIO_TYPE, sdlportflags, 0);
|
||||
if (device->hidden->sdlports[i] == NULL) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("jack_port_register failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (JACK_jack_set_buffer_size_callback(client, jackBufferSizeCallback, device) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("JACK: Couldn't set buffer size callback");
|
||||
} else if (JACK_jack_set_sample_rate_callback(client, jackSampleRateCallback, device) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("JACK: Couldn't set sample rate callback");
|
||||
} else if (JACK_jack_set_process_callback(client, callback, device) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("JACK: Couldn't set process callback");
|
||||
}
|
||||
|
||||
JACK_jack_on_shutdown(client, jackShutdownCallback, device);
|
||||
|
||||
if (JACK_jack_activate(client) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("Failed to activate JACK client");
|
||||
}
|
||||
|
||||
// once activated, we can connect all the ports.
|
||||
for (i = 0; i < channels; i++) {
|
||||
const char *sdlport = JACK_jack_port_name(device->hidden->sdlports[i]);
|
||||
const char *srcport = recording ? devports[audio_ports[i]] : sdlport;
|
||||
const char *dstport = recording ? sdlport : devports[audio_ports[i]];
|
||||
if (JACK_jack_connect(client, srcport, dstport) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("Couldn't connect JACK ports: %s => %s", srcport, dstport);
|
||||
}
|
||||
}
|
||||
|
||||
// don't need these anymore.
|
||||
JACK_jack_free(devports);
|
||||
SDL_free(audio_ports);
|
||||
|
||||
// We're ready to rock and roll. :-)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void JACK_Deinitialize(void)
|
||||
{
|
||||
UnloadJackLibrary();
|
||||
}
|
||||
|
||||
static bool JACK_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!LoadJackLibrary()) {
|
||||
return false;
|
||||
} else {
|
||||
// Make sure a JACK server is running and available.
|
||||
jack_status_t status;
|
||||
jack_client_t *client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL);
|
||||
if (!client) {
|
||||
UnloadJackLibrary();
|
||||
return SDL_SetError("Can't open JACK client");
|
||||
}
|
||||
JACK_jack_client_close(client);
|
||||
}
|
||||
|
||||
impl->OpenDevice = JACK_OpenDevice;
|
||||
impl->GetDeviceBuf = JACK_GetDeviceBuf;
|
||||
impl->PlayDevice = JACK_PlayDevice;
|
||||
impl->CloseDevice = JACK_CloseDevice;
|
||||
impl->Deinitialize = JACK_Deinitialize;
|
||||
impl->RecordDevice = JACK_RecordDevice;
|
||||
impl->FlushRecording = JACK_FlushRecording;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
impl->OnlyHasDefaultRecordingDevice = true;
|
||||
impl->HasRecordingSupport = true;
|
||||
impl->ProvidesOwnCallbackThread = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap JACK_bootstrap = {
|
||||
"jack", "JACK Audio Connection Kit", JACK_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_JACK
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_N3DS
|
||||
|
||||
// N3DS Audio driver
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_n3dsaudio.h"
|
||||
|
||||
#define N3DSAUDIO_DRIVER_NAME "n3ds"
|
||||
|
||||
static dspHookCookie dsp_hook;
|
||||
static SDL_AudioDevice *audio_device;
|
||||
|
||||
// fully local functions related to the wavebufs / DSP, not the same as the `device->lock` SDL_Mutex!
|
||||
static SDL_INLINE void contextLock(SDL_AudioDevice *device)
|
||||
{
|
||||
LightLock_Lock(&device->hidden->lock);
|
||||
}
|
||||
|
||||
static SDL_INLINE void contextUnlock(SDL_AudioDevice *device)
|
||||
{
|
||||
LightLock_Unlock(&device->hidden->lock);
|
||||
}
|
||||
|
||||
static void N3DSAUD_DspHook(DSP_HookType hook)
|
||||
{
|
||||
if (hook == DSPHOOK_ONCANCEL) {
|
||||
contextLock(audio_device);
|
||||
audio_device->hidden->isCancelled = true;
|
||||
SDL_AudioDeviceDisconnected(audio_device);
|
||||
CondVar_Broadcast(&audio_device->hidden->cv);
|
||||
contextUnlock(audio_device);
|
||||
}
|
||||
}
|
||||
|
||||
static void AudioFrameFinished(void *vdevice)
|
||||
{
|
||||
bool shouldBroadcast = false;
|
||||
unsigned i;
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *)vdevice;
|
||||
|
||||
contextLock(device);
|
||||
|
||||
for (i = 0; i < NUM_BUFFERS; i++) {
|
||||
if (device->hidden->waveBuf[i].status == NDSP_WBUF_DONE) {
|
||||
device->hidden->waveBuf[i].status = NDSP_WBUF_FREE;
|
||||
shouldBroadcast = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldBroadcast) {
|
||||
CondVar_Broadcast(&device->hidden->cv);
|
||||
}
|
||||
|
||||
contextUnlock(device);
|
||||
}
|
||||
|
||||
static bool N3DSAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
Result ndsp_init_res;
|
||||
Uint8 *data_vaddr;
|
||||
float mix[12];
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialise the DSP service
|
||||
ndsp_init_res = ndspInit();
|
||||
if (R_FAILED(ndsp_init_res)) {
|
||||
if ((R_SUMMARY(ndsp_init_res) == RS_NOTFOUND) && (R_MODULE(ndsp_init_res) == RM_DSP)) {
|
||||
return SDL_SetError("DSP init failed: dspfirm.cdc missing!");
|
||||
} else {
|
||||
return SDL_SetError("DSP init failed. Error code: 0x%lX", ndsp_init_res);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise internal state
|
||||
LightLock_Init(&device->hidden->lock);
|
||||
CondVar_Init(&device->hidden->cv);
|
||||
|
||||
if (device->spec.channels > 2) {
|
||||
device->spec.channels = 2;
|
||||
}
|
||||
|
||||
Uint32 format = 0;
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (test_format == SDL_AUDIO_S8) { // Signed 8-bit audio supported
|
||||
format = (device->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM8 : NDSP_FORMAT_MONO_PCM8;
|
||||
break;
|
||||
} else if (test_format == SDL_AUDIO_S16) { // Signed 16-bit audio supported
|
||||
format = (device->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM16 : NDSP_FORMAT_MONO_PCM16;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) { // shouldn't happen, but just in case...
|
||||
return SDL_SetError("No supported audio format found.");
|
||||
}
|
||||
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
// Allocate mixing buffer
|
||||
if (device->buffer_size >= SDL_MAX_UINT32 / 2) {
|
||||
return SDL_SetError("Mixing buffer is too large.");
|
||||
}
|
||||
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
|
||||
data_vaddr = (Uint8 *)linearAlloc(device->buffer_size * NUM_BUFFERS);
|
||||
if (!data_vaddr) {
|
||||
return SDL_OutOfMemory();
|
||||
}
|
||||
|
||||
SDL_memset(data_vaddr, 0, device->buffer_size * NUM_BUFFERS);
|
||||
DSP_FlushDataCache(data_vaddr, device->buffer_size * NUM_BUFFERS);
|
||||
|
||||
device->hidden->nextbuf = 0;
|
||||
|
||||
ndspChnReset(0);
|
||||
|
||||
ndspChnSetInterp(0, NDSP_INTERP_LINEAR);
|
||||
ndspChnSetRate(0, device->spec.freq);
|
||||
ndspChnSetFormat(0, format);
|
||||
|
||||
SDL_zeroa(mix);
|
||||
mix[0] = mix[1] = 1.0f;
|
||||
ndspChnSetMix(0, mix);
|
||||
|
||||
SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
|
||||
|
||||
const int sample_frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
for (unsigned i = 0; i < NUM_BUFFERS; i++) {
|
||||
device->hidden->waveBuf[i].data_vaddr = data_vaddr;
|
||||
device->hidden->waveBuf[i].nsamples = device->buffer_size / sample_frame_size;
|
||||
data_vaddr += device->buffer_size;
|
||||
}
|
||||
|
||||
// Setup callback
|
||||
audio_device = device;
|
||||
ndspSetCallback(AudioFrameFinished, device);
|
||||
dspHook(&dsp_hook, N3DSAUD_DspHook);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool N3DSAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
contextLock(device);
|
||||
|
||||
const size_t nextbuf = device->hidden->nextbuf;
|
||||
|
||||
if (device->hidden->isCancelled ||
|
||||
device->hidden->waveBuf[nextbuf].status != NDSP_WBUF_FREE) {
|
||||
contextUnlock(device);
|
||||
return true; // !!! FIXME: is this a fatal error? If so, this should return false.
|
||||
}
|
||||
|
||||
device->hidden->nextbuf = (nextbuf + 1) % NUM_BUFFERS;
|
||||
|
||||
contextUnlock(device);
|
||||
|
||||
SDL_memcpy((void *)device->hidden->waveBuf[nextbuf].data_vaddr, buffer, buflen);
|
||||
DSP_FlushDataCache(device->hidden->waveBuf[nextbuf].data_vaddr, buflen);
|
||||
|
||||
ndspChnWaveBufAdd(0, &device->hidden->waveBuf[nextbuf]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool N3DSAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
contextLock(device);
|
||||
while (!device->hidden->isCancelled && !SDL_GetAtomicInt(&device->shutdown) &&
|
||||
device->hidden->waveBuf[device->hidden->nextbuf].status != NDSP_WBUF_FREE) {
|
||||
CondVar_Wait(&device->hidden->cv, &device->hidden->lock);
|
||||
}
|
||||
contextUnlock(device);
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *N3DSAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static void N3DSAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (!device->hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
contextLock(device);
|
||||
|
||||
dspUnhook(&dsp_hook);
|
||||
ndspSetCallback(NULL, NULL);
|
||||
|
||||
if (!device->hidden->isCancelled) {
|
||||
ndspChnReset(0);
|
||||
SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
|
||||
CondVar_Broadcast(&device->hidden->cv);
|
||||
}
|
||||
|
||||
contextUnlock(device);
|
||||
|
||||
ndspExit();
|
||||
|
||||
if (device->hidden->waveBuf[0].data_vaddr) {
|
||||
linearFree((void *)device->hidden->waveBuf[0].data_vaddr);
|
||||
}
|
||||
|
||||
if (device->hidden->mixbuf) {
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
device->hidden->mixbuf = NULL;
|
||||
}
|
||||
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
|
||||
static void N3DSAUDIO_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
s32 current_priority = 0x30;
|
||||
svcGetThreadPriority(¤t_priority, CUR_THREAD_HANDLE);
|
||||
current_priority--;
|
||||
// 0x18 is reserved for video, 0x30 is the default for main thread
|
||||
current_priority = SDL_clamp(current_priority, 0x19, 0x2F);
|
||||
svcSetThreadPriority(CUR_THREAD_HANDLE, current_priority);
|
||||
}
|
||||
|
||||
static bool N3DSAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = N3DSAUDIO_OpenDevice;
|
||||
impl->PlayDevice = N3DSAUDIO_PlayDevice;
|
||||
impl->WaitDevice = N3DSAUDIO_WaitDevice;
|
||||
impl->GetDeviceBuf = N3DSAUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = N3DSAUDIO_CloseDevice;
|
||||
impl->ThreadInit = N3DSAUDIO_ThreadInit;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
|
||||
// Should be possible, but micInit would fail
|
||||
impl->HasRecordingSupport = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap N3DSAUDIO_bootstrap = {
|
||||
N3DSAUDIO_DRIVER_NAME,
|
||||
"SDL N3DS audio driver",
|
||||
N3DSAUDIO_Init,
|
||||
false,
|
||||
false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_N3DS
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_n3dsaudio_h
|
||||
#define SDL_n3dsaudio_h
|
||||
|
||||
#include <3ds.h>
|
||||
|
||||
#define NUM_BUFFERS 3 // -- Minimum 2!
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// Speaker data
|
||||
Uint8 *mixbuf;
|
||||
Uint32 nextbuf;
|
||||
ndspWaveBuf waveBuf[NUM_BUFFERS];
|
||||
LightLock lock;
|
||||
CondVar cv;
|
||||
bool isCancelled;
|
||||
};
|
||||
|
||||
#endif // SDL_n3dsaudio_h
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_NETBSD
|
||||
|
||||
// Driver for native NetBSD audio(4).
|
||||
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/audioio.h>
|
||||
|
||||
#include "../../core/unix/SDL_poll.h"
|
||||
#include "../SDL_audiodev_c.h"
|
||||
#include "SDL_netbsdaudio.h"
|
||||
|
||||
//#define DEBUG_AUDIO
|
||||
|
||||
static void NETBSDAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
SDL_EnumUnixAudioDevices(false, NULL);
|
||||
}
|
||||
|
||||
static void NETBSDAUDIO_Status(SDL_AudioDevice *device)
|
||||
{
|
||||
#ifdef DEBUG_AUDIO
|
||||
/* *INDENT-OFF* */ // clang-format off
|
||||
audio_info_t info;
|
||||
const struct audio_prinfo *prinfo;
|
||||
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
|
||||
fprintf(stderr, "AUDIO_GETINFO failed.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
prinfo = device->recording ? &info.record : &info.play;
|
||||
|
||||
fprintf(stderr, "\n"
|
||||
"[%s info]\n"
|
||||
"buffer size : %d bytes\n"
|
||||
"sample rate : %i Hz\n"
|
||||
"channels : %i\n"
|
||||
"precision : %i-bit\n"
|
||||
"encoding : 0x%x\n"
|
||||
"seek : %i\n"
|
||||
"sample count : %i\n"
|
||||
"EOF count : %i\n"
|
||||
"paused : %s\n"
|
||||
"error occurred : %s\n"
|
||||
"waiting : %s\n"
|
||||
"active : %s\n"
|
||||
"",
|
||||
device->recording ? "record" : "play",
|
||||
prinfo->buffer_size,
|
||||
prinfo->sample_rate,
|
||||
prinfo->channels,
|
||||
prinfo->precision,
|
||||
prinfo->encoding,
|
||||
prinfo->seek,
|
||||
prinfo->samples,
|
||||
prinfo->eof,
|
||||
prinfo->pause ? "yes" : "no",
|
||||
prinfo->error ? "yes" : "no",
|
||||
prinfo->waiting ? "yes" : "no",
|
||||
prinfo->active ? "yes" : "no");
|
||||
|
||||
fprintf(stderr, "\n"
|
||||
"[audio info]\n"
|
||||
"monitor_gain : %i\n"
|
||||
"hw block size : %d bytes\n"
|
||||
"hi watermark : %i\n"
|
||||
"lo watermark : %i\n"
|
||||
"audio mode : %s\n"
|
||||
"",
|
||||
info.monitor_gain,
|
||||
info.blocksize,
|
||||
info.hiwat, info.lowat,
|
||||
(info.mode == AUMODE_PLAY) ? "PLAY"
|
||||
: (info.mode == AUMODE_RECORD) ? "RECORD"
|
||||
: (info.mode == AUMODE_PLAY_ALL ? "PLAY_ALL" : "?"));
|
||||
|
||||
fprintf(stderr, "\n"
|
||||
"[audio spec]\n"
|
||||
"format : 0x%x\n"
|
||||
"size : %u\n"
|
||||
"",
|
||||
device->spec.format,
|
||||
device->buffer_size);
|
||||
/* *INDENT-ON* */ // clang-format on
|
||||
|
||||
#endif // DEBUG_AUDIO
|
||||
}
|
||||
|
||||
static bool NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const bool recording = device->recording;
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
audio_info_t info;
|
||||
const int rc = ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info);
|
||||
if (rc < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
continue;
|
||||
}
|
||||
// Hmm, not much we can do - abort
|
||||
fprintf(stderr, "netbsdaudio WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
const size_t remain = (size_t)((recording ? info.record.seek : info.play.seek) * SDL_AUDIO_BYTESIZE(device->spec.format));
|
||||
if (!recording && (remain >= device->buffer_size)) {
|
||||
SDL_Delay(10);
|
||||
} else if (recording && (remain < device->buffer_size)) {
|
||||
SDL_Delay(10);
|
||||
} else {
|
||||
break; // ready to go!
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool NETBSDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
const int written = write(h->audio_fd, buffer, buflen);
|
||||
if (written != buflen) { // Treat even partial writes as fatal errors.
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Wrote %d bytes of audio data\n", written);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *NETBSDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int NETBSDAUDIO_RecordDevice(SDL_AudioDevice *device, void *vbuffer, int buflen)
|
||||
{
|
||||
Uint8 *buffer = (Uint8 *)vbuffer;
|
||||
const int br = read(device->hidden->audio_fd, buffer, buflen);
|
||||
if (br == -1) {
|
||||
// Non recoverable error has occurred. It should be reported!!!
|
||||
perror("audio");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Recorded %d bytes of audio data\n", br);
|
||||
#endif
|
||||
return br;
|
||||
}
|
||||
|
||||
static void NETBSDAUDIO_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
audio_info_t info;
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) == 0) {
|
||||
size_t remain = (size_t)(info.record.seek * SDL_AUDIO_BYTESIZE(device->spec.format));
|
||||
while (remain > 0) {
|
||||
char buf[512];
|
||||
const size_t len = SDL_min(sizeof(buf), remain);
|
||||
const ssize_t br = read(h->audio_fd, buf, len);
|
||||
if (br <= 0) {
|
||||
break;
|
||||
}
|
||||
remain -= br;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void NETBSDAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->audio_fd >= 0) {
|
||||
close(device->hidden->audio_fd);
|
||||
}
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool NETBSDAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const bool recording = device->recording;
|
||||
int encoding = AUDIO_ENCODING_NONE;
|
||||
audio_info_t info, hwinfo;
|
||||
struct audio_prinfo *prinfo = recording ? &info.record : &info.play;
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
|
||||
const int flags = ((device->recording) ? O_RDONLY : O_WRONLY);
|
||||
device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC);
|
||||
if (device->hidden->audio_fd < 0) {
|
||||
return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno));
|
||||
}
|
||||
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
#ifdef AUDIO_GETFORMAT // Introduced in NetBSD 9.0
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_GETFORMAT, &hwinfo) != -1) {
|
||||
// Use the device's native sample rate so the kernel doesn't have to resample.
|
||||
device->spec.freq = recording ? hwinfo.record.sample_rate : hwinfo.play.sample_rate;
|
||||
}
|
||||
#endif
|
||||
|
||||
prinfo->sample_rate = device->spec.freq;
|
||||
prinfo->channels = device->spec.channels;
|
||||
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_U8:
|
||||
encoding = AUDIO_ENCODING_ULINEAR;
|
||||
break;
|
||||
case SDL_AUDIO_S8:
|
||||
encoding = AUDIO_ENCODING_SLINEAR;
|
||||
break;
|
||||
case SDL_AUDIO_S16LE:
|
||||
encoding = AUDIO_ENCODING_SLINEAR_LE;
|
||||
break;
|
||||
case SDL_AUDIO_S16BE:
|
||||
encoding = AUDIO_ENCODING_SLINEAR_BE;
|
||||
break;
|
||||
case SDL_AUDIO_S32LE:
|
||||
encoding = AUDIO_ENCODING_SLINEAR_LE;
|
||||
break;
|
||||
case SDL_AUDIO_S32BE:
|
||||
encoding = AUDIO_ENCODING_SLINEAR_BE;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("%s: Unsupported audio format", "netbsd");
|
||||
}
|
||||
prinfo->encoding = encoding;
|
||||
prinfo->precision = SDL_AUDIO_BITSIZE(test_format);
|
||||
|
||||
info.hiwat = 5;
|
||||
info.lowat = 3;
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_SETINFO, &info) < 0) {
|
||||
return SDL_SetError("AUDIO_SETINFO failed for %s: %s", device->name, strerror(errno));
|
||||
}
|
||||
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
|
||||
return SDL_SetError("AUDIO_GETINFO failed for %s: %s", device->name, strerror(errno));
|
||||
}
|
||||
|
||||
// Final spec used for the device.
|
||||
device->spec.format = test_format;
|
||||
device->spec.freq = prinfo->sample_rate;
|
||||
device->spec.channels = prinfo->channels;
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
if (!recording) {
|
||||
// Allocate mixing buffer
|
||||
device->hidden->mixlen = device->buffer_size;
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->hidden->mixlen);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
NETBSDAUDIO_Status(device);
|
||||
|
||||
return true; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static bool NETBSDAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->DetectDevices = NETBSDAUDIO_DetectDevices;
|
||||
impl->OpenDevice = NETBSDAUDIO_OpenDevice;
|
||||
impl->WaitDevice = NETBSDAUDIO_WaitDevice;
|
||||
impl->PlayDevice = NETBSDAUDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = NETBSDAUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = NETBSDAUDIO_CloseDevice;
|
||||
impl->WaitRecordingDevice = NETBSDAUDIO_WaitDevice;
|
||||
impl->RecordDevice = NETBSDAUDIO_RecordDevice;
|
||||
impl->FlushRecording = NETBSDAUDIO_FlushRecording;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap NETBSDAUDIO_bootstrap = {
|
||||
"netbsd", "NetBSD audio", NETBSDAUDIO_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_NETBSD
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_NGAGE
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_ngageaudio.h"
|
||||
|
||||
static SDL_AudioDevice *devptr = NULL;
|
||||
|
||||
SDL_AudioDevice *NGAGE_GetAudioDeviceAddr()
|
||||
{
|
||||
return devptr;
|
||||
}
|
||||
|
||||
static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
SDL_PrivateAudioData *phdata = SDL_calloc(1, sizeof(SDL_PrivateAudioData));
|
||||
if (!phdata) {
|
||||
SDL_OutOfMemory();
|
||||
return false;
|
||||
}
|
||||
device->hidden = phdata;
|
||||
|
||||
phdata->buffer[0] = SDL_calloc(1, device->buffer_size);
|
||||
phdata->buffer[1] = SDL_calloc(1, device->buffer_size);
|
||||
if (!phdata->buffer[0] || !phdata->buffer[1])
|
||||
{
|
||||
SDL_Log("Error: Failed to allocate audio buffers");
|
||||
SDL_free(phdata->buffer[0]);
|
||||
SDL_free(phdata->buffer[1]);
|
||||
SDL_free(phdata);
|
||||
return false;
|
||||
}
|
||||
|
||||
phdata->fill_index = 0;
|
||||
|
||||
devptr = device;
|
||||
|
||||
device->spec.format = SDL_AUDIO_S16LE;
|
||||
device->spec.channels = 1;
|
||||
device->spec.freq = 8000;
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*********************************************
|
||||
|
||||
NGAGEAUDIO_GetDeviceBuf -
|
||||
|
||||
Return the buffer that is currently being filled by SDL
|
||||
|
||||
**********************************************/
|
||||
static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
|
||||
if (!phdata) {
|
||||
*buffer_size = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*buffer_size = device->buffer_size;
|
||||
|
||||
return phdata->buffer[phdata->fill_index];
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void NGAGEAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
|
||||
|
||||
SDL_free(phdata->buffer[0]);
|
||||
SDL_free(phdata->buffer[1]);
|
||||
SDL_free(phdata);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool NGAGEAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = NGAGEAUDIO_OpenDevice;
|
||||
impl->GetDeviceBuf = NGAGEAUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = NGAGEAUDIO_CloseDevice;
|
||||
|
||||
impl->ProvidesOwnCallbackThread = true;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap NGAGEAUDIO_bootstrap = { "N-Gage", "N-Gage audio driver", NGAGEAUDIO_Init, false };
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_NGAGE
|
||||
+374
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "SDL_ngageaudio.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_NGAGE
|
||||
|
||||
#include "SDL_ngageaudio.hpp"
|
||||
|
||||
CAudio::CAudio() : CActive(EPriorityStandard), iBufDes(NULL, 0) {}
|
||||
|
||||
CAudio *CAudio::NewL(TInt aLatency)
|
||||
{
|
||||
CAudio *self = new (ELeave) CAudio();
|
||||
CleanupStack::PushL(self);
|
||||
self->ConstructL(aLatency);
|
||||
CleanupStack::Pop(self);
|
||||
return self;
|
||||
}
|
||||
|
||||
void CAudio::ConstructL(TInt aLatency)
|
||||
{
|
||||
CActiveScheduler::Add(this);
|
||||
User::LeaveIfError(iTimer.CreateLocal());
|
||||
iTimerCreated = ETrue;
|
||||
|
||||
iStream = CMdaAudioOutputStream::NewL(*this);
|
||||
if (!iStream) {
|
||||
SDL_Log("Error: Failed to create audio stream");
|
||||
User::Leave(KErrNoMemory);
|
||||
}
|
||||
|
||||
iLatency = aLatency;
|
||||
iLatencySamples = aLatency * 8; // 8kHz.
|
||||
|
||||
// Determine minimum and maximum number of samples to write with one
|
||||
// WriteL request.
|
||||
iMinWrite = iLatencySamples / 8;
|
||||
iMaxWrite = iLatencySamples / 2;
|
||||
|
||||
// Set defaults.
|
||||
iState = EStateNone;
|
||||
iTimerCreated = EFalse;
|
||||
iTimerActive = EFalse;
|
||||
}
|
||||
|
||||
CAudio::~CAudio()
|
||||
{
|
||||
if (iStream) {
|
||||
// Stop the processing thread before stopping the stream.
|
||||
StopThread();
|
||||
|
||||
iStream->Stop();
|
||||
|
||||
// MaoscBufferCopied(KErrAbort) can't fire here because the active
|
||||
// scheduler is no longer pumping; force the state to avoid deadlock.
|
||||
iState = EStateDone;
|
||||
|
||||
delete iStream;
|
||||
iStream = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CAudio::Start()
|
||||
{
|
||||
if (iStream) {
|
||||
// Set to 8kHz mono audio.
|
||||
iStreamSettings.iChannels = TMdaAudioDataSettings::EChannelsMono;
|
||||
iStreamSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate8000Hz;
|
||||
iStream->Open(&iStreamSettings);
|
||||
iState = EStateOpening;
|
||||
} else {
|
||||
SDL_Log("Error: Failed to open audio stream");
|
||||
}
|
||||
}
|
||||
|
||||
void CAudio::RunL()
|
||||
{
|
||||
iTimerActive = EFalse;
|
||||
}
|
||||
|
||||
void CAudio::DoCancel()
|
||||
{
|
||||
iTimerActive = EFalse;
|
||||
iTimer.Cancel();
|
||||
}
|
||||
|
||||
void CAudio::StartThread()
|
||||
{
|
||||
TInt heapMinSize = 8192; // 8 KB initial heap size.
|
||||
TInt heapMaxSize = 1024 * 1024; // 1 MB maximum heap size.
|
||||
|
||||
TInt err = iProcess.Create(_L("ProcessThread"), ProcessThreadCB, KDefaultStackSize * 2, heapMinSize, heapMaxSize, this);
|
||||
if (err == KErrNone) {
|
||||
TThreadPriority prio = EPriorityLess;
|
||||
|
||||
const char *prioHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_PRIORITY);
|
||||
if (prioHint) {
|
||||
// Symbian priorities: 10 (MuchLess), 20 (Less), 30 (Normal), 40 (More)
|
||||
prio = (TThreadPriority)SDL_atoi(prioHint);
|
||||
RThread().SetPriority(prio);
|
||||
}
|
||||
|
||||
iProcess.SetPriority(prio);
|
||||
iProcess.Resume();
|
||||
} else {
|
||||
SDL_Log("Error: Failed to create audio processing thread: %d", err);
|
||||
}
|
||||
}
|
||||
|
||||
void CAudio::StopThread()
|
||||
{
|
||||
if (iStreamStarted) {
|
||||
iProcess.Kill(KErrNone);
|
||||
iProcess.Close();
|
||||
iStreamStarted = EFalse;
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************
|
||||
* ProcessThreadCB -
|
||||
*
|
||||
* This thread calls the SDL mixer when the buffer is ready and self->iState == EStatePlaying (basically other than initial stated, when not writing)
|
||||
*
|
||||
* It only mixes, never calls WriteL
|
||||
****************************************************/
|
||||
|
||||
TInt CAudio::ProcessThreadCB(TAny *aPtr)
|
||||
{
|
||||
CTrapCleanup *cleanup = CTrapCleanup::New();
|
||||
if (!cleanup)
|
||||
return KErrNoMemory;
|
||||
|
||||
CAudio *self = static_cast<CAudio *>(aPtr);
|
||||
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
|
||||
|
||||
TInt processTick = 40000; // Default 40ms
|
||||
const char *tickHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_TICK);
|
||||
if (tickHint) {
|
||||
processTick = SDL_atoi(tickHint) * 1000;
|
||||
}
|
||||
|
||||
while (self->iStreamStarted) {
|
||||
if (self->iState == EStatePlaying && !self->iBufferReady) {
|
||||
/* Ask SDL to mix audio into buffer[fill_index]*/
|
||||
SDL_PlaybackAudioThreadIterate(device);
|
||||
|
||||
/* Signal AudioThreadCB to write it*/
|
||||
self->iBufferReady = ETrue;
|
||||
} else {
|
||||
/*if we are not ready to obtain the mix data we sleep a bit this thread*/
|
||||
User::After(processTick);
|
||||
}
|
||||
}
|
||||
|
||||
delete cleanup;
|
||||
return KErrNone;
|
||||
}
|
||||
|
||||
static TBool gAudioRunning;
|
||||
/***************************************************
|
||||
* AudioThreadCB -
|
||||
*
|
||||
* This thread owns the scheduler and calls WriteL, wich queues the assigned sound buffer to be played
|
||||
****************************************************/
|
||||
|
||||
TInt AudioThreadCB(TAny *aParams)
|
||||
{
|
||||
CTrapCleanup *cleanup = CTrapCleanup::New();
|
||||
CActiveScheduler *scheduler = new CActiveScheduler();
|
||||
CActiveScheduler::Install(scheduler);
|
||||
|
||||
TRAPD(err, {
|
||||
TInt latency = *(TInt *)aParams;
|
||||
CAudio *audio = CAudio::NewL(latency);
|
||||
CleanupStack::PushL(audio);
|
||||
|
||||
audio->iBufferReady = EFalse;
|
||||
|
||||
gAudioRunning = ETrue;
|
||||
audio->Start();
|
||||
|
||||
TInt processTick = 5000; // Default 5ms
|
||||
const char *tickHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_TICK);
|
||||
if (tickHint) {
|
||||
processTick = SDL_atoi(tickHint) * 1000;
|
||||
}
|
||||
|
||||
while (gAudioRunning) {
|
||||
TInt error;
|
||||
CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
|
||||
|
||||
/*there is some mix data sound ready*/
|
||||
if (audio->iBufferReady) {
|
||||
audio->iBufferReady = EFalse;
|
||||
|
||||
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
|
||||
|
||||
if (device && device->hidden) {
|
||||
SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
|
||||
audio->iState = EStateWriting;
|
||||
/*sends the chuck mixed to the queue*/
|
||||
audio->iBufDes.Set(phdata->buffer[phdata->fill_index], device->buffer_size, device->buffer_size);
|
||||
TRAPD(werr, audio->iStream->WriteL(audio->iBufDes));
|
||||
|
||||
if (werr != KErrNone) {
|
||||
/*asks ProcessThreadCB to bring another mix chunk*/
|
||||
audio->iState = EStatePlaying;
|
||||
} else {
|
||||
/*swap buffers so while this buffer is being played we can get the mix of the next one if we can*/
|
||||
phdata->fill_index = 1 - phdata->fill_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*sleep a bit this thread not to hog the CPU*/
|
||||
User::After(processTick);
|
||||
}
|
||||
|
||||
CleanupStack::PopAndDestroy(audio);
|
||||
});
|
||||
|
||||
delete scheduler;
|
||||
delete cleanup;
|
||||
return err;
|
||||
}
|
||||
|
||||
/***************************************************
|
||||
* MaoscOpenComplete -
|
||||
*
|
||||
* Opens the audiostream
|
||||
*
|
||||
* *******************************************************/
|
||||
|
||||
void CAudio::MaoscOpenComplete(TInt aError)
|
||||
{
|
||||
if (aError == KErrNone) {
|
||||
/*setting the volume to max, users can change the volume later of their channels individually in code*/
|
||||
iStream->SetVolume(iStream->MaxVolume());
|
||||
iStreamStarted = ETrue;
|
||||
|
||||
/* Wait until SDL has set devptr and hidden data*/
|
||||
SDL_AudioDevice *device = NULL;
|
||||
while (!device || !device->hidden) {
|
||||
User::After(10000); // 10ms poll
|
||||
device = NGAGE_GetAudioDeviceAddr();
|
||||
}
|
||||
|
||||
/* Now start the ProcessThreadCB thread*/
|
||||
StartThread();
|
||||
|
||||
/* Kickstart: device is guaranteed valid now*/
|
||||
this->iState = EStatePlaying;
|
||||
|
||||
} else {
|
||||
SDL_Log("Error: Failed to open audio stream: %d", aError);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************
|
||||
* MaoscOpenComplete -
|
||||
*
|
||||
* This signals the mixed data has been finally copied to the designated audio buffer
|
||||
*
|
||||
* *******************************************************/
|
||||
|
||||
void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/)
|
||||
{
|
||||
if (aError == KErrNone) {
|
||||
iState = EStatePlaying;
|
||||
} else if (aError == KErrAbort) {
|
||||
/* The stream has been stopped.*/
|
||||
iState = EStateDone;
|
||||
} else {
|
||||
SDL_Log("Error: Failed to copy audio buffer: %d", aError);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************
|
||||
* MaoscPlayComplete -
|
||||
*
|
||||
* The result after playing the mixed chunk
|
||||
*
|
||||
* *******************************************************/
|
||||
|
||||
void CAudio::MaoscPlayComplete(TInt aError)
|
||||
{
|
||||
|
||||
/* If we finish due to an underflow, we'll need to restart playback.
|
||||
Normally KErrUnderlow is raised at stream end, but in our case the API
|
||||
should never see the stream end -- we are continuously feeding it more
|
||||
data! Many underflow errors mean that the latency target is too low.*/
|
||||
if (aError == KErrUnderflow) {
|
||||
/* Restart the stream hardware */
|
||||
iStream->Stop();
|
||||
TInt ignoredError;
|
||||
TRAP(ignoredError, iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono));
|
||||
|
||||
/* This wakes up ProcessThreadCB so it can call SDL_PlaybackAudioThreadIterate*/
|
||||
iState = EStatePlaying;
|
||||
|
||||
return;
|
||||
} else if (aError != KErrNone) {
|
||||
}
|
||||
|
||||
/* We shouldn't get here.*/
|
||||
SDL_Log("%s: %d", SDL_FUNCTION, aError);
|
||||
}
|
||||
|
||||
TBool AudioIsReady()
|
||||
{
|
||||
return gAudioRunning;
|
||||
}
|
||||
|
||||
RThread audioThread;
|
||||
|
||||
void InitAudio(TInt *aLatency)
|
||||
{
|
||||
// Check if the user has provided a custom latency value via a hint
|
||||
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_LATENCY);
|
||||
if (hint) {
|
||||
*aLatency = (TInt)SDL_atoi(hint);
|
||||
}
|
||||
|
||||
_LIT(KAudioThreadName, "AudioThread");
|
||||
|
||||
TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency);
|
||||
if (err != KErrNone) {
|
||||
User::Leave(err);
|
||||
}
|
||||
|
||||
audioThread.Resume();
|
||||
}
|
||||
|
||||
void DeinitAudio()
|
||||
{
|
||||
gAudioRunning = EFalse;
|
||||
|
||||
TRequestStatus status;
|
||||
audioThread.Logon(status);
|
||||
User::WaitForRequest(status);
|
||||
|
||||
audioThread.Close();
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_NGAGE
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_ngageaudio_h
|
||||
#define SDL_ngageaudio_h
|
||||
|
||||
#ifndef SDL_HINT_AUDIO_NGAGE_LATENCY
|
||||
#define SDL_HINT_AUDIO_NGAGE_LATENCY "SDL_AUDIO_NGAGE_LATENCY"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_AUDIO_NGAGE_SCHEDULER_TICK
|
||||
#define SDL_HINT_AUDIO_NGAGE_SCHEDULER_TICK "SDL_AUDIO_NGAGE_SCHEDULER_TICK"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_AUDIO_NGAGE_PROCESS_TICK
|
||||
#define SDL_HINT_AUDIO_NGAGE_PROCESS_TICK "SDL_AUDIO_NGAGE_PROCESS_TICK"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_AUDIO_NGAGE_PROCESS_PRIORITY
|
||||
#define SDL_HINT_AUDIO_NGAGE_PROCESS_PRIORITY "SDL_AUDIO_NGAGE_PROCESS_PRIORITY"
|
||||
#endif
|
||||
typedef struct SDL_PrivateAudioData
|
||||
{
|
||||
Uint8 *buffer[2];
|
||||
int fill_index; /* Which buffer SDL is currently filling */
|
||||
int play_index; /* Which buffer the hardware is currently using*/
|
||||
int buffer_size;
|
||||
|
||||
} SDL_PrivateAudioData;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
SDL_AudioDevice *NGAGE_GetAudioDeviceAddr();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SDL_ngageaudio_h
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_ngageaudio_hpp
|
||||
#define SDL_ngageaudio_hpp
|
||||
|
||||
#include <e32base.h>
|
||||
#include <e32std.h>
|
||||
#include <mda/common/audio.h>
|
||||
#include <mdaaudiooutputstream.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_ngageaudio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
TBool AudioIsReady();
|
||||
void InitAudio(TInt *aLatency);
|
||||
void DeinitAudio();
|
||||
|
||||
enum TAudioState
|
||||
{
|
||||
EStateNone = 0,
|
||||
EStateOpening,
|
||||
EStatePlaying,
|
||||
EStateWriting,
|
||||
EStateDone
|
||||
};
|
||||
|
||||
|
||||
class CAudio : public CActive, public MMdaAudioOutputStreamCallback
|
||||
{
|
||||
public:
|
||||
static CAudio *NewL(TInt aLatency);
|
||||
~CAudio();
|
||||
|
||||
void ConstructL(TInt aLatency);
|
||||
void Start();
|
||||
|
||||
|
||||
void RunL();
|
||||
void DoCancel();
|
||||
|
||||
static TInt ProcessThreadCB(TAny * /*aPtr*/);
|
||||
|
||||
void MaoscOpenComplete(TInt aError);
|
||||
void MaoscBufferCopied(TInt aError, const TDesC8 &aBuffer);
|
||||
void MaoscPlayComplete(TInt aError);
|
||||
|
||||
|
||||
TAudioState iState;
|
||||
CMdaAudioOutputStream *iStream; /*CMdaAudioOutputStream handler*/
|
||||
TPtr8 iBufDes; /* Descriptor for the buffer.*/
|
||||
TBool iStreamStarted; /* have we initialized the audio stream?*/
|
||||
RThread iProcess; /* thread handler */
|
||||
TBool iBufferReady; /* Signal AudioThreadCB the buffer is ready*/
|
||||
private:
|
||||
CAudio();
|
||||
void StartThread();
|
||||
void StopThread();
|
||||
|
||||
|
||||
TMdaAudioDataSettings iStreamSettings;
|
||||
|
||||
TInt iLatency; // Latency target in ms
|
||||
TInt iLatencySamples; // Latency target in samples.
|
||||
TInt iMinWrite; // Min number of samples to write per turn.
|
||||
TInt iMaxWrite; // Max number of samples to write per turn.
|
||||
|
||||
|
||||
RTimer iTimer;
|
||||
TBool iTimerCreated;
|
||||
TBool iTimerActive;
|
||||
|
||||
};
|
||||
|
||||
#endif // SDL_ngageaudio_hpp
|
||||
+808
@@ -0,0 +1,808 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_OPENSLES
|
||||
|
||||
// For more discussion of low latency audio on Android, see this:
|
||||
// https://googlesamples.github.io/android-audio-high-performance/guides/opensl_es.html
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_openslES.h"
|
||||
|
||||
#include "../../core/android/SDL_android.h"
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include <android/log.h>
|
||||
|
||||
|
||||
#define NUM_BUFFERS 2 // -- Don't lower this!
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
Uint8 *mixbuff;
|
||||
int next_buffer;
|
||||
Uint8 *pmixbuff[NUM_BUFFERS];
|
||||
SDL_Semaphore *playsem;
|
||||
};
|
||||
|
||||
#if 0
|
||||
#define LOG_TAG "SDL_openslES"
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
//#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)
|
||||
#define LOGV(...)
|
||||
#else
|
||||
#define LOGE(...)
|
||||
#define LOGI(...)
|
||||
#define LOGV(...)
|
||||
#endif
|
||||
|
||||
/*
|
||||
#define SL_SPEAKER_FRONT_LEFT ((SLuint32) 0x00000001)
|
||||
#define SL_SPEAKER_FRONT_RIGHT ((SLuint32) 0x00000002)
|
||||
#define SL_SPEAKER_FRONT_CENTER ((SLuint32) 0x00000004)
|
||||
#define SL_SPEAKER_LOW_FREQUENCY ((SLuint32) 0x00000008)
|
||||
#define SL_SPEAKER_BACK_LEFT ((SLuint32) 0x00000010)
|
||||
#define SL_SPEAKER_BACK_RIGHT ((SLuint32) 0x00000020)
|
||||
#define SL_SPEAKER_FRONT_LEFT_OF_CENTER ((SLuint32) 0x00000040)
|
||||
#define SL_SPEAKER_FRONT_RIGHT_OF_CENTER ((SLuint32) 0x00000080)
|
||||
#define SL_SPEAKER_BACK_CENTER ((SLuint32) 0x00000100)
|
||||
#define SL_SPEAKER_SIDE_LEFT ((SLuint32) 0x00000200)
|
||||
#define SL_SPEAKER_SIDE_RIGHT ((SLuint32) 0x00000400)
|
||||
#define SL_SPEAKER_TOP_CENTER ((SLuint32) 0x00000800)
|
||||
#define SL_SPEAKER_TOP_FRONT_LEFT ((SLuint32) 0x00001000)
|
||||
#define SL_SPEAKER_TOP_FRONT_CENTER ((SLuint32) 0x00002000)
|
||||
#define SL_SPEAKER_TOP_FRONT_RIGHT ((SLuint32) 0x00004000)
|
||||
#define SL_SPEAKER_TOP_BACK_LEFT ((SLuint32) 0x00008000)
|
||||
#define SL_SPEAKER_TOP_BACK_CENTER ((SLuint32) 0x00010000)
|
||||
#define SL_SPEAKER_TOP_BACK_RIGHT ((SLuint32) 0x00020000)
|
||||
*/
|
||||
#define SL_ANDROID_SPEAKER_STEREO (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT)
|
||||
#define SL_ANDROID_SPEAKER_QUAD (SL_ANDROID_SPEAKER_STEREO | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT)
|
||||
#define SL_ANDROID_SPEAKER_5DOT1 (SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY)
|
||||
#define SL_ANDROID_SPEAKER_7DOT1 (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT)
|
||||
|
||||
// engine interfaces
|
||||
static SLObjectItf engineObject = NULL;
|
||||
static SLEngineItf engineEngine = NULL;
|
||||
|
||||
// output mix interfaces
|
||||
static SLObjectItf outputMixObject = NULL;
|
||||
|
||||
// buffer queue player interfaces
|
||||
static SLObjectItf bqPlayerObject = NULL;
|
||||
static SLPlayItf bqPlayerPlay = NULL;
|
||||
static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL;
|
||||
#if 0
|
||||
static SLVolumeItf bqPlayerVolume;
|
||||
#endif
|
||||
|
||||
// recorder interfaces
|
||||
static SLObjectItf recorderObject = NULL;
|
||||
static SLRecordItf recorderRecord = NULL;
|
||||
static SLAndroidSimpleBufferQueueItf recorderBufferQueue = NULL;
|
||||
|
||||
#if 0
|
||||
static const char *sldevaudiorecorderstr = "SLES Audio Recorder";
|
||||
static const char *sldevaudioplayerstr = "SLES Audio Player";
|
||||
|
||||
#define SLES_DEV_AUDIO_RECORDER sldevaudiorecorderstr
|
||||
#define SLES_DEV_AUDIO_PLAYER sldevaudioplayerstr
|
||||
static void OPENSLES_DetectDevices( int recording )
|
||||
{
|
||||
LOGI( "openSLES_DetectDevices()" );
|
||||
if ( recording )
|
||||
addfn( SLES_DEV_AUDIO_RECORDER );
|
||||
else
|
||||
addfn( SLES_DEV_AUDIO_PLAYER );
|
||||
}
|
||||
#endif
|
||||
|
||||
static void OPENSLES_DestroyEngine(void)
|
||||
{
|
||||
LOGI("OPENSLES_DestroyEngine()");
|
||||
|
||||
// destroy output mix object, and invalidate all associated interfaces
|
||||
if (outputMixObject != NULL) {
|
||||
(*outputMixObject)->Destroy(outputMixObject);
|
||||
outputMixObject = NULL;
|
||||
}
|
||||
|
||||
// destroy engine object, and invalidate all associated interfaces
|
||||
if (engineObject != NULL) {
|
||||
(*engineObject)->Destroy(engineObject);
|
||||
engineObject = NULL;
|
||||
engineEngine = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool OPENSLES_CreateEngine(void)
|
||||
{
|
||||
const SLInterfaceID ids[1] = { SL_IID_VOLUME };
|
||||
const SLboolean req[1] = { SL_BOOLEAN_FALSE };
|
||||
SLresult result;
|
||||
|
||||
LOGI("openSLES_CreateEngine()");
|
||||
|
||||
// create engine
|
||||
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("slCreateEngine failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
LOGI("slCreateEngine OK");
|
||||
|
||||
// realize the engine
|
||||
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RealizeEngine failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
LOGI("RealizeEngine OK");
|
||||
|
||||
// get the engine interface, which is needed in order to create other objects
|
||||
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("EngineGetInterface failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
LOGI("EngineGetInterface OK");
|
||||
|
||||
// create output mix
|
||||
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("CreateOutputMix failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
LOGI("CreateOutputMix OK");
|
||||
|
||||
// realize the output mix
|
||||
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RealizeOutputMix failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
return true;
|
||||
|
||||
error:
|
||||
OPENSLES_DestroyEngine();
|
||||
return false;
|
||||
}
|
||||
|
||||
// this callback handler is called every time a buffer finishes recording
|
||||
static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = (struct SDL_PrivateAudioData *)context;
|
||||
|
||||
LOGV("SLES: Recording Callback");
|
||||
SDL_SignalSemaphore(audiodata->playsem);
|
||||
}
|
||||
|
||||
static void OPENSLES_DestroyPCMRecorder(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
SLresult result;
|
||||
|
||||
// stop recording
|
||||
if (recorderRecord != NULL) {
|
||||
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SetRecordState stopped: %d", result);
|
||||
}
|
||||
}
|
||||
|
||||
// destroy audio recorder object, and invalidate all associated interfaces
|
||||
if (recorderObject != NULL) {
|
||||
(*recorderObject)->Destroy(recorderObject);
|
||||
recorderObject = NULL;
|
||||
recorderRecord = NULL;
|
||||
recorderBufferQueue = NULL;
|
||||
}
|
||||
|
||||
if (audiodata->playsem) {
|
||||
SDL_DestroySemaphore(audiodata->playsem);
|
||||
audiodata->playsem = NULL;
|
||||
}
|
||||
|
||||
SDL_free(audiodata->mixbuff);
|
||||
}
|
||||
|
||||
// !!! FIXME: make this non-blocking!
|
||||
static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted)
|
||||
{
|
||||
SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1);
|
||||
}
|
||||
|
||||
static bool OPENSLES_CreatePCMRecorder(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
SLDataFormat_PCM format_pcm;
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
|
||||
SLDataSink audioSnk;
|
||||
SLDataLocator_IODevice loc_dev;
|
||||
SLDataSource audioSrc;
|
||||
const SLInterfaceID ids[1] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
|
||||
const SLboolean req[1] = { SL_BOOLEAN_TRUE };
|
||||
SLresult result;
|
||||
int i;
|
||||
|
||||
// !!! FIXME: make this non-blocking!
|
||||
{
|
||||
SDL_AtomicInt permission_response;
|
||||
SDL_SetAtomicInt(&permission_response, 0);
|
||||
if (!SDL_RequestAndroidPermission("android.permission.RECORD_AUDIO", RequestAndroidPermissionBlockingCallback, &permission_response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (SDL_GetAtomicInt(&permission_response) == 0) {
|
||||
SDL_Delay(10);
|
||||
}
|
||||
|
||||
if (SDL_GetAtomicInt(&permission_response) < 0) {
|
||||
LOGE("This app doesn't have RECORD_AUDIO permission");
|
||||
return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
|
||||
}
|
||||
}
|
||||
|
||||
// Just go with signed 16-bit audio as it's the most compatible
|
||||
device->spec.format = SDL_AUDIO_S16;
|
||||
device->spec.channels = 1;
|
||||
//device->spec.freq = SL_SAMPLINGRATE_16 / 1000;*/
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
LOGI("Try to open %u hz %u bit %u channels %s samples %u",
|
||||
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
|
||||
device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
|
||||
|
||||
// configure audio source
|
||||
loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
|
||||
loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT;
|
||||
loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
|
||||
loc_dev.device = NULL;
|
||||
audioSrc.pLocator = &loc_dev;
|
||||
audioSrc.pFormat = NULL;
|
||||
|
||||
// configure audio sink
|
||||
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
loc_bufq.numBuffers = NUM_BUFFERS;
|
||||
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = device->spec.channels;
|
||||
format_pcm.samplesPerSec = device->spec.freq * 1000; // / kilo Hz to milli Hz
|
||||
format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
format_pcm.containerSize = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER;
|
||||
|
||||
audioSnk.pLocator = &loc_bufq;
|
||||
audioSnk.pFormat = &format_pcm;
|
||||
|
||||
// create audio recorder
|
||||
// (requires the RECORD_AUDIO permission)
|
||||
result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, &audioSnk, 1, ids, req);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("CreateAudioRecorder failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// realize the recorder
|
||||
result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RealizeAudioPlayer failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// get the record interface
|
||||
result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_RECORD interface get failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// get the buffer queue interface
|
||||
result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_BUFFERQUEUE interface get failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// register callback on the buffer queue
|
||||
// context is '(SDL_PrivateAudioData *)device->hidden'
|
||||
result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, device->hidden);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RegisterCallback failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// Create the audio buffer semaphore
|
||||
audiodata->playsem = SDL_CreateSemaphore(0);
|
||||
if (!audiodata->playsem) {
|
||||
LOGE("cannot create Semaphore!");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// Create the sound buffers
|
||||
audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size);
|
||||
if (!audiodata->mixbuff) {
|
||||
LOGE("mixbuffer allocate - out of memory");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_BUFFERS; i++) {
|
||||
audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size;
|
||||
}
|
||||
|
||||
// in case already recording, stop recording and clear buffer queue
|
||||
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Record set state failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// enqueue empty buffers to be filled by the recorder
|
||||
for (i = 0; i < NUM_BUFFERS; i++) {
|
||||
result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[i], device->buffer_size);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Record enqueue buffers failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
// start recording
|
||||
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Record set state failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
failed:
|
||||
return SDL_SetError("Open device failed!");
|
||||
}
|
||||
|
||||
// this callback handler is called every time a buffer finishes playing
|
||||
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = (struct SDL_PrivateAudioData *)context;
|
||||
|
||||
LOGV("SLES: Playback Callback");
|
||||
SDL_SignalSemaphore(audiodata->playsem);
|
||||
}
|
||||
|
||||
static void OPENSLES_DestroyPCMPlayer(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
// set the player's state to 'stopped'
|
||||
if (bqPlayerPlay != NULL) {
|
||||
const SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SetPlayState stopped failed: %d", result);
|
||||
}
|
||||
}
|
||||
|
||||
// destroy buffer queue audio player object, and invalidate all associated interfaces
|
||||
if (bqPlayerObject != NULL) {
|
||||
(*bqPlayerObject)->Destroy(bqPlayerObject);
|
||||
|
||||
bqPlayerObject = NULL;
|
||||
bqPlayerPlay = NULL;
|
||||
bqPlayerBufferQueue = NULL;
|
||||
}
|
||||
|
||||
if (audiodata->playsem) {
|
||||
SDL_DestroySemaphore(audiodata->playsem);
|
||||
audiodata->playsem = NULL;
|
||||
}
|
||||
|
||||
SDL_free(audiodata->mixbuff);
|
||||
}
|
||||
|
||||
static bool OPENSLES_CreatePCMPlayer(SDL_AudioDevice *device)
|
||||
{
|
||||
/* according to https://developer.android.com/ndk/guides/audio/opensl/opensl-for-android,
|
||||
Android's OpenSL ES only supports Uint8 and _littleendian_ Sint16.
|
||||
(and float32, with an extension we use, below.) */
|
||||
if (SDL_GetAndroidSDKVersion() >= 21) {
|
||||
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:
|
||||
case SDL_AUDIO_S16LE:
|
||||
case SDL_AUDIO_F32:
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
// Didn't find a compatible format :
|
||||
LOGI("No compatible audio format, using signed 16-bit LE audio");
|
||||
test_format = SDL_AUDIO_S16LE;
|
||||
}
|
||||
device->spec.format = test_format;
|
||||
} else {
|
||||
// Just go with signed 16-bit audio as it's the most compatible
|
||||
device->spec.format = SDL_AUDIO_S16LE;
|
||||
}
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
LOGI("Try to open %u hz %s %u bit %u channels %s samples %u",
|
||||
device->spec.freq, SDL_AUDIO_ISFLOAT(device->spec.format) ? "float" : "pcm", SDL_AUDIO_BITSIZE(device->spec.format),
|
||||
device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
|
||||
|
||||
// configure audio source
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
|
||||
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
loc_bufq.numBuffers = NUM_BUFFERS;
|
||||
|
||||
SLDataFormat_PCM format_pcm;
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = device->spec.channels;
|
||||
format_pcm.samplesPerSec = device->spec.freq * 1000; // / kilo Hz to milli Hz
|
||||
format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
format_pcm.containerSize = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
|
||||
if (SDL_AUDIO_ISBIGENDIAN(device->spec.format)) {
|
||||
format_pcm.endianness = SL_BYTEORDER_BIGENDIAN;
|
||||
} else {
|
||||
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
}
|
||||
|
||||
switch (device->spec.channels) {
|
||||
case 1:
|
||||
format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT;
|
||||
break;
|
||||
case 2:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_STEREO;
|
||||
break;
|
||||
case 3:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_STEREO | SL_SPEAKER_FRONT_CENTER;
|
||||
break;
|
||||
case 4:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_QUAD;
|
||||
break;
|
||||
case 5:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER;
|
||||
break;
|
||||
case 6:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_5DOT1;
|
||||
break;
|
||||
case 7:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_BACK_CENTER;
|
||||
break;
|
||||
case 8:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_7DOT1;
|
||||
break;
|
||||
default:
|
||||
// Unknown number of channels, fall back to stereo
|
||||
device->spec.channels = 2;
|
||||
format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
break;
|
||||
}
|
||||
|
||||
SLDataSink audioSnk;
|
||||
SLDataSource audioSrc;
|
||||
audioSrc.pFormat = (void *)&format_pcm;
|
||||
|
||||
SLAndroidDataFormat_PCM_EX format_pcm_ex;
|
||||
if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
|
||||
// Copy all setup into PCM EX structure
|
||||
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
format_pcm_ex.endianness = format_pcm.endianness;
|
||||
format_pcm_ex.channelMask = format_pcm.channelMask;
|
||||
format_pcm_ex.numChannels = format_pcm.numChannels;
|
||||
format_pcm_ex.sampleRate = format_pcm.samplesPerSec;
|
||||
format_pcm_ex.bitsPerSample = format_pcm.bitsPerSample;
|
||||
format_pcm_ex.containerSize = format_pcm.containerSize;
|
||||
format_pcm_ex.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
|
||||
audioSrc.pFormat = (void *)&format_pcm_ex;
|
||||
}
|
||||
|
||||
audioSrc.pLocator = &loc_bufq;
|
||||
|
||||
// configure audio sink
|
||||
SLDataLocator_OutputMix loc_outmix;
|
||||
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
|
||||
loc_outmix.outputMix = outputMixObject;
|
||||
audioSnk.pLocator = &loc_outmix;
|
||||
audioSnk.pFormat = NULL;
|
||||
|
||||
// create audio player
|
||||
const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
|
||||
const SLboolean req[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
|
||||
SLresult result;
|
||||
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("CreateAudioPlayer failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// realize the player
|
||||
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RealizeAudioPlayer failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// get the play interface
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_PLAY interface get failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// get the buffer queue interface
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bqPlayerBufferQueue);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_BUFFERQUEUE interface get failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// register callback on the buffer queue
|
||||
// context is '(SDL_PrivateAudioData *)device->hidden'
|
||||
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, device->hidden);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RegisterCallback failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// get the volume interface
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_VOLUME interface get failed: %d", result);
|
||||
// goto failed;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
// Create the audio buffer semaphore
|
||||
audiodata->playsem = SDL_CreateSemaphore(NUM_BUFFERS - 1);
|
||||
if (!audiodata->playsem) {
|
||||
LOGE("cannot create Semaphore!");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// Create the sound buffers
|
||||
audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size);
|
||||
if (!audiodata->mixbuff) {
|
||||
LOGE("mixbuffer allocate - out of memory");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_BUFFERS; i++) {
|
||||
audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size;
|
||||
}
|
||||
|
||||
// set the player's state to playing
|
||||
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Play set state failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
failed:
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool OPENSLES_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (device->recording) {
|
||||
LOGI("OPENSLES_OpenDevice() for recording");
|
||||
return OPENSLES_CreatePCMRecorder(device);
|
||||
} else {
|
||||
bool ret;
|
||||
LOGI("OPENSLES_OpenDevice() for playback");
|
||||
ret = OPENSLES_CreatePCMPlayer(device);
|
||||
if (!ret) {
|
||||
// Another attempt to open the device with a lower frequency
|
||||
if (device->spec.freq > 48000) {
|
||||
OPENSLES_DestroyPCMPlayer(device);
|
||||
device->spec.freq = 48000;
|
||||
ret = OPENSLES_CreatePCMPlayer(device);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
return SDL_SetError("Open device failed!");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool OPENSLES_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
LOGV("OPENSLES_WaitDevice()");
|
||||
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
// this semaphore won't fire when the app is in the background (OPENSLES_PauseDevices was called).
|
||||
if (SDL_WaitSemaphoreTimeout(audiodata->playsem, 100)) {
|
||||
return true; // semaphore was signaled, let's go!
|
||||
}
|
||||
// Still waiting on the semaphore (or the system), check other things then wait again.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool OPENSLES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
LOGV("======OPENSLES_PlayDevice()======");
|
||||
|
||||
// Queue it up
|
||||
const SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, buflen);
|
||||
|
||||
audiodata->next_buffer++;
|
||||
if (audiodata->next_buffer >= NUM_BUFFERS) {
|
||||
audiodata->next_buffer = 0;
|
||||
}
|
||||
|
||||
// If Enqueue fails, callback won't be called.
|
||||
// Post the semaphore, not to run out of buffer
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
SDL_SignalSemaphore(audiodata->playsem);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// n playn sem
|
||||
// getbuf 0 - 1
|
||||
// fill buff 0 - 1
|
||||
// play 0 - 0 1
|
||||
// wait 1 0 0
|
||||
// getbuf 1 0 0
|
||||
// fill buff 1 0 0
|
||||
// play 0 0 0
|
||||
// wait
|
||||
//
|
||||
// okay..
|
||||
|
||||
static Uint8 *OPENSLES_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
LOGV("OPENSLES_GetDeviceBuf()");
|
||||
return audiodata->pmixbuff[audiodata->next_buffer];
|
||||
}
|
||||
|
||||
static int OPENSLES_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
// Copy it to the output buffer
|
||||
SDL_assert(buflen == device->buffer_size);
|
||||
SDL_memcpy(buffer, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_size);
|
||||
|
||||
// Re-enqueue the buffer
|
||||
const SLresult result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_size);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Record enqueue buffers failed: %d", result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
audiodata->next_buffer++;
|
||||
if (audiodata->next_buffer >= NUM_BUFFERS) {
|
||||
audiodata->next_buffer = 0;
|
||||
}
|
||||
|
||||
return device->buffer_size;
|
||||
}
|
||||
|
||||
static void OPENSLES_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
if (device->hidden) {
|
||||
if (device->recording) {
|
||||
LOGI("OPENSLES_CloseDevice() for recording");
|
||||
OPENSLES_DestroyPCMRecorder(device);
|
||||
} else {
|
||||
LOGI("OPENSLES_CloseDevice() for playing");
|
||||
OPENSLES_DestroyPCMPlayer(device);
|
||||
}
|
||||
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool OPENSLES_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
LOGI("OPENSLES_Init() called");
|
||||
|
||||
if (!OPENSLES_CreateEngine()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("OPENSLES_Init() - set pointers");
|
||||
|
||||
// Set the function pointers
|
||||
// impl->DetectDevices = OPENSLES_DetectDevices;
|
||||
impl->ThreadInit = Android_AudioThreadInit;
|
||||
impl->OpenDevice = OPENSLES_OpenDevice;
|
||||
impl->WaitDevice = OPENSLES_WaitDevice;
|
||||
impl->PlayDevice = OPENSLES_PlayDevice;
|
||||
impl->GetDeviceBuf = OPENSLES_GetDeviceBuf;
|
||||
impl->WaitRecordingDevice = OPENSLES_WaitDevice;
|
||||
impl->RecordDevice = OPENSLES_RecordDevice;
|
||||
impl->CloseDevice = OPENSLES_CloseDevice;
|
||||
impl->Deinitialize = OPENSLES_DestroyEngine;
|
||||
|
||||
// and the capabilities
|
||||
impl->HasRecordingSupport = true;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
impl->OnlyHasDefaultRecordingDevice = true;
|
||||
|
||||
LOGI("OPENSLES_Init() - success");
|
||||
|
||||
// this audio target is available.
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap OPENSLES_bootstrap = {
|
||||
"openslES", "OpenSL ES audio driver", OPENSLES_Init, false, false
|
||||
};
|
||||
|
||||
void OPENSLES_ResumeDevices(void)
|
||||
{
|
||||
if (bqPlayerPlay != NULL) {
|
||||
// set the player's state to 'playing'
|
||||
SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("OPENSLES_ResumeDevices failed: %d", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OPENSLES_PauseDevices(void)
|
||||
{
|
||||
if (bqPlayerPlay != NULL) {
|
||||
// set the player's state to 'paused'
|
||||
SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("OPENSLES_PauseDevices failed: %d", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_OPENSLES
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_openslesaudio_h_
|
||||
#define SDL_openslesaudio_h_
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_OPENSLES
|
||||
|
||||
extern void OPENSLES_ResumeDevices(void);
|
||||
extern void OPENSLES_PauseDevices(void);
|
||||
|
||||
#else
|
||||
|
||||
#define OPENSLES_ResumeDevices()
|
||||
#define OPENSLES_PauseDevices()
|
||||
|
||||
#endif
|
||||
|
||||
#endif // SDL_openslesaudio_h_
|
||||
+1428
File diff suppressed because it is too large
Load Diff
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_ps2audio.h"
|
||||
|
||||
#include <kernel.h>
|
||||
#include <audsrv.h>
|
||||
#include <ps2_audio_driver.h>
|
||||
|
||||
static bool PS2AUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// These are the native supported audio PS2 configs
|
||||
switch (device->spec.freq) {
|
||||
case 11025:
|
||||
case 12000:
|
||||
case 22050:
|
||||
case 24000:
|
||||
case 32000:
|
||||
case 44100:
|
||||
case 48000:
|
||||
break; // acceptable value, keep it
|
||||
default:
|
||||
device->spec.freq = 48000;
|
||||
break;
|
||||
}
|
||||
|
||||
device->sample_frames = 512;
|
||||
device->spec.channels = device->spec.channels == 1 ? 1 : 2;
|
||||
device->spec.format = device->spec.format == SDL_AUDIO_S8 ? SDL_AUDIO_S8 : SDL_AUDIO_S16;
|
||||
|
||||
struct audsrv_fmt_t format;
|
||||
format.bits = device->spec.format == SDL_AUDIO_S8 ? 8 : 16;
|
||||
format.freq = device->spec.freq;
|
||||
format.channels = device->spec.channels;
|
||||
|
||||
device->hidden->channel = audsrv_set_format(&format);
|
||||
audsrv_set_volume(MAX_VOLUME);
|
||||
|
||||
if (device->hidden->channel < 0) {
|
||||
return SDL_SetError("Couldn't reserve hardware channel");
|
||||
}
|
||||
|
||||
// Update the fragment size as size in bytes.
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
/* Allocate the mixing buffer. Its size and starting address must
|
||||
be a multiple of 64 bytes. Our sample count is already a multiple of
|
||||
64, so spec->size should be a multiple of 64 as well. */
|
||||
const int mixlen = device->buffer_size * NUM_BUFFERS;
|
||||
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
|
||||
if (!device->hidden->rawbuf) {
|
||||
return SDL_SetError("Couldn't allocate mixing buffer");
|
||||
}
|
||||
|
||||
SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen);
|
||||
for (int i = 0; i < NUM_BUFFERS; i++) {
|
||||
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool PS2AUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
// this returns number of bytes accepted or a negative error. We assume anything other than buflen is a fatal error.
|
||||
return (audsrv_play_audio((char *)buffer, buflen) == buflen);
|
||||
}
|
||||
|
||||
static bool PS2AUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
audsrv_wait_audio(device->buffer_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *PS2AUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer];
|
||||
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void PS2AUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->channel >= 0) {
|
||||
audsrv_stop_audio();
|
||||
device->hidden->channel = -1;
|
||||
}
|
||||
|
||||
if (device->hidden->rawbuf) {
|
||||
SDL_aligned_free(device->hidden->rawbuf);
|
||||
device->hidden->rawbuf = NULL;
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void PS2AUDIO_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
/* Increase the priority of this audio thread by 1 to put it
|
||||
ahead of other SDL threads. */
|
||||
const int32_t thid = GetThreadId();
|
||||
ee_thread_status_t status;
|
||||
if (ReferThreadStatus(thid, &status) == 0) {
|
||||
ChangeThreadPriority(thid, status.current_priority - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void PS2AUDIO_Deinitialize(void)
|
||||
{
|
||||
deinit_audio_driver();
|
||||
}
|
||||
|
||||
static bool PS2AUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (init_audio_driver() < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
impl->OpenDevice = PS2AUDIO_OpenDevice;
|
||||
impl->PlayDevice = PS2AUDIO_PlayDevice;
|
||||
impl->WaitDevice = PS2AUDIO_WaitDevice;
|
||||
impl->GetDeviceBuf = PS2AUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = PS2AUDIO_CloseDevice;
|
||||
impl->ThreadInit = PS2AUDIO_ThreadInit;
|
||||
impl->Deinitialize = PS2AUDIO_Deinitialize;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
return true; // this audio target is available.
|
||||
}
|
||||
|
||||
AudioBootStrap PS2AUDIO_bootstrap = {
|
||||
"ps2", "PS2 audio driver", PS2AUDIO_Init, false, false
|
||||
};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_PSP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../SDL_audiodev_c.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_pspaudio.h"
|
||||
|
||||
#include <pspaudio.h>
|
||||
#include <pspthreadman.h>
|
||||
|
||||
static bool isBasicAudioConfig(const SDL_AudioSpec *spec)
|
||||
{
|
||||
return spec->freq == 44100;
|
||||
}
|
||||
|
||||
static bool PSPAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// device only natively supports S16LSB
|
||||
device->spec.format = SDL_AUDIO_S16LE;
|
||||
|
||||
/* PSP has some limitations with the Audio. It fully supports 44.1KHz (Mono & Stereo),
|
||||
however with frequencies different than 44.1KHz, it just supports Stereo,
|
||||
so a resampler must be done for these scenarios */
|
||||
if (isBasicAudioConfig(&device->spec)) {
|
||||
// The sample count must be a multiple of 64.
|
||||
device->sample_frames = PSP_AUDIO_SAMPLE_ALIGN(device->sample_frames);
|
||||
// The number of channels (1 or 2).
|
||||
device->spec.channels = device->spec.channels == 1 ? 1 : 2;
|
||||
const int format = (device->spec.channels == 1) ? PSP_AUDIO_FORMAT_MONO : PSP_AUDIO_FORMAT_STEREO;
|
||||
device->hidden->channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, device->sample_frames, format);
|
||||
} else {
|
||||
// 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11050, 8000
|
||||
switch (device->spec.freq) {
|
||||
case 8000:
|
||||
case 11025:
|
||||
case 12000:
|
||||
case 16000:
|
||||
case 22050:
|
||||
case 24000:
|
||||
case 32000:
|
||||
case 44100:
|
||||
case 48000:
|
||||
break; // acceptable, keep it
|
||||
default:
|
||||
device->spec.freq = 48000;
|
||||
break;
|
||||
}
|
||||
// The number of samples to output in one output call (min 17, max 4111).
|
||||
device->sample_frames = device->sample_frames < 17 ? 17 : (device->sample_frames > 4111 ? 4111 : device->sample_frames);
|
||||
device->spec.channels = 2; // we're forcing the hardware to stereo.
|
||||
device->hidden->channel = sceAudioSRCChReserve(device->sample_frames, device->spec.freq, 2);
|
||||
}
|
||||
|
||||
if (device->hidden->channel < 0) {
|
||||
return SDL_SetError("Couldn't reserve hardware channel");
|
||||
}
|
||||
|
||||
// Update the fragment size as size in bytes.
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
/* Allocate the mixing buffer. Its size and starting address must
|
||||
be a multiple of 64 bytes. Our sample count is already a multiple of
|
||||
64, so spec->size should be a multiple of 64 as well. */
|
||||
const int mixlen = device->buffer_size * NUM_BUFFERS;
|
||||
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
|
||||
if (!device->hidden->rawbuf) {
|
||||
return SDL_SetError("Couldn't allocate mixing buffer");
|
||||
}
|
||||
|
||||
SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen);
|
||||
for (int i = 0; i < NUM_BUFFERS; i++) {
|
||||
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
int rc;
|
||||
if (!isBasicAudioConfig(&device->spec)) {
|
||||
SDL_assert(device->spec.channels == 2);
|
||||
rc = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, (void *) buffer);
|
||||
} else {
|
||||
rc = sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, (void *) buffer);
|
||||
}
|
||||
return (rc >= 0);
|
||||
}
|
||||
|
||||
static bool PSPAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
return true; // Because we block when sending audio, there's no need for this function to do anything.
|
||||
}
|
||||
|
||||
static Uint8 *PSPAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer];
|
||||
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void PSPAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->channel >= 0) {
|
||||
if (!isBasicAudioConfig(&device->spec)) {
|
||||
sceAudioSRCChRelease();
|
||||
} else {
|
||||
sceAudioChRelease(device->hidden->channel);
|
||||
}
|
||||
device->hidden->channel = -1;
|
||||
}
|
||||
|
||||
if (device->hidden->rawbuf) {
|
||||
SDL_aligned_free(device->hidden->rawbuf);
|
||||
device->hidden->rawbuf = NULL;
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void PSPAUDIO_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
/* Increase the priority of this audio thread by 1 to put it
|
||||
ahead of other SDL threads. */
|
||||
const SceUID thid = sceKernelGetThreadId();
|
||||
SceKernelThreadInfo status;
|
||||
status.size = sizeof(SceKernelThreadInfo);
|
||||
if (sceKernelReferThreadStatus(thid, &status) == 0) {
|
||||
sceKernelChangeThreadPriority(thid, status.currentPriority - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static bool PSPAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = PSPAUDIO_OpenDevice;
|
||||
impl->PlayDevice = PSPAUDIO_PlayDevice;
|
||||
impl->WaitDevice = PSPAUDIO_WaitDevice;
|
||||
impl->GetDeviceBuf = PSPAUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = PSPAUDIO_CloseDevice;
|
||||
impl->ThreadInit = PSPAUDIO_ThreadInit;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
//impl->HasRecordingSupport = true;
|
||||
//impl->OnlyHasDefaultRecordingDevice = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap PSPAUDIO_bootstrap = {
|
||||
"psp", "PSP audio driver", PSPAUDIO_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_PSP
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_
|
||||
+1112
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_pulseaudio_h_
|
||||
#define SDL_pulseaudio_h_
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// pulseaudio structures
|
||||
pa_stream *stream;
|
||||
|
||||
// Raw mixing buffer
|
||||
Uint8 *mixbuf;
|
||||
|
||||
int bytes_requested; // bytes of data the hardware wants _now_.
|
||||
|
||||
const Uint8 *recordingbuf;
|
||||
int recordinglen;
|
||||
};
|
||||
|
||||
#endif // SDL_pulseaudio_h_
|
||||
+451
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
// !!! FIXME: can this target support hotplugging?
|
||||
|
||||
#include "../../SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_QNX
|
||||
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sched.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/neutrino.h>
|
||||
#include <sys/asoundlib.h>
|
||||
|
||||
#include "SDL3/SDL_timer.h"
|
||||
#include "SDL3/SDL_audio.h"
|
||||
#include "../../core/unix/SDL_poll.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_qsa_audio.h"
|
||||
|
||||
// default channel communication parameters
|
||||
#define DEFAULT_CPARAMS_RATE 44100
|
||||
#define DEFAULT_CPARAMS_VOICES 1
|
||||
|
||||
#define DEFAULT_CPARAMS_FRAG_SIZE 4096
|
||||
#define DEFAULT_CPARAMS_FRAGS_MIN 1
|
||||
#define DEFAULT_CPARAMS_FRAGS_MAX 1
|
||||
|
||||
#define QSA_MAX_NAME_LENGTH 81+16 // Hardcoded in QSA, can't be changed
|
||||
|
||||
static bool QSA_SetError(const char *fn, int status)
|
||||
{
|
||||
return SDL_SetError("QSA: %s() failed: %s", fn, snd_strerror(status));
|
||||
}
|
||||
|
||||
// !!! FIXME: does this need to be here? Does the SDL version not work?
|
||||
static void QSA_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
// Increase default 10 priority to 25 to avoid jerky sound
|
||||
struct sched_param param;
|
||||
if (SchedGet(0, 0, ¶m) != -1) {
|
||||
param.sched_priority = param.sched_curpriority + 15;
|
||||
SchedSet(0, 0, SCHED_NOCHANGE, ¶m);
|
||||
}
|
||||
}
|
||||
|
||||
// PCM channel parameters initialize function
|
||||
static void QSA_InitAudioParams(snd_pcm_channel_params_t * cpars)
|
||||
{
|
||||
SDL_zerop(cpars);
|
||||
cpars->channel = SND_PCM_CHANNEL_PLAYBACK;
|
||||
cpars->mode = SND_PCM_MODE_BLOCK;
|
||||
cpars->start_mode = SND_PCM_START_DATA;
|
||||
cpars->stop_mode = SND_PCM_STOP_STOP;
|
||||
cpars->format.format = SND_PCM_SFMT_S16_LE;
|
||||
cpars->format.interleave = 1;
|
||||
cpars->format.rate = DEFAULT_CPARAMS_RATE;
|
||||
cpars->format.voices = DEFAULT_CPARAMS_VOICES;
|
||||
cpars->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE;
|
||||
cpars->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN;
|
||||
cpars->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX;
|
||||
}
|
||||
|
||||
// This function waits until it is possible to write a full sound buffer
|
||||
static bool QSA_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Setup timeout for playing one fragment equal to 2 seconds
|
||||
// If timeout occurred then something wrong with hardware or driver
|
||||
// For example, Vortex 8820 audio driver hangs on second DAC because
|
||||
// it doesn't exist !
|
||||
const int result = SDL_IOReady(device->hidden->audio_fd,
|
||||
device->recording ? SDL_IOR_READ : SDL_IOR_WRITE,
|
||||
2 * 1000);
|
||||
switch (result) {
|
||||
case -1:
|
||||
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "QSA: SDL_IOReady() failed: %s", strerror(errno));
|
||||
return false;
|
||||
case 0:
|
||||
device->hidden->timeout_on_wait = true; // !!! FIXME: Should we just disconnect the device in this case?
|
||||
break;
|
||||
default:
|
||||
device->hidden->timeout_on_wait = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
if (SDL_GetAtomicInt(&device->shutdown) || !device->hidden) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int towrite = buflen;
|
||||
|
||||
// Write the audio data, checking for EAGAIN (buffer full) and underrun
|
||||
while ((towrite > 0) && !SDL_GetAtomicInt(&device->shutdown));
|
||||
const int bw = snd_pcm_plugin_write(device->hidden->audio_handle, buffer, towrite);
|
||||
if (bw != towrite) {
|
||||
// Check if samples playback got stuck somewhere in hardware or in the audio device driver
|
||||
if ((errno == EAGAIN) && (bw == 0)) {
|
||||
if (device->hidden->timeout_on_wait) {
|
||||
return true; // oh well, try again next time. !!! FIXME: Should we just disconnect the device in this case?
|
||||
}
|
||||
}
|
||||
|
||||
// Check for errors or conditions
|
||||
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
|
||||
SDL_Delay(1); // Let a little CPU time go by and try to write again
|
||||
|
||||
// if we wrote some data
|
||||
towrite -= bw;
|
||||
buffer += bw * device->spec.channels;
|
||||
continue;
|
||||
} else if ((errno == EINVAL) || (errno == EIO)) {
|
||||
snd_pcm_channel_status_t cstatus;
|
||||
SDL_zero(cstatus);
|
||||
cstatus.channel = device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
|
||||
|
||||
int status = snd_pcm_plugin_status(device->hidden->audio_handle, &cstatus);
|
||||
if (status < 0) {
|
||||
QSA_SetError("snd_pcm_plugin_status", status);
|
||||
return false;
|
||||
} else if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY)) {
|
||||
status = snd_pcm_plugin_prepare(device->hidden->audio_handle, device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
|
||||
if (status < 0) {
|
||||
QSA_SetError("snd_pcm_plugin_prepare", status);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// we wrote all remaining data
|
||||
towrite -= bw;
|
||||
buffer += bw * device->spec.channels;
|
||||
}
|
||||
}
|
||||
|
||||
// If we couldn't write, assume fatal error for now
|
||||
return (towrite == 0);
|
||||
}
|
||||
|
||||
static Uint8 *QSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->pcm_buf;
|
||||
}
|
||||
|
||||
static void QSA_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->audio_handle) {
|
||||
#if _NTO_VERSION < 710
|
||||
// Finish playing available samples or cancel unread samples during recording
|
||||
snd_pcm_plugin_flush(device->hidden->audio_handle, device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
|
||||
#endif
|
||||
snd_pcm_close(device->hidden->audio_handle);
|
||||
}
|
||||
|
||||
SDL_free(device->hidden->pcm_buf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool QSA_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->recording) {
|
||||
return SDL_SetError("SDL recording support isn't available on QNX atm"); // !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in!
|
||||
}
|
||||
|
||||
SDL_assert(device->handle != NULL); // NULL used to mean "system default device" in SDL2; it does not mean that in SDL3.
|
||||
const Uint32 sdlhandle = (Uint32) ((size_t) device->handle);
|
||||
const uint32_t cardno = (uint32_t) (sdlhandle & 0xFFFF);
|
||||
const uint32_t deviceno = (uint32_t) ((sdlhandle >> 16) & 0xFFFF);
|
||||
const bool recording = device->recording;
|
||||
int status = 0;
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, (sizeof (struct SDL_PrivateAudioData)));
|
||||
if (device->hidden == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize channel transfer parameters to default
|
||||
snd_pcm_channel_params_t cparams;
|
||||
QSA_InitAudioParams(&cparams);
|
||||
|
||||
// Open requested audio device
|
||||
status = snd_pcm_open(&device->hidden->audio_handle, cardno, deviceno, recording ? SND_PCM_OPEN_CAPTURE : SND_PCM_OPEN_PLAYBACK);
|
||||
if (status < 0) {
|
||||
device->hidden->audio_handle = NULL;
|
||||
return QSA_SetError("snd_pcm_open", status);
|
||||
}
|
||||
|
||||
// Try for a closest match on audio format
|
||||
SDL_AudioFormat test_format = 0;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
// if match found set format to equivalent QSA format
|
||||
switch (test_format) {
|
||||
#define CHECKFMT(sdlfmt, qsafmt) case SDL_AUDIO_##sdlfmt: cparams.format.format = SND_PCM_SFMT_##qsafmt; break
|
||||
CHECKFMT(U8, U8);
|
||||
CHECKFMT(S8, S8);
|
||||
CHECKFMT(S16LSB, S16_LE);
|
||||
CHECKFMT(S16MSB, S16_BE);
|
||||
CHECKFMT(S32LSB, S32_LE);
|
||||
CHECKFMT(S32MSB, S32_BE);
|
||||
CHECKFMT(F32LSB, FLOAT_LE);
|
||||
CHECKFMT(F32MSB, FLOAT_BE);
|
||||
#undef CHECKFMT
|
||||
default: continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// assumes test_format not 0 on success
|
||||
if (test_format == 0) {
|
||||
return SDL_SetError("QSA: Couldn't find any hardware audio formats");
|
||||
}
|
||||
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Set mono/stereo/4ch/6ch/8ch audio
|
||||
cparams.format.voices = device->spec.channels;
|
||||
|
||||
// Set rate
|
||||
cparams.format.rate = device->spec.freq;
|
||||
|
||||
// Setup the transfer parameters according to cparams
|
||||
status = snd_pcm_plugin_params(device->hidden->audio_handle, &cparams);
|
||||
if (status < 0) {
|
||||
return QSA_SetError("snd_pcm_plugin_params", status);
|
||||
}
|
||||
|
||||
// Make sure channel is setup right one last time
|
||||
snd_pcm_channel_setup_t csetup;
|
||||
SDL_zero(csetup);
|
||||
csetup.channel = recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
|
||||
if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) {
|
||||
return SDL_SetError("QSA: Unable to setup channel");
|
||||
}
|
||||
|
||||
device->sample_frames = csetup.buf.block.frag_size;
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
device->hidden->pcm_buf = (Uint8 *) SDL_malloc(device->buffer_size);
|
||||
if (device->hidden->pcm_buf == NULL) {
|
||||
return false;
|
||||
}
|
||||
SDL_memset(device->hidden->pcm_buf, device->silence_value, device->buffer_size);
|
||||
|
||||
// get the file descriptor
|
||||
device->hidden->audio_fd = snd_pcm_file_descriptor(device->hidden->audio_handle, csetup.channel);
|
||||
if (device->hidden->audio_fd < 0) {
|
||||
return QSA_SetError("snd_pcm_file_descriptor", device->hidden->audio_fd);
|
||||
}
|
||||
|
||||
// Prepare an audio channel
|
||||
status = snd_pcm_plugin_prepare(device->hidden->audio_handle, csetup.channel)
|
||||
if (status < 0) {
|
||||
return QSA_SetError("snd_pcm_plugin_prepare", status);
|
||||
}
|
||||
|
||||
return true; // We're really ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static SDL_AudioFormat QnxFormatToSDLFormat(const int32_t qnxfmt)
|
||||
{
|
||||
switch (qnxfmt) {
|
||||
#define CHECKFMT(sdlfmt, qsafmt) case SND_PCM_SFMT_##qsafmt: return SDL_AUDIO_##sdlfmt
|
||||
CHECKFMT(U8, U8);
|
||||
CHECKFMT(S8, S8);
|
||||
CHECKFMT(S16LSB, S16_LE);
|
||||
CHECKFMT(S16MSB, S16_BE);
|
||||
CHECKFMT(S32LSB, S32_LE);
|
||||
CHECKFMT(S32MSB, S32_BE);
|
||||
CHECKFMT(F32LSB, FLOAT_LE);
|
||||
CHECKFMT(F32MSB, FLOAT_BE);
|
||||
#undef CHECKFMT
|
||||
default: break;
|
||||
}
|
||||
return SDL_AUDIO_S16; // oh well.
|
||||
}
|
||||
|
||||
static void QSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
// Detect amount of available devices
|
||||
// this value can be changed in the runtime
|
||||
int num_cards = 0;
|
||||
(void) snd_cards_list(NULL, 0, &alloc_num_cards);
|
||||
bool isstack = false;
|
||||
int *cards = SDL_small_alloc(int, num_cards, &isstack);
|
||||
if (!cards) {
|
||||
return; // we're in trouble.
|
||||
}
|
||||
int overflow_cards = 0;
|
||||
const int total_num_cards = snd_cards_list(cards, num_cards, &overflow_cards);
|
||||
// if overflow_cards > 0 or total_num_cards > num_cards, it changed at the last moment; oh well, we lost some.
|
||||
num_cards = SDL_min(num_cards, total_num_cards); // ...but make sure it didn't _shrink_.
|
||||
|
||||
// If io-audio manager is not running we will get 0 as number of available audio devices
|
||||
if (num_cards == 0) { // not any available audio devices?
|
||||
SDL_small_free(cards, isstack);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find requested devices by type
|
||||
for (int it = 0; it < num_cards; it++) {
|
||||
const int card = cards[it];
|
||||
for (uint32_t deviceno = 0; ; deviceno++) {
|
||||
int32_t status;
|
||||
char name[QSA_MAX_NAME_LENGTH];
|
||||
|
||||
status = snd_card_get_longname(card, name, sizeof (name));
|
||||
if (status == EOK) {
|
||||
snd_pcm_t *handle;
|
||||
|
||||
// Add device number to device name
|
||||
char fullname[QSA_MAX_NAME_LENGTH + 32];
|
||||
SDL_snprintf(fullname, sizeof (fullname), "%s d%d", name, (int) deviceno);
|
||||
|
||||
// Check if this device id could play anything
|
||||
bool recording = false;
|
||||
status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_PLAYBACK);
|
||||
if (status != EOK) { // no? See if it's a recording device instead.
|
||||
#if 0 // !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in!
|
||||
status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_CAPTURE);
|
||||
if (status == EOK) {
|
||||
recording = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (status == EOK) {
|
||||
SDL_AudioSpec spec;
|
||||
SDL_zero(spec);
|
||||
SDL_AudioSpec *pspec = &spec;
|
||||
snd_pcm_channel_setup_t csetup;
|
||||
SDL_zero(csetup);
|
||||
csetup.channel = recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
|
||||
|
||||
if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) {
|
||||
pspec = NULL; // go on without spec info.
|
||||
} else {
|
||||
spec.format = QnxFormatToSDLFormat(csetup.format.format);
|
||||
spec.channels = csetup.format.channels;
|
||||
spec.freq = csetup.format.rate;
|
||||
}
|
||||
|
||||
status = snd_pcm_close(handle);
|
||||
if (status == EOK) {
|
||||
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
|
||||
SDL_assert(card <= 0xFFFF);
|
||||
SDL_assert(deviceno <= 0xFFFF);
|
||||
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
|
||||
SDL_AddAudioDevice(recording, fullname, pspec, (void *) ((size_t) sdlhandle));
|
||||
}
|
||||
} else {
|
||||
// Check if we got end of devices list
|
||||
if (status == -ENOENT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_small_free(cards, isstack);
|
||||
|
||||
// Try to open the "preferred" devices, which will tell us the card/device pairs for the default devices.
|
||||
snd_pcm_t handle;
|
||||
int cardno, deviceno;
|
||||
if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_PLAYBACK) == 0) {
|
||||
snd_pcm_close(handle);
|
||||
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
|
||||
SDL_assert(cardno <= 0xFFFF);
|
||||
SDL_assert(deviceno <= 0xFFFF);
|
||||
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
|
||||
*default_playback = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
|
||||
}
|
||||
|
||||
if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_CAPTURE) == 0) {
|
||||
snd_pcm_close(handle);
|
||||
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
|
||||
SDL_assert(cardno <= 0xFFFF);
|
||||
SDL_assert(deviceno <= 0xFFFF);
|
||||
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
|
||||
*default_recording = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
|
||||
}
|
||||
}
|
||||
|
||||
static void QSA_Deinitialize(void)
|
||||
{
|
||||
// nothing to do here atm.
|
||||
}
|
||||
|
||||
static bool QSA_Init(SDL_AudioDriverImpl * impl)
|
||||
{
|
||||
impl->DetectDevices = QSA_DetectDevices;
|
||||
impl->OpenDevice = QSA_OpenDevice;
|
||||
impl->ThreadInit = QSA_ThreadInit;
|
||||
impl->WaitDevice = QSA_WaitDevice;
|
||||
impl->PlayDevice = QSA_PlayDevice;
|
||||
impl->GetDeviceBuf = QSA_GetDeviceBuf;
|
||||
impl->CloseDevice = QSA_CloseDevice;
|
||||
impl->Deinitialize = QSA_Deinitialize;
|
||||
|
||||
// !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in!
|
||||
//impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap QSAAUDIO_bootstrap = {
|
||||
"qsa", "QNX QSA Audio", QSA_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_QNX
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "../../SDL_internal.h"
|
||||
|
||||
#ifndef __SDL_QSA_AUDIO_H__
|
||||
#define __SDL_QSA_AUDIO_H__
|
||||
|
||||
#include <sys/asoundlib.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
snd_pcm_t *audio_handle; // The audio device handle
|
||||
int audio_fd; // The audio file descriptor, for selecting on
|
||||
bool timeout_on_wait; // Select timeout status
|
||||
Uint8 *pcm_buf; // Raw mixing buffer
|
||||
};
|
||||
|
||||
#endif // __SDL_QSA_AUDIO_H__
|
||||
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_SNDIO
|
||||
|
||||
// OpenBSD sndio target
|
||||
|
||||
#ifdef HAVE_STDIO_H
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SIGNAL_H
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_sndioaudio.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
#endif
|
||||
|
||||
#ifndef INFTIM
|
||||
#define INFTIM -1
|
||||
#endif
|
||||
|
||||
#ifndef SIO_DEVANY
|
||||
#define SIO_DEVANY "default"
|
||||
#endif
|
||||
|
||||
static struct sio_hdl *(*SNDIO_sio_open)(const char *, unsigned int, int);
|
||||
static void (*SNDIO_sio_close)(struct sio_hdl *);
|
||||
static int (*SNDIO_sio_setpar)(struct sio_hdl *, struct sio_par *);
|
||||
static int (*SNDIO_sio_getpar)(struct sio_hdl *, struct sio_par *);
|
||||
static int (*SNDIO_sio_start)(struct sio_hdl *);
|
||||
static int (*SNDIO_sio_stop)(struct sio_hdl *);
|
||||
static size_t (*SNDIO_sio_read)(struct sio_hdl *, void *, size_t);
|
||||
static size_t (*SNDIO_sio_write)(struct sio_hdl *, const void *, size_t);
|
||||
static int (*SNDIO_sio_nfds)(struct sio_hdl *);
|
||||
static int (*SNDIO_sio_pollfd)(struct sio_hdl *, struct pollfd *, int);
|
||||
static int (*SNDIO_sio_revents)(struct sio_hdl *, struct pollfd *);
|
||||
static int (*SNDIO_sio_eof)(struct sio_hdl *);
|
||||
static void (*SNDIO_sio_initpar)(struct sio_par *);
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
static const char *sndio_library = SDL_AUDIO_DRIVER_SNDIO_DYNAMIC;
|
||||
static SDL_SharedObject *sndio_handle = NULL;
|
||||
|
||||
static bool load_sndio_sym(const char *fn, void **addr)
|
||||
{
|
||||
*addr = SDL_LoadFunction(sndio_handle, fn);
|
||||
if (!*addr) {
|
||||
return false; // Don't call SDL_SetError(): SDL_LoadFunction already did.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// cast funcs to char* first, to please GCC's strict aliasing rules.
|
||||
#define SDL_SNDIO_SYM(x) \
|
||||
if (!load_sndio_sym(#x, (void **)(char *)&SNDIO_##x)) \
|
||||
return false
|
||||
#else
|
||||
#define SDL_SNDIO_SYM(x) SNDIO_##x = x
|
||||
#endif
|
||||
|
||||
static bool load_sndio_syms(void)
|
||||
{
|
||||
SDL_SNDIO_SYM(sio_open);
|
||||
SDL_SNDIO_SYM(sio_close);
|
||||
SDL_SNDIO_SYM(sio_setpar);
|
||||
SDL_SNDIO_SYM(sio_getpar);
|
||||
SDL_SNDIO_SYM(sio_start);
|
||||
SDL_SNDIO_SYM(sio_stop);
|
||||
SDL_SNDIO_SYM(sio_read);
|
||||
SDL_SNDIO_SYM(sio_write);
|
||||
SDL_SNDIO_SYM(sio_nfds);
|
||||
SDL_SNDIO_SYM(sio_pollfd);
|
||||
SDL_SNDIO_SYM(sio_revents);
|
||||
SDL_SNDIO_SYM(sio_eof);
|
||||
SDL_SNDIO_SYM(sio_initpar);
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef SDL_SNDIO_SYM
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
|
||||
SDL_ELF_NOTE_DLOPEN(
|
||||
"audio-libsndio",
|
||||
"Support for audio through libsndio",
|
||||
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
|
||||
SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
)
|
||||
|
||||
static void UnloadSNDIOLibrary(void)
|
||||
{
|
||||
if (sndio_handle) {
|
||||
SDL_UnloadObject(sndio_handle);
|
||||
sndio_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool LoadSNDIOLibrary(void)
|
||||
{
|
||||
bool result = true;
|
||||
if (!sndio_handle) {
|
||||
sndio_handle = SDL_LoadObject(sndio_library);
|
||||
if (!sndio_handle) {
|
||||
result = false; // Don't call SDL_SetError(): SDL_LoadObject already did.
|
||||
} else {
|
||||
result = load_sndio_syms();
|
||||
if (!result) {
|
||||
UnloadSNDIOLibrary();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void UnloadSNDIOLibrary(void)
|
||||
{
|
||||
}
|
||||
|
||||
static bool LoadSNDIOLibrary(void)
|
||||
{
|
||||
load_sndio_syms();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
|
||||
static bool SNDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const bool recording = device->recording;
|
||||
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
if (SNDIO_sio_eof(device->hidden->dev)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int nfds = SNDIO_sio_pollfd(device->hidden->dev, device->hidden->pfd, recording ? POLLIN : POLLOUT);
|
||||
if (nfds <= 0 || poll(device->hidden->pfd, nfds, 10) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int revents = SNDIO_sio_revents(device->hidden->dev, device->hidden->pfd);
|
||||
if (recording && (revents & POLLIN)) {
|
||||
break;
|
||||
} else if (!recording && (revents & POLLOUT)) {
|
||||
break;
|
||||
} else if (revents & POLLHUP) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SNDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
// !!! FIXME: this should be non-blocking so we can check device->shutdown.
|
||||
// this is set to blocking, because we _have_ to send the entire buffer down, but hopefully WaitDevice took most of the delay time.
|
||||
if (SNDIO_sio_write(device->hidden->dev, buffer, buflen) != buflen) {
|
||||
return false; // If we couldn't write, assume fatal error for now
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Wrote %d bytes of audio data\n", written);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static int SNDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
// We set recording devices non-blocking; this can safely return 0 in SDL3, but we'll check for EOF to cause a device disconnect.
|
||||
const size_t br = SNDIO_sio_read(device->hidden->dev, buffer, buflen);
|
||||
if ((br == 0) && SNDIO_sio_eof(device->hidden->dev)) {
|
||||
return -1;
|
||||
}
|
||||
return (int) br;
|
||||
}
|
||||
|
||||
static void SNDIO_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
char buf[512];
|
||||
while (!SDL_GetAtomicInt(&device->shutdown) && (SNDIO_sio_read(device->hidden->dev, buf, sizeof(buf)) > 0)) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
static Uint8 *SNDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static void SNDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->dev) {
|
||||
SNDIO_sio_stop(device->hidden->dev);
|
||||
SNDIO_sio_close(device->hidden->dev);
|
||||
}
|
||||
SDL_free(device->hidden->pfd);
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool SNDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recording devices must be non-blocking for SNDIO_FlushRecording
|
||||
device->hidden->dev = SNDIO_sio_open(SIO_DEVANY,
|
||||
device->recording ? SIO_REC : SIO_PLAY, device->recording);
|
||||
if (!device->hidden->dev) {
|
||||
return SDL_SetError("sio_open() failed");
|
||||
}
|
||||
|
||||
device->hidden->pfd = SDL_malloc(sizeof(struct pollfd) * SNDIO_sio_nfds(device->hidden->dev));
|
||||
if (!device->hidden->pfd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sio_par par;
|
||||
SNDIO_sio_initpar(&par);
|
||||
|
||||
par.rate = device->spec.freq;
|
||||
par.pchan = device->spec.channels;
|
||||
par.round = device->sample_frames;
|
||||
par.appbufsz = par.round * 2;
|
||||
|
||||
// Try for a closest match on audio format
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (!SDL_AUDIO_ISFLOAT(test_format)) {
|
||||
par.le = SDL_AUDIO_ISLITTLEENDIAN(test_format) ? 1 : 0;
|
||||
par.sig = SDL_AUDIO_ISSIGNED(test_format) ? 1 : 0;
|
||||
par.bits = SDL_AUDIO_BITSIZE(test_format);
|
||||
|
||||
if (SNDIO_sio_setpar(device->hidden->dev, &par) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (SNDIO_sio_getpar(device->hidden->dev, &par) == 0) {
|
||||
return SDL_SetError("sio_getpar() failed");
|
||||
}
|
||||
if (par.bps != SIO_BPS(par.bits)) {
|
||||
continue;
|
||||
}
|
||||
if ((par.bits == 8 * par.bps) || (par.msb)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("sndio: Unsupported audio format");
|
||||
}
|
||||
|
||||
if ((par.bps == 4) && (par.sig) && (par.le)) {
|
||||
device->spec.format = SDL_AUDIO_S32LE;
|
||||
} else if ((par.bps == 4) && (par.sig) && (!par.le)) {
|
||||
device->spec.format = SDL_AUDIO_S32BE;
|
||||
} else if ((par.bps == 2) && (par.sig) && (par.le)) {
|
||||
device->spec.format = SDL_AUDIO_S16LE;
|
||||
} else if ((par.bps == 2) && (par.sig) && (!par.le)) {
|
||||
device->spec.format = SDL_AUDIO_S16BE;
|
||||
} else if ((par.bps == 1) && (par.sig)) {
|
||||
device->spec.format = SDL_AUDIO_S8;
|
||||
} else if ((par.bps == 1) && (!par.sig)) {
|
||||
device->spec.format = SDL_AUDIO_U8;
|
||||
} else {
|
||||
return SDL_SetError("sndio: Got unsupported hardware audio format.");
|
||||
}
|
||||
|
||||
device->spec.freq = par.rate;
|
||||
device->spec.channels = par.pchan;
|
||||
device->sample_frames = par.round;
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
// Allocate mixing buffer
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
|
||||
if (!SNDIO_sio_start(device->hidden->dev)) {
|
||||
return SDL_SetError("sio_start() failed");
|
||||
}
|
||||
|
||||
return true; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static void SNDIO_Deinitialize(void)
|
||||
{
|
||||
UnloadSNDIOLibrary();
|
||||
}
|
||||
|
||||
static void SNDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
*default_playback = SDL_AddAudioDevice(false, DEFAULT_PLAYBACK_DEVNAME, NULL, (void *)0x1);
|
||||
*default_recording = SDL_AddAudioDevice(true, DEFAULT_RECORDING_DEVNAME, NULL, (void *)0x2);
|
||||
}
|
||||
|
||||
static bool SNDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!LoadSNDIOLibrary()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
impl->OpenDevice = SNDIO_OpenDevice;
|
||||
impl->WaitDevice = SNDIO_WaitDevice;
|
||||
impl->PlayDevice = SNDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = SNDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = SNDIO_CloseDevice;
|
||||
impl->WaitRecordingDevice = SNDIO_WaitDevice;
|
||||
impl->RecordDevice = SNDIO_RecordDevice;
|
||||
impl->FlushRecording = SNDIO_FlushRecording;
|
||||
impl->Deinitialize = SNDIO_Deinitialize;
|
||||
impl->DetectDevices = SNDIO_DetectDevices;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap SNDIO_bootstrap = {
|
||||
"sndio", "OpenBSD sndio", SNDIO_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_SNDIO
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_VITA
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../SDL_audiodev_c.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_vitaaudio.h"
|
||||
|
||||
#include <psp2/kernel/threadmgr.h>
|
||||
#include <psp2/audioout.h>
|
||||
#include <psp2/audioin.h>
|
||||
|
||||
#define SCE_AUDIO_SAMPLE_ALIGN(s) (((s) + 63) & ~63)
|
||||
#define SCE_AUDIO_MAX_VOLUME 0x8000
|
||||
|
||||
static bool VITAAUD_OpenRecordingDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->spec.freq = 16000;
|
||||
device->spec.channels = 1;
|
||||
device->sample_frames = 512;
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
device->hidden->port = sceAudioInOpenPort(SCE_AUDIO_IN_PORT_TYPE_VOICE, 512, 16000, SCE_AUDIO_IN_PARAM_FORMAT_S16_MONO);
|
||||
|
||||
if (device->hidden->port < 0) {
|
||||
return SDL_SetError("Couldn't open audio in port: %x", device->hidden->port);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool VITAAUD_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
int format, mixlen, i, port = SCE_AUDIO_OUT_PORT_TYPE_MAIN;
|
||||
int vols[2] = { SCE_AUDIO_MAX_VOLUME, SCE_AUDIO_MAX_VOLUME };
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts;
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *)
|
||||
SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (test_format == SDL_AUDIO_S16LE) {
|
||||
device->spec.format = test_format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("Unsupported audio format");
|
||||
}
|
||||
|
||||
if (device->recording) {
|
||||
return VITAAUD_OpenRecordingDevice(device);
|
||||
}
|
||||
|
||||
// The sample count must be a multiple of 64.
|
||||
device->sample_frames = SCE_AUDIO_SAMPLE_ALIGN(device->sample_frames);
|
||||
|
||||
// Update the fragment size as size in bytes.
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
/* Allocate the mixing buffer. Its size and starting address must
|
||||
be a multiple of 64 bytes. Our sample count is already a multiple of
|
||||
64, so spec->size should be a multiple of 64 as well. */
|
||||
mixlen = device->buffer_size * NUM_BUFFERS;
|
||||
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
|
||||
if (!device->hidden->rawbuf) {
|
||||
return SDL_SetError("Couldn't allocate mixing buffer");
|
||||
}
|
||||
|
||||
// Setup the hardware channel.
|
||||
if (device->spec.channels == 1) {
|
||||
format = SCE_AUDIO_OUT_MODE_MONO;
|
||||
} else {
|
||||
format = SCE_AUDIO_OUT_MODE_STEREO;
|
||||
}
|
||||
|
||||
// the main port requires 48000Hz audio, so this drops to the background music port if necessary
|
||||
if (device->spec.freq < 48000) {
|
||||
port = SCE_AUDIO_OUT_PORT_TYPE_BGM;
|
||||
}
|
||||
|
||||
device->hidden->port = sceAudioOutOpenPort(port, device->sample_frames, device->spec.freq, format);
|
||||
if (device->hidden->port < 0) {
|
||||
SDL_aligned_free(device->hidden->rawbuf);
|
||||
device->hidden->rawbuf = NULL;
|
||||
return SDL_SetError("Couldn't open audio out port: %x", device->hidden->port);
|
||||
}
|
||||
|
||||
sceAudioOutSetVolume(device->hidden->port, SCE_AUDIO_VOLUME_FLAG_L_CH | SCE_AUDIO_VOLUME_FLAG_R_CH, vols);
|
||||
|
||||
SDL_memset(device->hidden->rawbuf, 0, mixlen);
|
||||
for (i = 0; i < NUM_BUFFERS; i++) {
|
||||
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
|
||||
}
|
||||
|
||||
device->hidden->next_buffer = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
// sceAudioOutOutput returns amount of samples queued or < 0 on error
|
||||
return (sceAudioOutOutput(device->hidden->port, buffer) >= 0);
|
||||
}
|
||||
|
||||
// This function waits until it is possible to write a full sound buffer
|
||||
static bool VITAAUD_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// !!! FIXME: we might just need to sleep roughly as long as playback buffers take to process, based on sample rate, etc.
|
||||
while (!SDL_GetAtomicInt(&device->shutdown) && (sceAudioOutGetRestSample(device->hidden->port) >= device->buffer_size)) {
|
||||
SDL_Delay(1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
Uint8 *result = device->hidden->mixbufs[device->hidden->next_buffer];
|
||||
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void VITAAUD_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->port >= 0) {
|
||||
if (device->recording) {
|
||||
sceAudioInReleasePort(device->hidden->port);
|
||||
} else {
|
||||
sceAudioOutReleasePort(device->hidden->port);
|
||||
}
|
||||
device->hidden->port = -1;
|
||||
}
|
||||
|
||||
if (!device->recording && device->hidden->rawbuf) {
|
||||
SDL_aligned_free(device->hidden->rawbuf); // this uses SDL_aligned_alloc(), not SDL_malloc()
|
||||
device->hidden->rawbuf = NULL;
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool VITAAUD_WaitRecordingDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// there's only a blocking call to obtain more data, so we'll just sleep as
|
||||
// long as a buffer would run.
|
||||
const Uint64 endticks = SDL_GetTicks() + ((device->sample_frames * 1000) / device->spec.freq);
|
||||
while (!SDL_GetAtomicInt(&device->shutdown) && (SDL_GetTicks() < endticks)) {
|
||||
SDL_Delay(1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int VITAAUD_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
int ret;
|
||||
SDL_assert(buflen == device->buffer_size);
|
||||
ret = sceAudioInInput(device->hidden->port, buffer);
|
||||
if (ret < 0) {
|
||||
SDL_SetError("Failed to record from device: %x", ret);
|
||||
return -1;
|
||||
}
|
||||
return device->buffer_size;
|
||||
}
|
||||
|
||||
static void VITAAUD_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
// just grab the latest and dump it.
|
||||
sceAudioInInput(device->hidden->port, device->work_buffer);
|
||||
}
|
||||
|
||||
static void VITAAUD_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
// Increase the priority of this audio thread by 1 to put it ahead of other SDL threads.
|
||||
SceUID thid;
|
||||
SceKernelThreadInfo info;
|
||||
thid = sceKernelGetThreadId();
|
||||
info.size = sizeof(SceKernelThreadInfo);
|
||||
if (sceKernelGetThreadInfo(thid, &info) == 0) {
|
||||
sceKernelChangeThreadPriority(thid, info.currentPriority - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static bool VITAAUD_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = VITAAUD_OpenDevice;
|
||||
impl->PlayDevice = VITAAUD_PlayDevice;
|
||||
impl->WaitDevice = VITAAUD_WaitDevice;
|
||||
impl->GetDeviceBuf = VITAAUD_GetDeviceBuf;
|
||||
impl->CloseDevice = VITAAUD_CloseDevice;
|
||||
impl->ThreadInit = VITAAUD_ThreadInit;
|
||||
impl->WaitRecordingDevice = VITAAUD_WaitRecordingDevice;
|
||||
impl->FlushRecording = VITAAUD_FlushRecording;
|
||||
impl->RecordDevice = VITAAUD_RecordDevice;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
impl->OnlyHasDefaultRecordingDevice = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap VITAAUD_bootstrap = {
|
||||
"vita", "VITA audio driver", VITAAUD_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_VITA
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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
|
||||
+997
@@ -0,0 +1,997 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_WASAPI
|
||||
|
||||
#include "../../core/windows/SDL_windows.h"
|
||||
#include "../../core/windows/SDL_immdevice.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#define COBJMACROS
|
||||
#include <audioclient.h>
|
||||
|
||||
#include "SDL_wasapi.h"
|
||||
|
||||
// These constants aren't available in older SDKs
|
||||
#ifndef AUDCLNT_STREAMFLAGS_RATEADJUST
|
||||
#define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000
|
||||
#endif
|
||||
#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
|
||||
#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
|
||||
#endif
|
||||
#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
|
||||
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
|
||||
#endif
|
||||
|
||||
// handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency).
|
||||
static HMODULE libavrt = NULL;
|
||||
typedef HANDLE (WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
|
||||
typedef BOOL (WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
|
||||
static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL;
|
||||
static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL;
|
||||
|
||||
// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
|
||||
static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
|
||||
static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } };
|
||||
static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
|
||||
#ifdef __IAudioClient2_INTERFACE_DEFINED__
|
||||
static const IID SDL_IID_IAudioClient2 = { 0x726778cd, 0xf60a, 0x4EDA, { 0x82, 0xde, 0xe4, 0x76, 0x10, 0xcd, 0x78, 0xaa } };
|
||||
#endif //
|
||||
#ifdef __IAudioClient3_INTERFACE_DEFINED__
|
||||
static const IID SDL_IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4, { 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } };
|
||||
#endif //
|
||||
|
||||
static bool immdevice_initialized = false;
|
||||
static bool supports_recording_on_playback_devices = false;
|
||||
|
||||
// WASAPI is _really_ particular about various things happening on the same thread, for COM and such,
|
||||
// so we proxy various stuff to a single background thread to manage.
|
||||
|
||||
typedef struct ManagementThreadPendingTask
|
||||
{
|
||||
ManagementThreadTask fn;
|
||||
void *userdata;
|
||||
bool result;
|
||||
SDL_Semaphore *task_complete_sem;
|
||||
char *errorstr;
|
||||
struct ManagementThreadPendingTask *next;
|
||||
} ManagementThreadPendingTask;
|
||||
|
||||
static SDL_Thread *ManagementThread = NULL;
|
||||
static ManagementThreadPendingTask *ManagementThreadPendingTasks = NULL;
|
||||
static SDL_Mutex *ManagementThreadLock = NULL;
|
||||
static SDL_Condition *ManagementThreadCondition = NULL;
|
||||
static SDL_AtomicInt ManagementThreadShutdown;
|
||||
|
||||
static void ManagementThreadMainloop(void)
|
||||
{
|
||||
SDL_LockMutex(ManagementThreadLock);
|
||||
ManagementThreadPendingTask *task;
|
||||
while (((task = (ManagementThreadPendingTask *)SDL_GetAtomicPointer((void **)&ManagementThreadPendingTasks)) != NULL) || !SDL_GetAtomicInt(&ManagementThreadShutdown)) {
|
||||
if (!task) {
|
||||
SDL_WaitCondition(ManagementThreadCondition, ManagementThreadLock); // block until there's something to do.
|
||||
} else {
|
||||
SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, task->next); // take task off the pending list.
|
||||
SDL_UnlockMutex(ManagementThreadLock); // let other things add to the list while we chew on this task.
|
||||
task->result = task->fn(task->userdata); // run this task.
|
||||
if (task->task_complete_sem) { // something waiting on result?
|
||||
task->errorstr = SDL_strdup(SDL_GetError());
|
||||
SDL_SignalSemaphore(task->task_complete_sem);
|
||||
} else { // nothing waiting, we're done, free it.
|
||||
SDL_free(task);
|
||||
}
|
||||
SDL_LockMutex(ManagementThreadLock); // regrab the lock so we can get the next task; if nothing to do, we'll release the lock in SDL_WaitCondition.
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(ManagementThreadLock); // told to shut down and out of tasks, let go of the lock and return.
|
||||
}
|
||||
|
||||
bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, bool *wait_on_result)
|
||||
{
|
||||
// We want to block for a result, but we are already running from the management thread! Just run the task now so we don't deadlock.
|
||||
if ((wait_on_result) && (SDL_GetCurrentThreadID() == SDL_GetThreadID(ManagementThread))) {
|
||||
*wait_on_result = task(userdata);
|
||||
return true; // completed!
|
||||
}
|
||||
|
||||
if (SDL_GetAtomicInt(&ManagementThreadShutdown)) {
|
||||
return SDL_SetError("Can't add task, we're shutting down");
|
||||
}
|
||||
|
||||
ManagementThreadPendingTask *pending = (ManagementThreadPendingTask *)SDL_calloc(1, sizeof(ManagementThreadPendingTask));
|
||||
if (!pending) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pending->fn = task;
|
||||
pending->userdata = userdata;
|
||||
|
||||
if (wait_on_result) {
|
||||
pending->task_complete_sem = SDL_CreateSemaphore(0);
|
||||
if (!pending->task_complete_sem) {
|
||||
SDL_free(pending);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pending->next = NULL;
|
||||
|
||||
SDL_LockMutex(ManagementThreadLock);
|
||||
|
||||
// add to end of task list.
|
||||
ManagementThreadPendingTask *prev = NULL;
|
||||
for (ManagementThreadPendingTask *i = (ManagementThreadPendingTask *)SDL_GetAtomicPointer((void **)&ManagementThreadPendingTasks); i; i = i->next) {
|
||||
prev = i;
|
||||
}
|
||||
|
||||
if (prev) {
|
||||
prev->next = pending;
|
||||
} else {
|
||||
SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, pending);
|
||||
}
|
||||
|
||||
// task is added to the end of the pending list, let management thread rip!
|
||||
SDL_SignalCondition(ManagementThreadCondition);
|
||||
SDL_UnlockMutex(ManagementThreadLock);
|
||||
|
||||
if (wait_on_result) {
|
||||
SDL_WaitSemaphore(pending->task_complete_sem);
|
||||
SDL_DestroySemaphore(pending->task_complete_sem);
|
||||
*wait_on_result = pending->result;
|
||||
if (pending->errorstr) {
|
||||
SDL_SetError("%s", pending->errorstr);
|
||||
SDL_free(pending->errorstr);
|
||||
}
|
||||
SDL_free(pending);
|
||||
}
|
||||
|
||||
return true; // successfully added (and possibly executed)!
|
||||
}
|
||||
|
||||
|
||||
static void AudioDeviceDisconnected(SDL_AudioDevice *device)
|
||||
{
|
||||
WASAPI_DisconnectDevice(device);
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_DefaultAudioDeviceChanged(void *userdata)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
|
||||
SDL_DefaultAudioDeviceChanged(device);
|
||||
UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
|
||||
return true;
|
||||
}
|
||||
|
||||
static void DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
|
||||
{
|
||||
// don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
|
||||
if (new_default_device) {
|
||||
RefPhysicalAudioDevice(new_default_device); // make sure this lives until the task completes.
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_DefaultAudioDeviceChanged, new_default_device, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void StopWasapiHotplug(void)
|
||||
{
|
||||
if (immdevice_initialized) {
|
||||
SDL_IMMDevice_Quit();
|
||||
immdevice_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void Deinit(void)
|
||||
{
|
||||
if (libavrt) {
|
||||
FreeLibrary(libavrt);
|
||||
libavrt = NULL;
|
||||
}
|
||||
|
||||
pAvSetMmThreadCharacteristicsW = NULL;
|
||||
pAvRevertMmThreadCharacteristics = NULL;
|
||||
|
||||
StopWasapiHotplug();
|
||||
|
||||
WIN_CoUninitialize();
|
||||
}
|
||||
|
||||
static bool ManagementThreadPrepare(void)
|
||||
{
|
||||
const SDL_IMMDevice_callbacks callbacks = { AudioDeviceDisconnected, DefaultAudioDeviceChanged };
|
||||
if (FAILED(WIN_CoInitialize())) {
|
||||
return SDL_SetError("CoInitialize() failed");
|
||||
} else if (!SDL_IMMDevice_Init(&callbacks)) {
|
||||
return false; // Error string is set by SDL_IMMDevice_Init
|
||||
}
|
||||
|
||||
immdevice_initialized = true;
|
||||
|
||||
libavrt = LoadLibrary(TEXT("avrt.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
|
||||
if (libavrt) {
|
||||
pAvSetMmThreadCharacteristicsW = (pfnAvSetMmThreadCharacteristicsW)GetProcAddress(libavrt, "AvSetMmThreadCharacteristicsW");
|
||||
pAvRevertMmThreadCharacteristics = (pfnAvRevertMmThreadCharacteristics)GetProcAddress(libavrt, "AvRevertMmThreadCharacteristics");
|
||||
}
|
||||
|
||||
ManagementThreadLock = SDL_CreateMutex();
|
||||
if (!ManagementThreadLock) {
|
||||
Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
ManagementThreadCondition = SDL_CreateCondition();
|
||||
if (!ManagementThreadCondition) {
|
||||
SDL_DestroyMutex(ManagementThreadLock);
|
||||
ManagementThreadLock = NULL;
|
||||
Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *errorstr;
|
||||
SDL_Semaphore *ready_sem;
|
||||
} ManagementThreadEntryData;
|
||||
|
||||
static int ManagementThreadEntry(void *userdata)
|
||||
{
|
||||
ManagementThreadEntryData *data = (ManagementThreadEntryData *)userdata;
|
||||
|
||||
if (!ManagementThreadPrepare()) {
|
||||
data->errorstr = SDL_strdup(SDL_GetError());
|
||||
SDL_SignalSemaphore(data->ready_sem); // unblock calling thread.
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_SignalSemaphore(data->ready_sem); // unblock calling thread.
|
||||
ManagementThreadMainloop();
|
||||
|
||||
Deinit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool InitManagementThread(void)
|
||||
{
|
||||
ManagementThreadEntryData mgmtdata;
|
||||
SDL_zero(mgmtdata);
|
||||
mgmtdata.ready_sem = SDL_CreateSemaphore(0);
|
||||
if (!mgmtdata.ready_sem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, NULL);
|
||||
SDL_SetAtomicInt(&ManagementThreadShutdown, 0);
|
||||
ManagementThread = SDL_CreateThreadWithStackSize(ManagementThreadEntry, "SDLWASAPIMgmt", 256 * 1024, &mgmtdata); // !!! FIXME: maybe even smaller stack size?
|
||||
if (!ManagementThread) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_WaitSemaphore(mgmtdata.ready_sem);
|
||||
SDL_DestroySemaphore(mgmtdata.ready_sem);
|
||||
|
||||
if (mgmtdata.errorstr) {
|
||||
SDL_WaitThread(ManagementThread, NULL);
|
||||
ManagementThread = NULL;
|
||||
SDL_SetError("%s", mgmtdata.errorstr);
|
||||
SDL_free(mgmtdata.errorstr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void DeinitManagementThread(void)
|
||||
{
|
||||
if (ManagementThread) {
|
||||
SDL_SetAtomicInt(&ManagementThreadShutdown, 1);
|
||||
SDL_LockMutex(ManagementThreadLock);
|
||||
SDL_SignalCondition(ManagementThreadCondition);
|
||||
SDL_UnlockMutex(ManagementThreadLock);
|
||||
SDL_WaitThread(ManagementThread, NULL);
|
||||
ManagementThread = NULL;
|
||||
}
|
||||
|
||||
SDL_assert(SDL_GetAtomicPointer((void **) &ManagementThreadPendingTasks) == NULL);
|
||||
|
||||
SDL_DestroyCondition(ManagementThreadCondition);
|
||||
SDL_DestroyMutex(ManagementThreadLock);
|
||||
ManagementThreadCondition = NULL;
|
||||
ManagementThreadLock = NULL;
|
||||
SDL_SetAtomicInt(&ManagementThreadShutdown, 0);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SDL_AudioDevice **default_playback;
|
||||
SDL_AudioDevice **default_recording;
|
||||
} mgmtthrtask_DetectDevicesData;
|
||||
|
||||
static bool mgmtthrtask_DetectDevices(void *userdata)
|
||||
{
|
||||
mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata;
|
||||
SDL_IMMDevice_EnumerateEndpoints(data->default_playback, data->default_recording, SDL_AUDIO_F32, supports_recording_on_playback_devices);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WASAPI_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
bool rc;
|
||||
// this blocks because it needs to finish before the audio subsystem inits
|
||||
mgmtthrtask_DetectDevicesData data;
|
||||
data.default_playback = default_playback;
|
||||
data.default_recording = default_recording;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc);
|
||||
}
|
||||
|
||||
void WASAPI_DisconnectDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// don't block in here; IMMDevice's own thread needs to return or everything will deadlock.
|
||||
if (device && (!device->hidden || SDL_CompareAndSwapAtomicInt(&device->hidden->device_disconnecting, 0, 1))) {
|
||||
SDL_AudioDeviceDisconnected(device); // this proxies the work to the main thread now, so no point in proxying to the management thread.
|
||||
}
|
||||
}
|
||||
|
||||
static bool WasapiFailed(SDL_AudioDevice *device, const HRESULT err)
|
||||
{
|
||||
if (err == S_OK) {
|
||||
return false;
|
||||
} else if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
|
||||
device->hidden->device_lost = true;
|
||||
} else {
|
||||
device->hidden->device_dead = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_StopAndReleaseClient(void *userdata)
|
||||
{
|
||||
IAudioClient *client = (IAudioClient *) userdata;
|
||||
IAudioClient_Stop(client);
|
||||
IAudioClient_Release(client);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_ReleaseCaptureClient(void *userdata)
|
||||
{
|
||||
IAudioCaptureClient_Release((IAudioCaptureClient *)userdata);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_ReleaseRenderClient(void *userdata)
|
||||
{
|
||||
IAudioRenderClient_Release((IAudioRenderClient *)userdata);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_CoTaskMemFree(void *userdata)
|
||||
{
|
||||
CoTaskMemFree(userdata);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_CloseHandle(void *userdata)
|
||||
{
|
||||
CloseHandle((HANDLE) userdata);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ResetWasapiDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (!device || !device->hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
// just queue up all the tasks in the management thread and don't block.
|
||||
// We don't care when any of these actually get free'd.
|
||||
|
||||
if (device->hidden->client) {
|
||||
IAudioClient *client = device->hidden->client;
|
||||
device->hidden->client = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_StopAndReleaseClient, client, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->render) {
|
||||
IAudioRenderClient *render = device->hidden->render;
|
||||
device->hidden->render = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseRenderClient, render, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->capture) {
|
||||
IAudioCaptureClient *capture = device->hidden->capture;
|
||||
device->hidden->capture = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseCaptureClient, capture, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->waveformat) {
|
||||
void *ptr = device->hidden->waveformat;
|
||||
device->hidden->waveformat = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_CoTaskMemFree, ptr, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->event) {
|
||||
HANDLE event = device->hidden->event;
|
||||
device->hidden->event = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_CloseHandle, (void *) event, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_ActivateDevice(void *userdata)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
|
||||
|
||||
IMMDevice *immdevice = NULL;
|
||||
if (!SDL_IMMDevice_Get(device, &immdevice, device->recording)) {
|
||||
device->hidden->client = NULL;
|
||||
return false; // This is already set by SDL_IMMDevice_Get
|
||||
}
|
||||
|
||||
device->hidden->isplayback = !SDL_IMMDevice_GetIsCapture(immdevice);
|
||||
|
||||
// this is _not_ async in standard win32, yay!
|
||||
HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client);
|
||||
IMMDevice_Release(immdevice);
|
||||
|
||||
if (FAILED(ret)) {
|
||||
SDL_assert(device->hidden->client == NULL);
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret);
|
||||
}
|
||||
|
||||
SDL_assert(device->hidden->client != NULL);
|
||||
if (!WASAPI_PrepDevice(device)) { // not async, fire it right away.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // good to go.
|
||||
}
|
||||
|
||||
static bool ActivateWasapiDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// this blocks because we're either being notified from a background thread or we're running during device open,
|
||||
// both of which won't deadlock vs the device thread.
|
||||
bool rc = false;
|
||||
return (WASAPI_ProxyToManagementThread(mgmtthrtask_ActivateDevice, device, &rc) && rc);
|
||||
}
|
||||
|
||||
// do not call when holding the device lock!
|
||||
static bool RecoverWasapiDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
ResetWasapiDevice(device); // dump the lost device's handles.
|
||||
|
||||
// This handles a non-default device that simply had its format changed in the Windows Control Panel.
|
||||
if (!ActivateWasapiDevice(device)) {
|
||||
WASAPI_DisconnectDevice(device);
|
||||
return false;
|
||||
}
|
||||
|
||||
device->hidden->device_lost = false;
|
||||
|
||||
return true; // okay, carry on with new device details!
|
||||
}
|
||||
|
||||
// do not call when holding the device lock!
|
||||
static bool RecoverWasapiIfLost(SDL_AudioDevice *device)
|
||||
{
|
||||
if (SDL_GetAtomicInt(&device->shutdown)) {
|
||||
return false; // closing, stop trying.
|
||||
} else if (SDL_GetAtomicInt(&device->hidden->device_disconnecting)) {
|
||||
return false; // failing via the WASAPI management thread, stop trying.
|
||||
} else if (device->hidden->device_dead) { // had a fatal error elsewhere, clean up and quit
|
||||
IAudioClient_Stop(device->hidden->client);
|
||||
WASAPI_DisconnectDevice(device);
|
||||
SDL_assert(SDL_GetAtomicInt(&device->shutdown)); // so we don't come back through here.
|
||||
return false; // already failed.
|
||||
} else if (SDL_GetAtomicInt(&device->zombie)) {
|
||||
return false; // we're already dead, so just leave and let the Zombie implementations take over.
|
||||
} else if (!device->hidden->client) {
|
||||
return true; // still waiting for activation.
|
||||
}
|
||||
|
||||
return device->hidden->device_lost ? RecoverWasapiDevice(device) : true;
|
||||
}
|
||||
|
||||
static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
// get an endpoint buffer from WASAPI.
|
||||
BYTE *buffer = NULL;
|
||||
|
||||
if (device->hidden->render) {
|
||||
const HRESULT ret = IAudioRenderClient_GetBuffer(device->hidden->render, device->sample_frames, &buffer);
|
||||
if (ret == AUDCLNT_E_BUFFER_TOO_LARGE) {
|
||||
SDL_assert(buffer == NULL);
|
||||
*buffer_size = 0; // just go back to WaitDevice and try again after the hardware has consumed some more data.
|
||||
} else if (WasapiFailed(device, ret)) {
|
||||
SDL_assert(buffer == NULL);
|
||||
if (device->hidden->device_lost) { // just use an available buffer, we won't be playing it anyhow.
|
||||
*buffer_size = 0; // we'll recover during WaitDevice and try again.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (Uint8 *)buffer;
|
||||
}
|
||||
|
||||
static bool WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
if (device->hidden->render && !SDL_GetAtomicInt(&device->hidden->device_disconnecting)) { // definitely activated?
|
||||
// WasapiFailed() will mark the device for reacquisition or removal elsewhere.
|
||||
WasapiFailed(device, IAudioRenderClient_ReleaseBuffer(device->hidden->render, device->sample_frames, 0));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool WASAPI_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// WaitDevice does not hold the device lock, so check for recovery/disconnect details here.
|
||||
while (RecoverWasapiIfLost(device) && device->hidden->client && device->hidden->event) {
|
||||
if (device->recording) {
|
||||
// Recording devices should return immediately if there is any data available
|
||||
UINT32 padding = 0;
|
||||
if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
|
||||
//SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);
|
||||
if (padding > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (WaitForSingleObjectEx(device->hidden->event, 200, FALSE)) {
|
||||
case WAIT_OBJECT_0:
|
||||
case WAIT_TIMEOUT:
|
||||
break;
|
||||
|
||||
default:
|
||||
//SDL_Log("WASAPI FAILED EVENT!");
|
||||
IAudioClient_Stop(device->hidden->client);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
DWORD waitResult = WaitForSingleObjectEx(device->hidden->event, 200, FALSE);
|
||||
if (waitResult == WAIT_OBJECT_0) {
|
||||
UINT32 padding = 0;
|
||||
if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
|
||||
//SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);
|
||||
if (padding <= (UINT32)device->sample_frames) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (waitResult != WAIT_TIMEOUT) {
|
||||
//SDL_Log("WASAPI FAILED EVENT!");*/
|
||||
IAudioClient_Stop(device->hidden->client);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int WASAPI_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
BYTE *ptr = NULL;
|
||||
UINT32 frames = 0;
|
||||
DWORD flags = 0;
|
||||
|
||||
while (device->hidden->capture && !SDL_GetAtomicInt(&device->hidden->device_disconnecting)) {
|
||||
const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
|
||||
if (ret == AUDCLNT_S_BUFFER_EMPTY) {
|
||||
return 0; // in theory we should have waited until there was data, but oh well, we'll go back to waiting. Returning 0 is safe in SDL3.
|
||||
}
|
||||
|
||||
WasapiFailed(device, ret); // mark device lost/failed if necessary.
|
||||
|
||||
if (ret == S_OK) {
|
||||
const int total = ((int)frames) * device->hidden->framesize;
|
||||
const int cpy = SDL_min(buflen, total);
|
||||
const int leftover = total - cpy;
|
||||
const bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? true : false;
|
||||
|
||||
SDL_assert(leftover == 0); // according to MSDN, this isn't everything available, just one "packet" of data per-GetBuffer call.
|
||||
|
||||
if (silent) {
|
||||
SDL_memset(buffer, device->silence_value, cpy);
|
||||
} else {
|
||||
SDL_memcpy(buffer, ptr, cpy);
|
||||
}
|
||||
|
||||
WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames));
|
||||
|
||||
return cpy;
|
||||
}
|
||||
}
|
||||
|
||||
return -1; // unrecoverable error.
|
||||
}
|
||||
|
||||
static void WASAPI_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
BYTE *ptr = NULL;
|
||||
UINT32 frames = 0;
|
||||
DWORD flags = 0;
|
||||
|
||||
// just read until we stop getting packets, throwing them away.
|
||||
while (!SDL_GetAtomicInt(&device->shutdown) && device->hidden->capture) {
|
||||
const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
|
||||
if (ret == AUDCLNT_S_BUFFER_EMPTY) {
|
||||
break; // no more buffered data; we're done.
|
||||
} else if (WasapiFailed(device, ret)) {
|
||||
break; // failed for some other reason, abort.
|
||||
} else if (WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames))) {
|
||||
break; // something broke.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void WASAPI_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
ResetWasapiDevice(device);
|
||||
SDL_free(device->hidden->devid);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_PrepDevice(void *userdata)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *)userdata;
|
||||
|
||||
/* !!! FIXME: we could request an exclusive mode stream, which is lower latency;
|
||||
!!! it will write into the kernel's audio buffer directly instead of
|
||||
!!! shared memory that a user-mode mixer then writes to the kernel with
|
||||
!!! everything else. Doing this means any other sound using this device will
|
||||
!!! stop playing, including the user's MP3 player and system notification
|
||||
!!! sounds. You'd probably need to release the device when the app isn't in
|
||||
!!! the foreground, to be a good citizen of the system. It's doable, but it's
|
||||
!!! more work and causes some annoyances, and I don't know what the latency
|
||||
!!! wins actually look like. Maybe add a hint to force exclusive mode at
|
||||
!!! some point. To be sure, defaulting to shared mode is the right thing to
|
||||
!!! do in any case. */
|
||||
const AUDCLNT_SHAREMODE sharemode = AUDCLNT_SHAREMODE_SHARED;
|
||||
|
||||
IAudioClient *client = device->hidden->client;
|
||||
SDL_assert(client != NULL);
|
||||
|
||||
device->hidden->event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
if (!device->hidden->event) {
|
||||
return WIN_SetError("WASAPI can't create an event handle");
|
||||
}
|
||||
|
||||
HRESULT ret;
|
||||
|
||||
WAVEFORMATEX *waveformat = NULL;
|
||||
ret = IAudioClient_GetMixFormat(client, &waveformat);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't determine mix format", ret);
|
||||
}
|
||||
SDL_assert(waveformat != NULL);
|
||||
device->hidden->waveformat = waveformat;
|
||||
|
||||
SDL_AudioSpec newspec;
|
||||
newspec.channels = (Uint8)waveformat->nChannels;
|
||||
|
||||
// Make sure we have a valid format that we can convert to whatever WASAPI wants.
|
||||
const SDL_AudioFormat wasapi_format = SDL_WaveFormatExToSDLFormat(waveformat);
|
||||
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (test_format == wasapi_format) {
|
||||
newspec.format = test_format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("%s: Unsupported audio format", "wasapi");
|
||||
}
|
||||
|
||||
REFERENCE_TIME default_period = 0;
|
||||
ret = IAudioClient_GetDevicePeriod(client, &default_period, NULL);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't determine minimum device period", ret);
|
||||
}
|
||||
|
||||
DWORD streamflags = 0;
|
||||
|
||||
/* we've gotten reports that WASAPI's resampler introduces distortions, but in the short term
|
||||
it fixes some other WASAPI-specific quirks we haven't quite tracked down.
|
||||
Refer to bug #6326 for the immediate concern. */
|
||||
#if 1
|
||||
// favor WASAPI's resampler over our own
|
||||
if ((DWORD)device->spec.freq != waveformat->nSamplesPerSec) {
|
||||
streamflags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY);
|
||||
waveformat->nSamplesPerSec = device->spec.freq;
|
||||
waveformat->nAvgBytesPerSec = waveformat->nSamplesPerSec * waveformat->nChannels * (waveformat->wBitsPerSample / 8);
|
||||
}
|
||||
#endif
|
||||
|
||||
newspec.freq = waveformat->nSamplesPerSec;
|
||||
|
||||
if (device->recording && device->hidden->isplayback) {
|
||||
streamflags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
|
||||
}
|
||||
|
||||
streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
|
||||
|
||||
int new_sample_frames = 0;
|
||||
bool iaudioclient3_initialized = false;
|
||||
|
||||
#ifdef __IAudioClient2_INTERFACE_DEFINED__
|
||||
IAudioClient2 *client2 = NULL;
|
||||
ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient2, (void **)&client2);
|
||||
if (SUCCEEDED(ret)) {
|
||||
AudioClientProperties audioProps;
|
||||
|
||||
SDL_zero(audioProps);
|
||||
audioProps.cbSize = sizeof(audioProps);
|
||||
|
||||
// Setting AudioCategory_GameChat breaks audio on several devices, including Behringer U-PHORIA UM2 and RODE NT-USB Mini.
|
||||
// We'll disable this for now until we understand more about what's happening.
|
||||
#if 0
|
||||
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE);
|
||||
if (hint && *hint) {
|
||||
if (SDL_strcasecmp(hint, "Communications") == 0) {
|
||||
audioProps.eCategory = AudioCategory_Communications;
|
||||
} else if (SDL_strcasecmp(hint, "Game") == 0) {
|
||||
// We'll add support for GameEffects as distinct from GameMedia later when we add stream roles
|
||||
audioProps.eCategory = AudioCategory_GameEffects;
|
||||
} else if (SDL_strcasecmp(hint, "GameChat") == 0) {
|
||||
audioProps.eCategory = AudioCategory_GameChat;
|
||||
} else if (SDL_strcasecmp(hint, "Movie") == 0) {
|
||||
audioProps.eCategory = AudioCategory_Movie;
|
||||
} else if (SDL_strcasecmp(hint, "Media") == 0) {
|
||||
audioProps.eCategory = AudioCategory_Media;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (SDL_GetHintBoolean(SDL_HINT_AUDIO_DEVICE_RAW_STREAM, false)) {
|
||||
audioProps.Options = AUDCLNT_STREAMOPTIONS_RAW;
|
||||
}
|
||||
|
||||
ret = IAudioClient2_SetClientProperties(client2, &audioProps);
|
||||
if (FAILED(ret)) {
|
||||
// This isn't fatal, let's log it instead of failing
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_AUDIO, "IAudioClient2_SetClientProperties failed: 0x%lx", ret);
|
||||
}
|
||||
IAudioClient2_Release(client2);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __IAudioClient3_INTERFACE_DEFINED__
|
||||
// Try querying IAudioClient3 if sharemode is AUDCLNT_SHAREMODE_SHARED
|
||||
if (sharemode == AUDCLNT_SHAREMODE_SHARED) {
|
||||
IAudioClient3 *client3 = NULL;
|
||||
ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient3, (void **)&client3);
|
||||
if (SUCCEEDED(ret)) {
|
||||
UINT32 default_period_in_frames = 0;
|
||||
UINT32 fundamental_period_in_frames = 0;
|
||||
UINT32 min_period_in_frames = 0;
|
||||
UINT32 max_period_in_frames = 0;
|
||||
ret = IAudioClient3_GetSharedModeEnginePeriod(client3, waveformat,
|
||||
&default_period_in_frames, &fundamental_period_in_frames, &min_period_in_frames, &max_period_in_frames);
|
||||
if (SUCCEEDED(ret)) {
|
||||
// IAudioClient3_InitializeSharedAudioStream only accepts the integral multiple of fundamental_period_in_frames
|
||||
UINT32 period_in_frames = fundamental_period_in_frames * (UINT32)SDL_round((double)device->sample_frames / fundamental_period_in_frames);
|
||||
period_in_frames = SDL_clamp(period_in_frames, min_period_in_frames, max_period_in_frames);
|
||||
|
||||
ret = IAudioClient3_InitializeSharedAudioStream(client3, streamflags, period_in_frames, waveformat, NULL);
|
||||
if (SUCCEEDED(ret)) {
|
||||
new_sample_frames = (int)period_in_frames;
|
||||
iaudioclient3_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
IAudioClient3_Release(client3);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!iaudioclient3_initialized)
|
||||
ret = IAudioClient_Initialize(client, sharemode, streamflags, 0, 0, waveformat, NULL);
|
||||
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't initialize audio client", ret);
|
||||
}
|
||||
|
||||
ret = IAudioClient_SetEventHandle(client, device->hidden->event);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't set event handle", ret);
|
||||
}
|
||||
|
||||
UINT32 bufsize = 0; // this is in sample frames, not samples, not bytes.
|
||||
ret = IAudioClient_GetBufferSize(client, &bufsize);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't determine buffer size", ret);
|
||||
}
|
||||
|
||||
// Match the callback size to the period size to cut down on the number of
|
||||
// interrupts waited for in each call to WaitDevice
|
||||
if (new_sample_frames <= 0) {
|
||||
const float period_millis = default_period / 10000.0f;
|
||||
const float period_frames = period_millis * newspec.freq / 1000.0f;
|
||||
new_sample_frames = (int) SDL_ceilf(period_frames);
|
||||
}
|
||||
|
||||
// regardless of what we calculated for the period size, clamp it to the expected hardware buffer size.
|
||||
if (new_sample_frames > (int) bufsize) {
|
||||
new_sample_frames = (int) bufsize;
|
||||
}
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
if (!SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device->hidden->framesize = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
|
||||
if (device->recording) {
|
||||
IAudioCaptureClient *capture = NULL;
|
||||
ret = IAudioClient_GetService(client, &SDL_IID_IAudioCaptureClient, (void **)&capture);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't get capture client service", ret);
|
||||
}
|
||||
|
||||
SDL_assert(capture != NULL);
|
||||
device->hidden->capture = capture;
|
||||
ret = IAudioClient_Start(client);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't start capture", ret);
|
||||
}
|
||||
|
||||
WASAPI_FlushRecording(device); // MSDN says you should flush the recording endpoint right after startup.
|
||||
} else {
|
||||
IAudioRenderClient *render = NULL;
|
||||
ret = IAudioClient_GetService(client, &SDL_IID_IAudioRenderClient, (void **)&render);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't get render client service", ret);
|
||||
}
|
||||
|
||||
SDL_assert(render != NULL);
|
||||
device->hidden->render = render;
|
||||
ret = IAudioClient_Start(client);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't start playback", ret);
|
||||
}
|
||||
}
|
||||
|
||||
return true; // good to go.
|
||||
}
|
||||
|
||||
// This is called once a device is activated, possibly asynchronously.
|
||||
bool WASAPI_PrepDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
bool rc = true;
|
||||
return (WASAPI_ProxyToManagementThread(mgmtthrtask_PrepDevice, device, &rc) && rc);
|
||||
}
|
||||
|
||||
static bool WASAPI_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
} else if (!ActivateWasapiDevice(device)) {
|
||||
return false; // already set error.
|
||||
}
|
||||
|
||||
/* Ready, but possibly waiting for async device activation.
|
||||
Until activation is successful, we will report silence from recording
|
||||
devices and ignore data on playback devices. Upon activation, we'll make
|
||||
sure any bound audio streams are adjusted for the final device format. */
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WASAPI_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
// this thread uses COM.
|
||||
if (SUCCEEDED(WIN_CoInitialize())) { // can't report errors, hope it worked!
|
||||
device->hidden->coinitialized = true;
|
||||
}
|
||||
|
||||
// Set this thread to very high "Pro Audio" priority.
|
||||
if (pAvSetMmThreadCharacteristicsW) {
|
||||
DWORD idx = 0;
|
||||
device->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx);
|
||||
} else {
|
||||
SDL_SetCurrentThreadPriority(device->recording ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
static void WASAPI_ThreadDeinit(SDL_AudioDevice *device)
|
||||
{
|
||||
// Set this thread back to normal priority.
|
||||
if (device->hidden->task && pAvRevertMmThreadCharacteristics) {
|
||||
pAvRevertMmThreadCharacteristics(device->hidden->task);
|
||||
device->hidden->task = NULL;
|
||||
}
|
||||
|
||||
if (device->hidden->coinitialized) {
|
||||
WIN_CoUninitialize();
|
||||
device->hidden->coinitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_FreeDeviceHandle(void *userdata)
|
||||
{
|
||||
SDL_IMMDevice_FreeDeviceHandle((SDL_AudioDevice *) userdata);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device)
|
||||
{
|
||||
bool rc;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc);
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_DeinitializeStart(void *userdata)
|
||||
{
|
||||
StopWasapiHotplug();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WASAPI_DeinitializeStart(void)
|
||||
{
|
||||
bool rc;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_DeinitializeStart, NULL, &rc);
|
||||
}
|
||||
|
||||
static void WASAPI_Deinitialize(void)
|
||||
{
|
||||
DeinitManagementThread();
|
||||
}
|
||||
|
||||
static bool WASAPI_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!InitManagementThread()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
impl->DetectDevices = WASAPI_DetectDevices;
|
||||
impl->ThreadInit = WASAPI_ThreadInit;
|
||||
impl->ThreadDeinit = WASAPI_ThreadDeinit;
|
||||
impl->OpenDevice = WASAPI_OpenDevice;
|
||||
impl->PlayDevice = WASAPI_PlayDevice;
|
||||
impl->WaitDevice = WASAPI_WaitDevice;
|
||||
impl->GetDeviceBuf = WASAPI_GetDeviceBuf;
|
||||
impl->WaitRecordingDevice = WASAPI_WaitDevice;
|
||||
impl->RecordDevice = WASAPI_RecordDevice;
|
||||
impl->FlushRecording = WASAPI_FlushRecording;
|
||||
impl->CloseDevice = WASAPI_CloseDevice;
|
||||
impl->DeinitializeStart = WASAPI_DeinitializeStart;
|
||||
impl->Deinitialize = WASAPI_Deinitialize;
|
||||
impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
supports_recording_on_playback_devices = SDL_GetHintBoolean(SDL_HINT_AUDIO_INCLUDE_MONITORS, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap WASAPI_bootstrap = {
|
||||
"wasapi", "WASAPI", WASAPI_Init, false, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_WASAPI
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_wasapi_h_
|
||||
#define SDL_wasapi_h_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
WCHAR *devid;
|
||||
WAVEFORMATEX *waveformat;
|
||||
IAudioClient *client;
|
||||
IAudioRenderClient *render;
|
||||
IAudioCaptureClient *capture;
|
||||
HANDLE event;
|
||||
HANDLE task;
|
||||
bool coinitialized;
|
||||
int framesize;
|
||||
SDL_AtomicInt device_disconnecting;
|
||||
bool device_lost;
|
||||
bool device_dead;
|
||||
bool isplayback;
|
||||
};
|
||||
|
||||
// win32 implementation calls into these.
|
||||
bool WASAPI_PrepDevice(SDL_AudioDevice *device);
|
||||
void WASAPI_DisconnectDevice(SDL_AudioDevice *device); // don't hold the device lock when calling this!
|
||||
|
||||
|
||||
// BE CAREFUL: if you are holding the device lock and proxy to the management thread with wait_until_complete, and grab the lock again, you will deadlock.
|
||||
typedef bool (*ManagementThreadTask)(void *userdata);
|
||||
bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, bool *wait_until_complete);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SDL_wasapi_h_
|
||||
Vendored
+1618
File diff suppressed because it is too large
Load Diff
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "../SDL_internal.h"
|
||||
|
||||
#ifndef SDL_camera_c_h_
|
||||
#define SDL_camera_c_h_
|
||||
|
||||
// Initialize the camera subsystem
|
||||
extern bool SDL_CameraInit(const char *driver_name);
|
||||
|
||||
// Shutdown the camera subsystem
|
||||
extern void SDL_QuitCamera(void);
|
||||
|
||||
// "Pump" the event queue.
|
||||
extern void SDL_UpdateCamera(void);
|
||||
|
||||
#endif // SDL_camera_c_h_
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "../SDL_internal.h"
|
||||
|
||||
#ifndef SDL_syscamera_h_
|
||||
#define SDL_syscamera_h_
|
||||
|
||||
#include "../video/SDL_surface_c.h"
|
||||
|
||||
#define DEBUG_CAMERA 0
|
||||
|
||||
/* Backends should call this as devices are added to the system (such as
|
||||
a USB camera being plugged in), and should also be called for
|
||||
for every device found during DetectDevices(). */
|
||||
extern SDL_Camera *SDL_AddCamera(const char *name, SDL_CameraPosition position, int num_specs, const SDL_CameraSpec *specs, void *handle);
|
||||
|
||||
/* Backends should call this if an opened camera device is lost.
|
||||
This can happen due to i/o errors, or a device being unplugged, etc. */
|
||||
extern void SDL_CameraDisconnected(SDL_Camera *device);
|
||||
|
||||
// Find an SDL_Camera, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
|
||||
extern SDL_Camera *SDL_FindPhysicalCameraByCallback(bool (*callback)(SDL_Camera *device, void *userdata), void *userdata);
|
||||
|
||||
// Backends should call this when the user has approved/denied access to a camera.
|
||||
extern void SDL_CameraPermissionOutcome(SDL_Camera *device, bool approved);
|
||||
|
||||
// Backends can call this to get a standardized name for a thread to power a specific camera device.
|
||||
extern char *SDL_GetCameraThreadName(SDL_Camera *device, char *buf, size_t buflen);
|
||||
|
||||
// Backends can call these to change a device's refcount.
|
||||
extern void RefPhysicalCamera(SDL_Camera *device);
|
||||
extern void UnrefPhysicalCamera(SDL_Camera *device);
|
||||
|
||||
// These functions are the heart of the camera threads. Backends can call them directly if they aren't using the SDL-provided thread.
|
||||
extern void SDL_CameraThreadSetup(SDL_Camera *device);
|
||||
extern bool SDL_CameraThreadIterate(SDL_Camera *device);
|
||||
extern void SDL_CameraThreadShutdown(SDL_Camera *device);
|
||||
|
||||
// Backends can call this if they have to finish initializing later, like Emscripten. Most backends should _not_ call this directly!
|
||||
extern bool SDL_PrepareCameraSurfaces(SDL_Camera *device);
|
||||
|
||||
|
||||
// common utility functionality to gather up camera specs. Not required!
|
||||
typedef struct CameraFormatAddData
|
||||
{
|
||||
SDL_CameraSpec *specs;
|
||||
int num_specs;
|
||||
int allocated_specs;
|
||||
} CameraFormatAddData;
|
||||
|
||||
bool SDL_AddCameraFormat(CameraFormatAddData *data, SDL_PixelFormat format, SDL_Colorspace colorspace, int w, int h, int framerate_numerator, int framerate_denominator);
|
||||
|
||||
typedef enum SDL_CameraFrameResult
|
||||
{
|
||||
SDL_CAMERA_FRAME_ERROR,
|
||||
SDL_CAMERA_FRAME_SKIP,
|
||||
SDL_CAMERA_FRAME_READY
|
||||
} SDL_CameraFrameResult;
|
||||
|
||||
typedef struct SurfaceList
|
||||
{
|
||||
SDL_Surface *surface;
|
||||
Uint64 timestampNS;
|
||||
struct SurfaceList *next;
|
||||
} SurfaceList;
|
||||
|
||||
// Define the SDL camera driver structure
|
||||
struct SDL_Camera
|
||||
{
|
||||
// A mutex for locking
|
||||
SDL_Mutex *lock;
|
||||
|
||||
// Human-readable device name.
|
||||
char *name;
|
||||
|
||||
// Position of camera (front-facing, back-facing, etc).
|
||||
SDL_CameraPosition position;
|
||||
|
||||
// When refcount hits zero, we destroy the device object.
|
||||
SDL_AtomicInt refcount;
|
||||
|
||||
// These are, initially, set from camera_driver, but we might swap them out with Zombie versions on disconnect/failure.
|
||||
bool (*WaitDevice)(SDL_Camera *device);
|
||||
SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation);
|
||||
void (*ReleaseFrame)(SDL_Camera *device, SDL_Surface *frame);
|
||||
|
||||
// All supported formats/dimensions for this device.
|
||||
SDL_CameraSpec *all_specs;
|
||||
|
||||
// Elements in all_specs.
|
||||
int num_specs;
|
||||
|
||||
// The device's actual specification that the camera is outputting, before conversion.
|
||||
SDL_CameraSpec actual_spec;
|
||||
|
||||
// The device's current camera specification, after conversions.
|
||||
SDL_CameraSpec spec;
|
||||
|
||||
// Unique value assigned at creation time.
|
||||
SDL_CameraID instance_id;
|
||||
|
||||
// Driver-specific hardware data on how to open device (`hidden` is driver-specific data _when opened_).
|
||||
void *handle;
|
||||
|
||||
// Dropping the first frame(s) after open seems to help timing on some platforms.
|
||||
int drop_frames;
|
||||
|
||||
// Backend timestamp of first acquired frame, so we can keep these meaningful regardless of epoch.
|
||||
Uint64 base_timestamp;
|
||||
|
||||
// SDL timestamp of first acquired frame, so we can roughly convert to SDL ticks.
|
||||
Uint64 adjust_timestamp;
|
||||
|
||||
// Pixel data flows from the driver into these, then gets converted for the app if necessary.
|
||||
SDL_Surface *acquire_surface;
|
||||
|
||||
// acquire_surface converts or scales to this surface before landing in output_surfaces, if necessary.
|
||||
SDL_Surface *conversion_surface;
|
||||
|
||||
// A queue of surfaces that buffer converted/scaled frames of video until the app claims them.
|
||||
SurfaceList output_surfaces[8];
|
||||
SurfaceList filled_output_surfaces; // this is FIFO
|
||||
SurfaceList empty_output_surfaces; // this is LIFO
|
||||
SurfaceList app_held_output_surfaces;
|
||||
|
||||
// A fake video frame we allocate if the camera fails/disconnects.
|
||||
Uint8 *zombie_pixels;
|
||||
|
||||
// non-zero if acquire_surface needs to be scaled for final output.
|
||||
int needs_scaling; // -1: downscale, 0: no scaling, 1: upscale
|
||||
|
||||
// true if acquire_surface needs to be converted for final output.
|
||||
bool needs_conversion;
|
||||
|
||||
// Current state flags
|
||||
SDL_AtomicInt shutdown;
|
||||
SDL_AtomicInt zombie;
|
||||
|
||||
// A thread to feed the camera device
|
||||
SDL_Thread *thread;
|
||||
|
||||
// Optional properties.
|
||||
SDL_PropertiesID props;
|
||||
|
||||
// Current state of user permission check.
|
||||
SDL_CameraPermissionState permission;
|
||||
|
||||
// Data private to this driver, used when device is opened and running.
|
||||
struct SDL_PrivateCameraData *hidden;
|
||||
};
|
||||
|
||||
|
||||
// Note that for AcquireFrame, `rotation` is degrees, with positive values rotating clockwise. This is the amount to rotate an image so it would be right-side up.
|
||||
// Rotations should be in 90 degree increments at this time (landscape to portrait, or upside down to right side up, etc).
|
||||
// Most platforms won't care about this, but mobile devices might need to deal with the device itself being physically rotated, causing the fixed-orientation camera to be presenting sideways images.
|
||||
|
||||
typedef struct SDL_CameraDriverImpl
|
||||
{
|
||||
void (*DetectDevices)(void);
|
||||
bool (*OpenDevice)(SDL_Camera *device, const SDL_CameraSpec *spec);
|
||||
void (*CloseDevice)(SDL_Camera *device);
|
||||
bool (*WaitDevice)(SDL_Camera *device);
|
||||
SDL_CameraFrameResult (*AcquireFrame)(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation); // set frame->pixels, frame->pitch, *timestampNS, and *rotation!
|
||||
void (*ReleaseFrame)(SDL_Camera *device, SDL_Surface *frame); // Reclaim frame->pixels and frame->pitch!
|
||||
void (*FreeDeviceHandle)(SDL_Camera *device); // SDL is done with this device; free the handle from SDL_AddCamera()
|
||||
void (*Deinitialize)(void);
|
||||
|
||||
bool ProvidesOwnCallbackThread;
|
||||
} SDL_CameraDriverImpl;
|
||||
|
||||
typedef struct SDL_PendingCameraEvent
|
||||
{
|
||||
Uint32 type;
|
||||
SDL_CameraID devid;
|
||||
struct SDL_PendingCameraEvent *next;
|
||||
} SDL_PendingCameraEvent;
|
||||
|
||||
typedef struct SDL_CameraDriver
|
||||
{
|
||||
const char *name; // The name of this camera driver
|
||||
const char *desc; // The description of this camera driver
|
||||
SDL_CameraDriverImpl impl; // the backend's interface
|
||||
|
||||
SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash` // !!! FIXME: device_hash _also_ has a rwlock, see if we still need this one.
|
||||
SDL_HashTable *device_hash; // the collection of currently-available camera devices
|
||||
SDL_PendingCameraEvent pending_events;
|
||||
SDL_PendingCameraEvent *pending_events_tail;
|
||||
|
||||
SDL_AtomicInt device_count;
|
||||
SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs.
|
||||
} SDL_CameraDriver;
|
||||
|
||||
typedef struct CameraBootStrap
|
||||
{
|
||||
const char *name;
|
||||
const char *desc;
|
||||
bool (*init)(SDL_CameraDriverImpl *impl);
|
||||
bool demand_only; // if true: request explicitly, or it won't be available.
|
||||
} CameraBootStrap;
|
||||
|
||||
// Not all of these are available in a given build. Use #ifdefs, etc.
|
||||
extern CameraBootStrap DUMMYCAMERA_bootstrap;
|
||||
extern CameraBootStrap PIPEWIRECAMERA_bootstrap;
|
||||
extern CameraBootStrap V4L2_bootstrap;
|
||||
extern CameraBootStrap COREMEDIA_bootstrap;
|
||||
extern CameraBootStrap ANDROIDCAMERA_bootstrap;
|
||||
extern CameraBootStrap EMSCRIPTENCAMERA_bootstrap;
|
||||
extern CameraBootStrap MEDIAFOUNDATION_bootstrap;
|
||||
extern CameraBootStrap VITACAMERA_bootstrap;
|
||||
|
||||
#endif // SDL_syscamera_h_
|
||||
@@ -0,0 +1,934 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "../SDL_syscamera.h"
|
||||
#include "../SDL_camera_c.h"
|
||||
#include "../../video/SDL_pixels_c.h"
|
||||
#include "../../video/SDL_surface_c.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
|
||||
#ifdef SDL_CAMERA_DRIVER_ANDROID
|
||||
|
||||
/*
|
||||
* AndroidManifest.xml:
|
||||
* <uses-permission android:name="android.permission.CAMERA"></uses-permission>
|
||||
* <uses-feature android:name="android.hardware.camera" />
|
||||
*
|
||||
* Very likely SDL must be build with YUV support (done by default)
|
||||
*
|
||||
* https://developer.android.com/reference/android/hardware/camera2/CameraManager
|
||||
* "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
|
||||
* before configuring sessions on any of the camera devices."
|
||||
*/
|
||||
|
||||
// this is kinda gross, but on older NDK headers all the camera stuff is
|
||||
// gated behind __ANDROID_API__. We'll dlopen() it at runtime, so we'll do
|
||||
// the right thing on pre-Android 7.0 devices, but we still
|
||||
// need the struct declarations and such in those headers.
|
||||
// The other option is to make a massive jump in minimum Android version we
|
||||
// support--going from ancient to merely really old--but this seems less
|
||||
// distasteful and using dlopen matches practices on other SDL platforms.
|
||||
// We'll see if it works out.
|
||||
#if __ANDROID_API__ < 24
|
||||
#undef __ANDROID_API__
|
||||
#define __ANDROID_API__ 24
|
||||
#endif
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <camera/NdkCameraDevice.h>
|
||||
#include <camera/NdkCameraManager.h>
|
||||
#include <media/NdkImage.h>
|
||||
#include <media/NdkImageReader.h>
|
||||
|
||||
#include "../../core/android/SDL_android.h"
|
||||
|
||||
static void *libcamera2ndk = NULL;
|
||||
typedef ACameraManager* (*pfnACameraManager_create)(void);
|
||||
typedef camera_status_t (*pfnACameraManager_registerAvailabilityCallback)(ACameraManager*, const ACameraManager_AvailabilityCallbacks*);
|
||||
typedef camera_status_t (*pfnACameraManager_unregisterAvailabilityCallback)(ACameraManager*, const ACameraManager_AvailabilityCallbacks*);
|
||||
typedef camera_status_t (*pfnACameraManager_getCameraIdList)(ACameraManager*, ACameraIdList**);
|
||||
typedef void (*pfnACameraManager_deleteCameraIdList)(ACameraIdList*);
|
||||
typedef void (*pfnACameraCaptureSession_close)(ACameraCaptureSession*);
|
||||
typedef void (*pfnACaptureRequest_free)(ACaptureRequest*);
|
||||
typedef void (*pfnACameraOutputTarget_free)(ACameraOutputTarget*);
|
||||
typedef camera_status_t (*pfnACameraDevice_close)(ACameraDevice*);
|
||||
typedef void (*pfnACameraManager_delete)(ACameraManager*);
|
||||
typedef void (*pfnACaptureSessionOutputContainer_free)(ACaptureSessionOutputContainer*);
|
||||
typedef void (*pfnACaptureSessionOutput_free)(ACaptureSessionOutput*);
|
||||
typedef camera_status_t (*pfnACameraManager_openCamera)(ACameraManager*, const char*, ACameraDevice_StateCallbacks*, ACameraDevice**);
|
||||
typedef camera_status_t (*pfnACameraDevice_createCaptureRequest)(const ACameraDevice*, ACameraDevice_request_template, ACaptureRequest**);
|
||||
typedef camera_status_t (*pfnACameraDevice_createCaptureSession)(ACameraDevice*, const ACaptureSessionOutputContainer*, const ACameraCaptureSession_stateCallbacks*,ACameraCaptureSession**);
|
||||
typedef camera_status_t (*pfnACameraManager_getCameraCharacteristics)(ACameraManager*, const char*, ACameraMetadata**);
|
||||
typedef void (*pfnACameraMetadata_free)(ACameraMetadata*);
|
||||
typedef camera_status_t (*pfnACameraMetadata_getConstEntry)(const ACameraMetadata*, uint32_t tag, ACameraMetadata_const_entry*);
|
||||
typedef camera_status_t (*pfnACameraCaptureSession_setRepeatingRequest)(ACameraCaptureSession*, ACameraCaptureSession_captureCallbacks*, int numRequests, ACaptureRequest**, int*);
|
||||
typedef camera_status_t (*pfnACameraOutputTarget_create)(ACameraWindowType*,ACameraOutputTarget**);
|
||||
typedef camera_status_t (*pfnACaptureRequest_addTarget)(ACaptureRequest*, const ACameraOutputTarget*);
|
||||
typedef camera_status_t (*pfnACaptureSessionOutputContainer_add)(ACaptureSessionOutputContainer*, const ACaptureSessionOutput*);
|
||||
typedef camera_status_t (*pfnACaptureSessionOutputContainer_create)(ACaptureSessionOutputContainer**);
|
||||
typedef camera_status_t (*pfnACaptureSessionOutput_create)(ACameraWindowType*, ACaptureSessionOutput**);
|
||||
static pfnACameraManager_create pACameraManager_create = NULL;
|
||||
static pfnACameraManager_registerAvailabilityCallback pACameraManager_registerAvailabilityCallback = NULL;
|
||||
static pfnACameraManager_unregisterAvailabilityCallback pACameraManager_unregisterAvailabilityCallback = NULL;
|
||||
static pfnACameraManager_getCameraIdList pACameraManager_getCameraIdList = NULL;
|
||||
static pfnACameraManager_deleteCameraIdList pACameraManager_deleteCameraIdList = NULL;
|
||||
static pfnACameraCaptureSession_close pACameraCaptureSession_close = NULL;
|
||||
static pfnACaptureRequest_free pACaptureRequest_free = NULL;
|
||||
static pfnACameraOutputTarget_free pACameraOutputTarget_free = NULL;
|
||||
static pfnACameraDevice_close pACameraDevice_close = NULL;
|
||||
static pfnACameraManager_delete pACameraManager_delete = NULL;
|
||||
static pfnACaptureSessionOutputContainer_free pACaptureSessionOutputContainer_free = NULL;
|
||||
static pfnACaptureSessionOutput_free pACaptureSessionOutput_free = NULL;
|
||||
static pfnACameraManager_openCamera pACameraManager_openCamera = NULL;
|
||||
static pfnACameraDevice_createCaptureRequest pACameraDevice_createCaptureRequest = NULL;
|
||||
static pfnACameraDevice_createCaptureSession pACameraDevice_createCaptureSession = NULL;
|
||||
static pfnACameraManager_getCameraCharacteristics pACameraManager_getCameraCharacteristics = NULL;
|
||||
static pfnACameraMetadata_free pACameraMetadata_free = NULL;
|
||||
static pfnACameraMetadata_getConstEntry pACameraMetadata_getConstEntry = NULL;
|
||||
static pfnACameraCaptureSession_setRepeatingRequest pACameraCaptureSession_setRepeatingRequest = NULL;
|
||||
static pfnACameraOutputTarget_create pACameraOutputTarget_create = NULL;
|
||||
static pfnACaptureRequest_addTarget pACaptureRequest_addTarget = NULL;
|
||||
static pfnACaptureSessionOutputContainer_add pACaptureSessionOutputContainer_add = NULL;
|
||||
static pfnACaptureSessionOutputContainer_create pACaptureSessionOutputContainer_create = NULL;
|
||||
static pfnACaptureSessionOutput_create pACaptureSessionOutput_create = NULL;
|
||||
|
||||
static void *libmediandk = NULL;
|
||||
typedef void (*pfnAImage_delete)(AImage*);
|
||||
typedef media_status_t (*pfnAImage_getTimestamp)(const AImage*, int64_t*);
|
||||
typedef media_status_t (*pfnAImage_getNumberOfPlanes)(const AImage*, int32_t*);
|
||||
typedef media_status_t (*pfnAImage_getPlaneRowStride)(const AImage*, int, int32_t*);
|
||||
typedef media_status_t (*pfnAImage_getPlaneData)(const AImage*, int, uint8_t**, int*);
|
||||
typedef media_status_t (*pfnAImageReader_acquireNextImage)(AImageReader*, AImage**);
|
||||
typedef void (*pfnAImageReader_delete)(AImageReader*);
|
||||
typedef media_status_t (*pfnAImageReader_setImageListener)(AImageReader*, AImageReader_ImageListener*);
|
||||
typedef media_status_t (*pfnAImageReader_getWindow)(AImageReader*, ANativeWindow**);
|
||||
typedef media_status_t (*pfnAImageReader_new)(int32_t, int32_t, int32_t, int32_t, AImageReader**);
|
||||
static pfnAImage_delete pAImage_delete = NULL;
|
||||
static pfnAImage_getTimestamp pAImage_getTimestamp = NULL;
|
||||
static pfnAImage_getNumberOfPlanes pAImage_getNumberOfPlanes = NULL;
|
||||
static pfnAImage_getPlaneRowStride pAImage_getPlaneRowStride = NULL;
|
||||
static pfnAImage_getPlaneData pAImage_getPlaneData = NULL;
|
||||
static pfnAImageReader_acquireNextImage pAImageReader_acquireNextImage = NULL;
|
||||
static pfnAImageReader_delete pAImageReader_delete = NULL;
|
||||
static pfnAImageReader_setImageListener pAImageReader_setImageListener = NULL;
|
||||
static pfnAImageReader_getWindow pAImageReader_getWindow = NULL;
|
||||
static pfnAImageReader_new pAImageReader_new = NULL;
|
||||
|
||||
typedef media_status_t (*pfnAImage_getWidth)(const AImage*, int32_t*);
|
||||
typedef media_status_t (*pfnAImage_getHeight)(const AImage*, int32_t*);
|
||||
static pfnAImage_getWidth pAImage_getWidth = NULL;
|
||||
static pfnAImage_getHeight pAImage_getHeight = NULL;
|
||||
|
||||
struct SDL_PrivateCameraData
|
||||
{
|
||||
ACameraDevice *device;
|
||||
AImageReader *reader;
|
||||
ANativeWindow *window;
|
||||
ACaptureSessionOutput *sessionOutput;
|
||||
ACaptureSessionOutputContainer *sessionOutputContainer;
|
||||
ACameraOutputTarget *outputTarget;
|
||||
ACaptureRequest *request;
|
||||
ACameraCaptureSession *session;
|
||||
SDL_CameraSpec requested_spec;
|
||||
int rotation; // degrees to rotate clockwise to get from camera's static orientation to device's native orientation. Apply this plus current phone rotation to get upright image!
|
||||
};
|
||||
|
||||
static bool SetErrorStr(const char *what, const char *errstr, const int rc)
|
||||
{
|
||||
char errbuf[128];
|
||||
if (!errstr) {
|
||||
SDL_snprintf(errbuf, sizeof (errbuf), "Unknown error #%d", rc);
|
||||
errstr = errbuf;
|
||||
}
|
||||
return SDL_SetError("%s: %s", what, errstr);
|
||||
}
|
||||
|
||||
static const char *CameraStatusStr(const camera_status_t rc)
|
||||
{
|
||||
switch (rc) {
|
||||
case ACAMERA_OK: return "no error";
|
||||
case ACAMERA_ERROR_UNKNOWN: return "unknown error";
|
||||
case ACAMERA_ERROR_INVALID_PARAMETER: return "invalid parameter";
|
||||
case ACAMERA_ERROR_CAMERA_DISCONNECTED: return "camera disconnected";
|
||||
case ACAMERA_ERROR_NOT_ENOUGH_MEMORY: return "not enough memory";
|
||||
case ACAMERA_ERROR_METADATA_NOT_FOUND: return "metadata not found";
|
||||
case ACAMERA_ERROR_CAMERA_DEVICE: return "camera device error";
|
||||
case ACAMERA_ERROR_CAMERA_SERVICE: return "camera service error";
|
||||
case ACAMERA_ERROR_SESSION_CLOSED: return "session closed";
|
||||
case ACAMERA_ERROR_INVALID_OPERATION: return "invalid operation";
|
||||
case ACAMERA_ERROR_STREAM_CONFIGURE_FAIL: return "configure failure";
|
||||
case ACAMERA_ERROR_CAMERA_IN_USE: return "camera in use";
|
||||
case ACAMERA_ERROR_MAX_CAMERA_IN_USE: return "max cameras in use";
|
||||
case ACAMERA_ERROR_CAMERA_DISABLED: return "camera disabled";
|
||||
case ACAMERA_ERROR_PERMISSION_DENIED: return "permission denied";
|
||||
case ACAMERA_ERROR_UNSUPPORTED_OPERATION: return "unsupported operation";
|
||||
default: break;
|
||||
}
|
||||
|
||||
return NULL; // unknown error
|
||||
}
|
||||
|
||||
static bool SetCameraError(const char *what, const camera_status_t rc)
|
||||
{
|
||||
return SetErrorStr(what, CameraStatusStr(rc), (int) rc);
|
||||
}
|
||||
|
||||
static const char *MediaStatusStr(const media_status_t rc)
|
||||
{
|
||||
switch (rc) {
|
||||
case AMEDIA_OK: return "no error";
|
||||
case AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE: return "insufficient resources";
|
||||
case AMEDIACODEC_ERROR_RECLAIMED: return "reclaimed";
|
||||
case AMEDIA_ERROR_UNKNOWN: return "unknown error";
|
||||
case AMEDIA_ERROR_MALFORMED: return "malformed";
|
||||
case AMEDIA_ERROR_UNSUPPORTED: return "unsupported";
|
||||
case AMEDIA_ERROR_INVALID_OBJECT: return "invalid object";
|
||||
case AMEDIA_ERROR_INVALID_PARAMETER: return "invalid parameter";
|
||||
case AMEDIA_ERROR_INVALID_OPERATION: return "invalid operation";
|
||||
case AMEDIA_ERROR_END_OF_STREAM: return "end of stream";
|
||||
case AMEDIA_ERROR_IO: return "i/o error";
|
||||
case AMEDIA_ERROR_WOULD_BLOCK: return "operation would block";
|
||||
case AMEDIA_DRM_NOT_PROVISIONED: return "DRM not provisioned";
|
||||
case AMEDIA_DRM_RESOURCE_BUSY: return "DRM resource busy";
|
||||
case AMEDIA_DRM_DEVICE_REVOKED: return "DRM device revoked";
|
||||
case AMEDIA_DRM_SHORT_BUFFER: return "DRM short buffer";
|
||||
case AMEDIA_DRM_SESSION_NOT_OPENED: return "DRM session not opened";
|
||||
case AMEDIA_DRM_TAMPER_DETECTED: return "DRM tampering detected";
|
||||
case AMEDIA_DRM_VERIFY_FAILED: return "DRM verify failed";
|
||||
case AMEDIA_DRM_NEED_KEY: return "DRM need key";
|
||||
case AMEDIA_DRM_LICENSE_EXPIRED: return "DRM license expired";
|
||||
case AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE: return "no buffer available";
|
||||
case AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED: return "maximum images acquired";
|
||||
case AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE: return "cannot lock image";
|
||||
case AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE: return "cannot unlock image";
|
||||
case AMEDIA_IMGREADER_IMAGE_NOT_LOCKED: return "image not locked";
|
||||
default: break;
|
||||
}
|
||||
|
||||
return NULL; // unknown error
|
||||
}
|
||||
|
||||
static bool SetMediaError(const char *what, const media_status_t rc)
|
||||
{
|
||||
return SetErrorStr(what, MediaStatusStr(rc), (int) rc);
|
||||
}
|
||||
|
||||
|
||||
static ACameraManager *cameraMgr = NULL;
|
||||
|
||||
static bool CreateCameraManager(void)
|
||||
{
|
||||
SDL_assert(cameraMgr == NULL);
|
||||
|
||||
cameraMgr = pACameraManager_create();
|
||||
if (!cameraMgr) {
|
||||
return SDL_SetError("Error creating ACameraManager");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void DestroyCameraManager(void)
|
||||
{
|
||||
if (cameraMgr) {
|
||||
pACameraManager_delete(cameraMgr);
|
||||
cameraMgr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void format_android_to_sdl(Uint32 fmt, SDL_PixelFormat *format, SDL_Colorspace *colorspace)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(x, y, z) case x: *format = y; *colorspace = z; return
|
||||
CASE(AIMAGE_FORMAT_YUV_420_888, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED);
|
||||
CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565, SDL_COLORSPACE_SRGB);
|
||||
CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_XRGB8888, SDL_COLORSPACE_SRGB);
|
||||
CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888, SDL_COLORSPACE_SRGB);
|
||||
CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888, SDL_COLORSPACE_SRGB);
|
||||
CASE(AIMAGE_FORMAT_RGBA_FP16, SDL_PIXELFORMAT_RGBA64_FLOAT, SDL_COLORSPACE_SRGB);
|
||||
#undef CASE
|
||||
default: break;
|
||||
}
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
//SDL_Log("Unknown format AIMAGE_FORMAT '%d'", fmt);
|
||||
#endif
|
||||
|
||||
*format = SDL_PIXELFORMAT_UNKNOWN;
|
||||
*colorspace = SDL_COLORSPACE_UNKNOWN;
|
||||
}
|
||||
|
||||
static Uint32 format_sdl_to_android(SDL_PixelFormat fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(x, y) case y: return x
|
||||
CASE(AIMAGE_FORMAT_YUV_420_888, SDL_PIXELFORMAT_NV12);
|
||||
CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565);
|
||||
CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_XRGB8888);
|
||||
CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888);
|
||||
CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888);
|
||||
#undef CASE
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ANDROIDCAMERA_WaitDevice(SDL_Camera *device)
|
||||
{
|
||||
return true; // this isn't used atm, since we run our own thread via onImageAvailable callbacks.
|
||||
}
|
||||
|
||||
static SDL_CameraFrameResult ANDROIDCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation)
|
||||
{
|
||||
SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
|
||||
media_status_t res;
|
||||
AImage *image = NULL;
|
||||
|
||||
res = pAImageReader_acquireNextImage(device->hidden->reader, &image);
|
||||
// We could also use this one:
|
||||
//res = AImageReader_acquireLatestImage(device->hidden->reader, &image);
|
||||
|
||||
SDL_assert(res != AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE); // we should only be here if onImageAvailable was called.
|
||||
|
||||
if (res != AMEDIA_OK) {
|
||||
SetMediaError("Error AImageReader_acquireNextImage", res);
|
||||
return SDL_CAMERA_FRAME_ERROR;
|
||||
}
|
||||
|
||||
int64_t atimestamp = 0;
|
||||
if (pAImage_getTimestamp(image, &atimestamp) == AMEDIA_OK) {
|
||||
*timestampNS = (Uint64) atimestamp;
|
||||
} else {
|
||||
*timestampNS = 0;
|
||||
}
|
||||
|
||||
// !!! FIXME: this currently copies the data to the surface (see FIXME about non-contiguous planar surfaces, but in theory we could just keep this locked until ReleaseFrame...
|
||||
int32_t num_planes = 0;
|
||||
pAImage_getNumberOfPlanes(image, &num_planes);
|
||||
|
||||
if ((num_planes == 3) && (device->spec.format == SDL_PIXELFORMAT_NV12)) {
|
||||
num_planes--; // treat the interleaved planes as one.
|
||||
}
|
||||
|
||||
size_t buflen = 0;
|
||||
pAImage_getPlaneRowStride(image, 0, &frame->pitch);
|
||||
for (int i = 0; (i < num_planes) && (i < 3); i++) {
|
||||
int32_t expected;
|
||||
if (i == 0) {
|
||||
expected = frame->pitch * frame->h;
|
||||
} else {
|
||||
expected = frame->pitch * (frame->h + 1) / 2;
|
||||
}
|
||||
buflen += expected;
|
||||
}
|
||||
|
||||
frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
|
||||
if (frame->pixels == NULL) {
|
||||
result = SDL_CAMERA_FRAME_ERROR;
|
||||
} else {
|
||||
Uint8 *dst = frame->pixels;
|
||||
|
||||
for (int i = 0; (i < num_planes) && (i < 3); i++) {
|
||||
uint8_t *data = NULL;
|
||||
int32_t datalen = 0;
|
||||
int32_t expected;
|
||||
if (i == 0) {
|
||||
expected = frame->pitch * frame->h;
|
||||
} else {
|
||||
expected = frame->pitch * (frame->h + 1) / 2;
|
||||
}
|
||||
pAImage_getPlaneData(image, i, &data, &datalen);
|
||||
|
||||
int32_t row_stride = 0;
|
||||
pAImage_getPlaneRowStride(image, i, &row_stride);
|
||||
SDL_assert(row_stride == frame->pitch);
|
||||
SDL_memcpy(dst, data, SDL_min(expected, datalen));
|
||||
dst += expected;
|
||||
}
|
||||
}
|
||||
|
||||
pAImage_delete(image);
|
||||
|
||||
int dev_rotation = 0;
|
||||
switch (Android_JNI_GetDisplayCurrentOrientation()) {
|
||||
case SDL_ORIENTATION_PORTRAIT: dev_rotation = 0; break;
|
||||
case SDL_ORIENTATION_LANDSCAPE: dev_rotation = 90; break;
|
||||
case SDL_ORIENTATION_PORTRAIT_FLIPPED: dev_rotation = 180; break;
|
||||
case SDL_ORIENTATION_LANDSCAPE_FLIPPED: dev_rotation = 270; break;
|
||||
default: SDL_assert(!"Unexpected device rotation!"); dev_rotation = 0; break;
|
||||
}
|
||||
|
||||
if (device->position == SDL_CAMERA_POSITION_BACK_FACING) {
|
||||
dev_rotation = -dev_rotation; // we want to subtract this value, instead of add, if back-facing.
|
||||
}
|
||||
|
||||
*rotation = (float) (dev_rotation + device->hidden->rotation); // current phone orientation, static camera orientation in relation to phone.
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void ANDROIDCAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
|
||||
{
|
||||
// !!! FIXME: this currently copies the data to the surface, but in theory we could just keep the AImage until ReleaseFrame...
|
||||
SDL_aligned_free(frame->pixels);
|
||||
}
|
||||
|
||||
static void onImageAvailable(void *context, AImageReader *reader)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onImageAvailable");
|
||||
#endif
|
||||
SDL_Camera *device = (SDL_Camera *) context;
|
||||
SDL_CameraThreadIterate(device);
|
||||
}
|
||||
|
||||
static void onDisconnected(void *context, ACameraDevice *device)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onDisconnected");
|
||||
#endif
|
||||
SDL_CameraDisconnected((SDL_Camera *) context);
|
||||
}
|
||||
|
||||
static void onError(void *context, ACameraDevice *device, int error)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onError");
|
||||
#endif
|
||||
SDL_CameraDisconnected((SDL_Camera *) context);
|
||||
}
|
||||
|
||||
static void onClosed(void* context, ACameraCaptureSession *session)
|
||||
{
|
||||
// SDL_Camera *_this = (SDL_Camera *) context;
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onClosed");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onReady(void* context, ACameraCaptureSession *session)
|
||||
{
|
||||
// SDL_Camera *_this = (SDL_Camera *) context;
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onReady");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onActive(void* context, ACameraCaptureSession *session)
|
||||
{
|
||||
// SDL_Camera *_this = (SDL_Camera *) context;
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onActive");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ANDROIDCAMERA_CloseDevice(SDL_Camera *device)
|
||||
{
|
||||
if (device && device->hidden) {
|
||||
struct SDL_PrivateCameraData *hidden = device->hidden;
|
||||
device->hidden = NULL;
|
||||
|
||||
if (hidden->reader) {
|
||||
pAImageReader_setImageListener(hidden->reader, NULL);
|
||||
}
|
||||
|
||||
if (hidden->session) {
|
||||
pACameraCaptureSession_close(hidden->session);
|
||||
}
|
||||
|
||||
if (hidden->request) {
|
||||
pACaptureRequest_free(hidden->request);
|
||||
}
|
||||
|
||||
if (hidden->outputTarget) {
|
||||
pACameraOutputTarget_free(hidden->outputTarget);
|
||||
}
|
||||
|
||||
if (hidden->sessionOutputContainer) {
|
||||
pACaptureSessionOutputContainer_free(hidden->sessionOutputContainer);
|
||||
}
|
||||
|
||||
if (hidden->sessionOutput) {
|
||||
pACaptureSessionOutput_free(hidden->sessionOutput);
|
||||
}
|
||||
|
||||
// we don't free hidden->window here, it'll be cleaned up by AImageReader_delete.
|
||||
|
||||
if (hidden->reader) {
|
||||
pAImageReader_delete(hidden->reader);
|
||||
}
|
||||
|
||||
if (hidden->device) {
|
||||
pACameraDevice_close(hidden->device);
|
||||
}
|
||||
|
||||
SDL_free(hidden);
|
||||
}
|
||||
}
|
||||
|
||||
// this is where the "opening" of the camera happens, after permission is granted.
|
||||
static bool PrepareCamera(SDL_Camera *device)
|
||||
{
|
||||
SDL_assert(device->hidden != NULL);
|
||||
|
||||
camera_status_t res;
|
||||
media_status_t res2;
|
||||
|
||||
ACameraDevice_StateCallbacks dev_callbacks;
|
||||
SDL_zero(dev_callbacks);
|
||||
dev_callbacks.context = device;
|
||||
dev_callbacks.onDisconnected = onDisconnected;
|
||||
dev_callbacks.onError = onError;
|
||||
|
||||
ACameraCaptureSession_stateCallbacks capture_callbacks;
|
||||
SDL_zero(capture_callbacks);
|
||||
capture_callbacks.context = device;
|
||||
capture_callbacks.onClosed = onClosed;
|
||||
capture_callbacks.onReady = onReady;
|
||||
capture_callbacks.onActive = onActive;
|
||||
|
||||
AImageReader_ImageListener imglistener;
|
||||
SDL_zero(imglistener);
|
||||
imglistener.context = device;
|
||||
imglistener.onImageAvailable = onImageAvailable;
|
||||
|
||||
|
||||
const char *devid = (const char *) device->handle;
|
||||
|
||||
device->hidden->rotation = 0;
|
||||
ACameraMetadata *metadata = NULL;
|
||||
ACameraMetadata_const_entry orientationentry;
|
||||
if (pACameraManager_getCameraCharacteristics(cameraMgr, devid, &metadata) == ACAMERA_OK) {
|
||||
if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SENSOR_ORIENTATION, &orientationentry) == ACAMERA_OK) {
|
||||
device->hidden->rotation = (int) (*orientationentry.data.i32 % 360);
|
||||
}
|
||||
pACameraMetadata_free(metadata);
|
||||
}
|
||||
|
||||
// just in case SDL_OpenCamera is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy.
|
||||
const SDL_CameraSpec *spec = &device->hidden->requested_spec;
|
||||
|
||||
if ((res = pACameraManager_openCamera(cameraMgr, devid, &dev_callbacks, &device->hidden->device)) != ACAMERA_OK) {
|
||||
return SetCameraError("Failed to open camera", res);
|
||||
} else if ((res2 = pAImageReader_new(spec->width, spec->height, format_sdl_to_android(spec->format), 10 /* nb buffers */, &device->hidden->reader)) != AMEDIA_OK) {
|
||||
return SetMediaError("Error AImageReader_new", res2);
|
||||
} else if ((res2 = pAImageReader_getWindow(device->hidden->reader, &device->hidden->window)) != AMEDIA_OK) {
|
||||
return SetMediaError("Error AImageReader_getWindow", res2);
|
||||
} else if ((res = pACaptureSessionOutput_create(device->hidden->window, &device->hidden->sessionOutput)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACaptureSessionOutput_create", res);
|
||||
} else if ((res = pACaptureSessionOutputContainer_create(&device->hidden->sessionOutputContainer)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACaptureSessionOutputContainer_create", res);
|
||||
} else if ((res = pACaptureSessionOutputContainer_add(device->hidden->sessionOutputContainer, device->hidden->sessionOutput)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACaptureSessionOutputContainer_add", res);
|
||||
} else if ((res = pACameraOutputTarget_create(device->hidden->window, &device->hidden->outputTarget)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACameraOutputTarget_create", res);
|
||||
} else if ((res = pACameraDevice_createCaptureRequest(device->hidden->device, TEMPLATE_RECORD, &device->hidden->request)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACameraDevice_createCaptureRequest", res);
|
||||
} else if ((res = pACaptureRequest_addTarget(device->hidden->request, device->hidden->outputTarget)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACaptureRequest_addTarget", res);
|
||||
} else if ((res = pACameraDevice_createCaptureSession(device->hidden->device, device->hidden->sessionOutputContainer, &capture_callbacks, &device->hidden->session)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACameraDevice_createCaptureSession", res);
|
||||
} else if ((res = pACameraCaptureSession_setRepeatingRequest(device->hidden->session, NULL, 1, &device->hidden->request, NULL)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACameraCaptureSession_setRepeatingRequest", res);
|
||||
} else if ((res2 = pAImageReader_setImageListener(device->hidden->reader, &imglistener)) != AMEDIA_OK) {
|
||||
return SetMediaError("Error AImageReader_setImageListener", res2);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void SDLCALL CameraPermissionCallback(void *userdata, const char *permission, bool granted)
|
||||
{
|
||||
SDL_Camera *device = (SDL_Camera *) userdata;
|
||||
if (device->hidden != NULL) { // if device was already closed, don't send an event.
|
||||
if (!granted) {
|
||||
SDL_CameraPermissionOutcome(device, false); // sorry, permission denied.
|
||||
} else if (!PrepareCamera(device)) { // permission given? Actually open the camera now.
|
||||
// uhoh, setup failed; since the app thinks we already "opened" the device, mark it as disconnected and don't report the permission.
|
||||
SDL_CameraDisconnected(device);
|
||||
} else {
|
||||
// okay! We have permission to use the camera _and_ opening the hardware worked out, report that the camera is usable!
|
||||
SDL_CameraPermissionOutcome(device, true); // go go go!
|
||||
}
|
||||
}
|
||||
|
||||
UnrefPhysicalCamera(device); // we ref'd this in OpenDevice, release the extra reference.
|
||||
}
|
||||
|
||||
|
||||
static bool ANDROIDCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
|
||||
{
|
||||
#if 0 // !!! FIXME: for now, we'll just let this fail if it is going to fail, without checking for this
|
||||
/* Cannot open a second camera, while the first one is opened.
|
||||
* If you want to play several camera, they must all be opened first, then played.
|
||||
*
|
||||
* https://developer.android.com/reference/android/hardware/camera2/CameraManager
|
||||
* "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
|
||||
* before configuring sessions on any of the camera devices. * "
|
||||
*
|
||||
*/
|
||||
if (CheckDevicePlaying()) {
|
||||
return SDL_SetError("A camera is already playing");
|
||||
}
|
||||
#endif
|
||||
|
||||
device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
|
||||
if (device->hidden == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RefPhysicalCamera(device); // ref'd until permission callback fires.
|
||||
|
||||
// just in case SDL_OpenCamera is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy.
|
||||
SDL_copyp(&device->hidden->requested_spec, spec);
|
||||
if (!SDL_RequestAndroidPermission("android.permission.CAMERA", CameraPermissionCallback, device)) {
|
||||
UnrefPhysicalCamera(device);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // we don't open the camera until permission is granted, so always succeed for now.
|
||||
}
|
||||
|
||||
static void ANDROIDCAMERA_FreeDeviceHandle(SDL_Camera *device)
|
||||
{
|
||||
if (device) {
|
||||
SDL_free(device->handle);
|
||||
}
|
||||
}
|
||||
|
||||
static void GatherCameraSpecs(const char *devid, CameraFormatAddData *add_data, char **fullname, SDL_CameraPosition *position)
|
||||
{
|
||||
SDL_zerop(add_data);
|
||||
|
||||
ACameraMetadata *metadata = NULL;
|
||||
ACameraMetadata_const_entry cfgentry;
|
||||
ACameraMetadata_const_entry durentry;
|
||||
ACameraMetadata_const_entry infoentry;
|
||||
|
||||
// This can fail with an "unknown error" (with `adb logcat` reporting "no such file or directory")
|
||||
// for "LEGACY" level cameras. I saw this happen on a 30-dollar budget phone I have for testing
|
||||
// (but a different brand budget phone worked, so it's not strictly the low-end of Android devices).
|
||||
// LEGACY devices are seen by onCameraAvailable, but are not otherwise accessible through
|
||||
// libcamera2ndk. The Java camera2 API apparently _can_ access these cameras, but we're going on
|
||||
// without them here for now, in hopes that such hardware is a dying breed.
|
||||
if (pACameraManager_getCameraCharacteristics(cameraMgr, devid, &metadata) != ACAMERA_OK) {
|
||||
return; // oh well.
|
||||
} else if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &cfgentry) != ACAMERA_OK) {
|
||||
pACameraMetadata_free(metadata);
|
||||
return; // oh well.
|
||||
} else if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS, &durentry) != ACAMERA_OK) {
|
||||
pACameraMetadata_free(metadata);
|
||||
return; // oh well.
|
||||
}
|
||||
|
||||
*fullname = NULL;
|
||||
if (pACameraMetadata_getConstEntry(metadata, ACAMERA_INFO_VERSION, &infoentry) == ACAMERA_OK) {
|
||||
*fullname = (char *) SDL_malloc(infoentry.count + 1);
|
||||
if (*fullname) {
|
||||
SDL_strlcpy(*fullname, (const char *) infoentry.data.u8, infoentry.count + 1);
|
||||
}
|
||||
}
|
||||
|
||||
ACameraMetadata_const_entry posentry;
|
||||
if (pACameraMetadata_getConstEntry(metadata, ACAMERA_LENS_FACING, &posentry) == ACAMERA_OK) { // ignore this if it fails.
|
||||
if (*posentry.data.u8 == ACAMERA_LENS_FACING_FRONT) {
|
||||
*position = SDL_CAMERA_POSITION_FRONT_FACING;
|
||||
if (!*fullname) {
|
||||
*fullname = SDL_strdup("Front-facing camera");
|
||||
}
|
||||
} else if (*posentry.data.u8 == ACAMERA_LENS_FACING_BACK) {
|
||||
*position = SDL_CAMERA_POSITION_BACK_FACING;
|
||||
if (!*fullname) {
|
||||
*fullname = SDL_strdup("Back-facing camera");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!*fullname) {
|
||||
*fullname = SDL_strdup("Generic camera"); // we tried.
|
||||
}
|
||||
|
||||
const int32_t *i32ptr = cfgentry.data.i32;
|
||||
for (int i = 0; i < cfgentry.count; i++, i32ptr += 4) {
|
||||
const int32_t fmt = i32ptr[0];
|
||||
const int w = i32ptr[1];
|
||||
const int h = i32ptr[2];
|
||||
const int32_t type = i32ptr[3];
|
||||
SDL_PixelFormat sdlfmt = SDL_PIXELFORMAT_UNKNOWN;
|
||||
SDL_Colorspace colorspace = SDL_COLORSPACE_UNKNOWN;
|
||||
|
||||
if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) {
|
||||
continue;
|
||||
} else if ((w <= 0) || (h <= 0)) {
|
||||
continue;
|
||||
} else {
|
||||
format_android_to_sdl(fmt, &sdlfmt, &colorspace);
|
||||
if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0 // !!! FIXME: these all come out with 0 durations on my test phone. :(
|
||||
const int64_t *i64ptr = durentry.data.i64;
|
||||
for (int j = 0; j < durentry.count; j++, i64ptr += 4) {
|
||||
const int32_t fpsfmt = (int32_t) i64ptr[0];
|
||||
const int fpsw = (int) i64ptr[1];
|
||||
const int fpsh = (int) i64ptr[2];
|
||||
const long long duration = (long long) i64ptr[3];
|
||||
SDL_Log("CAMERA: possible fps %s %dx%d duration=%lld", SDL_GetPixelFormatName(sdlfmt), fpsw, fpsh, duration);
|
||||
if ((duration > 0) && (fpsfmt == fmt) && (fpsw == w) && (fpsh == h)) {
|
||||
SDL_AddCameraFormat(add_data, sdlfmt, colorspace, w, h, 1000000000, duration);
|
||||
}
|
||||
}
|
||||
#else
|
||||
SDL_AddCameraFormat(add_data, sdlfmt, colorspace, w, h, 30, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
pACameraMetadata_free(metadata);
|
||||
}
|
||||
|
||||
static bool FindAndroidCameraByID(SDL_Camera *device, void *userdata)
|
||||
{
|
||||
const char *devid = (const char *) userdata;
|
||||
return (SDL_strcmp(devid, (const char *) device->handle) == 0);
|
||||
}
|
||||
|
||||
static void MaybeAddDevice(const char *devid)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: MaybeAddDevice('%s')", devid);
|
||||
#endif
|
||||
|
||||
if (SDL_FindPhysicalCameraByCallback(FindAndroidCameraByID, (void *) devid)) {
|
||||
return; // already have this one.
|
||||
}
|
||||
|
||||
SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
|
||||
char *fullname = NULL;
|
||||
CameraFormatAddData add_data;
|
||||
GatherCameraSpecs(devid, &add_data, &fullname, &position);
|
||||
if (add_data.num_specs > 0) {
|
||||
char *namecpy = SDL_strdup(devid);
|
||||
if (namecpy) {
|
||||
SDL_Camera *device = SDL_AddCamera(fullname, position, add_data.num_specs, add_data.specs, namecpy);
|
||||
if (!device) {
|
||||
SDL_free(namecpy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(fullname);
|
||||
SDL_free(add_data.specs);
|
||||
}
|
||||
|
||||
// note that camera "availability" covers both hotplugging and whether another
|
||||
// has the device opened, but for something like Android, it's probably fine
|
||||
// to treat both unplugging and loss of access as disconnection events. When
|
||||
// the other app closes the camera, we get an available event as if it was
|
||||
// just plugged back in.
|
||||
|
||||
static void onCameraAvailable(void *context, const char *cameraId)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onCameraAvailable('%s')", cameraId);
|
||||
#endif
|
||||
SDL_assert(cameraId != NULL);
|
||||
MaybeAddDevice(cameraId);
|
||||
}
|
||||
|
||||
static void onCameraUnavailable(void *context, const char *cameraId)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onCameraUnavailable('%s')", cameraId);
|
||||
#endif
|
||||
|
||||
SDL_assert(cameraId != NULL);
|
||||
|
||||
// THIS CALLBACK FIRES WHEN YOU OPEN THE DEVICE YOURSELF. :(
|
||||
// Make sure we don't have the device opened, in which case onDisconnected will fire instead if actually lost.
|
||||
SDL_Camera *device = SDL_FindPhysicalCameraByCallback(FindAndroidCameraByID, (void *) cameraId);
|
||||
if (device && !device->hidden) {
|
||||
SDL_CameraDisconnected(device);
|
||||
}
|
||||
}
|
||||
|
||||
static const ACameraManager_AvailabilityCallbacks camera_availability_listener = {
|
||||
NULL,
|
||||
onCameraAvailable,
|
||||
onCameraUnavailable
|
||||
};
|
||||
|
||||
static void ANDROIDCAMERA_DetectDevices(void)
|
||||
{
|
||||
ACameraIdList *list = NULL;
|
||||
camera_status_t res = pACameraManager_getCameraIdList(cameraMgr, &list);
|
||||
|
||||
if ((res == ACAMERA_OK) && list) {
|
||||
const int total = list->numCameras;
|
||||
for (int i = 0; i < total; i++) {
|
||||
MaybeAddDevice(list->cameraIds[i]);
|
||||
}
|
||||
|
||||
pACameraManager_deleteCameraIdList(list);
|
||||
}
|
||||
|
||||
pACameraManager_registerAvailabilityCallback(cameraMgr, &camera_availability_listener);
|
||||
}
|
||||
|
||||
static void ANDROIDCAMERA_Deinitialize(void)
|
||||
{
|
||||
pACameraManager_unregisterAvailabilityCallback(cameraMgr, &camera_availability_listener);
|
||||
DestroyCameraManager();
|
||||
|
||||
dlclose(libcamera2ndk);
|
||||
libcamera2ndk = NULL;
|
||||
pACameraManager_create = NULL;
|
||||
pACameraManager_registerAvailabilityCallback = NULL;
|
||||
pACameraManager_unregisterAvailabilityCallback = NULL;
|
||||
pACameraManager_getCameraIdList = NULL;
|
||||
pACameraManager_deleteCameraIdList = NULL;
|
||||
pACameraCaptureSession_close = NULL;
|
||||
pACaptureRequest_free = NULL;
|
||||
pACameraOutputTarget_free = NULL;
|
||||
pACameraDevice_close = NULL;
|
||||
pACameraManager_delete = NULL;
|
||||
pACaptureSessionOutputContainer_free = NULL;
|
||||
pACaptureSessionOutput_free = NULL;
|
||||
pACameraManager_openCamera = NULL;
|
||||
pACameraDevice_createCaptureRequest = NULL;
|
||||
pACameraDevice_createCaptureSession = NULL;
|
||||
pACameraManager_getCameraCharacteristics = NULL;
|
||||
pACameraMetadata_free = NULL;
|
||||
pACameraMetadata_getConstEntry = NULL;
|
||||
pACameraCaptureSession_setRepeatingRequest = NULL;
|
||||
pACameraOutputTarget_create = NULL;
|
||||
pACaptureRequest_addTarget = NULL;
|
||||
pACaptureSessionOutputContainer_add = NULL;
|
||||
pACaptureSessionOutputContainer_create = NULL;
|
||||
pACaptureSessionOutput_create = NULL;
|
||||
|
||||
dlclose(libmediandk);
|
||||
libmediandk = NULL;
|
||||
pAImage_delete = NULL;
|
||||
pAImage_getTimestamp = NULL;
|
||||
pAImage_getNumberOfPlanes = NULL;
|
||||
pAImage_getPlaneRowStride = NULL;
|
||||
pAImage_getPlaneData = NULL;
|
||||
pAImageReader_acquireNextImage = NULL;
|
||||
pAImageReader_delete = NULL;
|
||||
pAImageReader_setImageListener = NULL;
|
||||
pAImageReader_getWindow = NULL;
|
||||
pAImageReader_new = NULL;
|
||||
}
|
||||
|
||||
static bool ANDROIDCAMERA_Init(SDL_CameraDriverImpl *impl)
|
||||
{
|
||||
// !!! FIXME: slide this off into a subroutine
|
||||
// system libraries are in android-24 and later; we currently target android-21 and later, so check if they exist at runtime.
|
||||
void *libcamera2 = dlopen("libcamera2ndk.so", RTLD_NOW | RTLD_LOCAL);
|
||||
if (!libcamera2) {
|
||||
SDL_Log("CAMERA: libcamera2ndk.so can't be loaded: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
|
||||
void *libmedia = dlopen("libmediandk.so", RTLD_NOW | RTLD_LOCAL);
|
||||
if (!libmedia) {
|
||||
SDL_Log("CAMERA: libmediandk.so can't be loaded: %s", dlerror());
|
||||
dlclose(libcamera2);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool okay = true;
|
||||
#define LOADSYM(lib, fn) if (okay) { p##fn = (pfn##fn) dlsym(lib, #fn); if (!p##fn) { SDL_Log("CAMERA: symbol '%s' can't be found in %s: %s", #fn, #lib "ndk.so", dlerror()); okay = false; } }
|
||||
//#define LOADSYM(lib, fn) p##fn = (pfn##fn) fn
|
||||
LOADSYM(libcamera2, ACameraManager_create);
|
||||
LOADSYM(libcamera2, ACameraManager_registerAvailabilityCallback);
|
||||
LOADSYM(libcamera2, ACameraManager_unregisterAvailabilityCallback);
|
||||
LOADSYM(libcamera2, ACameraManager_getCameraIdList);
|
||||
LOADSYM(libcamera2, ACameraManager_deleteCameraIdList);
|
||||
LOADSYM(libcamera2, ACameraCaptureSession_close);
|
||||
LOADSYM(libcamera2, ACaptureRequest_free);
|
||||
LOADSYM(libcamera2, ACameraOutputTarget_free);
|
||||
LOADSYM(libcamera2, ACameraDevice_close);
|
||||
LOADSYM(libcamera2, ACameraManager_delete);
|
||||
LOADSYM(libcamera2, ACaptureSessionOutputContainer_free);
|
||||
LOADSYM(libcamera2, ACaptureSessionOutput_free);
|
||||
LOADSYM(libcamera2, ACameraManager_openCamera);
|
||||
LOADSYM(libcamera2, ACameraDevice_createCaptureRequest);
|
||||
LOADSYM(libcamera2, ACameraDevice_createCaptureSession);
|
||||
LOADSYM(libcamera2, ACameraManager_getCameraCharacteristics);
|
||||
LOADSYM(libcamera2, ACameraMetadata_free);
|
||||
LOADSYM(libcamera2, ACameraMetadata_getConstEntry);
|
||||
LOADSYM(libcamera2, ACameraCaptureSession_setRepeatingRequest);
|
||||
LOADSYM(libcamera2, ACameraOutputTarget_create);
|
||||
LOADSYM(libcamera2, ACaptureRequest_addTarget);
|
||||
LOADSYM(libcamera2, ACaptureSessionOutputContainer_add);
|
||||
LOADSYM(libcamera2, ACaptureSessionOutputContainer_create);
|
||||
LOADSYM(libcamera2, ACaptureSessionOutput_create);
|
||||
LOADSYM(libmedia, AImage_delete);
|
||||
LOADSYM(libmedia, AImage_getTimestamp);
|
||||
LOADSYM(libmedia, AImage_getNumberOfPlanes);
|
||||
LOADSYM(libmedia, AImage_getPlaneRowStride);
|
||||
LOADSYM(libmedia, AImage_getPlaneData);
|
||||
LOADSYM(libmedia, AImageReader_acquireNextImage);
|
||||
LOADSYM(libmedia, AImageReader_delete);
|
||||
LOADSYM(libmedia, AImageReader_setImageListener);
|
||||
LOADSYM(libmedia, AImageReader_getWindow);
|
||||
LOADSYM(libmedia, AImageReader_new);
|
||||
LOADSYM(libmedia, AImage_getWidth);
|
||||
LOADSYM(libmedia, AImage_getHeight);
|
||||
|
||||
#undef LOADSYM
|
||||
|
||||
if (!okay) {
|
||||
dlclose(libmedia);
|
||||
dlclose(libcamera2);
|
||||
}
|
||||
|
||||
if (!CreateCameraManager()) {
|
||||
dlclose(libmedia);
|
||||
dlclose(libcamera2);
|
||||
return false;
|
||||
}
|
||||
|
||||
libcamera2ndk = libcamera2;
|
||||
libmediandk = libmedia;
|
||||
|
||||
impl->DetectDevices = ANDROIDCAMERA_DetectDevices;
|
||||
impl->OpenDevice = ANDROIDCAMERA_OpenDevice;
|
||||
impl->CloseDevice = ANDROIDCAMERA_CloseDevice;
|
||||
impl->WaitDevice = ANDROIDCAMERA_WaitDevice;
|
||||
impl->AcquireFrame = ANDROIDCAMERA_AcquireFrame;
|
||||
impl->ReleaseFrame = ANDROIDCAMERA_ReleaseFrame;
|
||||
impl->FreeDeviceHandle = ANDROIDCAMERA_FreeDeviceHandle;
|
||||
impl->Deinitialize = ANDROIDCAMERA_Deinitialize;
|
||||
|
||||
impl->ProvidesOwnCallbackThread = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CameraBootStrap ANDROIDCAMERA_bootstrap = {
|
||||
"android", "SDL Android camera driver", ANDROIDCAMERA_Init, false
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,639 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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>
|
||||
|
||||
#if defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_TVOS)
|
||||
#define USE_UIKIT_DEVICE_ROTATION
|
||||
#endif
|
||||
|
||||
#ifdef USE_UIKIT_DEVICE_ROTATION
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Need to link with:: CoreMedia CoreVideo
|
||||
*
|
||||
* Add in pInfo.list:
|
||||
* <key>NSCameraUsageDescription</key> <string>Access camera</string>
|
||||
*
|
||||
*
|
||||
* MACOSX:
|
||||
* Add to the Code Sign Entitlement file:
|
||||
* <key>com.apple.security.device.camera</key> <true/>
|
||||
*/
|
||||
|
||||
static void CoreMediaFormatToSDL(FourCharCode fmt, SDL_PixelFormat *pixel_format, SDL_Colorspace *colorspace)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(x, y, z) case x: *pixel_format = y; *colorspace = z; return
|
||||
// the 16LE ones should use 16BE if we're on a Bigendian system like PowerPC,
|
||||
// but at current time there is no bigendian Apple platform that has CoreMedia.
|
||||
CASE(kCMPixelFormat_16LE555, SDL_PIXELFORMAT_XRGB1555, SDL_COLORSPACE_SRGB);
|
||||
CASE(kCMPixelFormat_16LE5551, SDL_PIXELFORMAT_RGBA5551, SDL_COLORSPACE_SRGB);
|
||||
CASE(kCMPixelFormat_16LE565, SDL_PIXELFORMAT_RGB565, SDL_COLORSPACE_SRGB);
|
||||
CASE(kCMPixelFormat_24RGB, SDL_PIXELFORMAT_RGB24, SDL_COLORSPACE_SRGB);
|
||||
CASE(kCMPixelFormat_32ARGB, SDL_PIXELFORMAT_ARGB32, SDL_COLORSPACE_SRGB);
|
||||
CASE(kCMPixelFormat_32BGRA, SDL_PIXELFORMAT_BGRA32, SDL_COLORSPACE_SRGB);
|
||||
CASE(kCMPixelFormat_422YpCbCr8, SDL_PIXELFORMAT_UYVY, SDL_COLORSPACE_BT709_LIMITED);
|
||||
CASE(kCMPixelFormat_422YpCbCr8_yuvs, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED);
|
||||
CASE(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_LIMITED);
|
||||
CASE(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, SDL_PIXELFORMAT_NV12, SDL_COLORSPACE_BT709_FULL);
|
||||
CASE(kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange, SDL_PIXELFORMAT_P010, SDL_COLORSPACE_BT2020_LIMITED);
|
||||
CASE(kCVPixelFormatType_420YpCbCr10BiPlanarFullRange, SDL_PIXELFORMAT_P010, SDL_COLORSPACE_BT2020_FULL);
|
||||
#undef CASE
|
||||
default:
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: Unknown format FourCharCode '%d'", (int) fmt);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
*pixel_format = SDL_PIXELFORMAT_UNKNOWN;
|
||||
*colorspace = SDL_COLORSPACE_UNKNOWN;
|
||||
}
|
||||
|
||||
@class SDLCaptureVideoDataOutputSampleBufferDelegate;
|
||||
|
||||
// just a simple wrapper to help ARC manage memory...
|
||||
@interface SDLPrivateCameraData : NSObject
|
||||
@property(nonatomic, retain) AVCaptureSession *session;
|
||||
@property(nonatomic, retain) SDLCaptureVideoDataOutputSampleBufferDelegate *delegate;
|
||||
@property(nonatomic, assign) CMSampleBufferRef current_sample;
|
||||
#ifdef USE_UIKIT_DEVICE_ROTATION
|
||||
@property(nonatomic, assign) UIDeviceOrientation last_device_orientation;
|
||||
#endif
|
||||
@end
|
||||
|
||||
@implementation SDLPrivateCameraData
|
||||
@end
|
||||
|
||||
|
||||
static bool CheckCameraPermissions(SDL_Camera *device)
|
||||
{
|
||||
if (device->permission == SDL_CAMERA_PERMISSION_STATE_PENDING) { // still expecting a permission result.
|
||||
if (@available(macOS 14, *)) {
|
||||
const AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
||||
if (status != AVAuthorizationStatusNotDetermined) { // NotDetermined == still waiting for an answer from the user.
|
||||
SDL_CameraPermissionOutcome(device, (status == AVAuthorizationStatusAuthorized) ? true : false);
|
||||
}
|
||||
} else {
|
||||
SDL_CameraPermissionOutcome(device, true); // always allowed (or just unqueryable...?) on older macOS.
|
||||
}
|
||||
}
|
||||
|
||||
return (device->permission > SDL_CAMERA_PERMISSION_STATE_PENDING);
|
||||
}
|
||||
|
||||
// this delegate just receives new video frames on a Grand Central Dispatch queue, and fires off the
|
||||
// main device thread iterate function directly to consume it.
|
||||
@interface SDLCaptureVideoDataOutputSampleBufferDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
|
||||
@property SDL_Camera *device;
|
||||
-(id) init:(SDL_Camera *) dev;
|
||||
-(void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
|
||||
@end
|
||||
|
||||
@implementation SDLCaptureVideoDataOutputSampleBufferDelegate
|
||||
|
||||
-(id) init:(SDL_Camera *) dev {
|
||||
if ( self = [super init] ) {
|
||||
_device = dev;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
|
||||
{
|
||||
SDL_Camera *device = self.device;
|
||||
if (!device || !device->hidden) {
|
||||
return; // oh well.
|
||||
}
|
||||
|
||||
if (!CheckCameraPermissions(device)) {
|
||||
return; // nothing to do right now, dump what is probably a completely black frame.
|
||||
}
|
||||
|
||||
SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
|
||||
hidden.current_sample = sampleBuffer;
|
||||
SDL_CameraThreadIterate(device);
|
||||
hidden.current_sample = NULL;
|
||||
}
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)output didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: Drop frame.");
|
||||
#endif
|
||||
}
|
||||
@end
|
||||
|
||||
static bool COREMEDIA_WaitDevice(SDL_Camera *device)
|
||||
{
|
||||
return true; // this isn't used atm, since we run our own thread out of Grand Central Dispatch.
|
||||
}
|
||||
|
||||
static SDL_CameraFrameResult COREMEDIA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation)
|
||||
{
|
||||
SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
|
||||
SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
|
||||
CMSampleBufferRef sample_buffer = hidden.current_sample;
|
||||
hidden.current_sample = NULL;
|
||||
SDL_assert(sample_buffer != NULL); // should only have been called from our delegate with a new frame.
|
||||
|
||||
CMSampleTimingInfo timinginfo;
|
||||
if (CMSampleBufferGetSampleTimingInfo(sample_buffer, 0, &timinginfo) == noErr) {
|
||||
*timestampNS = (Uint64) (CMTimeGetSeconds(timinginfo.presentationTimeStamp) * ((Float64) SDL_NS_PER_SECOND));
|
||||
} else {
|
||||
SDL_assert(!"this shouldn't happen, I think.");
|
||||
*timestampNS = 0;
|
||||
}
|
||||
|
||||
CVImageBufferRef image = CMSampleBufferGetImageBuffer(sample_buffer); // does not retain `image` (and we don't want it to).
|
||||
const int numPlanes = (int) CVPixelBufferGetPlaneCount(image);
|
||||
const int planar = (int) CVPixelBufferIsPlanar(image);
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
const int w = (int) CVPixelBufferGetWidth(image);
|
||||
const int h = (int) CVPixelBufferGetHeight(image);
|
||||
const int sz = (int) CVPixelBufferGetDataSize(image);
|
||||
const int pitch = (int) CVPixelBufferGetBytesPerRow(image);
|
||||
SDL_Log("CAMERA: buffer planar=%d numPlanes=%d %d x %d sz=%d pitch=%d", planar, numPlanes, w, h, sz, pitch);
|
||||
#endif
|
||||
|
||||
// !!! FIXME: this currently copies the data to the surface (see FIXME about non-contiguous planar surfaces, but in theory we could just keep this locked until ReleaseFrame...
|
||||
CVPixelBufferLockBaseAddress(image, 0);
|
||||
|
||||
frame->w = (int)CVPixelBufferGetWidth(image);
|
||||
frame->h = (int)CVPixelBufferGetHeight(image);
|
||||
|
||||
if ((planar == 0) && (numPlanes == 0)) {
|
||||
const int pitch = (int) CVPixelBufferGetBytesPerRow(image);
|
||||
const size_t buflen = pitch * frame->h;
|
||||
frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
|
||||
if (frame->pixels == NULL) {
|
||||
result = SDL_CAMERA_FRAME_ERROR;
|
||||
} else {
|
||||
frame->pitch = pitch;
|
||||
SDL_memcpy(frame->pixels, CVPixelBufferGetBaseAddress(image), buflen);
|
||||
}
|
||||
} else {
|
||||
// !!! FIXME: we have an open issue in SDL3 to allow SDL_Surface to support non-contiguous planar data, but we don't have it yet.
|
||||
size_t buflen = 0;
|
||||
for (int i = 0; i < numPlanes; i++) {
|
||||
size_t plane_height = CVPixelBufferGetHeightOfPlane(image, i);
|
||||
size_t plane_pitch = CVPixelBufferGetBytesPerRowOfPlane(image, i);
|
||||
size_t plane_size = (plane_pitch * plane_height);
|
||||
buflen += plane_size;
|
||||
}
|
||||
|
||||
frame->pitch = (int)CVPixelBufferGetBytesPerRowOfPlane(image, 0); // this is what SDL3 currently expects
|
||||
frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), buflen);
|
||||
if (frame->pixels == NULL) {
|
||||
result = SDL_CAMERA_FRAME_ERROR;
|
||||
} else {
|
||||
Uint8 *dst = frame->pixels;
|
||||
for (int i = 0; i < numPlanes; i++) {
|
||||
const void *src = CVPixelBufferGetBaseAddressOfPlane(image, i);
|
||||
size_t plane_height = CVPixelBufferGetHeightOfPlane(image, i);
|
||||
size_t plane_pitch = CVPixelBufferGetBytesPerRowOfPlane(image, i);
|
||||
size_t plane_size = (plane_pitch * plane_height);
|
||||
SDL_memcpy(dst, src, plane_size);
|
||||
dst += plane_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CVPixelBufferUnlockBaseAddress(image, 0);
|
||||
|
||||
#ifdef USE_UIKIT_DEVICE_ROTATION
|
||||
UIDeviceOrientation device_orientation = [[UIDevice currentDevice] orientation];
|
||||
if (!UIDeviceOrientationIsValidInterfaceOrientation(device_orientation)) {
|
||||
device_orientation = hidden.last_device_orientation; // possible the phone is laying flat or something went wrong, just stay with the last known-good orientation.
|
||||
} else {
|
||||
hidden.last_device_orientation = device_orientation; // update the last known-good orientation for later.
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
const UIInterfaceOrientation ui_orientation = [UIApplication sharedApplication].statusBarOrientation;
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
// there is probably math for this, but this is easy to slap into a table.
|
||||
// rotation = rotations[uiorientation-1][devorientation-1];
|
||||
if (device->position == SDL_CAMERA_POSITION_BACK_FACING) {
|
||||
static const Uint16 back_rotations[4][4] = {
|
||||
{ 90, 90, 90, 90 }, // ui portrait
|
||||
{ 270, 270, 270, 270 }, // ui portrait upside down
|
||||
{ 0, 0, 0, 0 }, // ui landscape left
|
||||
{ 180, 180, 180, 180 } // ui landscape right
|
||||
};
|
||||
*rotation = (float) back_rotations[ui_orientation - 1][device_orientation - 1];
|
||||
} else {
|
||||
static const Uint16 front_rotations[4][4] = {
|
||||
{ 90, 90, 270, 270 }, // ui portrait
|
||||
{ 270, 270, 90, 90 }, // ui portrait upside down
|
||||
{ 0, 0, 180, 180 }, // ui landscape left
|
||||
{ 180, 180, 0, 0 } // ui landscape right
|
||||
};
|
||||
*rotation = (float) front_rotations[ui_orientation - 1][device_orientation - 1];
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void COREMEDIA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
|
||||
{
|
||||
// !!! FIXME: this currently copies the data to the surface, but in theory we could just keep this locked until ReleaseFrame...
|
||||
SDL_aligned_free(frame->pixels);
|
||||
}
|
||||
|
||||
static void COREMEDIA_CloseDevice(SDL_Camera *device)
|
||||
{
|
||||
if (device && device->hidden) {
|
||||
#ifdef USE_UIKIT_DEVICE_ROTATION
|
||||
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
|
||||
#endif
|
||||
|
||||
SDLPrivateCameraData *hidden = (SDLPrivateCameraData *) CFBridgingRelease(device->hidden);
|
||||
device->hidden = NULL;
|
||||
|
||||
AVCaptureSession *session = hidden.session;
|
||||
if (session) {
|
||||
hidden.session = nil;
|
||||
[session stopRunning];
|
||||
[session removeInput:[session.inputs objectAtIndex:0]];
|
||||
[session removeOutput:(AVCaptureVideoDataOutput *)[session.outputs objectAtIndex:0]];
|
||||
session = nil;
|
||||
}
|
||||
|
||||
hidden.delegate = NULL;
|
||||
hidden.current_sample = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
|
||||
{
|
||||
AVCaptureDevice *avdevice = (__bridge AVCaptureDevice *) device->handle;
|
||||
|
||||
// Pick format that matches the spec
|
||||
const int w = spec->width;
|
||||
const int h = spec->height;
|
||||
const float rate = (float)spec->framerate_numerator / spec->framerate_denominator;
|
||||
AVCaptureDeviceFormat *spec_format = nil;
|
||||
NSArray<AVCaptureDeviceFormat *> *formats = [avdevice formats];
|
||||
for (AVCaptureDeviceFormat *format in formats) {
|
||||
CMFormatDescriptionRef formatDescription = [format formatDescription];
|
||||
SDL_PixelFormat device_format = SDL_PIXELFORMAT_UNKNOWN;
|
||||
SDL_Colorspace device_colorspace = SDL_COLORSPACE_UNKNOWN;
|
||||
CoreMediaFormatToSDL(CMFormatDescriptionGetMediaSubType(formatDescription), &device_format, &device_colorspace);
|
||||
if (device_format != spec->format || device_colorspace != spec->colorspace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDescription);
|
||||
if ((int)dim.width != w || (int)dim.height != h) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float FRAMERATE_EPSILON = 0.01f;
|
||||
for (AVFrameRateRange *framerate in format.videoSupportedFrameRateRanges) {
|
||||
// Check if the requested rate is within the supported range
|
||||
if (rate >= (framerate.minFrameRate - FRAMERATE_EPSILON) &&
|
||||
rate <= (framerate.maxFrameRate + FRAMERATE_EPSILON)) {
|
||||
|
||||
// Prefer formats with narrower frame rate ranges that are closer to our target
|
||||
// This helps avoid formats that support a wide range (like 10-60 FPS)
|
||||
// when we want a specific rate (like 30 FPS)
|
||||
bool should_select = false;
|
||||
if (spec_format == nil) {
|
||||
should_select = true;
|
||||
} else {
|
||||
AVFrameRateRange *current_range = spec_format.videoSupportedFrameRateRanges.firstObject;
|
||||
float current_range_width = current_range.maxFrameRate - current_range.minFrameRate;
|
||||
float new_range_width = framerate.maxFrameRate - framerate.minFrameRate;
|
||||
|
||||
// Prefer formats with narrower ranges, or if ranges are similar, prefer closer to target
|
||||
if (new_range_width < current_range_width) {
|
||||
should_select = true;
|
||||
} else if (SDL_fabsf(new_range_width - current_range_width) < 0.1f) {
|
||||
// Similar range width, prefer the one closer to our target rate
|
||||
float current_distance = SDL_fabsf(rate - current_range.minFrameRate);
|
||||
float new_distance = SDL_fabsf(rate - framerate.minFrameRate);
|
||||
if (new_distance < current_distance) {
|
||||
should_select = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (should_select) {
|
||||
spec_format = format;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Try to set the frame duration to enforce the requested frame rate
|
||||
const CMTime frameDuration = CMTimeMake(spec->framerate_denominator, spec->framerate_numerator);
|
||||
|
||||
// Check if the device supports setting frame duration
|
||||
if ([avdevice respondsToSelector:@selector(setActiveVideoMinFrameDuration:)] &&
|
||||
[avdevice respondsToSelector:@selector(setActiveVideoMaxFrameDuration:)]) {
|
||||
@try {
|
||||
avdevice.activeVideoMinFrameDuration = frameDuration;
|
||||
avdevice.activeVideoMaxFrameDuration = frameDuration;
|
||||
} @catch (NSException *exception) {
|
||||
// Some devices don't support setting frame duration, that's okay
|
||||
}
|
||||
}
|
||||
|
||||
[avdevice unlockForConfiguration];
|
||||
|
||||
AVCaptureSession *session = [[AVCaptureSession alloc] init];
|
||||
if (session == nil) {
|
||||
return SDL_SetError("Failed to allocate/init AVCaptureSession");
|
||||
}
|
||||
|
||||
session.sessionPreset = AVCaptureSessionPresetHigh;
|
||||
#if defined(SDL_PLATFORM_IOS)
|
||||
if (@available(iOS 10.0, tvOS 17.0, *)) {
|
||||
session.automaticallyConfiguresCaptureDeviceForWideColor = NO;
|
||||
}
|
||||
#endif
|
||||
|
||||
NSError *error = nil;
|
||||
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:avdevice error:&error];
|
||||
if (!input) {
|
||||
return SDL_SetError("Cannot create AVCaptureDeviceInput");
|
||||
}
|
||||
|
||||
AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
|
||||
if (!output) {
|
||||
return SDL_SetError("Cannot create AVCaptureVideoDataOutput");
|
||||
}
|
||||
|
||||
output.videoSettings = @{
|
||||
(id)kCVPixelBufferWidthKey : @(spec->width),
|
||||
(id)kCVPixelBufferHeightKey : @(spec->height),
|
||||
(id)kCVPixelBufferPixelFormatTypeKey : @(CMFormatDescriptionGetMediaSubType([spec_format formatDescription]))
|
||||
};
|
||||
|
||||
char threadname[64];
|
||||
SDL_GetCameraThreadName(device, threadname, sizeof (threadname));
|
||||
dispatch_queue_t queue = dispatch_queue_create(threadname, NULL);
|
||||
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
if (!queue) {
|
||||
return SDL_SetError("dispatch_queue_create() failed");
|
||||
}
|
||||
|
||||
SDLCaptureVideoDataOutputSampleBufferDelegate *delegate = [[SDLCaptureVideoDataOutputSampleBufferDelegate alloc] init:device];
|
||||
if (delegate == nil) {
|
||||
return SDL_SetError("Cannot create SDLCaptureVideoDataOutputSampleBufferDelegate");
|
||||
}
|
||||
[output setSampleBufferDelegate:delegate queue:queue];
|
||||
|
||||
if (![session canAddInput:input]) {
|
||||
return SDL_SetError("Cannot add AVCaptureDeviceInput");
|
||||
}
|
||||
[session addInput:input];
|
||||
|
||||
if (![session canAddOutput:output]) {
|
||||
return SDL_SetError("Cannot add AVCaptureVideoDataOutput");
|
||||
}
|
||||
[session addOutput:output];
|
||||
|
||||
// Try to set the frame rate on the device (preferred modern approach)
|
||||
if ([avdevice lockForConfiguration:nil]) {
|
||||
@try {
|
||||
avdevice.activeVideoMinFrameDuration = frameDuration;
|
||||
avdevice.activeVideoMaxFrameDuration = frameDuration;
|
||||
} @catch (NSException *exception) {
|
||||
// Some devices don't support setting frame duration, that's okay
|
||||
}
|
||||
[avdevice unlockForConfiguration];
|
||||
}
|
||||
|
||||
[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;
|
||||
|
||||
#ifdef USE_UIKIT_DEVICE_ROTATION
|
||||
// When using a camera, we turn on device orientation tracking. The docs note that this turns on
|
||||
// the device's accelerometer, so I assume this burns power, so we don't leave this running all
|
||||
// the time. These calls nest, so we just need to call the matching `end` message when we close.
|
||||
// You _can_ get an actual events through this mechanism, but we just want to be able to call
|
||||
// -[UIDevice orientation], which will update with real info while notifications are enabled.
|
||||
UIDevice *uidevice = [UIDevice currentDevice];
|
||||
[uidevice beginGeneratingDeviceOrientationNotifications];
|
||||
hidden.last_device_orientation = uidevice.orientation;
|
||||
if (!UIDeviceOrientationIsValidInterfaceOrientation(hidden.last_device_orientation)) {
|
||||
// accelerometer isn't ready yet or the phone is laying flat or something. Just try to guess from how the UI is oriented at the moment.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
switch ([UIApplication sharedApplication].statusBarOrientation) {
|
||||
#pragma clang diagnostic pop
|
||||
case UIInterfaceOrientationPortrait: hidden.last_device_orientation = UIDeviceOrientationPortrait; break;
|
||||
case UIInterfaceOrientationPortraitUpsideDown: hidden.last_device_orientation = UIDeviceOrientationPortraitUpsideDown; break;
|
||||
case UIInterfaceOrientationLandscapeLeft: hidden.last_device_orientation = UIDeviceOrientationLandscapeRight; break; // Apple docs say UI and device orientations are reversed in landscape.
|
||||
case UIInterfaceOrientationLandscapeRight: hidden.last_device_orientation = UIDeviceOrientationLandscapeLeft; break;
|
||||
default: hidden.last_device_orientation = UIDeviceOrientationPortrait; break; // oh well.
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
device->hidden = (struct SDL_PrivateCameraData *)CFBridgingRetain(hidden);
|
||||
|
||||
[session startRunning]; // !!! FIXME: docs say this can block while camera warms up and shouldn't be done on main thread. Maybe push through `queue`?
|
||||
|
||||
CheckCameraPermissions(device); // check right away, in case the process is already granted permission.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void COREMEDIA_FreeDeviceHandle(SDL_Camera *device)
|
||||
{
|
||||
if (device && device->handle) {
|
||||
CFBridgingRelease(device->handle);
|
||||
}
|
||||
}
|
||||
|
||||
static void GatherCameraSpecs(AVCaptureDevice *device, CameraFormatAddData *add_data)
|
||||
{
|
||||
SDL_zerop(add_data);
|
||||
|
||||
for (AVCaptureDeviceFormat *fmt in device.formats) {
|
||||
if (CMFormatDescriptionGetMediaType(fmt.formatDescription) != kCMMediaType_Video) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//NSLog(@"Available camera format: %@\n", fmt);
|
||||
SDL_PixelFormat device_format = SDL_PIXELFORMAT_UNKNOWN;
|
||||
SDL_Colorspace device_colorspace = SDL_COLORSPACE_UNKNOWN;
|
||||
CoreMediaFormatToSDL(CMFormatDescriptionGetMediaSubType(fmt.formatDescription), &device_format, &device_colorspace);
|
||||
if (device_format == SDL_PIXELFORMAT_UNKNOWN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(fmt.formatDescription);
|
||||
const int w = (int) dims.width;
|
||||
const int h = (int) dims.height;
|
||||
for (AVFrameRateRange *framerate in fmt.videoSupportedFrameRateRanges) {
|
||||
int min_numerator = 0, min_denominator = 1;
|
||||
int max_numerator = 0, max_denominator = 1;
|
||||
|
||||
SDL_CalculateFraction(framerate.minFrameRate, &min_numerator, &min_denominator);
|
||||
SDL_AddCameraFormat(add_data, device_format, device_colorspace, w, h, min_numerator, min_denominator);
|
||||
SDL_CalculateFraction(framerate.maxFrameRate, &max_numerator, &max_denominator);
|
||||
if (max_numerator != min_numerator || max_denominator != min_denominator) {
|
||||
SDL_AddCameraFormat(add_data, device_format, device_colorspace, w, h, max_numerator, max_denominator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool FindCoreMediaCameraByUniqueID(SDL_Camera *device, void *userdata)
|
||||
{
|
||||
NSString *uniqueid = (__bridge NSString *) userdata;
|
||||
AVCaptureDevice *avdev = (__bridge AVCaptureDevice *) device->handle;
|
||||
return ([uniqueid isEqualToString:avdev.uniqueID]) ? true : false;
|
||||
}
|
||||
|
||||
static void MaybeAddDevice(AVCaptureDevice *avdevice)
|
||||
{
|
||||
if (!avdevice.connected) {
|
||||
return; // not connected.
|
||||
} else if (![avdevice hasMediaType:AVMediaTypeVideo]) {
|
||||
return; // not a camera.
|
||||
} else if (SDL_FindPhysicalCameraByCallback(FindCoreMediaCameraByUniqueID, (__bridge void *) avdevice.uniqueID)) {
|
||||
return; // already have this one.
|
||||
}
|
||||
|
||||
CameraFormatAddData add_data;
|
||||
GatherCameraSpecs(avdevice, &add_data);
|
||||
if (add_data.num_specs > 0) {
|
||||
SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
|
||||
if (avdevice.position == AVCaptureDevicePositionFront) {
|
||||
position = SDL_CAMERA_POSITION_FRONT_FACING;
|
||||
} else if (avdevice.position == AVCaptureDevicePositionBack) {
|
||||
position = SDL_CAMERA_POSITION_BACK_FACING;
|
||||
}
|
||||
SDL_AddCamera(avdevice.localizedName.UTF8String, position, add_data.num_specs, add_data.specs, (void *) CFBridgingRetain(avdevice));
|
||||
}
|
||||
|
||||
SDL_free(add_data.specs);
|
||||
}
|
||||
|
||||
static void COREMEDIA_DetectDevices(void)
|
||||
{
|
||||
NSArray<AVCaptureDevice *> *devices = nil;
|
||||
|
||||
if (@available(macOS 10.15, iOS 13, *)) {
|
||||
// kind of annoying that there isn't a "give me anything that looks like a camera" option,
|
||||
// so this list will need to be updated when Apple decides to add
|
||||
// AVCaptureDeviceTypeBuiltInQuadrupleCamera some day.
|
||||
NSArray *device_types = @[
|
||||
#ifdef SDL_PLATFORM_IOS
|
||||
AVCaptureDeviceTypeBuiltInTelephotoCamera,
|
||||
AVCaptureDeviceTypeBuiltInDualCamera,
|
||||
AVCaptureDeviceTypeBuiltInDualWideCamera,
|
||||
AVCaptureDeviceTypeBuiltInTripleCamera,
|
||||
AVCaptureDeviceTypeBuiltInUltraWideCamera,
|
||||
#else
|
||||
AVCaptureDeviceTypeExternalUnknown,
|
||||
#endif
|
||||
AVCaptureDeviceTypeBuiltInWideAngleCamera
|
||||
];
|
||||
|
||||
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
|
||||
discoverySessionWithDeviceTypes:device_types
|
||||
mediaType:AVMediaTypeVideo
|
||||
position:AVCaptureDevicePositionUnspecified];
|
||||
|
||||
devices = discoverySession.devices;
|
||||
// !!! FIXME: this can use Key Value Observation to get hotplug events.
|
||||
} else {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
// 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.
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
for (AVCaptureDevice *device in devices) {
|
||||
MaybeAddDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
static void COREMEDIA_Deinitialize(void)
|
||||
{
|
||||
// !!! FIXME: disable hotplug.
|
||||
}
|
||||
|
||||
static bool COREMEDIA_Init(SDL_CameraDriverImpl *impl)
|
||||
{
|
||||
impl->DetectDevices = COREMEDIA_DetectDevices;
|
||||
impl->OpenDevice = COREMEDIA_OpenDevice;
|
||||
impl->CloseDevice = COREMEDIA_CloseDevice;
|
||||
impl->WaitDevice = COREMEDIA_WaitDevice;
|
||||
impl->AcquireFrame = COREMEDIA_AcquireFrame;
|
||||
impl->ReleaseFrame = COREMEDIA_ReleaseFrame;
|
||||
impl->FreeDeviceHandle = COREMEDIA_FreeDeviceHandle;
|
||||
impl->Deinitialize = COREMEDIA_Deinitialize;
|
||||
|
||||
impl->ProvidesOwnCallbackThread = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CameraBootStrap COREMEDIA_bootstrap = {
|
||||
"coremedia", "SDL Apple CoreMedia camera driver", COREMEDIA_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_CAMERA_DRIVER_COREMEDIA
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_CAMERA_DRIVER_DUMMY
|
||||
|
||||
#include "../SDL_syscamera.h"
|
||||
|
||||
static bool DUMMYCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static void DUMMYCAMERA_CloseDevice(SDL_Camera *device)
|
||||
{
|
||||
}
|
||||
|
||||
static bool DUMMYCAMERA_WaitDevice(SDL_Camera *device)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static SDL_CameraFrameResult DUMMYCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return SDL_CAMERA_FRAME_ERROR;
|
||||
}
|
||||
|
||||
static void DUMMYCAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
|
||||
{
|
||||
}
|
||||
|
||||
static void DUMMYCAMERA_DetectDevices(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void DUMMYCAMERA_FreeDeviceHandle(SDL_Camera *device)
|
||||
{
|
||||
}
|
||||
|
||||
static void DUMMYCAMERA_Deinitialize(void)
|
||||
{
|
||||
}
|
||||
|
||||
static bool DUMMYCAMERA_Init(SDL_CameraDriverImpl *impl)
|
||||
{
|
||||
impl->DetectDevices = DUMMYCAMERA_DetectDevices;
|
||||
impl->OpenDevice = DUMMYCAMERA_OpenDevice;
|
||||
impl->CloseDevice = DUMMYCAMERA_CloseDevice;
|
||||
impl->WaitDevice = DUMMYCAMERA_WaitDevice;
|
||||
impl->AcquireFrame = DUMMYCAMERA_AcquireFrame;
|
||||
impl->ReleaseFrame = DUMMYCAMERA_ReleaseFrame;
|
||||
impl->FreeDeviceHandle = DUMMYCAMERA_FreeDeviceHandle;
|
||||
impl->Deinitialize = DUMMYCAMERA_Deinitialize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CameraBootStrap DUMMYCAMERA_bootstrap = {
|
||||
"dummy", "SDL dummy camera driver", DUMMYCAMERA_Init, true
|
||||
};
|
||||
|
||||
#endif // SDL_CAMERA_DRIVER_DUMMY
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_CAMERA_DRIVER_EMSCRIPTEN
|
||||
|
||||
#include "../SDL_syscamera.h"
|
||||
#include "../SDL_camera_c.h"
|
||||
#include "../../video/SDL_pixels_c.h"
|
||||
#include "../../video/SDL_surface_c.h"
|
||||
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
// just turn off clang-format for this whole file, this INDENT_OFF stuff on
|
||||
// each EM_ASM section is ugly.
|
||||
/* *INDENT-OFF* */ // clang-format off
|
||||
|
||||
static bool EMSCRIPTENCAMERA_WaitDevice(SDL_Camera *device)
|
||||
{
|
||||
SDL_assert(!"This shouldn't be called"); // we aren't using SDL's internal thread.
|
||||
return false;
|
||||
}
|
||||
|
||||
static SDL_CameraFrameResult EMSCRIPTENCAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation)
|
||||
{
|
||||
void *rgba = SDL_malloc(device->actual_spec.width * device->actual_spec.height * 4);
|
||||
if (!rgba) {
|
||||
return SDL_CAMERA_FRAME_ERROR;
|
||||
}
|
||||
|
||||
*timestampNS = SDL_GetTicksNS(); // best we can do here.
|
||||
|
||||
const int rc = MAIN_THREAD_EM_ASM_INT({
|
||||
const w = $0;
|
||||
const h = $1;
|
||||
const rgba = $2;
|
||||
const SDL3 = Module['SDL3'];
|
||||
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.ctx2d) === 'undefined')) {
|
||||
return 0; // don't have something we need, oh well.
|
||||
}
|
||||
|
||||
SDL3.camera.ctx2d.drawImage(SDL3.camera.video, 0, 0, w, h);
|
||||
const imgrgba = SDL3.camera.ctx2d.getImageData(0, 0, w, h).data;
|
||||
HEAPU8.set(imgrgba, rgba);
|
||||
|
||||
return 1;
|
||||
}, device->actual_spec.width, device->actual_spec.height, rgba);
|
||||
|
||||
if (!rc) {
|
||||
SDL_free(rgba);
|
||||
return SDL_CAMERA_FRAME_ERROR; // something went wrong, maybe shutting down; just don't return a frame.
|
||||
}
|
||||
|
||||
frame->pixels = rgba;
|
||||
frame->pitch = device->actual_spec.width * 4;
|
||||
|
||||
return SDL_CAMERA_FRAME_READY;
|
||||
}
|
||||
|
||||
static void EMSCRIPTENCAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
|
||||
{
|
||||
SDL_free(frame->pixels);
|
||||
}
|
||||
|
||||
static void EMSCRIPTENCAMERA_CloseDevice(SDL_Camera *device)
|
||||
{
|
||||
if (device) {
|
||||
MAIN_THREAD_EM_ASM({
|
||||
const SDL3 = Module['SDL3'];
|
||||
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) {
|
||||
return; // camera was closed and/or subsystem was shut down, we're already done.
|
||||
}
|
||||
SDL3.camera.stream.getTracks().forEach(track => track.stop()); // stop all recording.
|
||||
SDL3.camera = {}; // dump our references to everything.
|
||||
});
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE int SDLEmscriptenCameraPermissionOutcome(SDL_Camera *device, int approved, int w, int h, int fps)
|
||||
{
|
||||
if (approved) {
|
||||
device->actual_spec.format = SDL_PIXELFORMAT_RGBA32;
|
||||
device->actual_spec.width = w;
|
||||
device->actual_spec.height = h;
|
||||
device->actual_spec.framerate_numerator = fps;
|
||||
device->actual_spec.framerate_denominator = 1;
|
||||
|
||||
if (!SDL_PrepareCameraSurfaces(device)) {
|
||||
// uhoh, we're in trouble. Probably ran out of memory.
|
||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Camera could not prepare surfaces: %s ... revoking approval!", SDL_GetError());
|
||||
approved = 0; // disconnecting the SDL camera might not be safe here, just mark it as denied by user.
|
||||
}
|
||||
}
|
||||
|
||||
SDL_CameraPermissionOutcome(device, approved ? true : false);
|
||||
return approved;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE bool SDLEmscriptenThreadIterate(SDL_Camera *device) {
|
||||
return SDL_CameraThreadIterate(device);
|
||||
}
|
||||
|
||||
static bool EMSCRIPTENCAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
|
||||
{
|
||||
MAIN_THREAD_EM_ASM({
|
||||
// Since we can't get actual specs until we make a move that prompts the user for
|
||||
// permission, we don't list any specs for the device and wrangle it during device open.
|
||||
const device = $0;
|
||||
const w = $1;
|
||||
const h = $2;
|
||||
const framerate_numerator = $3;
|
||||
const framerate_denominator = $4;
|
||||
const outcome = Module._SDLEmscriptenCameraPermissionOutcome;
|
||||
const iterate = Module._SDLEmscriptenThreadIterate;
|
||||
|
||||
const constraints = {};
|
||||
if ((w <= 0) || (h <= 0)) {
|
||||
constraints.video = true; // didn't ask for anything, let the system choose.
|
||||
} else {
|
||||
constraints.video = {}; // asked for a specific thing: request it as "ideal" but take closest hardware will offer.
|
||||
constraints.video.width = w;
|
||||
constraints.video.height = h;
|
||||
}
|
||||
|
||||
if ((framerate_numerator > 0) && (framerate_denominator > 0)) {
|
||||
var fps = framerate_numerator / framerate_denominator;
|
||||
constraints.video.frameRate = { ideal: fps };
|
||||
}
|
||||
|
||||
function grabNextCameraFrame() { // !!! FIXME: this (currently) runs as a requestAnimationFrame callback, for lack of a better option.
|
||||
const SDL3 = Module['SDL3'];
|
||||
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) {
|
||||
return; // camera was closed and/or subsystem was shut down, stop iterating here.
|
||||
}
|
||||
|
||||
// time for a new frame from the camera?
|
||||
const nextframems = SDL3.camera.next_frame_time;
|
||||
const now = performance.now();
|
||||
if (now >= nextframems) {
|
||||
iterate(device); // calls SDL_CameraThreadIterate, which will call our AcquireFrame implementation.
|
||||
|
||||
// bump ahead but try to stay consistent on timing, in case we dropped frames.
|
||||
while (SDL3.camera.next_frame_time < now) {
|
||||
SDL3.camera.next_frame_time += SDL3.camera.fpsincrms;
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(grabNextCameraFrame); // run this function again at the display framerate. (!!! FIXME: would this be better as requestIdleCallback?)
|
||||
}
|
||||
|
||||
navigator.mediaDevices.getUserMedia(constraints)
|
||||
.then((stream) => {
|
||||
const settings = stream.getVideoTracks()[0].getSettings();
|
||||
const actualw = settings.width;
|
||||
const actualh = settings.height;
|
||||
const actualfps = settings.frameRate;
|
||||
console.log("Camera is opened! Actual spec: (" + actualw + "x" + actualh + "), fps=" + actualfps);
|
||||
|
||||
if (outcome(device, 1, actualw, actualh, actualfps)) {
|
||||
const video = document.createElement("video");
|
||||
video.width = actualw;
|
||||
video.height = actualh;
|
||||
video.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels.
|
||||
video.srcObject = stream;
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = actualw;
|
||||
canvas.height = actualh;
|
||||
canvas.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels.
|
||||
|
||||
const ctx2d = canvas.getContext('2d');
|
||||
|
||||
const SDL3 = Module['SDL3'];
|
||||
SDL3.camera.width = actualw;
|
||||
SDL3.camera.height = actualh;
|
||||
SDL3.camera.fps = actualfps;
|
||||
SDL3.camera.fpsincrms = 1000.0 / actualfps;
|
||||
SDL3.camera.stream = stream;
|
||||
SDL3.camera.video = video;
|
||||
SDL3.camera.canvas = canvas;
|
||||
SDL3.camera.ctx2d = ctx2d;
|
||||
SDL3.camera.next_frame_time = performance.now();
|
||||
|
||||
video.play();
|
||||
video.addEventListener('loadedmetadata', () => {
|
||||
grabNextCameraFrame(); // start this loop going.
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Tried to open camera but it threw an error! " + err.name + ": " + err.message);
|
||||
outcome(device, 0, 0, 0, 0); // we call this a permission error, because it probably is.
|
||||
});
|
||||
}, device, spec->width, spec->height, spec->framerate_numerator, spec->framerate_denominator);
|
||||
|
||||
return true; // the real work waits until the user approves a camera.
|
||||
}
|
||||
|
||||
static void EMSCRIPTENCAMERA_FreeDeviceHandle(SDL_Camera *device)
|
||||
{
|
||||
// no-op.
|
||||
}
|
||||
|
||||
static void EMSCRIPTENCAMERA_Deinitialize(void)
|
||||
{
|
||||
MAIN_THREAD_EM_ASM({
|
||||
if (typeof(Module['SDL3']) !== 'undefined') {
|
||||
Module['SDL3'].camera = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void EMSCRIPTENCAMERA_DetectDevices(void)
|
||||
{
|
||||
// `navigator.mediaDevices` is not defined if unsupported or not in a secure context!
|
||||
const int supported = MAIN_THREAD_EM_ASM_INT({ return (navigator.mediaDevices === undefined) ? 0 : 1; });
|
||||
|
||||
// if we have support at all, report a single generic camera with no specs.
|
||||
// We'll find out if there really _is_ a camera when we try to open it, but querying it for real here
|
||||
// will pop up a user permission dialog warning them we're trying to access the camera, and we generally
|
||||
// don't want that during SDL_Init().
|
||||
if (supported) {
|
||||
SDL_AddCamera("Web browser's camera", SDL_CAMERA_POSITION_UNKNOWN, 0, NULL, (void *) (size_t) 0x1);
|
||||
}
|
||||
}
|
||||
|
||||
static bool EMSCRIPTENCAMERA_Init(SDL_CameraDriverImpl *impl)
|
||||
{
|
||||
MAIN_THREAD_EM_ASM({
|
||||
Module['SDL3'].camera = {};
|
||||
});
|
||||
|
||||
impl->DetectDevices = EMSCRIPTENCAMERA_DetectDevices;
|
||||
impl->OpenDevice = EMSCRIPTENCAMERA_OpenDevice;
|
||||
impl->CloseDevice = EMSCRIPTENCAMERA_CloseDevice;
|
||||
impl->WaitDevice = EMSCRIPTENCAMERA_WaitDevice;
|
||||
impl->AcquireFrame = EMSCRIPTENCAMERA_AcquireFrame;
|
||||
impl->ReleaseFrame = EMSCRIPTENCAMERA_ReleaseFrame;
|
||||
impl->FreeDeviceHandle = EMSCRIPTENCAMERA_FreeDeviceHandle;
|
||||
impl->Deinitialize = EMSCRIPTENCAMERA_Deinitialize;
|
||||
|
||||
impl->ProvidesOwnCallbackThread = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CameraBootStrap EMSCRIPTENCAMERA_bootstrap = {
|
||||
"emscripten", "SDL Emscripten MediaStream camera driver", EMSCRIPTENCAMERA_Init, false
|
||||
};
|
||||
|
||||
/* *INDENT-ON* */ // clang-format on
|
||||
|
||||
#endif // SDL_CAMERA_DRIVER_EMSCRIPTEN
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1175
File diff suppressed because it is too large
Load Diff
+978
@@ -0,0 +1,978 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_CAMERA_DRIVER_V4L2
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h> // low-level i/o
|
||||
#include <stddef.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#ifndef V4L2_PIX_FMT_RGBX32
|
||||
#define V4L2_PIX_FMT_RGBX32 v4l2_fourcc('X','B','2','4')
|
||||
#endif
|
||||
#ifndef V4L2_CAP_DEVICE_CAPS
|
||||
// device_caps was added to struct v4l2_capability as of kernel 3.4.
|
||||
#define device_caps reserved[0]
|
||||
SDL_COMPILE_TIME_ASSERT(v4l2devicecaps, offsetof(struct v4l2_capability,device_caps) == offsetof(struct v4l2_capability,capabilities) + 4);
|
||||
#endif
|
||||
|
||||
#include "../SDL_syscamera.h"
|
||||
#include "../SDL_camera_c.h"
|
||||
#include "../../video/SDL_pixels_c.h"
|
||||
#include "../../video/SDL_surface_c.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
#include "../../core/linux/SDL_evdev_capabilities.h"
|
||||
#include "../../core/linux/SDL_udev.h"
|
||||
|
||||
#ifndef SDL_USE_LIBUDEV
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
typedef struct V4L2DeviceHandle
|
||||
{
|
||||
char *bus_info;
|
||||
char *path;
|
||||
} V4L2DeviceHandle;
|
||||
|
||||
|
||||
typedef enum io_method {
|
||||
IO_METHOD_INVALID,
|
||||
IO_METHOD_READ,
|
||||
IO_METHOD_MMAP,
|
||||
IO_METHOD_USERPTR
|
||||
} io_method;
|
||||
|
||||
struct buffer {
|
||||
void *start;
|
||||
size_t length;
|
||||
int available; // Is available in userspace
|
||||
};
|
||||
|
||||
struct SDL_PrivateCameraData
|
||||
{
|
||||
int fd;
|
||||
io_method io;
|
||||
int nb_buffers;
|
||||
struct buffer *buffers;
|
||||
int driver_pitch;
|
||||
};
|
||||
|
||||
static int xioctl(int fh, int request, void *arg)
|
||||
{
|
||||
int r;
|
||||
|
||||
do {
|
||||
r = ioctl(fh, request, arg);
|
||||
} while ((r == -1) && (errno == EINTR));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static bool V4L2_WaitDevice(SDL_Camera *device)
|
||||
{
|
||||
const int fd = device->hidden->fd;
|
||||
|
||||
int rc;
|
||||
|
||||
do {
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 100 * 1000;
|
||||
|
||||
rc = select(fd + 1, &fds, NULL, NULL, &tv);
|
||||
if ((rc == -1) && (errno == EINTR)) {
|
||||
rc = 0; // pretend it was a timeout, keep looping.
|
||||
} else if (rc > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Thread is requested to shut down
|
||||
if (SDL_GetAtomicInt(&device->shutdown)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} while (rc == 0);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static SDL_CameraFrameResult V4L2_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation)
|
||||
{
|
||||
const int fd = device->hidden->fd;
|
||||
const io_method io = device->hidden->io;
|
||||
size_t size = device->hidden->buffers[0].length;
|
||||
struct v4l2_buffer buf;
|
||||
ssize_t amount;
|
||||
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
if ((amount = read(fd, device->hidden->buffers[0].start, size)) == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
return SDL_CAMERA_FRAME_SKIP;
|
||||
|
||||
case EIO:
|
||||
// Could ignore EIO, see spec.
|
||||
// fall through
|
||||
|
||||
default:
|
||||
SDL_SetError("read");
|
||||
return SDL_CAMERA_FRAME_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
*timestampNS = SDL_GetTicksNS(); // oh well, close enough.
|
||||
frame->pixels = device->hidden->buffers[0].start;
|
||||
if (device->hidden->driver_pitch) {
|
||||
frame->pitch = device->hidden->driver_pitch;
|
||||
} else {
|
||||
frame->pitch = (int)amount;
|
||||
}
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
return SDL_CAMERA_FRAME_SKIP;
|
||||
|
||||
case EIO:
|
||||
// Could ignore EIO, see spec.
|
||||
// fall through
|
||||
|
||||
default:
|
||||
SDL_SetError("VIDIOC_DQBUF: %d", errno);
|
||||
return SDL_CAMERA_FRAME_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if ((int)buf.index < 0 || (int)buf.index >= device->hidden->nb_buffers) {
|
||||
SDL_SetError("invalid buffer index");
|
||||
return SDL_CAMERA_FRAME_ERROR;
|
||||
}
|
||||
|
||||
frame->pixels = device->hidden->buffers[buf.index].start;
|
||||
if (device->hidden->driver_pitch) {
|
||||
frame->pitch = device->hidden->driver_pitch;
|
||||
} else {
|
||||
frame->pitch = buf.bytesused;
|
||||
}
|
||||
device->hidden->buffers[buf.index].available = 1;
|
||||
|
||||
*timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec);
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: debug mmap: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void *)frame->pixels);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_USERPTR;
|
||||
|
||||
if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
return SDL_CAMERA_FRAME_SKIP;
|
||||
|
||||
case EIO:
|
||||
// Could ignore EIO, see spec.
|
||||
// fall through
|
||||
|
||||
default:
|
||||
SDL_SetError("VIDIOC_DQBUF");
|
||||
return SDL_CAMERA_FRAME_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
if (buf.m.userptr == (unsigned long)device->hidden->buffers[i].start && buf.length == size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= device->hidden->nb_buffers) {
|
||||
SDL_SetError("invalid buffer index");
|
||||
return SDL_CAMERA_FRAME_ERROR;
|
||||
}
|
||||
|
||||
frame->pixels = (void *)buf.m.userptr;
|
||||
if (device->hidden->driver_pitch) {
|
||||
frame->pitch = device->hidden->driver_pitch;
|
||||
} else {
|
||||
frame->pitch = buf.bytesused;
|
||||
}
|
||||
device->hidden->buffers[i].available = 1;
|
||||
|
||||
*timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec);
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: debug userptr: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void *)frame->pixels);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case IO_METHOD_INVALID:
|
||||
SDL_assert(!"Shouldn't have hit this");
|
||||
break;
|
||||
}
|
||||
|
||||
return SDL_CAMERA_FRAME_READY;
|
||||
}
|
||||
|
||||
static void V4L2_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
|
||||
{
|
||||
struct v4l2_buffer buf;
|
||||
const int fd = device->hidden->fd;
|
||||
const io_method io = device->hidden->io;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
if (frame->pixels == device->hidden->buffers[i].start) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= device->hidden->nb_buffers) {
|
||||
return; // oh well, we didn't own this.
|
||||
}
|
||||
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
// !!! FIXME: disconnect the device.
|
||||
return; //SDL_SetError("VIDIOC_QBUF");
|
||||
}
|
||||
device->hidden->buffers[i].available = 0;
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_USERPTR;
|
||||
buf.index = i;
|
||||
buf.m.userptr = (unsigned long)frame->pixels;
|
||||
buf.length = (int) device->hidden->buffers[i].length;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
// !!! FIXME: disconnect the device.
|
||||
return; //SDL_SetError("VIDIOC_QBUF");
|
||||
}
|
||||
device->hidden->buffers[i].available = 0;
|
||||
break;
|
||||
|
||||
case IO_METHOD_INVALID:
|
||||
SDL_assert(!"Shouldn't have hit this");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool EnqueueBuffers(SDL_Camera *device)
|
||||
{
|
||||
const int fd = device->hidden->fd;
|
||||
const io_method io = device->hidden->io;
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
for (int i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
if (device->hidden->buffers[i].available == 0) {
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
SDL_zero(buf);
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
return SDL_SetError("VIDIOC_QBUF");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
for (int i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
if (device->hidden->buffers[i].available == 0) {
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
SDL_zero(buf);
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_USERPTR;
|
||||
buf.index = i;
|
||||
buf.m.userptr = (unsigned long)device->hidden->buffers[i].start;
|
||||
buf.length = (int) device->hidden->buffers[i].length;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
return SDL_SetError("VIDIOC_QBUF");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IO_METHOD_INVALID: SDL_assert(!"Shouldn't have hit this"); break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool AllocBufferRead(SDL_Camera *device, size_t buffer_size)
|
||||
{
|
||||
device->hidden->buffers[0].length = buffer_size;
|
||||
device->hidden->buffers[0].start = SDL_calloc(1, buffer_size);
|
||||
return (device->hidden->buffers[0].start != NULL);
|
||||
}
|
||||
|
||||
static bool AllocBufferMmap(SDL_Camera *device)
|
||||
{
|
||||
const int fd = device->hidden->fd;
|
||||
int i;
|
||||
for (i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
|
||||
return SDL_SetError("VIDIOC_QUERYBUF");
|
||||
}
|
||||
|
||||
device->hidden->buffers[i].length = buf.length;
|
||||
device->hidden->buffers[i].start =
|
||||
mmap(NULL /* start anywhere */,
|
||||
buf.length,
|
||||
PROT_READ | PROT_WRITE /* required */,
|
||||
MAP_SHARED /* recommended */,
|
||||
fd, buf.m.offset);
|
||||
|
||||
if (MAP_FAILED == device->hidden->buffers[i].start) {
|
||||
return SDL_SetError("mmap");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool AllocBufferUserPtr(SDL_Camera *device, size_t buffer_size)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
device->hidden->buffers[i].length = buffer_size;
|
||||
device->hidden->buffers[i].start = SDL_calloc(1, buffer_size);
|
||||
|
||||
if (!device->hidden->buffers[i].start) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void format_v4l2_to_sdl(Uint32 fmt, SDL_PixelFormat *format, SDL_Colorspace *colorspace)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(x, y, z) case x: *format = y; *colorspace = z; return
|
||||
CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2, SDL_COLORSPACE_BT709_LIMITED);
|
||||
CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG, SDL_COLORSPACE_SRGB);
|
||||
CASE(V4L2_PIX_FMT_RGBX32, SDL_PIXELFORMAT_RGBX32, SDL_COLORSPACE_SRGB);
|
||||
#undef CASE
|
||||
default:
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: Unknown format V4L2_PIX_FORMAT '%c%c%c%c' (0x%x)",
|
||||
(char)(Uint8)(fmt >> 0),
|
||||
(char)(Uint8)(fmt >> 8),
|
||||
(char)(Uint8)(fmt >> 16),
|
||||
(char)(Uint8)(fmt >> 24), fmt);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
*format = SDL_PIXELFORMAT_UNKNOWN;
|
||||
*colorspace = SDL_COLORSPACE_UNKNOWN;
|
||||
}
|
||||
|
||||
static Uint32 format_sdl_to_v4l2(SDL_PixelFormat fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(y, x) case x: return y
|
||||
CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2);
|
||||
CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_MJPG);
|
||||
CASE(V4L2_PIX_FMT_RGBX32, SDL_PIXELFORMAT_RGBX32);
|
||||
#undef CASE
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void V4L2_CloseDevice(SDL_Camera *device)
|
||||
{
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (device->hidden) {
|
||||
const io_method io = device->hidden->io;
|
||||
const int fd = device->hidden->fd;
|
||||
|
||||
if ((io == IO_METHOD_MMAP) || (io == IO_METHOD_USERPTR)) {
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
xioctl(fd, VIDIOC_STREAMOFF, &type);
|
||||
}
|
||||
|
||||
if (device->hidden->buffers) {
|
||||
switch (io) {
|
||||
case IO_METHOD_INVALID:
|
||||
break;
|
||||
|
||||
case IO_METHOD_READ:
|
||||
SDL_free(device->hidden->buffers[0].start);
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
for (int i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
if (munmap(device->hidden->buffers[i].start, device->hidden->buffers[i].length) == -1) {
|
||||
SDL_SetError("munmap");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
for (int i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
SDL_free(device->hidden->buffers[i].start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_free(device->hidden->buffers);
|
||||
}
|
||||
|
||||
if (fd != -1) {
|
||||
close(fd);
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool V4L2_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
|
||||
{
|
||||
const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
|
||||
struct stat st;
|
||||
struct v4l2_capability cap;
|
||||
const int fd = open(handle->path, O_RDWR /* required */ | O_NONBLOCK, 0);
|
||||
|
||||
// most of this probably shouldn't fail unless the filesystem node changed out from under us since MaybeAddDevice().
|
||||
if (fd == -1) {
|
||||
return SDL_SetError("Cannot open '%s': %d, %s", handle->path, errno, strerror(errno));
|
||||
} else if (fstat(fd, &st) == -1) {
|
||||
close(fd);
|
||||
return SDL_SetError("Cannot identify '%s': %d, %s", handle->path, errno, strerror(errno));
|
||||
} else if (!S_ISCHR(st.st_mode)) {
|
||||
close(fd);
|
||||
return SDL_SetError("%s is not a character device", handle->path);
|
||||
} else if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
|
||||
const int err = errno;
|
||||
close(fd);
|
||||
if (err == EINVAL) {
|
||||
return SDL_SetError("%s is unexpectedly not a V4L2 device", handle->path);
|
||||
}
|
||||
return SDL_SetError("Error VIDIOC_QUERYCAP errno=%d device%s is no V4L2 device", err, handle->path);
|
||||
} else if ((cap.device_caps & V4L2_CAP_VIDEO_CAPTURE) == 0) {
|
||||
close(fd);
|
||||
return SDL_SetError("%s is unexpectedly not a video capture device", handle->path);
|
||||
}
|
||||
|
||||
device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
|
||||
if (device->hidden == NULL) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
device->hidden->fd = fd;
|
||||
device->hidden->io = IO_METHOD_INVALID;
|
||||
|
||||
// Select video input, video standard and tune here.
|
||||
// errors in the crop code are not fatal.
|
||||
struct v4l2_cropcap cropcap;
|
||||
SDL_zero(cropcap);
|
||||
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) {
|
||||
struct v4l2_crop crop;
|
||||
SDL_zero(crop);
|
||||
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
crop.c = cropcap.defrect; // reset to default
|
||||
xioctl(fd, VIDIOC_S_CROP, &crop);
|
||||
}
|
||||
|
||||
struct v4l2_format fmt;
|
||||
SDL_zero(fmt);
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmt.fmt.pix.width = spec->width;
|
||||
fmt.fmt.pix.height = spec->height;
|
||||
fmt.fmt.pix.pixelformat = format_sdl_to_v4l2(spec->format);
|
||||
//fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_ANY;
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: set SDL format %s", SDL_GetPixelFormatName(spec->format));
|
||||
{ const Uint32 f = fmt.fmt.pix.pixelformat; SDL_Log("CAMERA: set format V4L2_format=%d %c%c%c%c", f, (f >> 0) & 0xff, (f >> 8) & 0xff, (f >> 16) & 0xff, (f >> 24) & 0xff); }
|
||||
#endif
|
||||
|
||||
if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
|
||||
return SDL_SetError("Error VIDIOC_S_FMT");
|
||||
}
|
||||
|
||||
if (spec->framerate_numerator && spec->framerate_denominator) {
|
||||
struct v4l2_streamparm setfps;
|
||||
SDL_zero(setfps);
|
||||
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (xioctl(fd, VIDIOC_G_PARM, &setfps) == 0) {
|
||||
if ( (setfps.parm.capture.timeperframe.denominator != spec->framerate_numerator) ||
|
||||
(setfps.parm.capture.timeperframe.numerator != spec->framerate_denominator) ) {
|
||||
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
setfps.parm.capture.timeperframe.numerator = spec->framerate_denominator;
|
||||
setfps.parm.capture.timeperframe.denominator = spec->framerate_numerator;
|
||||
if (xioctl(fd, VIDIOC_S_PARM, &setfps) == -1) {
|
||||
return SDL_SetError("Error VIDIOC_S_PARM");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_zero(fmt);
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
|
||||
return SDL_SetError("Error VIDIOC_G_FMT");
|
||||
}
|
||||
device->hidden->driver_pitch = fmt.fmt.pix.bytesperline;
|
||||
|
||||
io_method io = IO_METHOD_INVALID;
|
||||
if ((io == IO_METHOD_INVALID) && (cap.device_caps & V4L2_CAP_STREAMING)) {
|
||||
struct v4l2_requestbuffers req;
|
||||
SDL_zero(req);
|
||||
req.count = 8;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
if ((xioctl(fd, VIDIOC_REQBUFS, &req) == 0) && (req.count >= 2)) {
|
||||
io = IO_METHOD_MMAP;
|
||||
device->hidden->nb_buffers = req.count;
|
||||
} else { // mmap didn't work out? Try USERPTR.
|
||||
SDL_zero(req);
|
||||
req.count = 8;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_USERPTR;
|
||||
if (xioctl(fd, VIDIOC_REQBUFS, &req) == 0) {
|
||||
io = IO_METHOD_USERPTR;
|
||||
device->hidden->nb_buffers = 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((io == IO_METHOD_INVALID) && (cap.device_caps & V4L2_CAP_READWRITE)) {
|
||||
io = IO_METHOD_READ;
|
||||
device->hidden->nb_buffers = 1;
|
||||
}
|
||||
|
||||
if (io == IO_METHOD_INVALID) {
|
||||
return SDL_SetError("Don't have a way to talk to this device");
|
||||
}
|
||||
|
||||
device->hidden->io = io;
|
||||
|
||||
device->hidden->buffers = SDL_calloc(device->hidden->nb_buffers, sizeof(*device->hidden->buffers));
|
||||
if (!device->hidden->buffers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t size, pitch;
|
||||
if (!SDL_CalculateSurfaceSize(device->spec.format, device->spec.width, device->spec.height, &size, &pitch, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rc = true;
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
rc = AllocBufferRead(device, size);
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
rc = AllocBufferMmap(device);
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
rc = AllocBufferUserPtr(device, size);
|
||||
break;
|
||||
|
||||
case IO_METHOD_INVALID:
|
||||
SDL_assert(!"Shouldn't have hit this");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!rc) {
|
||||
return false;
|
||||
} else if (!EnqueueBuffers(device)) {
|
||||
return false;
|
||||
} else if (io != IO_METHOD_READ) {
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (xioctl(fd, VIDIOC_STREAMON, &type) == -1) {
|
||||
return SDL_SetError("VIDIOC_STREAMON");
|
||||
}
|
||||
}
|
||||
|
||||
// Currently there is no user permission prompt for camera access, but maybe there will be a D-Bus portal interface at some point.
|
||||
SDL_CameraPermissionOutcome(device, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool FindV4L2CameraByBusInfoCallback(SDL_Camera *device, void *userdata)
|
||||
{
|
||||
const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
|
||||
return (SDL_strcmp(handle->bus_info, (const char *) userdata) == 0);
|
||||
}
|
||||
|
||||
static bool AddCameraFormat(const int fd, CameraFormatAddData *data, SDL_PixelFormat sdlfmt, SDL_Colorspace colorspace, Uint32 v4l2fmt, int w, int h)
|
||||
{
|
||||
struct v4l2_frmivalenum frmivalenum;
|
||||
SDL_zero(frmivalenum);
|
||||
frmivalenum.pixel_format = v4l2fmt;
|
||||
frmivalenum.width = (Uint32) w;
|
||||
frmivalenum.height = (Uint32) h;
|
||||
|
||||
while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmivalenum) == 0) {
|
||||
if (frmivalenum.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
|
||||
const int numerator = (int) frmivalenum.discrete.numerator;
|
||||
const int denominator = (int) frmivalenum.discrete.denominator;
|
||||
#if DEBUG_CAMERA
|
||||
const float fps = (float) denominator / (float) numerator;
|
||||
SDL_Log("CAMERA: * Has discrete frame interval (%d / %d), fps=%f", numerator, denominator, fps);
|
||||
#endif
|
||||
if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, denominator, numerator)) {
|
||||
return false; // Probably out of memory; we'll go with what we have, if anything.
|
||||
}
|
||||
frmivalenum.index++; // set up for the next one.
|
||||
} else if (frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) {
|
||||
int d = frmivalenum.stepwise.min.denominator;
|
||||
// !!! FIXME: should we step by the numerator...?
|
||||
for (int n = (int) frmivalenum.stepwise.min.numerator; n <= (int) frmivalenum.stepwise.max.numerator; n += (int) frmivalenum.stepwise.step.numerator) {
|
||||
#if DEBUG_CAMERA
|
||||
const float fps = (float) d / (float) n;
|
||||
SDL_Log("CAMERA: * Has %s frame interval (%d / %d), fps=%f", (frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) ? "stepwise" : "continuous", n, d, fps);
|
||||
#endif
|
||||
// SDL expects framerate, V4L2 provides interval
|
||||
if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, d, n)) {
|
||||
return false; // Probably out of memory; we'll go with what we have, if anything.
|
||||
}
|
||||
d += (int) frmivalenum.stepwise.step.denominator;
|
||||
}
|
||||
break;
|
||||
} else if (frmivalenum.type == V4L2_FRMIVAL_TYPE_CONTINUOUS) {
|
||||
// FIXME: The current API does not enable exposing continuous ranges, so for now let's expose some common values that are within the range
|
||||
const int min_numer = frmivalenum.stepwise.min.numerator;
|
||||
const int min_denom = frmivalenum.stepwise.min.denominator;
|
||||
const int max_numer = frmivalenum.stepwise.max.numerator;
|
||||
const int max_denom = frmivalenum.stepwise.max.denominator;
|
||||
const float minrate = (float) min_numer / (float) min_denom;
|
||||
const float maxrate = (float) max_numer / (float) max_denom;
|
||||
if (minrate <= 1.001 / 24 && maxrate >= 1.001 / 24) {
|
||||
if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 24000, 1001)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (minrate <= 1.000 / 24 && maxrate >= 1.000 / 24) {
|
||||
if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 24000, 1000)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (minrate <= 1.001 / 30 && maxrate >= 1.001 / 30) {
|
||||
if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 30000, 1001)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (minrate <= 1.000 / 30 && maxrate >= 1.000 / 30) {
|
||||
if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 30000, 1000)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (minrate <= 1.000 / 50 && maxrate >= 1.000 / 50) {
|
||||
if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 50000, 1000)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (minrate <= 1.001 / 60 && maxrate >= 1.001 / 60) {
|
||||
if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 60000, 1001)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (minrate <= 1.000 / 60 && maxrate >= 1.000 / 60) {
|
||||
if (!SDL_AddCameraFormat(data, sdlfmt, colorspace, w, h, 60000, 1000)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void MaybeAddDevice(const char *path)
|
||||
{
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
const int fd = open(path, O_RDWR /* required */ | O_NONBLOCK, 0);
|
||||
if (fd == -1) {
|
||||
return; // can't open it? skip it.
|
||||
} else if (fstat(fd, &st) == -1) {
|
||||
close(fd);
|
||||
return; // can't stat it? skip it.
|
||||
} else if (!S_ISCHR(st.st_mode)) {
|
||||
close(fd);
|
||||
return; // not a character device.
|
||||
}
|
||||
|
||||
struct v4l2_capability vcap;
|
||||
const int rc = ioctl(fd, VIDIOC_QUERYCAP, &vcap);
|
||||
if (rc != 0) {
|
||||
close(fd);
|
||||
return; // probably not a v4l2 device at all.
|
||||
} else if ((vcap.device_caps & V4L2_CAP_VIDEO_CAPTURE) == 0) {
|
||||
close(fd);
|
||||
return; // not a video capture device.
|
||||
} else if (SDL_FindPhysicalCameraByCallback(FindV4L2CameraByBusInfoCallback, vcap.bus_info)) {
|
||||
close(fd);
|
||||
return; // already have it.
|
||||
}
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: V4L2 camera path='%s' bus_info='%s' name='%s'", path, (const char *) vcap.bus_info, vcap.card);
|
||||
#endif
|
||||
|
||||
CameraFormatAddData add_data;
|
||||
SDL_zero(add_data);
|
||||
|
||||
struct v4l2_fmtdesc fmtdesc;
|
||||
SDL_zero(fmtdesc);
|
||||
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
|
||||
SDL_PixelFormat sdlfmt = SDL_PIXELFORMAT_UNKNOWN;
|
||||
SDL_Colorspace colorspace = SDL_COLORSPACE_UNKNOWN;
|
||||
format_v4l2_to_sdl(fmtdesc.pixelformat, &sdlfmt, &colorspace);
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: - Has format '%s'%s%s", SDL_GetPixelFormatName(sdlfmt),
|
||||
(fmtdesc.flags & V4L2_FMT_FLAG_EMULATED) ? " [EMULATED]" : "",
|
||||
(fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED) ? " [COMPRESSED]" : "");
|
||||
#endif
|
||||
|
||||
fmtdesc.index++; // prepare for next iteration.
|
||||
|
||||
if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) {
|
||||
continue; // unsupported by SDL atm.
|
||||
}
|
||||
|
||||
struct v4l2_frmsizeenum frmsizeenum;
|
||||
SDL_zero(frmsizeenum);
|
||||
frmsizeenum.pixel_format = fmtdesc.pixelformat;
|
||||
|
||||
while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) {
|
||||
if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
|
||||
const int w = (int) frmsizeenum.discrete.width;
|
||||
const int h = (int) frmsizeenum.discrete.height;
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: * Has discrete size %dx%d", w, h);
|
||||
#endif
|
||||
if (!AddCameraFormat(fd, &add_data, sdlfmt, colorspace, fmtdesc.pixelformat, w, h)) {
|
||||
break; // Probably out of memory; we'll go with what we have, if anything.
|
||||
}
|
||||
frmsizeenum.index++; // set up for the next one.
|
||||
} else if ((frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) || (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS)) {
|
||||
const int minw = (int) frmsizeenum.stepwise.min_width;
|
||||
const int minh = (int) frmsizeenum.stepwise.min_height;
|
||||
const int maxw = (int) frmsizeenum.stepwise.max_width;
|
||||
const int maxh = (int) frmsizeenum.stepwise.max_height;
|
||||
const int stepw = (int) frmsizeenum.stepwise.step_width;
|
||||
const int steph = (int) frmsizeenum.stepwise.step_height;
|
||||
for (int w = minw; w <= maxw; w += stepw) {
|
||||
for (int h = minh; h <= maxh; h += steph) {
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: * Has %s size %dx%d", (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) ? "stepwise" : "continuous", w, h);
|
||||
#endif
|
||||
if (!AddCameraFormat(fd, &add_data, sdlfmt, colorspace, fmtdesc.pixelformat, w, h)) {
|
||||
break; // Probably out of memory; we'll go with what we have, if anything.
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: (total specs: %d)", add_data.num_specs);
|
||||
#endif
|
||||
|
||||
if (add_data.num_specs > 0) {
|
||||
V4L2DeviceHandle *handle = (V4L2DeviceHandle *) SDL_calloc(1, sizeof (V4L2DeviceHandle));
|
||||
if (handle) {
|
||||
handle->path = SDL_strdup(path);
|
||||
if (handle->path) {
|
||||
handle->bus_info = SDL_strdup((char *)vcap.bus_info);
|
||||
if (handle->bus_info) {
|
||||
if (SDL_AddCamera((const char *) vcap.card, SDL_CAMERA_POSITION_UNKNOWN, add_data.num_specs, add_data.specs, handle)) {
|
||||
SDL_free(add_data.specs);
|
||||
return; // good to go.
|
||||
}
|
||||
SDL_free(handle->bus_info);
|
||||
}
|
||||
SDL_free(handle->path);
|
||||
}
|
||||
SDL_free(handle);
|
||||
}
|
||||
}
|
||||
SDL_free(add_data.specs);
|
||||
}
|
||||
|
||||
static void V4L2_FreeDeviceHandle(SDL_Camera *device)
|
||||
{
|
||||
if (device) {
|
||||
V4L2DeviceHandle *handle = (V4L2DeviceHandle *) device->handle;
|
||||
SDL_free(handle->path);
|
||||
SDL_free(handle->bus_info);
|
||||
SDL_free(handle);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SDL_USE_LIBUDEV
|
||||
static bool FindV4L2CameraByPathCallback(SDL_Camera *device, void *userdata)
|
||||
{
|
||||
const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
|
||||
return (SDL_strcmp(handle->path, (const char *) userdata) == 0);
|
||||
}
|
||||
|
||||
static void MaybeRemoveDevice(const char *path)
|
||||
{
|
||||
if (path) {
|
||||
SDL_CameraDisconnected(SDL_FindPhysicalCameraByCallback(FindV4L2CameraByPathCallback, (void *) path));
|
||||
}
|
||||
}
|
||||
|
||||
static void CameraUdevCallback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath)
|
||||
{
|
||||
if (devpath && (udev_class & SDL_UDEV_DEVICE_VIDEO_CAPTURE)) {
|
||||
if (udev_type == SDL_UDEV_DEVICEADDED) {
|
||||
MaybeAddDevice(devpath);
|
||||
} else if (udev_type == SDL_UDEV_DEVICEREMOVED) {
|
||||
MaybeRemoveDevice(devpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // SDL_USE_LIBUDEV
|
||||
|
||||
static void V4L2_Deinitialize(void)
|
||||
{
|
||||
#ifdef SDL_USE_LIBUDEV
|
||||
SDL_UDEV_DelCallback(CameraUdevCallback);
|
||||
SDL_UDEV_Quit();
|
||||
#endif // SDL_USE_LIBUDEV
|
||||
}
|
||||
|
||||
static void V4L2_DetectDevices(void)
|
||||
{
|
||||
#ifdef SDL_USE_LIBUDEV
|
||||
if (SDL_UDEV_Init()) {
|
||||
if (SDL_UDEV_AddCallback(CameraUdevCallback)) {
|
||||
SDL_UDEV_Scan(); // Force a scan to build the initial device list
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif // SDL_USE_LIBUDEV
|
||||
|
||||
DIR *dirp = opendir("/dev");
|
||||
if (dirp) {
|
||||
struct dirent *dent;
|
||||
while ((dent = readdir(dirp)) != NULL) {
|
||||
int num = 0;
|
||||
if (SDL_sscanf(dent->d_name, "video%d", &num) == 1) {
|
||||
char fullpath[64];
|
||||
SDL_snprintf(fullpath, sizeof (fullpath), "/dev/video%d", num);
|
||||
MaybeAddDevice(fullpath);
|
||||
}
|
||||
}
|
||||
closedir(dirp);
|
||||
}
|
||||
}
|
||||
|
||||
static bool V4L2_Init(SDL_CameraDriverImpl *impl)
|
||||
{
|
||||
impl->DetectDevices = V4L2_DetectDevices;
|
||||
impl->OpenDevice = V4L2_OpenDevice;
|
||||
impl->CloseDevice = V4L2_CloseDevice;
|
||||
impl->WaitDevice = V4L2_WaitDevice;
|
||||
impl->AcquireFrame = V4L2_AcquireFrame;
|
||||
impl->ReleaseFrame = V4L2_ReleaseFrame;
|
||||
impl->FreeDeviceHandle = V4L2_FreeDeviceHandle;
|
||||
impl->Deinitialize = V4L2_Deinitialize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CameraBootStrap V4L2_bootstrap = {
|
||||
"v4l2", "SDL Video4Linux2 camera driver", V4L2_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_CAMERA_DRIVER_V4L2
|
||||
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_CAMERA_DRIVER_VITA
|
||||
|
||||
#include "../SDL_syscamera.h"
|
||||
#include <psp2/camera.h>
|
||||
#include <psp2/kernel/sysmem.h>
|
||||
|
||||
static struct {
|
||||
Sint32 w;
|
||||
Sint32 h;
|
||||
Sint32 res;
|
||||
} resolutions[] = {
|
||||
{640, 480, SCE_CAMERA_RESOLUTION_640_480},
|
||||
{320, 240, SCE_CAMERA_RESOLUTION_320_240},
|
||||
{160, 120, SCE_CAMERA_RESOLUTION_160_120},
|
||||
{352, 288, SCE_CAMERA_RESOLUTION_352_288},
|
||||
{176, 144, SCE_CAMERA_RESOLUTION_176_144},
|
||||
{480, 272, SCE_CAMERA_RESOLUTION_480_272},
|
||||
{640, 360, SCE_CAMERA_RESOLUTION_640_360},
|
||||
{0, 0, 0}
|
||||
};
|
||||
|
||||
static Sint32 fps[] = {5, 10, 15, 20, 24, 25, 30, 60, 0};
|
||||
|
||||
static void GatherCameraSpecs(Sint32 devid, CameraFormatAddData *add_data, char **fullname, SDL_CameraPosition *position)
|
||||
{
|
||||
SDL_zerop(add_data);
|
||||
|
||||
if (devid == SCE_CAMERA_DEVICE_FRONT) {
|
||||
*position = SDL_CAMERA_POSITION_FRONT_FACING;
|
||||
*fullname = SDL_strdup("Front-facing camera");
|
||||
} else if (devid == SCE_CAMERA_DEVICE_BACK) {
|
||||
*position = SDL_CAMERA_POSITION_BACK_FACING;
|
||||
*fullname = SDL_strdup("Back-facing camera");
|
||||
}
|
||||
|
||||
if (!*fullname) {
|
||||
*fullname = SDL_strdup("Generic camera");
|
||||
}
|
||||
|
||||
// Note: there are actually more fps and pixelformats. Planar YUV is fastest. Support only YUV and integer fps for now
|
||||
Sint32 idx = 0;
|
||||
while (resolutions[idx].res > 0) {
|
||||
Sint32 fps_idx = 0;
|
||||
while (fps[fps_idx] > 0) {
|
||||
SDL_AddCameraFormat(add_data, SDL_PIXELFORMAT_IYUV, SDL_COLORSPACE_BT601_LIMITED, resolutions[idx].w, resolutions[idx].h, fps[fps_idx], 1); /* SCE_CAMERA_FORMAT_ARGB */
|
||||
fps_idx++;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool FindVitaCameraByID(SDL_Camera *device, void *userdata)
|
||||
{
|
||||
Sint32 devid = (Sint32) userdata;
|
||||
return (devid == (Sint32)device->handle);
|
||||
}
|
||||
|
||||
static void MaybeAddDevice(Sint32 devid)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: MaybeAddDevice('%d')", devid);
|
||||
#endif
|
||||
|
||||
if (SDL_FindPhysicalCameraByCallback(FindVitaCameraByID, (void *) devid)) {
|
||||
return; // already have this one.
|
||||
}
|
||||
|
||||
SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
|
||||
char *fullname = NULL;
|
||||
CameraFormatAddData add_data;
|
||||
GatherCameraSpecs(devid, &add_data, &fullname, &position);
|
||||
|
||||
if (add_data.num_specs > 0) {
|
||||
SDL_AddCamera(fullname, position, add_data.num_specs, add_data.specs, (void *)devid);
|
||||
}
|
||||
|
||||
SDL_free(fullname);
|
||||
SDL_free(add_data.specs);
|
||||
}
|
||||
|
||||
static SceUID imbUid = -1;
|
||||
|
||||
static void freeBuffers(SceCameraInfo *info)
|
||||
{
|
||||
if (imbUid != -1) {
|
||||
sceKernelFreeMemBlock(imbUid);
|
||||
info->pIBase = NULL;
|
||||
imbUid = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static bool VITACAMERA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
|
||||
{
|
||||
// we can't open more than one camera, so error-out early
|
||||
if (imbUid != -1) {
|
||||
return SDL_SetError("Only one camera can be active");
|
||||
}
|
||||
|
||||
SceCameraInfo *info = (SceCameraInfo *)SDL_calloc(1, sizeof(SceCameraInfo));
|
||||
|
||||
info->size = sizeof(SceCameraInfo);
|
||||
info->priority = SCE_CAMERA_PRIORITY_SHARE;
|
||||
info->buffer = 0; // target buffer set by sceCameraOpen
|
||||
|
||||
info->framerate = spec->framerate_numerator / spec->framerate_denominator;
|
||||
|
||||
Sint32 idx = 0;
|
||||
while (resolutions[idx].res > 0) {
|
||||
if (spec->width == resolutions[idx].w && spec->height == resolutions[idx].h) {
|
||||
info->resolution = resolutions[idx].res;
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
info->range = 1;
|
||||
info->format = SCE_CAMERA_FORMAT_YUV420_PLANE;
|
||||
info->pitch = 0; // same size surface
|
||||
|
||||
info->sizeIBase = spec->width * spec->height;
|
||||
info->sizeUBase = ((spec->width+1)/2) * ((spec->height+1) / 2);
|
||||
info->sizeVBase = ((spec->width+1)/2) * ((spec->height+1) / 2);
|
||||
|
||||
// PHYCONT memory size *must* be a multiple of 1MB, we can just always spend 2MB, since we don't use PHYCONT anywhere else
|
||||
imbUid = sceKernelAllocMemBlock("CameraI", SCE_KERNEL_MEMBLOCK_TYPE_USER_MAIN_PHYCONT_NC_RW, 2 * 1024 * 1024 , NULL);
|
||||
if (imbUid < 0)
|
||||
{
|
||||
return SDL_SetError("sceKernelAllocMemBlock error: 0x%08X", imbUid);
|
||||
}
|
||||
sceKernelGetMemBlockBase(imbUid, &(info->pIBase));
|
||||
|
||||
info->pUBase = info->pIBase + info->sizeIBase;
|
||||
info->pVBase = info->pIBase + (info->sizeIBase + info->sizeUBase);
|
||||
|
||||
device->hidden = (struct SDL_PrivateCameraData *)info;
|
||||
|
||||
int ret = sceCameraOpen((int)device->handle, info);
|
||||
if (ret == 0) {
|
||||
ret = sceCameraStart((int)device->handle);
|
||||
if (ret == 0) {
|
||||
SDL_CameraPermissionOutcome(device, true);
|
||||
return true;
|
||||
} else {
|
||||
SDL_SetError("sceCameraStart error: 0x%08X", imbUid);
|
||||
}
|
||||
} else {
|
||||
SDL_SetError("sceCameraOpen error: 0x%08X", imbUid);
|
||||
}
|
||||
|
||||
freeBuffers(info);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void VITACAMERA_CloseDevice(SDL_Camera *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
sceCameraStop((int)device->handle);
|
||||
sceCameraClose((int)device->handle);
|
||||
freeBuffers((SceCameraInfo *)device->hidden);
|
||||
SDL_free(device->hidden);
|
||||
}
|
||||
}
|
||||
|
||||
static bool VITACAMERA_WaitDevice(SDL_Camera *device)
|
||||
{
|
||||
while(!sceCameraIsActive((int)device->handle)) {}
|
||||
return true;
|
||||
}
|
||||
|
||||
static SDL_CameraFrameResult VITACAMERA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, float *rotation)
|
||||
{
|
||||
SceCameraRead read = {0};
|
||||
read.size = sizeof(SceCameraRead);
|
||||
read.mode = 1; // don't wait next frame
|
||||
|
||||
int ret = sceCameraRead((int)device->handle, &read);
|
||||
|
||||
if (ret < 0) {
|
||||
SDL_SetError("sceCameraRead error: 0x%08X", ret);
|
||||
return SDL_CAMERA_FRAME_ERROR;
|
||||
}
|
||||
|
||||
*timestampNS = read.timestamp;
|
||||
|
||||
SceCameraInfo *info = (SceCameraInfo *)(device->hidden);
|
||||
|
||||
frame->pitch = info->width;
|
||||
frame->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), info->sizeIBase + info->sizeUBase + info->sizeVBase);
|
||||
|
||||
if (frame->pixels) {
|
||||
SDL_memcpy(frame->pixels, info->pIBase, info->sizeIBase + info->sizeUBase + info->sizeVBase);
|
||||
return SDL_CAMERA_FRAME_READY;
|
||||
}
|
||||
|
||||
return SDL_CAMERA_FRAME_ERROR;
|
||||
}
|
||||
|
||||
static void VITACAMERA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
|
||||
{
|
||||
SDL_aligned_free(frame->pixels);
|
||||
}
|
||||
|
||||
static void VITACAMERA_DetectDevices(void)
|
||||
{
|
||||
MaybeAddDevice(SCE_CAMERA_DEVICE_FRONT);
|
||||
MaybeAddDevice(SCE_CAMERA_DEVICE_BACK);
|
||||
}
|
||||
|
||||
static void VITACAMERA_FreeDeviceHandle(SDL_Camera *device)
|
||||
{
|
||||
}
|
||||
|
||||
static void VITACAMERA_Deinitialize(void)
|
||||
{
|
||||
}
|
||||
|
||||
static bool VITACAMERA_Init(SDL_CameraDriverImpl *impl)
|
||||
{
|
||||
impl->DetectDevices = VITACAMERA_DetectDevices;
|
||||
impl->OpenDevice = VITACAMERA_OpenDevice;
|
||||
impl->CloseDevice = VITACAMERA_CloseDevice;
|
||||
impl->WaitDevice = VITACAMERA_WaitDevice;
|
||||
impl->AcquireFrame = VITACAMERA_AcquireFrame;
|
||||
impl->ReleaseFrame = VITACAMERA_ReleaseFrame;
|
||||
impl->FreeDeviceHandle = VITACAMERA_FreeDeviceHandle;
|
||||
impl->Deinitialize = VITACAMERA_Deinitialize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CameraBootStrap VITACAMERA_bootstrap = {
|
||||
"vita", "SDL PSVita camera driver", VITACAMERA_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_CAMERA_DRIVER_VITA
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_core_unsupported.h"
|
||||
|
||||
#ifndef SDL_VIDEO_DRIVER_X11
|
||||
|
||||
void SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* !SDL_VIDEO_DRIVER_X11 */
|
||||
|
||||
#ifndef SDL_PLATFORM_LINUX
|
||||
|
||||
bool SDL_SetLinuxThreadPriority(Sint64 threadID, int priority)
|
||||
{
|
||||
(void)threadID;
|
||||
(void)priority;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
bool SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy)
|
||||
{
|
||||
(void)threadID;
|
||||
(void)sdlPriority;
|
||||
(void)schedPolicy;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
#endif /* !SDL_PLATFORM_LINUX */
|
||||
|
||||
#ifndef SDL_PLATFORM_GDK
|
||||
|
||||
void SDL_GDKSuspendComplete(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
}
|
||||
|
||||
bool SDL_GetGDKDefaultUser(XUserHandle *outUserHandle)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
void SDL_GDKSuspendGPU(SDL_GPUDevice *device)
|
||||
{
|
||||
}
|
||||
|
||||
void SDL_GDKResumeGPU(SDL_GPUDevice *device)
|
||||
{
|
||||
}
|
||||
|
||||
void SDL_GDKSuspendRenderer(SDL_Renderer *renderer)
|
||||
{
|
||||
}
|
||||
|
||||
void SDL_GDKResumeRenderer(SDL_Renderer *renderer)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* !SDL_PLATFORM_GDK */
|
||||
|
||||
#if !defined(SDL_PLATFORM_WINDOWS)
|
||||
|
||||
bool SDL_RegisterApp(const char *name, Uint32 style, void *hInst)
|
||||
{
|
||||
(void)name;
|
||||
(void)style;
|
||||
(void)hInst;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
void SDL_SetWindowsMessageHook(void *callback, void *userdata)
|
||||
{
|
||||
(void)callback;
|
||||
(void)userdata;
|
||||
SDL_Unsupported();
|
||||
}
|
||||
|
||||
void SDL_UnregisterApp(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
}
|
||||
|
||||
#endif /* !SDL_PLATFORM_WINDOWS */
|
||||
|
||||
#ifndef SDL_PLATFORM_ANDROID
|
||||
|
||||
void SDL_SendAndroidBackButton(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
}
|
||||
|
||||
void *SDL_GetAndroidActivity(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *SDL_GetAndroidCachePath(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
const char *SDL_GetAndroidExternalStoragePath(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Uint32 SDL_GetAndroidExternalStorageState(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return 0;
|
||||
}
|
||||
const char *SDL_GetAndroidInternalStoragePath(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *SDL_GetAndroidJNIEnv(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata)
|
||||
{
|
||||
(void)permission;
|
||||
(void)cb;
|
||||
(void)userdata;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
bool SDL_SendAndroidMessage(Uint32 command, int param)
|
||||
{
|
||||
(void)command;
|
||||
(void)param;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
bool SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xoffset, int yoffset)
|
||||
{
|
||||
(void)message;
|
||||
(void)duration;
|
||||
(void)gravity;
|
||||
(void)xoffset;
|
||||
(void)yoffset;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
int SDL_GetAndroidSDKVersion(void)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
bool SDL_IsChromebook(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDL_IsDeXMode(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return false;
|
||||
}
|
||||
|
||||
Sint32 JNI_OnLoad(JavaVM *vm, void *reserved)
|
||||
{
|
||||
(void)vm;
|
||||
(void)reserved;
|
||||
return 0x00010004; // JNI_VERSION_1_4
|
||||
}
|
||||
#endif
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as 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_VIDEO_DRIVER_X11
|
||||
extern SDL_DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata);
|
||||
#endif
|
||||
|
||||
#ifndef SDL_PLATFORM_LINUX
|
||||
extern SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriority(Sint64 threadID, int priority);
|
||||
extern SDL_DECLSPEC bool SDLCALL SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy);
|
||||
#endif
|
||||
|
||||
#if !defined(SDL_PLATFORM_GDK)
|
||||
typedef struct XUserHandle XUserHandle;
|
||||
|
||||
extern SDL_DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void);
|
||||
extern SDL_DECLSPEC bool SDLCALL SDL_GetGDKDefaultUser(XUserHandle *outUserHandle);
|
||||
extern SDL_DECLSPEC void SDLCALL SDL_GDKSuspendGPU(SDL_GPUDevice *device);
|
||||
extern SDL_DECLSPEC void SDLCALL SDL_GDKResumeGPU(SDL_GPUDevice *device);
|
||||
extern SDL_DECLSPEC void SDLCALL SDL_GDKSuspendRenderer(SDL_Renderer *renderer);
|
||||
extern SDL_DECLSPEC void SDLCALL SDL_GDKResumeRenderer(SDL_Renderer *renderer);
|
||||
#endif /* !SDL_PLATFORM_GDK */
|
||||
|
||||
#if !defined(SDL_PLATFORM_WINDOWS)
|
||||
extern SDL_DECLSPEC bool SDLCALL SDL_RegisterApp(const char *name, Uint32 style, void *hInst);
|
||||
extern SDL_DECLSPEC void SDLCALL SDL_SetWindowsMessageHook(void *callback, void *userdata);
|
||||
extern SDL_DECLSPEC void SDLCALL SDL_UnregisterApp(void);
|
||||
#endif /* !SDL_PLATFORM_WINDOWS */
|
||||
|
||||
#if !defined(SDL_PLATFORM_ANDROID)
|
||||
|
||||
typedef void *SDL_RequestAndroidPermissionCallback;
|
||||
typedef void *JavaVM;
|
||||
|
||||
extern SDL_DECLSPEC void SDLCALL SDL_SendAndroidBackButton(void);
|
||||
extern SDL_DECLSPEC void * SDLCALL SDL_GetAndroidActivity(void);
|
||||
extern SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidCachePath(void);
|
||||
extern SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidExternalStoragePath(void);
|
||||
extern SDL_DECLSPEC Uint32 SDLCALL SDL_GetAndroidExternalStorageState(void);
|
||||
extern SDL_DECLSPEC const char * SDLCALL SDL_GetAndroidInternalStoragePath(void);
|
||||
extern SDL_DECLSPEC void * SDLCALL SDL_GetAndroidJNIEnv(void);
|
||||
extern SDL_DECLSPEC bool SDLCALL SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata);
|
||||
extern SDL_DECLSPEC bool SDLCALL SDL_SendAndroidMessage(Uint32 command, int param);
|
||||
extern SDL_DECLSPEC bool SDLCALL SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xoffset, int yoffset);
|
||||
extern SDL_DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void);
|
||||
extern SDL_DECLSPEC bool SDLCALL SDL_IsChromebook(void);
|
||||
extern SDL_DECLSPEC bool SDLCALL SDL_IsDeXMode(void);
|
||||
extern SDL_DECLSPEC Sint32 SDLCALL JNI_OnLoad(JavaVM *vm, void *reserved);
|
||||
#endif /* !SDL_PLATFORM_ANDROID */
|
||||
+3426
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user