mirror of
https://github.com/love2d/megasource.git
synced 2026-08-23 14:05:35 +02:00
Add SDL3 3.1.1-preview
This commit is contained in:
@@ -0,0 +1,716 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
#include "SDL3/SDL_revision.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_GDK)
|
||||
#include "core/windows/SDL_windows.h"
|
||||
#elif !defined(SDL_PLATFORM_WINRT)
|
||||
#include <unistd.h> /* _exit(), etc. */
|
||||
#endif
|
||||
|
||||
/* this checks for HAVE_DBUS_DBUS_H internally. */
|
||||
#include "core/linux/SDL_dbus.h"
|
||||
|
||||
#ifdef SDL_PLATFORM_EMSCRIPTEN
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
/* Initialization code for SDL */
|
||||
|
||||
#include "SDL_assert_c.h"
|
||||
#include "SDL_hints_c.h"
|
||||
#include "SDL_log_c.h"
|
||||
#include "SDL_properties_c.h"
|
||||
#include "audio/SDL_sysaudio.h"
|
||||
#include "cpuinfo/SDL_cpuinfo_c.h"
|
||||
#include "video/SDL_video_c.h"
|
||||
#include "events/SDL_events_c.h"
|
||||
#include "haptic/SDL_haptic_c.h"
|
||||
#include "joystick/SDL_gamepad_c.h"
|
||||
#include "joystick/SDL_joystick_c.h"
|
||||
#include "sensor/SDL_sensor_c.h"
|
||||
#include "camera/SDL_camera_c.h"
|
||||
|
||||
#define SDL_INIT_EVERYTHING ~0U
|
||||
|
||||
/* Initialization/Cleanup routines */
|
||||
#include "time/SDL_time_c.h"
|
||||
#include "timer/SDL_timer_c.h"
|
||||
#ifdef SDL_VIDEO_DRIVER_WINDOWS
|
||||
extern int SDL_HelperWindowCreate(void);
|
||||
extern int SDL_HelperWindowDestroy(void);
|
||||
#endif
|
||||
|
||||
#ifdef SDL_BUILD_MAJOR_VERSION
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MAJOR_VERSION,
|
||||
SDL_MAJOR_VERSION == SDL_BUILD_MAJOR_VERSION);
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MINOR_VERSION,
|
||||
SDL_MINOR_VERSION == SDL_BUILD_MINOR_VERSION);
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MICRO_VERSION,
|
||||
SDL_PATCHLEVEL == SDL_BUILD_MICRO_VERSION);
|
||||
#endif
|
||||
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_MAJOR_VERSION_min, SDL_MAJOR_VERSION >= 0);
|
||||
/* Limited only by the need to fit in SDL_Version */
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_MAJOR_VERSION_max, SDL_MAJOR_VERSION <= 255);
|
||||
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_MINOR_VERSION_min, SDL_MINOR_VERSION >= 0);
|
||||
/* Limited only by the need to fit in SDL_Version */
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_MINOR_VERSION_max, SDL_MINOR_VERSION <= 255);
|
||||
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_PATCHLEVEL_min, SDL_PATCHLEVEL >= 0);
|
||||
/* Limited by its encoding in SDL_VERSIONNUM and in the ABI versions */
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_PATCHLEVEL_max, SDL_PATCHLEVEL <= 99);
|
||||
|
||||
/* This is not declared in any header, although it is shared between some
|
||||
parts of SDL, because we don't want anything calling it without an
|
||||
extremely good reason. */
|
||||
extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
|
||||
SDL_NORETURN void SDL_ExitProcess(int exitcode)
|
||||
{
|
||||
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_GDK)
|
||||
/* "if you do not know the state of all threads in your process, it is
|
||||
better to call TerminateProcess than ExitProcess"
|
||||
https://msdn.microsoft.com/en-us/library/windows/desktop/ms682658(v=vs.85).aspx */
|
||||
TerminateProcess(GetCurrentProcess(), exitcode);
|
||||
/* MingW doesn't have TerminateProcess marked as noreturn, so add an
|
||||
ExitProcess here that will never be reached but make MingW happy. */
|
||||
ExitProcess(exitcode);
|
||||
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
|
||||
emscripten_cancel_main_loop(); /* this should "kill" the app. */
|
||||
emscripten_force_exit(exitcode); /* this should "kill" the app. */
|
||||
exit(exitcode);
|
||||
#elif defined(SDL_PLATFORM_HAIKU) /* Haiku has _Exit, but it's not marked noreturn. */
|
||||
_exit(exitcode);
|
||||
#elif defined(HAVE__EXIT) /* Upper case _Exit() */
|
||||
_Exit(exitcode);
|
||||
#else
|
||||
_exit(exitcode);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* The initialized subsystems */
|
||||
#ifdef SDL_MAIN_NEEDED
|
||||
static SDL_bool SDL_MainIsReady = SDL_FALSE;
|
||||
#else
|
||||
static SDL_bool SDL_MainIsReady = SDL_TRUE;
|
||||
#endif
|
||||
static SDL_bool SDL_bInMainQuit = SDL_FALSE;
|
||||
static Uint8 SDL_SubsystemRefCount[32];
|
||||
|
||||
/* Private helper to increment a subsystem's ref counter. */
|
||||
static void SDL_IncrementSubsystemRefCount(Uint32 subsystem)
|
||||
{
|
||||
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
|
||||
SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255));
|
||||
if (subsystem_index >= 0) {
|
||||
++SDL_SubsystemRefCount[subsystem_index];
|
||||
}
|
||||
}
|
||||
|
||||
/* Private helper to decrement a subsystem's ref counter. */
|
||||
static void SDL_DecrementSubsystemRefCount(Uint32 subsystem)
|
||||
{
|
||||
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
|
||||
if ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] > 0)) {
|
||||
if (SDL_bInMainQuit) {
|
||||
SDL_SubsystemRefCount[subsystem_index] = 0;
|
||||
} else {
|
||||
--SDL_SubsystemRefCount[subsystem_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Private helper to check if a system needs init. */
|
||||
static SDL_bool SDL_ShouldInitSubsystem(Uint32 subsystem)
|
||||
{
|
||||
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
|
||||
SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255));
|
||||
return ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 0));
|
||||
}
|
||||
|
||||
/* Private helper to check if a system needs to be quit. */
|
||||
static SDL_bool SDL_ShouldQuitSubsystem(Uint32 subsystem)
|
||||
{
|
||||
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
|
||||
if ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 0)) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
/* If we're in SDL_Quit, we shut down every subsystem, even if refcount
|
||||
* isn't zero.
|
||||
*/
|
||||
return (((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 1)) || SDL_bInMainQuit);
|
||||
}
|
||||
|
||||
/* Private helper to either increment's existing ref counter,
|
||||
* or fully init a new subsystem. */
|
||||
static SDL_bool SDL_InitOrIncrementSubsystem(Uint32 subsystem)
|
||||
{
|
||||
int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
|
||||
SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255));
|
||||
if (subsystem_index < 0) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
if (SDL_SubsystemRefCount[subsystem_index] > 0) {
|
||||
++SDL_SubsystemRefCount[subsystem_index];
|
||||
return SDL_TRUE;
|
||||
}
|
||||
return SDL_InitSubSystem(subsystem) == 0;
|
||||
}
|
||||
|
||||
void SDL_SetMainReady(void)
|
||||
{
|
||||
SDL_MainIsReady = SDL_TRUE;
|
||||
}
|
||||
|
||||
int SDL_InitSubSystem(Uint32 flags)
|
||||
{
|
||||
Uint32 flags_initialized = 0;
|
||||
|
||||
if (!SDL_MainIsReady) {
|
||||
return SDL_SetError("Application didn't initialize properly, did you include SDL_main.h in the file containing your main() function?");
|
||||
}
|
||||
|
||||
SDL_InitLog();
|
||||
SDL_InitProperties();
|
||||
SDL_GetGlobalProperties();
|
||||
|
||||
/* Clear the error message */
|
||||
SDL_ClearError();
|
||||
|
||||
#ifdef SDL_USE_LIBDBUS
|
||||
SDL_DBus_Init();
|
||||
#endif
|
||||
|
||||
#ifdef SDL_VIDEO_DRIVER_WINDOWS
|
||||
if (flags & (SDL_INIT_HAPTIC | SDL_INIT_JOYSTICK)) {
|
||||
if (SDL_HelperWindowCreate() < 0) {
|
||||
goto quit_and_error;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SDL_InitTime();
|
||||
SDL_InitTicks();
|
||||
|
||||
/* Initialize the event subsystem */
|
||||
if (flags & SDL_INIT_EVENTS) {
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_EVENTS)) {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS);
|
||||
if (SDL_InitEvents() < 0) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS);
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_EVENTS;
|
||||
}
|
||||
|
||||
/* Initialize the timer subsystem */
|
||||
if (flags & SDL_INIT_TIMER) {
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_TIMER)) {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_TIMER);
|
||||
if (SDL_InitTimers() < 0) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_TIMER);
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_TIMER);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_TIMER;
|
||||
}
|
||||
|
||||
/* Initialize the video subsystem */
|
||||
if (flags & SDL_INIT_VIDEO) {
|
||||
#ifndef SDL_VIDEO_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_VIDEO)) {
|
||||
/* video implies events */
|
||||
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
|
||||
goto quit_and_error;
|
||||
}
|
||||
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO);
|
||||
if (SDL_VideoInit(NULL) < 0) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_VIDEO;
|
||||
#else
|
||||
SDL_SetError("SDL not built with video support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Initialize the audio subsystem */
|
||||
if (flags & SDL_INIT_AUDIO) {
|
||||
#ifndef SDL_AUDIO_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_AUDIO)) {
|
||||
/* audio implies events */
|
||||
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
|
||||
goto quit_and_error;
|
||||
}
|
||||
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_AUDIO);
|
||||
if (SDL_InitAudio(NULL) < 0) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO);
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_AUDIO);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_AUDIO;
|
||||
#else
|
||||
SDL_SetError("SDL not built with audio support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Initialize the joystick subsystem */
|
||||
if (flags & SDL_INIT_JOYSTICK) {
|
||||
#ifndef SDL_JOYSTICK_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_JOYSTICK)) {
|
||||
/* joystick implies events */
|
||||
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
|
||||
goto quit_and_error;
|
||||
}
|
||||
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_JOYSTICK);
|
||||
if (SDL_InitJoysticks() < 0) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK);
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_JOYSTICK;
|
||||
#else
|
||||
SDL_SetError("SDL not built with joystick support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (flags & SDL_INIT_GAMEPAD) {
|
||||
#ifndef SDL_JOYSTICK_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_GAMEPAD)) {
|
||||
/* game controller implies joystick */
|
||||
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_JOYSTICK)) {
|
||||
goto quit_and_error;
|
||||
}
|
||||
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_GAMEPAD);
|
||||
if (SDL_InitGamepads() < 0) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD);
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_GAMEPAD);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_GAMEPAD;
|
||||
#else
|
||||
SDL_SetError("SDL not built with joystick support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Initialize the haptic subsystem */
|
||||
if (flags & SDL_INIT_HAPTIC) {
|
||||
#ifndef SDL_HAPTIC_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_HAPTIC)) {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_HAPTIC);
|
||||
if (SDL_InitHaptics() < 0) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_HAPTIC);
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_HAPTIC);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_HAPTIC;
|
||||
#else
|
||||
SDL_SetError("SDL not built with haptic (force feedback) support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Initialize the sensor subsystem */
|
||||
if (flags & SDL_INIT_SENSOR) {
|
||||
#ifndef SDL_SENSOR_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_SENSOR)) {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_SENSOR);
|
||||
if (SDL_InitSensors() < 0) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_SENSOR);
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_SENSOR);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_SENSOR;
|
||||
#else
|
||||
SDL_SetError("SDL not built with sensor support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Initialize the camera subsystem */
|
||||
if (flags & SDL_INIT_CAMERA) {
|
||||
#ifndef SDL_CAMERA_DISABLED
|
||||
if (SDL_ShouldInitSubsystem(SDL_INIT_CAMERA)) {
|
||||
/* camera implies events */
|
||||
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
|
||||
goto quit_and_error;
|
||||
}
|
||||
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA);
|
||||
if (SDL_CameraInit(NULL) < 0) {
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
|
||||
goto quit_and_error;
|
||||
}
|
||||
} else {
|
||||
SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA);
|
||||
}
|
||||
flags_initialized |= SDL_INIT_CAMERA;
|
||||
#else
|
||||
SDL_SetError("SDL not built with camera support");
|
||||
goto quit_and_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
(void)flags_initialized; /* make static analysis happy, since this only gets used in error cases. */
|
||||
|
||||
return 0;
|
||||
|
||||
quit_and_error:
|
||||
SDL_QuitSubSystem(flags_initialized);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int SDL_Init(Uint32 flags)
|
||||
{
|
||||
return SDL_InitSubSystem(flags);
|
||||
}
|
||||
|
||||
void SDL_QuitSubSystem(Uint32 flags)
|
||||
{
|
||||
/* Shut down requested initialized subsystems */
|
||||
|
||||
#ifndef SDL_CAMERA_DISABLED
|
||||
if (flags & SDL_INIT_CAMERA) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_CAMERA)) {
|
||||
SDL_QuitCamera();
|
||||
/* camera implies events */
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SDL_SENSOR_DISABLED
|
||||
if (flags & SDL_INIT_SENSOR) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_SENSOR)) {
|
||||
SDL_QuitSensors();
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_SENSOR);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SDL_JOYSTICK_DISABLED
|
||||
if (flags & SDL_INIT_GAMEPAD) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_GAMEPAD)) {
|
||||
SDL_QuitGamepads();
|
||||
/* game controller implies joystick */
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD);
|
||||
}
|
||||
|
||||
if (flags & SDL_INIT_JOYSTICK) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_JOYSTICK)) {
|
||||
SDL_QuitJoysticks();
|
||||
/* joystick implies events */
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HAPTIC_DISABLED
|
||||
if (flags & SDL_INIT_HAPTIC) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_HAPTIC)) {
|
||||
SDL_QuitHaptics();
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_HAPTIC);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SDL_AUDIO_DISABLED
|
||||
if (flags & SDL_INIT_AUDIO) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_AUDIO)) {
|
||||
SDL_QuitAudio();
|
||||
/* audio implies events */
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SDL_VIDEO_DISABLED
|
||||
if (flags & SDL_INIT_VIDEO) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_VIDEO)) {
|
||||
SDL_VideoQuit();
|
||||
/* video implies events */
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (flags & SDL_INIT_TIMER) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_TIMER)) {
|
||||
SDL_QuitTimers();
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_TIMER);
|
||||
}
|
||||
|
||||
if (flags & SDL_INIT_EVENTS) {
|
||||
if (SDL_ShouldQuitSubsystem(SDL_INIT_EVENTS)) {
|
||||
SDL_QuitEvents();
|
||||
}
|
||||
SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS);
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 SDL_WasInit(Uint32 flags)
|
||||
{
|
||||
int i;
|
||||
int num_subsystems = SDL_arraysize(SDL_SubsystemRefCount);
|
||||
Uint32 initialized = 0;
|
||||
|
||||
/* Fast path for checking one flag */
|
||||
if (SDL_HasExactlyOneBitSet32(flags)) {
|
||||
int subsystem_index = SDL_MostSignificantBitIndex32(flags);
|
||||
return SDL_SubsystemRefCount[subsystem_index] ? flags : 0;
|
||||
}
|
||||
|
||||
if (!flags) {
|
||||
flags = SDL_INIT_EVERYTHING;
|
||||
}
|
||||
|
||||
num_subsystems = SDL_min(num_subsystems, SDL_MostSignificantBitIndex32(flags) + 1);
|
||||
|
||||
/* Iterate over each bit in flags, and check the matching subsystem. */
|
||||
for (i = 0; i < num_subsystems; ++i) {
|
||||
if ((flags & 1) && SDL_SubsystemRefCount[i] > 0) {
|
||||
initialized |= (1 << i);
|
||||
}
|
||||
|
||||
flags >>= 1;
|
||||
}
|
||||
|
||||
return initialized;
|
||||
}
|
||||
|
||||
void SDL_Quit(void)
|
||||
{
|
||||
SDL_bInMainQuit = SDL_TRUE;
|
||||
|
||||
/* Quit all subsystems */
|
||||
#ifdef SDL_VIDEO_DRIVER_WINDOWS
|
||||
SDL_HelperWindowDestroy();
|
||||
#endif
|
||||
SDL_QuitSubSystem(SDL_INIT_EVERYTHING);
|
||||
|
||||
SDL_QuitTicks();
|
||||
SDL_QuitTime();
|
||||
|
||||
#ifdef SDL_USE_LIBDBUS
|
||||
SDL_DBus_Quit();
|
||||
#endif
|
||||
|
||||
SDL_ClearHints();
|
||||
SDL_AssertionsQuit();
|
||||
|
||||
SDL_QuitCPUInfo();
|
||||
|
||||
SDL_QuitProperties();
|
||||
SDL_QuitLog();
|
||||
|
||||
/* Now that every subsystem has been quit, we reset the subsystem refcount
|
||||
* and the list of initialized subsystems.
|
||||
*/
|
||||
SDL_memset(SDL_SubsystemRefCount, 0x0, sizeof(SDL_SubsystemRefCount));
|
||||
|
||||
SDL_CleanupTLS();
|
||||
|
||||
SDL_bInMainQuit = SDL_FALSE;
|
||||
}
|
||||
|
||||
/* Assume we can wrap SDL_AtomicInt values and cast to Uint32 */
|
||||
SDL_COMPILE_TIME_ASSERT(sizeof_object_id, sizeof(int) == sizeof(Uint32));
|
||||
|
||||
Uint32 SDL_GetNextObjectID(void)
|
||||
{
|
||||
static SDL_AtomicInt last_id;
|
||||
|
||||
Uint32 id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
|
||||
if (id == 0) {
|
||||
id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/* Get the library version number */
|
||||
int SDL_GetVersion(SDL_Version *ver)
|
||||
{
|
||||
static SDL_bool check_hint = SDL_TRUE;
|
||||
static SDL_bool legacy_version = SDL_FALSE;
|
||||
|
||||
if (!ver) {
|
||||
return SDL_InvalidParamError("ver");
|
||||
}
|
||||
|
||||
SDL_VERSION(ver);
|
||||
|
||||
if (check_hint) {
|
||||
check_hint = SDL_FALSE;
|
||||
legacy_version = SDL_GetHintBoolean("SDL_LEGACY_VERSION", SDL_FALSE);
|
||||
}
|
||||
|
||||
if (legacy_version) {
|
||||
/* Prior to SDL 2.24.0, the patch version was incremented with every release */
|
||||
ver->patch = ver->minor;
|
||||
ver->minor = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get the library source revision */
|
||||
const char *SDL_GetRevision(void)
|
||||
{
|
||||
return SDL_REVISION;
|
||||
}
|
||||
|
||||
/* Get the name of the platform */
|
||||
const char *SDL_GetPlatform(void)
|
||||
{
|
||||
#if defined(SDL_PLATFORM_AIX)
|
||||
return "AIX";
|
||||
#elif defined(SDL_PLATFORM_ANDROID)
|
||||
return "Android";
|
||||
#elif defined(SDL_PLATFORM_BSDI)
|
||||
return "BSDI";
|
||||
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
|
||||
return "Emscripten";
|
||||
#elif defined(SDL_PLATFORM_FREEBSD)
|
||||
return "FreeBSD";
|
||||
#elif defined(SDL_PLATFORM_HAIKU)
|
||||
return "Haiku";
|
||||
#elif defined(SDL_PLATFORM_HPUX)
|
||||
return "HP-UX";
|
||||
#elif defined(SDL_PLATFORM_IRIX)
|
||||
return "Irix";
|
||||
#elif defined(SDL_PLATFORM_LINUX)
|
||||
return "Linux";
|
||||
#elif defined(__MINT__)
|
||||
return "Atari MiNT";
|
||||
#elif defined(SDL_PLATFORM_MACOS)
|
||||
return "macOS";
|
||||
#elif defined(SDL_PLATFORM_NETBSD)
|
||||
return "NetBSD";
|
||||
#elif defined(SDL_PLATFORM_OPENBSD)
|
||||
return "OpenBSD";
|
||||
#elif defined(SDL_PLATFORM_OS2)
|
||||
return "OS/2";
|
||||
#elif defined(SDL_PLATFORM_OSF)
|
||||
return "OSF/1";
|
||||
#elif defined(SDL_PLATFORM_QNXNTO)
|
||||
return "QNX Neutrino";
|
||||
#elif defined(SDL_PLATFORM_RISCOS)
|
||||
return "RISC OS";
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
return "Solaris";
|
||||
#elif defined(SDL_PLATFORM_WIN32)
|
||||
return "Windows";
|
||||
#elif defined(SDL_PLATFORM_WINRT)
|
||||
return "WinRT";
|
||||
#elif defined(SDL_PLATFORM_WINGDK)
|
||||
return "WinGDK";
|
||||
#elif defined(SDL_PLATFORM_XBOXONE)
|
||||
return "Xbox One";
|
||||
#elif defined(SDL_PLATFORM_XBOXSERIES)
|
||||
return "Xbox Series X|S";
|
||||
#elif defined(SDL_PLATFORM_IOS)
|
||||
return "iOS";
|
||||
#elif defined(SDL_PLATFORM_TVOS)
|
||||
return "tvOS";
|
||||
#elif defined(SDL_PLATFORM_PS2)
|
||||
return "PlayStation 2";
|
||||
#elif defined(SDL_PLATFORM_PSP)
|
||||
return "PlayStation Portable";
|
||||
#elif defined(SDL_PLATFORM_VITA)
|
||||
return "PlayStation Vita";
|
||||
#elif defined(SDL_PLATFORM_NGAGE)
|
||||
return "Nokia N-Gage";
|
||||
#elif defined(SDL_PLATFORM_3DS)
|
||||
return "Nintendo 3DS";
|
||||
#elif defined(__managarm__)
|
||||
return "Managarm";
|
||||
#else
|
||||
return "Unknown (see SDL_platform.h)";
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_bool SDL_IsTablet(void)
|
||||
{
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
extern SDL_bool SDL_IsAndroidTablet(void);
|
||||
return SDL_IsAndroidTablet();
|
||||
#elif defined(SDL_PLATFORM_IOS)
|
||||
extern SDL_bool SDL_IsIPad(void);
|
||||
return SDL_IsIPad();
|
||||
#else
|
||||
return SDL_FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef SDL_PLATFORM_WIN32
|
||||
|
||||
#if (!defined(HAVE_LIBC) || defined(__WATCOMC__)) && !defined(SDL_STATIC_LIB)
|
||||
/* FIXME: Still need to include DllMain() on Watcom C ? */
|
||||
|
||||
BOOL APIENTRY MINGW32_FORCEALIGN _DllMainCRTStartup(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call) {
|
||||
case DLL_PROCESS_ATTACH:
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
case DLL_PROCESS_DETACH:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
#endif /* Building DLL */
|
||||
|
||||
#endif /* defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_GDK) */
|
||||
@@ -0,0 +1,453 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_GDK)
|
||||
#include "core/windows/SDL_windows.h"
|
||||
#endif
|
||||
|
||||
#include "SDL_assert_c.h"
|
||||
#include "video/SDL_sysvideo.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_GDK)
|
||||
#ifndef WS_OVERLAPPEDWINDOW
|
||||
#define WS_OVERLAPPEDWINDOW 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef SDL_PLATFORM_EMSCRIPTEN
|
||||
#include <emscripten.h>
|
||||
/* older Emscriptens don't have this, but we need to for wasm64 compatibility. */
|
||||
#ifndef MAIN_THREAD_EM_ASM_PTR
|
||||
#ifdef __wasm64__
|
||||
#error You need to upgrade your Emscripten compiler to support wasm64
|
||||
#else
|
||||
#define MAIN_THREAD_EM_ASM_PTR MAIN_THREAD_EM_ASM_INT
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* The size of the stack buffer to use for rendering assert messages. */
|
||||
#define SDL_MAX_ASSERT_MESSAGE_STACK 256
|
||||
|
||||
static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata);
|
||||
|
||||
/*
|
||||
* We keep all triggered assertions in a singly-linked list so we can
|
||||
* generate a report later.
|
||||
*/
|
||||
static SDL_AssertData *triggered_assertions = NULL;
|
||||
|
||||
#ifndef SDL_THREADS_DISABLED
|
||||
static SDL_Mutex *assertion_mutex = NULL;
|
||||
#endif
|
||||
|
||||
static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion;
|
||||
static void *assertion_userdata = NULL;
|
||||
|
||||
#ifdef __GNUC__
|
||||
static void debug_print(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||
#endif
|
||||
|
||||
static void debug_print(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_WARN, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static void SDL_AddAssertionToReport(SDL_AssertData *data)
|
||||
{
|
||||
/* (data) is always a static struct defined with the assert macros, so
|
||||
we don't have to worry about copying or allocating them. */
|
||||
data->trigger_count++;
|
||||
if (data->trigger_count == 1) { /* not yet added? */
|
||||
data->next = triggered_assertions;
|
||||
triggered_assertions = data;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_GDK)
|
||||
#define ENDLINE "\r\n"
|
||||
#else
|
||||
#define ENDLINE "\n"
|
||||
#endif
|
||||
|
||||
static int SDL_RenderAssertMessage(char *buf, size_t buf_len, const SDL_AssertData *data)
|
||||
{
|
||||
return SDL_snprintf(buf, buf_len,
|
||||
"Assertion failure at %s (%s:%d), triggered %u %s:" ENDLINE " '%s'",
|
||||
data->function, data->filename, data->linenum,
|
||||
data->trigger_count, (data->trigger_count == 1) ? "time" : "times",
|
||||
data->condition);
|
||||
}
|
||||
|
||||
static void SDL_GenerateAssertionReport(void)
|
||||
{
|
||||
const SDL_AssertData *item = triggered_assertions;
|
||||
|
||||
/* only do this if the app hasn't assigned an assertion handler. */
|
||||
if ((item) && (assertion_handler != SDL_PromptAssertion)) {
|
||||
debug_print("\n\nSDL assertion report.\n");
|
||||
debug_print("All SDL assertions between last init/quit:\n\n");
|
||||
|
||||
while (item) {
|
||||
debug_print(
|
||||
"'%s'\n"
|
||||
" * %s (%s:%d)\n"
|
||||
" * triggered %u time%s.\n"
|
||||
" * always ignore: %s.\n",
|
||||
item->condition, item->function, item->filename,
|
||||
item->linenum, item->trigger_count,
|
||||
(item->trigger_count == 1) ? "" : "s",
|
||||
item->always_ignore ? "yes" : "no");
|
||||
item = item->next;
|
||||
}
|
||||
debug_print("\n");
|
||||
|
||||
SDL_ResetAssertionReport();
|
||||
}
|
||||
}
|
||||
|
||||
/* This is not declared in any header, although it is shared between some
|
||||
parts of SDL, because we don't want anything calling it without an
|
||||
extremely good reason. */
|
||||
#ifdef __WATCOMC__
|
||||
extern void SDL_ExitProcess(int exitcode);
|
||||
#pragma aux SDL_ExitProcess aborts;
|
||||
#endif
|
||||
extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
|
||||
|
||||
#ifdef __WATCOMC__
|
||||
static void SDL_AbortAssertion(void);
|
||||
#pragma aux SDL_AbortAssertion aborts;
|
||||
#endif
|
||||
static SDL_NORETURN void SDL_AbortAssertion(void)
|
||||
{
|
||||
SDL_Quit();
|
||||
SDL_ExitProcess(42);
|
||||
}
|
||||
|
||||
static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata)
|
||||
{
|
||||
const char *envr;
|
||||
SDL_AssertState state = SDL_ASSERTION_ABORT;
|
||||
SDL_Window *window;
|
||||
SDL_MessageBoxData messagebox;
|
||||
SDL_MessageBoxButtonData buttons[] = {
|
||||
{ 0, SDL_ASSERTION_RETRY, "Retry" },
|
||||
{ 0, SDL_ASSERTION_BREAK, "Break" },
|
||||
{ 0, SDL_ASSERTION_ABORT, "Abort" },
|
||||
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
|
||||
SDL_ASSERTION_IGNORE, "Ignore" },
|
||||
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
|
||||
SDL_ASSERTION_ALWAYS_IGNORE, "Always Ignore" }
|
||||
};
|
||||
int selected;
|
||||
|
||||
char stack_buf[SDL_MAX_ASSERT_MESSAGE_STACK];
|
||||
char *message = stack_buf;
|
||||
size_t buf_len = sizeof(stack_buf);
|
||||
int len;
|
||||
|
||||
(void)userdata; /* unused in default handler. */
|
||||
|
||||
/* Assume the output will fit... */
|
||||
len = SDL_RenderAssertMessage(message, buf_len, data);
|
||||
|
||||
/* .. and if it didn't, try to allocate as much room as we actually need. */
|
||||
if (len >= (int)buf_len) {
|
||||
if (SDL_size_add_overflow(len, 1, &buf_len) == 0) {
|
||||
message = (char *)SDL_malloc(buf_len);
|
||||
if (message) {
|
||||
len = SDL_RenderAssertMessage(message, buf_len, data);
|
||||
} else {
|
||||
message = stack_buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Something went very wrong */
|
||||
if (len < 0) {
|
||||
if (message != stack_buf) {
|
||||
SDL_free(message);
|
||||
}
|
||||
return SDL_ASSERTION_ABORT;
|
||||
}
|
||||
|
||||
debug_print("\n\n%s\n\n", message);
|
||||
|
||||
/* let env. variable override, so unit tests won't block in a GUI. */
|
||||
envr = SDL_getenv("SDL_ASSERT");
|
||||
if (envr) {
|
||||
if (message != stack_buf) {
|
||||
SDL_free(message);
|
||||
}
|
||||
|
||||
if (SDL_strcmp(envr, "abort") == 0) {
|
||||
return SDL_ASSERTION_ABORT;
|
||||
} else if (SDL_strcmp(envr, "break") == 0) {
|
||||
return SDL_ASSERTION_BREAK;
|
||||
} else if (SDL_strcmp(envr, "retry") == 0) {
|
||||
return SDL_ASSERTION_RETRY;
|
||||
} else if (SDL_strcmp(envr, "ignore") == 0) {
|
||||
return SDL_ASSERTION_IGNORE;
|
||||
} else if (SDL_strcmp(envr, "always_ignore") == 0) {
|
||||
return SDL_ASSERTION_ALWAYS_IGNORE;
|
||||
} else {
|
||||
return SDL_ASSERTION_ABORT; /* oh well. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Leave fullscreen mode, if possible (scary!) */
|
||||
window = SDL_GetToplevelForKeyboardFocus();
|
||||
if (window) {
|
||||
if (window->fullscreen_exclusive) {
|
||||
SDL_MinimizeWindow(window);
|
||||
} else {
|
||||
/* !!! FIXME: ungrab the input if we're not fullscreen? */
|
||||
/* No need to mess with the window */
|
||||
window = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Show a messagebox if we can, otherwise fall back to stdio */
|
||||
SDL_zero(messagebox);
|
||||
messagebox.flags = SDL_MESSAGEBOX_WARNING;
|
||||
messagebox.window = window;
|
||||
messagebox.title = "Assertion Failed";
|
||||
messagebox.message = message;
|
||||
messagebox.numbuttons = SDL_arraysize(buttons);
|
||||
messagebox.buttons = buttons;
|
||||
|
||||
if (SDL_ShowMessageBox(&messagebox, &selected) == 0) {
|
||||
if (selected == -1) {
|
||||
state = SDL_ASSERTION_IGNORE;
|
||||
} else {
|
||||
state = (SDL_AssertState)selected;
|
||||
}
|
||||
} else {
|
||||
#ifdef SDL_PLATFORM_EMSCRIPTEN
|
||||
/* This is nasty, but we can't block on a custom UI. */
|
||||
for (;;) {
|
||||
SDL_bool okay = SDL_TRUE;
|
||||
/* *INDENT-OFF* */ /* clang-format off */
|
||||
char *buf = (char *) MAIN_THREAD_EM_ASM_PTR({
|
||||
var str =
|
||||
UTF8ToString($0) + '\n\n' +
|
||||
'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
|
||||
var reply = window.prompt(str, "i");
|
||||
if (reply === null) {
|
||||
reply = "i";
|
||||
}
|
||||
return allocate(intArrayFromString(reply), 'i8', ALLOC_NORMAL);
|
||||
}, message);
|
||||
/* *INDENT-ON* */ /* clang-format on */
|
||||
|
||||
if (SDL_strcmp(buf, "a") == 0) {
|
||||
state = SDL_ASSERTION_ABORT;
|
||||
#if 0 /* (currently) no break functionality on Emscripten */
|
||||
} else if (SDL_strcmp(buf, "b") == 0) {
|
||||
state = SDL_ASSERTION_BREAK;
|
||||
#endif
|
||||
} else if (SDL_strcmp(buf, "r") == 0) {
|
||||
state = SDL_ASSERTION_RETRY;
|
||||
} else if (SDL_strcmp(buf, "i") == 0) {
|
||||
state = SDL_ASSERTION_IGNORE;
|
||||
} else if (SDL_strcmp(buf, "A") == 0) {
|
||||
state = SDL_ASSERTION_ALWAYS_IGNORE;
|
||||
} else {
|
||||
okay = SDL_FALSE;
|
||||
}
|
||||
free(buf); /* This should NOT be SDL_free() */
|
||||
|
||||
if (okay) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#elif defined(HAVE_STDIO_H)
|
||||
/* this is a little hacky. */
|
||||
for (;;) {
|
||||
char buf[32];
|
||||
(void)fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : ");
|
||||
(void)fflush(stderr);
|
||||
if (fgets(buf, sizeof(buf), stdin) == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (SDL_strncmp(buf, "a", 1) == 0) {
|
||||
state = SDL_ASSERTION_ABORT;
|
||||
break;
|
||||
} else if (SDL_strncmp(buf, "b", 1) == 0) {
|
||||
state = SDL_ASSERTION_BREAK;
|
||||
break;
|
||||
} else if (SDL_strncmp(buf, "r", 1) == 0) {
|
||||
state = SDL_ASSERTION_RETRY;
|
||||
break;
|
||||
} else if (SDL_strncmp(buf, "i", 1) == 0) {
|
||||
state = SDL_ASSERTION_IGNORE;
|
||||
break;
|
||||
} else if (SDL_strncmp(buf, "A", 1) == 0) {
|
||||
state = SDL_ASSERTION_ALWAYS_IGNORE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif /* HAVE_STDIO_H */
|
||||
}
|
||||
|
||||
/* Re-enter fullscreen mode */
|
||||
if (window) {
|
||||
SDL_RestoreWindow(window);
|
||||
}
|
||||
|
||||
if (message != stack_buf) {
|
||||
SDL_free(message);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
SDL_AssertState SDL_ReportAssertion(SDL_AssertData *data, const char *func, const char *file, int line)
|
||||
{
|
||||
SDL_AssertState state = SDL_ASSERTION_IGNORE;
|
||||
static int assertion_running = 0;
|
||||
|
||||
#ifndef SDL_THREADS_DISABLED
|
||||
static SDL_SpinLock spinlock = 0;
|
||||
SDL_LockSpinlock(&spinlock);
|
||||
if (!assertion_mutex) { /* never called SDL_Init()? */
|
||||
assertion_mutex = SDL_CreateMutex();
|
||||
if (!assertion_mutex) {
|
||||
SDL_UnlockSpinlock(&spinlock);
|
||||
return SDL_ASSERTION_IGNORE; /* oh well, I guess. */
|
||||
}
|
||||
}
|
||||
SDL_UnlockSpinlock(&spinlock);
|
||||
|
||||
SDL_LockMutex(assertion_mutex);
|
||||
#endif /* !SDL_THREADS_DISABLED */
|
||||
|
||||
/* doing this because Visual C is upset over assigning in the macro. */
|
||||
if (data->trigger_count == 0) {
|
||||
data->function = func;
|
||||
data->filename = file;
|
||||
data->linenum = line;
|
||||
}
|
||||
|
||||
SDL_AddAssertionToReport(data);
|
||||
|
||||
assertion_running++;
|
||||
if (assertion_running > 1) { /* assert during assert! Abort. */
|
||||
if (assertion_running == 2) {
|
||||
SDL_AbortAssertion();
|
||||
} else if (assertion_running == 3) { /* Abort asserted! */
|
||||
SDL_ExitProcess(42);
|
||||
} else {
|
||||
while (1) { /* do nothing but spin; what else can you do?! */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!data->always_ignore) {
|
||||
state = assertion_handler(data, assertion_userdata);
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case SDL_ASSERTION_ALWAYS_IGNORE:
|
||||
state = SDL_ASSERTION_IGNORE;
|
||||
data->always_ignore = 1;
|
||||
break;
|
||||
|
||||
case SDL_ASSERTION_IGNORE:
|
||||
case SDL_ASSERTION_RETRY:
|
||||
case SDL_ASSERTION_BREAK:
|
||||
break; /* macro handles these. */
|
||||
|
||||
case SDL_ASSERTION_ABORT:
|
||||
SDL_AbortAssertion();
|
||||
/*break; ...shouldn't return, but oh well. */
|
||||
}
|
||||
|
||||
assertion_running--;
|
||||
|
||||
#ifndef SDL_THREADS_DISABLED
|
||||
SDL_UnlockMutex(assertion_mutex);
|
||||
#endif
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void SDL_AssertionsQuit(void)
|
||||
{
|
||||
#if SDL_ASSERT_LEVEL > 0
|
||||
SDL_GenerateAssertionReport();
|
||||
#ifndef SDL_THREADS_DISABLED
|
||||
if (assertion_mutex) {
|
||||
SDL_DestroyMutex(assertion_mutex);
|
||||
assertion_mutex = NULL;
|
||||
}
|
||||
#endif
|
||||
#endif /* SDL_ASSERT_LEVEL > 0 */
|
||||
}
|
||||
|
||||
void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata)
|
||||
{
|
||||
if (handler != NULL) {
|
||||
assertion_handler = handler;
|
||||
assertion_userdata = userdata;
|
||||
} else {
|
||||
assertion_handler = SDL_PromptAssertion;
|
||||
assertion_userdata = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const SDL_AssertData *SDL_GetAssertionReport(void)
|
||||
{
|
||||
return triggered_assertions;
|
||||
}
|
||||
|
||||
void SDL_ResetAssertionReport(void)
|
||||
{
|
||||
SDL_AssertData *next = NULL;
|
||||
SDL_AssertData *item;
|
||||
for (item = triggered_assertions; item; item = next) {
|
||||
next = (SDL_AssertData *)item->next;
|
||||
item->always_ignore = SDL_FALSE;
|
||||
item->trigger_count = 0;
|
||||
item->next = NULL;
|
||||
}
|
||||
|
||||
triggered_assertions = NULL;
|
||||
}
|
||||
|
||||
SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
|
||||
{
|
||||
return SDL_PromptAssertion;
|
||||
}
|
||||
|
||||
SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata)
|
||||
{
|
||||
if (userdata) {
|
||||
*userdata = assertion_userdata;
|
||||
}
|
||||
return assertion_handler;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_assert_c_h_
|
||||
#define SDL_assert_c_h_
|
||||
|
||||
extern void SDL_AssertionsQuit(void);
|
||||
|
||||
#endif /* SDL_assert_c_h_ */
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
/* Simple error handling in SDL */
|
||||
|
||||
#include "SDL_error_c.h"
|
||||
|
||||
int SDL_SetError(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
/* Ignore call if invalid format pointer was passed */
|
||||
if (fmt) {
|
||||
va_list ap;
|
||||
int result;
|
||||
SDL_error *error = SDL_GetErrBuf(SDL_TRUE);
|
||||
|
||||
error->error = SDL_ErrorCodeGeneric;
|
||||
|
||||
va_start(ap, fmt);
|
||||
result = SDL_vsnprintf(error->str, error->len, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (result >= 0 && (size_t)result >= error->len && error->realloc_func) {
|
||||
size_t len = (size_t)result + 1;
|
||||
char *str = (char *)error->realloc_func(error->str, len);
|
||||
if (str) {
|
||||
error->str = str;
|
||||
error->len = len;
|
||||
va_start(ap, fmt);
|
||||
(void)SDL_vsnprintf(error->str, error->len, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
|
||||
if (SDL_LogGetPriority(SDL_LOG_CATEGORY_ERROR) <= SDL_LOG_PRIORITY_DEBUG) {
|
||||
/* If we are in debug mode, print out the error message */
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_ERROR, "%s", error->str);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Available for backwards compatibility */
|
||||
const char *SDL_GetError(void)
|
||||
{
|
||||
const SDL_error *error = SDL_GetErrBuf(SDL_FALSE);
|
||||
|
||||
if (!error) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (error->error) {
|
||||
case SDL_ErrorCodeGeneric:
|
||||
return error->str;
|
||||
case SDL_ErrorCodeOutOfMemory:
|
||||
return "Out of memory";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_ClearError(void)
|
||||
{
|
||||
SDL_error *error = SDL_GetErrBuf(SDL_FALSE);
|
||||
|
||||
if (error) {
|
||||
error->error = SDL_ErrorCodeNone;
|
||||
}
|
||||
}
|
||||
|
||||
/* Very common errors go here */
|
||||
int SDL_Error(SDL_errorcode code)
|
||||
{
|
||||
switch (code) {
|
||||
case SDL_ENOMEM:
|
||||
SDL_GetErrBuf(SDL_TRUE)->error = SDL_ErrorCodeOutOfMemory;
|
||||
return -1;
|
||||
case SDL_EFREAD:
|
||||
return SDL_SetError("Error reading from datastream");
|
||||
case SDL_EFWRITE:
|
||||
return SDL_SetError("Error writing to datastream");
|
||||
case SDL_EFSEEK:
|
||||
return SDL_SetError("Error seeking in datastream");
|
||||
case SDL_UNSUPPORTED:
|
||||
return SDL_SetError("That operation is not supported");
|
||||
default:
|
||||
return SDL_SetError("Unknown SDL error");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
/* This file defines a structure that carries language-independent
|
||||
error messages
|
||||
*/
|
||||
|
||||
#ifndef SDL_error_c_h_
|
||||
#define SDL_error_c_h_
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SDL_ErrorCodeNone,
|
||||
SDL_ErrorCodeGeneric,
|
||||
SDL_ErrorCodeOutOfMemory,
|
||||
} SDL_ErrorCode;
|
||||
|
||||
typedef struct SDL_error
|
||||
{
|
||||
SDL_ErrorCode error;
|
||||
char *str;
|
||||
size_t len;
|
||||
SDL_realloc_func realloc_func;
|
||||
SDL_free_func free_func;
|
||||
} SDL_error;
|
||||
|
||||
/* Defined in SDL_thread.c */
|
||||
extern SDL_error *SDL_GetErrBuf(SDL_bool create);
|
||||
|
||||
#endif /* SDL_error_c_h_ */
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
/* convert the guid to a printable string */
|
||||
int SDL_GUIDToString(SDL_GUID guid, char *pszGUID, int cbGUID)
|
||||
{
|
||||
static const char k_rgchHexToASCII[] = "0123456789abcdef";
|
||||
int i;
|
||||
|
||||
if (!pszGUID) {
|
||||
return SDL_InvalidParamError("pszGUID");
|
||||
}
|
||||
if (cbGUID <= 0) {
|
||||
return SDL_InvalidParamError("cbGUID");
|
||||
}
|
||||
|
||||
for (i = 0; i < sizeof(guid.data) && i < (cbGUID - 1) / 2; i++) {
|
||||
/* each input byte writes 2 ascii chars, and might write a null byte. */
|
||||
/* If we don't have room for next input byte, stop */
|
||||
unsigned char c = guid.data[i];
|
||||
|
||||
*pszGUID++ = k_rgchHexToASCII[c >> 4];
|
||||
*pszGUID++ = k_rgchHexToASCII[c & 0x0F];
|
||||
}
|
||||
*pszGUID = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Purpose: Returns the 4 bit nibble for a hex character
|
||||
* Input : c -
|
||||
* Output : unsigned char
|
||||
*-----------------------------------------------------------------------------*/
|
||||
static unsigned char nibble(unsigned char c)
|
||||
{
|
||||
if ((c >= '0') && (c <= '9')) {
|
||||
return c - '0';
|
||||
}
|
||||
|
||||
if ((c >= 'A') && (c <= 'F')) {
|
||||
return c - 'A' + 0x0a;
|
||||
}
|
||||
|
||||
if ((c >= 'a') && (c <= 'f')) {
|
||||
return c - 'a' + 0x0a;
|
||||
}
|
||||
|
||||
/* received an invalid character, and no real way to return an error */
|
||||
/* AssertMsg1(false, "Q_nibble invalid hex character '%c' ", c); */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* convert the string version of a guid to the struct */
|
||||
SDL_GUID SDL_GUIDFromString(const char *pchGUID)
|
||||
{
|
||||
SDL_GUID guid;
|
||||
int maxoutputbytes = sizeof(guid);
|
||||
size_t len = SDL_strlen(pchGUID);
|
||||
Uint8 *p;
|
||||
size_t i;
|
||||
|
||||
/* Make sure it's even */
|
||||
len = (len) & ~0x1;
|
||||
|
||||
SDL_memset(&guid, 0x00, sizeof(guid));
|
||||
|
||||
p = (Uint8 *)&guid;
|
||||
for (i = 0; (i < len) && ((p - (Uint8 *)&guid) < maxoutputbytes); i += 2, p++) {
|
||||
*p = (nibble((unsigned char)pchGUID[i]) << 4) | nibble((unsigned char)pchGUID[i + 1]);
|
||||
}
|
||||
|
||||
return guid;
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
#include "SDL_hashtable.h"
|
||||
|
||||
typedef struct SDL_HashItem
|
||||
{
|
||||
const void *key;
|
||||
const void *value;
|
||||
struct SDL_HashItem *next;
|
||||
} SDL_HashItem;
|
||||
|
||||
struct SDL_HashTable
|
||||
{
|
||||
SDL_HashItem **table;
|
||||
Uint32 table_len;
|
||||
SDL_bool stackable;
|
||||
void *data;
|
||||
SDL_HashTable_HashFn hash;
|
||||
SDL_HashTable_KeyMatchFn keymatch;
|
||||
SDL_HashTable_NukeFn nuke;
|
||||
};
|
||||
|
||||
SDL_HashTable *SDL_CreateHashTable(void *data, const Uint32 num_buckets, const SDL_HashTable_HashFn hashfn,
|
||||
const SDL_HashTable_KeyMatchFn keymatchfn,
|
||||
const SDL_HashTable_NukeFn nukefn,
|
||||
const SDL_bool stackable)
|
||||
{
|
||||
SDL_HashTable *table;
|
||||
|
||||
// num_buckets must be a power of two so we get a solid block of bits to mask hash values against.
|
||||
if ((num_buckets == 0) || ((num_buckets & (num_buckets - 1)) != 0)) {
|
||||
SDL_SetError("num_buckets must be a power of two");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
table = (SDL_HashTable *) SDL_calloc(1, sizeof (SDL_HashTable));
|
||||
if (!table) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
table->table = (SDL_HashItem **) SDL_calloc(num_buckets, sizeof (SDL_HashItem *));
|
||||
if (!table->table) {
|
||||
SDL_free(table);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
table->table_len = num_buckets;
|
||||
table->stackable = stackable;
|
||||
table->data = data;
|
||||
table->hash = hashfn;
|
||||
table->keymatch = keymatchfn;
|
||||
table->nuke = nukefn;
|
||||
return table;
|
||||
}
|
||||
|
||||
static SDL_INLINE Uint32 calc_hash(const SDL_HashTable *table, const void *key)
|
||||
{
|
||||
return table->hash(key, table->data) & (table->table_len - 1);
|
||||
}
|
||||
|
||||
|
||||
SDL_bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value)
|
||||
{
|
||||
SDL_HashItem *item;
|
||||
const Uint32 hash = calc_hash(table, key);
|
||||
|
||||
if ( (!table->stackable) && (SDL_FindInHashTable(table, key, NULL)) ) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
// !!! FIXME: grow and rehash table if it gets too saturated.
|
||||
item = (SDL_HashItem *) SDL_malloc(sizeof (SDL_HashItem));
|
||||
if (!item) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
item->key = key;
|
||||
item->value = value;
|
||||
item->next = table->table[hash];
|
||||
table->table[hash] = item;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
SDL_bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **_value)
|
||||
{
|
||||
const Uint32 hash = calc_hash(table, key);
|
||||
void *data = table->data;
|
||||
SDL_HashItem *i;
|
||||
|
||||
for (i = table->table[hash]; i; i = i->next) {
|
||||
if (table->keymatch(key, i->key, data)) {
|
||||
if (_value) {
|
||||
*_value = i->value;
|
||||
}
|
||||
return SDL_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
SDL_bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key)
|
||||
{
|
||||
const Uint32 hash = calc_hash(table, key);
|
||||
SDL_HashItem *item = NULL;
|
||||
SDL_HashItem *prev = NULL;
|
||||
void *data = table->data;
|
||||
|
||||
for (item = table->table[hash]; item; item = item->next) {
|
||||
if (table->keymatch(key, item->key, data)) {
|
||||
if (prev) {
|
||||
prev->next = item->next;
|
||||
} else {
|
||||
table->table[hash] = item->next;
|
||||
}
|
||||
|
||||
table->nuke(item->key, item->value, data);
|
||||
SDL_free(item);
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
prev = item;
|
||||
}
|
||||
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
SDL_bool SDL_IterateHashTableKey(const SDL_HashTable *table, const void *key, const void **_value, void **iter)
|
||||
{
|
||||
SDL_HashItem *item = *iter ? ((SDL_HashItem *) *iter)->next : table->table[calc_hash(table, key)];
|
||||
|
||||
while (item) {
|
||||
if (table->keymatch(key, item->key, table->data)) {
|
||||
*_value = item->value;
|
||||
*iter = item;
|
||||
return SDL_TRUE;
|
||||
}
|
||||
item = item->next;
|
||||
}
|
||||
|
||||
// no more matches.
|
||||
*_value = NULL;
|
||||
*iter = NULL;
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
SDL_bool SDL_IterateHashTable(const SDL_HashTable *table, const void **_key, const void **_value, void **iter)
|
||||
{
|
||||
SDL_HashItem *item = (SDL_HashItem *) *iter;
|
||||
Uint32 idx = 0;
|
||||
|
||||
if (item) {
|
||||
const SDL_HashItem *orig = item;
|
||||
item = item->next;
|
||||
if (!item) {
|
||||
idx = calc_hash(table, orig->key) + 1; // !!! FIXME: we probably shouldn't rehash each time.
|
||||
}
|
||||
}
|
||||
|
||||
while (!item && (idx < table->table_len)) {
|
||||
item = table->table[idx++]; // skip empty buckets...
|
||||
}
|
||||
|
||||
if (!item) { // no more matches?
|
||||
*_key = NULL;
|
||||
*iter = NULL;
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
*_key = item->key;
|
||||
*_value = item->value;
|
||||
*iter = item;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
SDL_bool SDL_HashTableEmpty(SDL_HashTable *table)
|
||||
{
|
||||
if (table) {
|
||||
Uint32 i;
|
||||
|
||||
for (i = 0; i < table->table_len; i++) {
|
||||
SDL_HashItem *item = table->table[i];
|
||||
if (item) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
void SDL_DestroyHashTable(SDL_HashTable *table)
|
||||
{
|
||||
if (table) {
|
||||
void *data = table->data;
|
||||
Uint32 i;
|
||||
|
||||
for (i = 0; i < table->table_len; i++) {
|
||||
SDL_HashItem *item = table->table[i];
|
||||
while (item) {
|
||||
SDL_HashItem *next = item->next;
|
||||
table->nuke(item->key, item->value, data);
|
||||
SDL_free(item);
|
||||
item = next;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(table->table);
|
||||
SDL_free(table);
|
||||
}
|
||||
}
|
||||
|
||||
// this is djb's xor hashing function.
|
||||
static SDL_INLINE Uint32 hash_string_djbxor(const char *str, size_t len)
|
||||
{
|
||||
Uint32 hash = 5381;
|
||||
while (len--) {
|
||||
hash = ((hash << 5) + hash) ^ *(str++);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
Uint32 SDL_HashString(const void *key, void *data)
|
||||
{
|
||||
const char *str = (const char *)key;
|
||||
return hash_string_djbxor(str, SDL_strlen(str));
|
||||
}
|
||||
|
||||
SDL_bool SDL_KeyMatchString(const void *a, const void *b, void *data)
|
||||
{
|
||||
if (a == b) {
|
||||
return SDL_TRUE; // same pointer, must match.
|
||||
} else if (!a || !b) {
|
||||
return SDL_FALSE; // one pointer is NULL (and first test shows they aren't the same pointer), must not match.
|
||||
}
|
||||
return (SDL_strcmp((const char *)a, (const char *)b) == 0); // Check against actual string contents.
|
||||
}
|
||||
|
||||
// We assume we can fit the ID in the key directly
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_HashID_KeySize, sizeof(Uint32) <= sizeof(const void *));
|
||||
|
||||
Uint32 SDL_HashID(const void *key, void *unused)
|
||||
{
|
||||
return (Uint32)(uintptr_t)key;
|
||||
}
|
||||
|
||||
SDL_bool SDL_KeyMatchID(const void *a, const void *b, void *unused)
|
||||
{
|
||||
if (a == b) {
|
||||
return SDL_TRUE;
|
||||
}
|
||||
return SDL_FALSE;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#ifndef SDL_hashtable_h_
|
||||
#define SDL_hashtable_h_
|
||||
|
||||
/* this is not (currently) a public API. But maybe it should be! */
|
||||
|
||||
struct SDL_HashTable;
|
||||
typedef struct SDL_HashTable SDL_HashTable;
|
||||
typedef Uint32 (*SDL_HashTable_HashFn)(const void *key, void *data);
|
||||
typedef SDL_bool (*SDL_HashTable_KeyMatchFn)(const void *a, const void *b, void *data);
|
||||
typedef void (*SDL_HashTable_NukeFn)(const void *key, const void *value, void *data);
|
||||
|
||||
SDL_HashTable *SDL_CreateHashTable(void *data,
|
||||
const Uint32 num_buckets,
|
||||
const SDL_HashTable_HashFn hashfn,
|
||||
const SDL_HashTable_KeyMatchFn keymatchfn,
|
||||
const SDL_HashTable_NukeFn nukefn,
|
||||
const SDL_bool stackable);
|
||||
|
||||
void SDL_DestroyHashTable(SDL_HashTable *table);
|
||||
SDL_bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value);
|
||||
SDL_bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key);
|
||||
SDL_bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **_value);
|
||||
SDL_bool SDL_HashTableEmpty(SDL_HashTable *table);
|
||||
|
||||
// iterate all values for a specific key. This only makes sense if the hash is stackable. If not-stackable, just use SDL_FindInHashTable().
|
||||
SDL_bool SDL_IterateHashTableKey(const SDL_HashTable *table, const void *key, const void **_value, void **iter);
|
||||
|
||||
// iterate all key/value pairs in a hash (stackable hashes can have duplicate keys with multiple values).
|
||||
SDL_bool SDL_IterateHashTable(const SDL_HashTable *table, const void **_key, const void **_value, void **iter);
|
||||
|
||||
Uint32 SDL_HashString(const void *key, void *unused);
|
||||
SDL_bool SDL_KeyMatchString(const void *a, const void *b, void *unused);
|
||||
|
||||
Uint32 SDL_HashID(const void *key, void *unused);
|
||||
SDL_bool SDL_KeyMatchID(const void *a, const void *b, void *unused);
|
||||
|
||||
#endif /* SDL_hashtable_h_ */
|
||||
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_hints_c.h"
|
||||
|
||||
/* Assuming there aren't many hints set and they aren't being queried in
|
||||
critical performance paths, we'll just use linked lists here.
|
||||
*/
|
||||
typedef struct SDL_HintWatch
|
||||
{
|
||||
SDL_HintCallback callback;
|
||||
void *userdata;
|
||||
struct SDL_HintWatch *next;
|
||||
} SDL_HintWatch;
|
||||
|
||||
typedef struct SDL_Hint
|
||||
{
|
||||
char *name;
|
||||
char *value;
|
||||
SDL_HintPriority priority;
|
||||
SDL_HintWatch *callbacks;
|
||||
struct SDL_Hint *next;
|
||||
} SDL_Hint;
|
||||
|
||||
static SDL_Hint *SDL_hints;
|
||||
|
||||
SDL_bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriority priority)
|
||||
{
|
||||
const char *env;
|
||||
SDL_Hint *hint;
|
||||
SDL_HintWatch *entry;
|
||||
|
||||
if (!name) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
env = SDL_getenv(name);
|
||||
if (env && priority < SDL_HINT_OVERRIDE) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
for (hint = SDL_hints; hint; hint = hint->next) {
|
||||
if (SDL_strcmp(name, hint->name) == 0) {
|
||||
if (priority < hint->priority) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
if (hint->value != value &&
|
||||
(!value || !hint->value || SDL_strcmp(hint->value, value) != 0)) {
|
||||
char *old_value = hint->value;
|
||||
|
||||
hint->value = value ? SDL_strdup(value) : NULL;
|
||||
for (entry = hint->callbacks; entry;) {
|
||||
/* Save the next entry in case this one is deleted */
|
||||
SDL_HintWatch *next = entry->next;
|
||||
entry->callback(entry->userdata, name, old_value, value);
|
||||
entry = next;
|
||||
}
|
||||
if (old_value) {
|
||||
SDL_free(old_value);
|
||||
}
|
||||
}
|
||||
hint->priority = priority;
|
||||
return SDL_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Couldn't find the hint, add a new one */
|
||||
hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
|
||||
if (!hint) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
hint->name = SDL_strdup(name);
|
||||
hint->value = value ? SDL_strdup(value) : NULL;
|
||||
hint->priority = priority;
|
||||
hint->callbacks = NULL;
|
||||
hint->next = SDL_hints;
|
||||
SDL_hints = hint;
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
SDL_bool SDL_ResetHint(const char *name)
|
||||
{
|
||||
const char *env;
|
||||
SDL_Hint *hint;
|
||||
SDL_HintWatch *entry;
|
||||
|
||||
if (!name) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
env = SDL_getenv(name);
|
||||
for (hint = SDL_hints; hint; hint = hint->next) {
|
||||
if (SDL_strcmp(name, hint->name) == 0) {
|
||||
if ((!env && hint->value) ||
|
||||
(env && !hint->value) ||
|
||||
(env && SDL_strcmp(env, hint->value) != 0)) {
|
||||
for (entry = hint->callbacks; entry;) {
|
||||
/* Save the next entry in case this one is deleted */
|
||||
SDL_HintWatch *next = entry->next;
|
||||
entry->callback(entry->userdata, name, hint->value, env);
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
SDL_free(hint->value);
|
||||
hint->value = NULL;
|
||||
hint->priority = SDL_HINT_DEFAULT;
|
||||
return SDL_TRUE;
|
||||
}
|
||||
}
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
void SDL_ResetHints(void)
|
||||
{
|
||||
const char *env;
|
||||
SDL_Hint *hint;
|
||||
SDL_HintWatch *entry;
|
||||
|
||||
for (hint = SDL_hints; hint; hint = hint->next) {
|
||||
env = SDL_getenv(hint->name);
|
||||
if ((!env && hint->value) ||
|
||||
(env && !hint->value) ||
|
||||
(env && SDL_strcmp(env, hint->value) != 0)) {
|
||||
for (entry = hint->callbacks; entry;) {
|
||||
/* Save the next entry in case this one is deleted */
|
||||
SDL_HintWatch *next = entry->next;
|
||||
entry->callback(entry->userdata, hint->name, hint->value, env);
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
SDL_free(hint->value);
|
||||
hint->value = NULL;
|
||||
hint->priority = SDL_HINT_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_bool SDL_SetHint(const char *name, const char *value)
|
||||
{
|
||||
return SDL_SetHintWithPriority(name, value, SDL_HINT_NORMAL);
|
||||
}
|
||||
|
||||
const char *SDL_GetHint(const char *name)
|
||||
{
|
||||
const char *env;
|
||||
SDL_Hint *hint;
|
||||
|
||||
env = SDL_getenv(name);
|
||||
for (hint = SDL_hints; hint; hint = hint->next) {
|
||||
if (SDL_strcmp(name, hint->name) == 0) {
|
||||
if (!env || hint->priority == SDL_HINT_OVERRIDE) {
|
||||
return hint->value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
int SDL_GetStringInteger(const char *value, int default_value)
|
||||
{
|
||||
if (!value || !*value) {
|
||||
return default_value;
|
||||
}
|
||||
if (*value == '0' || SDL_strcasecmp(value, "false") == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (*value == '1' || SDL_strcasecmp(value, "true") == 0) {
|
||||
return 1;
|
||||
}
|
||||
if (*value == '-' || SDL_isdigit(*value)) {
|
||||
return SDL_atoi(value);
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
SDL_bool SDL_GetStringBoolean(const char *value, SDL_bool default_value)
|
||||
{
|
||||
if (!value || !*value) {
|
||||
return default_value;
|
||||
}
|
||||
if (*value == '0' || SDL_strcasecmp(value, "false") == 0) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
SDL_bool SDL_GetHintBoolean(const char *name, SDL_bool default_value)
|
||||
{
|
||||
const char *hint = SDL_GetHint(name);
|
||||
return SDL_GetStringBoolean(hint, default_value);
|
||||
}
|
||||
|
||||
int SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_Hint *hint;
|
||||
SDL_HintWatch *entry;
|
||||
const char *value;
|
||||
|
||||
if (!name || !*name) {
|
||||
return SDL_InvalidParamError("name");
|
||||
}
|
||||
if (!callback) {
|
||||
return SDL_InvalidParamError("callback");
|
||||
}
|
||||
|
||||
SDL_DelHintCallback(name, callback, userdata);
|
||||
|
||||
entry = (SDL_HintWatch *)SDL_malloc(sizeof(*entry));
|
||||
if (!entry) {
|
||||
return -1;
|
||||
}
|
||||
entry->callback = callback;
|
||||
entry->userdata = userdata;
|
||||
|
||||
for (hint = SDL_hints; hint; hint = hint->next) {
|
||||
if (SDL_strcmp(name, hint->name) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hint) {
|
||||
/* Need to add a hint entry for this watcher */
|
||||
hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
|
||||
if (!hint) {
|
||||
SDL_free(entry);
|
||||
return -1;
|
||||
}
|
||||
hint->name = SDL_strdup(name);
|
||||
if (!hint->name) {
|
||||
SDL_free(entry);
|
||||
SDL_free(hint);
|
||||
return -1;
|
||||
}
|
||||
hint->value = NULL;
|
||||
hint->priority = SDL_HINT_DEFAULT;
|
||||
hint->callbacks = NULL;
|
||||
hint->next = SDL_hints;
|
||||
SDL_hints = hint;
|
||||
}
|
||||
|
||||
/* Add it to the callbacks for this hint */
|
||||
entry->next = hint->callbacks;
|
||||
hint->callbacks = entry;
|
||||
|
||||
/* Now call it with the current value */
|
||||
value = SDL_GetHint(name);
|
||||
callback(userdata, name, value, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SDL_DelHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
|
||||
{
|
||||
SDL_Hint *hint;
|
||||
SDL_HintWatch *entry, *prev;
|
||||
|
||||
for (hint = SDL_hints; hint; hint = hint->next) {
|
||||
if (SDL_strcmp(name, hint->name) == 0) {
|
||||
prev = NULL;
|
||||
for (entry = hint->callbacks; entry; entry = entry->next) {
|
||||
if (callback == entry->callback && userdata == entry->userdata) {
|
||||
if (prev) {
|
||||
prev->next = entry->next;
|
||||
} else {
|
||||
hint->callbacks = entry->next;
|
||||
}
|
||||
SDL_free(entry);
|
||||
break;
|
||||
}
|
||||
prev = entry;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_ClearHints(void)
|
||||
{
|
||||
SDL_Hint *hint;
|
||||
SDL_HintWatch *entry;
|
||||
|
||||
while (SDL_hints) {
|
||||
hint = SDL_hints;
|
||||
SDL_hints = hint->next;
|
||||
|
||||
SDL_free(hint->name);
|
||||
SDL_free(hint->value);
|
||||
for (entry = hint->callbacks; entry;) {
|
||||
SDL_HintWatch *freeable = entry;
|
||||
entry = entry->next;
|
||||
SDL_free(freeable);
|
||||
}
|
||||
SDL_free(hint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
/* This file defines useful function for working with SDL hints */
|
||||
|
||||
#ifndef SDL_hints_c_h_
|
||||
#define SDL_hints_c_h_
|
||||
|
||||
extern SDL_bool SDL_GetStringBoolean(const char *value, SDL_bool default_value);
|
||||
extern int SDL_GetStringInteger(const char *value, int default_value);
|
||||
extern void SDL_ClearHints(void);
|
||||
|
||||
#endif /* SDL_hints_c_h_ */
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#ifndef SDL_internal_h_
|
||||
#define SDL_internal_h_
|
||||
|
||||
/* Many of SDL's features require _GNU_SOURCE on various platforms */
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
/* Need this so Linux systems define fseek64o, ftell64o and off64_t */
|
||||
#ifndef _LARGEFILE64_SOURCE
|
||||
#define _LARGEFILE64_SOURCE 1
|
||||
#endif
|
||||
|
||||
/* This is for a variable-length array at the end of a struct:
|
||||
struct x { int y; char z[SDL_VARIABLE_LENGTH_ARRAY]; };
|
||||
Use this because GCC 2 needs different magic than other compilers. */
|
||||
#if (defined(__GNUC__) && (__GNUC__ <= 2)) || defined(__CC_ARM) || defined(__cplusplus)
|
||||
#define SDL_VARIABLE_LENGTH_ARRAY 1
|
||||
#else
|
||||
#define SDL_VARIABLE_LENGTH_ARRAY
|
||||
#endif
|
||||
|
||||
#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) || defined(__clang__)
|
||||
#define HAVE_GCC_DIAGNOSTIC_PRAGMA 1
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER /* We use constant comparison for generated code */
|
||||
#pragma warning(disable : 6326)
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER /* SDL_MAX_SMALL_ALLOC_STACKSIZE is smaller than _ALLOCA_S_THRESHOLD and should be generally safe */
|
||||
#pragma warning(disable : 6255)
|
||||
#endif
|
||||
#define SDL_MAX_SMALL_ALLOC_STACKSIZE 128
|
||||
#define SDL_small_alloc(type, count, pisstack) ((*(pisstack) = ((sizeof(type) * (count)) < SDL_MAX_SMALL_ALLOC_STACKSIZE)), (*(pisstack) ? SDL_stack_alloc(type, count) : (type *)SDL_malloc(sizeof(type) * (count))))
|
||||
#define SDL_small_free(ptr, isstack) \
|
||||
if ((isstack)) { \
|
||||
SDL_stack_free(ptr); \
|
||||
} else { \
|
||||
SDL_free(ptr); \
|
||||
}
|
||||
|
||||
#include "build_config/SDL_build_config.h"
|
||||
|
||||
#include "dynapi/SDL_dynapi.h"
|
||||
|
||||
#if SDL_DYNAMIC_API
|
||||
#include "dynapi/SDL_dynapi_overrides.h"
|
||||
/* force DECLSPEC off...it's all internal symbols now.
|
||||
These will have actual #defines during SDL_dynapi.c only */
|
||||
#ifdef DECLSPEC
|
||||
#undef DECLSPEC
|
||||
#endif
|
||||
#define DECLSPEC
|
||||
#endif
|
||||
|
||||
#ifdef SDL_PLATFORM_APPLE
|
||||
#ifndef _DARWIN_C_SOURCE
|
||||
#define _DARWIN_C_SOURCE 1 /* for memset_pattern4() */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SYS_TYPES_H
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDIO_H
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDLIB_H
|
||||
#include <stdlib.h>
|
||||
#elif defined(HAVE_MALLOC_H)
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDDEF_H
|
||||
#include <stddef.h>
|
||||
#endif
|
||||
#ifdef HAVE_STDARG_H
|
||||
#include <stdarg.h>
|
||||
#endif
|
||||
#ifdef HAVE_STRING_H
|
||||
#ifdef HAVE_MEMORY_H
|
||||
#include <memory.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#endif
|
||||
#ifdef HAVE_STRINGS_H
|
||||
#include <strings.h>
|
||||
#endif
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif
|
||||
#ifdef HAVE_INTTYPES_H
|
||||
#include <inttypes.h>
|
||||
#elif defined(HAVE_STDINT_H)
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
#ifdef HAVE_MATH_H
|
||||
#include <math.h>
|
||||
#endif
|
||||
#ifdef HAVE_FLOAT_H
|
||||
#include <float.h>
|
||||
#endif
|
||||
|
||||
/* If you run into a warning that O_CLOEXEC is redefined, update the SDL configuration header for your platform to add HAVE_O_CLOEXEC */
|
||||
#ifndef HAVE_O_CLOEXEC
|
||||
#define O_CLOEXEC 0
|
||||
#endif
|
||||
|
||||
/* A few #defines to reduce SDL footprint.
|
||||
Only effective when library is statically linked.
|
||||
You have to manually edit this file. */
|
||||
#ifndef SDL_LEAN_AND_MEAN
|
||||
#define SDL_LEAN_AND_MEAN 0
|
||||
#endif
|
||||
|
||||
/* Optimized functions from 'SDL_blit_0.c'
|
||||
- blit with source bits_per_pixel < 8, palette */
|
||||
#ifndef SDL_HAVE_BLIT_0
|
||||
#define SDL_HAVE_BLIT_0 !SDL_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
/* Optimized functions from 'SDL_blit_1.c'
|
||||
- blit with source bytes_per_pixel == 1, palette */
|
||||
#ifndef SDL_HAVE_BLIT_1
|
||||
#define SDL_HAVE_BLIT_1 !SDL_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
/* Optimized functions from 'SDL_blit_A.c'
|
||||
- blit with 'SDL_BLENDMODE_BLEND' blending mode */
|
||||
#ifndef SDL_HAVE_BLIT_A
|
||||
#define SDL_HAVE_BLIT_A !SDL_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
/* Optimized functions from 'SDL_blit_N.c'
|
||||
- blit with COLORKEY mode, or nothing */
|
||||
#ifndef SDL_HAVE_BLIT_N
|
||||
#define SDL_HAVE_BLIT_N !SDL_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
/* Optimized functions from 'SDL_blit_N.c'
|
||||
- RGB565 conversion with Lookup tables */
|
||||
#ifndef SDL_HAVE_BLIT_N_RGB565
|
||||
#define SDL_HAVE_BLIT_N_RGB565 !SDL_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
/* Optimized functions from 'SDL_blit_AUTO.c'
|
||||
- blit with modulate color, modulate alpha, any blending mode
|
||||
- scaling or not */
|
||||
#ifndef SDL_HAVE_BLIT_AUTO
|
||||
#define SDL_HAVE_BLIT_AUTO !SDL_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
/* Run-Length-Encoding
|
||||
- SDL_SetSurfaceColorKey() called with SDL_RLEACCEL flag */
|
||||
#ifndef SDL_HAVE_RLE
|
||||
#define SDL_HAVE_RLE !SDL_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
/* Software SDL_Renderer
|
||||
- creation of software renderer
|
||||
- *not* general blitting functions
|
||||
- {blend,draw}{fillrect,line,point} internal functions */
|
||||
#ifndef SDL_VIDEO_RENDER_SW
|
||||
#define SDL_VIDEO_RENDER_SW !SDL_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
/* YUV formats
|
||||
- handling of YUV surfaces
|
||||
- blitting and conversion functions */
|
||||
#ifndef SDL_HAVE_YUV
|
||||
#define SDL_HAVE_YUV !SDL_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#ifndef SDL_RENDER_DISABLED
|
||||
/* define the not defined ones as 0 */
|
||||
#ifndef SDL_VIDEO_RENDER_D3D
|
||||
#define SDL_VIDEO_RENDER_D3D 0
|
||||
#endif
|
||||
#ifndef SDL_VIDEO_RENDER_D3D11
|
||||
#define SDL_VIDEO_RENDER_D3D11 0
|
||||
#endif
|
||||
#ifndef SDL_VIDEO_RENDER_D3D12
|
||||
#define SDL_VIDEO_RENDER_D3D12 0
|
||||
#endif
|
||||
#ifndef SDL_VIDEO_RENDER_METAL
|
||||
#define SDL_VIDEO_RENDER_METAL 0
|
||||
#endif
|
||||
#ifndef SDL_VIDEO_RENDER_OGL
|
||||
#define SDL_VIDEO_RENDER_OGL 0
|
||||
#endif
|
||||
#ifndef SDL_VIDEO_RENDER_OGL_ES2
|
||||
#define SDL_VIDEO_RENDER_OGL_ES2 0
|
||||
#endif
|
||||
#ifndef SDL_VIDEO_RENDER_PS2
|
||||
#define SDL_VIDEO_RENDER_PS2 0
|
||||
#endif
|
||||
#ifndef SDL_VIDEO_RENDER_PSP
|
||||
#define SDL_VIDEO_RENDER_PSP 0
|
||||
#endif
|
||||
#ifndef SDL_VIDEO_RENDER_VITA_GXM
|
||||
#define SDL_VIDEO_RENDER_VITA_GXM 0
|
||||
#endif
|
||||
#ifndef SDL_VIDEO_RENDER_VULKAN
|
||||
#define SDL_VIDEO_RENDER_VULKAN 0
|
||||
#endif
|
||||
#else /* define all as 0 */
|
||||
#undef SDL_VIDEO_RENDER_SW
|
||||
#define SDL_VIDEO_RENDER_SW 0
|
||||
#undef SDL_VIDEO_RENDER_D3D
|
||||
#define SDL_VIDEO_RENDER_D3D 0
|
||||
#undef SDL_VIDEO_RENDER_D3D11
|
||||
#define SDL_VIDEO_RENDER_D3D11 0
|
||||
#undef SDL_VIDEO_RENDER_D3D12
|
||||
#define SDL_VIDEO_RENDER_D3D12 0
|
||||
#undef SDL_VIDEO_RENDER_METAL
|
||||
#define SDL_VIDEO_RENDER_METAL 0
|
||||
#undef SDL_VIDEO_RENDER_OGL
|
||||
#define SDL_VIDEO_RENDER_OGL 0
|
||||
#undef SDL_VIDEO_RENDER_OGL_ES2
|
||||
#define SDL_VIDEO_RENDER_OGL_ES2 0
|
||||
#undef SDL_VIDEO_RENDER_PS2
|
||||
#define SDL_VIDEO_RENDER_PS2 0
|
||||
#undef SDL_VIDEO_RENDER_PSP
|
||||
#define SDL_VIDEO_RENDER_PSP 0
|
||||
#undef SDL_VIDEO_RENDER_VITA_GXM
|
||||
#define SDL_VIDEO_RENDER_VITA_GXM 0
|
||||
#undef SDL_VIDEO_RENDER_VULKAN
|
||||
#define SDL_VIDEO_RENDER_VULKAN 0
|
||||
#endif /* SDL_RENDER_DISABLED */
|
||||
|
||||
#define SDL_HAS_RENDER_DRIVER \
|
||||
(SDL_VIDEO_RENDER_SW | \
|
||||
SDL_VIDEO_RENDER_D3D | \
|
||||
SDL_VIDEO_RENDER_D3D11 | \
|
||||
SDL_VIDEO_RENDER_D3D12 | \
|
||||
SDL_VIDEO_RENDER_METAL | \
|
||||
SDL_VIDEO_RENDER_OGL | \
|
||||
SDL_VIDEO_RENDER_OGL_ES2 | \
|
||||
SDL_VIDEO_RENDER_PS2 | \
|
||||
SDL_VIDEO_RENDER_PSP | \
|
||||
SDL_VIDEO_RENDER_VITA_GXM | \
|
||||
SDL_VIDEO_RENDER_VULKAN )
|
||||
|
||||
#if !defined(SDL_RENDER_DISABLED) && !SDL_HAS_RENDER_DRIVER
|
||||
#error SDL_RENDER enabled without any backend drivers.
|
||||
#endif
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_intrin.h>
|
||||
|
||||
#define SDL_MAIN_NOIMPL /* don't drag in header-only implementation of SDL_main */
|
||||
#include <SDL3/SDL_main.h>
|
||||
|
||||
/* The internal implementations of these functions have up to nanosecond precision.
|
||||
We can expose these functions as part of the API if we want to later.
|
||||
*/
|
||||
/* Set up for C function definitions, even when using C++ */
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern DECLSPEC Uint32 SDLCALL SDL_GetNextObjectID(void);
|
||||
extern DECLSPEC int SDLCALL SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS);
|
||||
extern DECLSPEC int SDLCALL SDL_WaitConditionTimeoutNS(SDL_Condition *cond, SDL_Mutex *mutex, Sint64 timeoutNS);
|
||||
extern DECLSPEC SDL_bool SDLCALL SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS);
|
||||
|
||||
/* Ends C function definitions when using C++ */
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SDL_internal_h_ */
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "./SDL_list.h"
|
||||
|
||||
/* Push */
|
||||
int SDL_ListAdd(SDL_ListNode **head, void *ent)
|
||||
{
|
||||
SDL_ListNode *node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
|
||||
|
||||
if (!node) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
node->entry = ent;
|
||||
node->next = *head;
|
||||
*head = node;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Pop from end as a FIFO (if add with SDL_ListAdd) */
|
||||
void SDL_ListPop(SDL_ListNode **head, void **ent)
|
||||
{
|
||||
SDL_ListNode **ptr = head;
|
||||
|
||||
/* Invalid or empty */
|
||||
if (!head || !*head) {
|
||||
return;
|
||||
}
|
||||
|
||||
while ((*ptr)->next) {
|
||||
ptr = &(*ptr)->next;
|
||||
}
|
||||
|
||||
if (ent) {
|
||||
*ent = (*ptr)->entry;
|
||||
}
|
||||
|
||||
SDL_free(*ptr);
|
||||
*ptr = NULL;
|
||||
}
|
||||
|
||||
void SDL_ListRemove(SDL_ListNode **head, void *ent)
|
||||
{
|
||||
SDL_ListNode **ptr = head;
|
||||
|
||||
while (*ptr) {
|
||||
if ((*ptr)->entry == ent) {
|
||||
SDL_ListNode *tmp = *ptr;
|
||||
*ptr = (*ptr)->next;
|
||||
SDL_free(tmp);
|
||||
return;
|
||||
}
|
||||
ptr = &(*ptr)->next;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_ListClear(SDL_ListNode **head)
|
||||
{
|
||||
SDL_ListNode *l = *head;
|
||||
*head = NULL;
|
||||
while (l) {
|
||||
SDL_ListNode *tmp = l;
|
||||
l = l->next;
|
||||
SDL_free(tmp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_list_h_
|
||||
#define SDL_list_h_
|
||||
|
||||
typedef struct SDL_ListNode
|
||||
{
|
||||
void *entry;
|
||||
struct SDL_ListNode *next;
|
||||
} SDL_ListNode;
|
||||
|
||||
int SDL_ListAdd(SDL_ListNode **head, void *ent);
|
||||
void SDL_ListPop(SDL_ListNode **head, void **ent);
|
||||
void SDL_ListRemove(SDL_ListNode **head, void *ent);
|
||||
void SDL_ListClear(SDL_ListNode **head);
|
||||
|
||||
#endif /* SDL_list_h_ */
|
||||
@@ -0,0 +1,611 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINRT) || defined(SDL_PLATFORM_GDK)
|
||||
#include "core/windows/SDL_windows.h"
|
||||
#endif
|
||||
|
||||
/* Simple log messages in SDL */
|
||||
|
||||
#include "SDL_log_c.h"
|
||||
|
||||
#ifdef HAVE_STDIO_H
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
#include "stdlib/SDL_vacopy.h"
|
||||
|
||||
/* The size of the stack buffer to use for rendering log messages. */
|
||||
#define SDL_MAX_LOG_MESSAGE_STACK 256
|
||||
|
||||
#define DEFAULT_CATEGORY -1
|
||||
|
||||
typedef struct SDL_LogLevel
|
||||
{
|
||||
int category;
|
||||
SDL_LogPriority priority;
|
||||
struct SDL_LogLevel *next;
|
||||
} SDL_LogLevel;
|
||||
|
||||
/* The default log output function */
|
||||
static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority, const char *message);
|
||||
|
||||
static SDL_LogLevel *SDL_loglevels;
|
||||
static SDL_bool SDL_forced_priority = SDL_FALSE;
|
||||
static SDL_LogPriority SDL_forced_priority_level;
|
||||
static SDL_LogOutputFunction SDL_log_function = SDL_LogOutput;
|
||||
static void *SDL_log_userdata = NULL;
|
||||
static SDL_Mutex *log_function_mutex = NULL;
|
||||
|
||||
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
#endif
|
||||
|
||||
/* If this list changes, update the documentation for SDL_HINT_LOGGING */
|
||||
static const char *SDL_priority_prefixes[] = {
|
||||
NULL,
|
||||
"VERBOSE",
|
||||
"DEBUG",
|
||||
"INFO",
|
||||
"WARN",
|
||||
"ERROR",
|
||||
"CRITICAL"
|
||||
};
|
||||
SDL_COMPILE_TIME_ASSERT(priority_prefixes, SDL_arraysize(SDL_priority_prefixes) == SDL_NUM_LOG_PRIORITIES);
|
||||
|
||||
/* If this list changes, update the documentation for SDL_HINT_LOGGING */
|
||||
static const char *SDL_category_prefixes[] = {
|
||||
"APP",
|
||||
"ERROR",
|
||||
"ASSERT",
|
||||
"SYSTEM",
|
||||
"AUDIO",
|
||||
"VIDEO",
|
||||
"RENDER",
|
||||
"INPUT",
|
||||
"TEST"
|
||||
};
|
||||
SDL_COMPILE_TIME_ASSERT(category_prefixes, SDL_arraysize(SDL_category_prefixes) == SDL_LOG_CATEGORY_RESERVED1);
|
||||
|
||||
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
static int SDL_android_priority[SDL_NUM_LOG_PRIORITIES] = {
|
||||
ANDROID_LOG_UNKNOWN,
|
||||
ANDROID_LOG_VERBOSE,
|
||||
ANDROID_LOG_DEBUG,
|
||||
ANDROID_LOG_INFO,
|
||||
ANDROID_LOG_WARN,
|
||||
ANDROID_LOG_ERROR,
|
||||
ANDROID_LOG_FATAL
|
||||
};
|
||||
#endif /* SDL_PLATFORM_ANDROID */
|
||||
|
||||
void SDL_InitLog(void)
|
||||
{
|
||||
if (!log_function_mutex) {
|
||||
/* if this fails we'll try to continue without it. */
|
||||
log_function_mutex = SDL_CreateMutex();
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_QuitLog(void)
|
||||
{
|
||||
SDL_LogResetPriorities();
|
||||
if (log_function_mutex) {
|
||||
SDL_DestroyMutex(log_function_mutex);
|
||||
log_function_mutex = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_LogSetAllPriority(SDL_LogPriority priority)
|
||||
{
|
||||
SDL_LogLevel *entry;
|
||||
|
||||
for (entry = SDL_loglevels; entry; entry = entry->next) {
|
||||
entry->priority = priority;
|
||||
}
|
||||
|
||||
SDL_forced_priority = SDL_TRUE;
|
||||
SDL_forced_priority_level = priority;
|
||||
}
|
||||
|
||||
void SDL_LogSetPriority(int category, SDL_LogPriority priority)
|
||||
{
|
||||
SDL_LogLevel *entry;
|
||||
|
||||
for (entry = SDL_loglevels; entry; entry = entry->next) {
|
||||
if (entry->category == category) {
|
||||
entry->priority = priority;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create a new entry */
|
||||
entry = (SDL_LogLevel *)SDL_malloc(sizeof(*entry));
|
||||
if (entry) {
|
||||
entry->category = category;
|
||||
entry->priority = priority;
|
||||
entry->next = SDL_loglevels;
|
||||
SDL_loglevels = entry;
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_bool SDL_ParseLogCategory(const char *string, size_t length, int *category)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (SDL_isdigit(*string)) {
|
||||
*category = SDL_atoi(string);
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
if (*string == '*') {
|
||||
*category = DEFAULT_CATEGORY;
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
for (i = 0; i < SDL_arraysize(SDL_category_prefixes); ++i) {
|
||||
if (SDL_strncasecmp(string, SDL_category_prefixes[i], length) == 0) {
|
||||
*category = i;
|
||||
return SDL_TRUE;
|
||||
}
|
||||
}
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
static SDL_bool SDL_ParseLogPriority(const char *string, size_t length, SDL_LogPriority *priority)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (SDL_isdigit(*string)) {
|
||||
i = SDL_atoi(string);
|
||||
if (i == 0) {
|
||||
/* 0 has a special meaning of "disable this category" */
|
||||
*priority = SDL_NUM_LOG_PRIORITIES;
|
||||
return SDL_TRUE;
|
||||
}
|
||||
if (i >= SDL_LOG_PRIORITY_VERBOSE && i < SDL_NUM_LOG_PRIORITIES) {
|
||||
*priority = (SDL_LogPriority)i;
|
||||
return SDL_TRUE;
|
||||
}
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
if (SDL_strncasecmp(string, "quiet", length) == 0) {
|
||||
*priority = SDL_NUM_LOG_PRIORITIES;
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
for (i = SDL_LOG_PRIORITY_VERBOSE; i < SDL_NUM_LOG_PRIORITIES; ++i) {
|
||||
if (SDL_strncasecmp(string, SDL_priority_prefixes[i], length) == 0) {
|
||||
*priority = (SDL_LogPriority)i;
|
||||
return SDL_TRUE;
|
||||
}
|
||||
}
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
static SDL_bool SDL_ParseLogCategoryPriority(const char *hint, int category, SDL_LogPriority *priority)
|
||||
{
|
||||
const char *name, *next;
|
||||
int current_category;
|
||||
|
||||
if (category == DEFAULT_CATEGORY && SDL_strchr(hint, '=') == NULL) {
|
||||
return SDL_ParseLogPriority(hint, SDL_strlen(hint), priority);
|
||||
}
|
||||
|
||||
for (name = hint; name; name = next) {
|
||||
const char *sep = SDL_strchr(name, '=');
|
||||
if (!sep) {
|
||||
break;
|
||||
}
|
||||
next = SDL_strchr(sep, ',');
|
||||
if (next) {
|
||||
++next;
|
||||
}
|
||||
|
||||
if (SDL_ParseLogCategory(name, (sep - name), ¤t_category)) {
|
||||
if (current_category == category) {
|
||||
const char *value = sep + 1;
|
||||
size_t len;
|
||||
if (next) {
|
||||
len = (next - value - 1);
|
||||
} else {
|
||||
len = SDL_strlen(value);
|
||||
}
|
||||
return SDL_ParseLogPriority(value, len, priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
static SDL_LogPriority SDL_GetDefaultLogPriority(int category)
|
||||
{
|
||||
const char *hint = SDL_GetHint(SDL_HINT_LOGGING);
|
||||
if (hint) {
|
||||
SDL_LogPriority priority;
|
||||
|
||||
if (SDL_ParseLogCategoryPriority(hint, category, &priority)) {
|
||||
return priority;
|
||||
}
|
||||
if (SDL_ParseLogCategoryPriority(hint, DEFAULT_CATEGORY, &priority)) {
|
||||
return priority;
|
||||
}
|
||||
}
|
||||
|
||||
switch (category) {
|
||||
case SDL_LOG_CATEGORY_APPLICATION:
|
||||
return SDL_LOG_PRIORITY_INFO;
|
||||
case SDL_LOG_CATEGORY_ASSERT:
|
||||
return SDL_LOG_PRIORITY_WARN;
|
||||
case SDL_LOG_CATEGORY_TEST:
|
||||
return SDL_LOG_PRIORITY_VERBOSE;
|
||||
default:
|
||||
return SDL_LOG_PRIORITY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_LogPriority SDL_LogGetPriority(int category)
|
||||
{
|
||||
SDL_LogLevel *entry;
|
||||
|
||||
for (entry = SDL_loglevels; entry; entry = entry->next) {
|
||||
if (entry->category == category) {
|
||||
return entry->priority;
|
||||
}
|
||||
}
|
||||
|
||||
if (SDL_forced_priority) {
|
||||
return SDL_forced_priority_level;
|
||||
}
|
||||
|
||||
return SDL_GetDefaultLogPriority(category);
|
||||
}
|
||||
|
||||
void SDL_LogResetPriorities(void)
|
||||
{
|
||||
SDL_LogLevel *entry;
|
||||
|
||||
while (SDL_loglevels) {
|
||||
entry = SDL_loglevels;
|
||||
SDL_loglevels = entry->next;
|
||||
SDL_free(entry);
|
||||
}
|
||||
SDL_forced_priority = SDL_FALSE;
|
||||
}
|
||||
|
||||
void SDL_Log(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogVerbose(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_VERBOSE, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogDebug(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_DEBUG, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogInfo(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_INFO, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogWarn(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_WARN, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogError(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_ERROR, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogCritical(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, SDL_LOG_PRIORITY_CRITICAL, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void SDL_LogMessage(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
SDL_LogMessageV(category, priority, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
static const char *GetCategoryPrefix(int category)
|
||||
{
|
||||
if (category < SDL_LOG_CATEGORY_RESERVED1) {
|
||||
return SDL_category_prefixes[category];
|
||||
}
|
||||
if (category < SDL_LOG_CATEGORY_CUSTOM) {
|
||||
return "RESERVED";
|
||||
}
|
||||
return "CUSTOM";
|
||||
}
|
||||
#endif /* SDL_PLATFORM_ANDROID */
|
||||
|
||||
void SDL_LogMessageV(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)
|
||||
{
|
||||
char *message = NULL;
|
||||
char stack_buf[SDL_MAX_LOG_MESSAGE_STACK];
|
||||
size_t len_plus_term;
|
||||
int len;
|
||||
va_list aq;
|
||||
|
||||
/* Nothing to do if we don't have an output function */
|
||||
if (!SDL_log_function) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Make sure we don't exceed array bounds */
|
||||
if ((int)priority < 0 || priority >= SDL_NUM_LOG_PRIORITIES) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* See if we want to do anything with this message */
|
||||
if (priority < SDL_LogGetPriority(category)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!log_function_mutex) {
|
||||
/* this mutex creation can race if you log from two threads at startup. You should have called SDL_Init first! */
|
||||
log_function_mutex = SDL_CreateMutex();
|
||||
}
|
||||
|
||||
/* Render into stack buffer */
|
||||
va_copy(aq, ap);
|
||||
len = SDL_vsnprintf(stack_buf, sizeof(stack_buf), fmt, aq);
|
||||
va_end(aq);
|
||||
|
||||
if (len < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* If message truncated, allocate and re-render */
|
||||
if (len >= sizeof(stack_buf) && SDL_size_add_overflow(len, 1, &len_plus_term) == 0) {
|
||||
/* Allocate exactly what we need, including the zero-terminator */
|
||||
message = (char *)SDL_malloc(len_plus_term);
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
va_copy(aq, ap);
|
||||
len = SDL_vsnprintf(message, len_plus_term, fmt, aq);
|
||||
va_end(aq);
|
||||
} else {
|
||||
message = stack_buf;
|
||||
}
|
||||
|
||||
/* Chop off final endline. */
|
||||
if ((len > 0) && (message[len - 1] == '\n')) {
|
||||
message[--len] = '\0';
|
||||
if ((len > 0) && (message[len - 1] == '\r')) { /* catch "\r\n", too. */
|
||||
message[--len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
SDL_LockMutex(log_function_mutex);
|
||||
SDL_log_function(SDL_log_userdata, category, priority, message);
|
||||
SDL_UnlockMutex(log_function_mutex);
|
||||
|
||||
/* Free only if dynamically allocated */
|
||||
if (message != stack_buf) {
|
||||
SDL_free(message);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(SDL_PLATFORM_WIN32) && !defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_GDK)
|
||||
/* Flag tracking the attachment of the console: 0=unattached, 1=attached to a console, 2=attached to a file, -1=error */
|
||||
static int consoleAttached = 0;
|
||||
|
||||
/* Handle to stderr output of console. */
|
||||
static HANDLE stderrHandle = NULL;
|
||||
#endif
|
||||
|
||||
static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority,
|
||||
const char *message)
|
||||
{
|
||||
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINRT) || defined(SDL_PLATFORM_GDK)
|
||||
/* Way too many allocations here, urgh */
|
||||
/* Note: One can't call SDL_SetError here, since that function itself logs. */
|
||||
{
|
||||
char *output;
|
||||
size_t length;
|
||||
LPTSTR tstr;
|
||||
SDL_bool isstack;
|
||||
|
||||
#if !defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_GDK)
|
||||
BOOL attachResult;
|
||||
DWORD attachError;
|
||||
DWORD charsWritten;
|
||||
DWORD consoleMode;
|
||||
|
||||
/* Maybe attach console and get stderr handle */
|
||||
if (consoleAttached == 0) {
|
||||
attachResult = AttachConsole(ATTACH_PARENT_PROCESS);
|
||||
if (!attachResult) {
|
||||
attachError = GetLastError();
|
||||
if (attachError == ERROR_INVALID_HANDLE) {
|
||||
/* This is expected when running from Visual Studio */
|
||||
/*OutputDebugString(TEXT("Parent process has no console\r\n"));*/
|
||||
consoleAttached = -1;
|
||||
} else if (attachError == ERROR_GEN_FAILURE) {
|
||||
OutputDebugString(TEXT("Could not attach to console of parent process\r\n"));
|
||||
consoleAttached = -1;
|
||||
} else if (attachError == ERROR_ACCESS_DENIED) {
|
||||
/* Already attached */
|
||||
consoleAttached = 1;
|
||||
} else {
|
||||
OutputDebugString(TEXT("Error attaching console\r\n"));
|
||||
consoleAttached = -1;
|
||||
}
|
||||
} else {
|
||||
/* Newly attached */
|
||||
consoleAttached = 1;
|
||||
}
|
||||
|
||||
if (consoleAttached == 1) {
|
||||
stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
|
||||
|
||||
if (GetConsoleMode(stderrHandle, &consoleMode) == 0) {
|
||||
/* WriteConsole fails if the output is redirected to a file. Must use WriteFile instead. */
|
||||
consoleAttached = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* !defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_GDK) */
|
||||
|
||||
length = SDL_strlen(SDL_priority_prefixes[priority]) + 2 + SDL_strlen(message) + 1 + 1 + 1;
|
||||
output = SDL_small_alloc(char, length, &isstack);
|
||||
(void)SDL_snprintf(output, length, "%s: %s\r\n", SDL_priority_prefixes[priority], message);
|
||||
tstr = WIN_UTF8ToString(output);
|
||||
|
||||
/* Output to debugger */
|
||||
OutputDebugString(tstr);
|
||||
|
||||
#if !defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_GDK)
|
||||
/* Screen output to stderr, if console was attached. */
|
||||
if (consoleAttached == 1) {
|
||||
if (!WriteConsole(stderrHandle, tstr, (DWORD)SDL_tcslen(tstr), &charsWritten, NULL)) {
|
||||
OutputDebugString(TEXT("Error calling WriteConsole\r\n"));
|
||||
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) {
|
||||
OutputDebugString(TEXT("Insufficient heap memory to write message\r\n"));
|
||||
}
|
||||
}
|
||||
|
||||
} else if (consoleAttached == 2) {
|
||||
if (!WriteFile(stderrHandle, output, (DWORD)SDL_strlen(output), &charsWritten, NULL)) {
|
||||
OutputDebugString(TEXT("Error calling WriteFile\r\n"));
|
||||
}
|
||||
}
|
||||
#endif /* !defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_GDK) */
|
||||
|
||||
SDL_free(tstr);
|
||||
SDL_small_free(output, isstack);
|
||||
}
|
||||
#elif defined(SDL_PLATFORM_ANDROID)
|
||||
{
|
||||
char tag[32];
|
||||
|
||||
SDL_snprintf(tag, SDL_arraysize(tag), "SDL/%s", GetCategoryPrefix(category));
|
||||
__android_log_write(SDL_android_priority[priority], tag, message);
|
||||
}
|
||||
#elif defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))
|
||||
/* Technically we don't need Cocoa/UIKit, but that's where this function is defined for now.
|
||||
*/
|
||||
extern void SDL_NSLog(const char *prefix, const char *text);
|
||||
{
|
||||
SDL_NSLog(SDL_priority_prefixes[priority], message);
|
||||
return;
|
||||
}
|
||||
#elif defined(SDL_PLATFORM_PSP) || defined(SDL_PLATFORM_PS2)
|
||||
{
|
||||
FILE *pFile;
|
||||
pFile = fopen("SDL_Log.txt", "a");
|
||||
if (pFile) {
|
||||
(void)fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message);
|
||||
(void)fclose(pFile);
|
||||
}
|
||||
}
|
||||
#elif defined(SDL_PLATFORM_VITA)
|
||||
{
|
||||
FILE *pFile;
|
||||
pFile = fopen("ux0:/data/SDL_Log.txt", "a");
|
||||
if (pFile) {
|
||||
(void)fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message);
|
||||
(void)fclose(pFile);
|
||||
}
|
||||
}
|
||||
#elif defined(SDL_PLATFORM_3DS)
|
||||
{
|
||||
FILE *pFile;
|
||||
pFile = fopen("sdmc:/3ds/SDL_Log.txt", "a");
|
||||
if (pFile) {
|
||||
(void)fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message);
|
||||
(void)fclose(pFile);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(HAVE_STDIO_H) && \
|
||||
!(defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT)))
|
||||
(void)fprintf(stderr, "%s: %s\n", SDL_priority_prefixes[priority], message);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SDL_GetLogOutputFunction(SDL_LogOutputFunction *callback, void **userdata)
|
||||
{
|
||||
if (callback) {
|
||||
*callback = SDL_log_function;
|
||||
}
|
||||
if (userdata) {
|
||||
*userdata = SDL_log_userdata;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_SetLogOutputFunction(SDL_LogOutputFunction callback, void *userdata)
|
||||
{
|
||||
SDL_log_function = callback;
|
||||
SDL_log_userdata = userdata;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
/* This file defines useful function for working with SDL logging */
|
||||
|
||||
#ifndef SDL_log_c_h_
|
||||
#define SDL_log_c_h_
|
||||
|
||||
extern void SDL_InitLog(void);
|
||||
extern void SDL_QuitLog(void);
|
||||
|
||||
#endif /* SDL_log_c_h_ */
|
||||
@@ -0,0 +1,785 @@
|
||||
/*
|
||||
Simple DiretMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
#include "SDL_hashtable.h"
|
||||
#include "SDL_hints_c.h"
|
||||
#include "SDL_properties_c.h"
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SDL_PropertyType type;
|
||||
|
||||
union {
|
||||
void *pointer_value;
|
||||
char *string_value;
|
||||
Sint64 number_value;
|
||||
float float_value;
|
||||
SDL_bool boolean_value;
|
||||
} value;
|
||||
|
||||
char *string_storage;
|
||||
|
||||
void (SDLCALL *cleanup)(void *userdata, void *value);
|
||||
void *userdata;
|
||||
} SDL_Property;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SDL_HashTable *props;
|
||||
SDL_Mutex *lock;
|
||||
} SDL_Properties;
|
||||
|
||||
static SDL_HashTable *SDL_properties;
|
||||
static SDL_Mutex *SDL_properties_lock;
|
||||
static SDL_PropertiesID SDL_last_properties_id;
|
||||
static SDL_PropertiesID SDL_global_properties;
|
||||
|
||||
|
||||
static void SDL_FreePropertyWithCleanup(const void *key, const void *value, void *data, SDL_bool cleanup)
|
||||
{
|
||||
SDL_Property *property = (SDL_Property *)value;
|
||||
if (property) {
|
||||
switch (property->type) {
|
||||
case SDL_PROPERTY_TYPE_POINTER:
|
||||
if (property->cleanup && cleanup) {
|
||||
property->cleanup(property->userdata, property->value.pointer_value);
|
||||
}
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_STRING:
|
||||
SDL_free(property->value.string_value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (property->string_storage) {
|
||||
SDL_free(property->string_storage);
|
||||
}
|
||||
}
|
||||
SDL_free((void *)key);
|
||||
SDL_free((void *)value);
|
||||
}
|
||||
|
||||
static void SDL_FreeProperty(const void *key, const void *value, void *data)
|
||||
{
|
||||
SDL_FreePropertyWithCleanup(key, value, data, SDL_TRUE);
|
||||
}
|
||||
|
||||
static void SDL_FreeProperties(const void *key, const void *value, void *data)
|
||||
{
|
||||
SDL_Properties *properties = (SDL_Properties *)value;
|
||||
if (properties) {
|
||||
if (properties->props) {
|
||||
SDL_DestroyHashTable(properties->props);
|
||||
properties->props = NULL;
|
||||
}
|
||||
if (properties->lock) {
|
||||
SDL_DestroyMutex(properties->lock);
|
||||
properties->lock = NULL;
|
||||
}
|
||||
SDL_free(properties);
|
||||
}
|
||||
}
|
||||
|
||||
int SDL_InitProperties(void)
|
||||
{
|
||||
if (!SDL_properties_lock) {
|
||||
SDL_properties_lock = SDL_CreateMutex();
|
||||
if (!SDL_properties_lock) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (!SDL_properties) {
|
||||
SDL_properties = SDL_CreateHashTable(NULL, 16, SDL_HashID, SDL_KeyMatchID, SDL_FreeProperties, SDL_FALSE);
|
||||
if (!SDL_properties) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SDL_QuitProperties(void)
|
||||
{
|
||||
if (SDL_global_properties) {
|
||||
SDL_DestroyProperties(SDL_global_properties);
|
||||
SDL_global_properties = 0;
|
||||
}
|
||||
if (SDL_properties) {
|
||||
SDL_DestroyHashTable(SDL_properties);
|
||||
SDL_properties = NULL;
|
||||
}
|
||||
if (SDL_properties_lock) {
|
||||
SDL_DestroyMutex(SDL_properties_lock);
|
||||
SDL_properties_lock = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_PropertiesID SDL_GetGlobalProperties(void)
|
||||
{
|
||||
if (!SDL_global_properties) {
|
||||
SDL_global_properties = SDL_CreateProperties();
|
||||
}
|
||||
return SDL_global_properties;
|
||||
}
|
||||
|
||||
SDL_PropertiesID SDL_CreateProperties(void)
|
||||
{
|
||||
SDL_PropertiesID props = 0;
|
||||
SDL_Properties *properties = NULL;
|
||||
SDL_bool inserted = SDL_FALSE;
|
||||
|
||||
if (!SDL_properties && SDL_InitProperties() < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
properties = (SDL_Properties *)SDL_calloc(1, sizeof(*properties));
|
||||
if (!properties) {
|
||||
goto error;
|
||||
}
|
||||
properties->props = SDL_CreateHashTable(NULL, 4, SDL_HashString, SDL_KeyMatchString, SDL_FreeProperty, SDL_FALSE);
|
||||
if (!properties->props) {
|
||||
goto error;
|
||||
}
|
||||
properties->lock = SDL_CreateMutex();
|
||||
if (!properties->lock) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (SDL_InitProperties() < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
++SDL_last_properties_id;
|
||||
if (SDL_last_properties_id == 0) {
|
||||
++SDL_last_properties_id;
|
||||
}
|
||||
props = SDL_last_properties_id;
|
||||
if (SDL_InsertIntoHashTable(SDL_properties, (const void *)(uintptr_t)props, properties)) {
|
||||
inserted = SDL_TRUE;
|
||||
}
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
|
||||
if (inserted) {
|
||||
/* All done! */
|
||||
return props;
|
||||
}
|
||||
|
||||
error:
|
||||
SDL_FreeProperties(NULL, properties, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst)
|
||||
{
|
||||
SDL_Properties *src_properties = NULL;
|
||||
SDL_Properties *dst_properties = NULL;
|
||||
int result = 0;
|
||||
|
||||
if (!src) {
|
||||
return SDL_InvalidParamError("src");
|
||||
}
|
||||
if (!dst) {
|
||||
return SDL_InvalidParamError("dst");
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)src, (const void **)&src_properties);
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)dst, (const void **)&dst_properties);
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
|
||||
if (!src_properties) {
|
||||
return SDL_InvalidParamError("src");
|
||||
}
|
||||
if (!dst_properties) {
|
||||
return SDL_InvalidParamError("dst");
|
||||
}
|
||||
|
||||
SDL_LockMutex(src_properties->lock);
|
||||
SDL_LockMutex(dst_properties->lock);
|
||||
{
|
||||
void *iter;
|
||||
const void *key, *value;
|
||||
|
||||
iter = NULL;
|
||||
while (SDL_IterateHashTable(src_properties->props, &key, &value, &iter)) {
|
||||
const char *src_name = (const char *)key;
|
||||
const SDL_Property *src_property = (const SDL_Property *)value;
|
||||
char *dst_name;
|
||||
SDL_Property *dst_property;
|
||||
|
||||
if (src_property->cleanup) {
|
||||
/* Can't copy properties with cleanup functions, we don't know how to duplicate the data */
|
||||
continue;
|
||||
}
|
||||
|
||||
SDL_RemoveFromHashTable(dst_properties->props, src_name);
|
||||
|
||||
dst_name = SDL_strdup(src_name);
|
||||
if (!dst_name) {
|
||||
result = -1;
|
||||
continue;
|
||||
}
|
||||
dst_property = (SDL_Property *)SDL_malloc(sizeof(*dst_property));
|
||||
if (!dst_property) {
|
||||
SDL_free(dst_name);
|
||||
result = -1;
|
||||
continue;
|
||||
}
|
||||
SDL_copyp(dst_property, src_property);
|
||||
if (src_property->type == SDL_PROPERTY_TYPE_STRING) {
|
||||
dst_property->value.string_value = SDL_strdup(src_property->value.string_value);
|
||||
}
|
||||
if (!SDL_InsertIntoHashTable(dst_properties->props, dst_name, dst_property)) {
|
||||
SDL_FreePropertyWithCleanup(dst_name, dst_property, NULL, SDL_FALSE);
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(dst_properties->lock);
|
||||
SDL_UnlockMutex(src_properties->lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int SDL_LockProperties(SDL_PropertiesID props)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
|
||||
if (!props) {
|
||||
return SDL_InvalidParamError("props");
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
|
||||
if (!properties) {
|
||||
return SDL_InvalidParamError("props");
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SDL_UnlockProperties(SDL_PropertiesID props)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
|
||||
if (!props) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
|
||||
if (!properties) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
}
|
||||
|
||||
static int SDL_PrivateSetProperty(SDL_PropertiesID props, const char *name, SDL_Property *property)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
int result = 0;
|
||||
|
||||
if (!props) {
|
||||
SDL_FreePropertyWithCleanup(NULL, property, NULL, SDL_FALSE);
|
||||
return SDL_InvalidParamError("props");
|
||||
}
|
||||
if (!name || !*name) {
|
||||
SDL_FreePropertyWithCleanup(NULL, property, NULL, SDL_FALSE);
|
||||
return SDL_InvalidParamError("name");
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
|
||||
if (!properties) {
|
||||
SDL_FreePropertyWithCleanup(NULL, property, NULL, SDL_FALSE);
|
||||
return SDL_InvalidParamError("props");
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_RemoveFromHashTable(properties->props, name);
|
||||
if (property) {
|
||||
char *key = SDL_strdup(name);
|
||||
if (!SDL_InsertIntoHashTable(properties->props, key, property)) {
|
||||
SDL_FreePropertyWithCleanup(key, property, NULL, SDL_FALSE);
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int SDL_SetPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, void (SDLCALL *cleanup)(void *userdata, void *value), void *userdata)
|
||||
{
|
||||
SDL_Property *property;
|
||||
|
||||
if (!value) {
|
||||
return SDL_ClearProperty(props, name);
|
||||
}
|
||||
|
||||
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
|
||||
if (!property) {
|
||||
SDL_FreePropertyWithCleanup(NULL, property, NULL, SDL_FALSE);
|
||||
return -1;
|
||||
}
|
||||
property->type = SDL_PROPERTY_TYPE_POINTER;
|
||||
property->value.pointer_value = value;
|
||||
property->cleanup = cleanup;
|
||||
property->userdata = userdata;
|
||||
return SDL_PrivateSetProperty(props, name, property);
|
||||
}
|
||||
|
||||
int SDL_SetProperty(SDL_PropertiesID props, const char *name, void *value)
|
||||
{
|
||||
SDL_Property *property;
|
||||
|
||||
if (!value) {
|
||||
return SDL_ClearProperty(props, name);
|
||||
}
|
||||
|
||||
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
|
||||
if (!property) {
|
||||
return -1;
|
||||
}
|
||||
property->type = SDL_PROPERTY_TYPE_POINTER;
|
||||
property->value.pointer_value = value;
|
||||
return SDL_PrivateSetProperty(props, name, property);
|
||||
}
|
||||
|
||||
static void SDLCALL CleanupFreeableProperty(void *userdata, void *value)
|
||||
{
|
||||
SDL_free(value);
|
||||
}
|
||||
|
||||
int SDL_SetFreeableProperty(SDL_PropertiesID props, const char *name, void *value)
|
||||
{
|
||||
return SDL_SetPropertyWithCleanup(props, name, value, CleanupFreeableProperty, NULL);
|
||||
}
|
||||
|
||||
static void SDLCALL CleanupSurface(void *userdata, void *value)
|
||||
{
|
||||
SDL_Surface *surface = (SDL_Surface *)value;
|
||||
|
||||
SDL_DestroySurface(surface);
|
||||
}
|
||||
|
||||
int SDL_SetSurfaceProperty(SDL_PropertiesID props, const char *name, SDL_Surface *surface)
|
||||
{
|
||||
return SDL_SetPropertyWithCleanup(props, name, surface, CleanupSurface, NULL);
|
||||
}
|
||||
|
||||
int SDL_SetStringProperty(SDL_PropertiesID props, const char *name, const char *value)
|
||||
{
|
||||
SDL_Property *property;
|
||||
|
||||
if (!value) {
|
||||
return SDL_ClearProperty(props, name);
|
||||
}
|
||||
|
||||
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
|
||||
if (!property) {
|
||||
return -1;
|
||||
}
|
||||
property->type = SDL_PROPERTY_TYPE_STRING;
|
||||
property->value.string_value = SDL_strdup(value);
|
||||
if (!property->value.string_value) {
|
||||
SDL_free(property);
|
||||
return -1;
|
||||
}
|
||||
return SDL_PrivateSetProperty(props, name, property);
|
||||
}
|
||||
|
||||
int SDL_SetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 value)
|
||||
{
|
||||
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
|
||||
if (!property) {
|
||||
return -1;
|
||||
}
|
||||
property->type = SDL_PROPERTY_TYPE_NUMBER;
|
||||
property->value.number_value = value;
|
||||
return SDL_PrivateSetProperty(props, name, property);
|
||||
}
|
||||
|
||||
int SDL_SetFloatProperty(SDL_PropertiesID props, const char *name, float value)
|
||||
{
|
||||
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
|
||||
if (!property) {
|
||||
return -1;
|
||||
}
|
||||
property->type = SDL_PROPERTY_TYPE_FLOAT;
|
||||
property->value.float_value = value;
|
||||
return SDL_PrivateSetProperty(props, name, property);
|
||||
}
|
||||
|
||||
int SDL_SetBooleanProperty(SDL_PropertiesID props, const char *name, SDL_bool value)
|
||||
{
|
||||
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
|
||||
if (!property) {
|
||||
return -1;
|
||||
}
|
||||
property->type = SDL_PROPERTY_TYPE_BOOLEAN;
|
||||
property->value.boolean_value = value ? SDL_TRUE : SDL_FALSE;
|
||||
return SDL_PrivateSetProperty(props, name, property);
|
||||
}
|
||||
|
||||
SDL_bool SDL_HasProperty(SDL_PropertiesID props, const char *name)
|
||||
{
|
||||
return (SDL_GetPropertyType(props, name) != SDL_PROPERTY_TYPE_INVALID);
|
||||
}
|
||||
|
||||
SDL_PropertyType SDL_GetPropertyType(SDL_PropertiesID props, const char *name)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
SDL_PropertyType type = SDL_PROPERTY_TYPE_INVALID;
|
||||
|
||||
if (!props) {
|
||||
return SDL_PROPERTY_TYPE_INVALID;
|
||||
}
|
||||
if (!name || !*name) {
|
||||
return SDL_PROPERTY_TYPE_INVALID;
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
|
||||
if (!properties) {
|
||||
return SDL_PROPERTY_TYPE_INVALID;
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_Property *property = NULL;
|
||||
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
|
||||
type = property->type;
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
void *SDL_GetProperty(SDL_PropertiesID props, const char *name, void *default_value)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
void *value = default_value;
|
||||
|
||||
if (!props) {
|
||||
return value;
|
||||
}
|
||||
if (!name || !*name) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
|
||||
if (!properties) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Note that taking the lock here only guarantees that we won't read the
|
||||
* hashtable while it's being modified. The value itself can easily be
|
||||
* freed from another thread after it is returned here.
|
||||
*/
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_Property *property = NULL;
|
||||
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
|
||||
if (property->type == SDL_PROPERTY_TYPE_POINTER) {
|
||||
value = property->value.pointer_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
const char *SDL_GetStringProperty(SDL_PropertiesID props, const char *name, const char *default_value)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
const char *value = default_value;
|
||||
|
||||
if (!props) {
|
||||
return value;
|
||||
}
|
||||
if (!name || !*name) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
|
||||
if (!properties) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Note that taking the lock here only guarantees that we won't read the
|
||||
* hashtable while it's being modified. The value itself can easily be
|
||||
* freed from another thread after it is returned here.
|
||||
*
|
||||
* FIXME: Should we SDL_strdup() the return value to avoid this?
|
||||
*/
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_Property *property = NULL;
|
||||
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
|
||||
switch (property->type) {
|
||||
case SDL_PROPERTY_TYPE_STRING:
|
||||
value = property->value.string_value;
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_NUMBER:
|
||||
if (property->string_storage) {
|
||||
value = property->string_storage;
|
||||
} else {
|
||||
SDL_asprintf(&property->string_storage, "%" SDL_PRIs64, property->value.number_value);
|
||||
if (property->string_storage) {
|
||||
value = property->string_storage;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_FLOAT:
|
||||
if (property->string_storage) {
|
||||
value = property->string_storage;
|
||||
} else {
|
||||
SDL_asprintf(&property->string_storage, "%f", property->value.float_value);
|
||||
if (property->string_storage) {
|
||||
value = property->string_storage;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_BOOLEAN:
|
||||
value = property->value.boolean_value ? "true" : "false";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
Sint64 SDL_GetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 default_value)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
Sint64 value = default_value;
|
||||
|
||||
if (!props) {
|
||||
return value;
|
||||
}
|
||||
if (!name || !*name) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
|
||||
if (!properties) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_Property *property = NULL;
|
||||
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
|
||||
switch (property->type) {
|
||||
case SDL_PROPERTY_TYPE_STRING:
|
||||
value = SDL_strtoll(property->value.string_value, NULL, 0);
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_NUMBER:
|
||||
value = property->value.number_value;
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_FLOAT:
|
||||
value = (Sint64)SDL_round((double)property->value.float_value);
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_BOOLEAN:
|
||||
value = property->value.boolean_value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
float SDL_GetFloatProperty(SDL_PropertiesID props, const char *name, float default_value)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
float value = default_value;
|
||||
|
||||
if (!props) {
|
||||
return value;
|
||||
}
|
||||
if (!name || !*name) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
|
||||
if (!properties) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_Property *property = NULL;
|
||||
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
|
||||
switch (property->type) {
|
||||
case SDL_PROPERTY_TYPE_STRING:
|
||||
value = (float)SDL_atof(property->value.string_value);
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_NUMBER:
|
||||
value = (float)property->value.number_value;
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_FLOAT:
|
||||
value = property->value.float_value;
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_BOOLEAN:
|
||||
value = (float)property->value.boolean_value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_bool SDL_GetBooleanProperty(SDL_PropertiesID props, const char *name, SDL_bool default_value)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
SDL_bool value = default_value;
|
||||
|
||||
if (!props) {
|
||||
return value;
|
||||
}
|
||||
if (!name || !*name) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
|
||||
if (!properties) {
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
SDL_Property *property = NULL;
|
||||
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
|
||||
switch (property->type) {
|
||||
case SDL_PROPERTY_TYPE_STRING:
|
||||
value = SDL_GetStringBoolean(property->value.string_value, default_value);
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_NUMBER:
|
||||
value = (property->value.number_value != 0);
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_FLOAT:
|
||||
value = (property->value.float_value != 0.0f);
|
||||
break;
|
||||
case SDL_PROPERTY_TYPE_BOOLEAN:
|
||||
value = property->value.boolean_value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
int SDL_ClearProperty(SDL_PropertiesID props, const char *name)
|
||||
{
|
||||
return SDL_PrivateSetProperty(props, name, NULL);
|
||||
}
|
||||
|
||||
int SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCallback callback, void *userdata)
|
||||
{
|
||||
SDL_Properties *properties = NULL;
|
||||
|
||||
if (!props) {
|
||||
return SDL_InvalidParamError("props");
|
||||
}
|
||||
if (!callback) {
|
||||
return SDL_InvalidParamError("callback");
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
|
||||
if (!properties) {
|
||||
return SDL_InvalidParamError("props");
|
||||
}
|
||||
|
||||
SDL_LockMutex(properties->lock);
|
||||
{
|
||||
void *iter;
|
||||
const void *key, *value;
|
||||
|
||||
iter = NULL;
|
||||
while (SDL_IterateHashTable(properties->props, &key, &value, &iter)) {
|
||||
callback(userdata, props, (const char *)key);
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(properties->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SDL_DestroyProperties(SDL_PropertiesID props)
|
||||
{
|
||||
if (!props) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_LockMutex(SDL_properties_lock);
|
||||
SDL_RemoveFromHashTable(SDL_properties, (const void *)(uintptr_t)props);
|
||||
SDL_UnlockMutex(SDL_properties_lock);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Simple DiretMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
extern int SDL_InitProperties(void);
|
||||
extern int SDL_SetFreeableProperty(SDL_PropertiesID props, const char *name, void *value);
|
||||
extern int SDL_SetSurfaceProperty(SDL_PropertiesID props, const char *name, SDL_Surface *surface);
|
||||
extern void SDL_QuitProperties(void);
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_utils_c.h"
|
||||
|
||||
/* Common utility functions that aren't in the public API */
|
||||
|
||||
int SDL_powerof2(int x)
|
||||
{
|
||||
int value;
|
||||
|
||||
if (x <= 0) {
|
||||
/* Return some sane value - we shouldn't hit this in our use cases */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* This trick works for 32-bit values */
|
||||
{
|
||||
SDL_COMPILE_TIME_ASSERT(SDL_powerof2, sizeof(x) == sizeof(Uint32));
|
||||
}
|
||||
value = x;
|
||||
value -= 1;
|
||||
value |= value >> 1;
|
||||
value |= value >> 2;
|
||||
value |= value >> 4;
|
||||
value |= value >> 8;
|
||||
value |= value >> 16;
|
||||
value += 1;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
SDL_bool SDL_endswith(const char *string, const char *suffix)
|
||||
{
|
||||
size_t string_length = string ? SDL_strlen(string) : 0;
|
||||
size_t suffix_length = suffix ? SDL_strlen(suffix) : 0;
|
||||
|
||||
if (suffix_length > 0 && suffix_length <= string_length) {
|
||||
if (SDL_memcmp(string + string_length - suffix_length, suffix, suffix_length) == 0) {
|
||||
return SDL_TRUE;
|
||||
}
|
||||
}
|
||||
return SDL_FALSE;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_utils_h_
|
||||
#define SDL_utils_h_
|
||||
|
||||
/* Common utility functions that aren't in the public API */
|
||||
|
||||
/* Return the smallest power of 2 greater than or equal to 'x' */
|
||||
extern int SDL_powerof2(int x);
|
||||
|
||||
SDL_bool SDL_endswith(const char *string, const char *suffix);
|
||||
|
||||
#endif /* SDL_utils_h_ */
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1900)
|
||||
#include <intrin.h>
|
||||
#define HAVE_MSC_ATOMICS 1
|
||||
#endif
|
||||
|
||||
#ifdef SDL_PLATFORM_MACOS /* !!! FIXME: should we favor gcc atomics? */
|
||||
#include <libkern/OSAtomic.h>
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_SOLARIS)
|
||||
#include <atomic.h>
|
||||
#endif
|
||||
|
||||
/* The __atomic_load_n() intrinsic showed up in different times for different compilers. */
|
||||
#ifdef __clang__
|
||||
#if __has_builtin(__atomic_load_n) || defined(HAVE_GCC_ATOMICS)
|
||||
/* !!! FIXME: this advertises as available in the NDK but uses an external symbol we don't have.
|
||||
It might be in a later NDK or we might need an extra library? --ryan. */
|
||||
#ifndef SDL_PLATFORM_ANDROID
|
||||
#define HAVE_ATOMIC_LOAD_N 1
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(__GNUC__)
|
||||
#if (__GNUC__ >= 5)
|
||||
#define HAVE_ATOMIC_LOAD_N 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* *INDENT-OFF* */ /* clang-format off */
|
||||
#if defined(__WATCOMC__) && defined(__386__)
|
||||
SDL_COMPILE_TIME_ASSERT(intsize, 4==sizeof(int));
|
||||
#define HAVE_WATCOM_ATOMICS
|
||||
extern __inline int _SDL_xchg_watcom(volatile int *a, int v);
|
||||
#pragma aux _SDL_xchg_watcom = \
|
||||
"lock xchg [ecx], eax" \
|
||||
parm [ecx] [eax] \
|
||||
value [eax] \
|
||||
modify exact [eax];
|
||||
|
||||
extern __inline unsigned char _SDL_cmpxchg_watcom(volatile int *a, int newval, int oldval);
|
||||
#pragma aux _SDL_cmpxchg_watcom = \
|
||||
"lock cmpxchg [edx], ecx" \
|
||||
"setz al" \
|
||||
parm [edx] [ecx] [eax] \
|
||||
value [al] \
|
||||
modify exact [eax];
|
||||
|
||||
extern __inline int _SDL_xadd_watcom(volatile int *a, int v);
|
||||
#pragma aux _SDL_xadd_watcom = \
|
||||
"lock xadd [ecx], eax" \
|
||||
parm [ecx] [eax] \
|
||||
value [eax] \
|
||||
modify exact [eax];
|
||||
|
||||
#endif /* __WATCOMC__ && __386__ */
|
||||
/* *INDENT-ON* */ /* clang-format on */
|
||||
|
||||
/*
|
||||
If any of the operations are not provided then we must emulate some
|
||||
of them. That means we need a nice implementation of spin locks
|
||||
that avoids the "one big lock" problem. We use a vector of spin
|
||||
locks and pick which one to use based on the address of the operand
|
||||
of the function.
|
||||
|
||||
To generate the index of the lock we first shift by 3 bits to get
|
||||
rid on the zero bits that result from 32 and 64 bit alignment of
|
||||
data. We then mask off all but 5 bits and use those 5 bits as an
|
||||
index into the table.
|
||||
|
||||
Picking the lock this way insures that accesses to the same data at
|
||||
the same time will go to the same lock. OTOH, accesses to different
|
||||
data have only a 1/32 chance of hitting the same lock. That should
|
||||
pretty much eliminate the chances of several atomic operations on
|
||||
different data from waiting on the same "big lock". If it isn't
|
||||
then the table of locks can be expanded to a new size so long as
|
||||
the new size is a power of two.
|
||||
|
||||
Contributed by Bob Pendleton, bob@pendleton.com
|
||||
*/
|
||||
|
||||
#if !defined(HAVE_MSC_ATOMICS) && !defined(HAVE_GCC_ATOMICS) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_SOLARIS) && !defined(HAVE_WATCOM_ATOMICS)
|
||||
#define EMULATE_CAS 1
|
||||
#endif
|
||||
|
||||
#ifdef EMULATE_CAS
|
||||
static SDL_SpinLock locks[32];
|
||||
|
||||
static SDL_INLINE void enterLock(void *a)
|
||||
{
|
||||
uintptr_t index = ((((uintptr_t)a) >> 3) & 0x1f);
|
||||
|
||||
SDL_LockSpinlock(&locks[index]);
|
||||
}
|
||||
|
||||
static SDL_INLINE void leaveLock(void *a)
|
||||
{
|
||||
uintptr_t index = ((((uintptr_t)a) >> 3) & 0x1f);
|
||||
|
||||
SDL_UnlockSpinlock(&locks[index]);
|
||||
}
|
||||
#endif
|
||||
|
||||
SDL_bool SDL_AtomicCompareAndSwap(SDL_AtomicInt *a, int oldval, int newval)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value));
|
||||
return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval;
|
||||
#elif defined(HAVE_WATCOM_ATOMICS)
|
||||
return _SDL_cmpxchg_watcom(&a->value, newval, oldval);
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_bool_compare_and_swap(&a->value, oldval, newval);
|
||||
#elif defined(SDL_PLATFORM_MACOS) /* this is deprecated in 10.12 sdk; favor gcc atomics. */
|
||||
return OSAtomicCompareAndSwap32Barrier(oldval, newval, &a->value);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
return ((int)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval);
|
||||
#elif defined(EMULATE_CAS)
|
||||
SDL_bool retval = SDL_FALSE;
|
||||
|
||||
enterLock(a);
|
||||
if (a->value == oldval) {
|
||||
a->value = newval;
|
||||
retval = SDL_TRUE;
|
||||
}
|
||||
leaveLock(a);
|
||||
|
||||
return retval;
|
||||
#else
|
||||
#error Please define your platform.
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_bool SDL_AtomicCompareAndSwapPointer(void **a, void *oldval, void *newval)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
return _InterlockedCompareExchangePointer(a, newval, oldval) == oldval;
|
||||
#elif defined(HAVE_WATCOM_ATOMICS)
|
||||
return _SDL_cmpxchg_watcom((int *)a, (long)newval, (long)oldval);
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_bool_compare_and_swap(a, oldval, newval);
|
||||
#elif defined(SDL_PLATFORM_MACOS) && defined(__LP64__) /* this is deprecated in 10.12 sdk; favor gcc atomics. */
|
||||
return OSAtomicCompareAndSwap64Barrier((int64_t)oldval, (int64_t)newval, (int64_t *)a);
|
||||
#elif defined(SDL_PLATFORM_MACOS) && !defined(__LP64__) /* this is deprecated in 10.12 sdk; favor gcc atomics. */
|
||||
return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t *)a);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
return (atomic_cas_ptr(a, oldval, newval) == oldval);
|
||||
#elif defined(EMULATE_CAS)
|
||||
SDL_bool retval = SDL_FALSE;
|
||||
|
||||
enterLock(a);
|
||||
if (*a == oldval) {
|
||||
*a = newval;
|
||||
retval = SDL_TRUE;
|
||||
}
|
||||
leaveLock(a);
|
||||
|
||||
return retval;
|
||||
#else
|
||||
#error Please define your platform.
|
||||
#endif
|
||||
}
|
||||
|
||||
int SDL_AtomicSet(SDL_AtomicInt *a, int v)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value));
|
||||
return _InterlockedExchange((long *)&a->value, v);
|
||||
#elif defined(HAVE_WATCOM_ATOMICS)
|
||||
return _SDL_xchg_watcom(&a->value, v);
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_lock_test_and_set(&a->value, v);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
return (int)atomic_swap_uint((volatile uint_t *)&a->value, v);
|
||||
#else
|
||||
int value;
|
||||
do {
|
||||
value = a->value;
|
||||
} while (!SDL_AtomicCompareAndSwap(a, value, v));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
void *SDL_AtomicSetPtr(void **a, void *v)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
return _InterlockedExchangePointer(a, v);
|
||||
#elif defined(HAVE_WATCOM_ATOMICS)
|
||||
return (void *)_SDL_xchg_watcom((int *)a, (long)v);
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_lock_test_and_set(a, v);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
return atomic_swap_ptr(a, v);
|
||||
#else
|
||||
void *value;
|
||||
do {
|
||||
value = *a;
|
||||
} while (!SDL_AtomicCompareAndSwapPointer(a, value, v));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
int SDL_AtomicAdd(SDL_AtomicInt *a, int v)
|
||||
{
|
||||
#ifdef HAVE_MSC_ATOMICS
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(long) == sizeof(a->value));
|
||||
return _InterlockedExchangeAdd((long *)&a->value, v);
|
||||
#elif defined(HAVE_WATCOM_ATOMICS)
|
||||
return _SDL_xadd_watcom(&a->value, v);
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_fetch_and_add(&a->value, v);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
int pv = a->value;
|
||||
membar_consumer();
|
||||
atomic_add_int((volatile uint_t *)&a->value, v);
|
||||
return pv;
|
||||
#else
|
||||
int value;
|
||||
do {
|
||||
value = a->value;
|
||||
} while (!SDL_AtomicCompareAndSwap(a, value, (value + v)));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
int SDL_AtomicGet(SDL_AtomicInt *a)
|
||||
{
|
||||
#ifdef HAVE_ATOMIC_LOAD_N
|
||||
return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST);
|
||||
#elif defined(HAVE_MSC_ATOMICS)
|
||||
SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(long) == sizeof(a->value));
|
||||
return _InterlockedOr((long *)&a->value, 0);
|
||||
#elif defined(HAVE_WATCOM_ATOMICS)
|
||||
return _SDL_xadd_watcom(&a->value, 0);
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_or_and_fetch(&a->value, 0);
|
||||
#elif defined(SDL_PLATFORM_MACOS) /* this is deprecated in 10.12 sdk; favor gcc atomics. */
|
||||
return sizeof(a->value) == sizeof(uint32_t) ? OSAtomicOr32Barrier(0, (volatile uint32_t *)&a->value) : OSAtomicAdd64Barrier(0, (volatile int64_t *)&a->value);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
return atomic_or_uint((volatile uint_t *)&a->value, 0);
|
||||
#else
|
||||
int value;
|
||||
do {
|
||||
value = a->value;
|
||||
} while (!SDL_AtomicCompareAndSwap(a, value, value));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
void *SDL_AtomicGetPtr(void **a)
|
||||
{
|
||||
#ifdef HAVE_ATOMIC_LOAD_N
|
||||
return __atomic_load_n(a, __ATOMIC_SEQ_CST);
|
||||
#elif defined(HAVE_MSC_ATOMICS)
|
||||
return _InterlockedCompareExchangePointer(a, NULL, NULL);
|
||||
#elif defined(HAVE_GCC_ATOMICS)
|
||||
return __sync_val_compare_and_swap(a, (void *)0, (void *)0);
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
return atomic_cas_ptr(a, (void *)0, (void *)0);
|
||||
#else
|
||||
void *value;
|
||||
do {
|
||||
value = *a;
|
||||
} while (!SDL_AtomicCompareAndSwapPointer(a, value, value));
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef SDL_MEMORY_BARRIER_USES_FUNCTION
|
||||
#error This file should be built in arm mode so the mcr instruction is available for memory barriers
|
||||
#endif
|
||||
|
||||
void SDL_MemoryBarrierReleaseFunction(void)
|
||||
{
|
||||
SDL_MemoryBarrierRelease();
|
||||
}
|
||||
|
||||
void SDL_MemoryBarrierAcquireFunction(void)
|
||||
{
|
||||
SDL_MemoryBarrierAcquire();
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINRT) || defined(SDL_PLATFORM_GDK)
|
||||
#include "../core/windows/SDL_windows.h"
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_SOLARIS)
|
||||
#include <atomic.h>
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_RISCOS)
|
||||
#include <unixlib/local.h>
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#ifdef PS2
|
||||
#include <kernel.h>
|
||||
#endif
|
||||
|
||||
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_MACOS)
|
||||
#include <libkern/OSAtomic.h>
|
||||
#endif
|
||||
|
||||
/* *INDENT-OFF* */ /* clang-format off */
|
||||
#if defined(__WATCOMC__) && defined(__386__)
|
||||
SDL_COMPILE_TIME_ASSERT(locksize, 4==sizeof(SDL_SpinLock));
|
||||
extern __inline int _SDL_xchg_watcom(volatile int *a, int v);
|
||||
#pragma aux _SDL_xchg_watcom = \
|
||||
"lock xchg [ecx], eax" \
|
||||
parm [ecx] [eax] \
|
||||
value [eax] \
|
||||
modify exact [eax];
|
||||
#endif /* __WATCOMC__ && __386__ */
|
||||
/* *INDENT-ON* */ /* clang-format on */
|
||||
|
||||
/* This function is where all the magic happens... */
|
||||
SDL_bool SDL_TryLockSpinlock(SDL_SpinLock *lock)
|
||||
{
|
||||
#if defined(HAVE_GCC_ATOMICS) || defined(HAVE_GCC_SYNC_LOCK_TEST_AND_SET)
|
||||
return __sync_lock_test_and_set(lock, 1) == 0;
|
||||
|
||||
#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64))
|
||||
return _InterlockedExchange_acq(lock, 1) == 0;
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
SDL_COMPILE_TIME_ASSERT(locksize, sizeof(*lock) == sizeof(long));
|
||||
return InterlockedExchange((long *)lock, 1) == 0;
|
||||
|
||||
#elif defined(__WATCOMC__) && defined(__386__)
|
||||
return _SDL_xchg_watcom(lock, 1) == 0;
|
||||
|
||||
#elif defined(__GNUC__) && defined(__arm__) && \
|
||||
(defined(__ARM_ARCH_3__) || defined(__ARM_ARCH_3M__) || \
|
||||
defined(__ARM_ARCH_4__) || defined(__ARM_ARCH_4T__) || \
|
||||
defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5TE__) || \
|
||||
defined(__ARM_ARCH_5TEJ__))
|
||||
int result;
|
||||
|
||||
#ifdef SDL_PLATFORM_RISCOS
|
||||
if (__cpucap_have_rex()) {
|
||||
__asm__ __volatile__(
|
||||
"ldrex %0, [%2]\nteq %0, #0\nstrexeq %0, %1, [%2]"
|
||||
: "=&r"(result)
|
||||
: "r"(1), "r"(lock)
|
||||
: "cc", "memory");
|
||||
return result == 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
__asm__ __volatile__(
|
||||
"swp %0, %1, [%2]\n"
|
||||
: "=&r,&r"(result)
|
||||
: "r,0"(1), "r,r"(lock)
|
||||
: "memory");
|
||||
return result == 0;
|
||||
|
||||
#elif defined(__GNUC__) && defined(__arm__)
|
||||
int result;
|
||||
__asm__ __volatile__(
|
||||
"ldrex %0, [%2]\nteq %0, #0\nstrexeq %0, %1, [%2]"
|
||||
: "=&r"(result)
|
||||
: "r"(1), "r"(lock)
|
||||
: "cc", "memory");
|
||||
return result == 0;
|
||||
|
||||
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
|
||||
int result;
|
||||
__asm__ __volatile__(
|
||||
"lock ; xchgl %0, (%1)\n"
|
||||
: "=r"(result)
|
||||
: "r"(lock), "0"(1)
|
||||
: "cc", "memory");
|
||||
return result == 0;
|
||||
|
||||
#elif defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)
|
||||
/* Maybe used for PowerPC, but the Intel asm or gcc atomics are favored. */
|
||||
return OSAtomicCompareAndSwap32Barrier(0, 1, lock);
|
||||
|
||||
#elif defined(SDL_PLATFORM_SOLARIS) && defined(_LP64)
|
||||
/* Used for Solaris with non-gcc compilers. */
|
||||
return ((int)atomic_cas_64((volatile uint64_t *)lock, 0, 1) == 0);
|
||||
|
||||
#elif defined(SDL_PLATFORM_SOLARIS) && !defined(_LP64)
|
||||
/* Used for Solaris with non-gcc compilers. */
|
||||
return ((int)atomic_cas_32((volatile uint32_t *)lock, 0, 1) == 0);
|
||||
#elif defined(PS2)
|
||||
uint32_t oldintr;
|
||||
SDL_bool res = SDL_FALSE;
|
||||
// disable interuption
|
||||
oldintr = DIntr();
|
||||
|
||||
if (*lock == 0) {
|
||||
*lock = 1;
|
||||
res = SDL_TRUE;
|
||||
}
|
||||
// enable interuption
|
||||
if (oldintr) {
|
||||
EIntr();
|
||||
}
|
||||
return res;
|
||||
#else
|
||||
/* Terrible terrible damage */
|
||||
static SDL_Mutex *_spinlock_mutex;
|
||||
|
||||
if (!_spinlock_mutex) {
|
||||
/* Race condition on first lock... */
|
||||
_spinlock_mutex = SDL_CreateMutex();
|
||||
}
|
||||
SDL_LockMutex(_spinlock_mutex);
|
||||
if (*lock == 0) {
|
||||
*lock = 1;
|
||||
SDL_UnlockMutex(_spinlock_mutex);
|
||||
return SDL_TRUE;
|
||||
} else {
|
||||
SDL_UnlockMutex(_spinlock_mutex);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SDL_LockSpinlock(SDL_SpinLock *lock)
|
||||
{
|
||||
int iterations = 0;
|
||||
/* FIXME: Should we have an eventual timeout? */
|
||||
while (!SDL_TryLockSpinlock(lock)) {
|
||||
if (iterations < 32) {
|
||||
iterations++;
|
||||
SDL_CPUPauseInstruction();
|
||||
} else {
|
||||
/* !!! FIXME: this doesn't definitely give up the current timeslice, it does different things on various platforms. */
|
||||
SDL_Delay(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_UnlockSpinlock(SDL_SpinLock *lock)
|
||||
{
|
||||
#if defined(HAVE_GCC_ATOMICS) || defined(HAVE_GCC_SYNC_LOCK_TEST_AND_SET)
|
||||
__sync_lock_release(lock);
|
||||
|
||||
#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64))
|
||||
_InterlockedExchange_rel(lock, 0);
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
_ReadWriteBarrier();
|
||||
*lock = 0;
|
||||
|
||||
#elif defined(__WATCOMC__) && defined(__386__)
|
||||
SDL_CompilerBarrier();
|
||||
*lock = 0;
|
||||
|
||||
#elif defined(SDL_PLATFORM_SOLARIS)
|
||||
/* Used for Solaris when not using gcc. */
|
||||
*lock = 0;
|
||||
membar_producer();
|
||||
|
||||
#else
|
||||
*lock = 0;
|
||||
#endif
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_audio_c_h_
|
||||
#define SDL_audio_c_h_
|
||||
|
||||
extern void SDL_UpdateAudio(void);
|
||||
|
||||
#endif /* SDL_audio_c_h_ */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,544 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
// DO NOT EDIT, THIS FILE WAS GENERATED BY build-scripts/gen_audio_resampler_filter.c
|
||||
|
||||
#define RESAMPLER_ZERO_CROSSINGS 5
|
||||
#define RESAMPLER_BITS_PER_SAMPLE 16
|
||||
#define RESAMPLER_BITS_PER_ZERO_CROSSING ((RESAMPLER_BITS_PER_SAMPLE / 2) + 1)
|
||||
#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << RESAMPLER_BITS_PER_ZERO_CROSSING)
|
||||
#define RESAMPLER_FILTER_SIZE (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * RESAMPLER_ZERO_CROSSINGS)
|
||||
|
||||
static const float ResamplerFilter[RESAMPLER_FILTER_SIZE] = {
|
||||
1.000000000f, 0.000000000f,-0.000000000f, 0.000000000f,-0.000000000f,
|
||||
0.999993165f,-0.001679888f, 0.000529080f,-0.000151513f, 0.000027455f,
|
||||
0.999972661f,-0.003351212f, 0.001055794f,-0.000302183f, 0.000054683f,
|
||||
0.999938488f,-0.005013955f, 0.001580128f,-0.000452009f, 0.000081685f,
|
||||
0.999890647f,-0.006668099f, 0.002102071f,-0.000600987f, 0.000108459f,
|
||||
0.999829139f,-0.008313629f, 0.002621611f,-0.000749115f, 0.000135007f,
|
||||
0.999753966f,-0.009950528f, 0.003138734f,-0.000896389f, 0.000161328f,
|
||||
0.999665131f,-0.011578779f, 0.003653429f,-0.001042807f, 0.000187423f,
|
||||
0.999562634f,-0.013198368f, 0.004165684f,-0.001188367f, 0.000213291f,
|
||||
0.999446480f,-0.014809279f, 0.004675488f,-0.001333066f, 0.000238933f,
|
||||
0.999316672f,-0.016411497f, 0.005182828f,-0.001476901f, 0.000264348f,
|
||||
0.999173212f,-0.018005007f, 0.005687694f,-0.001619871f, 0.000289537f,
|
||||
0.999016105f,-0.019589795f, 0.006190074f,-0.001761971f, 0.000314501f,
|
||||
0.998845356f,-0.021165846f, 0.006689957f,-0.001903201f, 0.000339239f,
|
||||
0.998660968f,-0.022733147f, 0.007187332f,-0.002043558f, 0.000363751f,
|
||||
0.998462946f,-0.024291684f, 0.007682189f,-0.002183039f, 0.000388037f,
|
||||
0.998251297f,-0.025841443f, 0.008174516f,-0.002321643f, 0.000412099f,
|
||||
0.998026026f,-0.027382413f, 0.008664303f,-0.002459367f, 0.000435935f,
|
||||
0.997787138f,-0.028914579f, 0.009151540f,-0.002596209f, 0.000459547f,
|
||||
0.997534641f,-0.030437930f, 0.009636217f,-0.002732167f, 0.000482934f,
|
||||
0.997268542f,-0.031952453f, 0.010118324f,-0.002867240f, 0.000506097f,
|
||||
0.996988847f,-0.033458137f, 0.010597850f,-0.003001425f, 0.000529036f,
|
||||
0.996695563f,-0.034954970f, 0.011074786f,-0.003134721f, 0.000551752f,
|
||||
0.996388700f,-0.036442941f, 0.011549123f,-0.003267125f, 0.000574244f,
|
||||
0.996068266f,-0.037922039f, 0.012020851f,-0.003398637f, 0.000596512f,
|
||||
0.995734268f,-0.039392253f, 0.012489961f,-0.003529253f, 0.000618558f,
|
||||
0.995386717f,-0.040853574f, 0.012956443f,-0.003658973f, 0.000640382f,
|
||||
0.995025621f,-0.042305990f, 0.013420290f,-0.003787796f, 0.000661984f,
|
||||
0.994650990f,-0.043749493f, 0.013881491f,-0.003915718f, 0.000683363f,
|
||||
0.994262835f,-0.045184072f, 0.014340039f,-0.004042740f, 0.000704522f,
|
||||
0.993861166f,-0.046609719f, 0.014795924f,-0.004168860f, 0.000725459f,
|
||||
0.993445994f,-0.048026424f, 0.015249139f,-0.004294075f, 0.000746176f,
|
||||
0.993017331f,-0.049434180f, 0.015699676f,-0.004418386f, 0.000766672f,
|
||||
0.992575187f,-0.050832978f, 0.016147525f,-0.004541790f, 0.000786949f,
|
||||
0.992119574f,-0.052222809f, 0.016592680f,-0.004664287f, 0.000807006f,
|
||||
0.991650506f,-0.053603666f, 0.017035133f,-0.004785875f, 0.000826844f,
|
||||
0.991167995f,-0.054975543f, 0.017474875f,-0.004906553f, 0.000846464f,
|
||||
0.990672054f,-0.056338431f, 0.017911900f,-0.005026320f, 0.000865865f,
|
||||
0.990162696f,-0.057692323f, 0.018346201f,-0.005145175f, 0.000885049f,
|
||||
0.989639935f,-0.059037214f, 0.018777770f,-0.005263117f, 0.000904016f,
|
||||
0.989103786f,-0.060373097f, 0.019206599f,-0.005380146f, 0.000922766f,
|
||||
0.988554262f,-0.061699966f, 0.019632684f,-0.005496260f, 0.000941300f,
|
||||
0.987991380f,-0.063017815f, 0.020056015f,-0.005611458f, 0.000959619f,
|
||||
0.987415153f,-0.064326639f, 0.020476588f,-0.005725741f, 0.000977722f,
|
||||
0.986825598f,-0.065626433f, 0.020894396f,-0.005839106f, 0.000995611f,
|
||||
0.986222730f,-0.066917192f, 0.021309432f,-0.005951554f, 0.001013285f,
|
||||
0.985606567f,-0.068198912f, 0.021721690f,-0.006063084f, 0.001030746f,
|
||||
0.984977124f,-0.069471588f, 0.022131165f,-0.006173695f, 0.001047994f,
|
||||
0.984334418f,-0.070735217f, 0.022537850f,-0.006283387f, 0.001065030f,
|
||||
0.983678468f,-0.071989794f, 0.022941741f,-0.006392159f, 0.001081853f,
|
||||
0.983009290f,-0.073235317f, 0.023342830f,-0.006500011f, 0.001098466f,
|
||||
0.982326903f,-0.074471782f, 0.023741114f,-0.006606943f, 0.001114868f,
|
||||
0.981631326f,-0.075699186f, 0.024136587f,-0.006712954f, 0.001131059f,
|
||||
0.980922577f,-0.076917527f, 0.024529243f,-0.006818044f, 0.001147042f,
|
||||
0.980200675f,-0.078126804f, 0.024919078f,-0.006922213f, 0.001162815f,
|
||||
0.979465640f,-0.079327013f, 0.025306087f,-0.007025460f, 0.001178380f,
|
||||
0.978717491f,-0.080518153f, 0.025690266f,-0.007127786f, 0.001193738f,
|
||||
0.977956250f,-0.081700223f, 0.026071609f,-0.007229191f, 0.001208889f,
|
||||
0.977181936f,-0.082873221f, 0.026450113f,-0.007329674f, 0.001223833f,
|
||||
0.976394570f,-0.084037148f, 0.026825773f,-0.007429235f, 0.001238572f,
|
||||
0.975594175f,-0.085192002f, 0.027198586f,-0.007527875f, 0.001253106f,
|
||||
0.974780770f,-0.086337783f, 0.027568547f,-0.007625593f, 0.001267436f,
|
||||
0.973954379f,-0.087474491f, 0.027935652f,-0.007722391f, 0.001281562f,
|
||||
0.973115024f,-0.088602126f, 0.028299898f,-0.007818267f, 0.001295485f,
|
||||
0.972262727f,-0.089720690f, 0.028661282f,-0.007913223f, 0.001309207f,
|
||||
0.971397512f,-0.090830182f, 0.029019799f,-0.008007258f, 0.001322726f,
|
||||
0.970519401f,-0.091930604f, 0.029375448f,-0.008100373f, 0.001336045f,
|
||||
0.969628418f,-0.093021958f, 0.029728224f,-0.008192568f, 0.001349164f,
|
||||
0.968724588f,-0.094104245f, 0.030078125f,-0.008283845f, 0.001362084f,
|
||||
0.967807935f,-0.095177467f, 0.030425148f,-0.008374202f, 0.001374806f,
|
||||
0.966878483f,-0.096241627f, 0.030769290f,-0.008463642f, 0.001387329f,
|
||||
0.965936258f,-0.097296726f, 0.031110550f,-0.008552163f, 0.001399656f,
|
||||
0.964981285f,-0.098342768f, 0.031448923f,-0.008639768f, 0.001411786f,
|
||||
0.964013590f,-0.099379756f, 0.031784409f,-0.008726456f, 0.001423721f,
|
||||
0.963033199f,-0.100407693f, 0.032117005f,-0.008812229f, 0.001435461f,
|
||||
0.962040138f,-0.101426582f, 0.032446709f,-0.008897086f, 0.001447008f,
|
||||
0.961034434f,-0.102436428f, 0.032773519f,-0.008981030f, 0.001458361f,
|
||||
0.960016114f,-0.103437235f, 0.033097434f,-0.009064060f, 0.001469522f,
|
||||
0.958985206f,-0.104429007f, 0.033418451f,-0.009146178f, 0.001480492f,
|
||||
0.957941737f,-0.105411749f, 0.033736571f,-0.009227385f, 0.001491271f,
|
||||
0.956885736f,-0.106385466f, 0.034051790f,-0.009307680f, 0.001501860f,
|
||||
0.955817231f,-0.107350163f, 0.034364109f,-0.009387067f, 0.001512261f,
|
||||
0.954736250f,-0.108305845f, 0.034673526f,-0.009465545f, 0.001522473f,
|
||||
0.953642823f,-0.109252518f, 0.034980040f,-0.009543115f, 0.001532497f,
|
||||
0.952536979f,-0.110190189f, 0.035283651f,-0.009619779f, 0.001542336f,
|
||||
0.951418748f,-0.111118864f, 0.035584357f,-0.009695538f, 0.001551988f,
|
||||
0.950288160f,-0.112038548f, 0.035882158f,-0.009770393f, 0.001561456f,
|
||||
0.949145245f,-0.112949250f, 0.036177055f,-0.009844346f, 0.001570741f,
|
||||
0.947990034f,-0.113850976f, 0.036469046f,-0.009917397f, 0.001579842f,
|
||||
0.946822559f,-0.114743733f, 0.036758132f,-0.009989548f, 0.001588761f,
|
||||
0.945642850f,-0.115627529f, 0.037044312f,-0.010060800f, 0.001597498f,
|
||||
0.944450939f,-0.116502372f, 0.037327588f,-0.010131156f, 0.001606056f,
|
||||
0.943246858f,-0.117368270f, 0.037607958f,-0.010200615f, 0.001614434f,
|
||||
0.942030639f,-0.118225231f, 0.037885424f,-0.010269180f, 0.001622633f,
|
||||
0.940802316f,-0.119073264f, 0.038159985f,-0.010336852f, 0.001630655f,
|
||||
0.939561921f,-0.119912378f, 0.038431644f,-0.010403633f, 0.001638500f,
|
||||
0.938309487f,-0.120742582f, 0.038700400f,-0.010469524f, 0.001646169f,
|
||||
0.937045048f,-0.121563886f, 0.038966254f,-0.010534527f, 0.001653663f,
|
||||
0.935768638f,-0.122376299f, 0.039229208f,-0.010598644f, 0.001660984f,
|
||||
0.934480291f,-0.123179830f, 0.039489262f,-0.010661876f, 0.001668131f,
|
||||
0.933180042f,-0.123974491f, 0.039746418f,-0.010724225f, 0.001675106f,
|
||||
0.931867925f,-0.124760291f, 0.040000678f,-0.010785693f, 0.001681910f,
|
||||
0.930543975f,-0.125537242f, 0.040252042f,-0.010846282f, 0.001688544f,
|
||||
0.929208228f,-0.126305353f, 0.040500513f,-0.010905994f, 0.001695008f,
|
||||
0.927860720f,-0.127064637f, 0.040746092f,-0.010964829f, 0.001701305f,
|
||||
0.926501487f,-0.127815104f, 0.040988782f,-0.011022792f, 0.001707433f,
|
||||
0.925130565f,-0.128556767f, 0.041228583f,-0.011079882f, 0.001713396f,
|
||||
0.923747991f,-0.129289637f, 0.041465499f,-0.011136103f, 0.001719193f,
|
||||
0.922353802f,-0.130013726f, 0.041699532f,-0.011191456f, 0.001724825f,
|
||||
0.920948034f,-0.130729047f, 0.041930683f,-0.011245944f, 0.001730294f,
|
||||
0.919530726f,-0.131435613f, 0.042158956f,-0.011299568f, 0.001735601f,
|
||||
0.918101916f,-0.132133435f, 0.042384354f,-0.011352330f, 0.001740746f,
|
||||
0.916661641f,-0.132822528f, 0.042606878f,-0.011404234f, 0.001745730f,
|
||||
0.915209940f,-0.133502905f, 0.042826532f,-0.011455280f, 0.001750555f,
|
||||
0.913746852f,-0.134174578f, 0.043043318f,-0.011505472f, 0.001755221f,
|
||||
0.912272416f,-0.134837563f, 0.043257241f,-0.011554812f, 0.001759730f,
|
||||
0.910786671f,-0.135491873f, 0.043468303f,-0.011603301f, 0.001764082f,
|
||||
0.909289657f,-0.136137522f, 0.043676506f,-0.011650942f, 0.001768278f,
|
||||
0.907781413f,-0.136774525f, 0.043881856f,-0.011697738f, 0.001772320f,
|
||||
0.906261980f,-0.137402897f, 0.044084355f,-0.011743690f, 0.001776208f,
|
||||
0.904731398f,-0.138022653f, 0.044284007f,-0.011788802f, 0.001779944f,
|
||||
0.903189708f,-0.138633807f, 0.044480816f,-0.011833076f, 0.001783528f,
|
||||
0.901636952f,-0.139236376f, 0.044674785f,-0.011876514f, 0.001786962f,
|
||||
0.900073170f,-0.139830375f, 0.044865920f,-0.011919118f, 0.001790246f,
|
||||
0.898498403f,-0.140415819f, 0.045054222f,-0.011960892f, 0.001793381f,
|
||||
0.896912695f,-0.140992726f, 0.045239698f,-0.012001838f, 0.001796369f,
|
||||
0.895316086f,-0.141561111f, 0.045422352f,-0.012041958f, 0.001799211f,
|
||||
0.893708620f,-0.142120991f, 0.045602186f,-0.012081256f, 0.001801907f,
|
||||
0.892090339f,-0.142672383f, 0.045779208f,-0.012119733f, 0.001804459f,
|
||||
0.890461286f,-0.143215304f, 0.045953420f,-0.012157393f, 0.001806868f,
|
||||
0.888821505f,-0.143749771f, 0.046124828f,-0.012194238f, 0.001809134f,
|
||||
0.887171038f,-0.144275802f, 0.046293436f,-0.012230270f, 0.001811259f,
|
||||
0.885509930f,-0.144793414f, 0.046459250f,-0.012265494f, 0.001813243f,
|
||||
0.883838224f,-0.145302625f, 0.046622274f,-0.012299911f, 0.001815089f,
|
||||
0.882155965f,-0.145803453f, 0.046782515f,-0.012333524f, 0.001816796f,
|
||||
0.880463198f,-0.146295917f, 0.046939976f,-0.012366337f, 0.001818366f,
|
||||
0.878759967f,-0.146780035f, 0.047094664f,-0.012398351f, 0.001819800f,
|
||||
0.877046317f,-0.147255826f, 0.047246583f,-0.012429571f, 0.001821099f,
|
||||
0.875322295f,-0.147723309f, 0.047395741f,-0.012459998f, 0.001822264f,
|
||||
0.873587944f,-0.148182503f, 0.047542141f,-0.012489637f, 0.001823295f,
|
||||
0.871843312f,-0.148633428f, 0.047685790f,-0.012518489f, 0.001824196f,
|
||||
0.870088444f,-0.149076103f, 0.047826695f,-0.012546558f, 0.001824965f,
|
||||
0.868323386f,-0.149510548f, 0.047964860f,-0.012573847f, 0.001825604f,
|
||||
0.866548186f,-0.149936783f, 0.048100292f,-0.012600359f, 0.001826115f,
|
||||
0.864762890f,-0.150354828f, 0.048232997f,-0.012626097f, 0.001826498f,
|
||||
0.862967545f,-0.150764704f, 0.048362981f,-0.012651064f, 0.001826754f,
|
||||
0.861162199f,-0.151166432f, 0.048490252f,-0.012675264f, 0.001826885f,
|
||||
0.859346899f,-0.151560031f, 0.048614814f,-0.012698699f, 0.001826891f,
|
||||
0.857521693f,-0.151945524f, 0.048736676f,-0.012721373f, 0.001826774f,
|
||||
0.855686629f,-0.152322931f, 0.048855842f,-0.012743288f, 0.001826534f,
|
||||
0.853841755f,-0.152692274f, 0.048972321f,-0.012764449f, 0.001826173f,
|
||||
0.851987121f,-0.153053574f, 0.049086119f,-0.012784858f, 0.001825691f,
|
||||
0.850122774f,-0.153406854f, 0.049197244f,-0.012804518f, 0.001825091f,
|
||||
0.848248764f,-0.153752135f, 0.049305701f,-0.012823434f, 0.001824372f,
|
||||
0.846365140f,-0.154089440f, 0.049411498f,-0.012841607f, 0.001823536f,
|
||||
0.844471951f,-0.154418791f, 0.049514643f,-0.012859042f, 0.001822584f,
|
||||
0.842569248f,-0.154740210f, 0.049615142f,-0.012875742f, 0.001821517f,
|
||||
0.840657079f,-0.155053721f, 0.049713003f,-0.012891710f, 0.001820336f,
|
||||
0.838735496f,-0.155359346f, 0.049808234f,-0.012906950f, 0.001819042f,
|
||||
0.836804549f,-0.155657108f, 0.049900842f,-0.012921465f, 0.001817636f,
|
||||
0.834864288f,-0.155947032f, 0.049990834f,-0.012935259f, 0.001816120f,
|
||||
0.832914765f,-0.156229140f, 0.050078219f,-0.012948334f, 0.001814493f,
|
||||
0.830956029f,-0.156503456f, 0.050163005f,-0.012960695f, 0.001812758f,
|
||||
0.828988133f,-0.156770004f, 0.050245198f,-0.012972345f, 0.001810916f,
|
||||
0.827011128f,-0.157028808f, 0.050324808f,-0.012983287f, 0.001808967f,
|
||||
0.825025066f,-0.157279893f, 0.050401842f,-0.012993525f, 0.001806912f,
|
||||
0.823029998f,-0.157523282f, 0.050476308f,-0.013003063f, 0.001804753f,
|
||||
0.821025977f,-0.157759001f, 0.050548215f,-0.013011904f, 0.001802491f,
|
||||
0.819013055f,-0.157987074f, 0.050617571f,-0.013020051f, 0.001800126f,
|
||||
0.816991284f,-0.158207526f, 0.050684384f,-0.013027509f, 0.001797660f,
|
||||
0.814960718f,-0.158420382f, 0.050748664f,-0.013034280f, 0.001795094f,
|
||||
0.812921409f,-0.158625668f, 0.050810417f,-0.013040370f, 0.001792428f,
|
||||
0.810873410f,-0.158823410f, 0.050869654f,-0.013045780f, 0.001789664f,
|
||||
0.808816775f,-0.159013631f, 0.050926382f,-0.013050515f, 0.001786804f,
|
||||
0.806751557f,-0.159196360f, 0.050980610f,-0.013054579f, 0.001783847f,
|
||||
0.804677811f,-0.159371620f, 0.051032348f,-0.013057974f, 0.001780795f,
|
||||
0.802595589f,-0.159539440f, 0.051081605f,-0.013060706f, 0.001777649f,
|
||||
0.800504946f,-0.159699844f, 0.051128389f,-0.013062778f, 0.001774411f,
|
||||
0.798405936f,-0.159852860f, 0.051172709f,-0.013064192f, 0.001771080f,
|
||||
0.796298614f,-0.159998514f, 0.051214574f,-0.013064954f, 0.001767659f,
|
||||
0.794183034f,-0.160136832f, 0.051253995f,-0.013065067f, 0.001764147f,
|
||||
0.792059252f,-0.160267843f, 0.051290979f,-0.013064535f, 0.001760547f,
|
||||
0.789927322f,-0.160391572f, 0.051325537f,-0.013063361f, 0.001756860f,
|
||||
0.787787300f,-0.160508047f, 0.051357678f,-0.013061549f, 0.001753085f,
|
||||
0.785639241f,-0.160617296f, 0.051387412f,-0.013059104f, 0.001749225f,
|
||||
0.783483200f,-0.160719346f, 0.051414747f,-0.013056029f, 0.001745280f,
|
||||
0.781319234f,-0.160814225f, 0.051439694f,-0.013052327f, 0.001741252f,
|
||||
0.779147398f,-0.160901960f, 0.051462263f,-0.013048003f, 0.001737141f,
|
||||
0.776967749f,-0.160982580f, 0.051482462f,-0.013043061f, 0.001732948f,
|
||||
0.774780342f,-0.161056113f, 0.051500303f,-0.013037504f, 0.001728675f,
|
||||
0.772585234f,-0.161122587f, 0.051515795f,-0.013031336f, 0.001724323f,
|
||||
0.770382481f,-0.161182031f, 0.051528947f,-0.013024562f, 0.001719892f,
|
||||
0.768172142f,-0.161234473f, 0.051539771f,-0.013017185f, 0.001715383f,
|
||||
0.765954271f,-0.161279942f, 0.051548275f,-0.013009209f, 0.001710798f,
|
||||
0.763728927f,-0.161318466f, 0.051554471f,-0.013000638f, 0.001706137f,
|
||||
0.761496167f,-0.161350075f, 0.051558368f,-0.012991476f, 0.001701402f,
|
||||
0.759256048f,-0.161374798f, 0.051559977f,-0.012981727f, 0.001696593f,
|
||||
0.757008627f,-0.161392665f, 0.051559309f,-0.012971395f, 0.001691712f,
|
||||
0.754753963f,-0.161403704f, 0.051556372f,-0.012960484f, 0.001686760f,
|
||||
0.752492113f,-0.161407945f, 0.051551179f,-0.012948997f, 0.001681737f,
|
||||
0.750223135f,-0.161405418f, 0.051543739f,-0.012936940f, 0.001676644f,
|
||||
0.747947088f,-0.161396153f, 0.051534064f,-0.012924315f, 0.001671483f,
|
||||
0.745664029f,-0.161380179f, 0.051522163f,-0.012911128f, 0.001666254f,
|
||||
0.743374018f,-0.161357527f, 0.051508048f,-0.012897381f, 0.001660959f,
|
||||
0.741077112f,-0.161328227f, 0.051491729f,-0.012883079f, 0.001655598f,
|
||||
0.738773370f,-0.161292309f, 0.051473217f,-0.012868227f, 0.001650173f,
|
||||
0.736462852f,-0.161249804f, 0.051452522f,-0.012852827f, 0.001644684f,
|
||||
0.734145616f,-0.161200742f, 0.051429657f,-0.012836884f, 0.001639133f,
|
||||
0.731821721f,-0.161145154f, 0.051404631f,-0.012820403f, 0.001633520f,
|
||||
0.729491227f,-0.161083070f, 0.051377457f,-0.012803386f, 0.001627846f,
|
||||
0.727154193f,-0.161014523f, 0.051348144f,-0.012785839f, 0.001622112f,
|
||||
0.724810678f,-0.160939542f, 0.051316704f,-0.012767765f, 0.001616320f,
|
||||
0.722460743f,-0.160858158f, 0.051283148f,-0.012749169f, 0.001610470f,
|
||||
0.720104446f,-0.160770404f, 0.051247488f,-0.012730054f, 0.001604563f,
|
||||
0.717741848f,-0.160676310f, 0.051209734f,-0.012710425f, 0.001598600f,
|
||||
0.715373009f,-0.160575909f, 0.051169898f,-0.012690285f, 0.001592582f,
|
||||
0.712997988f,-0.160469230f, 0.051127991f,-0.012669640f, 0.001586510f,
|
||||
0.710616847f,-0.160356307f, 0.051084025f,-0.012648492f, 0.001580385f,
|
||||
0.708229645f,-0.160237171f, 0.051038012f,-0.012626846f, 0.001574207f,
|
||||
0.705836443f,-0.160111854f, 0.050989962f,-0.012604706f, 0.001567979f,
|
||||
0.703437301f,-0.159980389f, 0.050939887f,-0.012582077f, 0.001561700f,
|
||||
0.701032280f,-0.159842806f, 0.050887799f,-0.012558961f, 0.001555372f,
|
||||
0.698621441f,-0.159699138f, 0.050833709f,-0.012535365f, 0.001548995f,
|
||||
0.696204845f,-0.159549419f, 0.050777630f,-0.012511290f, 0.001542571f,
|
||||
0.693782552f,-0.159393679f, 0.050719572f,-0.012486743f, 0.001536101f,
|
||||
0.691354624f,-0.159231952f, 0.050659547f,-0.012461726f, 0.001529584f,
|
||||
0.688921121f,-0.159064270f, 0.050597568f,-0.012436245f, 0.001523023f,
|
||||
0.686482106f,-0.158890666f, 0.050533646f,-0.012410302f, 0.001516417f,
|
||||
0.684037639f,-0.158711173f, 0.050467793f,-0.012383903f, 0.001509769f,
|
||||
0.681587783f,-0.158525823f, 0.050400021f,-0.012357051f, 0.001503079f,
|
||||
0.679132597f,-0.158334650f, 0.050330342f,-0.012329751f, 0.001496347f,
|
||||
0.676672145f,-0.158137687f, 0.050258767f,-0.012302006f, 0.001489575f,
|
||||
0.674206487f,-0.157934966f, 0.050185310f,-0.012273821f, 0.001482764f,
|
||||
0.671735685f,-0.157726522f, 0.050109981f,-0.012245200f, 0.001475914f,
|
||||
0.669259802f,-0.157512387f, 0.050032793f,-0.012216147f, 0.001469026f,
|
||||
0.666778900f,-0.157292594f, 0.049953758f,-0.012186667f, 0.001462101f,
|
||||
0.664293039f,-0.157067178f, 0.049872888f,-0.012156762f, 0.001455141f,
|
||||
0.661802283f,-0.156836172f, 0.049790195f,-0.012126439f, 0.001448145f,
|
||||
0.659306693f,-0.156599609f, 0.049705692f,-0.012095699f, 0.001441115f,
|
||||
0.656806333f,-0.156357523f, 0.049619391f,-0.012064549f, 0.001434051f,
|
||||
0.654301263f,-0.156109948f, 0.049531303f,-0.012032992f, 0.001426955f,
|
||||
0.651791546f,-0.155856918f, 0.049441442f,-0.012001031f, 0.001419827f,
|
||||
0.649277246f,-0.155598467f, 0.049349819f,-0.011968672f, 0.001412668f,
|
||||
0.646758423f,-0.155334628f, 0.049256448f,-0.011935918f, 0.001405479f,
|
||||
0.644235142f,-0.155065436f, 0.049161340f,-0.011902774f, 0.001398261f,
|
||||
0.641707464f,-0.154790925f, 0.049064507f,-0.011869243f, 0.001391015f,
|
||||
0.639175452f,-0.154511129f, 0.048965963f,-0.011835330f, 0.001383741f,
|
||||
0.636639169f,-0.154226083f, 0.048865719f,-0.011801038f, 0.001376440f,
|
||||
0.634098677f,-0.153935820f, 0.048763788f,-0.011766373f, 0.001369113f,
|
||||
0.631554040f,-0.153640376f, 0.048660183f,-0.011731337f, 0.001361761f,
|
||||
0.629005320f,-0.153339784f, 0.048554915f,-0.011695936f, 0.001354384f,
|
||||
0.626452580f,-0.153034079f, 0.048447999f,-0.011660173f, 0.001346984f,
|
||||
0.623895883f,-0.152723296f, 0.048339445f,-0.011624053f, 0.001339561f,
|
||||
0.621335293f,-0.152407470f, 0.048229267f,-0.011587579f, 0.001332116f,
|
||||
0.618770871f,-0.152086634f, 0.048117478f,-0.011550756f, 0.001324650f,
|
||||
0.616202682f,-0.151760824f, 0.048004090f,-0.011513587f, 0.001317164f,
|
||||
0.613630788f,-0.151430075f, 0.047889115f,-0.011476077f, 0.001309657f,
|
||||
0.611055252f,-0.151094421f, 0.047772566f,-0.011438230f, 0.001302132f,
|
||||
0.608476138f,-0.150753897f, 0.047654456f,-0.011400050f, 0.001294589f,
|
||||
0.605893509f,-0.150408538f, 0.047534798f,-0.011361541f, 0.001287028f,
|
||||
0.603307427f,-0.150058380f, 0.047413604f,-0.011322706f, 0.001279451f,
|
||||
0.600717957f,-0.149703457f, 0.047290887f,-0.011283552f, 0.001271857f,
|
||||
0.598125161f,-0.149343804f, 0.047166660f,-0.011244080f, 0.001264249f,
|
||||
0.595529103f,-0.148979456f, 0.047040936f,-0.011204296f, 0.001256626f,
|
||||
0.592929846f,-0.148610450f, 0.046913726f,-0.011164202f, 0.001248990f,
|
||||
0.590327454f,-0.148236818f, 0.046785045f,-0.011123805f, 0.001241340f,
|
||||
0.587721989f,-0.147858598f, 0.046654904f,-0.011083106f, 0.001233679f,
|
||||
0.585113515f,-0.147475824f, 0.046523317f,-0.011042111f, 0.001226005f,
|
||||
0.582502096f,-0.147088532f, 0.046390297f,-0.011000824f, 0.001218321f,
|
||||
0.579887795f,-0.146696757f, 0.046255856f,-0.010959248f, 0.001210627f,
|
||||
0.577270674f,-0.146300533f, 0.046120007f,-0.010917387f, 0.001202924f,
|
||||
0.574650799f,-0.145899898f, 0.045982762f,-0.010875246f, 0.001195211f,
|
||||
0.572028231f,-0.145494885f, 0.045844136f,-0.010832828f, 0.001187491f,
|
||||
0.569403034f,-0.145085532f, 0.045704140f,-0.010790137f, 0.001179764f,
|
||||
0.566775272f,-0.144671872f, 0.045562787f,-0.010747178f, 0.001172030f,
|
||||
0.564145009f,-0.144253941f, 0.045420091f,-0.010703955f, 0.001164289f,
|
||||
0.561512306f,-0.143831776f, 0.045276064f,-0.010660470f, 0.001156544f,
|
||||
0.558877229f,-0.143405412f, 0.045130719f,-0.010616729f, 0.001148794f,
|
||||
0.556239839f,-0.142974883f, 0.044984069f,-0.010572735f, 0.001141040f,
|
||||
0.553600201f,-0.142540227f, 0.044836126f,-0.010528493f, 0.001133283f,
|
||||
0.550958378f,-0.142101478f, 0.044686904f,-0.010484005f, 0.001125523f,
|
||||
0.548314433f,-0.141658672f, 0.044536416f,-0.010439276f, 0.001117761f,
|
||||
0.545668429f,-0.141211844f, 0.044384675f,-0.010394310f, 0.001109997f,
|
||||
0.543020430f,-0.140761032f, 0.044231692f,-0.010349111f, 0.001102233f,
|
||||
0.540370499f,-0.140306269f, 0.044077482f,-0.010303683f, 0.001094469f,
|
||||
0.537718699f,-0.139847592f, 0.043922057f,-0.010258029f, 0.001086705f,
|
||||
0.535065094f,-0.139385036f, 0.043765430f,-0.010212153f, 0.001078942f,
|
||||
0.532409746f,-0.138918637f, 0.043607614f,-0.010166060f, 0.001071182f,
|
||||
0.529752718f,-0.138448432f, 0.043448622f,-0.010119753f, 0.001063423f,
|
||||
0.527094075f,-0.137974455f, 0.043288466f,-0.010073235f, 0.001055668f,
|
||||
0.524433878f,-0.137496742f, 0.043127159f,-0.010026512f, 0.001047916f,
|
||||
0.521772191f,-0.137015329f, 0.042964716f,-0.009979586f, 0.001040168f,
|
||||
0.519109077f,-0.136530252f, 0.042801147f,-0.009932461f, 0.001032425f,
|
||||
0.516444599f,-0.136041547f, 0.042636467f,-0.009885141f, 0.001024687f,
|
||||
0.513778819f,-0.135549248f, 0.042470687f,-0.009837631f, 0.001016955f,
|
||||
0.511111801f,-0.135053393f, 0.042303821f,-0.009789933f, 0.001009230f,
|
||||
0.508443608f,-0.134554017f, 0.042135882f,-0.009742051f, 0.001001511f,
|
||||
0.505774302f,-0.134051154f, 0.041966883f,-0.009693990f, 0.000993800f,
|
||||
0.503103946f,-0.133544842f, 0.041796836f,-0.009645752f, 0.000986098f,
|
||||
0.500432602f,-0.133035116f, 0.041625754f,-0.009597342f, 0.000978404f,
|
||||
0.497760334f,-0.132522012f, 0.041453650f,-0.009548764f, 0.000970719f,
|
||||
0.495087203f,-0.132005564f, 0.041280536f,-0.009500020f, 0.000963044f,
|
||||
0.492413273f,-0.131485810f, 0.041106427f,-0.009451116f, 0.000955380f,
|
||||
0.489738606f,-0.130962784f, 0.040931333f,-0.009402053f, 0.000947726f,
|
||||
0.487063264f,-0.130436523f, 0.040755269f,-0.009352837f, 0.000940083f,
|
||||
0.484387309f,-0.129907061f, 0.040578247f,-0.009303470f, 0.000932453f,
|
||||
0.481710804f,-0.129374435f, 0.040400279f,-0.009253957f, 0.000924835f,
|
||||
0.479033811f,-0.128838680f, 0.040221378f,-0.009204300f, 0.000917229f,
|
||||
0.476356392f,-0.128299831f, 0.040041558f,-0.009154505f, 0.000909637f,
|
||||
0.473678610f,-0.127757925f, 0.039860830f,-0.009104573f, 0.000902059f,
|
||||
0.471000525f,-0.127212997f, 0.039679208f,-0.009054509f, 0.000894496f,
|
||||
0.468322201f,-0.126665082f, 0.039496704f,-0.009004316f, 0.000886947f,
|
||||
0.465643698f,-0.126114216f, 0.039313331f,-0.008953998f, 0.000879414f,
|
||||
0.462965079f,-0.125560435f, 0.039129101f,-0.008903558f, 0.000871896f,
|
||||
0.460286405f,-0.125003773f, 0.038944027f,-0.008853000f, 0.000864395f,
|
||||
0.457607738f,-0.124444266f, 0.038758122f,-0.008802328f, 0.000856910f,
|
||||
0.454929140f,-0.123881950f, 0.038571399f,-0.008751544f, 0.000849443f,
|
||||
0.452250671f,-0.123316860f, 0.038383869f,-0.008700653f, 0.000841994f,
|
||||
0.449572393f,-0.122749031f, 0.038195545f,-0.008649657f, 0.000834562f,
|
||||
0.446894368f,-0.122178499f, 0.038006440f,-0.008598561f, 0.000827149f,
|
||||
0.444216656f,-0.121605299f, 0.037816567f,-0.008547367f, 0.000819755f,
|
||||
0.441539319f,-0.121029465f, 0.037625937f,-0.008496080f, 0.000812381f,
|
||||
0.438862417f,-0.120451035f, 0.037434564f,-0.008444702f, 0.000805026f,
|
||||
0.436186011f,-0.119870041f, 0.037242460f,-0.008393237f, 0.000797692f,
|
||||
0.433510163f,-0.119286520f, 0.037049637f,-0.008341688f, 0.000790378f,
|
||||
0.430834932f,-0.118700507f, 0.036856108f,-0.008290058f, 0.000783086f,
|
||||
0.428160380f,-0.118112036f, 0.036661885f,-0.008238352f, 0.000775815f,
|
||||
0.425486566f,-0.117521144f, 0.036466980f,-0.008186572f, 0.000768565f,
|
||||
0.422813551f,-0.116927863f, 0.036271406f,-0.008134722f, 0.000761339f,
|
||||
0.420141396f,-0.116332231f, 0.036075174f,-0.008082804f, 0.000754135f,
|
||||
0.417470160f,-0.115734281f, 0.035878299f,-0.008030823f, 0.000746954f,
|
||||
0.414799903f,-0.115134048f, 0.035680790f,-0.007978781f, 0.000739796f,
|
||||
0.412130686f,-0.114531567f, 0.035482662f,-0.007926681f, 0.000732662f,
|
||||
0.409462568f,-0.113926872f, 0.035283926f,-0.007874528f, 0.000725553f,
|
||||
0.406795609f,-0.113320000f, 0.035084593f,-0.007822324f, 0.000718468f,
|
||||
0.404129868f,-0.112710983f, 0.034884678f,-0.007770072f, 0.000711408f,
|
||||
0.401465405f,-0.112099857f, 0.034684190f,-0.007717775f, 0.000704373f,
|
||||
0.398802279f,-0.111486655f, 0.034483143f,-0.007665437f, 0.000697364f,
|
||||
0.396140548f,-0.110871414f, 0.034281549f,-0.007613061f, 0.000690381f,
|
||||
0.393480274f,-0.110254166f, 0.034079419f,-0.007560650f, 0.000683424f,
|
||||
0.390821513f,-0.109634947f, 0.033876767f,-0.007508207f, 0.000676494f,
|
||||
0.388164325f,-0.109013790f, 0.033673602f,-0.007455735f, 0.000669590f,
|
||||
0.385508769f,-0.108390730f, 0.033469939f,-0.007403238f, 0.000662714f,
|
||||
0.382854903f,-0.107765801f, 0.033265787f,-0.007350717f, 0.000655866f,
|
||||
0.380202785f,-0.107139037f, 0.033061161f,-0.007298177f, 0.000649046f,
|
||||
0.377552474f,-0.106510472f, 0.032856070f,-0.007245621f, 0.000642253f,
|
||||
0.374904028f,-0.105880140f, 0.032650528f,-0.007193050f, 0.000635490f,
|
||||
0.372257505f,-0.105248075f, 0.032444545f,-0.007140469f, 0.000628755f,
|
||||
0.369612962f,-0.104614312f, 0.032238134f,-0.007087881f, 0.000622049f,
|
||||
0.366970458f,-0.103978882f, 0.032031306f,-0.007035287f, 0.000615373f,
|
||||
0.364330050f,-0.103341821f, 0.031824073f,-0.006982692f, 0.000608726f,
|
||||
0.361691794f,-0.102703162f, 0.031616447f,-0.006930098f, 0.000602110f,
|
||||
0.359055750f,-0.102062939f, 0.031408439f,-0.006877508f, 0.000595523f,
|
||||
0.356421972f,-0.101421184f, 0.031200061f,-0.006824926f, 0.000588967f,
|
||||
0.353790520f,-0.100777932f, 0.030991325f,-0.006772353f, 0.000582442f,
|
||||
0.351161448f,-0.100133216f, 0.030782241f,-0.006719792f, 0.000575948f,
|
||||
0.348534815f,-0.099487069f, 0.030572821f,-0.006667247f, 0.000569485f,
|
||||
0.345910675f,-0.098839524f, 0.030363078f,-0.006614721f, 0.000563054f,
|
||||
0.343289087f,-0.098190615f, 0.030153021f,-0.006562215f, 0.000556654f,
|
||||
0.340670105f,-0.097540374f, 0.029942663f,-0.006509734f, 0.000550287f,
|
||||
0.338053786f,-0.096888834f, 0.029732015f,-0.006457279f, 0.000543951f,
|
||||
0.335440186f,-0.096236029f, 0.029521089f,-0.006404853f, 0.000537649f,
|
||||
0.332829360f,-0.095581991f, 0.029309894f,-0.006352460f, 0.000531378f,
|
||||
0.330221364f,-0.094926753f, 0.029098443f,-0.006300101f, 0.000525141f,
|
||||
0.327616253f,-0.094270347f, 0.028886747f,-0.006247780f, 0.000518937f,
|
||||
0.325014082f,-0.093612807f, 0.028674817f,-0.006195500f, 0.000512766f,
|
||||
0.322414906f,-0.092954163f, 0.028462663f,-0.006143262f, 0.000506629f,
|
||||
0.319818781f,-0.092294450f, 0.028250298f,-0.006091069f, 0.000500525f,
|
||||
0.317225760f,-0.091633699f, 0.028037732f,-0.006038925f, 0.000494456f,
|
||||
0.314635898f,-0.090971943f, 0.027824976f,-0.005986831f, 0.000488420f,
|
||||
0.312049250f,-0.090309213f, 0.027612040f,-0.005934790f, 0.000482419f,
|
||||
0.309465869f,-0.089645542f, 0.027398936f,-0.005882806f, 0.000476453f,
|
||||
0.306885810f,-0.088980961f, 0.027185675f,-0.005830879f, 0.000470521f,
|
||||
0.304309126f,-0.088315503f, 0.026972268f,-0.005779014f, 0.000464623f,
|
||||
0.301735870f,-0.087649199f, 0.026758724f,-0.005727211f, 0.000458761f,
|
||||
0.299166097f,-0.086982081f, 0.026545055f,-0.005675475f, 0.000452934f,
|
||||
0.296599859f,-0.086314180f, 0.026331272f,-0.005623807f, 0.000447143f,
|
||||
0.294037209f,-0.085645528f, 0.026117385f,-0.005572209f, 0.000441387f,
|
||||
0.291478200f,-0.084976157f, 0.025903405f,-0.005520685f, 0.000435666f,
|
||||
0.288922885f,-0.084306097f, 0.025689341f,-0.005469237f, 0.000429981f,
|
||||
0.286371316f,-0.083635380f, 0.025475206f,-0.005417866f, 0.000424332f,
|
||||
0.283823545f,-0.082964036f, 0.025261008f,-0.005366575f, 0.000418720f,
|
||||
0.281279624f,-0.082292098f, 0.025046759f,-0.005315368f, 0.000413143f,
|
||||
0.278739606f,-0.081619595f, 0.024832469f,-0.005264245f, 0.000407603f,
|
||||
0.276203542f,-0.080946559f, 0.024618148f,-0.005213209f, 0.000402099f,
|
||||
0.273671482f,-0.080273020f, 0.024403807f,-0.005162263f, 0.000396632f,
|
||||
0.271143479f,-0.079599009f, 0.024189455f,-0.005111409f, 0.000391201f,
|
||||
0.268619584f,-0.078924556f, 0.023975103f,-0.005060649f, 0.000385807f,
|
||||
0.266099847f,-0.078249692f, 0.023760760f,-0.005009985f, 0.000380450f,
|
||||
0.263584318f,-0.077574447f, 0.023546438f,-0.004959420f, 0.000375130f,
|
||||
0.261073049f,-0.076898851f, 0.023332145f,-0.004908956f, 0.000369847f,
|
||||
0.258566090f,-0.076222934f, 0.023117893f,-0.004858594f, 0.000364602f,
|
||||
0.256063490f,-0.075546727f, 0.022903690f,-0.004808337f, 0.000359393f,
|
||||
0.253565299f,-0.074870258f, 0.022689547f,-0.004758188f, 0.000354222f,
|
||||
0.251071568f,-0.074193558f, 0.022475473f,-0.004708148f, 0.000349088f,
|
||||
0.248582344f,-0.073516656f, 0.022261479f,-0.004658219f, 0.000343992f,
|
||||
0.246097678f,-0.072839582f, 0.022047574f,-0.004608404f, 0.000338934f,
|
||||
0.243617618f,-0.072162365f, 0.021833767f,-0.004558704f, 0.000333913f,
|
||||
0.241142213f,-0.071485035f, 0.021620069f,-0.004509122f, 0.000328930f,
|
||||
0.238671511f,-0.070807621f, 0.021406488f,-0.004459659f, 0.000323984f,
|
||||
0.236205561f,-0.070130151f, 0.021193035f,-0.004410318f, 0.000319077f,
|
||||
0.233744411f,-0.069452655f, 0.020979718f,-0.004361100f, 0.000314207f,
|
||||
0.231288109f,-0.068775161f, 0.020766548f,-0.004312008f, 0.000309375f,
|
||||
0.228836701f,-0.068097699f, 0.020553533f,-0.004263043f, 0.000304582f,
|
||||
0.226390237f,-0.067420297f, 0.020340683f,-0.004214207f, 0.000299826f,
|
||||
0.223948762f,-0.066742982f, 0.020128008f,-0.004165503f, 0.000295108f,
|
||||
0.221512323f,-0.066065785f, 0.019915515f,-0.004116932f, 0.000290429f,
|
||||
0.219080968f,-0.065388732f, 0.019703215f,-0.004068496f, 0.000285787f,
|
||||
0.216654743f,-0.064711852f, 0.019491117f,-0.004020196f, 0.000281184f,
|
||||
0.214233694f,-0.064035173f, 0.019279229f,-0.003972036f, 0.000276619f,
|
||||
0.211817866f,-0.063358724f, 0.019067561f,-0.003924015f, 0.000272092f,
|
||||
0.209407307f,-0.062682530f, 0.018856122f,-0.003876137f, 0.000267604f,
|
||||
0.207002061f,-0.062006621f, 0.018644920f,-0.003828403f, 0.000263153f,
|
||||
0.204602173f,-0.061331024f, 0.018433965f,-0.003780814f, 0.000258741f,
|
||||
0.202207689f,-0.060655766f, 0.018223264f,-0.003733373f, 0.000254367f,
|
||||
0.199818654f,-0.059980874f, 0.018012827f,-0.003686081f, 0.000250032f,
|
||||
0.197435111f,-0.059306375f, 0.017802663f,-0.003638940f, 0.000245734f,
|
||||
0.195057107f,-0.058632298f, 0.017592780f,-0.003591951f, 0.000241475f,
|
||||
0.192684683f,-0.057958667f, 0.017383186f,-0.003545116f, 0.000237254f,
|
||||
0.190317885f,-0.057285511f, 0.017173891f,-0.003498437f, 0.000233071f,
|
||||
0.187956756f,-0.056612855f, 0.016964902f,-0.003451915f, 0.000228927f,
|
||||
0.185601339f,-0.055940727f, 0.016756228f,-0.003405553f, 0.000224821f,
|
||||
0.183251678f,-0.055269152f, 0.016547878f,-0.003359350f, 0.000220752f,
|
||||
0.180907815f,-0.054598157f, 0.016339859f,-0.003313310f, 0.000216722f,
|
||||
0.178569792f,-0.053927768f, 0.016132180f,-0.003267434f, 0.000212731f,
|
||||
0.176237654f,-0.053258011f, 0.015924850f,-0.003221722f, 0.000208777f,
|
||||
0.173911440f,-0.052588912f, 0.015717875f,-0.003176177f, 0.000204861f,
|
||||
0.171591194f,-0.051920496f, 0.015511264f,-0.003130800f, 0.000200983f,
|
||||
0.169276957f,-0.051252790f, 0.015305026f,-0.003085592f, 0.000197143f,
|
||||
0.166968771f,-0.050585817f, 0.015099168f,-0.003040555f, 0.000193341f,
|
||||
0.164666676f,-0.049919605f, 0.014893698f,-0.002995691f, 0.000189577f,
|
||||
0.162370713f,-0.049254177f, 0.014688624f,-0.002951000f, 0.000185851f,
|
||||
0.160080923f,-0.048589560f, 0.014483954f,-0.002906485f, 0.000182163f,
|
||||
0.157797346f,-0.047925777f, 0.014279695f,-0.002862145f, 0.000178512f,
|
||||
0.155520023f,-0.047262854f, 0.014075855f,-0.002817984f, 0.000174899f,
|
||||
0.153248993f,-0.046600815f, 0.013872442f,-0.002774001f, 0.000171324f,
|
||||
0.150984296f,-0.045939685f, 0.013669462f,-0.002730199f, 0.000167786f,
|
||||
0.148725971f,-0.045279488f, 0.013466925f,-0.002686579f, 0.000164285f,
|
||||
0.146474057f,-0.044620249f, 0.013264837f,-0.002643141f, 0.000160822f,
|
||||
0.144228593f,-0.043961991f, 0.013063205f,-0.002599888f, 0.000157396f,
|
||||
0.141989618f,-0.043304738f, 0.012862038f,-0.002556820f, 0.000154008f,
|
||||
0.139757169f,-0.042648514f, 0.012661341f,-0.002513938f, 0.000150656f,
|
||||
0.137531285f,-0.041993344f, 0.012461123f,-0.002471244f, 0.000147342f,
|
||||
0.135312004f,-0.041339249f, 0.012261390f,-0.002428739f, 0.000144064f,
|
||||
0.133099362f,-0.040686254f, 0.012062150f,-0.002386425f, 0.000140824f,
|
||||
0.130893398f,-0.040034383f, 0.011863409f,-0.002344301f, 0.000137620f,
|
||||
0.128694148f,-0.039383657f, 0.011665175f,-0.002302370f, 0.000134453f,
|
||||
0.126501649f,-0.038734100f, 0.011467454f,-0.002260631f, 0.000131323f,
|
||||
0.124315937f,-0.038085735f, 0.011270254f,-0.002219088f, 0.000128229f,
|
||||
0.122137048f,-0.037438584f, 0.011073581f,-0.002177740f, 0.000125171f,
|
||||
0.119965018f,-0.036792671f, 0.010877441f,-0.002136588f, 0.000122150f,
|
||||
0.117799883f,-0.036148017f, 0.010681842f,-0.002095634f, 0.000119165f,
|
||||
0.115641679f,-0.035504644f, 0.010486791f,-0.002054878f, 0.000116216f,
|
||||
0.113490439f,-0.034862575f, 0.010292293f,-0.002014322f, 0.000113304f,
|
||||
0.111346199f,-0.034221832f, 0.010098355f,-0.001973967f, 0.000110427f,
|
||||
0.109208994f,-0.033582436f, 0.009904984f,-0.001933813f, 0.000107585f,
|
||||
0.107078857f,-0.032944409f, 0.009712186f,-0.001893861f, 0.000104780f,
|
||||
0.104955823f,-0.032307773f, 0.009519967f,-0.001854112f, 0.000102010f,
|
||||
0.102839925f,-0.031672548f, 0.009328334f,-0.001814568f, 0.000099275f,
|
||||
0.100731196f,-0.031038757f, 0.009137293f,-0.001775228f, 0.000096576f,
|
||||
0.098629671f,-0.030406420f, 0.008946849f,-0.001736095f, 0.000093911f,
|
||||
0.096535380f,-0.029775558f, 0.008757010f,-0.001697168f, 0.000091282f,
|
||||
0.094448358f,-0.029146192f, 0.008567781f,-0.001658448f, 0.000088688f,
|
||||
0.092368636f,-0.028518343f, 0.008379168f,-0.001619937f, 0.000086128f,
|
||||
0.090296247f,-0.027892030f, 0.008191177f,-0.001581635f, 0.000083603f,
|
||||
0.088231221f,-0.027267275f, 0.008003813f,-0.001543542f, 0.000081113f,
|
||||
0.086173591f,-0.026644097f, 0.007817084f,-0.001505661f, 0.000078656f,
|
||||
0.084123388f,-0.026022517f, 0.007630993f,-0.001467990f, 0.000076234f,
|
||||
0.082080642f,-0.025402554f, 0.007445548f,-0.001430532f, 0.000073846f,
|
||||
0.080045384f,-0.024784228f, 0.007260753f,-0.001393286f, 0.000071492f,
|
||||
0.078017645f,-0.024167559f, 0.007076615f,-0.001356254f, 0.000069172f,
|
||||
0.075997454f,-0.023552566f, 0.006893138f,-0.001319435f, 0.000066885f,
|
||||
0.073984841f,-0.022939269f, 0.006710328f,-0.001282832f, 0.000064632f,
|
||||
0.071979836f,-0.022327686f, 0.006528191f,-0.001246443f, 0.000062412f,
|
||||
0.069982467f,-0.021717836f, 0.006346731f,-0.001210271f, 0.000060225f,
|
||||
0.067992764f,-0.021109739f, 0.006165955f,-0.001174315f, 0.000058072f,
|
||||
0.066010755f,-0.020503413f, 0.005985866f,-0.001138576f, 0.000055951f,
|
||||
0.064036468f,-0.019898876f, 0.005806472f,-0.001103055f, 0.000053862f,
|
||||
0.062069932f,-0.019296147f, 0.005627775f,-0.001067751f, 0.000051807f,
|
||||
0.060111174f,-0.018695244f, 0.005449782f,-0.001032667f, 0.000049783f,
|
||||
0.058160222f,-0.018096185f, 0.005272497f,-0.000997801f, 0.000047792f,
|
||||
0.056217102f,-0.017498988f, 0.005095926f,-0.000963156f, 0.000045833f,
|
||||
0.054281842f,-0.016903671f, 0.004920073f,-0.000928730f, 0.000043905f,
|
||||
0.052354468f,-0.016310251f, 0.004744943f,-0.000894525f, 0.000042010f,
|
||||
0.050435006f,-0.015718746f, 0.004570540f,-0.000860541f, 0.000040146f,
|
||||
0.048523483f,-0.015129173f, 0.004396870f,-0.000826779f, 0.000038313f,
|
||||
0.046619924f,-0.014541549f, 0.004223937f,-0.000793238f, 0.000036511f,
|
||||
0.044724355f,-0.013955892f, 0.004051745f,-0.000759920f, 0.000034740f,
|
||||
0.042836800f,-0.013372217f, 0.003880299f,-0.000726824f, 0.000033000f,
|
||||
0.040957285f,-0.012790543f, 0.003709604f,-0.000693951f, 0.000031291f,
|
||||
0.039085833f,-0.012210884f, 0.003539663f,-0.000661301f, 0.000029612f,
|
||||
0.037222470f,-0.011633259f, 0.003370481f,-0.000628876f, 0.000027964f,
|
||||
0.035367219f,-0.011057682f, 0.003202062f,-0.000596674f, 0.000026345f,
|
||||
0.033520103f,-0.010484170f, 0.003034411f,-0.000564696f, 0.000024756f,
|
||||
0.031681147f,-0.009912739f, 0.002867532f,-0.000532943f, 0.000023197f,
|
||||
0.029850373f,-0.009343404f, 0.002701428f,-0.000501414f, 0.000021668f,
|
||||
0.028027804f,-0.008776182f, 0.002536104f,-0.000470111f, 0.000020168f,
|
||||
0.026213462f,-0.008211087f, 0.002371564f,-0.000439032f, 0.000018697f,
|
||||
0.024407370f,-0.007648135f, 0.002207811f,-0.000408180f, 0.000017255f,
|
||||
0.022609549f,-0.007087341f, 0.002044849f,-0.000377553f, 0.000015842f,
|
||||
0.020820021f,-0.006528720f, 0.001882682f,-0.000347151f, 0.000014457f,
|
||||
0.019038808f,-0.005972286f, 0.001721315f,-0.000316976f, 0.000013101f,
|
||||
0.017265930f,-0.005418056f, 0.001560750f,-0.000287027f, 0.000011773f,
|
||||
0.015501408f,-0.004866042f, 0.001400991f,-0.000257305f, 0.000010473f,
|
||||
0.013745262f,-0.004316260f, 0.001242041f,-0.000227808f, 0.000009201f,
|
||||
0.011997513f,-0.003768723f, 0.001083905f,-0.000198539f, 0.000007956f,
|
||||
0.010258180f,-0.003223445f, 0.000926585f,-0.000169495f, 0.000006739f,
|
||||
0.008527283f,-0.002680442f, 0.000770086f,-0.000140679f, 0.000005549f,
|
||||
0.006804842f,-0.002139725f, 0.000614409f,-0.000112090f, 0.000004386f,
|
||||
0.005090874f,-0.001601309f, 0.000459559f,-0.000083727f, 0.000003250f,
|
||||
0.003385399f,-0.001065208f, 0.000305539f,-0.000055591f, 0.000002140f,
|
||||
0.001688435f,-0.000531434f, 0.000152351f,-0.000027682f, 0.000001057f,
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// Get the name of the audio device we use for output
|
||||
|
||||
#if defined(SDL_AUDIO_DRIVER_NETBSD) || defined(SDL_AUDIO_DRIVER_OSS)
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h> // For close()
|
||||
|
||||
#include "SDL_audiodev_c.h"
|
||||
|
||||
#ifndef SDL_PATH_DEV_DSP
|
||||
#if defined(SDL_PLATFORM_NETBSD) || defined(SDL_PLATFORM_OPENBSD)
|
||||
#define SDL_PATH_DEV_DSP "/dev/audio"
|
||||
#else
|
||||
#define SDL_PATH_DEV_DSP "/dev/dsp"
|
||||
#endif
|
||||
#endif
|
||||
#ifndef SDL_PATH_DEV_DSP24
|
||||
#define SDL_PATH_DEV_DSP24 "/dev/sound/dsp"
|
||||
#endif
|
||||
#ifndef SDL_PATH_DEV_AUDIO
|
||||
#define SDL_PATH_DEV_AUDIO "/dev/audio"
|
||||
#endif
|
||||
|
||||
static void test_device(const SDL_bool iscapture, const char *fname, int flags, SDL_bool (*test)(int fd))
|
||||
{
|
||||
struct stat sb;
|
||||
const int audio_fd = open(fname, flags | O_CLOEXEC, 0);
|
||||
if (audio_fd >= 0) {
|
||||
if ((fstat(audio_fd, &sb) == 0) && (S_ISCHR(sb.st_mode))) {
|
||||
const SDL_bool okay = test(audio_fd);
|
||||
close(audio_fd);
|
||||
if (okay) {
|
||||
static size_t dummyhandle = 0;
|
||||
dummyhandle++;
|
||||
SDL_assert(dummyhandle != 0);
|
||||
|
||||
/* Note that spec is NULL; while we are opening the device
|
||||
* endpoint here, the endpoint does not provide any mix format
|
||||
* information, making this information inaccessible at
|
||||
* enumeration time
|
||||
*/
|
||||
SDL_AddAudioDevice(iscapture, fname, NULL, (void *)(uintptr_t)dummyhandle);
|
||||
}
|
||||
} else {
|
||||
close(audio_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_bool test_stub(int fd)
|
||||
{
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
static void SDL_EnumUnixAudioDevices_Internal(const SDL_bool iscapture, const SDL_bool classic, SDL_bool (*test)(int))
|
||||
{
|
||||
const int flags = iscapture ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT;
|
||||
const char *audiodev;
|
||||
char audiopath[1024];
|
||||
|
||||
if (!test) {
|
||||
test = test_stub;
|
||||
}
|
||||
|
||||
// Figure out what our audio device is
|
||||
audiodev = SDL_getenv("SDL_PATH_DSP");
|
||||
if (!audiodev) {
|
||||
audiodev = SDL_getenv("AUDIODEV");
|
||||
}
|
||||
if (!audiodev) {
|
||||
if (classic) {
|
||||
audiodev = SDL_PATH_DEV_AUDIO;
|
||||
} else {
|
||||
struct stat sb;
|
||||
|
||||
// Added support for /dev/sound/\* in Linux 2.4
|
||||
if (((stat("/dev/sound", &sb) == 0) && S_ISDIR(sb.st_mode)) && ((stat(SDL_PATH_DEV_DSP24, &sb) == 0) && S_ISCHR(sb.st_mode))) {
|
||||
audiodev = SDL_PATH_DEV_DSP24;
|
||||
} else {
|
||||
audiodev = SDL_PATH_DEV_DSP;
|
||||
}
|
||||
}
|
||||
}
|
||||
test_device(iscapture, audiodev, flags, test);
|
||||
|
||||
if (SDL_strlen(audiodev) < (sizeof(audiopath) - 3)) {
|
||||
int instance = 0;
|
||||
while (instance <= 64) {
|
||||
(void)SDL_snprintf(audiopath, SDL_arraysize(audiopath),
|
||||
"%s%d", audiodev, instance);
|
||||
instance++;
|
||||
test_device(iscapture, audiopath, flags, test);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_EnumUnixAudioDevices(const SDL_bool classic, SDL_bool (*test)(int))
|
||||
{
|
||||
SDL_EnumUnixAudioDevices_Internal(SDL_TRUE, classic, test);
|
||||
SDL_EnumUnixAudioDevices_Internal(SDL_FALSE, classic, test);
|
||||
}
|
||||
|
||||
#endif // Audio device selection
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_audiodev_c_h_
|
||||
#define SDL_audiodev_c_h_
|
||||
|
||||
#include "SDL_internal.h"
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
// Open the audio device for playback, and don't block if busy
|
||||
//#define USE_BLOCKING_WRITES
|
||||
|
||||
#ifdef USE_BLOCKING_WRITES
|
||||
#define OPEN_FLAGS_OUTPUT O_WRONLY
|
||||
#define OPEN_FLAGS_INPUT O_RDONLY
|
||||
#else
|
||||
#define OPEN_FLAGS_OUTPUT (O_WRONLY | O_NONBLOCK)
|
||||
#define OPEN_FLAGS_INPUT (O_RDONLY | O_NONBLOCK)
|
||||
#endif
|
||||
|
||||
extern void SDL_EnumUnixAudioDevices(const SDL_bool classic, SDL_bool (*test)(int));
|
||||
|
||||
#endif // SDL_audiodev_c_h_
|
||||
@@ -0,0 +1,514 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_audioqueue.h"
|
||||
|
||||
#define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq))
|
||||
|
||||
struct SDL_AudioTrack
|
||||
{
|
||||
SDL_AudioSpec spec;
|
||||
SDL_bool flushed;
|
||||
SDL_AudioTrack *next;
|
||||
|
||||
size_t (*avail)(void *ctx);
|
||||
int (*write)(void *ctx, const Uint8 *buf, size_t len);
|
||||
size_t (*read)(void *ctx, Uint8 *buf, size_t len, SDL_bool advance);
|
||||
void (*destroy)(void *ctx);
|
||||
};
|
||||
|
||||
struct SDL_AudioQueue
|
||||
{
|
||||
SDL_AudioTrack *head;
|
||||
SDL_AudioTrack *tail;
|
||||
size_t chunk_size;
|
||||
};
|
||||
|
||||
typedef struct SDL_AudioChunk SDL_AudioChunk;
|
||||
|
||||
struct SDL_AudioChunk
|
||||
{
|
||||
SDL_AudioChunk *next;
|
||||
size_t head;
|
||||
size_t tail;
|
||||
Uint8 data[SDL_VARIABLE_LENGTH_ARRAY];
|
||||
};
|
||||
|
||||
typedef struct SDL_ChunkedAudioTrack
|
||||
{
|
||||
SDL_AudioTrack track;
|
||||
|
||||
size_t chunk_size;
|
||||
|
||||
SDL_AudioChunk *head;
|
||||
SDL_AudioChunk *tail;
|
||||
size_t queued_bytes;
|
||||
|
||||
SDL_AudioChunk *free_chunks;
|
||||
size_t num_free_chunks;
|
||||
} SDL_ChunkedAudioTrack;
|
||||
|
||||
static void DestroyAudioChunk(SDL_AudioChunk *chunk)
|
||||
{
|
||||
SDL_free(chunk);
|
||||
}
|
||||
|
||||
static void DestroyAudioChunks(SDL_AudioChunk *chunk)
|
||||
{
|
||||
while (chunk) {
|
||||
SDL_AudioChunk *next = chunk->next;
|
||||
DestroyAudioChunk(chunk);
|
||||
chunk = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void ResetAudioChunk(SDL_AudioChunk *chunk)
|
||||
{
|
||||
chunk->next = NULL;
|
||||
chunk->head = 0;
|
||||
chunk->tail = 0;
|
||||
}
|
||||
|
||||
static SDL_AudioChunk *CreateAudioChunk(size_t chunk_size)
|
||||
{
|
||||
SDL_AudioChunk *chunk = (SDL_AudioChunk *)SDL_malloc(sizeof(*chunk) + chunk_size);
|
||||
|
||||
if (!chunk) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ResetAudioChunk(chunk);
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
static void DestroyAudioTrackChunk(SDL_ChunkedAudioTrack *track, SDL_AudioChunk *chunk)
|
||||
{
|
||||
// Keeping a list of free chunks reduces memory allocations,
|
||||
// But also increases the amount of work to perform when freeing the track.
|
||||
const size_t max_free_bytes = 64 * 1024;
|
||||
|
||||
if (track->chunk_size * track->num_free_chunks < max_free_bytes) {
|
||||
chunk->next = track->free_chunks;
|
||||
track->free_chunks = chunk;
|
||||
++track->num_free_chunks;
|
||||
} else {
|
||||
DestroyAudioChunk(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_AudioChunk *CreateAudioTrackChunk(SDL_ChunkedAudioTrack *track)
|
||||
{
|
||||
if (track->num_free_chunks > 0) {
|
||||
SDL_AudioChunk *chunk = track->free_chunks;
|
||||
|
||||
track->free_chunks = chunk->next;
|
||||
--track->num_free_chunks;
|
||||
|
||||
ResetAudioChunk(chunk);
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
return CreateAudioChunk(track->chunk_size);
|
||||
}
|
||||
|
||||
static size_t AvailChunkedAudioTrack(void *ctx)
|
||||
{
|
||||
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx;
|
||||
|
||||
return track->queued_bytes;
|
||||
}
|
||||
|
||||
static int WriteToChunkedAudioTrack(void *ctx, const Uint8 *data, size_t len)
|
||||
{
|
||||
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx;
|
||||
|
||||
SDL_AudioChunk *chunk = track->tail;
|
||||
|
||||
// Handle the first chunk
|
||||
if (!chunk) {
|
||||
chunk = CreateAudioTrackChunk(track);
|
||||
|
||||
if (!chunk) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_assert((track->head == NULL) && (track->tail == NULL) && (track->queued_bytes == 0));
|
||||
track->head = chunk;
|
||||
track->tail = chunk;
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
size_t old_tail = chunk->tail;
|
||||
size_t chunk_size = track->chunk_size;
|
||||
|
||||
while (chunk) {
|
||||
size_t to_write = chunk_size - chunk->tail;
|
||||
to_write = SDL_min(to_write, len - total);
|
||||
SDL_memcpy(&chunk->data[chunk->tail], &data[total], to_write);
|
||||
total += to_write;
|
||||
|
||||
chunk->tail += to_write;
|
||||
|
||||
if (total == len) {
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_AudioChunk *next = CreateAudioTrackChunk(track);
|
||||
chunk->next = next;
|
||||
chunk = next;
|
||||
}
|
||||
|
||||
// Roll back the changes if we couldn't write all the data
|
||||
if (!chunk) {
|
||||
chunk = track->tail;
|
||||
|
||||
SDL_AudioChunk *next = chunk->next;
|
||||
chunk->next = NULL;
|
||||
chunk->tail = old_tail;
|
||||
|
||||
DestroyAudioChunks(next);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
track->tail = chunk;
|
||||
track->queued_bytes += total;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t ReadFromChunkedAudioTrack(void *ctx, Uint8 *data, size_t len, SDL_bool advance)
|
||||
{
|
||||
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx;
|
||||
SDL_AudioChunk *chunk = track->head;
|
||||
|
||||
size_t total = 0;
|
||||
size_t head = 0;
|
||||
|
||||
while (chunk) {
|
||||
head = chunk->head;
|
||||
|
||||
size_t to_read = chunk->tail - head;
|
||||
to_read = SDL_min(to_read, len - total);
|
||||
SDL_memcpy(&data[total], &chunk->data[head], to_read);
|
||||
total += to_read;
|
||||
|
||||
SDL_AudioChunk *next = chunk->next;
|
||||
|
||||
if (total == len) {
|
||||
head += to_read;
|
||||
break;
|
||||
}
|
||||
|
||||
if (advance) {
|
||||
DestroyAudioTrackChunk(track, chunk);
|
||||
}
|
||||
|
||||
chunk = next;
|
||||
}
|
||||
|
||||
if (advance) {
|
||||
if (chunk) {
|
||||
chunk->head = head;
|
||||
track->head = chunk;
|
||||
} else {
|
||||
track->head = NULL;
|
||||
track->tail = NULL;
|
||||
}
|
||||
|
||||
track->queued_bytes -= total;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static void DestroyChunkedAudioTrack(void *ctx)
|
||||
{
|
||||
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx;
|
||||
DestroyAudioChunks(track->head);
|
||||
DestroyAudioChunks(track->free_chunks);
|
||||
SDL_free(track);
|
||||
}
|
||||
|
||||
static SDL_AudioTrack *CreateChunkedAudioTrack(const SDL_AudioSpec *spec, size_t chunk_size)
|
||||
{
|
||||
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)SDL_calloc(1, sizeof(*track));
|
||||
|
||||
if (!track) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_copyp(&track->track.spec, spec);
|
||||
track->track.avail = AvailChunkedAudioTrack;
|
||||
track->track.write = WriteToChunkedAudioTrack;
|
||||
track->track.read = ReadFromChunkedAudioTrack;
|
||||
track->track.destroy = DestroyChunkedAudioTrack;
|
||||
|
||||
track->chunk_size = chunk_size;
|
||||
|
||||
return &track->track;
|
||||
}
|
||||
|
||||
SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size)
|
||||
{
|
||||
SDL_AudioQueue *queue = (SDL_AudioQueue *)SDL_calloc(1, sizeof(*queue));
|
||||
|
||||
if (!queue) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
queue->chunk_size = chunk_size;
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
void SDL_DestroyAudioQueue(SDL_AudioQueue *queue)
|
||||
{
|
||||
SDL_ClearAudioQueue(queue);
|
||||
|
||||
SDL_free(queue);
|
||||
}
|
||||
|
||||
void SDL_ClearAudioQueue(SDL_AudioQueue *queue)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
queue->head = NULL;
|
||||
queue->tail = NULL;
|
||||
|
||||
while (track) {
|
||||
SDL_AudioTrack *next = track->next;
|
||||
track->destroy(track);
|
||||
track = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_FlushAudioTrack(SDL_AudioTrack *track)
|
||||
{
|
||||
track->flushed = SDL_TRUE;
|
||||
track->write = NULL;
|
||||
}
|
||||
|
||||
void SDL_FlushAudioQueue(SDL_AudioQueue *queue)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->tail;
|
||||
|
||||
if (track) {
|
||||
SDL_FlushAudioTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
for (;;) {
|
||||
SDL_bool flushed = track->flushed;
|
||||
|
||||
SDL_AudioTrack *next = track->next;
|
||||
track->destroy(track);
|
||||
track = next;
|
||||
|
||||
if (flushed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
queue->head = track;
|
||||
|
||||
if (!track) {
|
||||
queue->tail = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
size_t SDL_GetAudioQueueChunkSize(SDL_AudioQueue *queue)
|
||||
{
|
||||
return queue->chunk_size;
|
||||
}
|
||||
|
||||
SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uint8 *data, size_t len, size_t chunk_size)
|
||||
{
|
||||
SDL_AudioTrack *track = CreateChunkedAudioTrack(spec, chunk_size);
|
||||
|
||||
if (!track) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (track->write(track, data, len) != 0) {
|
||||
track->destroy(track);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track)
|
||||
{
|
||||
SDL_AudioTrack *tail = queue->tail;
|
||||
|
||||
if (tail) {
|
||||
// If the spec has changed, make sure to flush the previous track
|
||||
if (!AUDIO_SPECS_EQUAL(tail->spec, track->spec)) {
|
||||
SDL_FlushAudioTrack(tail);
|
||||
}
|
||||
|
||||
tail->next = track;
|
||||
} else {
|
||||
queue->head = track;
|
||||
}
|
||||
|
||||
queue->tail = track;
|
||||
}
|
||||
|
||||
int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len)
|
||||
{
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_AudioTrack *track = queue->tail;
|
||||
|
||||
if ((track) && !AUDIO_SPECS_EQUAL(track->spec, *spec)) {
|
||||
SDL_FlushAudioTrack(track);
|
||||
}
|
||||
|
||||
if ((!track) || (!track->write)) {
|
||||
SDL_AudioTrack *new_track = CreateChunkedAudioTrack(spec, queue->chunk_size);
|
||||
|
||||
if (!new_track) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (track) {
|
||||
track->next = new_track;
|
||||
} else {
|
||||
queue->head = new_track;
|
||||
}
|
||||
|
||||
queue->tail = new_track;
|
||||
|
||||
track = new_track;
|
||||
}
|
||||
|
||||
return track->write(track, data, len);
|
||||
}
|
||||
|
||||
void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue)
|
||||
{
|
||||
return queue->head;
|
||||
}
|
||||
|
||||
size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed)
|
||||
{
|
||||
SDL_AudioTrack *iter = (SDL_AudioTrack *)(*inout_iter);
|
||||
SDL_assert(iter != NULL);
|
||||
|
||||
SDL_copyp(out_spec, &iter->spec);
|
||||
|
||||
SDL_bool flushed = SDL_FALSE;
|
||||
size_t queued_bytes = 0;
|
||||
|
||||
while (iter) {
|
||||
SDL_AudioTrack *track = iter;
|
||||
iter = iter->next;
|
||||
|
||||
size_t avail = track->avail(track);
|
||||
|
||||
if (avail >= SDL_SIZE_MAX - queued_bytes) {
|
||||
queued_bytes = SDL_SIZE_MAX;
|
||||
flushed = SDL_FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
queued_bytes += avail;
|
||||
flushed = track->flushed;
|
||||
|
||||
if (flushed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*inout_iter = iter;
|
||||
*out_flushed = flushed;
|
||||
|
||||
return queued_bytes;
|
||||
}
|
||||
|
||||
int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
|
||||
{
|
||||
size_t total = 0;
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
for (;;) {
|
||||
if (!track) {
|
||||
return SDL_SetError("Reading past end of queue");
|
||||
}
|
||||
|
||||
total += track->read(track, &data[total], len - total, SDL_TRUE);
|
||||
|
||||
if (total == len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (track->flushed) {
|
||||
return SDL_SetError("Reading past end of flushed track");
|
||||
}
|
||||
|
||||
SDL_AudioTrack *next = track->next;
|
||||
|
||||
if (!next) {
|
||||
return SDL_SetError("Reading past end of incomplete track");
|
||||
}
|
||||
|
||||
queue->head = next;
|
||||
|
||||
track->destroy(track);
|
||||
track = next;
|
||||
}
|
||||
}
|
||||
|
||||
int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
|
||||
{
|
||||
size_t total = 0;
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
for (;;) {
|
||||
if (!track) {
|
||||
return SDL_SetError("Peeking past end of queue");
|
||||
}
|
||||
|
||||
total += track->read(track, &data[total], len - total, SDL_FALSE);
|
||||
|
||||
if (total == len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (track->flushed) {
|
||||
// If we have run out of data, fill the rest with silence.
|
||||
SDL_memset(&data[total], SDL_GetSilenceValueForFormat(track->spec.format), len - total);
|
||||
return 0;
|
||||
}
|
||||
|
||||
track = track->next;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_audioqueue_h_
|
||||
#define SDL_audioqueue_h_
|
||||
|
||||
// Internal functions used by SDL_AudioStream for queueing audio.
|
||||
|
||||
typedef struct SDL_AudioQueue SDL_AudioQueue;
|
||||
typedef struct SDL_AudioTrack SDL_AudioTrack;
|
||||
|
||||
// Create a new audio queue
|
||||
SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size);
|
||||
|
||||
// Destroy an audio queue
|
||||
void SDL_DestroyAudioQueue(SDL_AudioQueue *queue);
|
||||
|
||||
// Completely clear the queue
|
||||
void SDL_ClearAudioQueue(SDL_AudioQueue *queue);
|
||||
|
||||
// Mark the last track as flushed
|
||||
void SDL_FlushAudioQueue(SDL_AudioQueue *queue);
|
||||
|
||||
// Pop the current head track
|
||||
// REQUIRES: The head track must exist, and must have been flushed
|
||||
void SDL_PopAudioQueueHead(SDL_AudioQueue *queue);
|
||||
|
||||
// Get the chunk size, mostly for use with SDL_CreateChunkedAudioTrack
|
||||
// This can be called from any thread
|
||||
size_t SDL_GetAudioQueueChunkSize(SDL_AudioQueue *queue);
|
||||
|
||||
// Write data to the end of queue
|
||||
// REQUIRES: If the spec has changed, the last track must have been flushed
|
||||
int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len);
|
||||
|
||||
// Create a track without needing to hold any locks
|
||||
SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uint8 *data, size_t len, size_t chunk_size);
|
||||
|
||||
// Add a track to the end of the queue
|
||||
// REQUIRES: `track != NULL`
|
||||
void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track);
|
||||
|
||||
// Iterate over the tracks in the queue
|
||||
void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue);
|
||||
|
||||
// Query and update the track iterator
|
||||
// REQUIRES: `*inout_iter != NULL` (a valid iterator)
|
||||
size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed);
|
||||
|
||||
// Read data from the start of the queue
|
||||
// REQUIRES: There must be enough data in the queue
|
||||
int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len);
|
||||
|
||||
// Peek into the start of the queue
|
||||
// REQUIRES: There must be enough data in the queue, unless it has been flushed, in which case missing data is filled with silence.
|
||||
int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len);
|
||||
|
||||
#endif // SDL_audioqueue_h_
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_sysaudio.h"
|
||||
#include "SDL_audioresample.h"
|
||||
|
||||
// SDL's resampler uses a "bandlimited interpolation" algorithm:
|
||||
// https://ccrma.stanford.edu/~jos/resample/
|
||||
|
||||
#include "SDL_audio_resampler_filter.h"
|
||||
|
||||
// For a given srcpos, `srcpos + frame` are sampled, where `-RESAMPLER_ZERO_CROSSINGS < frame <= RESAMPLER_ZERO_CROSSINGS`.
|
||||
// Note, when upsampling, it is also possible to start sampling from `srcpos = -1`.
|
||||
#define RESAMPLER_MAX_PADDING_FRAMES (RESAMPLER_ZERO_CROSSINGS + 1)
|
||||
|
||||
#define RESAMPLER_FILTER_INTERP_BITS (32 - RESAMPLER_BITS_PER_ZERO_CROSSING)
|
||||
#define RESAMPLER_FILTER_INTERP_RANGE (1 << RESAMPLER_FILTER_INTERP_BITS)
|
||||
|
||||
#define RESAMPLER_SAMPLES_PER_FRAME (RESAMPLER_ZERO_CROSSINGS * 2)
|
||||
|
||||
#define RESAMPLER_FULL_FILTER_SIZE (RESAMPLER_SAMPLES_PER_FRAME * (RESAMPLER_SAMPLES_PER_ZERO_CROSSING + 1))
|
||||
|
||||
static void ResampleFrame_Scalar(const float *src, float *dst, const float *raw_filter, float interp, int chans)
|
||||
{
|
||||
int i, chan;
|
||||
|
||||
float filter[RESAMPLER_SAMPLES_PER_FRAME];
|
||||
|
||||
// Interpolate between the nearest two filters
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
|
||||
filter[i] = (raw_filter[i] * (1.0f - interp)) + (raw_filter[i + RESAMPLER_SAMPLES_PER_FRAME] * interp);
|
||||
}
|
||||
|
||||
if (chans == 2) {
|
||||
float out[2];
|
||||
out[0] = 0.0f;
|
||||
out[1] = 0.0f;
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
|
||||
const float scale = filter[i];
|
||||
out[0] += src[i * 2 + 0] * scale;
|
||||
out[1] += src[i * 2 + 1] * scale;
|
||||
}
|
||||
|
||||
dst[0] = out[0];
|
||||
dst[1] = out[1];
|
||||
return;
|
||||
}
|
||||
|
||||
if (chans == 1) {
|
||||
float out = 0.0f;
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
|
||||
out += src[i] * filter[i];
|
||||
}
|
||||
|
||||
dst[0] = out;
|
||||
return;
|
||||
}
|
||||
|
||||
for (chan = 0; chan < chans; chan++) {
|
||||
float f = 0.0f;
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
|
||||
f += src[i * chans + chan] * filter[i];
|
||||
}
|
||||
|
||||
dst[chan] = f;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SDL_SSE_INTRINSICS
|
||||
static void SDL_TARGETING("sse") ResampleFrame_SSE(const float *src, float *dst, const float *raw_filter, float interp, int chans)
|
||||
{
|
||||
#if RESAMPLER_SAMPLES_PER_FRAME != 10
|
||||
#error Invalid samples per frame
|
||||
#endif
|
||||
|
||||
// Load the filter
|
||||
__m128 f0 = _mm_loadu_ps(raw_filter + 0);
|
||||
__m128 f1 = _mm_loadu_ps(raw_filter + 4);
|
||||
__m128 f2 = _mm_loadl_pi(_mm_setzero_ps(), (const __m64 *)(raw_filter + 8));
|
||||
|
||||
__m128 g0 = _mm_loadu_ps(raw_filter + 10);
|
||||
__m128 g1 = _mm_loadu_ps(raw_filter + 14);
|
||||
__m128 g2 = _mm_loadl_pi(_mm_setzero_ps(), (const __m64 *)(raw_filter + 18));
|
||||
|
||||
__m128 interp1 = _mm_set1_ps(interp);
|
||||
__m128 interp2 = _mm_sub_ps(_mm_set1_ps(1.0f), _mm_set1_ps(interp));
|
||||
|
||||
// Linear interpolate the filter
|
||||
f0 = _mm_add_ps(_mm_mul_ps(f0, interp2), _mm_mul_ps(g0, interp1));
|
||||
f1 = _mm_add_ps(_mm_mul_ps(f1, interp2), _mm_mul_ps(g1, interp1));
|
||||
f2 = _mm_add_ps(_mm_mul_ps(f2, interp2), _mm_mul_ps(g2, interp1));
|
||||
|
||||
if (chans == 2) {
|
||||
// Duplicate each of the filter elements
|
||||
g0 = _mm_unpackhi_ps(f0, f0);
|
||||
f0 = _mm_unpacklo_ps(f0, f0);
|
||||
g1 = _mm_unpackhi_ps(f1, f1);
|
||||
f1 = _mm_unpacklo_ps(f1, f1);
|
||||
f2 = _mm_unpacklo_ps(f2, f2);
|
||||
|
||||
// Multiply the filter by the input
|
||||
f0 = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
|
||||
g0 = _mm_mul_ps(g0, _mm_loadu_ps(src + 4));
|
||||
f1 = _mm_mul_ps(f1, _mm_loadu_ps(src + 8));
|
||||
g1 = _mm_mul_ps(g1, _mm_loadu_ps(src + 12));
|
||||
f2 = _mm_mul_ps(f2, _mm_loadu_ps(src + 16));
|
||||
|
||||
// Calculate the sum
|
||||
f0 = _mm_add_ps(_mm_add_ps(_mm_add_ps(f0, g0), _mm_add_ps(f1, g1)), f2);
|
||||
f0 = _mm_add_ps(f0, _mm_movehl_ps(f0, f0));
|
||||
|
||||
// Store the result
|
||||
_mm_storel_pi((__m64 *)dst, f0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chans == 1) {
|
||||
// Multiply the filter by the input
|
||||
f0 = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
|
||||
f1 = _mm_mul_ps(f1, _mm_loadu_ps(src + 4));
|
||||
f2 = _mm_mul_ps(f2, _mm_loadl_pi(_mm_setzero_ps(), (const __m64 *)(src + 8)));
|
||||
|
||||
// Calculate the sum
|
||||
f0 = _mm_add_ps(f0, f1);
|
||||
f0 = _mm_add_ps(_mm_add_ps(f0, f2), _mm_movehl_ps(f0, f0));
|
||||
f0 = _mm_add_ss(f0, _mm_shuffle_ps(f0, f0, _MM_SHUFFLE(1, 1, 1, 1)));
|
||||
|
||||
// Store the result
|
||||
_mm_store_ss(dst, f0);
|
||||
return;
|
||||
}
|
||||
|
||||
float filter[RESAMPLER_SAMPLES_PER_FRAME];
|
||||
_mm_storeu_ps(filter + 0, f0);
|
||||
_mm_storeu_ps(filter + 4, f1);
|
||||
_mm_storel_pi((__m64 *)(filter + 8), f2);
|
||||
|
||||
int i, chan = 0;
|
||||
|
||||
for (; chan + 4 <= chans; chan += 4) {
|
||||
f0 = _mm_setzero_ps();
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
|
||||
f0 = _mm_add_ps(f0, _mm_mul_ps(_mm_loadu_ps(&src[i * chans + chan]), _mm_load1_ps(&filter[i])));
|
||||
}
|
||||
|
||||
_mm_storeu_ps(&dst[chan], f0);
|
||||
}
|
||||
|
||||
for (; chan < chans; chan++) {
|
||||
f0 = _mm_setzero_ps();
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
|
||||
f0 = _mm_add_ss(f0, _mm_mul_ss(_mm_load_ss(&src[i * chans + chan]), _mm_load_ss(&filter[i])));
|
||||
}
|
||||
|
||||
_mm_store_ss(&dst[chan], f0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void (*ResampleFrame)(const float *src, float *dst, const float *raw_filter, float interp, int chans);
|
||||
|
||||
static float FullResamplerFilter[RESAMPLER_FULL_FILTER_SIZE];
|
||||
|
||||
void SDL_SetupAudioResampler(void)
|
||||
{
|
||||
static SDL_bool setup = SDL_FALSE;
|
||||
if (setup) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build a table combining the left and right wings, for faster access
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
|
||||
for (j = 0; j < RESAMPLER_ZERO_CROSSINGS; j++) {
|
||||
int lwing = (i * RESAMPLER_SAMPLES_PER_FRAME) + (RESAMPLER_ZERO_CROSSINGS - 1) - j;
|
||||
int rwing = (RESAMPLER_FULL_FILTER_SIZE - 1) - lwing;
|
||||
|
||||
float value = ResamplerFilter[(i * RESAMPLER_ZERO_CROSSINGS) + j];
|
||||
FullResamplerFilter[lwing] = value;
|
||||
FullResamplerFilter[rwing] = value;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < RESAMPLER_ZERO_CROSSINGS; ++i) {
|
||||
int rwing = i + RESAMPLER_ZERO_CROSSINGS;
|
||||
int lwing = (RESAMPLER_FULL_FILTER_SIZE - 1) - rwing;
|
||||
|
||||
FullResamplerFilter[lwing] = 0.0f;
|
||||
FullResamplerFilter[rwing] = 0.0f;
|
||||
}
|
||||
|
||||
ResampleFrame = ResampleFrame_Scalar;
|
||||
|
||||
#ifdef SDL_SSE_INTRINSICS
|
||||
if (SDL_HasSSE()) {
|
||||
ResampleFrame = ResampleFrame_SSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
setup = SDL_TRUE;
|
||||
}
|
||||
|
||||
Sint64 SDL_GetResampleRate(int src_rate, int dst_rate)
|
||||
{
|
||||
SDL_assert(src_rate > 0);
|
||||
SDL_assert(dst_rate > 0);
|
||||
|
||||
Sint64 sample_rate = ((Sint64)src_rate << 32) / (Sint64)dst_rate;
|
||||
SDL_assert(sample_rate > 0);
|
||||
|
||||
return sample_rate;
|
||||
}
|
||||
|
||||
int SDL_GetResamplerHistoryFrames(void)
|
||||
{
|
||||
// Even if we aren't currently resampling, make sure to keep enough history in case we need to later.
|
||||
|
||||
return RESAMPLER_MAX_PADDING_FRAMES;
|
||||
}
|
||||
|
||||
int SDL_GetResamplerPaddingFrames(Sint64 resample_rate)
|
||||
{
|
||||
// This must always be <= SDL_GetResamplerHistoryFrames()
|
||||
|
||||
return resample_rate ? RESAMPLER_MAX_PADDING_FRAMES : 0;
|
||||
}
|
||||
|
||||
// These are not general purpose. They do not check for all possible underflow/overflow
|
||||
SDL_FORCE_INLINE Sint64 ResamplerAdd(Sint64 a, Sint64 b, Sint64 *ret)
|
||||
{
|
||||
if ((b > 0) && (a > SDL_MAX_SINT64 - b)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*ret = a + b;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_FORCE_INLINE Sint64 ResamplerMul(Sint64 a, Sint64 b, Sint64 *ret)
|
||||
{
|
||||
if ((b > 0) && (a > SDL_MAX_SINT64 / b)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*ret = a * b;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset)
|
||||
{
|
||||
// Calculate the index of the last input frame, then add 1.
|
||||
// ((((output_frames - 1) * resample_rate) + resample_offset) >> 32) + 1
|
||||
|
||||
Sint64 output_offset;
|
||||
if (ResamplerMul(output_frames, resample_rate, &output_offset) ||
|
||||
ResamplerAdd(output_offset, -resample_rate + resample_offset + 0x100000000, &output_offset)) {
|
||||
output_offset = SDL_MAX_SINT64;
|
||||
}
|
||||
|
||||
Sint64 input_frames = (Sint64)(Sint32)(output_offset >> 32);
|
||||
input_frames = SDL_max(input_frames, 0);
|
||||
|
||||
return input_frames;
|
||||
}
|
||||
|
||||
Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset)
|
||||
{
|
||||
Sint64 resample_offset = *inout_resample_offset;
|
||||
|
||||
// input_offset = (input_frames << 32) - resample_offset;
|
||||
Sint64 input_offset;
|
||||
if (ResamplerMul(input_frames, 0x100000000, &input_offset) ||
|
||||
ResamplerAdd(input_offset, -resample_offset, &input_offset)) {
|
||||
input_offset = SDL_MAX_SINT64;
|
||||
}
|
||||
|
||||
// output_frames = div_ceil(input_offset, resample_rate)
|
||||
Sint64 output_frames = (input_offset > 0) ? (((input_offset - 1) / resample_rate) + 1) : 0;
|
||||
|
||||
*inout_resample_offset = (output_frames * resample_rate) - input_offset;
|
||||
|
||||
return output_frames;
|
||||
}
|
||||
|
||||
void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
|
||||
Sint64 resample_rate, Sint64 *inout_resample_offset)
|
||||
{
|
||||
int i;
|
||||
Sint64 srcpos = *inout_resample_offset;
|
||||
|
||||
SDL_assert(resample_rate > 0);
|
||||
|
||||
for (i = 0; i < outframes; i++) {
|
||||
int srcindex = (int)(Sint32)(srcpos >> 32);
|
||||
Uint32 srcfraction = (Uint32)(srcpos & 0xFFFFFFFF);
|
||||
srcpos += resample_rate;
|
||||
|
||||
SDL_assert(srcindex >= -1 && srcindex < inframes);
|
||||
|
||||
const float *filter = &FullResamplerFilter[(srcfraction >> RESAMPLER_FILTER_INTERP_BITS) * RESAMPLER_SAMPLES_PER_FRAME];
|
||||
const float interp = (float)(srcfraction & (RESAMPLER_FILTER_INTERP_RANGE - 1)) * (1.0f / RESAMPLER_FILTER_INTERP_RANGE);
|
||||
|
||||
const float *frame = &src[(srcindex - (RESAMPLER_ZERO_CROSSINGS - 1)) * chans];
|
||||
ResampleFrame(frame, dst, filter, interp, chans);
|
||||
|
||||
dst += chans;
|
||||
}
|
||||
|
||||
*inout_resample_offset = srcpos - ((Sint64)inframes << 32);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_audioresample_h_
|
||||
#define SDL_audioresample_h_
|
||||
|
||||
// Internal functions used by SDL_AudioStream for resampling audio.
|
||||
// The resampler uses 32:32 fixed-point arithmetic to track its position.
|
||||
|
||||
Sint64 SDL_GetResampleRate(int src_rate, int dst_rate);
|
||||
|
||||
int SDL_GetResamplerHistoryFrames(void);
|
||||
int SDL_GetResamplerPaddingFrames(Sint64 resample_rate);
|
||||
|
||||
Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset);
|
||||
Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset);
|
||||
|
||||
// Resample some audio.
|
||||
// REQUIRES: `inframes >= SDL_GetResamplerInputFrames(outframes)`
|
||||
// REQUIRES: At least `SDL_GetResamplerPaddingFrames(...)` extra frames to the left of src, and right of src+inframes
|
||||
void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
|
||||
Sint64 resample_rate, Sint64 *inout_resample_offset);
|
||||
|
||||
#endif // SDL_audioresample_h_
|
||||
@@ -0,0 +1,990 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
// TODO: NEON is disabled until https://github.com/libsdl-org/SDL/issues/8352 can be fixed
|
||||
#undef SDL_NEON_INTRINSICS
|
||||
|
||||
#define DIVBY2147483648 0.0000000004656612873077392578125f // 0x1p-31f
|
||||
|
||||
// start fallback scalar converters
|
||||
|
||||
// This code requires that floats are in the IEEE-754 binary32 format
|
||||
SDL_COMPILE_TIME_ASSERT(float_bits, sizeof(float) == sizeof(Uint32));
|
||||
|
||||
union float_bits {
|
||||
Uint32 u32;
|
||||
float f32;
|
||||
};
|
||||
|
||||
static void SDL_Convert_S8_to_F32_Scalar(float *dst, const Sint8 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S8", "F32");
|
||||
|
||||
for (i = num_samples - 1; i >= 0; --i) {
|
||||
/* 1) Construct a float in the range [65536.0, 65538.0)
|
||||
* 2) Shift the float range to [-1.0, 1.0) */
|
||||
union float_bits x;
|
||||
x.u32 = (Uint8)src[i] ^ 0x47800080u;
|
||||
dst[i] = x.f32 - 65537.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_U8_to_F32_Scalar(float *dst, const Uint8 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("U8", "F32");
|
||||
|
||||
for (i = num_samples - 1; i >= 0; --i) {
|
||||
/* 1) Construct a float in the range [65536.0, 65538.0)
|
||||
* 2) Shift the float range to [-1.0, 1.0) */
|
||||
union float_bits x;
|
||||
x.u32 = src[i] ^ 0x47800000u;
|
||||
dst[i] = x.f32 - 65537.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_S16_to_F32_Scalar(float *dst, const Sint16 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S16", "F32");
|
||||
|
||||
for (i = num_samples - 1; i >= 0; --i) {
|
||||
/* 1) Construct a float in the range [256.0, 258.0)
|
||||
* 2) Shift the float range to [-1.0, 1.0) */
|
||||
union float_bits x;
|
||||
x.u32 = (Uint16)src[i] ^ 0x43808000u;
|
||||
dst[i] = x.f32 - 257.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_S32_to_F32_Scalar(float *dst, const Sint32 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S32", "F32");
|
||||
|
||||
for (i = num_samples - 1; i >= 0; --i) {
|
||||
dst[i] = (float)src[i] * DIVBY2147483648;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a bit-mask based on the sign-bit. Should optimize to a single arithmetic-shift-right
|
||||
#define SIGNMASK(x) (Uint32)(0u - ((Uint32)(x) >> 31))
|
||||
|
||||
static void SDL_Convert_F32_to_S8_Scalar(Sint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S8");
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
|
||||
* 2) Shift the integer range from [0x47BFFF80, 0x47C00080] to [-128, 128]
|
||||
* 3) Clamp the value to [-128, 127] */
|
||||
union float_bits x;
|
||||
x.f32 = src[i] + 98304.0f;
|
||||
|
||||
Uint32 y = x.u32 - 0x47C00000u;
|
||||
Uint32 z = 0x7Fu - (y ^ SIGNMASK(y));
|
||||
y = y ^ (z & SIGNMASK(z));
|
||||
|
||||
dst[i] = (Sint8)(y & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_U8_Scalar(Uint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "U8");
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
|
||||
* 2) Shift the integer range from [0x47BFFF80, 0x47C00080] to [-128, 128]
|
||||
* 3) Clamp the value to [-128, 127]
|
||||
* 4) Shift the integer range from [-128, 127] to [0, 255] */
|
||||
union float_bits x;
|
||||
x.f32 = src[i] + 98304.0f;
|
||||
|
||||
Uint32 y = x.u32 - 0x47C00000u;
|
||||
Uint32 z = 0x7Fu - (y ^ SIGNMASK(y));
|
||||
y = (y ^ 0x80u) ^ (z & SIGNMASK(z));
|
||||
|
||||
dst[i] = (Uint8)(y & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S16_Scalar(Sint16 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S16");
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [383.0, 385.0]
|
||||
* 2) Shift the integer range from [0x43BF8000, 0x43C08000] to [-32768, 32768]
|
||||
* 3) Clamp values outside the [-32768, 32767] range */
|
||||
union float_bits x;
|
||||
x.f32 = src[i] + 384.0f;
|
||||
|
||||
Uint32 y = x.u32 - 0x43C00000u;
|
||||
Uint32 z = 0x7FFFu - (y ^ SIGNMASK(y));
|
||||
y = y ^ (z & SIGNMASK(z));
|
||||
|
||||
dst[i] = (Sint16)(y & 0xFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S32_Scalar(Sint32 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S32");
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [-2147483648.0, 2147483648.0]
|
||||
* 2) Set values outside the [-2147483648.0, 2147483647.0] range to -2147483648.0
|
||||
* 3) Convert the float to an integer, and fixup values outside the valid range */
|
||||
union float_bits x;
|
||||
x.f32 = src[i];
|
||||
|
||||
Uint32 y = x.u32 + 0x0F800000u;
|
||||
Uint32 z = y - 0xCF000000u;
|
||||
z &= SIGNMASK(y ^ z);
|
||||
x.u32 = y - z;
|
||||
|
||||
dst[i] = (Sint32)x.f32 ^ (Sint32)SIGNMASK(z);
|
||||
}
|
||||
}
|
||||
|
||||
#undef SIGNMASK
|
||||
|
||||
// end fallback scalar converters
|
||||
|
||||
#ifdef SDL_SSE2_INTRINSICS
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_S8_to_F32_SSE2(float *dst, const Sint8 *src, int num_samples)
|
||||
{
|
||||
int i = num_samples;
|
||||
|
||||
/* 1) Flip the sign bit to convert from S8 to U8 format
|
||||
* 2) Construct a float in the range [65536.0, 65538.0)
|
||||
* 3) Shift the float range to [-1.0, 1.0)
|
||||
* dst[i] = i2f((src[i] ^ 0x80) | 0x47800000) - 65537.0 */
|
||||
const __m128i zero = _mm_setzero_si128();
|
||||
const __m128i flipper = _mm_set1_epi8(-0x80);
|
||||
const __m128i caster = _mm_set1_epi16(0x4780 /* 0x47800000 = f2i(65536.0) */);
|
||||
const __m128 offset = _mm_set1_ps(-65537.0);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using SSE2)");
|
||||
|
||||
while (i >= 16) {
|
||||
i -= 16;
|
||||
|
||||
const __m128i bytes = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i]), flipper);
|
||||
|
||||
const __m128i shorts1 = _mm_unpacklo_epi8(bytes, zero);
|
||||
const __m128i shorts2 = _mm_unpackhi_epi8(bytes, zero);
|
||||
|
||||
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
|
||||
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
|
||||
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts2, caster)), offset);
|
||||
const __m128 floats4 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts2, caster)), offset);
|
||||
|
||||
_mm_storeu_ps(&dst[i], floats1);
|
||||
_mm_storeu_ps(&dst[i + 4], floats2);
|
||||
_mm_storeu_ps(&dst[i + 8], floats3);
|
||||
_mm_storeu_ps(&dst[i + 12], floats4);
|
||||
}
|
||||
|
||||
while (i) {
|
||||
--i;
|
||||
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint8)src[i] ^ 0x47800080u)), offset));
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_U8_to_F32_SSE2(float *dst, const Uint8 *src, int num_samples)
|
||||
{
|
||||
int i = num_samples;
|
||||
|
||||
/* 1) Construct a float in the range [65536.0, 65538.0)
|
||||
* 2) Shift the float range to [-1.0, 1.0)
|
||||
* dst[i] = i2f(src[i] | 0x47800000) - 65537.0 */
|
||||
const __m128i zero = _mm_setzero_si128();
|
||||
const __m128i caster = _mm_set1_epi16(0x4780 /* 0x47800000 = f2i(65536.0) */);
|
||||
const __m128 offset = _mm_set1_ps(-65537.0);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using SSE2)");
|
||||
|
||||
while (i >= 16) {
|
||||
i -= 16;
|
||||
|
||||
const __m128i bytes = _mm_loadu_si128((const __m128i *)&src[i]);
|
||||
|
||||
const __m128i shorts1 = _mm_unpacklo_epi8(bytes, zero);
|
||||
const __m128i shorts2 = _mm_unpackhi_epi8(bytes, zero);
|
||||
|
||||
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
|
||||
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
|
||||
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts2, caster)), offset);
|
||||
const __m128 floats4 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts2, caster)), offset);
|
||||
|
||||
_mm_storeu_ps(&dst[i], floats1);
|
||||
_mm_storeu_ps(&dst[i + 4], floats2);
|
||||
_mm_storeu_ps(&dst[i + 8], floats3);
|
||||
_mm_storeu_ps(&dst[i + 12], floats4);
|
||||
}
|
||||
|
||||
while (i) {
|
||||
--i;
|
||||
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint8)src[i] ^ 0x47800000u)), offset));
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_S16_to_F32_SSE2(float *dst, const Sint16 *src, int num_samples)
|
||||
{
|
||||
int i = num_samples;
|
||||
|
||||
/* 1) Flip the sign bit to convert from S16 to U16 format
|
||||
* 2) Construct a float in the range [256.0, 258.0)
|
||||
* 3) Shift the float range to [-1.0, 1.0)
|
||||
* dst[i] = i2f((src[i] ^ 0x8000) | 0x43800000) - 257.0 */
|
||||
const __m128i flipper = _mm_set1_epi16(-0x8000);
|
||||
const __m128i caster = _mm_set1_epi16(0x4380 /* 0x43800000 = f2i(256.0) */);
|
||||
const __m128 offset = _mm_set1_ps(-257.0f);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using SSE2)");
|
||||
|
||||
while (i >= 16) {
|
||||
i -= 16;
|
||||
|
||||
const __m128i shorts1 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i]), flipper);
|
||||
const __m128i shorts2 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i + 8]), flipper);
|
||||
|
||||
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
|
||||
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
|
||||
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts2, caster)), offset);
|
||||
const __m128 floats4 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts2, caster)), offset);
|
||||
|
||||
_mm_storeu_ps(&dst[i], floats1);
|
||||
_mm_storeu_ps(&dst[i + 4], floats2);
|
||||
_mm_storeu_ps(&dst[i + 8], floats3);
|
||||
_mm_storeu_ps(&dst[i + 12], floats4);
|
||||
}
|
||||
|
||||
while (i) {
|
||||
--i;
|
||||
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint16)src[i] ^ 0x43808000u)), offset));
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_S32_to_F32_SSE2(float *dst, const Sint32 *src, int num_samples)
|
||||
{
|
||||
int i = num_samples;
|
||||
|
||||
// dst[i] = f32(src[i]) / f32(0x80000000)
|
||||
const __m128 scaler = _mm_set1_ps(DIVBY2147483648);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using SSE2)");
|
||||
|
||||
while (i >= 16) {
|
||||
i -= 16;
|
||||
|
||||
const __m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i]);
|
||||
const __m128i ints2 = _mm_loadu_si128((const __m128i *)&src[i + 4]);
|
||||
const __m128i ints3 = _mm_loadu_si128((const __m128i *)&src[i + 8]);
|
||||
const __m128i ints4 = _mm_loadu_si128((const __m128i *)&src[i + 12]);
|
||||
|
||||
const __m128 floats1 = _mm_mul_ps(_mm_cvtepi32_ps(ints1), scaler);
|
||||
const __m128 floats2 = _mm_mul_ps(_mm_cvtepi32_ps(ints2), scaler);
|
||||
const __m128 floats3 = _mm_mul_ps(_mm_cvtepi32_ps(ints3), scaler);
|
||||
const __m128 floats4 = _mm_mul_ps(_mm_cvtepi32_ps(ints4), scaler);
|
||||
|
||||
_mm_storeu_ps(&dst[i], floats1);
|
||||
_mm_storeu_ps(&dst[i + 4], floats2);
|
||||
_mm_storeu_ps(&dst[i + 8], floats3);
|
||||
_mm_storeu_ps(&dst[i + 12], floats4);
|
||||
}
|
||||
|
||||
while (i) {
|
||||
--i;
|
||||
_mm_store_ss(&dst[i], _mm_mul_ss(_mm_cvt_si2ss(_mm_setzero_ps(), src[i]), scaler));
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S8_SSE2(Sint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i = num_samples;
|
||||
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
|
||||
* 2) Extract the lowest 16 bits and clamp to [-128, 127]
|
||||
* Overflow is correctly handled for inputs between roughly [-255.0, 255.0]
|
||||
* dst[i] = clamp(i16(f2i(src[i] + 98304.0) & 0xFFFF), -128, 127) */
|
||||
const __m128 offset = _mm_set1_ps(98304.0f);
|
||||
const __m128i mask = _mm_set1_epi16(0xFF);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using SSE2)");
|
||||
|
||||
while (i >= 16) {
|
||||
const __m128 floats1 = _mm_loadu_ps(&src[0]);
|
||||
const __m128 floats2 = _mm_loadu_ps(&src[4]);
|
||||
const __m128 floats3 = _mm_loadu_ps(&src[8]);
|
||||
const __m128 floats4 = _mm_loadu_ps(&src[12]);
|
||||
|
||||
const __m128i ints1 = _mm_castps_si128(_mm_add_ps(floats1, offset));
|
||||
const __m128i ints2 = _mm_castps_si128(_mm_add_ps(floats2, offset));
|
||||
const __m128i ints3 = _mm_castps_si128(_mm_add_ps(floats3, offset));
|
||||
const __m128i ints4 = _mm_castps_si128(_mm_add_ps(floats4, offset));
|
||||
|
||||
const __m128i shorts1 = _mm_and_si128(_mm_packs_epi16(ints1, ints2), mask);
|
||||
const __m128i shorts2 = _mm_and_si128(_mm_packs_epi16(ints3, ints4), mask);
|
||||
|
||||
const __m128i bytes = _mm_packus_epi16(shorts1, shorts2);
|
||||
|
||||
_mm_storeu_si128((__m128i*)dst, bytes);
|
||||
|
||||
i -= 16;
|
||||
src += 16;
|
||||
dst += 16;
|
||||
}
|
||||
|
||||
while (i) {
|
||||
const __m128i ints = _mm_castps_si128(_mm_add_ss(_mm_load_ss(src), offset));
|
||||
*dst = (Sint8)(_mm_cvtsi128_si32(_mm_packs_epi16(ints, ints)) & 0xFF);
|
||||
|
||||
--i;
|
||||
++src;
|
||||
++dst;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_U8_SSE2(Uint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i = num_samples;
|
||||
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [98304.0, 98306.0]
|
||||
* 2) Extract the lowest 16 bits and clamp to [0, 255]
|
||||
* Overflow is correctly handled for inputs between roughly [-254.0, 254.0]
|
||||
* dst[i] = clamp(i16(f2i(src[i] + 98305.0) & 0xFFFF), 0, 255) */
|
||||
const __m128 offset = _mm_set1_ps(98305.0f);
|
||||
const __m128i mask = _mm_set1_epi16(0xFF);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using SSE2)");
|
||||
|
||||
while (i >= 16) {
|
||||
const __m128 floats1 = _mm_loadu_ps(&src[0]);
|
||||
const __m128 floats2 = _mm_loadu_ps(&src[4]);
|
||||
const __m128 floats3 = _mm_loadu_ps(&src[8]);
|
||||
const __m128 floats4 = _mm_loadu_ps(&src[12]);
|
||||
|
||||
const __m128i ints1 = _mm_castps_si128(_mm_add_ps(floats1, offset));
|
||||
const __m128i ints2 = _mm_castps_si128(_mm_add_ps(floats2, offset));
|
||||
const __m128i ints3 = _mm_castps_si128(_mm_add_ps(floats3, offset));
|
||||
const __m128i ints4 = _mm_castps_si128(_mm_add_ps(floats4, offset));
|
||||
|
||||
const __m128i shorts1 = _mm_and_si128(_mm_packus_epi16(ints1, ints2), mask);
|
||||
const __m128i shorts2 = _mm_and_si128(_mm_packus_epi16(ints3, ints4), mask);
|
||||
|
||||
const __m128i bytes = _mm_packus_epi16(shorts1, shorts2);
|
||||
|
||||
_mm_storeu_si128((__m128i*)dst, bytes);
|
||||
|
||||
i -= 16;
|
||||
src += 16;
|
||||
dst += 16;
|
||||
}
|
||||
|
||||
while (i) {
|
||||
const __m128i ints = _mm_castps_si128(_mm_add_ss(_mm_load_ss(src), offset));
|
||||
*dst = (Uint8)(_mm_cvtsi128_si32(_mm_packus_epi16(ints, ints)) & 0xFF);
|
||||
|
||||
--i;
|
||||
++src;
|
||||
++dst;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S16_SSE2(Sint16 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i = num_samples;
|
||||
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [256.0, 258.0]
|
||||
* 2) Shift the int range from [0x43800000, 0x43810000] to [-32768,32768]
|
||||
* 3) Clamp to range [-32768,32767]
|
||||
* Overflow is correctly handled for inputs between roughly [-257.0, +inf)
|
||||
* dst[i] = clamp(f2i(src[i] + 257.0) - 0x43808000, -32768, 32767) */
|
||||
const __m128 offset = _mm_set1_ps(257.0f);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using SSE2)");
|
||||
|
||||
while (i >= 16) {
|
||||
const __m128 floats1 = _mm_loadu_ps(&src[0]);
|
||||
const __m128 floats2 = _mm_loadu_ps(&src[4]);
|
||||
const __m128 floats3 = _mm_loadu_ps(&src[8]);
|
||||
const __m128 floats4 = _mm_loadu_ps(&src[12]);
|
||||
|
||||
const __m128i ints1 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats1, offset)), _mm_castps_si128(offset));
|
||||
const __m128i ints2 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats2, offset)), _mm_castps_si128(offset));
|
||||
const __m128i ints3 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats3, offset)), _mm_castps_si128(offset));
|
||||
const __m128i ints4 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats4, offset)), _mm_castps_si128(offset));
|
||||
|
||||
const __m128i shorts1 = _mm_packs_epi32(ints1, ints2);
|
||||
const __m128i shorts2 = _mm_packs_epi32(ints3, ints4);
|
||||
|
||||
_mm_storeu_si128((__m128i*)&dst[0], shorts1);
|
||||
_mm_storeu_si128((__m128i*)&dst[8], shorts2);
|
||||
|
||||
i -= 16;
|
||||
src += 16;
|
||||
dst += 16;
|
||||
}
|
||||
|
||||
while (i) {
|
||||
const __m128i ints = _mm_sub_epi32(_mm_castps_si128(_mm_add_ss(_mm_load_ss(src), offset)), _mm_castps_si128(offset));
|
||||
*dst = (Sint16)(_mm_cvtsi128_si32(_mm_packs_epi32(ints, ints)) & 0xFFFF);
|
||||
|
||||
--i;
|
||||
++src;
|
||||
++dst;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S32_SSE2(Sint32 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i = num_samples;
|
||||
|
||||
/* 1) Scale the float range from [-1.0, 1.0] to [-2147483648.0, 2147483648.0]
|
||||
* 2) Convert to integer (values too small/large become 0x80000000 = -2147483648)
|
||||
* 3) Fixup values which were too large (0x80000000 ^ 0xFFFFFFFF = 2147483647)
|
||||
* dst[i] = i32(src[i] * 2147483648.0) ^ ((src[i] >= 2147483648.0) ? 0xFFFFFFFF : 0x00000000) */
|
||||
const __m128 limit = _mm_set1_ps(2147483648.0f);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using SSE2)");
|
||||
|
||||
while (i >= 16) {
|
||||
const __m128 floats1 = _mm_loadu_ps(&src[0]);
|
||||
const __m128 floats2 = _mm_loadu_ps(&src[4]);
|
||||
const __m128 floats3 = _mm_loadu_ps(&src[8]);
|
||||
const __m128 floats4 = _mm_loadu_ps(&src[12]);
|
||||
|
||||
const __m128 values1 = _mm_mul_ps(floats1, limit);
|
||||
const __m128 values2 = _mm_mul_ps(floats2, limit);
|
||||
const __m128 values3 = _mm_mul_ps(floats3, limit);
|
||||
const __m128 values4 = _mm_mul_ps(floats4, limit);
|
||||
|
||||
const __m128i ints1 = _mm_xor_si128(_mm_cvttps_epi32(values1), _mm_castps_si128(_mm_cmpge_ps(values1, limit)));
|
||||
const __m128i ints2 = _mm_xor_si128(_mm_cvttps_epi32(values2), _mm_castps_si128(_mm_cmpge_ps(values2, limit)));
|
||||
const __m128i ints3 = _mm_xor_si128(_mm_cvttps_epi32(values3), _mm_castps_si128(_mm_cmpge_ps(values3, limit)));
|
||||
const __m128i ints4 = _mm_xor_si128(_mm_cvttps_epi32(values4), _mm_castps_si128(_mm_cmpge_ps(values4, limit)));
|
||||
|
||||
_mm_storeu_si128((__m128i*)&dst[0], ints1);
|
||||
_mm_storeu_si128((__m128i*)&dst[4], ints2);
|
||||
_mm_storeu_si128((__m128i*)&dst[8], ints3);
|
||||
_mm_storeu_si128((__m128i*)&dst[12], ints4);
|
||||
|
||||
i -= 16;
|
||||
src += 16;
|
||||
dst += 16;
|
||||
}
|
||||
|
||||
while (i) {
|
||||
const __m128 floats = _mm_load_ss(src);
|
||||
const __m128 values = _mm_mul_ss(floats, limit);
|
||||
const __m128i ints = _mm_xor_si128(_mm_cvttps_epi32(values), _mm_castps_si128(_mm_cmpge_ss(values, limit)));
|
||||
*dst = (Sint32)_mm_cvtsi128_si32(ints);
|
||||
|
||||
--i;
|
||||
++src;
|
||||
++dst;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
#define DIVBY128 0.0078125f // 0x1p-7f
|
||||
#define DIVBY32768 0.000030517578125f // 0x1p-15f
|
||||
#define DIVBY8388607 0.00000011920930376163766f // 0x1.000002p-23f
|
||||
|
||||
static void SDL_Convert_S8_to_F32_NEON(float *dst, const Sint8 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using NEON)");
|
||||
|
||||
src += num_samples - 1;
|
||||
dst += num_samples - 1;
|
||||
|
||||
// Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src)
|
||||
for (i = num_samples; i && (((size_t)(dst - 15)) & 15); --i, --src, --dst) {
|
||||
*dst = ((float)*src) * DIVBY128;
|
||||
}
|
||||
|
||||
src -= 15;
|
||||
dst -= 15; // adjust to read NEON blocks from the start.
|
||||
SDL_assert(!i || !(((size_t)dst) & 15));
|
||||
|
||||
// Make sure src is aligned too.
|
||||
if (!(((size_t)src) & 15)) {
|
||||
// Aligned! Do NEON blocks as long as we have 16 bytes available.
|
||||
const int8_t *mmsrc = (const int8_t *)src;
|
||||
const float32x4_t divby128 = vdupq_n_f32(DIVBY128);
|
||||
while (i >= 16) { // 16 * 8-bit
|
||||
const int8x16_t bytes = vld1q_s8(mmsrc); // get 16 sint8 into a NEON register.
|
||||
const int16x8_t int16hi = vmovl_s8(vget_high_s8(bytes)); // convert top 8 bytes to 8 int16
|
||||
const int16x8_t int16lo = vmovl_s8(vget_low_s8(bytes)); // convert bottom 8 bytes to 8 int16
|
||||
// split int16 to two int32, then convert to float, then multiply to normalize, store.
|
||||
vst1q_f32(dst, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(int16lo))), divby128));
|
||||
vst1q_f32(dst + 4, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(int16lo))), divby128));
|
||||
vst1q_f32(dst + 8, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(int16hi))), divby128));
|
||||
vst1q_f32(dst + 12, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(int16hi))), divby128));
|
||||
i -= 16;
|
||||
mmsrc -= 16;
|
||||
dst -= 16;
|
||||
}
|
||||
|
||||
src = (const Sint8 *)mmsrc;
|
||||
}
|
||||
|
||||
src += 15;
|
||||
dst += 15; // adjust for any scalar finishing.
|
||||
|
||||
// Finish off any leftovers with scalar operations.
|
||||
while (i) {
|
||||
*dst = ((float)*src) * DIVBY128;
|
||||
i--;
|
||||
src--;
|
||||
dst--;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_U8_to_F32_NEON(float *dst, const Uint8 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using NEON)");
|
||||
|
||||
src += num_samples - 1;
|
||||
dst += num_samples - 1;
|
||||
|
||||
// Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src)
|
||||
for (i = num_samples; i && (((size_t)(dst - 15)) & 15); --i, --src, --dst) {
|
||||
*dst = (((float)*src) * DIVBY128) - 1.0f;
|
||||
}
|
||||
|
||||
src -= 15;
|
||||
dst -= 15; // adjust to read NEON blocks from the start.
|
||||
SDL_assert(!i || !(((size_t)dst) & 15));
|
||||
|
||||
// Make sure src is aligned too.
|
||||
if (!(((size_t)src) & 15)) {
|
||||
// Aligned! Do NEON blocks as long as we have 16 bytes available.
|
||||
const uint8_t *mmsrc = (const uint8_t *)src;
|
||||
const float32x4_t divby128 = vdupq_n_f32(DIVBY128);
|
||||
const float32x4_t negone = vdupq_n_f32(-1.0f);
|
||||
while (i >= 16) { // 16 * 8-bit
|
||||
const uint8x16_t bytes = vld1q_u8(mmsrc); // get 16 uint8 into a NEON register.
|
||||
const uint16x8_t uint16hi = vmovl_u8(vget_high_u8(bytes)); // convert top 8 bytes to 8 uint16
|
||||
const uint16x8_t uint16lo = vmovl_u8(vget_low_u8(bytes)); // convert bottom 8 bytes to 8 uint16
|
||||
// split uint16 to two uint32, then convert to float, then multiply to normalize, subtract to adjust for sign, store.
|
||||
vst1q_f32(dst, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_low_u16(uint16lo))), divby128));
|
||||
vst1q_f32(dst + 4, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_high_u16(uint16lo))), divby128));
|
||||
vst1q_f32(dst + 8, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_low_u16(uint16hi))), divby128));
|
||||
vst1q_f32(dst + 12, vmlaq_f32(negone, vcvtq_f32_u32(vmovl_u16(vget_high_u16(uint16hi))), divby128));
|
||||
i -= 16;
|
||||
mmsrc -= 16;
|
||||
dst -= 16;
|
||||
}
|
||||
|
||||
src = (const Uint8 *)mmsrc;
|
||||
}
|
||||
|
||||
src += 15;
|
||||
dst += 15; // adjust for any scalar finishing.
|
||||
|
||||
// Finish off any leftovers with scalar operations.
|
||||
while (i) {
|
||||
*dst = (((float)*src) * DIVBY128) - 1.0f;
|
||||
i--;
|
||||
src--;
|
||||
dst--;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_S16_to_F32_NEON(float *dst, const Sint16 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using NEON)");
|
||||
|
||||
src += num_samples - 1;
|
||||
dst += num_samples - 1;
|
||||
|
||||
// Get dst aligned to 16 bytes (since buffer is growing, we don't have to worry about overreading from src)
|
||||
for (i = num_samples; i && (((size_t)(dst - 7)) & 15); --i, --src, --dst) {
|
||||
*dst = ((float)*src) * DIVBY32768;
|
||||
}
|
||||
|
||||
src -= 7;
|
||||
dst -= 7; // adjust to read NEON blocks from the start.
|
||||
SDL_assert(!i || !(((size_t)dst) & 15));
|
||||
|
||||
// Make sure src is aligned too.
|
||||
if (!(((size_t)src) & 15)) {
|
||||
// Aligned! Do NEON blocks as long as we have 16 bytes available.
|
||||
const float32x4_t divby32768 = vdupq_n_f32(DIVBY32768);
|
||||
while (i >= 8) { // 8 * 16-bit
|
||||
const int16x8_t ints = vld1q_s16((int16_t const *)src); // get 8 sint16 into a NEON register.
|
||||
// split int16 to two int32, then convert to float, then multiply to normalize, store.
|
||||
vst1q_f32(dst, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(ints))), divby32768));
|
||||
vst1q_f32(dst + 4, vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(ints))), divby32768));
|
||||
i -= 8;
|
||||
src -= 8;
|
||||
dst -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
src += 7;
|
||||
dst += 7; // adjust for any scalar finishing.
|
||||
|
||||
// Finish off any leftovers with scalar operations.
|
||||
while (i) {
|
||||
*dst = ((float)*src) * DIVBY32768;
|
||||
i--;
|
||||
src--;
|
||||
dst--;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_S32_to_F32_NEON(float *dst, const Sint32 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using NEON)");
|
||||
|
||||
// Get dst aligned to 16 bytes
|
||||
for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) {
|
||||
*dst = ((float)(*src >> 8)) * DIVBY8388607;
|
||||
}
|
||||
|
||||
SDL_assert(!i || !(((size_t)dst) & 15));
|
||||
|
||||
// Make sure src is aligned too.
|
||||
if (!(((size_t)src) & 15)) {
|
||||
// Aligned! Do NEON blocks as long as we have 16 bytes available.
|
||||
const float32x4_t divby8388607 = vdupq_n_f32(DIVBY8388607);
|
||||
const int32_t *mmsrc = (const int32_t *)src;
|
||||
while (i >= 4) { // 4 * sint32
|
||||
// shift out lowest bits so int fits in a float32. Small precision loss, but much faster.
|
||||
vst1q_f32(dst, vmulq_f32(vcvtq_f32_s32(vshrq_n_s32(vld1q_s32(mmsrc), 8)), divby8388607));
|
||||
i -= 4;
|
||||
mmsrc += 4;
|
||||
dst += 4;
|
||||
}
|
||||
src = (const Sint32 *)mmsrc;
|
||||
}
|
||||
|
||||
// Finish off any leftovers with scalar operations.
|
||||
while (i) {
|
||||
*dst = ((float)(*src >> 8)) * DIVBY8388607;
|
||||
i--;
|
||||
src++;
|
||||
dst++;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using NEON)");
|
||||
|
||||
// Get dst aligned to 16 bytes
|
||||
for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) {
|
||||
const float sample = *src;
|
||||
if (sample >= 1.0f) {
|
||||
*dst = 127;
|
||||
} else if (sample <= -1.0f) {
|
||||
*dst = -128;
|
||||
} else {
|
||||
*dst = (Sint8)(sample * 127.0f);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_assert(!i || !(((size_t)dst) & 15));
|
||||
|
||||
// Make sure src is aligned too.
|
||||
if (!(((size_t)src) & 15)) {
|
||||
// Aligned! Do NEON blocks as long as we have 16 bytes available.
|
||||
const float32x4_t one = vdupq_n_f32(1.0f);
|
||||
const float32x4_t negone = vdupq_n_f32(-1.0f);
|
||||
const float32x4_t mulby127 = vdupq_n_f32(127.0f);
|
||||
int8_t *mmdst = (int8_t *)dst;
|
||||
while (i >= 16) { // 16 * float32
|
||||
const int32x4_t ints1 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), mulby127)); // load 4 floats, clamp, convert to sint32
|
||||
const int32x4_t ints2 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 4)), one), mulby127)); // load 4 floats, clamp, convert to sint32
|
||||
const int32x4_t ints3 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 8)), one), mulby127)); // load 4 floats, clamp, convert to sint32
|
||||
const int32x4_t ints4 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 12)), one), mulby127)); // load 4 floats, clamp, convert to sint32
|
||||
const int8x8_t i8lo = vmovn_s16(vcombine_s16(vmovn_s32(ints1), vmovn_s32(ints2))); // narrow to sint16, combine, narrow to sint8
|
||||
const int8x8_t i8hi = vmovn_s16(vcombine_s16(vmovn_s32(ints3), vmovn_s32(ints4))); // narrow to sint16, combine, narrow to sint8
|
||||
vst1q_s8(mmdst, vcombine_s8(i8lo, i8hi)); // combine to int8x16_t, store out
|
||||
i -= 16;
|
||||
src += 16;
|
||||
mmdst += 16;
|
||||
}
|
||||
dst = (Sint8 *)mmdst;
|
||||
}
|
||||
|
||||
// Finish off any leftovers with scalar operations.
|
||||
while (i) {
|
||||
const float sample = *src;
|
||||
if (sample >= 1.0f) {
|
||||
*dst = 127;
|
||||
} else if (sample <= -1.0f) {
|
||||
*dst = -128;
|
||||
} else {
|
||||
*dst = (Sint8)(sample * 127.0f);
|
||||
}
|
||||
i--;
|
||||
src++;
|
||||
dst++;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using NEON)");
|
||||
|
||||
// Get dst aligned to 16 bytes
|
||||
for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) {
|
||||
const float sample = *src;
|
||||
if (sample >= 1.0f) {
|
||||
*dst = 255;
|
||||
} else if (sample <= -1.0f) {
|
||||
*dst = 0;
|
||||
} else {
|
||||
*dst = (Uint8)((sample + 1.0f) * 127.0f);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_assert(!i || !(((size_t)dst) & 15));
|
||||
|
||||
// Make sure src is aligned too.
|
||||
if (!(((size_t)src) & 15)) {
|
||||
// Aligned! Do NEON blocks as long as we have 16 bytes available.
|
||||
const float32x4_t one = vdupq_n_f32(1.0f);
|
||||
const float32x4_t negone = vdupq_n_f32(-1.0f);
|
||||
const float32x4_t mulby127 = vdupq_n_f32(127.0f);
|
||||
uint8_t *mmdst = (uint8_t *)dst;
|
||||
while (i >= 16) { // 16 * float32
|
||||
const uint32x4_t uints1 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), one), mulby127)); // load 4 floats, clamp, convert to uint32
|
||||
const uint32x4_t uints2 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 4)), one), one), mulby127)); // load 4 floats, clamp, convert to uint32
|
||||
const uint32x4_t uints3 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 8)), one), one), mulby127)); // load 4 floats, clamp, convert to uint32
|
||||
const uint32x4_t uints4 = vcvtq_u32_f32(vmulq_f32(vaddq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 12)), one), one), mulby127)); // load 4 floats, clamp, convert to uint32
|
||||
const uint8x8_t ui8lo = vmovn_u16(vcombine_u16(vmovn_u32(uints1), vmovn_u32(uints2))); // narrow to uint16, combine, narrow to uint8
|
||||
const uint8x8_t ui8hi = vmovn_u16(vcombine_u16(vmovn_u32(uints3), vmovn_u32(uints4))); // narrow to uint16, combine, narrow to uint8
|
||||
vst1q_u8(mmdst, vcombine_u8(ui8lo, ui8hi)); // combine to uint8x16_t, store out
|
||||
i -= 16;
|
||||
src += 16;
|
||||
mmdst += 16;
|
||||
}
|
||||
|
||||
dst = (Uint8 *)mmdst;
|
||||
}
|
||||
|
||||
// Finish off any leftovers with scalar operations.
|
||||
while (i) {
|
||||
const float sample = *src;
|
||||
if (sample >= 1.0f) {
|
||||
*dst = 255;
|
||||
} else if (sample <= -1.0f) {
|
||||
*dst = 0;
|
||||
} else {
|
||||
*dst = (Uint8)((sample + 1.0f) * 127.0f);
|
||||
}
|
||||
i--;
|
||||
src++;
|
||||
dst++;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using NEON)");
|
||||
|
||||
// Get dst aligned to 16 bytes
|
||||
for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) {
|
||||
const float sample = *src;
|
||||
if (sample >= 1.0f) {
|
||||
*dst = 32767;
|
||||
} else if (sample <= -1.0f) {
|
||||
*dst = -32768;
|
||||
} else {
|
||||
*dst = (Sint16)(sample * 32767.0f);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_assert(!i || !(((size_t)dst) & 15));
|
||||
|
||||
// Make sure src is aligned too.
|
||||
if (!(((size_t)src) & 15)) {
|
||||
// Aligned! Do NEON blocks as long as we have 16 bytes available.
|
||||
const float32x4_t one = vdupq_n_f32(1.0f);
|
||||
const float32x4_t negone = vdupq_n_f32(-1.0f);
|
||||
const float32x4_t mulby32767 = vdupq_n_f32(32767.0f);
|
||||
int16_t *mmdst = (int16_t *)dst;
|
||||
while (i >= 8) { // 8 * float32
|
||||
const int32x4_t ints1 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), mulby32767)); // load 4 floats, clamp, convert to sint32
|
||||
const int32x4_t ints2 = vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src + 4)), one), mulby32767)); // load 4 floats, clamp, convert to sint32
|
||||
vst1q_s16(mmdst, vcombine_s16(vmovn_s32(ints1), vmovn_s32(ints2))); // narrow to sint16, combine, store out.
|
||||
i -= 8;
|
||||
src += 8;
|
||||
mmdst += 8;
|
||||
}
|
||||
dst = (Sint16 *)mmdst;
|
||||
}
|
||||
|
||||
// Finish off any leftovers with scalar operations.
|
||||
while (i) {
|
||||
const float sample = *src;
|
||||
if (sample >= 1.0f) {
|
||||
*dst = 32767;
|
||||
} else if (sample <= -1.0f) {
|
||||
*dst = -32768;
|
||||
} else {
|
||||
*dst = (Sint16)(sample * 32767.0f);
|
||||
}
|
||||
i--;
|
||||
src++;
|
||||
dst++;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using NEON)");
|
||||
|
||||
// Get dst aligned to 16 bytes
|
||||
for (i = num_samples; i && (((size_t)dst) & 15); --i, ++src, ++dst) {
|
||||
const float sample = *src;
|
||||
if (sample >= 1.0f) {
|
||||
*dst = 2147483647;
|
||||
} else if (sample <= -1.0f) {
|
||||
*dst = (-2147483647) - 1;
|
||||
} else {
|
||||
*dst = ((Sint32)(sample * 8388607.0f)) << 8;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_assert(!i || !(((size_t)dst) & 15));
|
||||
SDL_assert(!i || !(((size_t)src) & 15));
|
||||
|
||||
{
|
||||
// Aligned! Do NEON blocks as long as we have 16 bytes available.
|
||||
const float32x4_t one = vdupq_n_f32(1.0f);
|
||||
const float32x4_t negone = vdupq_n_f32(-1.0f);
|
||||
const float32x4_t mulby8388607 = vdupq_n_f32(8388607.0f);
|
||||
int32_t *mmdst = (int32_t *)dst;
|
||||
while (i >= 4) { // 4 * float32
|
||||
vst1q_s32(mmdst, vshlq_n_s32(vcvtq_s32_f32(vmulq_f32(vminq_f32(vmaxq_f32(negone, vld1q_f32(src)), one), mulby8388607)), 8));
|
||||
i -= 4;
|
||||
src += 4;
|
||||
mmdst += 4;
|
||||
}
|
||||
dst = (Sint32 *)mmdst;
|
||||
}
|
||||
|
||||
// Finish off any leftovers with scalar operations.
|
||||
while (i) {
|
||||
const float sample = *src;
|
||||
if (sample >= 1.0f) {
|
||||
*dst = 2147483647;
|
||||
} else if (sample <= -1.0f) {
|
||||
*dst = (-2147483647) - 1;
|
||||
} else {
|
||||
*dst = ((Sint32)(sample * 8388607.0f)) << 8;
|
||||
}
|
||||
i--;
|
||||
src++;
|
||||
dst++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Function pointers set to a CPU-specific implementation.
|
||||
void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples) = NULL;
|
||||
void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples) = NULL;
|
||||
void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples) = NULL;
|
||||
void (*SDL_Convert_S32_to_F32)(float *dst, const Sint32 *src, int num_samples) = NULL;
|
||||
void (*SDL_Convert_F32_to_S8)(Sint8 *dst, const float *src, int num_samples) = NULL;
|
||||
void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples) = NULL;
|
||||
void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples) = NULL;
|
||||
void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples) = NULL;
|
||||
|
||||
void SDL_ChooseAudioConverters(void)
|
||||
{
|
||||
static SDL_bool converters_chosen = SDL_FALSE;
|
||||
if (converters_chosen) {
|
||||
return;
|
||||
}
|
||||
|
||||
#define SET_CONVERTER_FUNCS(fntype) \
|
||||
SDL_Convert_S8_to_F32 = SDL_Convert_S8_to_F32_##fntype; \
|
||||
SDL_Convert_U8_to_F32 = SDL_Convert_U8_to_F32_##fntype; \
|
||||
SDL_Convert_S16_to_F32 = SDL_Convert_S16_to_F32_##fntype; \
|
||||
SDL_Convert_S32_to_F32 = SDL_Convert_S32_to_F32_##fntype; \
|
||||
SDL_Convert_F32_to_S8 = SDL_Convert_F32_to_S8_##fntype; \
|
||||
SDL_Convert_F32_to_U8 = SDL_Convert_F32_to_U8_##fntype; \
|
||||
SDL_Convert_F32_to_S16 = SDL_Convert_F32_to_S16_##fntype; \
|
||||
SDL_Convert_F32_to_S32 = SDL_Convert_F32_to_S32_##fntype; \
|
||||
converters_chosen = SDL_TRUE
|
||||
|
||||
#ifdef SDL_SSE2_INTRINSICS
|
||||
if (SDL_HasSSE2()) {
|
||||
SET_CONVERTER_FUNCS(SSE2);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
if (SDL_HasNEON()) {
|
||||
SET_CONVERTER_FUNCS(NEON);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
SET_CONVERTER_FUNCS(Scalar);
|
||||
|
||||
#undef SET_CONVERTER_FUNCS
|
||||
|
||||
SDL_assert(converters_chosen == SDL_TRUE);
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// This provides the default mixing callback for the SDL audio routines
|
||||
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
/* This table is used to add two sound values together and pin
|
||||
* the value to avoid overflow. (used with permission from ARDI)
|
||||
*/
|
||||
static const Uint8 mix8[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03,
|
||||
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
|
||||
0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24,
|
||||
0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
|
||||
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
|
||||
0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
|
||||
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B,
|
||||
0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
|
||||
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71,
|
||||
0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C,
|
||||
0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
||||
0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92,
|
||||
0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
|
||||
0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8,
|
||||
0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3,
|
||||
0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
|
||||
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9,
|
||||
0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4,
|
||||
0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
|
||||
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA,
|
||||
0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5,
|
||||
0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
||||
};
|
||||
|
||||
// The volume ranges from 0 - 128
|
||||
#define ADJUST_VOLUME(type, s, v) ((s) = (type)(((s) * (v)) / SDL_MIX_MAXVOLUME))
|
||||
#define ADJUST_VOLUME_U8(s, v) ((s) = (Uint8)(((((s) - 128) * (v)) / SDL_MIX_MAXVOLUME) + 128))
|
||||
|
||||
|
||||
// !!! FIXME: this needs some SIMD magic.
|
||||
|
||||
int SDL_MixAudioFormat(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format,
|
||||
Uint32 len, int volume)
|
||||
{
|
||||
if (volume == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
|
||||
case SDL_AUDIO_U8:
|
||||
{
|
||||
Uint8 src_sample;
|
||||
|
||||
while (len--) {
|
||||
src_sample = *src;
|
||||
ADJUST_VOLUME_U8(src_sample, volume);
|
||||
*dst = mix8[*dst + src_sample];
|
||||
++dst;
|
||||
++src;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S8:
|
||||
{
|
||||
Sint8 *dst8, *src8;
|
||||
Sint8 src_sample;
|
||||
int dst_sample;
|
||||
const int max_audioval = SDL_MAX_SINT8;
|
||||
const int min_audioval = SDL_MIN_SINT8;
|
||||
|
||||
src8 = (Sint8 *)src;
|
||||
dst8 = (Sint8 *)dst;
|
||||
while (len--) {
|
||||
src_sample = *src8;
|
||||
ADJUST_VOLUME(Sint8, src_sample, volume);
|
||||
dst_sample = *dst8 + src_sample;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*dst8 = (Sint8)dst_sample;
|
||||
++dst8;
|
||||
++src8;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S16LE:
|
||||
{
|
||||
Sint16 src1, src2;
|
||||
int dst_sample;
|
||||
const int max_audioval = SDL_MAX_SINT16;
|
||||
const int min_audioval = SDL_MIN_SINT16;
|
||||
|
||||
len /= 2;
|
||||
while (len--) {
|
||||
src1 = SDL_SwapLE16(*(Sint16 *)src);
|
||||
ADJUST_VOLUME(Sint16, src1, volume);
|
||||
src2 = SDL_SwapLE16(*(Sint16 *)dst);
|
||||
src += 2;
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(Sint16 *)dst = SDL_SwapLE16((Sint16)dst_sample);
|
||||
dst += 2;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S16BE:
|
||||
{
|
||||
Sint16 src1, src2;
|
||||
int dst_sample;
|
||||
const int max_audioval = SDL_MAX_SINT16;
|
||||
const int min_audioval = SDL_MIN_SINT16;
|
||||
|
||||
len /= 2;
|
||||
while (len--) {
|
||||
src1 = SDL_SwapBE16(*(Sint16 *)src);
|
||||
ADJUST_VOLUME(Sint16, src1, volume);
|
||||
src2 = SDL_SwapBE16(*(Sint16 *)dst);
|
||||
src += 2;
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(Sint16 *)dst = SDL_SwapBE16((Sint16)dst_sample);
|
||||
dst += 2;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S32LE:
|
||||
{
|
||||
const Uint32 *src32 = (Uint32 *)src;
|
||||
Uint32 *dst32 = (Uint32 *)dst;
|
||||
Sint64 src1, src2;
|
||||
Sint64 dst_sample;
|
||||
const Sint64 max_audioval = SDL_MAX_SINT32;
|
||||
const Sint64 min_audioval = SDL_MIN_SINT32;
|
||||
|
||||
len /= 4;
|
||||
while (len--) {
|
||||
src1 = (Sint64)((Sint32)SDL_SwapLE32(*src32));
|
||||
src32++;
|
||||
ADJUST_VOLUME(Sint64, src1, volume);
|
||||
src2 = (Sint64)((Sint32)SDL_SwapLE32(*dst32));
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(dst32++) = SDL_SwapLE32((Uint32)((Sint32)dst_sample));
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S32BE:
|
||||
{
|
||||
const Uint32 *src32 = (Uint32 *)src;
|
||||
Uint32 *dst32 = (Uint32 *)dst;
|
||||
Sint64 src1, src2;
|
||||
Sint64 dst_sample;
|
||||
const Sint64 max_audioval = SDL_MAX_SINT32;
|
||||
const Sint64 min_audioval = SDL_MIN_SINT32;
|
||||
|
||||
len /= 4;
|
||||
while (len--) {
|
||||
src1 = (Sint64)((Sint32)SDL_SwapBE32(*src32));
|
||||
src32++;
|
||||
ADJUST_VOLUME(Sint64, src1, volume);
|
||||
src2 = (Sint64)((Sint32)SDL_SwapBE32(*dst32));
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(dst32++) = SDL_SwapBE32((Uint32)((Sint32)dst_sample));
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_F32LE:
|
||||
{
|
||||
const float fmaxvolume = 1.0f / ((float)SDL_MIX_MAXVOLUME);
|
||||
const float fvolume = (float)volume;
|
||||
const float *src32 = (float *)src;
|
||||
float *dst32 = (float *)dst;
|
||||
float src1, src2;
|
||||
double dst_sample;
|
||||
// !!! FIXME: are these right?
|
||||
const double max_audioval = 3.402823466e+38F;
|
||||
const double min_audioval = -3.402823466e+38F;
|
||||
|
||||
len /= 4;
|
||||
while (len--) {
|
||||
src1 = ((SDL_SwapFloatLE(*src32) * fvolume) * fmaxvolume);
|
||||
src2 = SDL_SwapFloatLE(*dst32);
|
||||
src32++;
|
||||
|
||||
dst_sample = ((double)src1) + ((double)src2);
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(dst32++) = SDL_SwapFloatLE((float)dst_sample);
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_F32BE:
|
||||
{
|
||||
const float fmaxvolume = 1.0f / ((float)SDL_MIX_MAXVOLUME);
|
||||
const float fvolume = (float)volume;
|
||||
const float *src32 = (float *)src;
|
||||
float *dst32 = (float *)dst;
|
||||
float src1, src2;
|
||||
double dst_sample;
|
||||
// !!! FIXME: are these right?
|
||||
const double max_audioval = 3.402823466e+38F;
|
||||
const double min_audioval = -3.402823466e+38F;
|
||||
|
||||
len /= 4;
|
||||
while (len--) {
|
||||
src1 = ((SDL_SwapFloatBE(*src32) * fvolume) * fmaxvolume);
|
||||
src2 = SDL_SwapFloatBE(*dst32);
|
||||
src32++;
|
||||
|
||||
dst_sample = ((double)src1) + ((double)src2);
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(dst32++) = SDL_SwapFloatBE((float)dst_sample);
|
||||
}
|
||||
} break;
|
||||
|
||||
default: // If this happens... FIXME!
|
||||
return SDL_SetError("SDL_MixAudioFormat(): unknown audio format");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_sysaudio_h_
|
||||
#define SDL_sysaudio_h_
|
||||
|
||||
#include "../SDL_hashtable.h"
|
||||
|
||||
#define DEBUG_AUDIOSTREAM 0
|
||||
#define DEBUG_AUDIO_CONVERT 0
|
||||
|
||||
#if DEBUG_AUDIO_CONVERT
|
||||
#define LOG_DEBUG_AUDIO_CONVERT(from, to) SDL_Log("SDL_AUDIO_CONVERT: Converting %s to %s.\n", from, to);
|
||||
#else
|
||||
#define LOG_DEBUG_AUDIO_CONVERT(from, to)
|
||||
#endif
|
||||
|
||||
// These pointers get set during SDL_ChooseAudioConverters() to various SIMD implementations.
|
||||
extern void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples);
|
||||
extern void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples);
|
||||
extern void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples);
|
||||
extern void (*SDL_Convert_S32_to_F32)(float *dst, const Sint32 *src, int num_samples);
|
||||
extern void (*SDL_Convert_F32_to_S8)(Sint8 *dst, const float *src, int num_samples);
|
||||
extern void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples);
|
||||
extern void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples);
|
||||
extern void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples);
|
||||
|
||||
// !!! FIXME: These are wordy and unlocalized...
|
||||
#define DEFAULT_OUTPUT_DEVNAME "System audio output device"
|
||||
#define DEFAULT_INPUT_DEVNAME "System audio capture device"
|
||||
|
||||
// these are used when no better specifics are known. We default to CD audio quality.
|
||||
#define DEFAULT_AUDIO_OUTPUT_FORMAT SDL_AUDIO_S16
|
||||
#define DEFAULT_AUDIO_OUTPUT_CHANNELS 2
|
||||
#define DEFAULT_AUDIO_OUTPUT_FREQUENCY 44100
|
||||
|
||||
#define DEFAULT_AUDIO_CAPTURE_FORMAT SDL_AUDIO_S16
|
||||
#define DEFAULT_AUDIO_CAPTURE_CHANNELS 1
|
||||
#define DEFAULT_AUDIO_CAPTURE_FREQUENCY 44100
|
||||
|
||||
#define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq))
|
||||
|
||||
typedef struct SDL_AudioDevice SDL_AudioDevice;
|
||||
typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice;
|
||||
|
||||
// Used by src/SDL.c to initialize a particular audio driver.
|
||||
extern int SDL_InitAudio(const char *driver_name);
|
||||
|
||||
// Used by src/SDL.c to shut down previously-initialized audio.
|
||||
extern void SDL_QuitAudio(void);
|
||||
|
||||
// Function to get a list of audio formats, ordered most similar to `format` to least, 0-terminated. Don't free results.
|
||||
const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format);
|
||||
|
||||
// Must be called at least once before using converters.
|
||||
extern void SDL_ChooseAudioConverters(void);
|
||||
extern void SDL_SetupAudioResampler(void);
|
||||
|
||||
/* Backends should call this as devices are added to the system (such as
|
||||
a USB headset being plugged in), and should also be called for
|
||||
for every device found during DetectDevices(). */
|
||||
extern SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, const SDL_AudioSpec *spec, void *handle);
|
||||
|
||||
/* Backends should call this if an opened audio device is lost.
|
||||
This can happen due to i/o errors, or a device being unplugged, etc. */
|
||||
extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device);
|
||||
|
||||
// Backends should call this if the system default device changes.
|
||||
extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device);
|
||||
|
||||
// Backends should call this if a device's format is changing (opened or not); SDL will update state and carry on with the new format.
|
||||
extern int SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
|
||||
|
||||
// Same as above, but assume the device is already locked.
|
||||
extern int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
|
||||
|
||||
// Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. DOES NOT LOCK THE DEVICE.
|
||||
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle);
|
||||
|
||||
// Find an SDL_AudioDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
|
||||
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(SDL_bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata);
|
||||
|
||||
// Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct.
|
||||
extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device);
|
||||
|
||||
// Backends can call this to get a standardized name for a thread to power a specific audio device.
|
||||
extern char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen);
|
||||
|
||||
// Backends can call these to change a device's refcount.
|
||||
extern void RefPhysicalAudioDevice(SDL_AudioDevice *device);
|
||||
extern void UnrefPhysicalAudioDevice(SDL_AudioDevice *device);
|
||||
|
||||
// These functions are the heart of the audio threads. Backends can call them directly if they aren't using the SDL-provided thread.
|
||||
extern void SDL_OutputAudioThreadSetup(SDL_AudioDevice *device);
|
||||
extern SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device);
|
||||
extern void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device);
|
||||
extern void SDL_CaptureAudioThreadSetup(SDL_AudioDevice *device);
|
||||
extern SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device);
|
||||
extern void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device);
|
||||
extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device);
|
||||
|
||||
// this gets used from the audio device threads. It has rules, don't use this if you don't know how to use it!
|
||||
extern void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, int src_channels,
|
||||
void *dst, SDL_AudioFormat dst_format, int dst_channels, void* scratch);
|
||||
|
||||
// Special case to let something in SDL_audiocvt.c access something in SDL_audio.c. Don't use this.
|
||||
extern void OnAudioStreamCreated(SDL_AudioStream *stream);
|
||||
extern void OnAudioStreamDestroy(SDL_AudioStream *stream);
|
||||
|
||||
typedef struct SDL_AudioDriverImpl
|
||||
{
|
||||
void (*DetectDevices)(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture);
|
||||
int (*OpenDevice)(SDL_AudioDevice *device);
|
||||
void (*ThreadInit)(SDL_AudioDevice *device); // Called by audio thread at start
|
||||
void (*ThreadDeinit)(SDL_AudioDevice *device); // Called by audio thread at end
|
||||
int (*WaitDevice)(SDL_AudioDevice *device);
|
||||
int (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen); // buffer and buflen are always from GetDeviceBuf, passed here for convenience.
|
||||
Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
|
||||
int (*WaitCaptureDevice)(SDL_AudioDevice *device);
|
||||
int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
|
||||
void (*FlushCapture)(SDL_AudioDevice *device);
|
||||
void (*CloseDevice)(SDL_AudioDevice *device);
|
||||
void (*FreeDeviceHandle)(SDL_AudioDevice *device); // SDL is done with this device; free the handle from SDL_AddAudioDevice()
|
||||
void (*DeinitializeStart)(void); // SDL calls this, then starts destroying objects, then calls Deinitialize. This is a good place to stop hotplug detection.
|
||||
void (*Deinitialize)(void);
|
||||
|
||||
// Some flags to push duplicate code into the core and reduce #ifdefs.
|
||||
SDL_bool ProvidesOwnCallbackThread; // !!! FIXME: rename this, it's not a callback thread anymore.
|
||||
SDL_bool HasCaptureSupport;
|
||||
SDL_bool OnlyHasDefaultOutputDevice;
|
||||
SDL_bool OnlyHasDefaultCaptureDevice; // !!! FIXME: is there ever a time where you'd have a default output and not a default capture (or vice versa)?
|
||||
} SDL_AudioDriverImpl;
|
||||
|
||||
|
||||
typedef struct SDL_PendingAudioDeviceEvent
|
||||
{
|
||||
Uint32 type;
|
||||
SDL_AudioDeviceID devid;
|
||||
struct SDL_PendingAudioDeviceEvent *next;
|
||||
} SDL_PendingAudioDeviceEvent;
|
||||
|
||||
typedef struct SDL_AudioDriver
|
||||
{
|
||||
const char *name; // The name of this audio driver
|
||||
const char *desc; // The description of this audio driver
|
||||
SDL_AudioDriverImpl impl; // the backend's interface
|
||||
SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash`
|
||||
SDL_HashTable *device_hash; // the collection of currently-available audio devices (capture, playback, logical and physical!)
|
||||
SDL_AudioStream *existing_streams; // a list of all existing SDL_AudioStreams.
|
||||
SDL_AudioDeviceID default_output_device_id;
|
||||
SDL_AudioDeviceID default_capture_device_id;
|
||||
SDL_PendingAudioDeviceEvent pending_events;
|
||||
SDL_PendingAudioDeviceEvent *pending_events_tail;
|
||||
|
||||
// !!! FIXME: most (all?) of these don't have to be atomic.
|
||||
SDL_AtomicInt output_device_count;
|
||||
SDL_AtomicInt capture_device_count;
|
||||
SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs.
|
||||
} SDL_AudioDriver;
|
||||
|
||||
struct SDL_AudioQueue; // forward decl.
|
||||
|
||||
struct SDL_AudioStream
|
||||
{
|
||||
SDL_Mutex* lock;
|
||||
|
||||
SDL_PropertiesID props;
|
||||
|
||||
SDL_AudioStreamCallback get_callback;
|
||||
void *get_callback_userdata;
|
||||
SDL_AudioStreamCallback put_callback;
|
||||
void *put_callback_userdata;
|
||||
|
||||
SDL_AudioSpec src_spec;
|
||||
SDL_AudioSpec dst_spec;
|
||||
float freq_ratio;
|
||||
|
||||
struct SDL_AudioQueue* queue;
|
||||
Uint64 total_bytes_queued;
|
||||
|
||||
SDL_AudioSpec input_spec; // The spec of input data currently being processed
|
||||
Sint64 resample_offset;
|
||||
|
||||
Uint8 *work_buffer; // used for scratch space during data conversion/resampling.
|
||||
size_t work_buffer_allocation;
|
||||
|
||||
Uint8 *history_buffer; // history for left padding and future sample rate changes.
|
||||
size_t history_buffer_allocation;
|
||||
|
||||
SDL_bool simplified; // SDL_TRUE if created via SDL_OpenAudioDeviceStream
|
||||
|
||||
SDL_LogicalAudioDevice *bound_device;
|
||||
SDL_AudioStream *next_binding;
|
||||
SDL_AudioStream *prev_binding;
|
||||
|
||||
SDL_AudioStream *prev; // linked list of all existing streams (so we can free them on shutdown).
|
||||
SDL_AudioStream *next; // linked list of all existing streams (so we can free them on shutdown).
|
||||
};
|
||||
|
||||
/* Logical devices are an abstraction in SDL3; you can open the same physical
|
||||
device multiple times, and each will result in an object with its own set
|
||||
of bound audio streams, etc, even though internally these are all processed
|
||||
as a group when mixing the final output for the physical device. */
|
||||
struct SDL_LogicalAudioDevice
|
||||
{
|
||||
// the unique instance ID of this device.
|
||||
SDL_AudioDeviceID instance_id;
|
||||
|
||||
// The physical device associated with this opened device.
|
||||
SDL_AudioDevice *physical_device;
|
||||
|
||||
// If whole logical device is paused (process no streams bound to this device).
|
||||
SDL_AtomicInt paused;
|
||||
|
||||
// double-linked list of all audio streams currently bound to this opened device.
|
||||
SDL_AudioStream *bound_streams;
|
||||
|
||||
// SDL_TRUE if this was opened as a default device.
|
||||
SDL_bool opened_as_default;
|
||||
|
||||
// SDL_TRUE if device was opened with SDL_OpenAudioDeviceStream (so it forbids binding changes, etc).
|
||||
SDL_bool simplified;
|
||||
|
||||
// If non-NULL, callback into the app that lets them access the final postmix buffer.
|
||||
SDL_AudioPostmixCallback postmix;
|
||||
|
||||
// App-supplied pointer for postmix callback.
|
||||
void *postmix_userdata;
|
||||
|
||||
// double-linked list of opened devices on the same physical device.
|
||||
SDL_LogicalAudioDevice *next;
|
||||
SDL_LogicalAudioDevice *prev;
|
||||
};
|
||||
|
||||
struct SDL_AudioDevice
|
||||
{
|
||||
// A mutex for locking access to this struct
|
||||
SDL_Mutex *lock;
|
||||
|
||||
// A condition variable to protect device close, where we can't hold the device lock forever.
|
||||
SDL_Condition *close_cond;
|
||||
|
||||
// Reference count of the device; logical devices, device threads, etc, add to this.
|
||||
SDL_AtomicInt refcount;
|
||||
|
||||
// These are, initially, set from current_audio, but we might swap them out with Zombie versions on disconnect/failure.
|
||||
int (*WaitDevice)(SDL_AudioDevice *device);
|
||||
int (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen);
|
||||
Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
|
||||
int (*WaitCaptureDevice)(SDL_AudioDevice *device);
|
||||
int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
|
||||
void (*FlushCapture)(SDL_AudioDevice *device);
|
||||
|
||||
// human-readable name of the device. ("SoundBlaster Pro 16")
|
||||
char *name;
|
||||
|
||||
// the unique instance ID of this device.
|
||||
SDL_AudioDeviceID instance_id;
|
||||
|
||||
// a way for the backend to identify this device _when not opened_
|
||||
void *handle;
|
||||
|
||||
// The device's current audio specification
|
||||
SDL_AudioSpec spec;
|
||||
int buffer_size;
|
||||
|
||||
// The device's default audio specification
|
||||
SDL_AudioSpec default_spec;
|
||||
|
||||
// Number of sample frames the devices wants per-buffer.
|
||||
int sample_frames;
|
||||
|
||||
// Value to use for SDL_memset to silence a buffer in this device's format
|
||||
int silence_value;
|
||||
|
||||
// non-zero if we are signaling the audio thread to end.
|
||||
SDL_AtomicInt shutdown;
|
||||
|
||||
// non-zero if this was a disconnected device and we're waiting for it to be decommissioned.
|
||||
SDL_AtomicInt zombie;
|
||||
|
||||
// SDL_TRUE if this is a capture device instead of an output device
|
||||
SDL_bool iscapture;
|
||||
|
||||
// SDL_TRUE if audio thread can skip silence/mix/convert stages and just do a basic memcpy.
|
||||
SDL_bool simple_copy;
|
||||
|
||||
// Scratch buffers used for mixing.
|
||||
Uint8 *work_buffer;
|
||||
Uint8 *mix_buffer;
|
||||
float *postmix_buffer;
|
||||
|
||||
// Size of work_buffer (and mix_buffer) in bytes.
|
||||
int work_buffer_size;
|
||||
|
||||
// A thread to feed the audio device
|
||||
SDL_Thread *thread;
|
||||
|
||||
// SDL_TRUE if this physical device is currently opened by the backend.
|
||||
SDL_bool currently_opened;
|
||||
|
||||
// Data private to this driver
|
||||
struct SDL_PrivateAudioData *hidden;
|
||||
|
||||
// All logical devices associated with this physical device.
|
||||
SDL_LogicalAudioDevice *logical_devices;
|
||||
};
|
||||
|
||||
typedef struct AudioBootStrap
|
||||
{
|
||||
const char *name;
|
||||
const char *desc;
|
||||
SDL_bool (*init)(SDL_AudioDriverImpl *impl);
|
||||
SDL_bool demand_only; // if SDL_TRUE: request explicitly, or it won't be available.
|
||||
} AudioBootStrap;
|
||||
|
||||
// Not all of these are available in a given build. Use #ifdefs, etc.
|
||||
extern AudioBootStrap PIPEWIRE_bootstrap;
|
||||
extern AudioBootStrap PULSEAUDIO_bootstrap;
|
||||
extern AudioBootStrap ALSA_bootstrap;
|
||||
extern AudioBootStrap JACK_bootstrap;
|
||||
extern AudioBootStrap SNDIO_bootstrap;
|
||||
extern AudioBootStrap NETBSDAUDIO_bootstrap;
|
||||
extern AudioBootStrap DSP_bootstrap;
|
||||
extern AudioBootStrap WASAPI_bootstrap;
|
||||
extern AudioBootStrap DSOUND_bootstrap;
|
||||
extern AudioBootStrap WINMM_bootstrap;
|
||||
extern AudioBootStrap HAIKUAUDIO_bootstrap;
|
||||
extern AudioBootStrap COREAUDIO_bootstrap;
|
||||
extern AudioBootStrap DISKAUDIO_bootstrap;
|
||||
extern AudioBootStrap DUMMYAUDIO_bootstrap;
|
||||
extern AudioBootStrap AAUDIO_bootstrap;
|
||||
extern AudioBootStrap OPENSLES_bootstrap;
|
||||
extern AudioBootStrap ANDROIDAUDIO_bootstrap;
|
||||
extern AudioBootStrap PS2AUDIO_bootstrap;
|
||||
extern AudioBootStrap PSPAUDIO_bootstrap;
|
||||
extern AudioBootStrap VITAAUD_bootstrap;
|
||||
extern AudioBootStrap N3DSAUDIO_bootstrap;
|
||||
extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
|
||||
extern AudioBootStrap QSAAUDIO_bootstrap;
|
||||
|
||||
#endif // SDL_sysaudio_h_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
/* RIFF WAVE files are little-endian */
|
||||
|
||||
/*******************************************/
|
||||
/* Define values for Microsoft WAVE format */
|
||||
/*******************************************/
|
||||
/* FOURCC */
|
||||
#define RIFF 0x46464952 /* "RIFF" */
|
||||
#define WAVE 0x45564157 /* "WAVE" */
|
||||
#define FACT 0x74636166 /* "fact" */
|
||||
#define LIST 0x5453494c /* "LIST" */
|
||||
#define BEXT 0x74786562 /* "bext" */
|
||||
#define JUNK 0x4B4E554A /* "JUNK" */
|
||||
#define FMT 0x20746D66 /* "fmt " */
|
||||
#define DATA 0x61746164 /* "data" */
|
||||
/* Format tags */
|
||||
#define UNKNOWN_CODE 0x0000
|
||||
#define PCM_CODE 0x0001
|
||||
#define MS_ADPCM_CODE 0x0002
|
||||
#define IEEE_FLOAT_CODE 0x0003
|
||||
#define ALAW_CODE 0x0006
|
||||
#define MULAW_CODE 0x0007
|
||||
#define IMA_ADPCM_CODE 0x0011
|
||||
#define MPEG_CODE 0x0050
|
||||
#define MPEGLAYER3_CODE 0x0055
|
||||
#define EXTENSIBLE_CODE 0xFFFE
|
||||
|
||||
/* Stores the WAVE format information. */
|
||||
typedef struct WaveFormat
|
||||
{
|
||||
Uint16 formattag; /* Raw value of the first field in the fmt chunk data. */
|
||||
Uint16 encoding; /* Actual encoding, possibly from the extensible header. */
|
||||
Uint16 channels; /* Number of channels. */
|
||||
Uint32 frequency; /* Sampling rate in Hz. */
|
||||
Uint32 byterate; /* Average bytes per second. */
|
||||
Uint16 blockalign; /* Bytes per block. */
|
||||
Uint16 bitspersample; /* Currently supported are 8, 16, 24, 32, and 4 for ADPCM. */
|
||||
|
||||
/* Extra information size. Number of extra bytes starting at byte 18 in the
|
||||
* fmt chunk data. This is at least 22 for the extensible header.
|
||||
*/
|
||||
Uint16 extsize;
|
||||
|
||||
/* Extensible WAVE header fields */
|
||||
Uint16 validsamplebits;
|
||||
Uint32 samplesperblock; /* For compressed formats. Can be zero. Actually 16 bits in the header. */
|
||||
Uint32 channelmask;
|
||||
Uint8 subformat[16]; /* A format GUID. */
|
||||
} WaveFormat;
|
||||
|
||||
/* Stores information on the fact chunk. */
|
||||
typedef struct WaveFact
|
||||
{
|
||||
/* Represents the state of the fact chunk in the WAVE file.
|
||||
* Set to -1 if the fact chunk is invalid.
|
||||
* Set to 0 if the fact chunk is not present
|
||||
* Set to 1 if the fact chunk is present and valid.
|
||||
* Set to 2 if samplelength is going to be used as the number of sample frames.
|
||||
*/
|
||||
Sint32 status;
|
||||
|
||||
/* Version 1 of the RIFF specification calls the field in the fact chunk
|
||||
* dwFileSize. The Standards Update then calls it dwSampleLength and specifies
|
||||
* that it is 'the length of the data in samples'. WAVE files from Windows
|
||||
* with this chunk have it set to the samples per channel (sample frames).
|
||||
* This is useful to truncate compressed audio to a specific sample count
|
||||
* because a compressed block is usually decoded to a fixed number of
|
||||
* sample frames.
|
||||
*/
|
||||
Uint32 samplelength; /* Raw sample length value from the fact chunk. */
|
||||
} WaveFact;
|
||||
|
||||
/* Generic struct for the chunks in the WAVE file. */
|
||||
typedef struct WaveChunk
|
||||
{
|
||||
Uint32 fourcc; /* FOURCC of the chunk. */
|
||||
Uint32 length; /* Size of the chunk data. */
|
||||
Sint64 position; /* Position of the data in the stream. */
|
||||
Uint8 *data; /* When allocated, this points to the chunk data. length is used for the memory allocation size. */
|
||||
size_t size; /* Number of bytes in data that could be read from the stream. Can be smaller than length. */
|
||||
} WaveChunk;
|
||||
|
||||
/* Controls how the size of the RIFF chunk affects the loading of a WAVE file. */
|
||||
typedef enum WaveRiffSizeHint
|
||||
{
|
||||
RiffSizeNoHint,
|
||||
RiffSizeForce,
|
||||
RiffSizeIgnoreZero,
|
||||
RiffSizeIgnore,
|
||||
RiffSizeMaximum
|
||||
} WaveRiffSizeHint;
|
||||
|
||||
/* Controls how a truncated WAVE file is handled. */
|
||||
typedef enum WaveTruncationHint
|
||||
{
|
||||
TruncNoHint,
|
||||
TruncVeryStrict,
|
||||
TruncStrict,
|
||||
TruncDropFrame,
|
||||
TruncDropBlock
|
||||
} WaveTruncationHint;
|
||||
|
||||
/* Controls how the fact chunk affects the loading of a WAVE file. */
|
||||
typedef enum WaveFactChunkHint
|
||||
{
|
||||
FactNoHint,
|
||||
FactTruncate,
|
||||
FactStrict,
|
||||
FactIgnoreZero,
|
||||
FactIgnore
|
||||
} WaveFactChunkHint;
|
||||
|
||||
typedef struct WaveFile
|
||||
{
|
||||
WaveChunk chunk;
|
||||
WaveFormat format;
|
||||
WaveFact fact;
|
||||
|
||||
/* Number of sample frames that will be decoded. Calculated either with the
|
||||
* size of the data chunk or, if the appropriate hint is enabled, with the
|
||||
* sample length value from the fact chunk.
|
||||
*/
|
||||
Sint64 sampleframes;
|
||||
|
||||
void *decoderdata; /* Some decoders require extra data for a state. */
|
||||
|
||||
WaveRiffSizeHint riffhint;
|
||||
WaveTruncationHint trunchint;
|
||||
WaveFactChunkHint facthint;
|
||||
} WaveFile;
|
||||
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_AAUDIO
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_aaudio.h"
|
||||
|
||||
#include "../../core/android/SDL_android.h"
|
||||
#include <stdbool.h>
|
||||
#include <aaudio/AAudio.h>
|
||||
|
||||
#if __ANDROID_API__ < 31
|
||||
#define AAUDIO_FORMAT_PCM_I32 4
|
||||
#endif
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
AAudioStream *stream;
|
||||
int num_buffers;
|
||||
Uint8 *mixbuf; // Raw mixing buffer
|
||||
size_t mixbuf_bytes; // num_buffers * device->buffer_size
|
||||
size_t callback_bytes;
|
||||
size_t processed_bytes;
|
||||
SDL_Semaphore *semaphore;
|
||||
SDL_AtomicInt error_callback_triggered;
|
||||
SDL_bool resume; // Resume device if it was paused automatically
|
||||
};
|
||||
|
||||
// Debug
|
||||
#if 0
|
||||
#define LOGI(...) SDL_Log(__VA_ARGS__);
|
||||
#else
|
||||
#define LOGI(...)
|
||||
#endif
|
||||
|
||||
#define LIB_AAUDIO_SO "libaaudio.so"
|
||||
|
||||
typedef struct AAUDIO_Data
|
||||
{
|
||||
void *handle;
|
||||
#define SDL_PROC(ret, func, params) ret (*func) params;
|
||||
#include "SDL_aaudiofuncs.h"
|
||||
} AAUDIO_Data;
|
||||
static AAUDIO_Data ctx;
|
||||
|
||||
static int AAUDIO_LoadFunctions(AAUDIO_Data *data)
|
||||
{
|
||||
#define SDL_PROC(ret, func, params) \
|
||||
do { \
|
||||
data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \
|
||||
if (!data->func) { \
|
||||
return SDL_SetError("Couldn't load AAUDIO function %s: %s", #func, SDL_GetError()); \
|
||||
} \
|
||||
} while (0);
|
||||
#include "SDL_aaudiofuncs.h"
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error)
|
||||
{
|
||||
LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error));
|
||||
|
||||
// You MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here.
|
||||
// Just flag the device so we can kill it in PlayDevice instead.
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
|
||||
SDL_AtomicSet(&device->hidden->error_callback_triggered, (int) error); // AAUDIO_OK is zero, so !triggered means no error.
|
||||
SDL_PostSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice.
|
||||
}
|
||||
|
||||
static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
size_t framesize = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
size_t callback_bytes = numFrames * framesize;
|
||||
size_t old_buffer_index = hidden->callback_bytes / device->buffer_size;
|
||||
|
||||
if (device->iscapture) {
|
||||
const Uint8 *input = (const Uint8 *)audioData;
|
||||
size_t available_bytes = hidden->mixbuf_bytes - (hidden->callback_bytes - hidden->processed_bytes);
|
||||
size_t size = SDL_min(available_bytes, callback_bytes);
|
||||
size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
|
||||
size_t end = (offset + size) % hidden->mixbuf_bytes;
|
||||
SDL_assert(size <= hidden->mixbuf_bytes);
|
||||
|
||||
//LOGI("Recorded %zu frames, %zu available, %zu max (%zu written, %zu read)\n", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->callback_bytes / framesize, hidden->processed_bytes / framesize);
|
||||
|
||||
if (offset <= end) {
|
||||
SDL_memcpy(&hidden->mixbuf[offset], input, size);
|
||||
} else {
|
||||
size_t partial = (hidden->mixbuf_bytes - offset);
|
||||
SDL_memcpy(&hidden->mixbuf[offset], &input[0], partial);
|
||||
SDL_memcpy(&hidden->mixbuf[0], &input[partial], end);
|
||||
}
|
||||
|
||||
SDL_MemoryBarrierRelease();
|
||||
hidden->callback_bytes += size;
|
||||
|
||||
if (size < callback_bytes) {
|
||||
LOGI("Audio recording overflow, dropped %zu frames\n", (callback_bytes - size) / framesize);
|
||||
}
|
||||
} else {
|
||||
Uint8 *output = (Uint8 *)audioData;
|
||||
size_t available_bytes = (hidden->processed_bytes - hidden->callback_bytes);
|
||||
size_t size = SDL_min(available_bytes, callback_bytes);
|
||||
size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
|
||||
size_t end = (offset + size) % hidden->mixbuf_bytes;
|
||||
SDL_assert(size <= hidden->mixbuf_bytes);
|
||||
|
||||
//LOGI("Playing %zu frames, %zu available, %zu max (%zu written, %zu read)\n", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->processed_bytes / framesize, hidden->callback_bytes / framesize);
|
||||
|
||||
SDL_MemoryBarrierAcquire();
|
||||
if (offset <= end) {
|
||||
SDL_memcpy(output, &hidden->mixbuf[offset], size);
|
||||
} else {
|
||||
size_t partial = (hidden->mixbuf_bytes - offset);
|
||||
SDL_memcpy(&output[0], &hidden->mixbuf[offset], partial);
|
||||
SDL_memcpy(&output[partial], &hidden->mixbuf[0], end);
|
||||
}
|
||||
hidden->callback_bytes += size;
|
||||
|
||||
if (size < callback_bytes) {
|
||||
LOGI("Audio playback underflow, missed %zu frames\n", (callback_bytes - size) / framesize);
|
||||
SDL_memset(&output[size], device->silence_value, (callback_bytes - size));
|
||||
}
|
||||
}
|
||||
|
||||
size_t new_buffer_index = hidden->callback_bytes / device->buffer_size;
|
||||
while (old_buffer_index < new_buffer_index) {
|
||||
// Trigger audio processing
|
||||
SDL_PostSemaphore(hidden->semaphore);
|
||||
++old_buffer_index;
|
||||
}
|
||||
|
||||
return AAUDIO_CALLBACK_RESULT_CONTINUE;
|
||||
}
|
||||
|
||||
static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
|
||||
return &hidden->mixbuf[offset];
|
||||
}
|
||||
|
||||
static int AAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
SDL_WaitSemaphore(device->hidden->semaphore);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int BuildAAudioStream(SDL_AudioDevice *device);
|
||||
|
||||
static int RecoverAAudioDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
|
||||
// attempt to build a new stream, in case there's a new default device.
|
||||
ctx.AAudioStream_requestStop(hidden->stream);
|
||||
ctx.AAudioStream_close(hidden->stream);
|
||||
hidden->stream = NULL;
|
||||
|
||||
SDL_aligned_free(hidden->mixbuf);
|
||||
hidden->mixbuf = NULL;
|
||||
|
||||
SDL_DestroySemaphore(hidden->semaphore);
|
||||
hidden->semaphore = NULL;
|
||||
|
||||
const int prev_sample_frames = device->sample_frames;
|
||||
SDL_AudioSpec prevspec;
|
||||
SDL_copyp(&prevspec, &device->spec);
|
||||
|
||||
if (BuildAAudioStream(device) < 0) {
|
||||
return -1; // oh well, we tried.
|
||||
}
|
||||
|
||||
// we don't know the new device spec until we open the new device, so we saved off the old one and force it back
|
||||
// so SDL_AudioDeviceFormatChanged can set up all the important state if necessary and then set it back to the new spec.
|
||||
const int new_sample_frames = device->sample_frames;
|
||||
SDL_AudioSpec newspec;
|
||||
SDL_copyp(&newspec, &device->spec);
|
||||
|
||||
device->sample_frames = prev_sample_frames;
|
||||
SDL_copyp(&device->spec, &prevspec);
|
||||
if (SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames) < 0) {
|
||||
return -1; // ugh
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
|
||||
// AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
|
||||
const aaudio_result_t err = (aaudio_result_t) SDL_AtomicGet(&hidden->error_callback_triggered);
|
||||
if (err) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "aaudio: Audio device triggered error %d (%s)", (int) err, ctx.AAudio_convertResultToText(err));
|
||||
|
||||
if (RecoverAAudioDevice(device) < 0) {
|
||||
return -1; // oh well, we went down hard.
|
||||
}
|
||||
} else {
|
||||
SDL_MemoryBarrierRelease();
|
||||
hidden->processed_bytes += buflen;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int AAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
|
||||
// AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
|
||||
if (SDL_AtomicGet(&hidden->error_callback_triggered)) {
|
||||
SDL_AtomicSet(&hidden->error_callback_triggered, 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_assert(buflen == device->buffer_size); // If this isn't true, we need to change semaphore trigger logic and account for wrapping copies here
|
||||
size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
|
||||
SDL_MemoryBarrierAcquire();
|
||||
SDL_memcpy(buffer, &hidden->mixbuf[offset], buflen);
|
||||
hidden->processed_bytes += buflen;
|
||||
return buflen;
|
||||
}
|
||||
|
||||
static void AAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
LOGI(__func__);
|
||||
|
||||
if (hidden) {
|
||||
if (hidden->stream) {
|
||||
ctx.AAudioStream_requestStop(hidden->stream);
|
||||
// !!! FIXME: do we have to wait for the state to change to make sure all buffered audio has played, or will close do this (or will the system do this after the close)?
|
||||
// !!! FIXME: also, will this definitely wait for a running data callback to finish, and then stop the callback from firing again?
|
||||
ctx.AAudioStream_close(hidden->stream);
|
||||
}
|
||||
|
||||
if (hidden->semaphore) {
|
||||
SDL_DestroySemaphore(hidden->semaphore);
|
||||
}
|
||||
|
||||
SDL_aligned_free(hidden->mixbuf);
|
||||
SDL_free(hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int BuildAAudioStream(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
const SDL_bool iscapture = device->iscapture;
|
||||
aaudio_result_t res;
|
||||
|
||||
SDL_AtomicSet(&hidden->error_callback_triggered, 0);
|
||||
|
||||
AAudioStreamBuilder *builder = NULL;
|
||||
res = ctx.AAudio_createStreamBuilder(&builder);
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudio_createStreamBuilder %d", res);
|
||||
return SDL_SetError("SDL Failed AAudio_createStreamBuilder %d", res);
|
||||
} else if (!builder) {
|
||||
LOGI("SDL Failed AAudio_createStreamBuilder - builder NULL");
|
||||
return SDL_SetError("SDL Failed AAudio_createStreamBuilder - builder NULL");
|
||||
}
|
||||
|
||||
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||
const int aaudio_device_id = (int) ((size_t) device->handle);
|
||||
LOGI("Opening device id %d", aaudio_device_id);
|
||||
ctx.AAudioStreamBuilder_setDeviceId(builder, aaudio_device_id);
|
||||
#endif
|
||||
|
||||
aaudio_format_t format;
|
||||
#ifdef SET_AUDIO_FORMAT
|
||||
if ((device->spec.format == SDL_AUDIO_S32) && (SDL_GetAndroidSDKVersion() >= 31)) {
|
||||
format = AAUDIO_FORMAT_PCM_I32;
|
||||
} else if (device->spec.format == SDL_AUDIO_F32) {
|
||||
format = AAUDIO_FORMAT_PCM_FLOAT;
|
||||
} else {
|
||||
format = AAUDIO_FORMAT_PCM_I16; // sint16 is a safe bet for everything else.
|
||||
}
|
||||
ctx.AAudioStreamBuilder_setFormat(builder, format);
|
||||
ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq);
|
||||
ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels);
|
||||
#endif
|
||||
|
||||
const aaudio_direction_t direction = (iscapture ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT);
|
||||
ctx.AAudioStreamBuilder_setDirection(builder, direction);
|
||||
ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, device);
|
||||
ctx.AAudioStreamBuilder_setDataCallback(builder, AAUDIO_dataCallback, device);
|
||||
// Some devices have flat sounding audio when low latency mode is enabled, but this is a better experience for most people
|
||||
if (SDL_GetHintBoolean("SDL_ANDROID_LOW_LATENCY_AUDIO", SDL_TRUE)) {
|
||||
ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
|
||||
}
|
||||
|
||||
LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u",
|
||||
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
|
||||
device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames);
|
||||
|
||||
res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream);
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res);
|
||||
ctx.AAudioStreamBuilder_delete(builder);
|
||||
return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
|
||||
}
|
||||
ctx.AAudioStreamBuilder_delete(builder);
|
||||
|
||||
device->sample_frames = (int)ctx.AAudioStream_getFramesPerDataCallback(hidden->stream);
|
||||
if (device->sample_frames == AAUDIO_UNSPECIFIED) {
|
||||
// We'll get variable frames in the callback, make sure we have at least half a buffer available
|
||||
device->sample_frames = (int)ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 2;
|
||||
}
|
||||
|
||||
device->spec.freq = ctx.AAudioStream_getSampleRate(hidden->stream);
|
||||
device->spec.channels = ctx.AAudioStream_getChannelCount(hidden->stream);
|
||||
|
||||
format = ctx.AAudioStream_getFormat(hidden->stream);
|
||||
if (format == AAUDIO_FORMAT_PCM_I16) {
|
||||
device->spec.format = SDL_AUDIO_S16;
|
||||
} else if (format == AAUDIO_FORMAT_PCM_I32) {
|
||||
device->spec.format = SDL_AUDIO_S32;
|
||||
} else if (format == AAUDIO_FORMAT_PCM_FLOAT) {
|
||||
device->spec.format = SDL_AUDIO_F32;
|
||||
} else {
|
||||
return SDL_SetError("Got unexpected audio format %d from AAudioStream_getFormat", (int) format);
|
||||
}
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
// Allocate a double buffered mixing buffer
|
||||
hidden->num_buffers = 2;
|
||||
hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size);
|
||||
hidden->mixbuf = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), hidden->mixbuf_bytes);
|
||||
if (!hidden->mixbuf) {
|
||||
return -1;
|
||||
}
|
||||
hidden->processed_bytes = 0;
|
||||
hidden->callback_bytes = 0;
|
||||
|
||||
hidden->semaphore = SDL_CreateSemaphore(iscapture ? 0 : hidden->num_buffers);
|
||||
if (!hidden->semaphore) {
|
||||
LOGI("SDL Failed SDL_CreateSemaphore %s iscapture:%d", SDL_GetError(), iscapture);
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOGI("AAudio Actually opened %u hz %u bit chan %u %s samples %u, buffers %d",
|
||||
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
|
||||
device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames, hidden->num_buffers);
|
||||
|
||||
res = ctx.AAudioStream_requestStart(hidden->stream);
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudioStream_requestStart %d iscapture:%d", res, iscapture);
|
||||
return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
|
||||
}
|
||||
|
||||
LOGI("SDL AAudioStream_requestStart OK");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// !!! FIXME: make this non-blocking!
|
||||
static void SDLCALL AndroidRequestPermissionBlockingCallback(void *userdata, const char *permission, SDL_bool granted)
|
||||
{
|
||||
SDL_AtomicSet((SDL_AtomicInt *) userdata, granted ? 1 : -1);
|
||||
}
|
||||
|
||||
static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||
SDL_assert(device->handle); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero.
|
||||
#endif
|
||||
|
||||
LOGI(__func__);
|
||||
|
||||
if (device->iscapture) {
|
||||
// !!! FIXME: make this non-blocking!
|
||||
SDL_AtomicInt permission_response;
|
||||
SDL_AtomicSet(&permission_response, 0);
|
||||
if (SDL_AndroidRequestPermission("android.permission.RECORD_AUDIO", AndroidRequestPermissionBlockingCallback, &permission_response) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (SDL_AtomicGet(&permission_response) == 0) {
|
||||
SDL_Delay(10);
|
||||
}
|
||||
|
||||
if (SDL_AtomicGet(&permission_response) < 0) {
|
||||
LOGI("This app doesn't have RECORD_AUDIO permission");
|
||||
return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
|
||||
}
|
||||
}
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return BuildAAudioStream(device);
|
||||
}
|
||||
|
||||
static SDL_bool PauseOneDevice(SDL_AudioDevice *device, void *userdata)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden;
|
||||
if (hidden) {
|
||||
if (hidden->stream) {
|
||||
aaudio_result_t res;
|
||||
|
||||
if (device->iscapture) {
|
||||
// Pause() isn't implemented for 'capture', use Stop()
|
||||
res = ctx.AAudioStream_requestStop(hidden->stream);
|
||||
} else {
|
||||
res = ctx.AAudioStream_requestPause(hidden->stream);
|
||||
}
|
||||
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudioStream_requestPause %d", res);
|
||||
SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
|
||||
}
|
||||
|
||||
SDL_LockMutex(device->lock);
|
||||
hidden->resume = SDL_TRUE;
|
||||
}
|
||||
}
|
||||
return SDL_FALSE; // keep enumerating.
|
||||
}
|
||||
|
||||
// Pause (block) all non already paused audio devices by taking their mixer lock
|
||||
void AAUDIO_PauseDevices(void)
|
||||
{
|
||||
if (ctx.handle) { // AAUDIO driver is used?
|
||||
(void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneDevice, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Resume (unblock) all non already paused audio devices by releasing their mixer lock
|
||||
static SDL_bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
if (hidden) {
|
||||
if (hidden->resume) {
|
||||
hidden->resume = SDL_FALSE;
|
||||
SDL_UnlockMutex(device->lock);
|
||||
}
|
||||
|
||||
if (hidden->stream) {
|
||||
aaudio_result_t res = ctx.AAudioStream_requestStart(hidden->stream);
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudioStream_requestStart %d", res);
|
||||
SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
|
||||
}
|
||||
}
|
||||
}
|
||||
return SDL_FALSE; // keep enumerating.
|
||||
}
|
||||
|
||||
void AAUDIO_ResumeDevices(void)
|
||||
{
|
||||
if (ctx.handle) { // AAUDIO driver is used?
|
||||
(void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneDevice, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void AAUDIO_Deinitialize(void)
|
||||
{
|
||||
Android_StopAudioHotplug();
|
||||
|
||||
LOGI(__func__);
|
||||
if (ctx.handle) {
|
||||
SDL_UnloadObject(ctx.handle);
|
||||
}
|
||||
SDL_zero(ctx);
|
||||
LOGI("End AAUDIO %s", SDL_GetError());
|
||||
}
|
||||
|
||||
|
||||
static SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
LOGI(__func__);
|
||||
|
||||
/* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release,
|
||||
* so don't use it until 8.1.
|
||||
*
|
||||
* See https://github.com/google/oboe/issues/40 for more information.
|
||||
*/
|
||||
if (SDL_GetAndroidSDKVersion() < 27) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
SDL_zero(ctx);
|
||||
|
||||
ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO);
|
||||
if (!ctx.handle) {
|
||||
LOGI("SDL couldn't find " LIB_AAUDIO_SO);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
if (AAUDIO_LoadFunctions(&ctx) < 0) {
|
||||
SDL_UnloadObject(ctx.handle);
|
||||
SDL_zero(ctx);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
impl->ThreadInit = Android_AudioThreadInit;
|
||||
impl->Deinitialize = AAUDIO_Deinitialize;
|
||||
impl->OpenDevice = AAUDIO_OpenDevice;
|
||||
impl->CloseDevice = AAUDIO_CloseDevice;
|
||||
impl->WaitDevice = AAUDIO_WaitDevice;
|
||||
impl->PlayDevice = AAUDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = AAUDIO_GetDeviceBuf;
|
||||
impl->WaitCaptureDevice = AAUDIO_WaitDevice;
|
||||
impl->CaptureFromDevice = AAUDIO_CaptureFromDevice;
|
||||
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
|
||||
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||
impl->DetectDevices = Android_StartAudioHotplug;
|
||||
#else
|
||||
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
|
||||
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
|
||||
#endif
|
||||
|
||||
LOGI("SDL AAUDIO_Init OK");
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap AAUDIO_bootstrap = {
|
||||
"AAudio", "AAudio audio driver", AAUDIO_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_AAUDIO
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_aaudio_h_
|
||||
#define SDL_aaudio_h_
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_AAUDIO
|
||||
|
||||
void AAUDIO_ResumeDevices(void);
|
||||
void AAUDIO_PauseDevices(void);
|
||||
|
||||
#else
|
||||
|
||||
#define AAUDIO_ResumeDevices()
|
||||
#define AAUDIO_PauseDevices()
|
||||
|
||||
#endif
|
||||
|
||||
#endif // SDL_aaudio_h_
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright , (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#define SDL_PROC_UNUSED(ret, func, params)
|
||||
|
||||
SDL_PROC(const char *, AAudio_convertResultToText, (aaudio_result_t returnCode))
|
||||
SDL_PROC(const char *, AAudio_convertStreamStateToText, (aaudio_stream_state_t state))
|
||||
SDL_PROC(aaudio_result_t, AAudio_createStreamBuilder, (AAudioStreamBuilder * *builder))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setDeviceId, (AAudioStreamBuilder * builder, int32_t deviceId))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setSampleRate, (AAudioStreamBuilder * builder, int32_t sampleRate))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setChannelCount, (AAudioStreamBuilder * builder, int32_t channelCount))
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSamplesPerFrame, (AAudioStreamBuilder * builder, int32_t samplesPerFrame))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aaudio_format_t format))
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction))
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode))
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) // API 29
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) // API 28
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) // API 30
|
||||
SDL_PROC(void, AAudioStreamBuilder_setDataCallback, (AAudioStreamBuilder * builder, AAudioStream_dataCallback callback, void *userData))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setFramesPerDataCallback, (AAudioStreamBuilder * builder, int32_t numFrames))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setErrorCallback, (AAudioStreamBuilder * builder, AAudioStream_errorCallback callback, void *userData))
|
||||
SDL_PROC(aaudio_result_t, AAudioStreamBuilder_openStream, (AAudioStreamBuilder * builder, AAudioStream **stream))
|
||||
SDL_PROC(aaudio_result_t, AAudioStreamBuilder_delete, (AAudioStreamBuilder * builder))
|
||||
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_release, (AAudioStream * stream)) // API 30
|
||||
SDL_PROC(aaudio_result_t, AAudioStream_close, (AAudioStream * stream))
|
||||
SDL_PROC(aaudio_result_t, AAudioStream_requestStart, (AAudioStream * stream))
|
||||
SDL_PROC(aaudio_result_t, AAudioStream_requestPause, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_requestFlush, (AAudioStream * stream))
|
||||
SDL_PROC(aaudio_result_t, AAudioStream_requestStop, (AAudioStream * stream))
|
||||
SDL_PROC(aaudio_stream_state_t, AAudioStream_getState, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_waitForStateChange, (AAudioStream * stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds))
|
||||
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_read, (AAudioStream * stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
|
||||
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_write, (AAudioStream * stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
|
||||
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_setBufferSizeInFrames, (AAudioStream * stream, int32_t numFrames))
|
||||
SDL_PROC_UNUSED(int32_t, AAudioStream_getBufferSizeInFrames, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(int32_t, AAudioStream_getFramesPerBurst, (AAudioStream * stream))
|
||||
SDL_PROC(int32_t, AAudioStream_getBufferCapacityInFrames, (AAudioStream * stream))
|
||||
SDL_PROC(int32_t, AAudioStream_getFramesPerDataCallback, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(int32_t, AAudioStream_getXRunCount, (AAudioStream * stream))
|
||||
SDL_PROC(int32_t, AAudioStream_getSampleRate, (AAudioStream * stream))
|
||||
SDL_PROC(int32_t, AAudioStream_getChannelCount, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(int32_t, AAudioStream_getSamplesPerFrame, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(int32_t, AAudioStream_getDeviceId, (AAudioStream * stream))
|
||||
SDL_PROC(aaudio_format_t, AAudioStream_getFormat, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(aaudio_sharing_mode_t, AAudioStream_getSharingMode, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(aaudio_performance_mode_t, AAudioStream_getPerformanceMode, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(aaudio_direction_t, AAudioStream_getDirection, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesWritten, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesRead, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(aaudio_session_id_t, AAudioStream_getSessionId, (AAudioStream * stream)) // API 28
|
||||
SDL_PROC(aaudio_result_t, AAudioStream_getTimestamp, (AAudioStream * stream, clockid_t clockid, int64_t *framePosition, int64_t *timeNanoseconds))
|
||||
SDL_PROC_UNUSED(aaudio_usage_t, AAudioStream_getUsage, (AAudioStream * stream)) // API 28
|
||||
SDL_PROC_UNUSED(aaudio_content_type_t, AAudioStream_getContentType, (AAudioStream * stream)) // API 28
|
||||
SDL_PROC_UNUSED(aaudio_input_preset_t, AAudioStream_getInputPreset, (AAudioStream * stream)) // API 28
|
||||
SDL_PROC_UNUSED(aaudio_allowed_capture_policy_t, AAudioStream_getAllowedCapturePolicy, (AAudioStream * stream)) // API 29
|
||||
SDL_PROC_UNUSED(bool, AAudioStream_isPrivacySensitive, (AAudioStream * stream)) // API 30
|
||||
|
||||
#undef SDL_PROC
|
||||
#undef SDL_PROC_UNUSED
|
||||
@@ -0,0 +1,994 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_ALSA
|
||||
|
||||
#ifndef SDL_ALSA_NON_BLOCKING
|
||||
#define SDL_ALSA_NON_BLOCKING 0
|
||||
#endif
|
||||
|
||||
// without the thread, you will detect devices on startup, but will not get further hotplug events. But that might be okay.
|
||||
#ifndef SDL_ALSA_HOTPLUG_THREAD
|
||||
#define SDL_ALSA_HOTPLUG_THREAD 1
|
||||
#endif
|
||||
|
||||
// Allow access to a raw mixing buffer
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <signal.h> // For kill()
|
||||
#include <string.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_alsa_audio.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
|
||||
#endif
|
||||
|
||||
static int (*ALSA_snd_pcm_open)(snd_pcm_t **, const char *, snd_pcm_stream_t, int);
|
||||
static int (*ALSA_snd_pcm_close)(snd_pcm_t *pcm);
|
||||
static int (*ALSA_snd_pcm_start)(snd_pcm_t *pcm);
|
||||
static snd_pcm_sframes_t (*ALSA_snd_pcm_writei)(snd_pcm_t *, const void *, snd_pcm_uframes_t);
|
||||
static snd_pcm_sframes_t (*ALSA_snd_pcm_readi)(snd_pcm_t *, void *, snd_pcm_uframes_t);
|
||||
static int (*ALSA_snd_pcm_recover)(snd_pcm_t *, int, int);
|
||||
static int (*ALSA_snd_pcm_prepare)(snd_pcm_t *);
|
||||
static int (*ALSA_snd_pcm_drain)(snd_pcm_t *);
|
||||
static const char *(*ALSA_snd_strerror)(int);
|
||||
static size_t (*ALSA_snd_pcm_hw_params_sizeof)(void);
|
||||
static size_t (*ALSA_snd_pcm_sw_params_sizeof)(void);
|
||||
static void (*ALSA_snd_pcm_hw_params_copy)(snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *);
|
||||
static int (*ALSA_snd_pcm_hw_params_any)(snd_pcm_t *, snd_pcm_hw_params_t *);
|
||||
static int (*ALSA_snd_pcm_hw_params_set_access)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
|
||||
static int (*ALSA_snd_pcm_hw_params_set_format)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
|
||||
static int (*ALSA_snd_pcm_hw_params_set_channels)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int);
|
||||
static int (*ALSA_snd_pcm_hw_params_get_channels)(const snd_pcm_hw_params_t *, unsigned int *);
|
||||
static int (*ALSA_snd_pcm_hw_params_set_rate_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
|
||||
static int (*ALSA_snd_pcm_hw_params_set_period_size_near)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
|
||||
static int (*ALSA_snd_pcm_hw_params_get_period_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
|
||||
static int (*ALSA_snd_pcm_hw_params_set_periods_min)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
|
||||
static int (*ALSA_snd_pcm_hw_params_set_periods_first)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
|
||||
static int (*ALSA_snd_pcm_hw_params_get_periods)(const snd_pcm_hw_params_t *, unsigned int *, int *);
|
||||
static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
|
||||
static int (*ALSA_snd_pcm_hw_params_get_buffer_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
|
||||
static int (*ALSA_snd_pcm_hw_params)(snd_pcm_t *, snd_pcm_hw_params_t *);
|
||||
static int (*ALSA_snd_pcm_sw_params_current)(snd_pcm_t *,
|
||||
snd_pcm_sw_params_t *);
|
||||
static int (*ALSA_snd_pcm_sw_params_set_start_threshold)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
|
||||
static int (*ALSA_snd_pcm_sw_params)(snd_pcm_t *, snd_pcm_sw_params_t *);
|
||||
static int (*ALSA_snd_pcm_nonblock)(snd_pcm_t *, int);
|
||||
static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int);
|
||||
static int (*ALSA_snd_pcm_sw_params_set_avail_min)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
|
||||
static int (*ALSA_snd_pcm_reset)(snd_pcm_t *);
|
||||
static int (*ALSA_snd_device_name_hint)(int, const char *, void ***);
|
||||
static char *(*ALSA_snd_device_name_get_hint)(const void *, const char *);
|
||||
static int (*ALSA_snd_device_name_free_hint)(void **);
|
||||
static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *);
|
||||
#ifdef SND_CHMAP_API_VERSION
|
||||
static snd_pcm_chmap_t *(*ALSA_snd_pcm_get_chmap)(snd_pcm_t *);
|
||||
static int (*ALSA_snd_pcm_chmap_print)(const snd_pcm_chmap_t *map, size_t maxlen, char *buf);
|
||||
#endif
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
|
||||
#define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
|
||||
#define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
|
||||
|
||||
static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
|
||||
static void *alsa_handle = NULL;
|
||||
|
||||
static int load_alsa_sym(const char *fn, void **addr)
|
||||
{
|
||||
*addr = SDL_LoadFunction(alsa_handle, fn);
|
||||
if (!*addr) {
|
||||
// Don't call SDL_SetError(): SDL_LoadFunction already did.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// cast funcs to char* first, to please GCC's strict aliasing rules.
|
||||
#define SDL_ALSA_SYM(x) \
|
||||
if (!load_alsa_sym(#x, (void **)(char *)&ALSA_##x)) \
|
||||
return -1
|
||||
#else
|
||||
#define SDL_ALSA_SYM(x) ALSA_##x = x
|
||||
#endif
|
||||
|
||||
static int load_alsa_syms(void)
|
||||
{
|
||||
SDL_ALSA_SYM(snd_pcm_open);
|
||||
SDL_ALSA_SYM(snd_pcm_close);
|
||||
SDL_ALSA_SYM(snd_pcm_start);
|
||||
SDL_ALSA_SYM(snd_pcm_writei);
|
||||
SDL_ALSA_SYM(snd_pcm_readi);
|
||||
SDL_ALSA_SYM(snd_pcm_recover);
|
||||
SDL_ALSA_SYM(snd_pcm_prepare);
|
||||
SDL_ALSA_SYM(snd_pcm_drain);
|
||||
SDL_ALSA_SYM(snd_strerror);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_sizeof);
|
||||
SDL_ALSA_SYM(snd_pcm_sw_params_sizeof);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_copy);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_any);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_set_access);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_set_format);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_set_channels);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_get_channels);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_set_rate_near);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_set_period_size_near);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_min);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_first);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_get_periods);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_set_buffer_size_near);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params_get_buffer_size);
|
||||
SDL_ALSA_SYM(snd_pcm_hw_params);
|
||||
SDL_ALSA_SYM(snd_pcm_sw_params_current);
|
||||
SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold);
|
||||
SDL_ALSA_SYM(snd_pcm_sw_params);
|
||||
SDL_ALSA_SYM(snd_pcm_nonblock);
|
||||
SDL_ALSA_SYM(snd_pcm_wait);
|
||||
SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
|
||||
SDL_ALSA_SYM(snd_pcm_reset);
|
||||
SDL_ALSA_SYM(snd_device_name_hint);
|
||||
SDL_ALSA_SYM(snd_device_name_get_hint);
|
||||
SDL_ALSA_SYM(snd_device_name_free_hint);
|
||||
SDL_ALSA_SYM(snd_pcm_avail);
|
||||
#ifdef SND_CHMAP_API_VERSION
|
||||
SDL_ALSA_SYM(snd_pcm_get_chmap);
|
||||
SDL_ALSA_SYM(snd_pcm_chmap_print);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef SDL_ALSA_SYM
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
|
||||
|
||||
static void UnloadALSALibrary(void)
|
||||
{
|
||||
if (alsa_handle) {
|
||||
SDL_UnloadObject(alsa_handle);
|
||||
alsa_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int LoadALSALibrary(void)
|
||||
{
|
||||
int retval = 0;
|
||||
if (!alsa_handle) {
|
||||
alsa_handle = SDL_LoadObject(alsa_library);
|
||||
if (!alsa_handle) {
|
||||
retval = -1;
|
||||
// Don't call SDL_SetError(): SDL_LoadObject already did.
|
||||
} else {
|
||||
retval = load_alsa_syms();
|
||||
if (retval < 0) {
|
||||
UnloadALSALibrary();
|
||||
}
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void UnloadALSALibrary(void)
|
||||
{
|
||||
}
|
||||
|
||||
static int LoadALSALibrary(void)
|
||||
{
|
||||
load_alsa_syms();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_ALSA_DYNAMIC
|
||||
|
||||
typedef struct ALSA_Device
|
||||
{
|
||||
char *name;
|
||||
SDL_bool iscapture;
|
||||
struct ALSA_Device *next;
|
||||
} ALSA_Device;
|
||||
|
||||
static const ALSA_Device default_output_handle = {
|
||||
"default",
|
||||
SDL_FALSE,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const ALSA_Device default_capture_handle = {
|
||||
"default",
|
||||
SDL_TRUE,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *get_audio_device(void *handle, const int channels)
|
||||
{
|
||||
SDL_assert(handle != NULL); // SDL2 used NULL to mean "default" but that's not true in SDL3.
|
||||
|
||||
ALSA_Device *dev = (ALSA_Device *)handle;
|
||||
if (SDL_strcmp(dev->name, "default") == 0) {
|
||||
const char *device = SDL_getenv("AUDIODEV"); // Is there a standard variable name?
|
||||
if (device) {
|
||||
return device;
|
||||
} else if (channels == 6) {
|
||||
return "plug:surround51";
|
||||
} else if (channels == 4) {
|
||||
return "plug:surround40";
|
||||
}
|
||||
return "default";
|
||||
}
|
||||
|
||||
return dev->name;
|
||||
}
|
||||
|
||||
// !!! FIXME: is there a channel swizzler in alsalib instead?
|
||||
|
||||
// https://bugzilla.libsdl.org/show_bug.cgi?id=110
|
||||
// "For Linux ALSA, this is FL-FR-RL-RR-C-LFE
|
||||
// and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR"
|
||||
#define SWIZ6(T) \
|
||||
static void swizzle_alsa_channels_6_##T(void *buffer, const Uint32 bufferlen) \
|
||||
{ \
|
||||
T *ptr = (T *)buffer; \
|
||||
Uint32 i; \
|
||||
for (i = 0; i < bufferlen; i++, ptr += 6) { \
|
||||
T tmp; \
|
||||
tmp = ptr[2]; \
|
||||
ptr[2] = ptr[4]; \
|
||||
ptr[4] = tmp; \
|
||||
tmp = ptr[3]; \
|
||||
ptr[3] = ptr[5]; \
|
||||
ptr[5] = tmp; \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
// !!! FIXME: is there a channel swizzler in alsalib instead?
|
||||
// !!! FIXME: this screams for a SIMD shuffle operation.
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/mapping-stream-formats-to-speaker-configurations
|
||||
// For Linux ALSA, this appears to be FL-FR-RL-RR-C-LFE-SL-SR
|
||||
// and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-SL-SR-RL-RR"
|
||||
#define SWIZ8(T) \
|
||||
static void swizzle_alsa_channels_8_##T(void *buffer, const Uint32 bufferlen) \
|
||||
{ \
|
||||
T *ptr = (T *)buffer; \
|
||||
Uint32 i; \
|
||||
for (i = 0; i < bufferlen; i++, ptr += 6) { \
|
||||
const T center = ptr[2]; \
|
||||
const T subwoofer = ptr[3]; \
|
||||
const T side_left = ptr[4]; \
|
||||
const T side_right = ptr[5]; \
|
||||
const T rear_left = ptr[6]; \
|
||||
const T rear_right = ptr[7]; \
|
||||
ptr[2] = rear_left; \
|
||||
ptr[3] = rear_right; \
|
||||
ptr[4] = center; \
|
||||
ptr[5] = subwoofer; \
|
||||
ptr[6] = side_left; \
|
||||
ptr[7] = side_right; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CHANNEL_SWIZZLE(x) \
|
||||
x(Uint64) \
|
||||
x(Uint32) \
|
||||
x(Uint16) \
|
||||
x(Uint8)
|
||||
|
||||
CHANNEL_SWIZZLE(SWIZ6)
|
||||
CHANNEL_SWIZZLE(SWIZ8)
|
||||
|
||||
#undef CHANNEL_SWIZZLE
|
||||
#undef SWIZ6
|
||||
#undef SWIZ8
|
||||
|
||||
// Called right before feeding device->hidden->mixbuf to the hardware. Swizzle
|
||||
// channels from Windows/Mac order to the format alsalib will want.
|
||||
static void swizzle_alsa_channels(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen)
|
||||
{
|
||||
switch (device->spec.channels) {
|
||||
#define CHANSWIZ(chans) \
|
||||
case chans: \
|
||||
switch ((device->spec.format & (0xFF))) { \
|
||||
case 8: \
|
||||
swizzle_alsa_channels_##chans##_Uint8(buffer, bufferlen); \
|
||||
break; \
|
||||
case 16: \
|
||||
swizzle_alsa_channels_##chans##_Uint16(buffer, bufferlen); \
|
||||
break; \
|
||||
case 32: \
|
||||
swizzle_alsa_channels_##chans##_Uint32(buffer, bufferlen); \
|
||||
break; \
|
||||
case 64: \
|
||||
swizzle_alsa_channels_##chans##_Uint64(buffer, bufferlen); \
|
||||
break; \
|
||||
default: \
|
||||
SDL_assert(!"unhandled bitsize"); \
|
||||
break; \
|
||||
} \
|
||||
return;
|
||||
|
||||
CHANSWIZ(6);
|
||||
CHANSWIZ(8);
|
||||
#undef CHANSWIZ
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SND_CHMAP_API_VERSION
|
||||
// Some devices have the right channel map, no swizzling necessary
|
||||
static void no_swizzle(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen)
|
||||
{
|
||||
}
|
||||
#endif // SND_CHMAP_API_VERSION
|
||||
|
||||
// This function waits until it is possible to write a full sound buffer
|
||||
static int ALSA_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const int fulldelay = (int) ((((Uint64) device->sample_frames) * 1000) / device->spec.freq);
|
||||
const int delay = SDL_max(fulldelay, 10);
|
||||
|
||||
while (!SDL_AtomicGet(&device->shutdown)) {
|
||||
const int rc = ALSA_snd_pcm_wait(device->hidden->pcm_handle, delay);
|
||||
if (rc < 0 && (rc != -EAGAIN)) {
|
||||
const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0);
|
||||
if (status < 0) {
|
||||
// Hmm, not much we can do - abort
|
||||
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA: snd_pcm_wait failed (unrecoverable): %s", ALSA_snd_strerror(rc));
|
||||
return -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rc > 0) {
|
||||
break; // ready to go!
|
||||
}
|
||||
|
||||
// Timed out! Make sure we aren't shutting down and then wait again.
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
SDL_assert(buffer == device->hidden->mixbuf);
|
||||
Uint8 *sample_buf = (Uint8 *) buffer; // !!! FIXME: deal with this without casting away constness
|
||||
const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
snd_pcm_uframes_t frames_left = (snd_pcm_uframes_t) (buflen / frame_size);
|
||||
|
||||
device->hidden->swizzle_func(device, sample_buf, frames_left);
|
||||
|
||||
while ((frames_left > 0) && !SDL_AtomicGet(&device->shutdown)) {
|
||||
const int rc = ALSA_snd_pcm_writei(device->hidden->pcm_handle, sample_buf, frames_left);
|
||||
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA PLAYDEVICE: WROTE %d of %d bytes", (rc >= 0) ? ((int) (rc * frame_size)) : rc, (int) (frames_left * frame_size));
|
||||
SDL_assert(rc != 0); // assuming this can't happen if we used snd_pcm_wait and queried for available space.
|
||||
if (rc < 0) {
|
||||
SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it!
|
||||
const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0);
|
||||
if (status < 0) {
|
||||
// Hmm, not much we can do - abort
|
||||
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(rc));
|
||||
return -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
sample_buf += rc * frame_size;
|
||||
frames_left -= rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
|
||||
if (rc <= 0) {
|
||||
// Wait a bit and try again, maybe the hardware isn't quite ready yet?
|
||||
SDL_Delay(1);
|
||||
|
||||
rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
|
||||
if (rc <= 0) {
|
||||
// We'll catch it next time
|
||||
*buffer_size = 0;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const int requested_frames = SDL_min(device->sample_frames, rc);
|
||||
const int requested_bytes = requested_frames * SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
SDL_assert(requested_bytes <= *buffer_size);
|
||||
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA GETDEVICEBUF: NEED %d BYTES", requested_bytes);
|
||||
*buffer_size = requested_bytes;
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int ALSA_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
SDL_assert((buflen % frame_size) == 0);
|
||||
|
||||
const snd_pcm_sframes_t total_available = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
|
||||
const int total_frames = SDL_min(buflen / frame_size, total_available);
|
||||
|
||||
const int rc = ALSA_snd_pcm_readi(device->hidden->pcm_handle, buffer, total_frames);
|
||||
|
||||
SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it!
|
||||
|
||||
if (rc < 0) {
|
||||
const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0);
|
||||
if (status < 0) {
|
||||
// Hmm, not much we can do - abort
|
||||
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA read failed (unrecoverable): %s", ALSA_snd_strerror(rc));
|
||||
return -1;
|
||||
}
|
||||
return 0; // go back to WaitDevice and try again.
|
||||
} else if (rc > 0) {
|
||||
device->hidden->swizzle_func(device, buffer, total_frames - rc);
|
||||
}
|
||||
|
||||
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: captured %d bytes", rc * frame_size);
|
||||
|
||||
return rc * frame_size;
|
||||
}
|
||||
|
||||
static void ALSA_FlushCapture(SDL_AudioDevice *device)
|
||||
{
|
||||
ALSA_snd_pcm_reset(device->hidden->pcm_handle);
|
||||
}
|
||||
|
||||
static void ALSA_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->pcm_handle) {
|
||||
// Wait for the submitted audio to drain. ALSA_snd_pcm_drop() can hang, so don't use that.
|
||||
SDL_Delay(((device->sample_frames * 1000) / device->spec.freq) * 2);
|
||||
ALSA_snd_pcm_close(device->hidden->pcm_handle);
|
||||
}
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
}
|
||||
}
|
||||
|
||||
static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *params)
|
||||
{
|
||||
int status;
|
||||
snd_pcm_hw_params_t *hwparams;
|
||||
snd_pcm_uframes_t persize;
|
||||
unsigned int periods;
|
||||
|
||||
// Copy the hardware parameters for this setup
|
||||
snd_pcm_hw_params_alloca(&hwparams);
|
||||
ALSA_snd_pcm_hw_params_copy(hwparams, params);
|
||||
|
||||
// Attempt to match the period size to the requested buffer size
|
||||
persize = device->sample_frames;
|
||||
status = ALSA_snd_pcm_hw_params_set_period_size_near(
|
||||
device->hidden->pcm_handle, hwparams, &persize, NULL);
|
||||
if (status < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Need to at least double buffer
|
||||
periods = 2;
|
||||
status = ALSA_snd_pcm_hw_params_set_periods_min(
|
||||
device->hidden->pcm_handle, hwparams, &periods, NULL);
|
||||
if (status < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = ALSA_snd_pcm_hw_params_set_periods_first(
|
||||
device->hidden->pcm_handle, hwparams, &periods, NULL);
|
||||
if (status < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// "set" the hardware with the desired parameters
|
||||
status = ALSA_snd_pcm_hw_params(device->hidden->pcm_handle, hwparams);
|
||||
if (status < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
device->sample_frames = persize;
|
||||
|
||||
// This is useful for debugging
|
||||
if (SDL_getenv("SDL_AUDIO_ALSA_DEBUG")) {
|
||||
snd_pcm_uframes_t bufsize;
|
||||
|
||||
ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize);
|
||||
|
||||
SDL_LogError(SDL_LOG_CATEGORY_AUDIO,
|
||||
"ALSA: period size = %ld, periods = %u, buffer size = %lu",
|
||||
persize, periods, bufsize);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ALSA_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const SDL_bool iscapture = device->iscapture;
|
||||
int status = 0;
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Open the audio device
|
||||
// Name of device should depend on # channels in spec
|
||||
snd_pcm_t *pcm_handle = NULL;
|
||||
status = ALSA_snd_pcm_open(&pcm_handle,
|
||||
get_audio_device(device->handle, device->spec.channels),
|
||||
iscapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
|
||||
SND_PCM_NONBLOCK);
|
||||
|
||||
if (status < 0) {
|
||||
return SDL_SetError("ALSA: Couldn't open audio device: %s", ALSA_snd_strerror(status));
|
||||
}
|
||||
|
||||
device->hidden->pcm_handle = pcm_handle;
|
||||
|
||||
// Figure out what the hardware is capable of
|
||||
snd_pcm_hw_params_t *hwparams = NULL;
|
||||
snd_pcm_hw_params_alloca(&hwparams);
|
||||
status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams);
|
||||
if (status < 0) {
|
||||
return SDL_SetError("ALSA: Couldn't get hardware config: %s", ALSA_snd_strerror(status));
|
||||
}
|
||||
|
||||
// SDL only uses interleaved sample output
|
||||
status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
if (status < 0) {
|
||||
return SDL_SetError("ALSA: Couldn't set interleaved access: %s", ALSA_snd_strerror(status));
|
||||
}
|
||||
|
||||
// Try for a closest match on audio format
|
||||
snd_pcm_format_t format = 0;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
SDL_AudioFormat test_format;
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_U8:
|
||||
format = SND_PCM_FORMAT_U8;
|
||||
break;
|
||||
case SDL_AUDIO_S8:
|
||||
format = SND_PCM_FORMAT_S8;
|
||||
break;
|
||||
case SDL_AUDIO_S16LE:
|
||||
format = SND_PCM_FORMAT_S16_LE;
|
||||
break;
|
||||
case SDL_AUDIO_S16BE:
|
||||
format = SND_PCM_FORMAT_S16_BE;
|
||||
break;
|
||||
case SDL_AUDIO_S32LE:
|
||||
format = SND_PCM_FORMAT_S32_LE;
|
||||
break;
|
||||
case SDL_AUDIO_S32BE:
|
||||
format = SND_PCM_FORMAT_S32_BE;
|
||||
break;
|
||||
case SDL_AUDIO_F32LE:
|
||||
format = SND_PCM_FORMAT_FLOAT_LE;
|
||||
break;
|
||||
case SDL_AUDIO_F32BE:
|
||||
format = SND_PCM_FORMAT_FLOAT_BE;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (ALSA_snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!test_format) {
|
||||
return SDL_SetError("ALSA: Unsupported audio format");
|
||||
}
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Validate number of channels and determine if swizzling is necessary.
|
||||
// Assume original swizzling, until proven otherwise.
|
||||
device->hidden->swizzle_func = swizzle_alsa_channels;
|
||||
#ifdef SND_CHMAP_API_VERSION
|
||||
snd_pcm_chmap_t *chmap = ALSA_snd_pcm_get_chmap(pcm_handle);
|
||||
if (chmap) {
|
||||
char chmap_str[64];
|
||||
if (ALSA_snd_pcm_chmap_print(chmap, sizeof(chmap_str), chmap_str) > 0) {
|
||||
if (SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0 ||
|
||||
SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0) {
|
||||
device->hidden->swizzle_func = no_swizzle;
|
||||
}
|
||||
}
|
||||
free(chmap); // This should NOT be SDL_free()
|
||||
}
|
||||
#endif // SND_CHMAP_API_VERSION
|
||||
|
||||
// Set the number of channels
|
||||
status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams,
|
||||
device->spec.channels);
|
||||
unsigned int channels = device->spec.channels;
|
||||
if (status < 0) {
|
||||
status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels);
|
||||
if (status < 0) {
|
||||
return SDL_SetError("ALSA: Couldn't set audio channels");
|
||||
}
|
||||
device->spec.channels = channels;
|
||||
}
|
||||
|
||||
// Set the audio rate
|
||||
unsigned int rate = device->spec.freq;
|
||||
status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams,
|
||||
&rate, NULL);
|
||||
if (status < 0) {
|
||||
return SDL_SetError("ALSA: Couldn't set audio frequency: %s", ALSA_snd_strerror(status));
|
||||
}
|
||||
device->spec.freq = rate;
|
||||
|
||||
// Set the buffer size, in samples
|
||||
status = ALSA_set_buffer_size(device, hwparams);
|
||||
if (status < 0) {
|
||||
return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status));
|
||||
}
|
||||
|
||||
// Set the software parameters
|
||||
snd_pcm_sw_params_t *swparams = NULL;
|
||||
snd_pcm_sw_params_alloca(&swparams);
|
||||
status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams);
|
||||
if (status < 0) {
|
||||
return SDL_SetError("ALSA: Couldn't get software config: %s", ALSA_snd_strerror(status));
|
||||
}
|
||||
status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, device->sample_frames);
|
||||
if (status < 0) {
|
||||
return SDL_SetError("Couldn't set minimum available samples: %s", ALSA_snd_strerror(status));
|
||||
}
|
||||
status =
|
||||
ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1);
|
||||
if (status < 0) {
|
||||
return SDL_SetError("ALSA: Couldn't set start threshold: %s", ALSA_snd_strerror(status));
|
||||
}
|
||||
status = ALSA_snd_pcm_sw_params(pcm_handle, swparams);
|
||||
if (status < 0) {
|
||||
return SDL_SetError("Couldn't set software audio parameters: %s", ALSA_snd_strerror(status));
|
||||
}
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
// Allocate mixing buffer
|
||||
if (!iscapture) {
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return -1;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
#if !SDL_ALSA_NON_BLOCKING
|
||||
if (!iscapture) {
|
||||
ALSA_snd_pcm_nonblock(pcm_handle, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
ALSA_snd_pcm_start(pcm_handle);
|
||||
|
||||
return 0; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static void add_device(const SDL_bool iscapture, const char *name, void *hint, ALSA_Device **pSeen)
|
||||
{
|
||||
ALSA_Device *dev = SDL_malloc(sizeof(ALSA_Device));
|
||||
char *desc;
|
||||
char *ptr;
|
||||
|
||||
if (!dev) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Not all alsa devices are enumerable via snd_device_name_get_hint
|
||||
// (i.e. bluetooth devices). Therefore if hint is passed in to this
|
||||
// function as NULL, assume name contains desc.
|
||||
// Make sure not to free the storage associated with desc in this case
|
||||
if (hint) {
|
||||
desc = ALSA_snd_device_name_get_hint(hint, "DESC");
|
||||
if (!desc) {
|
||||
SDL_free(dev);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
desc = (char *)name;
|
||||
}
|
||||
|
||||
SDL_assert(name != NULL);
|
||||
|
||||
// some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output".
|
||||
// just chop the extra lines off, this seems to get a reasonable device
|
||||
// name without extra details.
|
||||
ptr = SDL_strchr(desc, '\n');
|
||||
if (ptr) {
|
||||
*ptr = '\0';
|
||||
}
|
||||
|
||||
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: adding %s device '%s' (%s)", iscapture ? "capture" : "output", name, desc);
|
||||
|
||||
dev->name = SDL_strdup(name);
|
||||
if (!dev->name) {
|
||||
if (hint) {
|
||||
free(desc); // This should NOT be SDL_free()
|
||||
}
|
||||
SDL_free(dev->name);
|
||||
SDL_free(dev);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note that spec is NULL, because we are required to open the device before
|
||||
// acquiring the mix format, making this information inaccessible at
|
||||
// enumeration time
|
||||
SDL_AddAudioDevice(iscapture, desc, NULL, dev);
|
||||
if (hint) {
|
||||
free(desc); // This should NOT be SDL_free()
|
||||
}
|
||||
|
||||
dev->iscapture = iscapture;
|
||||
dev->next = *pSeen;
|
||||
*pSeen = dev;
|
||||
}
|
||||
|
||||
static ALSA_Device *hotplug_devices = NULL;
|
||||
|
||||
static void ALSA_HotplugIteration(SDL_bool *has_default_output, SDL_bool *has_default_capture)
|
||||
{
|
||||
void **hints = NULL;
|
||||
ALSA_Device *unseen = NULL;
|
||||
ALSA_Device *seen = NULL;
|
||||
|
||||
if (ALSA_snd_device_name_hint(-1, "pcm", &hints) == 0) {
|
||||
const char *match = NULL;
|
||||
int bestmatch = 0xFFFF;
|
||||
int has_default = -1;
|
||||
size_t match_len = 0;
|
||||
static const char *const prefixes[] = {
|
||||
"hw:", "sysdefault:", "default:", NULL
|
||||
};
|
||||
|
||||
unseen = hotplug_devices;
|
||||
seen = NULL;
|
||||
|
||||
// Apparently there are several different ways that ALSA lists
|
||||
// actual hardware. It could be prefixed with "hw:" or "default:"
|
||||
// or "sysdefault:" and maybe others. Go through the list and see
|
||||
// if we can find a preferred prefix for the system.
|
||||
for (int i = 0; hints[i]; i++) {
|
||||
char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SDL_strcmp(name, "default") == 0) {
|
||||
if (has_default < 0) {
|
||||
has_default = i;
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; prefixes[j]; j++) {
|
||||
const char *prefix = prefixes[j];
|
||||
const size_t prefixlen = SDL_strlen(prefix);
|
||||
if (SDL_strncmp(name, prefix, prefixlen) == 0) {
|
||||
if (j < bestmatch) {
|
||||
bestmatch = j;
|
||||
match = prefix;
|
||||
match_len = prefixlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(name); // This should NOT be SDL_free()
|
||||
}
|
||||
}
|
||||
|
||||
// look through the list of device names to find matches
|
||||
if (match || (has_default >= 0)) { // did we find a device name prefix we like at all...?
|
||||
for (int i = 0; hints[i]; i++) {
|
||||
char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// only want physical hardware interfaces
|
||||
const SDL_bool is_default = (has_default == i);
|
||||
if (is_default || (match && SDL_strncmp(name, match, match_len) == 0)) {
|
||||
char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID");
|
||||
const SDL_bool isoutput = (!ioid) || (SDL_strcmp(ioid, "Output") == 0);
|
||||
const SDL_bool isinput = (!ioid) || (SDL_strcmp(ioid, "Input") == 0);
|
||||
SDL_bool have_output = SDL_FALSE;
|
||||
SDL_bool have_input = SDL_FALSE;
|
||||
|
||||
free(ioid); // This should NOT be SDL_free()
|
||||
|
||||
if (!isoutput && !isinput) {
|
||||
free(name); // This should NOT be SDL_free()
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_default) {
|
||||
if (has_default_output && isoutput) {
|
||||
*has_default_output = SDL_TRUE;
|
||||
} else if (has_default_capture && isinput) {
|
||||
*has_default_capture = SDL_TRUE;
|
||||
}
|
||||
free(name); // This should NOT be SDL_free()
|
||||
continue;
|
||||
}
|
||||
|
||||
ALSA_Device *prev = NULL;
|
||||
ALSA_Device *next;
|
||||
for (ALSA_Device *dev = unseen; dev; dev = next) {
|
||||
next = dev->next;
|
||||
if ((SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->iscapture) || ((isoutput) && !dev->iscapture))) {
|
||||
if (prev) {
|
||||
prev->next = next;
|
||||
} else {
|
||||
unseen = next;
|
||||
}
|
||||
dev->next = seen;
|
||||
seen = dev;
|
||||
if (isinput) {
|
||||
have_input = SDL_TRUE;
|
||||
}
|
||||
if (isoutput) {
|
||||
have_output = SDL_TRUE;
|
||||
}
|
||||
} else {
|
||||
prev = dev;
|
||||
}
|
||||
}
|
||||
|
||||
if (isinput && !have_input) {
|
||||
add_device(SDL_TRUE, name, hints[i], &seen);
|
||||
}
|
||||
if (isoutput && !have_output) {
|
||||
add_device(SDL_FALSE, name, hints[i], &seen);
|
||||
}
|
||||
}
|
||||
|
||||
free(name); // This should NOT be SDL_free()
|
||||
}
|
||||
}
|
||||
|
||||
ALSA_snd_device_name_free_hint(hints);
|
||||
|
||||
hotplug_devices = seen; // now we have a known-good list of attached devices.
|
||||
|
||||
// report anything still in unseen as removed.
|
||||
ALSA_Device *next = NULL;
|
||||
for (ALSA_Device *dev = unseen; dev; dev = next) {
|
||||
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: removing %s device '%s'", dev->iscapture ? "capture" : "output", dev->name);
|
||||
next = dev->next;
|
||||
SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(dev));
|
||||
SDL_free(dev->name);
|
||||
SDL_free(dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if SDL_ALSA_HOTPLUG_THREAD
|
||||
static SDL_AtomicInt ALSA_hotplug_shutdown;
|
||||
static SDL_Thread *ALSA_hotplug_thread;
|
||||
|
||||
static int SDLCALL ALSA_HotplugThread(void *arg)
|
||||
{
|
||||
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW);
|
||||
|
||||
while (!SDL_AtomicGet(&ALSA_hotplug_shutdown)) {
|
||||
// Block awhile before checking again, unless we're told to stop.
|
||||
const Uint64 ticks = SDL_GetTicks() + 5000;
|
||||
while (!SDL_AtomicGet(&ALSA_hotplug_shutdown) && SDL_GetTicks() < ticks) {
|
||||
SDL_Delay(100);
|
||||
}
|
||||
|
||||
ALSA_HotplugIteration(NULL, NULL); // run the check.
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void ALSA_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
|
||||
{
|
||||
// ALSA doesn't have a concept of a changeable default device, afaik, so we expose a generic default
|
||||
// device here. It's the best we can do at this level.
|
||||
SDL_bool has_default_output = SDL_FALSE, has_default_capture = SDL_FALSE;
|
||||
ALSA_HotplugIteration(&has_default_output, &has_default_capture); // run once now before a thread continues to check.
|
||||
if (has_default_output) {
|
||||
*default_output = SDL_AddAudioDevice(/*iscapture=*/SDL_FALSE, "ALSA default output device", NULL, (void*)&default_output_handle);
|
||||
}
|
||||
if (has_default_capture) {
|
||||
*default_capture = SDL_AddAudioDevice(/*iscapture=*/SDL_TRUE, "ALSA default capture device", NULL, (void*)&default_capture_handle);
|
||||
}
|
||||
|
||||
#if SDL_ALSA_HOTPLUG_THREAD
|
||||
SDL_AtomicSet(&ALSA_hotplug_shutdown, 0);
|
||||
ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL);
|
||||
// if the thread doesn't spin, oh well, you just don't get further hotplug events.
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ALSA_DeinitializeStart(void)
|
||||
{
|
||||
ALSA_Device *dev;
|
||||
ALSA_Device *next;
|
||||
|
||||
#if SDL_ALSA_HOTPLUG_THREAD
|
||||
if (ALSA_hotplug_thread) {
|
||||
SDL_AtomicSet(&ALSA_hotplug_shutdown, 1);
|
||||
SDL_WaitThread(ALSA_hotplug_thread, NULL);
|
||||
ALSA_hotplug_thread = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Shutting down! Clean up any data we've gathered.
|
||||
for (dev = hotplug_devices; dev; dev = next) {
|
||||
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: at shutdown, removing %s device '%s'", dev->iscapture ? "capture" : "output", dev->name);
|
||||
next = dev->next;
|
||||
SDL_free(dev->name);
|
||||
SDL_free(dev);
|
||||
}
|
||||
hotplug_devices = NULL;
|
||||
}
|
||||
|
||||
static void ALSA_Deinitialize(void)
|
||||
{
|
||||
UnloadALSALibrary();
|
||||
}
|
||||
|
||||
static SDL_bool ALSA_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (LoadALSALibrary() < 0) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
impl->DetectDevices = ALSA_DetectDevices;
|
||||
impl->OpenDevice = ALSA_OpenDevice;
|
||||
impl->WaitDevice = ALSA_WaitDevice;
|
||||
impl->GetDeviceBuf = ALSA_GetDeviceBuf;
|
||||
impl->PlayDevice = ALSA_PlayDevice;
|
||||
impl->CloseDevice = ALSA_CloseDevice;
|
||||
impl->DeinitializeStart = ALSA_DeinitializeStart;
|
||||
impl->Deinitialize = ALSA_Deinitialize;
|
||||
impl->WaitCaptureDevice = ALSA_WaitDevice;
|
||||
impl->CaptureFromDevice = ALSA_CaptureFromDevice;
|
||||
impl->FlushCapture = ALSA_FlushCapture;
|
||||
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap ALSA_bootstrap = {
|
||||
"alsa", "ALSA PCM audio", ALSA_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_ALSA
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_ALSA_audio_h_
|
||||
#define SDL_ALSA_audio_h_
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The audio device handle
|
||||
snd_pcm_t *pcm_handle;
|
||||
|
||||
// Raw mixing buffer
|
||||
Uint8 *mixbuf;
|
||||
|
||||
// swizzle function
|
||||
void (*swizzle_func)(SDL_AudioDevice *_this, void *buffer, Uint32 bufferlen);
|
||||
};
|
||||
|
||||
#endif // SDL_ALSA_audio_h_
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_ANDROID
|
||||
|
||||
// Output audio to Android (legacy interface)
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_androidaudio.h"
|
||||
|
||||
#include "../../core/android/SDL_android.h"
|
||||
#include <android/log.h>
|
||||
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
int resume; // Resume device if it was paused automatically
|
||||
};
|
||||
|
||||
static SDL_AudioDevice *audioDevice = NULL;
|
||||
static SDL_AudioDevice *captureDevice = NULL;
|
||||
|
||||
static int ANDROIDAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const SDL_bool iscapture = device->iscapture;
|
||||
|
||||
if (iscapture) {
|
||||
if (captureDevice) {
|
||||
return SDL_SetError("An audio capture device is already opened");
|
||||
}
|
||||
captureDevice = device;
|
||||
} else {
|
||||
if (audioDevice) {
|
||||
return SDL_SetError("An audio playback device is already opened");
|
||||
}
|
||||
audioDevice = device;
|
||||
}
|
||||
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if ((test_format == SDL_AUDIO_U8) ||
|
||||
(test_format == SDL_AUDIO_S16) ||
|
||||
(test_format == SDL_AUDIO_F32)) {
|
||||
device->spec.format = test_format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("android: Unsupported audio format");
|
||||
}
|
||||
|
||||
if (Android_JNI_OpenAudioDevice(device) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// !!! FIXME: this needs a WaitDevice implementation.
|
||||
|
||||
static int ANDROIDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
Android_JNI_WriteAudioBuffer();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Uint8 *ANDROIDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return Android_JNI_GetAudioBuffer();
|
||||
}
|
||||
|
||||
static int ANDROIDAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
return Android_JNI_CaptureAudioBuffer(buffer, buflen);
|
||||
}
|
||||
|
||||
static void ANDROIDAUDIO_FlushCapture(SDL_AudioDevice *device)
|
||||
{
|
||||
Android_JNI_FlushCapturedAudio();
|
||||
}
|
||||
|
||||
static void ANDROIDAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
/* At this point SDL_CloseAudioDevice via close_audio_device took care of terminating the audio thread
|
||||
so it's safe to terminate the Java side buffer and AudioTrack
|
||||
*/
|
||||
if (device->hidden) {
|
||||
Android_JNI_CloseAudioDevice(device->iscapture);
|
||||
if (device->iscapture) {
|
||||
SDL_assert(captureDevice == device);
|
||||
captureDevice = NULL;
|
||||
} else {
|
||||
SDL_assert(audioDevice == device);
|
||||
audioDevice = NULL;
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Pause (block) all non already paused audio devices by taking their mixer lock
|
||||
void ANDROIDAUDIO_PauseDevices(void)
|
||||
{
|
||||
// TODO: Handle multiple devices?
|
||||
struct SDL_PrivateAudioData *hidden;
|
||||
if (audioDevice && audioDevice->hidden) {
|
||||
hidden = (struct SDL_PrivateAudioData *)audioDevice->hidden;
|
||||
SDL_LockMutex(audioDevice->lock);
|
||||
hidden->resume = SDL_TRUE;
|
||||
}
|
||||
|
||||
if (captureDevice && captureDevice->hidden) {
|
||||
hidden = (struct SDL_PrivateAudioData *)captureDevice->hidden;
|
||||
SDL_LockMutex(captureDevice->lock);
|
||||
hidden->resume = SDL_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Resume (unblock) all non already paused audio devices by releasing their mixer lock
|
||||
void ANDROIDAUDIO_ResumeDevices(void)
|
||||
{
|
||||
// TODO: Handle multiple devices?
|
||||
struct SDL_PrivateAudioData *hidden;
|
||||
if (audioDevice && audioDevice->hidden) {
|
||||
hidden = (struct SDL_PrivateAudioData *)audioDevice->hidden;
|
||||
if (hidden->resume) {
|
||||
hidden->resume = SDL_FALSE;
|
||||
SDL_UnlockMutex(audioDevice->lock);
|
||||
}
|
||||
}
|
||||
|
||||
if (captureDevice && captureDevice->hidden) {
|
||||
hidden = (struct SDL_PrivateAudioData *)captureDevice->hidden;
|
||||
if (hidden->resume) {
|
||||
hidden->resume = SDL_FALSE;
|
||||
SDL_UnlockMutex(captureDevice->lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_bool ANDROIDAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
// !!! FIXME: if on Android API < 24, DetectDevices and Deinitialize should be NULL and OnlyHasDefaultOutputDevice and OnlyHasDefaultCaptureDevice should be SDL_TRUE, since audio device enum and hotplug appears to require Android 7.0+.
|
||||
impl->ThreadInit = Android_AudioThreadInit;
|
||||
impl->DetectDevices = Android_StartAudioHotplug;
|
||||
impl->DeinitializeStart = Android_StopAudioHotplug;
|
||||
impl->OpenDevice = ANDROIDAUDIO_OpenDevice;
|
||||
impl->PlayDevice = ANDROIDAUDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = ANDROIDAUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = ANDROIDAUDIO_CloseDevice;
|
||||
impl->CaptureFromDevice = ANDROIDAUDIO_CaptureFromDevice;
|
||||
impl->FlushCapture = ANDROIDAUDIO_FlushCapture;
|
||||
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap ANDROIDAUDIO_bootstrap = {
|
||||
"android", "SDL Android audio driver", ANDROIDAUDIO_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_ANDROID
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_androidaudio_h_
|
||||
#define SDL_androidaudio_h_
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_ANDROID
|
||||
|
||||
void ANDROIDAUDIO_ResumeDevices(void);
|
||||
void ANDROIDAUDIO_PauseDevices(void);
|
||||
|
||||
#else
|
||||
|
||||
static void ANDROIDAUDIO_ResumeDevices(void) {}
|
||||
static void ANDROIDAUDIO_PauseDevices(void) {}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // SDL_androidaudio_h_
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_coreaudio_h_
|
||||
#define SDL_coreaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#ifndef SDL_PLATFORM_IOS
|
||||
#define MACOSX_COREAUDIO
|
||||
#endif
|
||||
|
||||
#ifdef MACOSX_COREAUDIO
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
#else
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <UIKit/UIApplication.h>
|
||||
#endif
|
||||
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
|
||||
// Things named "Master" were renamed to "Main" in macOS 12.0's SDK.
|
||||
#ifdef MACOSX_COREAUDIO
|
||||
#include <AvailabilityMacros.h>
|
||||
#ifndef MAC_OS_VERSION_12_0
|
||||
#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
SDL_Thread *thread;
|
||||
AudioQueueRef audioQueue;
|
||||
int numAudioBuffers;
|
||||
AudioQueueBufferRef *audioBuffer;
|
||||
AudioQueueBufferRef current_buffer;
|
||||
AudioStreamBasicDescription strdesc;
|
||||
SDL_Semaphore *ready_semaphore;
|
||||
char *thread_error;
|
||||
#ifdef MACOSX_COREAUDIO
|
||||
AudioDeviceID deviceID;
|
||||
#else
|
||||
SDL_bool interrupted;
|
||||
CFTypeRef interruption_listener;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // SDL_coreaudio_h_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,680 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_DSOUND
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_directsound.h"
|
||||
#include <mmreg.h>
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
#include "../../core/windows/SDL_immdevice.h"
|
||||
#endif
|
||||
|
||||
#ifndef WAVE_FORMAT_IEEE_FLOAT
|
||||
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
|
||||
#endif
|
||||
|
||||
// For Vista+, we can enumerate DSound devices with IMMDevice
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
static SDL_bool SupportsIMMDevice = SDL_FALSE;
|
||||
#endif
|
||||
|
||||
// DirectX function pointers for audio
|
||||
static void *DSoundDLL = NULL;
|
||||
typedef HRESULT(WINAPI *fnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
|
||||
typedef HRESULT(WINAPI *fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
|
||||
typedef HRESULT(WINAPI *fnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN);
|
||||
typedef HRESULT(WINAPI *fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
|
||||
typedef HRESULT(WINAPI *fnGetDeviceID)(LPCGUID, LPGUID);
|
||||
static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL;
|
||||
static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL;
|
||||
static fnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL;
|
||||
static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL;
|
||||
static fnGetDeviceID pGetDeviceID = NULL;
|
||||
|
||||
#include <initguid.h>
|
||||
DEFINE_GUID(SDL_DSDEVID_DefaultPlayback, 0xdef00000, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
|
||||
DEFINE_GUID(SDL_DSDEVID_DefaultCapture, 0xdef00001, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
|
||||
|
||||
static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
|
||||
static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
|
||||
|
||||
static void DSOUND_Unload(void)
|
||||
{
|
||||
pDirectSoundCreate8 = NULL;
|
||||
pDirectSoundEnumerateW = NULL;
|
||||
pDirectSoundCaptureCreate8 = NULL;
|
||||
pDirectSoundCaptureEnumerateW = NULL;
|
||||
pGetDeviceID = NULL;
|
||||
|
||||
if (DSoundDLL) {
|
||||
SDL_UnloadObject(DSoundDLL);
|
||||
DSoundDLL = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int DSOUND_Load(void)
|
||||
{
|
||||
int loaded = 0;
|
||||
|
||||
DSOUND_Unload();
|
||||
|
||||
DSoundDLL = SDL_LoadObject("DSOUND.DLL");
|
||||
if (!DSoundDLL) {
|
||||
SDL_SetError("DirectSound: failed to load DSOUND.DLL");
|
||||
} else {
|
||||
// Now make sure we have DirectX 8 or better...
|
||||
#define DSOUNDLOAD(f) \
|
||||
{ \
|
||||
p##f = (fn##f)SDL_LoadFunction(DSoundDLL, #f); \
|
||||
if (!p##f) \
|
||||
loaded = 0; \
|
||||
}
|
||||
loaded = 1; // will reset if necessary.
|
||||
DSOUNDLOAD(DirectSoundCreate8);
|
||||
DSOUNDLOAD(DirectSoundEnumerateW);
|
||||
DSOUNDLOAD(DirectSoundCaptureCreate8);
|
||||
DSOUNDLOAD(DirectSoundCaptureEnumerateW);
|
||||
DSOUNDLOAD(GetDeviceID);
|
||||
#undef DSOUNDLOAD
|
||||
|
||||
if (!loaded) {
|
||||
SDL_SetError("DirectSound: System doesn't appear to have DX8.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
DSOUND_Unload();
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
static int SetDSerror(const char *function, int code)
|
||||
{
|
||||
const char *error;
|
||||
|
||||
switch (code) {
|
||||
case E_NOINTERFACE:
|
||||
error = "Unsupported interface -- Is DirectX 8.0 or later installed?";
|
||||
break;
|
||||
case DSERR_ALLOCATED:
|
||||
error = "Audio device in use";
|
||||
break;
|
||||
case DSERR_BADFORMAT:
|
||||
error = "Unsupported audio format";
|
||||
break;
|
||||
case DSERR_BUFFERLOST:
|
||||
error = "Mixing buffer was lost";
|
||||
break;
|
||||
case DSERR_CONTROLUNAVAIL:
|
||||
error = "Control requested is not available";
|
||||
break;
|
||||
case DSERR_INVALIDCALL:
|
||||
error = "Invalid call for the current state";
|
||||
break;
|
||||
case DSERR_INVALIDPARAM:
|
||||
error = "Invalid parameter";
|
||||
break;
|
||||
case DSERR_NODRIVER:
|
||||
error = "No audio device found";
|
||||
break;
|
||||
case DSERR_OUTOFMEMORY:
|
||||
error = "Out of memory";
|
||||
break;
|
||||
case DSERR_PRIOLEVELNEEDED:
|
||||
error = "Caller doesn't have priority";
|
||||
break;
|
||||
case DSERR_UNSUPPORTED:
|
||||
error = "Function not supported";
|
||||
break;
|
||||
default:
|
||||
error = "Unknown DirectSound error";
|
||||
break;
|
||||
}
|
||||
|
||||
return SDL_SetError("%s: %s (0x%x)", function, error, code);
|
||||
}
|
||||
|
||||
static void DSOUND_FreeDeviceHandle(SDL_AudioDevice *device)
|
||||
{
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
if (SupportsIMMDevice) {
|
||||
SDL_IMMDevice_FreeDeviceHandle(device);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
SDL_free(device->handle);
|
||||
}
|
||||
}
|
||||
|
||||
// FindAllDevs is presumably only used on WinXP; Vista and later can use IMMDevice for better results.
|
||||
typedef struct FindAllDevsData
|
||||
{
|
||||
SDL_bool iscapture;
|
||||
SDL_AudioDevice **default_device;
|
||||
LPCGUID default_device_guid;
|
||||
} FindAllDevsData;
|
||||
|
||||
static BOOL CALLBACK FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID userdata)
|
||||
{
|
||||
FindAllDevsData *data = (FindAllDevsData *) userdata;
|
||||
if (guid != NULL) { // skip default device
|
||||
char *str = WIN_LookupAudioDeviceName(desc, guid);
|
||||
if (str) {
|
||||
LPGUID cpyguid = (LPGUID)SDL_malloc(sizeof(GUID));
|
||||
if (cpyguid) {
|
||||
SDL_copyp(cpyguid, guid);
|
||||
|
||||
/* Note that spec is NULL, because we are required to connect to the
|
||||
* device before getting the channel mask and output format, making
|
||||
* this information inaccessible at enumeration time
|
||||
*/
|
||||
SDL_AudioDevice *device = SDL_AddAudioDevice(data->iscapture, str, NULL, cpyguid);
|
||||
if (device && data->default_device && data->default_device_guid) {
|
||||
if (SDL_memcmp(cpyguid, data->default_device_guid, sizeof (GUID)) == 0) {
|
||||
*data->default_device = device;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_free(str); // SDL_AddAudioDevice() makes a copy of this string.
|
||||
}
|
||||
}
|
||||
return TRUE; // keep enumerating.
|
||||
}
|
||||
|
||||
static void DSOUND_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
|
||||
{
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
if (SupportsIMMDevice) {
|
||||
SDL_IMMDevice_EnumerateEndpoints(default_output, default_capture);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
// Without IMMDevice, you can enumerate devices and figure out the default devices,
|
||||
// but you won't get device hotplug or default device change notifications. But this is
|
||||
// only for WinXP; Windows Vista and later should be using IMMDevice.
|
||||
FindAllDevsData data;
|
||||
GUID guid;
|
||||
|
||||
data.iscapture = SDL_TRUE;
|
||||
data.default_device = default_capture;
|
||||
data.default_device_guid = (pGetDeviceID(&SDL_DSDEVID_DefaultCapture, &guid) == DS_OK) ? &guid : NULL;
|
||||
pDirectSoundCaptureEnumerateW(FindAllDevs, &data);
|
||||
|
||||
data.iscapture = SDL_FALSE;
|
||||
data.default_device = default_output;
|
||||
data.default_device_guid = (pGetDeviceID(&SDL_DSDEVID_DefaultPlayback, &guid) == DS_OK) ? &guid : NULL;
|
||||
pDirectSoundEnumerateW(FindAllDevs, &data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static int DSOUND_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
/* Semi-busy wait, since we have no way of getting play notification
|
||||
on a primary mixing buffer located in hardware (DirectX 5.0)
|
||||
*/
|
||||
while (!SDL_AtomicGet(&device->shutdown)) {
|
||||
DWORD status = 0;
|
||||
DWORD cursor = 0;
|
||||
DWORD junk = 0;
|
||||
HRESULT result = DS_OK;
|
||||
|
||||
// Try to restore a lost sound buffer
|
||||
IDirectSoundBuffer_GetStatus(device->hidden->mixbuf, &status);
|
||||
if (status & DSBSTATUS_BUFFERLOST) {
|
||||
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
|
||||
} else if (!(status & DSBSTATUS_PLAYING)) {
|
||||
result = IDirectSoundBuffer_Play(device->hidden->mixbuf, 0, 0, DSBPLAY_LOOPING);
|
||||
} else {
|
||||
// Find out where we are playing
|
||||
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, &junk, &cursor);
|
||||
if ((result == DS_OK) && ((cursor / device->buffer_size) != device->hidden->lastchunk)) {
|
||||
break; // ready for next chunk!
|
||||
}
|
||||
}
|
||||
|
||||
if ((result != DS_OK) && (result != DSERR_BUFFERLOST)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_Delay(1); // not ready yet; sleep a bit.
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DSOUND_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
// Unlock the buffer, allowing it to play
|
||||
SDL_assert(buflen == device->buffer_size);
|
||||
if (IDirectSoundBuffer_Unlock(device->hidden->mixbuf, (LPVOID) buffer, buflen, NULL, 0) != DS_OK) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Uint8 *DSOUND_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
DWORD cursor = 0;
|
||||
DWORD junk = 0;
|
||||
HRESULT result = DS_OK;
|
||||
|
||||
SDL_assert(*buffer_size == device->buffer_size);
|
||||
|
||||
// Figure out which blocks to fill next
|
||||
device->hidden->locked_buf = NULL;
|
||||
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf,
|
||||
&junk, &cursor);
|
||||
if (result == DSERR_BUFFERLOST) {
|
||||
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
|
||||
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf,
|
||||
&junk, &cursor);
|
||||
}
|
||||
if (result != DS_OK) {
|
||||
SetDSerror("DirectSound GetCurrentPosition", result);
|
||||
return NULL;
|
||||
}
|
||||
cursor /= device->buffer_size;
|
||||
#ifdef DEBUG_SOUND
|
||||
// Detect audio dropouts
|
||||
{
|
||||
DWORD spot = cursor;
|
||||
if (spot < device->hidden->lastchunk) {
|
||||
spot += device->hidden->num_buffers;
|
||||
}
|
||||
if (spot > device->hidden->lastchunk + 1) {
|
||||
fprintf(stderr, "Audio dropout, missed %d fragments\n",
|
||||
(spot - (device->hidden->lastchunk + 1)));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
device->hidden->lastchunk = cursor;
|
||||
cursor = (cursor + 1) % device->hidden->num_buffers;
|
||||
cursor *= device->buffer_size;
|
||||
|
||||
// Lock the audio buffer
|
||||
DWORD rawlen = 0;
|
||||
result = IDirectSoundBuffer_Lock(device->hidden->mixbuf, cursor,
|
||||
device->buffer_size,
|
||||
(LPVOID *)&device->hidden->locked_buf,
|
||||
&rawlen, NULL, &junk, 0);
|
||||
if (result == DSERR_BUFFERLOST) {
|
||||
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
|
||||
result = IDirectSoundBuffer_Lock(device->hidden->mixbuf, cursor,
|
||||
device->buffer_size,
|
||||
(LPVOID *)&device->hidden->locked_buf, &rawlen, NULL,
|
||||
&junk, 0);
|
||||
}
|
||||
if (result != DS_OK) {
|
||||
SetDSerror("DirectSound Lock", result);
|
||||
return NULL;
|
||||
}
|
||||
return device->hidden->locked_buf;
|
||||
}
|
||||
|
||||
static int DSOUND_WaitCaptureDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
while (!SDL_AtomicGet(&device->shutdown)) {
|
||||
DWORD junk, cursor;
|
||||
if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) {
|
||||
return -1;
|
||||
} else if ((cursor / device->buffer_size) != h->lastchunk) {
|
||||
break;
|
||||
}
|
||||
SDL_Delay(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DSOUND_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
DWORD ptr1len, ptr2len;
|
||||
VOID *ptr1, *ptr2;
|
||||
|
||||
SDL_assert(buflen == device->buffer_size);
|
||||
|
||||
if (IDirectSoundCaptureBuffer_Lock(h->capturebuf, h->lastchunk * buflen, buflen, &ptr1, &ptr1len, &ptr2, &ptr2len, 0) != DS_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_assert(ptr1len == (DWORD)buflen);
|
||||
SDL_assert(ptr2 == NULL);
|
||||
SDL_assert(ptr2len == 0);
|
||||
|
||||
SDL_memcpy(buffer, ptr1, ptr1len);
|
||||
|
||||
if (IDirectSoundCaptureBuffer_Unlock(h->capturebuf, ptr1, ptr1len, ptr2, ptr2len) != DS_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
h->lastchunk = (h->lastchunk + 1) % h->num_buffers;
|
||||
|
||||
return (int) ptr1len;
|
||||
}
|
||||
|
||||
static void DSOUND_FlushCapture(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
DWORD junk, cursor;
|
||||
if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) == DS_OK) {
|
||||
h->lastchunk = cursor / device->buffer_size;
|
||||
}
|
||||
}
|
||||
|
||||
static void DSOUND_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->mixbuf) {
|
||||
IDirectSoundBuffer_Stop(device->hidden->mixbuf);
|
||||
IDirectSoundBuffer_Release(device->hidden->mixbuf);
|
||||
}
|
||||
if (device->hidden->sound) {
|
||||
IDirectSound_Release(device->hidden->sound);
|
||||
}
|
||||
if (device->hidden->capturebuf) {
|
||||
IDirectSoundCaptureBuffer_Stop(device->hidden->capturebuf);
|
||||
IDirectSoundCaptureBuffer_Release(device->hidden->capturebuf);
|
||||
}
|
||||
if (device->hidden->capture) {
|
||||
IDirectSoundCapture_Release(device->hidden->capture);
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* This function tries to create a secondary audio buffer, and returns the
|
||||
number of audio chunks available in the created buffer. This is for
|
||||
playback devices, not capture.
|
||||
*/
|
||||
static int CreateSecondary(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt)
|
||||
{
|
||||
LPDIRECTSOUND sndObj = device->hidden->sound;
|
||||
LPDIRECTSOUNDBUFFER *sndbuf = &device->hidden->mixbuf;
|
||||
HRESULT result = DS_OK;
|
||||
DSBUFFERDESC format;
|
||||
LPVOID pvAudioPtr1, pvAudioPtr2;
|
||||
DWORD dwAudioBytes1, dwAudioBytes2;
|
||||
|
||||
// Try to create the secondary buffer
|
||||
SDL_zero(format);
|
||||
format.dwSize = sizeof(format);
|
||||
format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
|
||||
format.dwFlags |= DSBCAPS_GLOBALFOCUS;
|
||||
format.dwBufferBytes = bufsize;
|
||||
format.lpwfxFormat = wfmt;
|
||||
result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSound CreateSoundBuffer", result);
|
||||
}
|
||||
IDirectSoundBuffer_SetFormat(*sndbuf, wfmt);
|
||||
|
||||
// Silence the initial audio buffer
|
||||
result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes,
|
||||
(LPVOID *)&pvAudioPtr1, &dwAudioBytes1,
|
||||
(LPVOID *)&pvAudioPtr2, &dwAudioBytes2,
|
||||
DSBLOCK_ENTIREBUFFER);
|
||||
if (result == DS_OK) {
|
||||
SDL_memset(pvAudioPtr1, device->silence_value, dwAudioBytes1);
|
||||
IDirectSoundBuffer_Unlock(*sndbuf,
|
||||
(LPVOID)pvAudioPtr1, dwAudioBytes1,
|
||||
(LPVOID)pvAudioPtr2, dwAudioBytes2);
|
||||
}
|
||||
|
||||
return 0; // We're ready to go
|
||||
}
|
||||
|
||||
/* This function tries to create a capture buffer, and returns the
|
||||
number of audio chunks available in the created buffer. This is for
|
||||
capture devices, not playback.
|
||||
*/
|
||||
static int CreateCaptureBuffer(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt)
|
||||
{
|
||||
LPDIRECTSOUNDCAPTURE capture = device->hidden->capture;
|
||||
LPDIRECTSOUNDCAPTUREBUFFER *capturebuf = &device->hidden->capturebuf;
|
||||
DSCBUFFERDESC format;
|
||||
HRESULT result;
|
||||
|
||||
SDL_zero(format);
|
||||
format.dwSize = sizeof(format);
|
||||
format.dwFlags = DSCBCAPS_WAVEMAPPED;
|
||||
format.dwBufferBytes = bufsize;
|
||||
format.lpwfxFormat = wfmt;
|
||||
|
||||
result = IDirectSoundCapture_CreateCaptureBuffer(capture, &format, capturebuf, NULL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSound CreateCaptureBuffer", result);
|
||||
}
|
||||
|
||||
result = IDirectSoundCaptureBuffer_Start(*capturebuf, DSCBSTART_LOOPING);
|
||||
if (result != DS_OK) {
|
||||
IDirectSoundCaptureBuffer_Release(*capturebuf);
|
||||
return SetDSerror("DirectSound Start", result);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// presumably this starts at zero, but just in case...
|
||||
result = IDirectSoundCaptureBuffer_GetCurrentPosition(*capturebuf, &junk, &cursor);
|
||||
if (result != DS_OK) {
|
||||
IDirectSoundCaptureBuffer_Stop(*capturebuf);
|
||||
IDirectSoundCaptureBuffer_Release(*capturebuf);
|
||||
return SetDSerror("DirectSound GetCurrentPosition", result);
|
||||
}
|
||||
|
||||
device->hidden->lastchunk = cursor / device->buffer_size;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DSOUND_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Open the audio device
|
||||
LPGUID guid;
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
if (SupportsIMMDevice) {
|
||||
guid = SDL_IMMDevice_GetDirectSoundGUID(device);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
guid = (LPGUID) device->handle;
|
||||
}
|
||||
|
||||
SDL_assert(guid != NULL);
|
||||
|
||||
HRESULT result;
|
||||
if (device->iscapture) {
|
||||
result = pDirectSoundCaptureCreate8(guid, &device->hidden->capture, NULL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSoundCaptureCreate8", result);
|
||||
}
|
||||
} else {
|
||||
result = pDirectSoundCreate8(guid, &device->hidden->sound, NULL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSoundCreate8", result);
|
||||
}
|
||||
result = IDirectSound_SetCooperativeLevel(device->hidden->sound,
|
||||
GetDesktopWindow(),
|
||||
DSSCL_NORMAL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSound SetCooperativeLevel", result);
|
||||
}
|
||||
}
|
||||
|
||||
const DWORD numchunks = 8;
|
||||
DWORD bufsize;
|
||||
SDL_bool tried_format = SDL_FALSE;
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_U8:
|
||||
case SDL_AUDIO_S16:
|
||||
case SDL_AUDIO_S32:
|
||||
case SDL_AUDIO_F32:
|
||||
tried_format = SDL_TRUE;
|
||||
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
bufsize = numchunks * device->buffer_size;
|
||||
if ((bufsize < DSBSIZE_MIN) || (bufsize > DSBSIZE_MAX)) {
|
||||
SDL_SetError("Sound buffer size must be between %d and %d",
|
||||
(int)((DSBSIZE_MIN < numchunks) ? 1 : DSBSIZE_MIN / numchunks),
|
||||
(int)(DSBSIZE_MAX / numchunks));
|
||||
} else {
|
||||
WAVEFORMATEXTENSIBLE wfmt;
|
||||
SDL_zero(wfmt);
|
||||
if (device->spec.channels > 2) {
|
||||
wfmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
wfmt.Format.cbSize = sizeof(wfmt) - sizeof(WAVEFORMATEX);
|
||||
|
||||
if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
|
||||
SDL_memcpy(&wfmt.SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID));
|
||||
} else {
|
||||
SDL_memcpy(&wfmt.SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID));
|
||||
}
|
||||
wfmt.Samples.wValidBitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
|
||||
switch (device->spec.channels) {
|
||||
case 3: // 3.0 (or 2.1)
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER;
|
||||
break;
|
||||
case 4: // 4.0
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case 5: // 5.0 (or 4.1)
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case 6: // 5.1
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case 7: // 6.1
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_BACK_CENTER;
|
||||
break;
|
||||
case 8: // 7.1
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
|
||||
break;
|
||||
default:
|
||||
SDL_assert(!"Unsupported channel count!");
|
||||
break;
|
||||
}
|
||||
} else if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
|
||||
wfmt.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
|
||||
} else {
|
||||
wfmt.Format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
}
|
||||
|
||||
wfmt.Format.wBitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
wfmt.Format.nChannels = (WORD)device->spec.channels;
|
||||
wfmt.Format.nSamplesPerSec = device->spec.freq;
|
||||
wfmt.Format.nBlockAlign = wfmt.Format.nChannels * (wfmt.Format.wBitsPerSample / 8);
|
||||
wfmt.Format.nAvgBytesPerSec = wfmt.Format.nSamplesPerSec * wfmt.Format.nBlockAlign;
|
||||
|
||||
const int rc = device->iscapture ? CreateCaptureBuffer(device, bufsize, (WAVEFORMATEX *)&wfmt) : CreateSecondary(device, bufsize, (WAVEFORMATEX *)&wfmt);
|
||||
if (rc == 0) {
|
||||
device->hidden->num_buffers = numchunks;
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
if (tried_format) {
|
||||
return -1; // CreateSecondary() should have called SDL_SetError().
|
||||
}
|
||||
return SDL_SetError("%s: Unsupported audio format", "directsound");
|
||||
}
|
||||
|
||||
// Playback buffers will auto-start playing in DSOUND_WaitDevice()
|
||||
|
||||
return 0; // good to go.
|
||||
}
|
||||
|
||||
static void DSOUND_DeinitializeStart(void)
|
||||
{
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
if (SupportsIMMDevice) {
|
||||
SDL_IMMDevice_Quit();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void DSOUND_Deinitialize(void)
|
||||
{
|
||||
DSOUND_Unload();
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
SupportsIMMDevice = SDL_FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
static SDL_bool DSOUND_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!DSOUND_Load()) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
SupportsIMMDevice = !(SDL_IMMDevice_Init(NULL) < 0);
|
||||
#endif
|
||||
|
||||
impl->DetectDevices = DSOUND_DetectDevices;
|
||||
impl->OpenDevice = DSOUND_OpenDevice;
|
||||
impl->PlayDevice = DSOUND_PlayDevice;
|
||||
impl->WaitDevice = DSOUND_WaitDevice;
|
||||
impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
|
||||
impl->WaitCaptureDevice = DSOUND_WaitCaptureDevice;
|
||||
impl->CaptureFromDevice = DSOUND_CaptureFromDevice;
|
||||
impl->FlushCapture = DSOUND_FlushCapture;
|
||||
impl->CloseDevice = DSOUND_CloseDevice;
|
||||
impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle;
|
||||
impl->DeinitializeStart = DSOUND_DeinitializeStart;
|
||||
impl->Deinitialize = DSOUND_Deinitialize;
|
||||
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap DSOUND_bootstrap = {
|
||||
"directsound", "DirectSound", DSOUND_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_DSOUND
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_directsound_h_
|
||||
#define SDL_directsound_h_
|
||||
|
||||
#include "../../core/windows/SDL_directx.h"
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
// The DirectSound objects
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// !!! FIXME: make this a union with capture/playback sections?
|
||||
LPDIRECTSOUND sound;
|
||||
LPDIRECTSOUNDBUFFER mixbuf;
|
||||
LPDIRECTSOUNDCAPTURE capture;
|
||||
LPDIRECTSOUNDCAPTUREBUFFER capturebuf;
|
||||
int num_buffers;
|
||||
DWORD lastchunk;
|
||||
Uint8 *locked_buf;
|
||||
};
|
||||
|
||||
#endif // SDL_directsound_h_
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_DISK
|
||||
|
||||
// Output raw audio data to a file.
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_diskaudio.h"
|
||||
|
||||
// !!! FIXME: these should be SDL hints, not environment variables.
|
||||
// environment variables and defaults.
|
||||
#define DISKENVR_OUTFILE "SDL_DISKAUDIOFILE"
|
||||
#define DISKDEFAULT_OUTFILE "sdlaudio.raw"
|
||||
#define DISKENVR_INFILE "SDL_DISKAUDIOFILEIN"
|
||||
#define DISKDEFAULT_INFILE "sdlaudio-in.raw"
|
||||
#define DISKENVR_IODELAY "SDL_DISKAUDIODELAY"
|
||||
|
||||
static int DISKAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
SDL_Delay(device->hidden->io_delay);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DISKAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
const int written = (int)SDL_WriteIO(device->hidden->io, buffer, (size_t)buffer_size);
|
||||
if (written != buffer_size) { // If we couldn't write, assume fatal error for now
|
||||
return -1;
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
SDL_Log("DISKAUDIO: Wrote %d bytes of audio data", (int) written);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Uint8 *DISKAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int DISKAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
const int origbuflen = buflen;
|
||||
|
||||
if (h->io) {
|
||||
const int br = (int)SDL_ReadIO(h->io, buffer, (size_t)buflen);
|
||||
buflen -= br;
|
||||
buffer = ((Uint8 *)buffer) + br;
|
||||
if (buflen > 0) { // EOF (or error, but whatever).
|
||||
SDL_CloseIO(h->io);
|
||||
h->io = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// if we ran out of file, just write silence.
|
||||
SDL_memset(buffer, device->silence_value, buflen);
|
||||
|
||||
return origbuflen;
|
||||
}
|
||||
|
||||
static void DISKAUDIO_FlushCapture(SDL_AudioDevice *device)
|
||||
{
|
||||
// no op...we don't advance the file pointer or anything.
|
||||
}
|
||||
|
||||
static void DISKAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->io) {
|
||||
SDL_CloseIO(device->hidden->io);
|
||||
}
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *get_filename(const SDL_bool iscapture)
|
||||
{
|
||||
const char *devname = SDL_getenv(iscapture ? DISKENVR_INFILE : DISKENVR_OUTFILE);
|
||||
if (!devname) {
|
||||
devname = iscapture ? DISKDEFAULT_INFILE : DISKDEFAULT_OUTFILE;
|
||||
}
|
||||
return devname;
|
||||
}
|
||||
|
||||
static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
SDL_bool iscapture = device->iscapture;
|
||||
const char *fname = get_filename(iscapture);
|
||||
const char *envr = SDL_getenv(DISKENVR_IODELAY);
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (envr) {
|
||||
device->hidden->io_delay = SDL_atoi(envr);
|
||||
} else {
|
||||
device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq);
|
||||
}
|
||||
|
||||
// Open the "audio device"
|
||||
device->hidden->io = SDL_IOFromFile(fname, iscapture ? "rb" : "wb");
|
||||
if (!device->hidden->io) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Allocate mixing buffer
|
||||
if (!iscapture) {
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return -1;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, "You are using the SDL disk i/o audio driver!");
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s].\n", iscapture ? "Reading from" : "Writing to", fname);
|
||||
|
||||
return 0; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static void DISKAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
|
||||
{
|
||||
*default_output = SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1);
|
||||
*default_capture = SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2);
|
||||
}
|
||||
|
||||
static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = DISKAUDIO_OpenDevice;
|
||||
impl->WaitDevice = DISKAUDIO_WaitDevice;
|
||||
impl->WaitCaptureDevice = DISKAUDIO_WaitDevice;
|
||||
impl->PlayDevice = DISKAUDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = DISKAUDIO_GetDeviceBuf;
|
||||
impl->CaptureFromDevice = DISKAUDIO_CaptureFromDevice;
|
||||
impl->FlushCapture = DISKAUDIO_FlushCapture;
|
||||
impl->CloseDevice = DISKAUDIO_CloseDevice;
|
||||
impl->DetectDevices = DISKAUDIO_DetectDevices;
|
||||
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap DISKAUDIO_bootstrap = {
|
||||
"disk", "direct-to-disk audio", DISKAUDIO_Init, SDL_TRUE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_DISK
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_diskaudio_h_
|
||||
#define SDL_diskaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The file descriptor for the audio device
|
||||
SDL_IOStream *io;
|
||||
Uint32 io_delay;
|
||||
Uint8 *mixbuf;
|
||||
};
|
||||
|
||||
#endif // SDL_diskaudio_h_
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// !!! FIXME: clean out perror and fprintf calls in here.
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_OSS
|
||||
|
||||
#include <stdio.h> // For perror()
|
||||
#include <string.h> // For strerror()
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <sys/soundcard.h>
|
||||
|
||||
#include "../SDL_audiodev_c.h"
|
||||
#include "../../SDL_utils_c.h"
|
||||
#include "SDL_dspaudio.h"
|
||||
|
||||
static void DSP_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
|
||||
{
|
||||
SDL_EnumUnixAudioDevices(SDL_FALSE, NULL);
|
||||
}
|
||||
|
||||
static void DSP_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->audio_fd >= 0) {
|
||||
close(device->hidden->audio_fd);
|
||||
}
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
}
|
||||
}
|
||||
|
||||
static int DSP_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Make sure fragment size stays a power of 2, or OSS fails.
|
||||
// (I don't know which of these are actually legal values, though...)
|
||||
if (device->spec.channels > 8) {
|
||||
device->spec.channels = 8;
|
||||
} else if (device->spec.channels > 4) {
|
||||
device->spec.channels = 4;
|
||||
} else if (device->spec.channels > 2) {
|
||||
device->spec.channels = 2;
|
||||
}
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
|
||||
const int flags = ((device->iscapture) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT);
|
||||
device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC, 0);
|
||||
if (device->hidden->audio_fd < 0) {
|
||||
return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno));
|
||||
}
|
||||
|
||||
// Make the file descriptor use blocking i/o with fcntl()
|
||||
{
|
||||
const long ctlflags = fcntl(device->hidden->audio_fd, F_GETFL) & ~O_NONBLOCK;
|
||||
if (fcntl(device->hidden->audio_fd, F_SETFL, ctlflags) < 0) {
|
||||
return SDL_SetError("Couldn't set audio blocking mode");
|
||||
}
|
||||
}
|
||||
|
||||
// Get a list of supported hardware formats
|
||||
int value;
|
||||
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_GETFMTS, &value) < 0) {
|
||||
perror("SNDCTL_DSP_GETFMTS");
|
||||
return SDL_SetError("Couldn't get audio format list");
|
||||
}
|
||||
|
||||
// Try for a closest match on audio format
|
||||
int format = 0;
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
|
||||
#endif
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_U8:
|
||||
if (value & AFMT_U8) {
|
||||
format = AFMT_U8;
|
||||
}
|
||||
break;
|
||||
case SDL_AUDIO_S16LE:
|
||||
if (value & AFMT_S16_LE) {
|
||||
format = AFMT_S16_LE;
|
||||
}
|
||||
break;
|
||||
case SDL_AUDIO_S16BE:
|
||||
if (value & AFMT_S16_BE) {
|
||||
format = AFMT_S16_BE;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (format == 0) {
|
||||
return SDL_SetError("Couldn't find any hardware audio formats");
|
||||
}
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Set the audio format
|
||||
value = format;
|
||||
if ((ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFMT, &value) < 0) ||
|
||||
(value != format)) {
|
||||
perror("SNDCTL_DSP_SETFMT");
|
||||
return SDL_SetError("Couldn't set audio format");
|
||||
}
|
||||
|
||||
// Set the number of channels of output
|
||||
value = device->spec.channels;
|
||||
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_CHANNELS, &value) < 0) {
|
||||
perror("SNDCTL_DSP_CHANNELS");
|
||||
return SDL_SetError("Cannot set the number of channels");
|
||||
}
|
||||
device->spec.channels = value;
|
||||
|
||||
// Set the DSP frequency
|
||||
value = device->spec.freq;
|
||||
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SPEED, &value) < 0) {
|
||||
perror("SNDCTL_DSP_SPEED");
|
||||
return SDL_SetError("Couldn't set audio frequency");
|
||||
}
|
||||
device->spec.freq = value;
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
/* Determine the power of two of the fragment size
|
||||
Since apps don't control this in SDL3, and this driver only accepts 8, 16
|
||||
bit formats and 1, 2, 4, 8 channels, this should always be a power of 2 already. */
|
||||
SDL_assert(SDL_powerof2(device->buffer_size) == device->buffer_size);
|
||||
|
||||
int frag_spec = 0;
|
||||
while ((0x01U << frag_spec) < device->buffer_size) {
|
||||
frag_spec++;
|
||||
}
|
||||
frag_spec |= 0x00020000; // two fragments, for low latency
|
||||
|
||||
// Set the audio buffering parameters
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Requesting %d fragments of size %d\n",
|
||||
(frag_spec >> 16), 1 << (frag_spec & 0xFFFF));
|
||||
#endif
|
||||
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0) {
|
||||
perror("SNDCTL_DSP_SETFRAGMENT");
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
{
|
||||
audio_buf_info info;
|
||||
ioctl(device->hidden->audio_fd, SNDCTL_DSP_GETOSPACE, &info);
|
||||
fprintf(stderr, "fragments = %d\n", info.fragments);
|
||||
fprintf(stderr, "fragstotal = %d\n", info.fragstotal);
|
||||
fprintf(stderr, "fragsize = %d\n", info.fragsize);
|
||||
fprintf(stderr, "bytes = %d\n", info.bytes);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Allocate mixing buffer
|
||||
if (!device->iscapture) {
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return -1;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
return 0; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static int DSP_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const unsigned long ioctlreq = device->iscapture ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE;
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
|
||||
while (!SDL_AtomicGet(&device->shutdown)) {
|
||||
audio_buf_info info;
|
||||
const int rc = ioctl(h->audio_fd, ioctlreq, &info);
|
||||
if (rc < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
continue;
|
||||
}
|
||||
// Hmm, not much we can do - abort
|
||||
fprintf(stderr, "dsp WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno));
|
||||
return -1;
|
||||
} else if (info.bytes < device->buffer_size) {
|
||||
SDL_Delay(10);
|
||||
} else {
|
||||
break; // ready to go!
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DSP_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
if (write(h->audio_fd, buffer, buflen) == -1) {
|
||||
perror("Audio write");
|
||||
return -1;
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Wrote %d bytes of audio data\n", h->mixlen);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Uint8 *DSP_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int DSP_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
return (int)read(device->hidden->audio_fd, buffer, buflen);
|
||||
}
|
||||
|
||||
static void DSP_FlushCapture(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
audio_buf_info info;
|
||||
if (ioctl(h->audio_fd, SNDCTL_DSP_GETISPACE, &info) == 0) {
|
||||
while (info.bytes > 0) {
|
||||
char buf[512];
|
||||
const size_t len = SDL_min(sizeof(buf), info.bytes);
|
||||
const ssize_t br = read(h->audio_fd, buf, len);
|
||||
if (br <= 0) {
|
||||
break;
|
||||
}
|
||||
info.bytes -= br;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_bool InitTimeDevicesExist = SDL_FALSE;
|
||||
static SDL_bool look_for_devices_test(int fd)
|
||||
{
|
||||
InitTimeDevicesExist = SDL_TRUE; // note that _something_ exists.
|
||||
// Don't add to the device list, we're just seeing if any devices exist.
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
static SDL_bool DSP_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
InitTimeDevicesExist = SDL_FALSE;
|
||||
SDL_EnumUnixAudioDevices(SDL_FALSE, look_for_devices_test);
|
||||
if (!InitTimeDevicesExist) {
|
||||
SDL_SetError("dsp: No such audio device");
|
||||
return SDL_FALSE; // maybe try a different backend.
|
||||
}
|
||||
|
||||
impl->DetectDevices = DSP_DetectDevices;
|
||||
impl->OpenDevice = DSP_OpenDevice;
|
||||
impl->WaitDevice = DSP_WaitDevice;
|
||||
impl->PlayDevice = DSP_PlayDevice;
|
||||
impl->GetDeviceBuf = DSP_GetDeviceBuf;
|
||||
impl->CloseDevice = DSP_CloseDevice;
|
||||
impl->WaitCaptureDevice = DSP_WaitDevice;
|
||||
impl->CaptureFromDevice = DSP_CaptureFromDevice;
|
||||
impl->FlushCapture = DSP_FlushCapture;
|
||||
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap DSP_bootstrap = {
|
||||
"dsp", "Open Sound System (/dev/dsp)", DSP_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_OSS
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_dspaudio_h_
|
||||
#define SDL_dspaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The file descriptor for the audio device
|
||||
int audio_fd;
|
||||
|
||||
// Raw mixing buffer
|
||||
Uint8 *mixbuf;
|
||||
};
|
||||
|
||||
#endif // SDL_dspaudio_h_
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// Output audio to nowhere...
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_dummyaudio.h"
|
||||
|
||||
// !!! FIXME: this should be an SDL hint, not an environment variable.
|
||||
#define DUMMYENVR_IODELAY "SDL_DUMMYAUDIODELAY"
|
||||
|
||||
static int DUMMYAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
SDL_Delay(device->hidden->io_delay);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const char *envr = SDL_getenv(DUMMYENVR_IODELAY);
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!device->iscapture) {
|
||||
device->hidden->mixbuf = (Uint8 *) SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
device->hidden->io_delay = (Uint32) (envr ? SDL_atoi(envr) : ((device->sample_frames * 1000) / device->spec.freq));
|
||||
|
||||
return 0; // we're good; don't change reported device format.
|
||||
}
|
||||
|
||||
static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static Uint8 *DUMMYAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int DUMMYAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
// always return a full buffer of silence.
|
||||
SDL_memset(buffer, device->silence_value, buflen);
|
||||
return buflen;
|
||||
}
|
||||
|
||||
static SDL_bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = DUMMYAUDIO_OpenDevice;
|
||||
impl->CloseDevice = DUMMYAUDIO_CloseDevice;
|
||||
impl->WaitDevice = DUMMYAUDIO_WaitDevice;
|
||||
impl->GetDeviceBuf = DUMMYAUDIO_GetDeviceBuf;
|
||||
impl->WaitCaptureDevice = DUMMYAUDIO_WaitDevice;
|
||||
impl->CaptureFromDevice = DUMMYAUDIO_CaptureFromDevice;
|
||||
|
||||
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
|
||||
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap DUMMYAUDIO_bootstrap = {
|
||||
"dummy", "SDL dummy audio driver", DUMMYAUDIO_Init, SDL_TRUE
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_dummyaudio_h_
|
||||
#define SDL_dummyaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
Uint8 *mixbuf; // The file descriptor for the audio device
|
||||
Uint32 io_delay; // miliseconds to sleep in WaitDevice.
|
||||
};
|
||||
|
||||
#endif // SDL_dummyaudio_h_
|
||||
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_emscriptenaudio.h"
|
||||
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
// just turn off clang-format for this whole file, this INDENT_OFF stuff on
|
||||
// each EM_ASM section is ugly.
|
||||
/* *INDENT-OFF* */ /* clang-format off */
|
||||
|
||||
static Uint8 *EMSCRIPTENAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
const int framelen = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
var numChannels = SDL3.audio.currentOutputBuffer['numberOfChannels'];
|
||||
for (var c = 0; c < numChannels; ++c) {
|
||||
var channelData = SDL3.audio.currentOutputBuffer['getChannelData'](c);
|
||||
if (channelData.length != $1) {
|
||||
throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
|
||||
}
|
||||
|
||||
for (var j = 0; j < $1; ++j) {
|
||||
channelData[j] = HEAPF32[$0 + ((j*numChannels + c) << 2) >> 2]; // !!! FIXME: why are these shifts here?
|
||||
}
|
||||
}
|
||||
}, buffer, buffer_size / framelen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void EMSCRIPTENAUDIO_FlushCapture(SDL_AudioDevice *device)
|
||||
{
|
||||
// Do nothing, the new data will just be dropped.
|
||||
}
|
||||
|
||||
static int EMSCRIPTENAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
var numChannels = SDL3.capture.currentCaptureBuffer.numberOfChannels;
|
||||
for (var c = 0; c < numChannels; ++c) {
|
||||
var channelData = SDL3.capture.currentCaptureBuffer.getChannelData(c);
|
||||
if (channelData.length != $1) {
|
||||
throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
|
||||
}
|
||||
|
||||
if (numChannels == 1) { // fastpath this a little for the common (mono) case.
|
||||
for (var j = 0; j < $1; ++j) {
|
||||
setValue($0 + (j * 4), channelData[j], 'float');
|
||||
}
|
||||
} else {
|
||||
for (var j = 0; j < $1; ++j) {
|
||||
setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, buffer, (buflen / sizeof(float)) / device->spec.channels);
|
||||
|
||||
return buflen;
|
||||
}
|
||||
|
||||
static void EMSCRIPTENAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (!device->hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
if ($0) {
|
||||
if (SDL3.capture.silenceTimer !== undefined) {
|
||||
clearInterval(SDL3.capture.silenceTimer);
|
||||
}
|
||||
if (SDL3.capture.stream !== undefined) {
|
||||
var tracks = SDL3.capture.stream.getAudioTracks();
|
||||
for (var i = 0; i < tracks.length; i++) {
|
||||
SDL3.capture.stream.removeTrack(tracks[i]);
|
||||
}
|
||||
}
|
||||
if (SDL3.capture.scriptProcessorNode !== undefined) {
|
||||
SDL3.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {};
|
||||
SDL3.capture.scriptProcessorNode.disconnect();
|
||||
}
|
||||
if (SDL3.capture.mediaStreamNode !== undefined) {
|
||||
SDL3.capture.mediaStreamNode.disconnect();
|
||||
}
|
||||
SDL3.capture = undefined;
|
||||
} else {
|
||||
if (SDL3.audio.scriptProcessorNode != undefined) {
|
||||
SDL3.audio.scriptProcessorNode.disconnect();
|
||||
}
|
||||
if (SDL3.audio.silenceTimer !== undefined) {
|
||||
clearInterval(SDL3.audio.silenceTimer);
|
||||
}
|
||||
SDL3.audio = undefined;
|
||||
}
|
||||
if ((SDL3.audioContext !== undefined) && (SDL3.audio === undefined) && (SDL3.capture === undefined)) {
|
||||
SDL3.audioContext.close();
|
||||
SDL3.audioContext = undefined;
|
||||
}
|
||||
}, device->iscapture);
|
||||
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
|
||||
SDL_AudioThreadFinalize(device);
|
||||
}
|
||||
|
||||
EM_JS_DEPS(sdlaudio, "$autoResumeAudioContext,$dynCall");
|
||||
|
||||
static int EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// based on parts of library_sdl.js
|
||||
|
||||
// create context
|
||||
const int result = MAIN_THREAD_EM_ASM_INT({
|
||||
if (typeof(Module['SDL3']) === 'undefined') {
|
||||
Module['SDL3'] = {};
|
||||
}
|
||||
var SDL3 = Module['SDL3'];
|
||||
if (!$0) {
|
||||
SDL3.audio = {};
|
||||
} else {
|
||||
SDL3.capture = {};
|
||||
}
|
||||
|
||||
if (!SDL3.audioContext) {
|
||||
if (typeof(AudioContext) !== 'undefined') {
|
||||
SDL3.audioContext = new AudioContext();
|
||||
} else if (typeof(webkitAudioContext) !== 'undefined') {
|
||||
SDL3.audioContext = new webkitAudioContext();
|
||||
}
|
||||
if (SDL3.audioContext) {
|
||||
if ((typeof navigator.userActivation) === 'undefined') { // Firefox doesn't have this (as of August 2023), use autoResumeAudioContext instead.
|
||||
autoResumeAudioContext(SDL3.audioContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
return SDL3.audioContext === undefined ? -1 : 0;
|
||||
}, device->iscapture);
|
||||
|
||||
if (result < 0) {
|
||||
return SDL_SetError("Web Audio API is not available!");
|
||||
}
|
||||
|
||||
device->spec.format = SDL_AUDIO_F32; // web audio only supports floats
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// limit to native freq
|
||||
device->spec.freq = EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; });
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
if (!device->iscapture) {
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return -1;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
if (device->iscapture) {
|
||||
/* The idea is to take the capture media stream, hook it up to an
|
||||
audio graph where we can pass it through a ScriptProcessorNode
|
||||
to access the raw PCM samples and push them to the SDL app's
|
||||
callback. From there, we "process" the audio data into silence
|
||||
and forget about it.
|
||||
|
||||
This should, strictly speaking, use MediaRecorder for capture, but
|
||||
this API is cleaner to use and better supported, and fires a
|
||||
callback whenever there's enough data to fire down into the app.
|
||||
The downside is that we are spending CPU time silencing a buffer
|
||||
that the audiocontext uselessly mixes into any output. On the
|
||||
upside, both of those things are not only run in native code in
|
||||
the browser, they're probably SIMD code, too. MediaRecorder
|
||||
feels like it's a pretty inefficient tapdance in similar ways,
|
||||
to be honest. */
|
||||
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
var have_microphone = function(stream) {
|
||||
//console.log('SDL audio capture: we have a microphone! Replacing silence callback.');
|
||||
if (SDL3.capture.silenceTimer !== undefined) {
|
||||
clearInterval(SDL3.capture.silenceTimer);
|
||||
SDL3.capture.silenceTimer = undefined;
|
||||
SDL3.capture.silenceBuffer = undefined
|
||||
}
|
||||
SDL3.capture.mediaStreamNode = SDL3.audioContext.createMediaStreamSource(stream);
|
||||
SDL3.capture.scriptProcessorNode = SDL3.audioContext.createScriptProcessor($1, $0, 1);
|
||||
SDL3.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {
|
||||
if ((SDL3 === undefined) || (SDL3.capture === undefined)) { return; }
|
||||
audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0);
|
||||
SDL3.capture.currentCaptureBuffer = audioProcessingEvent.inputBuffer;
|
||||
dynCall('vi', $2, [$3]);
|
||||
};
|
||||
SDL3.capture.mediaStreamNode.connect(SDL3.capture.scriptProcessorNode);
|
||||
SDL3.capture.scriptProcessorNode.connect(SDL3.audioContext.destination);
|
||||
SDL3.capture.stream = stream;
|
||||
};
|
||||
|
||||
var no_microphone = function(error) {
|
||||
//console.log('SDL audio capture: we DO NOT have a microphone! (' + error.name + ')...leaving silence callback running.');
|
||||
};
|
||||
|
||||
// we write silence to the audio callback until the microphone is available (user approves use, etc).
|
||||
SDL3.capture.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
|
||||
SDL3.capture.silenceBuffer.getChannelData(0).fill(0.0);
|
||||
var silence_callback = function() {
|
||||
SDL3.capture.currentCaptureBuffer = SDL3.capture.silenceBuffer;
|
||||
dynCall('vi', $2, [$3]);
|
||||
};
|
||||
|
||||
SDL3.capture.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
|
||||
|
||||
if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) {
|
||||
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone);
|
||||
} else if (navigator.webkitGetUserMedia !== undefined) {
|
||||
navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone);
|
||||
}
|
||||
}, device->spec.channels, device->sample_frames, SDL_CaptureAudioThreadIterate, device);
|
||||
} else {
|
||||
// setup a ScriptProcessorNode
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
SDL3.audio.scriptProcessorNode = SDL3.audioContext['createScriptProcessor']($1, 0, $0);
|
||||
SDL3.audio.scriptProcessorNode['onaudioprocess'] = function (e) {
|
||||
if ((SDL3 === undefined) || (SDL3.audio === undefined)) { return; }
|
||||
// if we're actually running the node, we don't need the fake callback anymore, so kill it.
|
||||
if (SDL3.audio.silenceTimer !== undefined) {
|
||||
clearInterval(SDL3.audio.silenceTimer);
|
||||
SDL3.audio.silenceTimer = undefined;
|
||||
SDL3.audio.silenceBuffer = undefined;
|
||||
}
|
||||
SDL3.audio.currentOutputBuffer = e['outputBuffer'];
|
||||
dynCall('vi', $2, [$3]);
|
||||
};
|
||||
|
||||
SDL3.audio.scriptProcessorNode['connect'](SDL3.audioContext['destination']);
|
||||
|
||||
if (SDL3.audioContext.state === 'suspended') { // uhoh, autoplay is blocked.
|
||||
SDL3.audio.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
|
||||
SDL3.audio.silenceBuffer.getChannelData(0).fill(0.0);
|
||||
var silence_callback = function() {
|
||||
if ((typeof navigator.userActivation) !== 'undefined') { // Almost everything modern except Firefox (as of August 2023)
|
||||
if (navigator.userActivation.hasBeenActive) {
|
||||
SDL3.audioContext.resume();
|
||||
}
|
||||
}
|
||||
|
||||
// the buffer that gets filled here just gets ignored, so the app can make progress
|
||||
// and/or avoid flooding audio queues until we can actually play audio.
|
||||
SDL3.audio.currentOutputBuffer = SDL3.audio.silenceBuffer;
|
||||
dynCall('vi', $2, [$3]);
|
||||
SDL3.audio.currentOutputBuffer = undefined;
|
||||
};
|
||||
|
||||
SDL3.audio.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
|
||||
}
|
||||
}, device->spec.channels, device->sample_frames, SDL_OutputAudioThreadIterate, device);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SDL_bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
SDL_bool available, capture_available;
|
||||
|
||||
impl->OpenDevice = EMSCRIPTENAUDIO_OpenDevice;
|
||||
impl->CloseDevice = EMSCRIPTENAUDIO_CloseDevice;
|
||||
impl->GetDeviceBuf = EMSCRIPTENAUDIO_GetDeviceBuf;
|
||||
impl->PlayDevice = EMSCRIPTENAUDIO_PlayDevice;
|
||||
impl->FlushCapture = EMSCRIPTENAUDIO_FlushCapture;
|
||||
impl->CaptureFromDevice = EMSCRIPTENAUDIO_CaptureFromDevice;
|
||||
|
||||
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
|
||||
|
||||
// technically, this is just runs in idle time in the main thread, but it's close enough to a "thread" for our purposes.
|
||||
impl->ProvidesOwnCallbackThread = SDL_TRUE;
|
||||
|
||||
// check availability
|
||||
available = MAIN_THREAD_EM_ASM_INT({
|
||||
if (typeof(AudioContext) !== 'undefined') {
|
||||
return true;
|
||||
} else if (typeof(webkitAudioContext) !== 'undefined') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!available) {
|
||||
SDL_SetError("No audio context available");
|
||||
}
|
||||
|
||||
capture_available = available && MAIN_THREAD_EM_ASM_INT({
|
||||
if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) {
|
||||
return true;
|
||||
} else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
impl->HasCaptureSupport = capture_available;
|
||||
impl->OnlyHasDefaultCaptureDevice = capture_available;
|
||||
|
||||
return available;
|
||||
}
|
||||
|
||||
AudioBootStrap EMSCRIPTENAUDIO_bootstrap = {
|
||||
"emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
/* *INDENT-ON* */ /* clang-format on */
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_EMSCRIPTEN
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_emscriptenaudio_h_
|
||||
#define SDL_emscriptenaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
Uint8 *mixbuf;
|
||||
};
|
||||
|
||||
#endif // SDL_emscriptenaudio_h_
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_HAIKU
|
||||
|
||||
// Allow access to the audio stream on Haiku
|
||||
|
||||
#include <SoundPlayer.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "../../core/haiku/SDL_BeApp.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_haikuaudio.h"
|
||||
|
||||
}
|
||||
|
||||
static Uint8 *HAIKUAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
SDL_assert(device->hidden->current_buffer != NULL);
|
||||
SDL_assert(device->hidden->current_buffer_len > 0);
|
||||
*buffer_size = device->hidden->current_buffer_len;
|
||||
return device->hidden->current_buffer;
|
||||
}
|
||||
|
||||
static int HAIKUAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
// We already wrote our output right into the BSoundPlayer's callback's stream. Just clean up our stuff.
|
||||
SDL_assert(device->hidden->current_buffer != NULL);
|
||||
SDL_assert(device->hidden->current_buffer_len > 0);
|
||||
device->hidden->current_buffer = NULL;
|
||||
device->hidden->current_buffer_len = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The Haiku callback for handling the audio buffer
|
||||
static void FillSound(void *data, void *stream, size_t len, const media_raw_audio_format & format)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *)data;
|
||||
SDL_assert(device->hidden->current_buffer == NULL);
|
||||
SDL_assert(device->hidden->current_buffer_len == 0);
|
||||
device->hidden->current_buffer = (Uint8 *) stream;
|
||||
device->hidden->current_buffer_len = (int) len;
|
||||
SDL_OutputAudioThreadIterate(device);
|
||||
}
|
||||
|
||||
static void HAIKUAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->audio_obj) {
|
||||
device->hidden->audio_obj->Stop();
|
||||
delete device->hidden->audio_obj;
|
||||
}
|
||||
delete device->hidden;
|
||||
device->hidden = NULL;
|
||||
SDL_AudioThreadFinalize(device);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const int sig_list[] = {
|
||||
SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGALRM, SIGTERM, SIGWINCH, 0
|
||||
};
|
||||
|
||||
static inline void MaskSignals(sigset_t * omask)
|
||||
{
|
||||
sigset_t mask;
|
||||
int i;
|
||||
|
||||
sigemptyset(&mask);
|
||||
for (i = 0; sig_list[i]; ++i) {
|
||||
sigaddset(&mask, sig_list[i]);
|
||||
}
|
||||
sigprocmask(SIG_BLOCK, &mask, omask);
|
||||
}
|
||||
|
||||
static inline void UnmaskSignals(sigset_t * omask)
|
||||
{
|
||||
sigprocmask(SIG_SETMASK, omask, NULL);
|
||||
}
|
||||
|
||||
|
||||
static int HAIKUAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = new SDL_PrivateAudioData;
|
||||
if (device->hidden == NULL) {
|
||||
return SDL_OutOfMemory();
|
||||
}
|
||||
SDL_zerop(device->hidden);
|
||||
|
||||
// Parse the audio format and fill the Be raw audio format
|
||||
media_raw_audio_format format;
|
||||
SDL_zero(format);
|
||||
format.byte_order = B_MEDIA_LITTLE_ENDIAN;
|
||||
format.frame_rate = (float) device->spec.freq;
|
||||
format.channel_count = device->spec.channels; // !!! FIXME: support > 2?
|
||||
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_S8:
|
||||
format.format = media_raw_audio_format::B_AUDIO_CHAR;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_U8:
|
||||
format.format = media_raw_audio_format::B_AUDIO_UCHAR;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16LE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_SHORT;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16BE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_SHORT;
|
||||
format.byte_order = B_MEDIA_BIG_ENDIAN;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32LE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_INT;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32BE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_INT;
|
||||
format.byte_order = B_MEDIA_BIG_ENDIAN;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_F32LE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_FLOAT;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_F32BE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_FLOAT;
|
||||
format.byte_order = B_MEDIA_BIG_ENDIAN;
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!test_format) { // shouldn't happen, but just in case...
|
||||
return SDL_SetError("HAIKU: Unsupported audio format");
|
||||
}
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
format.buffer_size = device->buffer_size;
|
||||
|
||||
// Subscribe to the audio stream (creates a new thread)
|
||||
sigset_t omask;
|
||||
MaskSignals(&omask);
|
||||
device->hidden->audio_obj = new BSoundPlayer(&format, "SDL Audio",
|
||||
FillSound, NULL, device);
|
||||
UnmaskSignals(&omask);
|
||||
|
||||
if (device->hidden->audio_obj->Start() == B_NO_ERROR) {
|
||||
device->hidden->audio_obj->SetHasData(true);
|
||||
} else {
|
||||
return SDL_SetError("Unable to start Haiku audio");
|
||||
}
|
||||
|
||||
return 0; // We're running!
|
||||
}
|
||||
|
||||
static void HAIKUAUDIO_Deinitialize(void)
|
||||
{
|
||||
SDL_QuitBeApp();
|
||||
}
|
||||
|
||||
static SDL_bool HAIKUAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (SDL_InitBeApp() < 0) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
// Set the function pointers
|
||||
impl->OpenDevice = HAIKUAUDIO_OpenDevice;
|
||||
impl->GetDeviceBuf = HAIKUAUDIO_GetDeviceBuf;
|
||||
impl->PlayDevice = HAIKUAUDIO_PlayDevice;
|
||||
impl->CloseDevice = HAIKUAUDIO_CloseDevice;
|
||||
impl->Deinitialize = HAIKUAUDIO_Deinitialize;
|
||||
impl->ProvidesOwnCallbackThread = SDL_TRUE;
|
||||
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
|
||||
extern "C" { extern AudioBootStrap HAIKUAUDIO_bootstrap; }
|
||||
|
||||
AudioBootStrap HAIKUAUDIO_bootstrap = {
|
||||
"haiku", "Haiku BSoundPlayer", HAIKUAUDIO_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_HAIKU
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_haikuaudio_h_
|
||||
#define SDL_haikuaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
BSoundPlayer *audio_obj;
|
||||
Uint8 *current_buffer;
|
||||
int current_buffer_len;
|
||||
};
|
||||
|
||||
#endif // SDL_haikuaudio_h_
|
||||
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_JACK
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_jackaudio.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
|
||||
static jack_client_t *(*JACK_jack_client_open)(const char *, jack_options_t, jack_status_t *, ...);
|
||||
static int (*JACK_jack_client_close)(jack_client_t *);
|
||||
static void (*JACK_jack_on_shutdown)(jack_client_t *, JackShutdownCallback, void *);
|
||||
static int (*JACK_jack_activate)(jack_client_t *);
|
||||
static int (*JACK_jack_deactivate)(jack_client_t *);
|
||||
static void *(*JACK_jack_port_get_buffer)(jack_port_t *, jack_nframes_t);
|
||||
static int (*JACK_jack_port_unregister)(jack_client_t *, jack_port_t *);
|
||||
static void (*JACK_jack_free)(void *);
|
||||
static const char **(*JACK_jack_get_ports)(jack_client_t *, const char *, const char *, unsigned long);
|
||||
static jack_nframes_t (*JACK_jack_get_sample_rate)(jack_client_t *);
|
||||
static jack_nframes_t (*JACK_jack_get_buffer_size)(jack_client_t *);
|
||||
static jack_port_t *(*JACK_jack_port_register)(jack_client_t *, const char *, const char *, unsigned long, unsigned long);
|
||||
static jack_port_t *(*JACK_jack_port_by_name)(jack_client_t *, const char *);
|
||||
static const char *(*JACK_jack_port_name)(const jack_port_t *);
|
||||
static const char *(*JACK_jack_port_type)(const jack_port_t *);
|
||||
static int (*JACK_jack_connect)(jack_client_t *, const char *, const char *);
|
||||
static int (*JACK_jack_set_process_callback)(jack_client_t *, JackProcessCallback, void *);
|
||||
static int (*JACK_jack_set_sample_rate_callback)(jack_client_t *, JackSampleRateCallback, void *);
|
||||
static int (*JACK_jack_set_buffer_size_callback)(jack_client_t *, JackBufferSizeCallback, void *);
|
||||
|
||||
static int load_jack_syms(void);
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC
|
||||
|
||||
static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC;
|
||||
static void *jack_handle = NULL;
|
||||
|
||||
// !!! FIXME: this is copy/pasted in several places now
|
||||
static int load_jack_sym(const char *fn, void **addr)
|
||||
{
|
||||
*addr = SDL_LoadFunction(jack_handle, fn);
|
||||
if (!*addr) {
|
||||
// Don't call SDL_SetError(): SDL_LoadFunction already did.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// cast funcs to char* first, to please GCC's strict aliasing rules.
|
||||
#define SDL_JACK_SYM(x) \
|
||||
if (!load_jack_sym(#x, (void **)(char *)&JACK_##x)) \
|
||||
return -1
|
||||
|
||||
static void UnloadJackLibrary(void)
|
||||
{
|
||||
if (jack_handle) {
|
||||
SDL_UnloadObject(jack_handle);
|
||||
jack_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int LoadJackLibrary(void)
|
||||
{
|
||||
int retval = 0;
|
||||
if (!jack_handle) {
|
||||
jack_handle = SDL_LoadObject(jack_library);
|
||||
if (!jack_handle) {
|
||||
retval = -1;
|
||||
// Don't call SDL_SetError(): SDL_LoadObject already did.
|
||||
} else {
|
||||
retval = load_jack_syms();
|
||||
if (retval < 0) {
|
||||
UnloadJackLibrary();
|
||||
}
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define SDL_JACK_SYM(x) JACK_##x = x
|
||||
|
||||
static void UnloadJackLibrary(void)
|
||||
{
|
||||
}
|
||||
|
||||
static int LoadJackLibrary(void)
|
||||
{
|
||||
load_jack_syms();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_JACK_DYNAMIC
|
||||
|
||||
static int load_jack_syms(void)
|
||||
{
|
||||
SDL_JACK_SYM(jack_client_open);
|
||||
SDL_JACK_SYM(jack_client_close);
|
||||
SDL_JACK_SYM(jack_on_shutdown);
|
||||
SDL_JACK_SYM(jack_activate);
|
||||
SDL_JACK_SYM(jack_deactivate);
|
||||
SDL_JACK_SYM(jack_port_get_buffer);
|
||||
SDL_JACK_SYM(jack_port_unregister);
|
||||
SDL_JACK_SYM(jack_free);
|
||||
SDL_JACK_SYM(jack_get_ports);
|
||||
SDL_JACK_SYM(jack_get_sample_rate);
|
||||
SDL_JACK_SYM(jack_get_buffer_size);
|
||||
SDL_JACK_SYM(jack_port_register);
|
||||
SDL_JACK_SYM(jack_port_by_name);
|
||||
SDL_JACK_SYM(jack_port_name);
|
||||
SDL_JACK_SYM(jack_port_type);
|
||||
SDL_JACK_SYM(jack_connect);
|
||||
SDL_JACK_SYM(jack_set_process_callback);
|
||||
SDL_JACK_SYM(jack_set_sample_rate_callback);
|
||||
SDL_JACK_SYM(jack_set_buffer_size_callback);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void jackShutdownCallback(void *arg) // JACK went away; device is lost.
|
||||
{
|
||||
SDL_AudioDeviceDisconnected((SDL_AudioDevice *)arg);
|
||||
}
|
||||
|
||||
static int jackSampleRateCallback(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
//SDL_Log("JACK Sample Rate Callback! %d", (int) nframes);
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) arg;
|
||||
SDL_AudioSpec newspec;
|
||||
SDL_copyp(&newspec, &device->spec);
|
||||
newspec.freq = (int) nframes;
|
||||
if (SDL_AudioDeviceFormatChanged(device, &newspec, device->sample_frames) < 0) {
|
||||
SDL_AudioDeviceDisconnected(device);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jackBufferSizeCallback(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
//SDL_Log("JACK Buffer Size Callback! %d", (int) nframes);
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) arg;
|
||||
SDL_AudioSpec newspec;
|
||||
SDL_copyp(&newspec, &device->spec);
|
||||
if (SDL_AudioDeviceFormatChanged(device, &newspec, (int) nframes) < 0) {
|
||||
SDL_AudioDeviceDisconnected(device);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
|
||||
SDL_OutputAudioThreadIterate((SDL_AudioDevice *)arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int JACK_PlayDevice(SDL_AudioDevice *device, const Uint8 *ui8buffer, int buflen)
|
||||
{
|
||||
const float *buffer = (float *) ui8buffer;
|
||||
jack_port_t **ports = device->hidden->sdlports;
|
||||
const int total_channels = device->spec.channels;
|
||||
const int total_frames = device->sample_frames;
|
||||
const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames;
|
||||
|
||||
for (int channelsi = 0; channelsi < total_channels; channelsi++) {
|
||||
float *dst = (float *)JACK_jack_port_get_buffer(ports[channelsi], nframes);
|
||||
if (dst) {
|
||||
const float *src = buffer + channelsi;
|
||||
for (int framesi = 0; framesi < total_frames; framesi++) {
|
||||
*(dst++) = *src;
|
||||
src += total_channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return (Uint8 *)device->hidden->iobuffer;
|
||||
}
|
||||
|
||||
static int jackProcessCaptureCallback(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
|
||||
SDL_CaptureAudioThreadIterate((SDL_AudioDevice *)arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int JACK_CaptureFromDevice(SDL_AudioDevice *device, void *vbuffer, int buflen)
|
||||
{
|
||||
float *buffer = (float *) vbuffer;
|
||||
jack_port_t **ports = device->hidden->sdlports;
|
||||
const int total_channels = device->spec.channels;
|
||||
const int total_frames = device->sample_frames;
|
||||
const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames;
|
||||
|
||||
for (int channelsi = 0; channelsi < total_channels; channelsi++) {
|
||||
const float *src = (const float *)JACK_jack_port_get_buffer(ports[channelsi], nframes);
|
||||
if (src) {
|
||||
float *dst = buffer + channelsi;
|
||||
for (int framesi = 0; framesi < total_frames; framesi++) {
|
||||
*dst = *(src++);
|
||||
dst += total_channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buflen;
|
||||
}
|
||||
|
||||
static void JACK_FlushCapture(SDL_AudioDevice *device)
|
||||
{
|
||||
// do nothing, the data will just be replaced next callback.
|
||||
}
|
||||
|
||||
static void JACK_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->client) {
|
||||
JACK_jack_deactivate(device->hidden->client);
|
||||
|
||||
if (device->hidden->sdlports) {
|
||||
const int channels = device->spec.channels;
|
||||
int i;
|
||||
for (i = 0; i < channels; i++) {
|
||||
JACK_jack_port_unregister(device->hidden->client, device->hidden->sdlports[i]);
|
||||
}
|
||||
SDL_free(device->hidden->sdlports);
|
||||
}
|
||||
|
||||
JACK_jack_client_close(device->hidden->client);
|
||||
}
|
||||
|
||||
SDL_free(device->hidden->iobuffer);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
|
||||
SDL_AudioThreadFinalize(device);
|
||||
}
|
||||
}
|
||||
|
||||
// !!! FIXME: unify this (PulseAudio has a getAppName, Pipewire has a thing, etc
|
||||
static const char *GetJackAppName(void)
|
||||
{
|
||||
const char *retval = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME);
|
||||
if (retval && *retval) {
|
||||
return retval;
|
||||
}
|
||||
retval = SDL_GetHint(SDL_HINT_APP_NAME);
|
||||
if (retval && *retval) {
|
||||
return retval;
|
||||
}
|
||||
return "SDL Application";
|
||||
}
|
||||
|
||||
static int JACK_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
/* Note that JACK uses "output" for capture devices (they output audio
|
||||
data to us) and "input" for playback (we input audio data to them).
|
||||
Likewise, SDL's playback port will be "output" (we write data out)
|
||||
and capture will be "input" (we read data in). */
|
||||
SDL_bool iscapture = device->iscapture;
|
||||
const unsigned long sysportflags = iscapture ? JackPortIsOutput : JackPortIsInput;
|
||||
const unsigned long sdlportflags = iscapture ? JackPortIsInput : JackPortIsOutput;
|
||||
const JackProcessCallback callback = iscapture ? jackProcessCaptureCallback : jackProcessPlaybackCallback;
|
||||
const char *sdlportstr = iscapture ? "input" : "output";
|
||||
const char **devports = NULL;
|
||||
int *audio_ports;
|
||||
jack_client_t *client = NULL;
|
||||
jack_status_t status;
|
||||
int channels = 0;
|
||||
int ports = 0;
|
||||
int i;
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
client = JACK_jack_client_open(GetJackAppName(), JackNoStartServer, &status, NULL);
|
||||
device->hidden->client = client;
|
||||
if (!client) {
|
||||
return SDL_SetError("Can't open JACK client");
|
||||
}
|
||||
|
||||
devports = JACK_jack_get_ports(client, NULL, NULL, JackPortIsPhysical | sysportflags);
|
||||
if (!devports || !devports[0]) {
|
||||
return SDL_SetError("No physical JACK ports available");
|
||||
}
|
||||
|
||||
while (devports[++ports]) {
|
||||
// spin to count devports
|
||||
}
|
||||
|
||||
// Filter out non-audio ports
|
||||
audio_ports = SDL_calloc(ports, sizeof(*audio_ports));
|
||||
for (i = 0; i < ports; i++) {
|
||||
const jack_port_t *dport = JACK_jack_port_by_name(client, devports[i]);
|
||||
const char *type = JACK_jack_port_type(dport);
|
||||
const int len = SDL_strlen(type);
|
||||
// See if type ends with "audio"
|
||||
if (len >= 5 && !SDL_memcmp(type + len - 5, "audio", 5)) {
|
||||
audio_ports[channels++] = i;
|
||||
}
|
||||
}
|
||||
if (channels == 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("No physical JACK ports available");
|
||||
}
|
||||
|
||||
// Jack pretty much demands what it wants.
|
||||
device->spec.format = SDL_AUDIO_F32;
|
||||
device->spec.freq = JACK_jack_get_sample_rate(client);
|
||||
device->spec.channels = channels;
|
||||
device->sample_frames = JACK_jack_get_buffer_size(client);
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
if (!device->iscapture) {
|
||||
device->hidden->iobuffer = (float *)SDL_calloc(1, device->buffer_size);
|
||||
if (!device->hidden->iobuffer) {
|
||||
SDL_free(audio_ports);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Build SDL's ports, which we will connect to the device ports.
|
||||
device->hidden->sdlports = (jack_port_t **)SDL_calloc(channels, sizeof(jack_port_t *));
|
||||
if (!device->hidden->sdlports) {
|
||||
SDL_free(audio_ports);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < channels; i++) {
|
||||
char portname[32];
|
||||
(void)SDL_snprintf(portname, sizeof(portname), "sdl_jack_%s_%d", sdlportstr, i);
|
||||
device->hidden->sdlports[i] = JACK_jack_port_register(client, portname, JACK_DEFAULT_AUDIO_TYPE, sdlportflags, 0);
|
||||
if (device->hidden->sdlports[i] == NULL) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("jack_port_register failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (JACK_jack_set_buffer_size_callback(client, jackBufferSizeCallback, device) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("JACK: Couldn't set buffer size callback");
|
||||
} else if (JACK_jack_set_sample_rate_callback(client, jackSampleRateCallback, device) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("JACK: Couldn't set sample rate callback");
|
||||
} else if (JACK_jack_set_process_callback(client, callback, device) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("JACK: Couldn't set process callback");
|
||||
}
|
||||
|
||||
JACK_jack_on_shutdown(client, jackShutdownCallback, device);
|
||||
|
||||
if (JACK_jack_activate(client) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("Failed to activate JACK client");
|
||||
}
|
||||
|
||||
// once activated, we can connect all the ports.
|
||||
for (i = 0; i < channels; i++) {
|
||||
const char *sdlport = JACK_jack_port_name(device->hidden->sdlports[i]);
|
||||
const char *srcport = iscapture ? devports[audio_ports[i]] : sdlport;
|
||||
const char *dstport = iscapture ? sdlport : devports[audio_ports[i]];
|
||||
if (JACK_jack_connect(client, srcport, dstport) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("Couldn't connect JACK ports: %s => %s", srcport, dstport);
|
||||
}
|
||||
}
|
||||
|
||||
// don't need these anymore.
|
||||
JACK_jack_free(devports);
|
||||
SDL_free(audio_ports);
|
||||
|
||||
// We're ready to rock and roll. :-)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void JACK_Deinitialize(void)
|
||||
{
|
||||
UnloadJackLibrary();
|
||||
}
|
||||
|
||||
static SDL_bool JACK_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (LoadJackLibrary() < 0) {
|
||||
return SDL_FALSE;
|
||||
} else {
|
||||
// Make sure a JACK server is running and available.
|
||||
jack_status_t status;
|
||||
jack_client_t *client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL);
|
||||
if (!client) {
|
||||
UnloadJackLibrary();
|
||||
SDL_SetError("Can't open JACK client");
|
||||
return SDL_FALSE;
|
||||
}
|
||||
JACK_jack_client_close(client);
|
||||
}
|
||||
|
||||
impl->OpenDevice = JACK_OpenDevice;
|
||||
impl->GetDeviceBuf = JACK_GetDeviceBuf;
|
||||
impl->PlayDevice = JACK_PlayDevice;
|
||||
impl->CloseDevice = JACK_CloseDevice;
|
||||
impl->Deinitialize = JACK_Deinitialize;
|
||||
impl->CaptureFromDevice = JACK_CaptureFromDevice;
|
||||
impl->FlushCapture = JACK_FlushCapture;
|
||||
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
|
||||
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
impl->ProvidesOwnCallbackThread = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap JACK_bootstrap = {
|
||||
"jack", "JACK Audio Connection Kit", JACK_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_JACK
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#ifndef SDL_jackaudio_h_
|
||||
#define SDL_jackaudio_h_
|
||||
|
||||
#include <jack/jack.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
jack_client_t *client;
|
||||
jack_port_t **sdlports;
|
||||
float *iobuffer;
|
||||
};
|
||||
|
||||
#endif // SDL_jackaudio_h_
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_N3DS
|
||||
|
||||
// N3DS Audio driver
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_n3dsaudio.h"
|
||||
|
||||
#define N3DSAUDIO_DRIVER_NAME "n3ds"
|
||||
|
||||
static dspHookCookie dsp_hook;
|
||||
static SDL_AudioDevice *audio_device;
|
||||
|
||||
// fully local functions related to the wavebufs / DSP, not the same as the `device->lock` SDL_Mutex!
|
||||
static SDL_INLINE void contextLock(SDL_AudioDevice *device)
|
||||
{
|
||||
LightLock_Lock(&device->hidden->lock);
|
||||
}
|
||||
|
||||
static SDL_INLINE void contextUnlock(SDL_AudioDevice *device)
|
||||
{
|
||||
LightLock_Unlock(&device->hidden->lock);
|
||||
}
|
||||
|
||||
static void N3DSAUD_DspHook(DSP_HookType hook)
|
||||
{
|
||||
if (hook == DSPHOOK_ONCANCEL) {
|
||||
contextLock(audio_device);
|
||||
audio_device->hidden->isCancelled = SDL_TRUE;
|
||||
SDL_AudioDeviceDisconnected(audio_device);
|
||||
CondVar_Broadcast(&audio_device->hidden->cv);
|
||||
contextUnlock(audio_device);
|
||||
}
|
||||
}
|
||||
|
||||
static void AudioFrameFinished(void *vdevice)
|
||||
{
|
||||
bool shouldBroadcast = false;
|
||||
unsigned i;
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *)vdevice;
|
||||
|
||||
contextLock(device);
|
||||
|
||||
for (i = 0; i < NUM_BUFFERS; i++) {
|
||||
if (device->hidden->waveBuf[i].status == NDSP_WBUF_DONE) {
|
||||
device->hidden->waveBuf[i].status = NDSP_WBUF_FREE;
|
||||
shouldBroadcast = SDL_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldBroadcast) {
|
||||
CondVar_Broadcast(&device->hidden->cv);
|
||||
}
|
||||
|
||||
contextUnlock(device);
|
||||
}
|
||||
|
||||
static int N3DSAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
Result ndsp_init_res;
|
||||
Uint8 *data_vaddr;
|
||||
float mix[12];
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Initialise the DSP service
|
||||
ndsp_init_res = ndspInit();
|
||||
if (R_FAILED(ndsp_init_res)) {
|
||||
if ((R_SUMMARY(ndsp_init_res) == RS_NOTFOUND) && (R_MODULE(ndsp_init_res) == RM_DSP)) {
|
||||
SDL_SetError("DSP init failed: dspfirm.cdc missing!");
|
||||
} else {
|
||||
SDL_SetError("DSP init failed. Error code: 0x%lX", ndsp_init_res);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Initialise internal state
|
||||
LightLock_Init(&device->hidden->lock);
|
||||
CondVar_Init(&device->hidden->cv);
|
||||
|
||||
if (device->spec.channels > 2) {
|
||||
device->spec.channels = 2;
|
||||
}
|
||||
|
||||
Uint32 format = 0;
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (test_format == SDL_AUDIO_S8) { // Signed 8-bit audio supported
|
||||
format = (device->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM8 : NDSP_FORMAT_MONO_PCM8;
|
||||
break;
|
||||
} else if (test_format == SDL_AUDIO_S16) { // Signed 16-bit audio supported
|
||||
format = (device->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM16 : NDSP_FORMAT_MONO_PCM16;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) { // shouldn't happen, but just in case...
|
||||
return SDL_SetError("No supported audio format found.");
|
||||
}
|
||||
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
// Allocate mixing buffer
|
||||
if (device->buffer_size >= SDL_MAX_UINT32 / 2) {
|
||||
return SDL_SetError("Mixing buffer is too large.");
|
||||
}
|
||||
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
|
||||
data_vaddr = (Uint8 *)linearAlloc(device->buffer_size * NUM_BUFFERS);
|
||||
if (!data_vaddr) {
|
||||
return SDL_OutOfMemory();
|
||||
}
|
||||
|
||||
SDL_memset(data_vaddr, 0, device->buffer_size * NUM_BUFFERS);
|
||||
DSP_FlushDataCache(data_vaddr, device->buffer_size * NUM_BUFFERS);
|
||||
|
||||
device->hidden->nextbuf = 0;
|
||||
|
||||
ndspChnReset(0);
|
||||
|
||||
ndspChnSetInterp(0, NDSP_INTERP_LINEAR);
|
||||
ndspChnSetRate(0, device->spec.freq);
|
||||
ndspChnSetFormat(0, format);
|
||||
|
||||
SDL_zeroa(mix);
|
||||
mix[0] = mix[1] = 1.0f;
|
||||
ndspChnSetMix(0, mix);
|
||||
|
||||
SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
|
||||
|
||||
const int sample_frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
for (unsigned i = 0; i < NUM_BUFFERS; i++) {
|
||||
device->hidden->waveBuf[i].data_vaddr = data_vaddr;
|
||||
device->hidden->waveBuf[i].nsamples = device->buffer_size / sample_frame_size;
|
||||
data_vaddr += device->buffer_size;
|
||||
}
|
||||
|
||||
// Setup callback
|
||||
audio_device = device;
|
||||
ndspSetCallback(AudioFrameFinished, device);
|
||||
dspHook(&dsp_hook, N3DSAUD_DspHook);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int N3DSAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
contextLock(device);
|
||||
|
||||
const size_t nextbuf = device->hidden->nextbuf;
|
||||
|
||||
if (device->hidden->isCancelled ||
|
||||
device->hidden->waveBuf[nextbuf].status != NDSP_WBUF_FREE) {
|
||||
contextUnlock(device);
|
||||
return 0; // !!! FIXME: is this a fatal error? If so, this should return -1.
|
||||
}
|
||||
|
||||
device->hidden->nextbuf = (nextbuf + 1) % NUM_BUFFERS;
|
||||
|
||||
contextUnlock(device);
|
||||
|
||||
SDL_memcpy((void *)device->hidden->waveBuf[nextbuf].data_vaddr, buffer, buflen);
|
||||
DSP_FlushDataCache(device->hidden->waveBuf[nextbuf].data_vaddr, buflen);
|
||||
|
||||
ndspChnWaveBufAdd(0, &device->hidden->waveBuf[nextbuf]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int N3DSAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
contextLock(device);
|
||||
while (!device->hidden->isCancelled && !SDL_AtomicGet(&device->shutdown) &&
|
||||
device->hidden->waveBuf[device->hidden->nextbuf].status != NDSP_WBUF_FREE) {
|
||||
CondVar_Wait(&device->hidden->cv, &device->hidden->lock);
|
||||
}
|
||||
contextUnlock(device);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Uint8 *N3DSAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static void N3DSAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (!device->hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
contextLock(device);
|
||||
|
||||
dspUnhook(&dsp_hook);
|
||||
ndspSetCallback(NULL, NULL);
|
||||
|
||||
if (!device->hidden->isCancelled) {
|
||||
ndspChnReset(0);
|
||||
SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
|
||||
CondVar_Broadcast(&device->hidden->cv);
|
||||
}
|
||||
|
||||
contextUnlock(device);
|
||||
|
||||
ndspExit();
|
||||
|
||||
if (device->hidden->waveBuf[0].data_vaddr) {
|
||||
linearFree((void *)device->hidden->waveBuf[0].data_vaddr);
|
||||
}
|
||||
|
||||
if (device->hidden->mixbuf) {
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
device->hidden->mixbuf = NULL;
|
||||
}
|
||||
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
|
||||
static void N3DSAUDIO_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
s32 current_priority = 0x30;
|
||||
svcGetThreadPriority(¤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 SDL_bool N3DSAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = N3DSAUDIO_OpenDevice;
|
||||
impl->PlayDevice = N3DSAUDIO_PlayDevice;
|
||||
impl->WaitDevice = N3DSAUDIO_WaitDevice;
|
||||
impl->GetDeviceBuf = N3DSAUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = N3DSAUDIO_CloseDevice;
|
||||
impl->ThreadInit = N3DSAUDIO_ThreadInit;
|
||||
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
|
||||
|
||||
// Should be possible, but micInit would fail
|
||||
impl->HasCaptureSupport = SDL_FALSE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap N3DSAUDIO_bootstrap = {
|
||||
N3DSAUDIO_DRIVER_NAME,
|
||||
"SDL N3DS audio driver",
|
||||
N3DSAUDIO_Init,
|
||||
0
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_N3DS
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_n3dsaudio_h
|
||||
#define SDL_n3dsaudio_h
|
||||
|
||||
#include <3ds.h>
|
||||
|
||||
#define NUM_BUFFERS 3 // -- Minimum 2!
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// Speaker data
|
||||
Uint8 *mixbuf;
|
||||
Uint32 nextbuf;
|
||||
ndspWaveBuf waveBuf[NUM_BUFFERS];
|
||||
LightLock lock;
|
||||
CondVar cv;
|
||||
SDL_bool isCancelled;
|
||||
};
|
||||
|
||||
#endif // SDL_n3dsaudio_h
|
||||
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_NETBSD
|
||||
|
||||
// Driver for native NetBSD audio(4).
|
||||
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/audioio.h>
|
||||
|
||||
#include "../../core/unix/SDL_poll.h"
|
||||
#include "../SDL_audiodev_c.h"
|
||||
#include "SDL_netbsdaudio.h"
|
||||
|
||||
//#define DEBUG_AUDIO
|
||||
|
||||
static void NETBSDAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
|
||||
{
|
||||
SDL_EnumUnixAudioDevices(SDL_FALSE, NULL);
|
||||
}
|
||||
|
||||
static void NETBSDAUDIO_Status(SDL_AudioDevice *device)
|
||||
{
|
||||
#ifdef DEBUG_AUDIO
|
||||
/* *INDENT-OFF* */ /* clang-format off */
|
||||
audio_info_t info;
|
||||
const struct audio_prinfo *prinfo;
|
||||
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
|
||||
fprintf(stderr, "AUDIO_GETINFO failed.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
prinfo = device->iscapture ? &info.record : &info.play;
|
||||
|
||||
fprintf(stderr, "\n"
|
||||
"[%s info]\n"
|
||||
"buffer size : %d bytes\n"
|
||||
"sample rate : %i Hz\n"
|
||||
"channels : %i\n"
|
||||
"precision : %i-bit\n"
|
||||
"encoding : 0x%x\n"
|
||||
"seek : %i\n"
|
||||
"sample count : %i\n"
|
||||
"EOF count : %i\n"
|
||||
"paused : %s\n"
|
||||
"error occurred : %s\n"
|
||||
"waiting : %s\n"
|
||||
"active : %s\n"
|
||||
"",
|
||||
device->iscapture ? "record" : "play",
|
||||
prinfo->buffer_size,
|
||||
prinfo->sample_rate,
|
||||
prinfo->channels,
|
||||
prinfo->precision,
|
||||
prinfo->encoding,
|
||||
prinfo->seek,
|
||||
prinfo->samples,
|
||||
prinfo->eof,
|
||||
prinfo->pause ? "yes" : "no",
|
||||
prinfo->error ? "yes" : "no",
|
||||
prinfo->waiting ? "yes" : "no",
|
||||
prinfo->active ? "yes" : "no");
|
||||
|
||||
fprintf(stderr, "\n"
|
||||
"[audio info]\n"
|
||||
"monitor_gain : %i\n"
|
||||
"hw block size : %d bytes\n"
|
||||
"hi watermark : %i\n"
|
||||
"lo watermark : %i\n"
|
||||
"audio mode : %s\n"
|
||||
"",
|
||||
info.monitor_gain,
|
||||
info.blocksize,
|
||||
info.hiwat, info.lowat,
|
||||
(info.mode == AUMODE_PLAY) ? "PLAY"
|
||||
: (info.mode == AUMODE_RECORD) ? "RECORD"
|
||||
: (info.mode == AUMODE_PLAY_ALL ? "PLAY_ALL" : "?"));
|
||||
|
||||
fprintf(stderr, "\n"
|
||||
"[audio spec]\n"
|
||||
"format : 0x%x\n"
|
||||
"size : %u\n"
|
||||
"",
|
||||
device->spec.format,
|
||||
device->buffer_size);
|
||||
/* *INDENT-ON* */ /* clang-format on */
|
||||
|
||||
#endif // DEBUG_AUDIO
|
||||
}
|
||||
|
||||
static int NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const SDL_bool iscapture = device->iscapture;
|
||||
while (!SDL_AtomicGet(&device->shutdown)) {
|
||||
audio_info_t info;
|
||||
const int rc = ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info);
|
||||
if (rc < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
continue;
|
||||
}
|
||||
// Hmm, not much we can do - abort
|
||||
fprintf(stderr, "netbsdaudio WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
const size_t remain = (size_t)((iscapture ? info.record.seek : info.play.seek) * SDL_AUDIO_BYTESIZE(device->spec.format));
|
||||
if (!iscapture && (remain >= device->buffer_size)) {
|
||||
SDL_Delay(10);
|
||||
} else if (iscapture && (remain < device->buffer_size)) {
|
||||
SDL_Delay(10);
|
||||
} else {
|
||||
break; // ready to go!
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int NETBSDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
const int written = write(h->audio_fd, buffer, buflen);
|
||||
if (written != buflen) { // Treat even partial writes as fatal errors.
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Wrote %d bytes of audio data\n", written);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Uint8 *NETBSDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int NETBSDAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *vbuffer, int buflen)
|
||||
{
|
||||
Uint8 *buffer = (Uint8 *)vbuffer;
|
||||
const int br = read(device->hidden->audio_fd, buffer, buflen);
|
||||
if (br == -1) {
|
||||
// Non recoverable error has occurred. It should be reported!!!
|
||||
perror("audio");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Captured %d bytes of audio data\n", br);
|
||||
#endif
|
||||
return br;
|
||||
}
|
||||
|
||||
static void NETBSDAUDIO_FlushCapture(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
audio_info_t info;
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) == 0) {
|
||||
size_t remain = (size_t)(info.record.seek * SDL_AUDIO_BYTESIZE(device->spec.format));
|
||||
while (remain > 0) {
|
||||
char buf[512];
|
||||
const size_t len = SDL_min(sizeof(buf), remain);
|
||||
const ssize_t br = read(h->audio_fd, buf, len);
|
||||
if (br <= 0) {
|
||||
break;
|
||||
}
|
||||
remain -= br;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void NETBSDAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->audio_fd >= 0) {
|
||||
close(device->hidden->audio_fd);
|
||||
}
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int NETBSDAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const SDL_bool iscapture = device->iscapture;
|
||||
int encoding = AUDIO_ENCODING_NONE;
|
||||
audio_info_t info, hwinfo;
|
||||
struct audio_prinfo *prinfo = iscapture ? &info.record : &info.play;
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
|
||||
const int flags = ((device->iscapture) ? O_RDONLY : O_WRONLY);
|
||||
device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC);
|
||||
if (device->hidden->audio_fd < 0) {
|
||||
return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno));
|
||||
}
|
||||
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
#ifdef AUDIO_GETFORMAT // Introduced in NetBSD 9.0
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_GETFORMAT, &hwinfo) != -1) {
|
||||
// Use the device's native sample rate so the kernel doesn't have to resample.
|
||||
device->spec.freq = iscapture ? hwinfo.record.sample_rate : hwinfo.play.sample_rate;
|
||||
}
|
||||
#endif
|
||||
|
||||
prinfo->sample_rate = device->spec.freq;
|
||||
prinfo->channels = device->spec.channels;
|
||||
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_U8:
|
||||
encoding = AUDIO_ENCODING_ULINEAR;
|
||||
break;
|
||||
case SDL_AUDIO_S8:
|
||||
encoding = AUDIO_ENCODING_SLINEAR;
|
||||
break;
|
||||
case SDL_AUDIO_S16LE:
|
||||
encoding = AUDIO_ENCODING_SLINEAR_LE;
|
||||
break;
|
||||
case SDL_AUDIO_S16BE:
|
||||
encoding = AUDIO_ENCODING_SLINEAR_BE;
|
||||
break;
|
||||
case SDL_AUDIO_S32LE:
|
||||
encoding = AUDIO_ENCODING_SLINEAR_LE;
|
||||
break;
|
||||
case SDL_AUDIO_S32BE:
|
||||
encoding = AUDIO_ENCODING_SLINEAR_BE;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("%s: Unsupported audio format", "netbsd");
|
||||
}
|
||||
prinfo->encoding = encoding;
|
||||
prinfo->precision = SDL_AUDIO_BITSIZE(test_format);
|
||||
|
||||
info.hiwat = 5;
|
||||
info.lowat = 3;
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_SETINFO, &info) < 0) {
|
||||
return SDL_SetError("AUDIO_SETINFO failed for %s: %s", device->name, strerror(errno));
|
||||
}
|
||||
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
|
||||
return SDL_SetError("AUDIO_GETINFO failed for %s: %s", device->name, strerror(errno));
|
||||
}
|
||||
|
||||
// Final spec used for the device.
|
||||
device->spec.format = test_format;
|
||||
device->spec.freq = prinfo->sample_rate;
|
||||
device->spec.channels = prinfo->channels;
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
if (!iscapture) {
|
||||
// Allocate mixing buffer
|
||||
device->hidden->mixlen = device->buffer_size;
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->hidden->mixlen);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return -1;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
NETBSDAUDIO_Status(device);
|
||||
|
||||
return 0; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static SDL_bool NETBSDAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->DetectDevices = NETBSDAUDIO_DetectDevices;
|
||||
impl->OpenDevice = NETBSDAUDIO_OpenDevice;
|
||||
impl->WaitDevice = NETBSDAUDIO_WaitDevice;
|
||||
impl->PlayDevice = NETBSDAUDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = NETBSDAUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = NETBSDAUDIO_CloseDevice;
|
||||
impl->WaitCaptureDevice = NETBSDAUDIO_WaitDevice;
|
||||
impl->CaptureFromDevice = NETBSDAUDIO_CaptureFromDevice;
|
||||
impl->FlushCapture = NETBSDAUDIO_FlushCapture;
|
||||
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap NETBSDAUDIO_bootstrap = {
|
||||
"netbsd", "NetBSD audio", NETBSDAUDIO_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_NETBSD
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_netbsdaudio_h_
|
||||
#define SDL_netbsdaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The file descriptor for the audio device
|
||||
int audio_fd;
|
||||
|
||||
// Raw mixing buffer
|
||||
Uint8 *mixbuf;
|
||||
int mixlen;
|
||||
|
||||
// Support for audio timing using a timer, in addition to SDL_IOReady()
|
||||
float frame_ticks;
|
||||
float next_frame;
|
||||
};
|
||||
|
||||
#define FUDGE_TICKS 10 // The scheduler overhead ticks per frame
|
||||
|
||||
#endif // SDL_netbsdaudio_h_
|
||||
@@ -0,0 +1,801 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_OPENSLES
|
||||
|
||||
// For more discussion of low latency audio on Android, see this:
|
||||
// https://googlesamples.github.io/android-audio-high-performance/guides/opensl_es.html
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_openslES.h"
|
||||
|
||||
#include "../../core/android/SDL_android.h"
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include <android/log.h>
|
||||
|
||||
|
||||
#define NUM_BUFFERS 2 // -- Don't lower this!
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
Uint8 *mixbuff;
|
||||
int next_buffer;
|
||||
Uint8 *pmixbuff[NUM_BUFFERS];
|
||||
SDL_Semaphore *playsem;
|
||||
};
|
||||
|
||||
#if 0
|
||||
#define LOG_TAG "SDL_openslES"
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
//#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)
|
||||
#define LOGV(...)
|
||||
#else
|
||||
#define LOGE(...)
|
||||
#define LOGI(...)
|
||||
#define LOGV(...)
|
||||
#endif
|
||||
|
||||
/*
|
||||
#define SL_SPEAKER_FRONT_LEFT ((SLuint32) 0x00000001)
|
||||
#define SL_SPEAKER_FRONT_RIGHT ((SLuint32) 0x00000002)
|
||||
#define SL_SPEAKER_FRONT_CENTER ((SLuint32) 0x00000004)
|
||||
#define SL_SPEAKER_LOW_FREQUENCY ((SLuint32) 0x00000008)
|
||||
#define SL_SPEAKER_BACK_LEFT ((SLuint32) 0x00000010)
|
||||
#define SL_SPEAKER_BACK_RIGHT ((SLuint32) 0x00000020)
|
||||
#define SL_SPEAKER_FRONT_LEFT_OF_CENTER ((SLuint32) 0x00000040)
|
||||
#define SL_SPEAKER_FRONT_RIGHT_OF_CENTER ((SLuint32) 0x00000080)
|
||||
#define SL_SPEAKER_BACK_CENTER ((SLuint32) 0x00000100)
|
||||
#define SL_SPEAKER_SIDE_LEFT ((SLuint32) 0x00000200)
|
||||
#define SL_SPEAKER_SIDE_RIGHT ((SLuint32) 0x00000400)
|
||||
#define SL_SPEAKER_TOP_CENTER ((SLuint32) 0x00000800)
|
||||
#define SL_SPEAKER_TOP_FRONT_LEFT ((SLuint32) 0x00001000)
|
||||
#define SL_SPEAKER_TOP_FRONT_CENTER ((SLuint32) 0x00002000)
|
||||
#define SL_SPEAKER_TOP_FRONT_RIGHT ((SLuint32) 0x00004000)
|
||||
#define SL_SPEAKER_TOP_BACK_LEFT ((SLuint32) 0x00008000)
|
||||
#define SL_SPEAKER_TOP_BACK_CENTER ((SLuint32) 0x00010000)
|
||||
#define SL_SPEAKER_TOP_BACK_RIGHT ((SLuint32) 0x00020000)
|
||||
*/
|
||||
#define SL_ANDROID_SPEAKER_STEREO (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT)
|
||||
#define SL_ANDROID_SPEAKER_QUAD (SL_ANDROID_SPEAKER_STEREO | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT)
|
||||
#define SL_ANDROID_SPEAKER_5DOT1 (SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY)
|
||||
#define SL_ANDROID_SPEAKER_7DOT1 (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT)
|
||||
|
||||
// engine interfaces
|
||||
static SLObjectItf engineObject = NULL;
|
||||
static SLEngineItf engineEngine = NULL;
|
||||
|
||||
// output mix interfaces
|
||||
static SLObjectItf outputMixObject = NULL;
|
||||
|
||||
// buffer queue player interfaces
|
||||
static SLObjectItf bqPlayerObject = NULL;
|
||||
static SLPlayItf bqPlayerPlay = NULL;
|
||||
static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL;
|
||||
#if 0
|
||||
static SLVolumeItf bqPlayerVolume;
|
||||
#endif
|
||||
|
||||
// recorder interfaces
|
||||
static SLObjectItf recorderObject = NULL;
|
||||
static SLRecordItf recorderRecord = NULL;
|
||||
static SLAndroidSimpleBufferQueueItf recorderBufferQueue = NULL;
|
||||
|
||||
#if 0
|
||||
static const char *sldevaudiorecorderstr = "SLES Audio Recorder";
|
||||
static const char *sldevaudioplayerstr = "SLES Audio Player";
|
||||
|
||||
#define SLES_DEV_AUDIO_RECORDER sldevaudiorecorderstr
|
||||
#define SLES_DEV_AUDIO_PLAYER sldevaudioplayerstr
|
||||
static void OPENSLES_DetectDevices( int iscapture )
|
||||
{
|
||||
LOGI( "openSLES_DetectDevices()" );
|
||||
if ( iscapture )
|
||||
addfn( SLES_DEV_AUDIO_RECORDER );
|
||||
else
|
||||
addfn( SLES_DEV_AUDIO_PLAYER );
|
||||
}
|
||||
#endif
|
||||
|
||||
static void OPENSLES_DestroyEngine(void)
|
||||
{
|
||||
LOGI("OPENSLES_DestroyEngine()");
|
||||
|
||||
// destroy output mix object, and invalidate all associated interfaces
|
||||
if (outputMixObject != NULL) {
|
||||
(*outputMixObject)->Destroy(outputMixObject);
|
||||
outputMixObject = NULL;
|
||||
}
|
||||
|
||||
// destroy engine object, and invalidate all associated interfaces
|
||||
if (engineObject != NULL) {
|
||||
(*engineObject)->Destroy(engineObject);
|
||||
engineObject = NULL;
|
||||
engineEngine = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int OPENSLES_CreateEngine(void)
|
||||
{
|
||||
const SLInterfaceID ids[1] = { SL_IID_VOLUME };
|
||||
const SLboolean req[1] = { SL_BOOLEAN_FALSE };
|
||||
SLresult result;
|
||||
|
||||
LOGI("openSLES_CreateEngine()");
|
||||
|
||||
// create engine
|
||||
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("slCreateEngine failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
LOGI("slCreateEngine OK");
|
||||
|
||||
// realize the engine
|
||||
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RealizeEngine failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
LOGI("RealizeEngine OK");
|
||||
|
||||
// get the engine interface, which is needed in order to create other objects
|
||||
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("EngineGetInterface failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
LOGI("EngineGetInterface OK");
|
||||
|
||||
// create output mix
|
||||
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("CreateOutputMix failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
LOGI("CreateOutputMix OK");
|
||||
|
||||
// realize the output mix
|
||||
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RealizeOutputMix failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
return 1;
|
||||
|
||||
error:
|
||||
OPENSLES_DestroyEngine();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// this callback handler is called every time a buffer finishes recording
|
||||
static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = (struct SDL_PrivateAudioData *)context;
|
||||
|
||||
LOGV("SLES: Recording Callback");
|
||||
SDL_PostSemaphore(audiodata->playsem);
|
||||
}
|
||||
|
||||
static void OPENSLES_DestroyPCMRecorder(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
SLresult result;
|
||||
|
||||
// stop recording
|
||||
if (recorderRecord != NULL) {
|
||||
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SetRecordState stopped: %d", result);
|
||||
}
|
||||
}
|
||||
|
||||
// destroy audio recorder object, and invalidate all associated interfaces
|
||||
if (recorderObject != NULL) {
|
||||
(*recorderObject)->Destroy(recorderObject);
|
||||
recorderObject = NULL;
|
||||
recorderRecord = NULL;
|
||||
recorderBufferQueue = NULL;
|
||||
}
|
||||
|
||||
if (audiodata->playsem) {
|
||||
SDL_DestroySemaphore(audiodata->playsem);
|
||||
audiodata->playsem = NULL;
|
||||
}
|
||||
|
||||
if (audiodata->mixbuff) {
|
||||
SDL_free(audiodata->mixbuff);
|
||||
}
|
||||
}
|
||||
|
||||
// !!! FIXME: make this non-blocking!
|
||||
static void SDLCALL AndroidRequestPermissionBlockingCallback(void *userdata, const char *permission, SDL_bool granted)
|
||||
{
|
||||
SDL_AtomicSet((SDL_AtomicInt *) userdata, granted ? 1 : -1);
|
||||
}
|
||||
|
||||
static int OPENSLES_CreatePCMRecorder(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
SLDataFormat_PCM format_pcm;
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
|
||||
SLDataSink audioSnk;
|
||||
SLDataLocator_IODevice loc_dev;
|
||||
SLDataSource audioSrc;
|
||||
const SLInterfaceID ids[1] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
|
||||
const SLboolean req[1] = { SL_BOOLEAN_TRUE };
|
||||
SLresult result;
|
||||
int i;
|
||||
|
||||
// !!! FIXME: make this non-blocking!
|
||||
{
|
||||
SDL_AtomicInt permission_response;
|
||||
SDL_AtomicSet(&permission_response, 0);
|
||||
if (SDL_AndroidRequestPermission("android.permission.RECORD_AUDIO", AndroidRequestPermissionBlockingCallback, &permission_response) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (SDL_AtomicGet(&permission_response) == 0) {
|
||||
SDL_Delay(10);
|
||||
}
|
||||
|
||||
if (SDL_AtomicGet(&permission_response) < 0) {
|
||||
LOGE("This app doesn't have RECORD_AUDIO permission");
|
||||
return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
|
||||
}
|
||||
}
|
||||
|
||||
// Just go with signed 16-bit audio as it's the most compatible
|
||||
device->spec.format = SDL_AUDIO_S16;
|
||||
device->spec.channels = 1;
|
||||
//device->spec.freq = SL_SAMPLINGRATE_16 / 1000;*/
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
LOGI("Try to open %u hz %u bit chan %u %s samples %u",
|
||||
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
|
||||
device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
|
||||
|
||||
// configure audio source
|
||||
loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
|
||||
loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT;
|
||||
loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
|
||||
loc_dev.device = NULL;
|
||||
audioSrc.pLocator = &loc_dev;
|
||||
audioSrc.pFormat = NULL;
|
||||
|
||||
// configure audio sink
|
||||
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
loc_bufq.numBuffers = NUM_BUFFERS;
|
||||
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = device->spec.channels;
|
||||
format_pcm.samplesPerSec = device->spec.freq * 1000; // / kilo Hz to milli Hz
|
||||
format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
format_pcm.containerSize = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER;
|
||||
|
||||
audioSnk.pLocator = &loc_bufq;
|
||||
audioSnk.pFormat = &format_pcm;
|
||||
|
||||
// create audio recorder
|
||||
// (requires the RECORD_AUDIO permission)
|
||||
result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, &audioSnk, 1, ids, req);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("CreateAudioRecorder failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// realize the recorder
|
||||
result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RealizeAudioPlayer failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// get the record interface
|
||||
result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_RECORD interface get failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// get the buffer queue interface
|
||||
result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_BUFFERQUEUE interface get failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// register callback on the buffer queue
|
||||
// context is '(SDL_PrivateAudioData *)device->hidden'
|
||||
result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, device->hidden);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RegisterCallback failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// Create the audio buffer semaphore
|
||||
audiodata->playsem = SDL_CreateSemaphore(0);
|
||||
if (!audiodata->playsem) {
|
||||
LOGE("cannot create Semaphore!");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// Create the sound buffers
|
||||
audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size);
|
||||
if (!audiodata->mixbuff) {
|
||||
LOGE("mixbuffer allocate - out of memory");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_BUFFERS; i++) {
|
||||
audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size;
|
||||
}
|
||||
|
||||
// in case already recording, stop recording and clear buffer queue
|
||||
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Record set state failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// enqueue empty buffers to be filled by the recorder
|
||||
for (i = 0; i < NUM_BUFFERS; i++) {
|
||||
result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[i], device->buffer_size);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Record enqueue buffers failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
// start recording
|
||||
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Record set state failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
failed:
|
||||
return SDL_SetError("Open device failed!");
|
||||
}
|
||||
|
||||
// this callback handler is called every time a buffer finishes playing
|
||||
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = (struct SDL_PrivateAudioData *)context;
|
||||
|
||||
LOGV("SLES: Playback Callback");
|
||||
SDL_PostSemaphore(audiodata->playsem);
|
||||
}
|
||||
|
||||
static void OPENSLES_DestroyPCMPlayer(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
// set the player's state to 'stopped'
|
||||
if (bqPlayerPlay != NULL) {
|
||||
const SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SetPlayState stopped failed: %d", result);
|
||||
}
|
||||
}
|
||||
|
||||
// destroy buffer queue audio player object, and invalidate all associated interfaces
|
||||
if (bqPlayerObject != NULL) {
|
||||
(*bqPlayerObject)->Destroy(bqPlayerObject);
|
||||
|
||||
bqPlayerObject = NULL;
|
||||
bqPlayerPlay = NULL;
|
||||
bqPlayerBufferQueue = NULL;
|
||||
}
|
||||
|
||||
if (audiodata->playsem) {
|
||||
SDL_DestroySemaphore(audiodata->playsem);
|
||||
audiodata->playsem = NULL;
|
||||
}
|
||||
|
||||
if (audiodata->mixbuff) {
|
||||
SDL_free(audiodata->mixbuff);
|
||||
}
|
||||
}
|
||||
|
||||
static int OPENSLES_CreatePCMPlayer(SDL_AudioDevice *device)
|
||||
{
|
||||
/* If we want to add floating point audio support (requires API level 21)
|
||||
it can be done as described here:
|
||||
https://developer.android.com/ndk/guides/audio/opensl/android-extensions.html#floating-point
|
||||
*/
|
||||
if (SDL_GetAndroidSDKVersion() >= 21) {
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
SDL_AudioFormat test_format;
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (SDL_AUDIO_ISSIGNED(test_format)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
// Didn't find a compatible format :
|
||||
LOGI("No compatible audio format, using signed 16-bit audio");
|
||||
test_format = SDL_AUDIO_S16;
|
||||
}
|
||||
device->spec.format = test_format;
|
||||
} else {
|
||||
// Just go with signed 16-bit audio as it's the most compatible
|
||||
device->spec.format = SDL_AUDIO_S16;
|
||||
}
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
LOGI("Try to open %u hz %s %u bit chan %u %s samples %u",
|
||||
device->spec.freq, SDL_AUDIO_ISFLOAT(device->spec.format) ? "float" : "pcm", SDL_AUDIO_BITSIZE(device->spec.format),
|
||||
device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
|
||||
|
||||
// configure audio source
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
|
||||
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
loc_bufq.numBuffers = NUM_BUFFERS;
|
||||
|
||||
SLDataFormat_PCM format_pcm;
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = device->spec.channels;
|
||||
format_pcm.samplesPerSec = device->spec.freq * 1000; // / kilo Hz to milli Hz
|
||||
format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
format_pcm.containerSize = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
|
||||
if (SDL_AUDIO_ISBIGENDIAN(device->spec.format)) {
|
||||
format_pcm.endianness = SL_BYTEORDER_BIGENDIAN;
|
||||
} else {
|
||||
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
}
|
||||
|
||||
switch (device->spec.channels) {
|
||||
case 1:
|
||||
format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT;
|
||||
break;
|
||||
case 2:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_STEREO;
|
||||
break;
|
||||
case 3:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_STEREO | SL_SPEAKER_FRONT_CENTER;
|
||||
break;
|
||||
case 4:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_QUAD;
|
||||
break;
|
||||
case 5:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER;
|
||||
break;
|
||||
case 6:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_5DOT1;
|
||||
break;
|
||||
case 7:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_BACK_CENTER;
|
||||
break;
|
||||
case 8:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_7DOT1;
|
||||
break;
|
||||
default:
|
||||
// Unknown number of channels, fall back to stereo
|
||||
device->spec.channels = 2;
|
||||
format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
break;
|
||||
}
|
||||
|
||||
SLDataSink audioSnk;
|
||||
SLDataSource audioSrc;
|
||||
audioSrc.pFormat = (void *)&format_pcm;
|
||||
|
||||
SLAndroidDataFormat_PCM_EX format_pcm_ex;
|
||||
if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
|
||||
// Copy all setup into PCM EX structure
|
||||
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
format_pcm_ex.endianness = format_pcm.endianness;
|
||||
format_pcm_ex.channelMask = format_pcm.channelMask;
|
||||
format_pcm_ex.numChannels = format_pcm.numChannels;
|
||||
format_pcm_ex.sampleRate = format_pcm.samplesPerSec;
|
||||
format_pcm_ex.bitsPerSample = format_pcm.bitsPerSample;
|
||||
format_pcm_ex.containerSize = format_pcm.containerSize;
|
||||
format_pcm_ex.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
|
||||
audioSrc.pFormat = (void *)&format_pcm_ex;
|
||||
}
|
||||
|
||||
audioSrc.pLocator = &loc_bufq;
|
||||
|
||||
// configure audio sink
|
||||
SLDataLocator_OutputMix loc_outmix;
|
||||
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
|
||||
loc_outmix.outputMix = outputMixObject;
|
||||
audioSnk.pLocator = &loc_outmix;
|
||||
audioSnk.pFormat = NULL;
|
||||
|
||||
// create audio player
|
||||
const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
|
||||
const SLboolean req[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
|
||||
SLresult result;
|
||||
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("CreateAudioPlayer failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// realize the player
|
||||
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RealizeAudioPlayer failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// get the play interface
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_PLAY interface get failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// get the buffer queue interface
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bqPlayerBufferQueue);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_BUFFERQUEUE interface get failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// register callback on the buffer queue
|
||||
// context is '(SDL_PrivateAudioData *)device->hidden'
|
||||
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, device->hidden);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RegisterCallback failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// get the volume interface
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_VOLUME interface get failed: %d", result);
|
||||
// goto failed;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
// Create the audio buffer semaphore
|
||||
audiodata->playsem = SDL_CreateSemaphore(NUM_BUFFERS - 1);
|
||||
if (!audiodata->playsem) {
|
||||
LOGE("cannot create Semaphore!");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// Create the sound buffers
|
||||
audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size);
|
||||
if (!audiodata->mixbuff) {
|
||||
LOGE("mixbuffer allocate - out of memory");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_BUFFERS; i++) {
|
||||
audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size;
|
||||
}
|
||||
|
||||
// set the player's state to playing
|
||||
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Play set state failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
failed:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int OPENSLES_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (device->iscapture) {
|
||||
LOGI("OPENSLES_OpenDevice() for capture");
|
||||
return OPENSLES_CreatePCMRecorder(device);
|
||||
} else {
|
||||
int ret;
|
||||
LOGI("OPENSLES_OpenDevice() for playing");
|
||||
ret = OPENSLES_CreatePCMPlayer(device);
|
||||
if (ret < 0) {
|
||||
// Another attempt to open the device with a lower frequency
|
||||
if (device->spec.freq > 48000) {
|
||||
OPENSLES_DestroyPCMPlayer(device);
|
||||
device->spec.freq = 48000;
|
||||
ret = OPENSLES_CreatePCMPlayer(device);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
return SDL_SetError("Open device failed!");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int OPENSLES_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
LOGV("OPENSLES_WaitDevice()");
|
||||
|
||||
// Wait for an audio chunk to finish
|
||||
return SDL_WaitSemaphore(audiodata->playsem);
|
||||
}
|
||||
|
||||
static int OPENSLES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
LOGV("======OPENSLES_PlayDevice()======");
|
||||
|
||||
// Queue it up
|
||||
const SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, buflen);
|
||||
|
||||
audiodata->next_buffer++;
|
||||
if (audiodata->next_buffer >= NUM_BUFFERS) {
|
||||
audiodata->next_buffer = 0;
|
||||
}
|
||||
|
||||
// If Enqueue fails, callback won't be called.
|
||||
// Post the semaphore, not to run out of buffer
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
SDL_PostSemaphore(audiodata->playsem);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// n playn sem
|
||||
// getbuf 0 - 1
|
||||
// fill buff 0 - 1
|
||||
// play 0 - 0 1
|
||||
// wait 1 0 0
|
||||
// getbuf 1 0 0
|
||||
// fill buff 1 0 0
|
||||
// play 0 0 0
|
||||
// wait
|
||||
//
|
||||
// okay..
|
||||
|
||||
static Uint8 *OPENSLES_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
LOGV("OPENSLES_GetDeviceBuf()");
|
||||
return audiodata->pmixbuff[audiodata->next_buffer];
|
||||
}
|
||||
|
||||
static int OPENSLES_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
// Copy it to the output buffer
|
||||
SDL_assert(buflen == device->buffer_size);
|
||||
SDL_memcpy(buffer, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_size);
|
||||
|
||||
// Re-enqueue the buffer
|
||||
const SLresult result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_size);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Record enqueue buffers failed: %d", result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
audiodata->next_buffer++;
|
||||
if (audiodata->next_buffer >= NUM_BUFFERS) {
|
||||
audiodata->next_buffer = 0;
|
||||
}
|
||||
|
||||
return device->buffer_size;
|
||||
}
|
||||
|
||||
static void OPENSLES_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
if (device->hidden) {
|
||||
if (device->iscapture) {
|
||||
LOGI("OPENSLES_CloseDevice() for capture");
|
||||
OPENSLES_DestroyPCMRecorder(device);
|
||||
} else {
|
||||
LOGI("OPENSLES_CloseDevice() for playing");
|
||||
OPENSLES_DestroyPCMPlayer(device);
|
||||
}
|
||||
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_bool OPENSLES_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
LOGI("OPENSLES_Init() called");
|
||||
|
||||
if (!OPENSLES_CreateEngine()) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
LOGI("OPENSLES_Init() - set pointers");
|
||||
|
||||
// Set the function pointers
|
||||
// impl->DetectDevices = OPENSLES_DetectDevices;
|
||||
impl->ThreadInit = Android_AudioThreadInit;
|
||||
impl->OpenDevice = OPENSLES_OpenDevice;
|
||||
impl->WaitDevice = OPENSLES_WaitDevice;
|
||||
impl->PlayDevice = OPENSLES_PlayDevice;
|
||||
impl->GetDeviceBuf = OPENSLES_GetDeviceBuf;
|
||||
impl->WaitCaptureDevice = OPENSLES_WaitDevice;
|
||||
impl->CaptureFromDevice = OPENSLES_CaptureFromDevice;
|
||||
impl->CloseDevice = OPENSLES_CloseDevice;
|
||||
impl->Deinitialize = OPENSLES_DestroyEngine;
|
||||
|
||||
// and the capabilities
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
|
||||
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
|
||||
|
||||
LOGI("OPENSLES_Init() - success");
|
||||
|
||||
// this audio target is available.
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap OPENSLES_bootstrap = {
|
||||
"openslES", "OpenSL ES audio driver", OPENSLES_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
void OPENSLES_ResumeDevices(void)
|
||||
{
|
||||
if (bqPlayerPlay != NULL) {
|
||||
// set the player's state to 'playing'
|
||||
SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("OPENSLES_ResumeDevices failed: %d", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OPENSLES_PauseDevices(void)
|
||||
{
|
||||
if (bqPlayerPlay != NULL) {
|
||||
// set the player's state to 'paused'
|
||||
SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("OPENSLES_PauseDevices failed: %d", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_OPENSLES
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_openslesaudio_h_
|
||||
#define SDL_openslesaudio_h_
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_OPENSLES
|
||||
|
||||
void OPENSLES_ResumeDevices(void);
|
||||
void OPENSLES_PauseDevices(void);
|
||||
|
||||
#else
|
||||
|
||||
static void OPENSLES_ResumeDevices(void) {}
|
||||
static void OPENSLES_PauseDevices(void) {}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // SDL_openslesaudio_h_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_pipewire_h_
|
||||
#define SDL_pipewire_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include <pipewire/pipewire.h>
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
struct pw_thread_loop *loop;
|
||||
struct pw_stream *stream;
|
||||
struct pw_context *context;
|
||||
|
||||
Sint32 stride; // Bytes-per-frame
|
||||
int stream_init_status;
|
||||
|
||||
// Set in GetDeviceBuf, filled in AudioThreadIterate, queued in PlayDevice
|
||||
struct pw_buffer *pw_buf;
|
||||
};
|
||||
|
||||
#endif // SDL_pipewire_h_
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_ps2audio.h"
|
||||
|
||||
#include <kernel.h>
|
||||
#include <audsrv.h>
|
||||
#include <ps2_audio_driver.h>
|
||||
|
||||
static int PS2AUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// These are the native supported audio PS2 configs
|
||||
switch (device->spec.freq) {
|
||||
case 11025:
|
||||
case 12000:
|
||||
case 22050:
|
||||
case 24000:
|
||||
case 32000:
|
||||
case 44100:
|
||||
case 48000:
|
||||
break; // acceptable value, keep it
|
||||
default:
|
||||
device->spec.freq = 48000;
|
||||
break;
|
||||
}
|
||||
|
||||
device->sample_frames = 512;
|
||||
device->spec.channels = device->spec.channels == 1 ? 1 : 2;
|
||||
device->spec.format = device->spec.format == SDL_AUDIO_S8 ? SDL_AUDIO_S8 : SDL_AUDIO_S16;
|
||||
|
||||
struct audsrv_fmt_t format;
|
||||
format.bits = device->spec.format == SDL_AUDIO_S8 ? 8 : 16;
|
||||
format.freq = device->spec.freq;
|
||||
format.channels = device->spec.channels;
|
||||
|
||||
device->hidden->channel = audsrv_set_format(&format);
|
||||
audsrv_set_volume(MAX_VOLUME);
|
||||
|
||||
if (device->hidden->channel < 0) {
|
||||
return SDL_SetError("Couldn't reserve hardware channel");
|
||||
}
|
||||
|
||||
// Update the fragment size as size in bytes.
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
/* Allocate the mixing buffer. Its size and starting address must
|
||||
be a multiple of 64 bytes. Our sample count is already a multiple of
|
||||
64, so spec->size should be a multiple of 64 as well. */
|
||||
const int mixlen = device->buffer_size * NUM_BUFFERS;
|
||||
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
|
||||
if (!device->hidden->rawbuf) {
|
||||
return SDL_SetError("Couldn't allocate mixing buffer");
|
||||
}
|
||||
|
||||
SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen);
|
||||
for (int i = 0; i < NUM_BUFFERS; i++) {
|
||||
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int PS2AUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
// this returns number of bytes accepted or a negative error. We assume anything other than buflen is a fatal error.
|
||||
return (audsrv_play_audio((char *)buffer, buflen) != buflen) ? -1 : 0;
|
||||
}
|
||||
|
||||
static int PS2AUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
audsrv_wait_audio(device->buffer_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Uint8 *PS2AUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer];
|
||||
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void PS2AUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->channel >= 0) {
|
||||
audsrv_stop_audio();
|
||||
device->hidden->channel = -1;
|
||||
}
|
||||
|
||||
if (device->hidden->rawbuf) {
|
||||
SDL_aligned_free(device->hidden->rawbuf);
|
||||
device->hidden->rawbuf = NULL;
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void PS2AUDIO_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
/* Increase the priority of this audio thread by 1 to put it
|
||||
ahead of other SDL threads. */
|
||||
const int32_t thid = GetThreadId();
|
||||
ee_thread_status_t status;
|
||||
if (ReferThreadStatus(thid, &status) == 0) {
|
||||
ChangeThreadPriority(thid, status.current_priority - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void PS2AUDIO_Deinitialize(void)
|
||||
{
|
||||
deinit_audio_driver();
|
||||
}
|
||||
|
||||
static SDL_bool PS2AUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (init_audio_driver() < 0) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
impl->OpenDevice = PS2AUDIO_OpenDevice;
|
||||
impl->PlayDevice = PS2AUDIO_PlayDevice;
|
||||
impl->WaitDevice = PS2AUDIO_WaitDevice;
|
||||
impl->GetDeviceBuf = PS2AUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = PS2AUDIO_CloseDevice;
|
||||
impl->ThreadInit = PS2AUDIO_ThreadInit;
|
||||
impl->Deinitialize = PS2AUDIO_Deinitialize;
|
||||
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
|
||||
return SDL_TRUE; // this audio target is available.
|
||||
}
|
||||
|
||||
AudioBootStrap PS2AUDIO_bootstrap = {
|
||||
"ps2", "PS2 audio driver", PS2AUDIO_Init, SDL_FALSE
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_ps2audio_h_
|
||||
#define SDL_ps2audio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#define NUM_BUFFERS 2
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The hardware output channel.
|
||||
int channel;
|
||||
// The raw allocated mixing buffer.
|
||||
Uint8 *rawbuf;
|
||||
// Individual mixing buffers.
|
||||
Uint8 *mixbufs[NUM_BUFFERS];
|
||||
// Index of the next available mixing buffer.
|
||||
int next_buffer;
|
||||
};
|
||||
|
||||
#endif // SDL_ps2audio_h_
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_PSP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../SDL_audiodev_c.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_pspaudio.h"
|
||||
|
||||
#include <pspaudio.h>
|
||||
#include <pspthreadman.h>
|
||||
|
||||
static inline SDL_bool isBasicAudioConfig(const SDL_AudioSpec *spec)
|
||||
{
|
||||
return spec->freq == 44100;
|
||||
}
|
||||
|
||||
static int PSPAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// device only natively supports S16LSB
|
||||
device->spec.format = SDL_AUDIO_S16LE;
|
||||
|
||||
/* PSP has some limitations with the Audio. It fully supports 44.1KHz (Mono & Stereo),
|
||||
however with frequencies different than 44.1KHz, it just supports Stereo,
|
||||
so a resampler must be done for these scenarios */
|
||||
if (isBasicAudioConfig(&device->spec)) {
|
||||
// The sample count must be a multiple of 64.
|
||||
device->sample_frames = PSP_AUDIO_SAMPLE_ALIGN(device->sample_frames);
|
||||
// The number of channels (1 or 2).
|
||||
device->spec.channels = device->spec.channels == 1 ? 1 : 2;
|
||||
const int format = (device->spec.channels == 1) ? PSP_AUDIO_FORMAT_MONO : PSP_AUDIO_FORMAT_STEREO;
|
||||
device->hidden->channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, device->sample_frames, format);
|
||||
} else {
|
||||
// 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11050, 8000
|
||||
switch (device->spec.freq) {
|
||||
case 8000:
|
||||
case 11025:
|
||||
case 12000:
|
||||
case 16000:
|
||||
case 22050:
|
||||
case 24000:
|
||||
case 32000:
|
||||
case 44100:
|
||||
case 48000:
|
||||
break; // acceptable, keep it
|
||||
default:
|
||||
device->spec.freq = 48000;
|
||||
break;
|
||||
}
|
||||
// The number of samples to output in one output call (min 17, max 4111).
|
||||
device->sample_frames = device->sample_frames < 17 ? 17 : (device->sample_frames > 4111 ? 4111 : device->sample_frames);
|
||||
device->spec.channels = 2; // we're forcing the hardware to stereo.
|
||||
device->hidden->channel = sceAudioSRCChReserve(device->sample_frames, device->spec.freq, 2);
|
||||
}
|
||||
|
||||
if (device->hidden->channel < 0) {
|
||||
return SDL_SetError("Couldn't reserve hardware channel");
|
||||
}
|
||||
|
||||
// Update the fragment size as size in bytes.
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
/* Allocate the mixing buffer. Its size and starting address must
|
||||
be a multiple of 64 bytes. Our sample count is already a multiple of
|
||||
64, so spec->size should be a multiple of 64 as well. */
|
||||
const int mixlen = device->buffer_size * NUM_BUFFERS;
|
||||
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
|
||||
if (!device->hidden->rawbuf) {
|
||||
return SDL_SetError("Couldn't allocate mixing buffer");
|
||||
}
|
||||
|
||||
SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen);
|
||||
for (int i = 0; i < NUM_BUFFERS; i++) {
|
||||
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
int rc;
|
||||
if (!isBasicAudioConfig(&device->spec)) {
|
||||
SDL_assert(device->spec.channels == 2);
|
||||
rc = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, (void *) buffer);
|
||||
} else {
|
||||
rc = sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, (void *) buffer);
|
||||
}
|
||||
return (rc == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int PSPAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
return 0; // Because we block when sending audio, there's no need for this function to do anything.
|
||||
}
|
||||
|
||||
static Uint8 *PSPAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer];
|
||||
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void PSPAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->channel >= 0) {
|
||||
if (!isBasicAudioConfig(&device->spec)) {
|
||||
sceAudioSRCChRelease();
|
||||
} else {
|
||||
sceAudioChRelease(device->hidden->channel);
|
||||
}
|
||||
device->hidden->channel = -1;
|
||||
}
|
||||
|
||||
if (device->hidden->rawbuf) {
|
||||
SDL_aligned_free(device->hidden->rawbuf);
|
||||
device->hidden->rawbuf = NULL;
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void PSPAUDIO_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
/* Increase the priority of this audio thread by 1 to put it
|
||||
ahead of other SDL threads. */
|
||||
const SceUID thid = sceKernelGetThreadId();
|
||||
SceKernelThreadInfo status;
|
||||
status.size = sizeof(SceKernelThreadInfo);
|
||||
if (sceKernelReferThreadStatus(thid, &status) == 0) {
|
||||
sceKernelChangeThreadPriority(thid, status.currentPriority - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_bool PSPAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = PSPAUDIO_OpenDevice;
|
||||
impl->PlayDevice = PSPAUDIO_PlayDevice;
|
||||
impl->WaitDevice = PSPAUDIO_WaitDevice;
|
||||
impl->GetDeviceBuf = PSPAUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = PSPAUDIO_CloseDevice;
|
||||
impl->ThreadInit = PSPAUDIO_ThreadInit;
|
||||
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
|
||||
//impl->HasCaptureSupport = SDL_TRUE;
|
||||
//impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap PSPAUDIO_bootstrap = {
|
||||
"psp", "PSP audio driver", PSPAUDIO_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_PSP
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_pspaudio_h_
|
||||
#define SDL_pspaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#define NUM_BUFFERS 2
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The hardware output channel.
|
||||
int channel;
|
||||
// The raw allocated mixing buffer.
|
||||
Uint8 *rawbuf;
|
||||
// Individual mixing buffers.
|
||||
Uint8 *mixbufs[NUM_BUFFERS];
|
||||
// Index of the next available mixing buffer.
|
||||
int next_buffer;
|
||||
};
|
||||
|
||||
#endif // SDL_pspaudio_h_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_pulseaudio_h_
|
||||
#define SDL_pulseaudio_h_
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// pulseaudio structures
|
||||
pa_stream *stream;
|
||||
|
||||
// Raw mixing buffer
|
||||
Uint8 *mixbuf;
|
||||
|
||||
int bytes_requested; // bytes of data the hardware wants _now_.
|
||||
|
||||
const Uint8 *capturebuf;
|
||||
int capturelen;
|
||||
};
|
||||
|
||||
#endif // SDL_pulseaudio_h_
|
||||
@@ -0,0 +1,450 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
// !!! FIXME: can this target support hotplugging?
|
||||
|
||||
#include "../../SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_QNX
|
||||
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sched.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/neutrino.h>
|
||||
#include <sys/asoundlib.h>
|
||||
|
||||
#include "SDL3/SDL_timer.h"
|
||||
#include "SDL3/SDL_audio.h"
|
||||
#include "../../core/unix/SDL_poll.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_qsa_audio.h"
|
||||
|
||||
// default channel communication parameters
|
||||
#define DEFAULT_CPARAMS_RATE 44100
|
||||
#define DEFAULT_CPARAMS_VOICES 1
|
||||
|
||||
#define DEFAULT_CPARAMS_FRAG_SIZE 4096
|
||||
#define DEFAULT_CPARAMS_FRAGS_MIN 1
|
||||
#define DEFAULT_CPARAMS_FRAGS_MAX 1
|
||||
|
||||
#define QSA_MAX_NAME_LENGTH 81+16 // Hardcoded in QSA, can't be changed
|
||||
|
||||
static int QSA_SetError(const char *fn, int status)
|
||||
{
|
||||
return SDL_SetError("QSA: %s() failed: %s", fn, snd_strerror(status));
|
||||
}
|
||||
|
||||
// !!! FIXME: does this need to be here? Does the SDL version not work?
|
||||
static void QSA_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
// Increase default 10 priority to 25 to avoid jerky sound
|
||||
struct sched_param param;
|
||||
if (SchedGet(0, 0, ¶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 int QSA_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Setup timeout for playing one fragment equal to 2 seconds
|
||||
// If timeout occurred than something wrong with hardware or driver
|
||||
// For example, Vortex 8820 audio driver stucks on second DAC because
|
||||
// it doesn't exist !
|
||||
const int result = SDL_IOReady(device->hidden->audio_fd,
|
||||
device->iscapture ? SDL_IOR_READ : SDL_IOR_WRITE,
|
||||
2 * 1000);
|
||||
switch (result) {
|
||||
case -1:
|
||||
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "QSA: SDL_IOReady() failed: %s", strerror(errno));
|
||||
return -1;
|
||||
case 0:
|
||||
device->hidden->timeout_on_wait = SDL_TRUE; // !!! FIXME: Should we just disconnect the device in this case?
|
||||
break;
|
||||
default:
|
||||
device->hidden->timeout_on_wait = SDL_FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
if (SDL_AtomicGet(&device->shutdown) || !device->hidden) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int towrite = buflen;
|
||||
|
||||
// Write the audio data, checking for EAGAIN (buffer full) and underrun
|
||||
while ((towrite > 0) && !SDL_AtomicGet(&device->shutdown));
|
||||
const int bw = snd_pcm_plugin_write(device->hidden->audio_handle, buffer, towrite);
|
||||
if (bw != towrite) {
|
||||
// Check if samples playback got stuck somewhere in hardware or in the audio device driver
|
||||
if ((errno == EAGAIN) && (bw == 0)) {
|
||||
if (device->hidden->timeout_on_wait) {
|
||||
return 0; // oh well, try again next time. !!! FIXME: Should we just disconnect the device in this case?
|
||||
}
|
||||
}
|
||||
|
||||
// Check for errors or conditions
|
||||
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
|
||||
SDL_Delay(1); // Let a little CPU time go by and try to write again
|
||||
|
||||
// if we wrote some data
|
||||
towrite -= bw;
|
||||
buffer += bw * device->spec.channels;
|
||||
continue;
|
||||
} else if ((errno == EINVAL) || (errno == EIO)) {
|
||||
snd_pcm_channel_status_t cstatus;
|
||||
SDL_zero(cstatus);
|
||||
cstatus.channel = device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
|
||||
|
||||
int status = snd_pcm_plugin_status(device->hidden->audio_handle, &cstatus);
|
||||
if (status < 0) {
|
||||
QSA_SetError("snd_pcm_plugin_status", status);
|
||||
return -1;
|
||||
} else if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY)) {
|
||||
status = snd_pcm_plugin_prepare(device->hidden->audio_handle, device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
|
||||
if (status < 0) {
|
||||
QSA_SetError("snd_pcm_plugin_prepare", status);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// we wrote all remaining data
|
||||
towrite -= bw;
|
||||
buffer += bw * device->spec.channels;
|
||||
}
|
||||
}
|
||||
|
||||
// If we couldn't write, assume fatal error for now
|
||||
return (towrite != 0) ? -1 : 0;
|
||||
}
|
||||
|
||||
static Uint8 *QSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->pcm_buf;
|
||||
}
|
||||
|
||||
static void QSA_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->audio_handle) {
|
||||
#if _NTO_VERSION < 710
|
||||
// Finish playing available samples or cancel unread samples during capture
|
||||
snd_pcm_plugin_flush(device->hidden->audio_handle, device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
|
||||
#endif
|
||||
snd_pcm_close(device->hidden->audio_handle);
|
||||
}
|
||||
|
||||
SDL_free(device->hidden->pcm_buf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int QSA_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->iscapture) {
|
||||
return SDL_SetError("SDL capture support isn't available on QNX atm"); // !!! FIXME: most of this code has support for capture devices, but there's no CaptureFromDevice, etc functions. Fill them in!
|
||||
}
|
||||
|
||||
SDL_assert(device->handle != NULL); // NULL used to mean "system default device" in SDL2; it does not mean that in SDL3.
|
||||
const Uint32 sdlhandle = (Uint32) ((size_t) device->handle);
|
||||
const uint32_t cardno = (uint32_t) (sdlhandle & 0xFFFF);
|
||||
const uint32_t deviceno = (uint32_t) ((sdlhandle >> 16) & 0xFFFF);
|
||||
const SDL_bool iscapture = device->iscapture;
|
||||
int status = 0;
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, (sizeof (struct SDL_PrivateAudioData)));
|
||||
if (device->hidden == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Initialize channel transfer parameters to default
|
||||
snd_pcm_channel_params_t cparams;
|
||||
QSA_InitAudioParams(&cparams);
|
||||
|
||||
// Open requested audio device
|
||||
status = snd_pcm_open(&device->hidden->audio_handle, cardno, deviceno, iscapture ? SND_PCM_OPEN_CAPTURE : SND_PCM_OPEN_PLAYBACK);
|
||||
if (status < 0) {
|
||||
device->hidden->audio_handle = NULL;
|
||||
return QSA_SetError("snd_pcm_open", status);
|
||||
}
|
||||
|
||||
// Try for a closest match on audio format
|
||||
SDL_AudioFormat test_format = 0;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
// if match found set format to equivalent QSA format
|
||||
switch (test_format) {
|
||||
#define CHECKFMT(sdlfmt, qsafmt) case SDL_AUDIO_##sdlfmt: cparams.format.format = SND_PCM_SFMT_##qsafmt; break
|
||||
CHECKFMT(U8, U8);
|
||||
CHECKFMT(S8, S8);
|
||||
CHECKFMT(S16LSB, S16_LE);
|
||||
CHECKFMT(S16MSB, S16_BE);
|
||||
CHECKFMT(S32LSB, S32_LE);
|
||||
CHECKFMT(S32MSB, S32_BE);
|
||||
CHECKFMT(F32LSB, FLOAT_LE);
|
||||
CHECKFMT(F32MSB, FLOAT_BE);
|
||||
#undef CHECKFMT
|
||||
default: continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// assumes test_format not 0 on success
|
||||
if (test_format == 0) {
|
||||
return SDL_SetError("QSA: Couldn't find any hardware audio formats");
|
||||
}
|
||||
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Set mono/stereo/4ch/6ch/8ch audio
|
||||
cparams.format.voices = device->spec.channels;
|
||||
|
||||
// Set rate
|
||||
cparams.format.rate = device->spec.freq;
|
||||
|
||||
// Setup the transfer parameters according to cparams
|
||||
status = snd_pcm_plugin_params(device->hidden->audio_handle, &cparams);
|
||||
if (status < 0) {
|
||||
return QSA_SetError("snd_pcm_plugin_params", status);
|
||||
}
|
||||
|
||||
// Make sure channel is setup right one last time
|
||||
snd_pcm_channel_setup_t csetup;
|
||||
SDL_zero(csetup);
|
||||
csetup.channel = iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
|
||||
if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) {
|
||||
return SDL_SetError("QSA: Unable to setup channel");
|
||||
}
|
||||
|
||||
device->sample_frames = csetup.buf.block.frag_size;
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
device->hidden->pcm_buf = (Uint8 *) SDL_malloc(device->buffer_size);
|
||||
if (device->hidden->pcm_buf == NULL) {
|
||||
return -1;
|
||||
}
|
||||
SDL_memset(device->hidden->pcm_buf, device->silence_value, device->buffer_size);
|
||||
|
||||
// get the file descriptor
|
||||
device->hidden->audio_fd = snd_pcm_file_descriptor(device->hidden->audio_handle, csetup.channel);
|
||||
if (device->hidden->audio_fd < 0) {
|
||||
return QSA_SetError("snd_pcm_file_descriptor", device->hidden->audio_fd);
|
||||
}
|
||||
|
||||
// Prepare an audio channel
|
||||
status = snd_pcm_plugin_prepare(device->hidden->audio_handle, csetup.channel)
|
||||
if (status < 0) {
|
||||
return QSA_SetError("snd_pcm_plugin_prepare", status);
|
||||
}
|
||||
|
||||
return 0; // We're really ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static SDL_AudioFormat QnxFormatToSDLFormat(const int32_t qnxfmt)
|
||||
{
|
||||
switch (qnxfmt) {
|
||||
#define CHECKFMT(sdlfmt, qsafmt) case SND_PCM_SFMT_##qsafmt: return SDL_AUDIO_##sdlfmt
|
||||
CHECKFMT(U8, U8);
|
||||
CHECKFMT(S8, S8);
|
||||
CHECKFMT(S16LSB, S16_LE);
|
||||
CHECKFMT(S16MSB, S16_BE);
|
||||
CHECKFMT(S32LSB, S32_LE);
|
||||
CHECKFMT(S32MSB, S32_BE);
|
||||
CHECKFMT(F32LSB, FLOAT_LE);
|
||||
CHECKFMT(F32MSB, FLOAT_BE);
|
||||
#undef CHECKFMT
|
||||
default: break;
|
||||
}
|
||||
return SDL_AUDIO_S16; // oh well.
|
||||
}
|
||||
|
||||
static void QSA_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
|
||||
{
|
||||
// Detect amount of available devices
|
||||
// this value can be changed in the runtime
|
||||
int num_cards = 0;
|
||||
(void) snd_cards_list(NULL, 0, &alloc_num_cards);
|
||||
SDL_bool isstack = SDL_FALSE;
|
||||
int *cards = SDL_small_alloc(int, num_cards, &isstack);
|
||||
if (!cards) {
|
||||
return; // we're in trouble.
|
||||
}
|
||||
int overflow_cards = 0;
|
||||
const int total_num_cards = snd_cards_list(cards, num_cards, &overflow_cards);
|
||||
// if overflow_cards > 0 or total_num_cards > num_cards, it changed at the last moment; oh well, we lost some.
|
||||
num_cards = SDL_min(num_cards, total_num_cards); // ...but make sure it didn't _shrink_.
|
||||
|
||||
// If io-audio manager is not running we will get 0 as number of available audio devices
|
||||
if (num_cards == 0) { // not any available audio devices?
|
||||
SDL_small_free(cards, isstack);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find requested devices by type
|
||||
for (int it = 0; it < num_cards; it++) {
|
||||
const int card = cards[it];
|
||||
for (uint32_t deviceno = 0; ; deviceno++) {
|
||||
int32_t status;
|
||||
char name[QSA_MAX_NAME_LENGTH];
|
||||
|
||||
status = snd_card_get_longname(card, name, sizeof (name));
|
||||
if (status == EOK) {
|
||||
snd_pcm_t *handle;
|
||||
|
||||
// Add device number to device name
|
||||
char fullname[QSA_MAX_NAME_LENGTH + 32];
|
||||
SDL_snprintf(fullname, sizeof (fullname), "%s d%d", name, (int) deviceno);
|
||||
|
||||
// Check if this device id could play anything
|
||||
SDL_bool iscapture = SDL_FALSE;
|
||||
status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_PLAYBACK);
|
||||
if (status != EOK) { // no? See if it's a capture device instead.
|
||||
#if 0 // !!! FIXME: most of this code has support for capture devices, but there's no CaptureFromDevice, etc functions. Fill them in!
|
||||
status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_CAPTURE);
|
||||
if (status == EOK) {
|
||||
iscapture = SDL_TRUE;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (status == EOK) {
|
||||
SDL_AudioSpec spec;
|
||||
SDL_AudioSpec *pspec = &spec;
|
||||
snd_pcm_channel_setup_t csetup;
|
||||
SDL_zero(csetup);
|
||||
csetup.channel = iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
|
||||
|
||||
if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) {
|
||||
pspec = NULL; // go on without spec info.
|
||||
} else {
|
||||
spec.format = QnxFormatToSDLFormat(csetup.format.format);
|
||||
spec.channels = csetup.format.channels;
|
||||
spec.freq = csetup.format.rate;
|
||||
}
|
||||
|
||||
status = snd_pcm_close(handle);
|
||||
if (status == EOK) {
|
||||
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
|
||||
SDL_assert(card <= 0xFFFF);
|
||||
SDL_assert(deviceno <= 0xFFFF);
|
||||
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
|
||||
SDL_AddAudioDevice(iscapture, fullname, pspec, (void *) ((size_t) sdlhandle));
|
||||
}
|
||||
} else {
|
||||
// Check if we got end of devices list
|
||||
if (status == -ENOENT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_small_free(cards, isstack);
|
||||
|
||||
// Try to open the "preferred" devices, which will tell us the card/device pairs for the default devices.
|
||||
snd_pcm_t handle;
|
||||
int cardno, deviceno;
|
||||
if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_PLAYBACK) == 0) {
|
||||
snd_pcm_close(handle);
|
||||
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
|
||||
SDL_assert(cardno <= 0xFFFF);
|
||||
SDL_assert(deviceno <= 0xFFFF);
|
||||
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
|
||||
*default_output = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
|
||||
}
|
||||
|
||||
if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_CAPTURE) == 0) {
|
||||
snd_pcm_close(handle);
|
||||
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
|
||||
SDL_assert(cardno <= 0xFFFF);
|
||||
SDL_assert(deviceno <= 0xFFFF);
|
||||
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
|
||||
*default_capture = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
|
||||
}
|
||||
}
|
||||
|
||||
static void QSA_Deinitialize(void)
|
||||
{
|
||||
// nothing to do here atm.
|
||||
}
|
||||
|
||||
static SDL_bool QSA_Init(SDL_AudioDriverImpl * impl)
|
||||
{
|
||||
impl->DetectDevices = QSA_DetectDevices;
|
||||
impl->OpenDevice = QSA_OpenDevice;
|
||||
impl->ThreadInit = QSA_ThreadInit;
|
||||
impl->WaitDevice = QSA_WaitDevice;
|
||||
impl->PlayDevice = QSA_PlayDevice;
|
||||
impl->GetDeviceBuf = QSA_GetDeviceBuf;
|
||||
impl->CloseDevice = QSA_CloseDevice;
|
||||
impl->Deinitialize = QSA_Deinitialize;
|
||||
|
||||
// !!! FIXME: most of this code has support for capture devices, but there's no CaptureFromDevice, etc functions. Fill them in!
|
||||
//impl->HasCaptureSupport = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap QSAAUDIO_bootstrap = {
|
||||
"qsa", "QNX QSA Audio", QSA_Init, 0
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_QNX
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "../../SDL_internal.h"
|
||||
|
||||
#ifndef __SDL_QSA_AUDIO_H__
|
||||
#define __SDL_QSA_AUDIO_H__
|
||||
|
||||
#include <sys/asoundlib.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
snd_pcm_t *audio_handle; // The audio device handle
|
||||
int audio_fd; // The audio file descriptor, for selecting on
|
||||
SDL_bool timeout_on_wait; // Select timeout status
|
||||
Uint8 *pcm_buf; // Raw mixing buffer
|
||||
};
|
||||
|
||||
#endif // __SDL_QSA_AUDIO_H__
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_SNDIO
|
||||
|
||||
// OpenBSD sndio target
|
||||
|
||||
#ifdef HAVE_STDIO_H
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SIGNAL_H
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_sndioaudio.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
#endif
|
||||
|
||||
#ifndef INFTIM
|
||||
#define INFTIM -1
|
||||
#endif
|
||||
|
||||
#ifndef SIO_DEVANY
|
||||
#define SIO_DEVANY "default"
|
||||
#endif
|
||||
|
||||
static struct sio_hdl *(*SNDIO_sio_open)(const char *, unsigned int, int);
|
||||
static void (*SNDIO_sio_close)(struct sio_hdl *);
|
||||
static int (*SNDIO_sio_setpar)(struct sio_hdl *, struct sio_par *);
|
||||
static int (*SNDIO_sio_getpar)(struct sio_hdl *, struct sio_par *);
|
||||
static int (*SNDIO_sio_start)(struct sio_hdl *);
|
||||
static int (*SNDIO_sio_stop)(struct sio_hdl *);
|
||||
static size_t (*SNDIO_sio_read)(struct sio_hdl *, void *, size_t);
|
||||
static size_t (*SNDIO_sio_write)(struct sio_hdl *, const void *, size_t);
|
||||
static int (*SNDIO_sio_nfds)(struct sio_hdl *);
|
||||
static int (*SNDIO_sio_pollfd)(struct sio_hdl *, struct pollfd *, int);
|
||||
static int (*SNDIO_sio_revents)(struct sio_hdl *, struct pollfd *);
|
||||
static int (*SNDIO_sio_eof)(struct sio_hdl *);
|
||||
static void (*SNDIO_sio_initpar)(struct sio_par *);
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
static const char *sndio_library = SDL_AUDIO_DRIVER_SNDIO_DYNAMIC;
|
||||
static void *sndio_handle = NULL;
|
||||
|
||||
static int load_sndio_sym(const char *fn, void **addr)
|
||||
{
|
||||
*addr = SDL_LoadFunction(sndio_handle, fn);
|
||||
if (!*addr) {
|
||||
return 0; // Don't call SDL_SetError(): SDL_LoadFunction already did.
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// cast funcs to char* first, to please GCC's strict aliasing rules.
|
||||
#define SDL_SNDIO_SYM(x) \
|
||||
if (!load_sndio_sym(#x, (void **)(char *)&SNDIO_##x)) \
|
||||
return -1
|
||||
#else
|
||||
#define SDL_SNDIO_SYM(x) SNDIO_##x = x
|
||||
#endif
|
||||
|
||||
static int load_sndio_syms(void)
|
||||
{
|
||||
SDL_SNDIO_SYM(sio_open);
|
||||
SDL_SNDIO_SYM(sio_close);
|
||||
SDL_SNDIO_SYM(sio_setpar);
|
||||
SDL_SNDIO_SYM(sio_getpar);
|
||||
SDL_SNDIO_SYM(sio_start);
|
||||
SDL_SNDIO_SYM(sio_stop);
|
||||
SDL_SNDIO_SYM(sio_read);
|
||||
SDL_SNDIO_SYM(sio_write);
|
||||
SDL_SNDIO_SYM(sio_nfds);
|
||||
SDL_SNDIO_SYM(sio_pollfd);
|
||||
SDL_SNDIO_SYM(sio_revents);
|
||||
SDL_SNDIO_SYM(sio_eof);
|
||||
SDL_SNDIO_SYM(sio_initpar);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef SDL_SNDIO_SYM
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
|
||||
static void UnloadSNDIOLibrary(void)
|
||||
{
|
||||
if (sndio_handle) {
|
||||
SDL_UnloadObject(sndio_handle);
|
||||
sndio_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int LoadSNDIOLibrary(void)
|
||||
{
|
||||
int retval = 0;
|
||||
if (!sndio_handle) {
|
||||
sndio_handle = SDL_LoadObject(sndio_library);
|
||||
if (!sndio_handle) {
|
||||
retval = -1; // Don't call SDL_SetError(): SDL_LoadObject already did.
|
||||
} else {
|
||||
retval = load_sndio_syms();
|
||||
if (retval < 0) {
|
||||
UnloadSNDIOLibrary();
|
||||
}
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void UnloadSNDIOLibrary(void)
|
||||
{
|
||||
}
|
||||
|
||||
static int LoadSNDIOLibrary(void)
|
||||
{
|
||||
load_sndio_syms();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
|
||||
static int SNDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const SDL_bool iscapture = device->iscapture;
|
||||
|
||||
while (!SDL_AtomicGet(&device->shutdown)) {
|
||||
if (SNDIO_sio_eof(device->hidden->dev)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const int nfds = SNDIO_sio_pollfd(device->hidden->dev, device->hidden->pfd, iscapture ? POLLIN : POLLOUT);
|
||||
if (nfds <= 0 || poll(device->hidden->pfd, nfds, 10) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const int revents = SNDIO_sio_revents(device->hidden->dev, device->hidden->pfd);
|
||||
if (iscapture && (revents & POLLIN)) {
|
||||
break;
|
||||
} else if (!iscapture && (revents & POLLOUT)) {
|
||||
break;
|
||||
} else if (revents & POLLHUP) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int SNDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
// !!! FIXME: this should be non-blocking so we can check device->shutdown.
|
||||
// this is set to blocking, because we _have_ to send the entire buffer down, but hopefully WaitDevice took most of the delay time.
|
||||
if (SNDIO_sio_write(device->hidden->dev, buffer, buflen) != buflen) {
|
||||
return -1; // If we couldn't write, assume fatal error for now
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Wrote %d bytes of audio data\n", written);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int SNDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
// We set capture devices non-blocking; this can safely return 0 in SDL3, but we'll check for EOF to cause a device disconnect.
|
||||
const size_t br = SNDIO_sio_read(device->hidden->dev, buffer, buflen);
|
||||
if ((br == 0) && SNDIO_sio_eof(device->hidden->dev)) {
|
||||
return -1;
|
||||
}
|
||||
return (int) br;
|
||||
}
|
||||
|
||||
static void SNDIO_FlushCapture(SDL_AudioDevice *device)
|
||||
{
|
||||
char buf[512];
|
||||
while (!SDL_AtomicGet(&device->shutdown) && (SNDIO_sio_read(device->hidden->dev, buf, sizeof(buf)) > 0)) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
static Uint8 *SNDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static void SNDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->dev) {
|
||||
SNDIO_sio_stop(device->hidden->dev);
|
||||
SNDIO_sio_close(device->hidden->dev);
|
||||
}
|
||||
SDL_free(device->hidden->pfd);
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int SNDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// !!! FIXME: we really should standardize this on a specific SDL hint.
|
||||
const char *audiodev = SDL_getenv("AUDIODEV");
|
||||
|
||||
// Capture devices must be non-blocking for SNDIO_FlushCapture
|
||||
device->hidden->dev = SNDIO_sio_open(audiodev ? audiodev : SIO_DEVANY,
|
||||
device->iscapture ? SIO_REC : SIO_PLAY, device->iscapture);
|
||||
if (!device->hidden->dev) {
|
||||
return SDL_SetError("sio_open() failed");
|
||||
}
|
||||
|
||||
device->hidden->pfd = SDL_malloc(sizeof(struct pollfd) * SNDIO_sio_nfds(device->hidden->dev));
|
||||
if (!device->hidden->pfd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sio_par par;
|
||||
SNDIO_sio_initpar(&par);
|
||||
|
||||
par.rate = device->spec.freq;
|
||||
par.pchan = device->spec.channels;
|
||||
par.round = device->sample_frames;
|
||||
par.appbufsz = par.round * 2;
|
||||
|
||||
// Try for a closest match on audio format
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (!SDL_AUDIO_ISFLOAT(test_format)) {
|
||||
par.le = SDL_AUDIO_ISLITTLEENDIAN(test_format) ? 1 : 0;
|
||||
par.sig = SDL_AUDIO_ISSIGNED(test_format) ? 1 : 0;
|
||||
par.bits = SDL_AUDIO_BITSIZE(test_format);
|
||||
|
||||
if (SNDIO_sio_setpar(device->hidden->dev, &par) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (SNDIO_sio_getpar(device->hidden->dev, &par) == 0) {
|
||||
return SDL_SetError("sio_getpar() failed");
|
||||
}
|
||||
if (par.bps != SIO_BPS(par.bits)) {
|
||||
continue;
|
||||
}
|
||||
if ((par.bits == 8 * par.bps) || (par.msb)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("sndio: Unsupported audio format");
|
||||
}
|
||||
|
||||
if ((par.bps == 4) && (par.sig) && (par.le)) {
|
||||
device->spec.format = SDL_AUDIO_S32LE;
|
||||
} else if ((par.bps == 4) && (par.sig) && (!par.le)) {
|
||||
device->spec.format = SDL_AUDIO_S32BE;
|
||||
} else if ((par.bps == 2) && (par.sig) && (par.le)) {
|
||||
device->spec.format = SDL_AUDIO_S16LE;
|
||||
} else if ((par.bps == 2) && (par.sig) && (!par.le)) {
|
||||
device->spec.format = SDL_AUDIO_S16BE;
|
||||
} else if ((par.bps == 1) && (par.sig)) {
|
||||
device->spec.format = SDL_AUDIO_S8;
|
||||
} else if ((par.bps == 1) && (!par.sig)) {
|
||||
device->spec.format = SDL_AUDIO_U8;
|
||||
} else {
|
||||
return SDL_SetError("sndio: Got unsupported hardware audio format.");
|
||||
}
|
||||
|
||||
device->spec.freq = par.rate;
|
||||
device->spec.channels = par.pchan;
|
||||
device->sample_frames = par.round;
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
// Allocate mixing buffer
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return -1;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
|
||||
if (!SNDIO_sio_start(device->hidden->dev)) {
|
||||
return SDL_SetError("sio_start() failed");
|
||||
}
|
||||
|
||||
return 0; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static void SNDIO_Deinitialize(void)
|
||||
{
|
||||
UnloadSNDIOLibrary();
|
||||
}
|
||||
|
||||
static void SNDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
|
||||
{
|
||||
*default_output = SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1);
|
||||
*default_capture = SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2);
|
||||
}
|
||||
|
||||
static SDL_bool SNDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (LoadSNDIOLibrary() < 0) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
impl->OpenDevice = SNDIO_OpenDevice;
|
||||
impl->WaitDevice = SNDIO_WaitDevice;
|
||||
impl->PlayDevice = SNDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = SNDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = SNDIO_CloseDevice;
|
||||
impl->WaitCaptureDevice = SNDIO_WaitDevice;
|
||||
impl->CaptureFromDevice = SNDIO_CaptureFromDevice;
|
||||
impl->FlushCapture = SNDIO_FlushCapture;
|
||||
impl->Deinitialize = SNDIO_Deinitialize;
|
||||
impl->DetectDevices = SNDIO_DetectDevices;
|
||||
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap SNDIO_bootstrap = {
|
||||
"sndio", "OpenBSD sndio", SNDIO_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_SNDIO
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_sndioaudio_h_
|
||||
#define SDL_sndioaudio_h_
|
||||
|
||||
#include <poll.h>
|
||||
#include <sndio.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
struct sio_hdl *dev; // The audio device handle
|
||||
Uint8 *mixbuf; // Raw mixing buffer
|
||||
struct pollfd *pfd; // Polling structures for non-blocking sndio devices
|
||||
};
|
||||
|
||||
#endif // SDL_sndioaudio_h_
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_VITA
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../SDL_audiodev_c.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_vitaaudio.h"
|
||||
|
||||
#include <psp2/kernel/threadmgr.h>
|
||||
#include <psp2/audioout.h>
|
||||
#include <psp2/audioin.h>
|
||||
|
||||
#define SCE_AUDIO_SAMPLE_ALIGN(s) (((s) + 63) & ~63)
|
||||
#define SCE_AUDIO_MAX_VOLUME 0x8000
|
||||
|
||||
static int VITAAUD_OpenCaptureDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->spec.freq = 16000;
|
||||
device->spec.channels = 1;
|
||||
device->sample_frames = 512;
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
device->hidden->port = sceAudioInOpenPort(SCE_AUDIO_IN_PORT_TYPE_VOICE, 512, 16000, SCE_AUDIO_IN_PARAM_FORMAT_S16_MONO);
|
||||
|
||||
if (device->hidden->port < 0) {
|
||||
return SDL_SetError("Couldn't open audio in port: %x", device->hidden->port);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int VITAAUD_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
int format, mixlen, i, port = SCE_AUDIO_OUT_PORT_TYPE_MAIN;
|
||||
int vols[2] = { SCE_AUDIO_MAX_VOLUME, SCE_AUDIO_MAX_VOLUME };
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts;
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *)
|
||||
SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (test_format == SDL_AUDIO_S16LE) {
|
||||
device->spec.format = test_format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("Unsupported audio format");
|
||||
}
|
||||
|
||||
if (device->iscapture) {
|
||||
return VITAAUD_OpenCaptureDevice(device);
|
||||
}
|
||||
|
||||
// The sample count must be a multiple of 64.
|
||||
device->sample_frames = SCE_AUDIO_SAMPLE_ALIGN(device->sample_frames);
|
||||
|
||||
// Update the fragment size as size in bytes.
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
/* Allocate the mixing buffer. Its size and starting address must
|
||||
be a multiple of 64 bytes. Our sample count is already a multiple of
|
||||
64, so spec->size should be a multiple of 64 as well. */
|
||||
mixlen = device->buffer_size * NUM_BUFFERS;
|
||||
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
|
||||
if (!device->hidden->rawbuf) {
|
||||
return SDL_SetError("Couldn't allocate mixing buffer");
|
||||
}
|
||||
|
||||
// Setup the hardware channel.
|
||||
if (device->spec.channels == 1) {
|
||||
format = SCE_AUDIO_OUT_MODE_MONO;
|
||||
} else {
|
||||
format = SCE_AUDIO_OUT_MODE_STEREO;
|
||||
}
|
||||
|
||||
// the main port requires 48000Hz audio, so this drops to the background music port if necessary
|
||||
if (device->spec.freq < 48000) {
|
||||
port = SCE_AUDIO_OUT_PORT_TYPE_BGM;
|
||||
}
|
||||
|
||||
device->hidden->port = sceAudioOutOpenPort(port, device->sample_frames, device->spec.freq, format);
|
||||
if (device->hidden->port < 0) {
|
||||
SDL_aligned_free(device->hidden->rawbuf);
|
||||
device->hidden->rawbuf = NULL;
|
||||
return SDL_SetError("Couldn't open audio out port: %x", device->hidden->port);
|
||||
}
|
||||
|
||||
sceAudioOutSetVolume(device->hidden->port, SCE_AUDIO_VOLUME_FLAG_L_CH | SCE_AUDIO_VOLUME_FLAG_R_CH, vols);
|
||||
|
||||
SDL_memset(device->hidden->rawbuf, 0, mixlen);
|
||||
for (i = 0; i < NUM_BUFFERS; i++) {
|
||||
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
|
||||
}
|
||||
|
||||
device->hidden->next_buffer = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
return (sceAudioOutOutput(device->hidden->port, buffer) == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
// This function waits until it is possible to write a full sound buffer
|
||||
static int VITAAUD_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// !!! FIXME: we might just need to sleep roughly as long as playback buffers take to process, based on sample rate, etc.
|
||||
while (!SDL_AtomicGet(&device->shutdown) && (sceAudioOutGetRestSample(device->hidden->port) >= device->buffer_size)) {
|
||||
SDL_Delay(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
Uint8 *retval = device->hidden->mixbufs[device->hidden->next_buffer];
|
||||
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void VITAAUD_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->port >= 0) {
|
||||
if (device->iscapture) {
|
||||
sceAudioInReleasePort(device->hidden->port);
|
||||
} else {
|
||||
sceAudioOutReleasePort(device->hidden->port);
|
||||
}
|
||||
device->hidden->port = -1;
|
||||
}
|
||||
|
||||
if (!device->iscapture && device->hidden->rawbuf) {
|
||||
SDL_aligned_free(device->hidden->rawbuf); // this uses SDL_aligned_alloc(), not SDL_malloc()
|
||||
device->hidden->rawbuf = NULL;
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int VITAAUD_WaitCaptureDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// there's only a blocking call to obtain more data, so we'll just sleep as
|
||||
// long as a buffer would run.
|
||||
const Uint64 endticks = SDL_GetTicks() + ((device->sample_frames * 1000) / device->spec.freq);
|
||||
while (!SDL_AtomicGet(&device->shutdown) && (SDL_GetTicks() < endticks)) {
|
||||
SDL_Delay(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int VITAAUD_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
int ret;
|
||||
SDL_assert(buflen == device->buffer_size);
|
||||
ret = sceAudioInInput(device->hidden->port, buffer);
|
||||
if (ret < 0) {
|
||||
return SDL_SetError("Failed to capture from device: %x", ret);
|
||||
}
|
||||
return device->buffer_size;
|
||||
}
|
||||
|
||||
static void VITAAUD_FlushCapture(SDL_AudioDevice *device)
|
||||
{
|
||||
// just grab the latest and dump it.
|
||||
sceAudioInInput(device->hidden->port, device->work_buffer);
|
||||
}
|
||||
|
||||
static void VITAAUD_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
// Increase the priority of this audio thread by 1 to put it ahead of other SDL threads.
|
||||
SceUID thid;
|
||||
SceKernelThreadInfo info;
|
||||
thid = sceKernelGetThreadId();
|
||||
info.size = sizeof(SceKernelThreadInfo);
|
||||
if (sceKernelGetThreadInfo(thid, &info) == 0) {
|
||||
sceKernelChangeThreadPriority(thid, info.currentPriority - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_bool VITAAUD_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = VITAAUD_OpenDevice;
|
||||
impl->PlayDevice = VITAAUD_PlayDevice;
|
||||
impl->WaitDevice = VITAAUD_WaitDevice;
|
||||
impl->GetDeviceBuf = VITAAUD_GetDeviceBuf;
|
||||
impl->CloseDevice = VITAAUD_CloseDevice;
|
||||
impl->ThreadInit = VITAAUD_ThreadInit;
|
||||
impl->WaitCaptureDevice = VITAAUD_WaitCaptureDevice;
|
||||
impl->FlushCapture = VITAAUD_FlushCapture;
|
||||
impl->CaptureFromDevice = VITAAUD_CaptureFromDevice;
|
||||
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
|
||||
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap VITAAUD_bootstrap = {
|
||||
"vita", "VITA audio driver", VITAAUD_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_VITA
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_vitaaudio_h
|
||||
#define SDL_vitaaudio_h
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#define NUM_BUFFERS 2
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The hardware input/output port.
|
||||
int port;
|
||||
// The raw allocated mixing buffer.
|
||||
Uint8 *rawbuf;
|
||||
// Individual mixing buffers.
|
||||
Uint8 *mixbufs[NUM_BUFFERS];
|
||||
// Index of the next available mixing buffer.
|
||||
int next_buffer;
|
||||
};
|
||||
|
||||
#endif // SDL_vitaaudio_h
|
||||
@@ -0,0 +1,796 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_WASAPI
|
||||
|
||||
#include "../../core/windows/SDL_windows.h"
|
||||
#include "../../core/windows/SDL_immdevice.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#define COBJMACROS
|
||||
#include <audioclient.h>
|
||||
|
||||
#include "SDL_wasapi.h"
|
||||
|
||||
// These constants aren't available in older SDKs
|
||||
#ifndef AUDCLNT_STREAMFLAGS_RATEADJUST
|
||||
#define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000
|
||||
#endif
|
||||
#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
|
||||
#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
|
||||
#endif
|
||||
#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
|
||||
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
|
||||
#endif
|
||||
|
||||
// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
|
||||
static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
|
||||
static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } };
|
||||
|
||||
|
||||
// WASAPI is _really_ particular about various things happening on the same thread, for COM and such,
|
||||
// so we proxy various stuff to a single background thread to manage.
|
||||
|
||||
typedef struct ManagementThreadPendingTask
|
||||
{
|
||||
ManagementThreadTask fn;
|
||||
void *userdata;
|
||||
int result;
|
||||
SDL_Semaphore *task_complete_sem;
|
||||
char *errorstr;
|
||||
struct ManagementThreadPendingTask *next;
|
||||
} ManagementThreadPendingTask;
|
||||
|
||||
static SDL_Thread *ManagementThread = NULL;
|
||||
static ManagementThreadPendingTask *ManagementThreadPendingTasks = NULL;
|
||||
static SDL_Mutex *ManagementThreadLock = NULL;
|
||||
static SDL_Condition *ManagementThreadCondition = NULL;
|
||||
static SDL_AtomicInt ManagementThreadShutdown;
|
||||
|
||||
static void ManagementThreadMainloop(void)
|
||||
{
|
||||
SDL_LockMutex(ManagementThreadLock);
|
||||
ManagementThreadPendingTask *task;
|
||||
while (((task = (ManagementThreadPendingTask *)SDL_AtomicGetPtr((void **)&ManagementThreadPendingTasks)) != NULL) || !SDL_AtomicGet(&ManagementThreadShutdown)) {
|
||||
if (!task) {
|
||||
SDL_WaitCondition(ManagementThreadCondition, ManagementThreadLock); // block until there's something to do.
|
||||
} else {
|
||||
SDL_AtomicSetPtr((void **) &ManagementThreadPendingTasks, task->next); // take task off the pending list.
|
||||
SDL_UnlockMutex(ManagementThreadLock); // let other things add to the list while we chew on this task.
|
||||
task->result = task->fn(task->userdata); // run this task.
|
||||
if (task->task_complete_sem) { // something waiting on result?
|
||||
task->errorstr = SDL_strdup(SDL_GetError());
|
||||
SDL_PostSemaphore(task->task_complete_sem);
|
||||
} else { // nothing waiting, we're done, free it.
|
||||
SDL_free(task);
|
||||
}
|
||||
SDL_LockMutex(ManagementThreadLock); // regrab the lock so we can get the next task; if nothing to do, we'll release the lock in SDL_WaitCondition.
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(ManagementThreadLock); // told to shut down and out of tasks, let go of the lock and return.
|
||||
}
|
||||
|
||||
int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_on_result)
|
||||
{
|
||||
// We want to block for a result, but we are already running from the management thread! Just run the task now so we don't deadlock.
|
||||
if ((wait_on_result) && (SDL_GetCurrentThreadID() == SDL_GetThreadID(ManagementThread))) {
|
||||
*wait_on_result = task(userdata);
|
||||
return 0; // completed!
|
||||
}
|
||||
|
||||
if (SDL_AtomicGet(&ManagementThreadShutdown)) {
|
||||
return SDL_SetError("Can't add task, we're shutting down");
|
||||
}
|
||||
|
||||
ManagementThreadPendingTask *pending = (ManagementThreadPendingTask *)SDL_calloc(1, sizeof(ManagementThreadPendingTask));
|
||||
if (!pending) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
pending->fn = task;
|
||||
pending->userdata = userdata;
|
||||
|
||||
if (wait_on_result) {
|
||||
pending->task_complete_sem = SDL_CreateSemaphore(0);
|
||||
if (!pending->task_complete_sem) {
|
||||
SDL_free(pending);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
pending->next = NULL;
|
||||
|
||||
SDL_LockMutex(ManagementThreadLock);
|
||||
|
||||
// add to end of task list.
|
||||
ManagementThreadPendingTask *prev = NULL;
|
||||
for (ManagementThreadPendingTask *i = (ManagementThreadPendingTask *)SDL_AtomicGetPtr((void **)&ManagementThreadPendingTasks); i; i = i->next) {
|
||||
prev = i;
|
||||
}
|
||||
|
||||
if (prev) {
|
||||
prev->next = pending;
|
||||
} else {
|
||||
SDL_AtomicSetPtr((void **) &ManagementThreadPendingTasks, pending);
|
||||
}
|
||||
|
||||
// task is added to the end of the pending list, let management thread rip!
|
||||
SDL_SignalCondition(ManagementThreadCondition);
|
||||
SDL_UnlockMutex(ManagementThreadLock);
|
||||
|
||||
if (wait_on_result) {
|
||||
SDL_WaitSemaphore(pending->task_complete_sem);
|
||||
SDL_DestroySemaphore(pending->task_complete_sem);
|
||||
*wait_on_result = pending->result;
|
||||
if (pending->errorstr) {
|
||||
SDL_SetError("%s", pending->errorstr);
|
||||
SDL_free(pending->errorstr);
|
||||
}
|
||||
SDL_free(pending);
|
||||
}
|
||||
|
||||
return 0; // successfully added (and possibly executed)!
|
||||
}
|
||||
|
||||
static int ManagementThreadPrepare(void)
|
||||
{
|
||||
if (WASAPI_PlatformInit() == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ManagementThreadLock = SDL_CreateMutex();
|
||||
if (!ManagementThreadLock) {
|
||||
WASAPI_PlatformDeinit();
|
||||
return -1;
|
||||
}
|
||||
|
||||
ManagementThreadCondition = SDL_CreateCondition();
|
||||
if (!ManagementThreadCondition) {
|
||||
SDL_DestroyMutex(ManagementThreadLock);
|
||||
ManagementThreadLock = NULL;
|
||||
WASAPI_PlatformDeinit();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *errorstr;
|
||||
SDL_Semaphore *ready_sem;
|
||||
} ManagementThreadEntryData;
|
||||
|
||||
static int ManagementThreadEntry(void *userdata)
|
||||
{
|
||||
ManagementThreadEntryData *data = (ManagementThreadEntryData *)userdata;
|
||||
|
||||
if (ManagementThreadPrepare() < 0) {
|
||||
data->errorstr = SDL_strdup(SDL_GetError());
|
||||
SDL_PostSemaphore(data->ready_sem); // unblock calling thread.
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_PostSemaphore(data->ready_sem); // unblock calling thread.
|
||||
ManagementThreadMainloop();
|
||||
|
||||
WASAPI_PlatformDeinit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int InitManagementThread(void)
|
||||
{
|
||||
ManagementThreadEntryData mgmtdata;
|
||||
SDL_zero(mgmtdata);
|
||||
mgmtdata.ready_sem = SDL_CreateSemaphore(0);
|
||||
if (!mgmtdata.ready_sem) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_AtomicSetPtr((void **) &ManagementThreadPendingTasks, NULL);
|
||||
SDL_AtomicSet(&ManagementThreadShutdown, 0);
|
||||
ManagementThread = SDL_CreateThreadInternal(ManagementThreadEntry, "SDLWASAPIMgmt", 256 * 1024, &mgmtdata); // !!! FIXME: maybe even smaller stack size?
|
||||
if (!ManagementThread) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_WaitSemaphore(mgmtdata.ready_sem);
|
||||
SDL_DestroySemaphore(mgmtdata.ready_sem);
|
||||
|
||||
if (mgmtdata.errorstr) {
|
||||
SDL_WaitThread(ManagementThread, NULL);
|
||||
ManagementThread = NULL;
|
||||
SDL_SetError("%s", mgmtdata.errorstr);
|
||||
SDL_free(mgmtdata.errorstr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void DeinitManagementThread(void)
|
||||
{
|
||||
if (ManagementThread) {
|
||||
SDL_AtomicSet(&ManagementThreadShutdown, 1);
|
||||
SDL_LockMutex(ManagementThreadLock);
|
||||
SDL_SignalCondition(ManagementThreadCondition);
|
||||
SDL_UnlockMutex(ManagementThreadLock);
|
||||
SDL_WaitThread(ManagementThread, NULL);
|
||||
ManagementThread = NULL;
|
||||
}
|
||||
|
||||
SDL_assert(SDL_AtomicGetPtr((void **) &ManagementThreadPendingTasks) == NULL);
|
||||
|
||||
SDL_DestroyCondition(ManagementThreadCondition);
|
||||
SDL_DestroyMutex(ManagementThreadLock);
|
||||
ManagementThreadCondition = NULL;
|
||||
ManagementThreadLock = NULL;
|
||||
SDL_AtomicSet(&ManagementThreadShutdown, 0);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SDL_AudioDevice **default_output;
|
||||
SDL_AudioDevice **default_capture;
|
||||
} mgmtthrtask_DetectDevicesData;
|
||||
|
||||
static int mgmtthrtask_DetectDevices(void *userdata)
|
||||
{
|
||||
mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata;
|
||||
WASAPI_EnumerateEndpoints(data->default_output, data->default_capture);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void WASAPI_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
|
||||
{
|
||||
int rc;
|
||||
// this blocks because it needs to finish before the audio subsystem inits
|
||||
mgmtthrtask_DetectDevicesData data;
|
||||
data.default_output = default_output;
|
||||
data.default_capture = default_capture;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc);
|
||||
}
|
||||
|
||||
static int mgmtthrtask_DisconnectDevice(void *userdata)
|
||||
{
|
||||
SDL_AudioDeviceDisconnected((SDL_AudioDevice *)userdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WASAPI_DisconnectDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
int rc; // block on this; don't disconnect while holding the device lock!
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, &rc);
|
||||
}
|
||||
|
||||
static SDL_bool WasapiFailed(SDL_AudioDevice *device, const HRESULT err)
|
||||
{
|
||||
if (err == S_OK) {
|
||||
return SDL_FALSE;
|
||||
} else if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
|
||||
device->hidden->device_lost = SDL_TRUE;
|
||||
} else {
|
||||
device->hidden->device_dead = SDL_TRUE;
|
||||
}
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
static int mgmtthrtask_StopAndReleaseClient(void *userdata)
|
||||
{
|
||||
IAudioClient *client = (IAudioClient *) userdata;
|
||||
IAudioClient_Stop(client);
|
||||
IAudioClient_Release(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mgmtthrtask_ReleaseCaptureClient(void *userdata)
|
||||
{
|
||||
IAudioCaptureClient_Release((IAudioCaptureClient *)userdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mgmtthrtask_ReleaseRenderClient(void *userdata)
|
||||
{
|
||||
IAudioRenderClient_Release((IAudioRenderClient *)userdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mgmtthrtask_CoTaskMemFree(void *userdata)
|
||||
{
|
||||
CoTaskMemFree(userdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mgmtthrtask_PlatformDeleteActivationHandler(void *userdata)
|
||||
{
|
||||
WASAPI_PlatformDeleteActivationHandler(userdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mgmtthrtask_CloseHandle(void *userdata)
|
||||
{
|
||||
CloseHandle((HANDLE) userdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ResetWasapiDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (!device || !device->hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
// just queue up all the tasks in the management thread and don't block.
|
||||
// We don't care when any of these actually get free'd.
|
||||
|
||||
if (device->hidden->client) {
|
||||
IAudioClient *client = device->hidden->client;
|
||||
device->hidden->client = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_StopAndReleaseClient, client, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->render) {
|
||||
IAudioRenderClient *render = device->hidden->render;
|
||||
device->hidden->render = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseRenderClient, render, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->capture) {
|
||||
IAudioCaptureClient *capture = device->hidden->capture;
|
||||
device->hidden->capture = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseCaptureClient, capture, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->waveformat) {
|
||||
void *ptr = device->hidden->waveformat;
|
||||
device->hidden->waveformat = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_CoTaskMemFree, ptr, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->activation_handler) {
|
||||
void *activation_handler = device->hidden->activation_handler;
|
||||
device->hidden->activation_handler = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_PlatformDeleteActivationHandler, activation_handler, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->event) {
|
||||
HANDLE event = device->hidden->event;
|
||||
device->hidden->event = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_CloseHandle, (void *) event, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static int mgmtthrtask_ActivateDevice(void *userdata)
|
||||
{
|
||||
return WASAPI_ActivateDevice((SDL_AudioDevice *)userdata);
|
||||
}
|
||||
|
||||
static int ActivateWasapiDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// this blocks because we're either being notified from a background thread or we're running during device open,
|
||||
// both of which won't deadlock vs the device thread.
|
||||
int rc = -1;
|
||||
return ((WASAPI_ProxyToManagementThread(mgmtthrtask_ActivateDevice, device, &rc) < 0) || (rc < 0)) ? -1 : 0;
|
||||
}
|
||||
|
||||
// do not call when holding the device lock!
|
||||
static SDL_bool RecoverWasapiDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
ResetWasapiDevice(device); // dump the lost device's handles.
|
||||
|
||||
// This handles a non-default device that simply had its format changed in the Windows Control Panel.
|
||||
if (ActivateWasapiDevice(device) == -1) {
|
||||
WASAPI_DisconnectDevice(device);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
device->hidden->device_lost = SDL_FALSE;
|
||||
|
||||
return SDL_TRUE; // okay, carry on with new device details!
|
||||
}
|
||||
|
||||
// do not call when holding the device lock!
|
||||
static SDL_bool RecoverWasapiIfLost(SDL_AudioDevice *device)
|
||||
{
|
||||
if (SDL_AtomicGet(&device->shutdown)) {
|
||||
return SDL_FALSE; // already failed.
|
||||
} else if (device->hidden->device_dead) { // had a fatal error elsewhere, clean up and quit
|
||||
IAudioClient_Stop(device->hidden->client);
|
||||
WASAPI_DisconnectDevice(device);
|
||||
SDL_assert(SDL_AtomicGet(&device->shutdown)); // so we don't come back through here.
|
||||
return SDL_FALSE; // already failed.
|
||||
} else if (SDL_AtomicGet(&device->zombie)) {
|
||||
return SDL_FALSE; // we're already dead, so just leave and let the Zombie implementations take over.
|
||||
} else if (!device->hidden->client) {
|
||||
return SDL_TRUE; // still waiting for activation.
|
||||
}
|
||||
|
||||
return device->hidden->device_lost ? RecoverWasapiDevice(device) : SDL_TRUE;
|
||||
}
|
||||
|
||||
static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
// get an endpoint buffer from WASAPI.
|
||||
BYTE *buffer = NULL;
|
||||
|
||||
if (device->hidden->render) {
|
||||
const HRESULT ret = IAudioRenderClient_GetBuffer(device->hidden->render, device->sample_frames, &buffer);
|
||||
if (ret == AUDCLNT_E_BUFFER_TOO_LARGE) {
|
||||
SDL_assert(buffer == NULL);
|
||||
*buffer_size = 0; // just go back to WaitDevice and try again after the hardware has consumed some more data.
|
||||
} else if (WasapiFailed(device, ret)) {
|
||||
SDL_assert(buffer == NULL);
|
||||
if (device->hidden->device_lost) { // just use an available buffer, we won't be playing it anyhow.
|
||||
*buffer_size = 0; // we'll recover during WaitDevice and try again.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (Uint8 *)buffer;
|
||||
}
|
||||
|
||||
static int WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
if (device->hidden->render) { // definitely activated?
|
||||
// WasapiFailed() will mark the device for reacquisition or removal elsewhere.
|
||||
WasapiFailed(device, IAudioRenderClient_ReleaseBuffer(device->hidden->render, device->sample_frames, 0));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int WASAPI_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// WaitDevice does not hold the device lock, so check for recovery/disconnect details here.
|
||||
while (RecoverWasapiIfLost(device) && device->hidden->client && device->hidden->event) {
|
||||
DWORD waitResult = WaitForSingleObjectEx(device->hidden->event, 200, FALSE);
|
||||
if (waitResult == WAIT_OBJECT_0) {
|
||||
const UINT32 maxpadding = device->sample_frames;
|
||||
UINT32 padding = 0;
|
||||
if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
|
||||
//SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);*/
|
||||
if (device->iscapture && (padding > 0)) {
|
||||
break;
|
||||
} else if (!device->iscapture && (padding <= maxpadding)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (waitResult != WAIT_TIMEOUT) {
|
||||
//SDL_Log("WASAPI FAILED EVENT!");*/
|
||||
IAudioClient_Stop(device->hidden->client);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int WASAPI_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
BYTE *ptr = NULL;
|
||||
UINT32 frames = 0;
|
||||
DWORD flags = 0;
|
||||
|
||||
while (device->hidden->capture) {
|
||||
const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
|
||||
if (ret == AUDCLNT_S_BUFFER_EMPTY) {
|
||||
return 0; // in theory we should have waited until there was data, but oh well, we'll go back to waiting. Returning 0 is safe in SDL3.
|
||||
}
|
||||
|
||||
WasapiFailed(device, ret); // mark device lost/failed if necessary.
|
||||
|
||||
if (ret == S_OK) {
|
||||
const int total = ((int)frames) * device->hidden->framesize;
|
||||
const int cpy = SDL_min(buflen, total);
|
||||
const int leftover = total - cpy;
|
||||
const SDL_bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? SDL_TRUE : SDL_FALSE;
|
||||
|
||||
SDL_assert(leftover == 0); // according to MSDN, this isn't everything available, just one "packet" of data per-GetBuffer call.
|
||||
|
||||
if (silent) {
|
||||
SDL_memset(buffer, device->silence_value, cpy);
|
||||
} else {
|
||||
SDL_memcpy(buffer, ptr, cpy);
|
||||
}
|
||||
|
||||
WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames));
|
||||
|
||||
return cpy;
|
||||
}
|
||||
}
|
||||
|
||||
return -1; // unrecoverable error.
|
||||
}
|
||||
|
||||
static void WASAPI_FlushCapture(SDL_AudioDevice *device)
|
||||
{
|
||||
BYTE *ptr = NULL;
|
||||
UINT32 frames = 0;
|
||||
DWORD flags = 0;
|
||||
|
||||
// just read until we stop getting packets, throwing them away.
|
||||
while (!SDL_AtomicGet(&device->shutdown) && device->hidden->capture) {
|
||||
const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
|
||||
if (ret == AUDCLNT_S_BUFFER_EMPTY) {
|
||||
break; // no more buffered data; we're done.
|
||||
} else if (WasapiFailed(device, ret)) {
|
||||
break; // failed for some other reason, abort.
|
||||
} else if (WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames))) {
|
||||
break; // something broke.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void WASAPI_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
ResetWasapiDevice(device);
|
||||
SDL_free(device->hidden->devid);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int mgmtthrtask_PrepDevice(void *userdata)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *)userdata;
|
||||
|
||||
/* !!! FIXME: we could request an exclusive mode stream, which is lower latency;
|
||||
!!! it will write into the kernel's audio buffer directly instead of
|
||||
!!! shared memory that a user-mode mixer then writes to the kernel with
|
||||
!!! everything else. Doing this means any other sound using this device will
|
||||
!!! stop playing, including the user's MP3 player and system notification
|
||||
!!! sounds. You'd probably need to release the device when the app isn't in
|
||||
!!! the foreground, to be a good citizen of the system. It's doable, but it's
|
||||
!!! more work and causes some annoyances, and I don't know what the latency
|
||||
!!! wins actually look like. Maybe add a hint to force exclusive mode at
|
||||
!!! some point. To be sure, defaulting to shared mode is the right thing to
|
||||
!!! do in any case. */
|
||||
const AUDCLNT_SHAREMODE sharemode = AUDCLNT_SHAREMODE_SHARED;
|
||||
|
||||
IAudioClient *client = device->hidden->client;
|
||||
SDL_assert(client != NULL);
|
||||
|
||||
#if defined(SDL_PLATFORM_WINRT) || defined(SDL_PLATFORM_GDK) // CreateEventEx() arrived in Vista, so we need an #ifdef for XP.
|
||||
device->hidden->event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
|
||||
#else
|
||||
device->hidden->event = CreateEventW(NULL, 0, 0, NULL);
|
||||
#endif
|
||||
|
||||
if (!device->hidden->event) {
|
||||
return WIN_SetError("WASAPI can't create an event handle");
|
||||
}
|
||||
|
||||
HRESULT ret;
|
||||
|
||||
WAVEFORMATEX *waveformat = NULL;
|
||||
ret = IAudioClient_GetMixFormat(client, &waveformat);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't determine mix format", ret);
|
||||
}
|
||||
SDL_assert(waveformat != NULL);
|
||||
device->hidden->waveformat = waveformat;
|
||||
|
||||
SDL_AudioSpec newspec;
|
||||
newspec.channels = (Uint8)waveformat->nChannels;
|
||||
|
||||
// Make sure we have a valid format that we can convert to whatever WASAPI wants.
|
||||
const SDL_AudioFormat wasapi_format = SDL_WaveFormatExToSDLFormat(waveformat);
|
||||
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (test_format == wasapi_format) {
|
||||
newspec.format = test_format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("%s: Unsupported audio format", "wasapi");
|
||||
}
|
||||
|
||||
REFERENCE_TIME default_period = 0;
|
||||
ret = IAudioClient_GetDevicePeriod(client, &default_period, NULL);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't determine minimum device period", ret);
|
||||
}
|
||||
|
||||
DWORD streamflags = 0;
|
||||
|
||||
/* we've gotten reports that WASAPI's resampler introduces distortions, but in the short term
|
||||
it fixes some other WASAPI-specific quirks we haven't quite tracked down.
|
||||
Refer to bug #6326 for the immediate concern. */
|
||||
#if 1
|
||||
// favor WASAPI's resampler over our own
|
||||
if ((DWORD)device->spec.freq != waveformat->nSamplesPerSec) {
|
||||
streamflags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY);
|
||||
waveformat->nSamplesPerSec = device->spec.freq;
|
||||
waveformat->nAvgBytesPerSec = waveformat->nSamplesPerSec * waveformat->nChannels * (waveformat->wBitsPerSample / 8);
|
||||
}
|
||||
#endif
|
||||
|
||||
newspec.freq = waveformat->nSamplesPerSec;
|
||||
|
||||
streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
|
||||
ret = IAudioClient_Initialize(client, sharemode, streamflags, 0, 0, waveformat, NULL);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't initialize audio client", ret);
|
||||
}
|
||||
|
||||
ret = IAudioClient_SetEventHandle(client, device->hidden->event);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't set event handle", ret);
|
||||
}
|
||||
|
||||
UINT32 bufsize = 0; // this is in sample frames, not samples, not bytes.
|
||||
ret = IAudioClient_GetBufferSize(client, &bufsize);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't determine buffer size", ret);
|
||||
}
|
||||
|
||||
// Match the callback size to the period size to cut down on the number of
|
||||
// interrupts waited for in each call to WaitDevice
|
||||
const float period_millis = default_period / 10000.0f;
|
||||
const float period_frames = period_millis * newspec.freq / 1000.0f;
|
||||
int new_sample_frames = (int) SDL_ceilf(period_frames);
|
||||
|
||||
// regardless of what we calculated for the period size, clamp it to the expected hardware buffer size.
|
||||
if (new_sample_frames > (int) bufsize) {
|
||||
new_sample_frames = (int) bufsize;
|
||||
}
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
if (SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
device->hidden->framesize = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
|
||||
if (device->iscapture) {
|
||||
IAudioCaptureClient *capture = NULL;
|
||||
ret = IAudioClient_GetService(client, &SDL_IID_IAudioCaptureClient, (void **)&capture);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't get capture client service", ret);
|
||||
}
|
||||
|
||||
SDL_assert(capture != NULL);
|
||||
device->hidden->capture = capture;
|
||||
ret = IAudioClient_Start(client);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't start capture", ret);
|
||||
}
|
||||
|
||||
WASAPI_FlushCapture(device); // MSDN says you should flush capture endpoint right after startup.
|
||||
} else {
|
||||
IAudioRenderClient *render = NULL;
|
||||
ret = IAudioClient_GetService(client, &SDL_IID_IAudioRenderClient, (void **)&render);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't get render client service", ret);
|
||||
}
|
||||
|
||||
SDL_assert(render != NULL);
|
||||
device->hidden->render = render;
|
||||
ret = IAudioClient_Start(client);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't start playback", ret);
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // good to go.
|
||||
}
|
||||
|
||||
// This is called once a device is activated, possibly asynchronously.
|
||||
int WASAPI_PrepDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
int rc = 0;
|
||||
return (WASAPI_ProxyToManagementThread(mgmtthrtask_PrepDevice, device, &rc) < 0) ? -1 : rc;
|
||||
}
|
||||
|
||||
static int WASAPI_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return -1;
|
||||
} else if (ActivateWasapiDevice(device) < 0) {
|
||||
return -1; // already set error.
|
||||
}
|
||||
|
||||
/* Ready, but possibly waiting for async device activation.
|
||||
Until activation is successful, we will report silence from capture
|
||||
devices and ignore data on playback devices. Upon activation, we'll make
|
||||
sure any bound audio streams are adjusted for the final device format. */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void WASAPI_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
WASAPI_PlatformThreadInit(device);
|
||||
}
|
||||
|
||||
static void WASAPI_ThreadDeinit(SDL_AudioDevice *device)
|
||||
{
|
||||
WASAPI_PlatformThreadDeinit(device);
|
||||
}
|
||||
|
||||
static int mgmtthrtask_FreeDeviceHandle(void *userdata)
|
||||
{
|
||||
WASAPI_PlatformFreeDeviceHandle((SDL_AudioDevice *)userdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device)
|
||||
{
|
||||
int rc;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc);
|
||||
}
|
||||
|
||||
static int mgmtthrtask_DeinitializeStart(void *userdata)
|
||||
{
|
||||
WASAPI_PlatformDeinitializeStart();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void WASAPI_DeinitializeStart(void)
|
||||
{
|
||||
int rc;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_DeinitializeStart, NULL, &rc);
|
||||
}
|
||||
|
||||
static void WASAPI_Deinitialize(void)
|
||||
{
|
||||
DeinitManagementThread();
|
||||
}
|
||||
|
||||
static SDL_bool WASAPI_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (InitManagementThread() < 0) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
impl->DetectDevices = WASAPI_DetectDevices;
|
||||
impl->ThreadInit = WASAPI_ThreadInit;
|
||||
impl->ThreadDeinit = WASAPI_ThreadDeinit;
|
||||
impl->OpenDevice = WASAPI_OpenDevice;
|
||||
impl->PlayDevice = WASAPI_PlayDevice;
|
||||
impl->WaitDevice = WASAPI_WaitDevice;
|
||||
impl->GetDeviceBuf = WASAPI_GetDeviceBuf;
|
||||
impl->WaitCaptureDevice = WASAPI_WaitDevice;
|
||||
impl->CaptureFromDevice = WASAPI_CaptureFromDevice;
|
||||
impl->FlushCapture = WASAPI_FlushCapture;
|
||||
impl->CloseDevice = WASAPI_CloseDevice;
|
||||
impl->DeinitializeStart = WASAPI_DeinitializeStart;
|
||||
impl->Deinitialize = WASAPI_Deinitialize;
|
||||
impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle;
|
||||
|
||||
impl->HasCaptureSupport = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
AudioBootStrap WASAPI_bootstrap = {
|
||||
"wasapi", "WASAPI", WASAPI_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_WASAPI
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_wasapi_h_
|
||||
#define SDL_wasapi_h_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
WCHAR *devid;
|
||||
WAVEFORMATEX *waveformat;
|
||||
IAudioClient *client;
|
||||
IAudioRenderClient *render;
|
||||
IAudioCaptureClient *capture;
|
||||
HANDLE event;
|
||||
HANDLE task;
|
||||
SDL_bool coinitialized;
|
||||
int framesize;
|
||||
SDL_bool device_lost;
|
||||
SDL_bool device_dead;
|
||||
void *activation_handler;
|
||||
};
|
||||
|
||||
// win32 and winrt implementations call into these.
|
||||
int WASAPI_PrepDevice(SDL_AudioDevice *device);
|
||||
void WASAPI_DisconnectDevice(SDL_AudioDevice *device); // don't hold the device lock when calling this!
|
||||
|
||||
|
||||
// BE CAREFUL: if you are holding the device lock and proxy to the management thread with wait_until_complete, and grab the lock again, you will deadlock.
|
||||
typedef int (*ManagementThreadTask)(void *userdata);
|
||||
int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_until_complete);
|
||||
|
||||
// These are functions that are implemented differently for Windows vs WinRT.
|
||||
// UNLESS OTHERWISE NOTED THESE ALL HAPPEN ON THE MANAGEMENT THREAD.
|
||||
int WASAPI_PlatformInit(void);
|
||||
void WASAPI_PlatformDeinit(void);
|
||||
void WASAPI_PlatformDeinitializeStart(void);
|
||||
void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture);
|
||||
int WASAPI_ActivateDevice(SDL_AudioDevice *device);
|
||||
void WASAPI_PlatformThreadInit(SDL_AudioDevice *device); // this happens on the audio device thread, not the management thread.
|
||||
void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device); // this happens on the audio device thread, not the management thread.
|
||||
void WASAPI_PlatformDeleteActivationHandler(void *handler);
|
||||
void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SDL_wasapi_h_
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
/* This is code that Windows uses to talk to WASAPI-related system APIs.
|
||||
This is for non-WinRT desktop apps. The C++/CX implementation of these
|
||||
functions, exclusive to WinRT, are in SDL_wasapi_winrt.cpp.
|
||||
The code in SDL_wasapi.c is used by both standard Windows and WinRT builds
|
||||
to deal with audio and calls into these functions. */
|
||||
|
||||
#if defined(SDL_AUDIO_DRIVER_WASAPI) && !defined(SDL_PLATFORM_WINRT)
|
||||
|
||||
#include "../../core/windows/SDL_windows.h"
|
||||
#include "../../core/windows/SDL_immdevice.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#include <audioclient.h>
|
||||
|
||||
#include "SDL_wasapi.h"
|
||||
|
||||
// handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency).
|
||||
static HMODULE libavrt = NULL;
|
||||
typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
|
||||
typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
|
||||
static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL;
|
||||
static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL;
|
||||
|
||||
static SDL_bool immdevice_initialized = SDL_FALSE;
|
||||
|
||||
// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
|
||||
static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
|
||||
|
||||
static int mgmtthrtask_AudioDeviceDisconnected(void *userdata)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
|
||||
SDL_AudioDeviceDisconnected(device);
|
||||
UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void WASAPI_AudioDeviceDisconnected(SDL_AudioDevice *device)
|
||||
{
|
||||
// don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
|
||||
if (device) {
|
||||
RefPhysicalAudioDevice(device); // make sure this lives until the task completes.
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_AudioDeviceDisconnected, device, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static int mgmtthrtask_DefaultAudioDeviceChanged(void *userdata)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
|
||||
SDL_DefaultAudioDeviceChanged(device);
|
||||
UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void WASAPI_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
|
||||
{
|
||||
// don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
|
||||
if (new_default_device) {
|
||||
RefPhysicalAudioDevice(new_default_device); // make sure this lives until the task completes.
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_DefaultAudioDeviceChanged, new_default_device, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
int WASAPI_PlatformInit(void)
|
||||
{
|
||||
const SDL_IMMDevice_callbacks callbacks = { WASAPI_AudioDeviceDisconnected, WASAPI_DefaultAudioDeviceChanged };
|
||||
if (FAILED(WIN_CoInitialize())) {
|
||||
return SDL_SetError("CoInitialize() failed");
|
||||
} else if (SDL_IMMDevice_Init(&callbacks) < 0) {
|
||||
return -1; // Error string is set by SDL_IMMDevice_Init
|
||||
}
|
||||
|
||||
immdevice_initialized = SDL_TRUE;
|
||||
|
||||
libavrt = LoadLibrary(TEXT("avrt.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
|
||||
if (libavrt) {
|
||||
pAvSetMmThreadCharacteristicsW = (pfnAvSetMmThreadCharacteristicsW)GetProcAddress(libavrt, "AvSetMmThreadCharacteristicsW");
|
||||
pAvRevertMmThreadCharacteristics = (pfnAvRevertMmThreadCharacteristics)GetProcAddress(libavrt, "AvRevertMmThreadCharacteristics");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void StopWasapiHotplug(void)
|
||||
{
|
||||
if (immdevice_initialized) {
|
||||
SDL_IMMDevice_Quit();
|
||||
immdevice_initialized = SDL_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
void WASAPI_PlatformDeinit(void)
|
||||
{
|
||||
if (libavrt) {
|
||||
FreeLibrary(libavrt);
|
||||
libavrt = NULL;
|
||||
}
|
||||
|
||||
pAvSetMmThreadCharacteristicsW = NULL;
|
||||
pAvRevertMmThreadCharacteristics = NULL;
|
||||
|
||||
StopWasapiHotplug();
|
||||
|
||||
WIN_CoUninitialize();
|
||||
}
|
||||
|
||||
void WASAPI_PlatformDeinitializeStart(void)
|
||||
{
|
||||
StopWasapiHotplug();
|
||||
}
|
||||
|
||||
void WASAPI_PlatformThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
// this thread uses COM.
|
||||
if (SUCCEEDED(WIN_CoInitialize())) { // can't report errors, hope it worked!
|
||||
device->hidden->coinitialized = SDL_TRUE;
|
||||
}
|
||||
|
||||
// Set this thread to very high "Pro Audio" priority.
|
||||
if (pAvSetMmThreadCharacteristicsW) {
|
||||
DWORD idx = 0;
|
||||
device->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx);
|
||||
} else {
|
||||
SDL_SetThreadPriority(device->iscapture ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device)
|
||||
{
|
||||
// Set this thread back to normal priority.
|
||||
if (device->hidden->task && pAvRevertMmThreadCharacteristics) {
|
||||
pAvRevertMmThreadCharacteristics(device->hidden->task);
|
||||
device->hidden->task = NULL;
|
||||
}
|
||||
|
||||
if (device->hidden->coinitialized) {
|
||||
WIN_CoUninitialize();
|
||||
device->hidden->coinitialized = SDL_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int WASAPI_ActivateDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
IMMDevice *immdevice = NULL;
|
||||
if (SDL_IMMDevice_Get(device, &immdevice, device->iscapture) < 0) {
|
||||
device->hidden->client = NULL;
|
||||
return -1; // This is already set by SDL_IMMDevice_Get
|
||||
}
|
||||
|
||||
// this is _not_ async in standard win32, yay!
|
||||
HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client);
|
||||
IMMDevice_Release(immdevice);
|
||||
|
||||
if (FAILED(ret)) {
|
||||
SDL_assert(device->hidden->client == NULL);
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret);
|
||||
}
|
||||
|
||||
SDL_assert(device->hidden->client != NULL);
|
||||
if (WASAPI_PrepDevice(device) == -1) { // not async, fire it right away.
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0; // good to go.
|
||||
}
|
||||
|
||||
void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
|
||||
{
|
||||
SDL_IMMDevice_EnumerateEndpoints(default_output, default_capture);
|
||||
}
|
||||
|
||||
void WASAPI_PlatformDeleteActivationHandler(void *handler)
|
||||
{
|
||||
// not asynchronous.
|
||||
SDL_assert(!"This function should have only been called on WinRT.");
|
||||
}
|
||||
|
||||
void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device)
|
||||
{
|
||||
SDL_IMMDevice_FreeDeviceHandle(device);
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_WASAPI && !defined(SDL_PLATFORM_WINRT)
|
||||
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// This is C++/CX code that the WinRT port uses to talk to WASAPI-related
|
||||
// system APIs. The C implementation of these functions, for non-WinRT apps,
|
||||
// is in SDL_wasapi_win32.c. The code in SDL_wasapi.c is used by both standard
|
||||
// Windows and WinRT builds to deal with audio and calls into these functions.
|
||||
|
||||
#if defined(SDL_AUDIO_DRIVER_WASAPI) && defined(SDL_PLATFORM_WINRT)
|
||||
|
||||
#include <Windows.h>
|
||||
#include <windows.ui.core.h>
|
||||
#include <windows.devices.enumeration.h>
|
||||
#include <windows.media.devices.h>
|
||||
#include <wrl/implements.h>
|
||||
#include <collection.h>
|
||||
|
||||
extern "C" {
|
||||
#include "../../core/windows/SDL_windows.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
}
|
||||
|
||||
#define COBJMACROS
|
||||
#include <mmdeviceapi.h>
|
||||
#include <audioclient.h>
|
||||
|
||||
#include "SDL_wasapi.h"
|
||||
|
||||
using namespace Windows::Devices::Enumeration;
|
||||
using namespace Windows::Media::Devices;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
static Platform::String ^ SDL_PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0";
|
||||
|
||||
|
||||
static SDL_bool FindWinRTAudioDeviceCallback(SDL_AudioDevice *device, void *userdata)
|
||||
{
|
||||
return (SDL_wcscmp((LPCWSTR) device->handle, (LPCWSTR) userdata) == 0);
|
||||
}
|
||||
|
||||
static SDL_AudioDevice *FindWinRTAudioDevice(LPCWSTR devid)
|
||||
{
|
||||
return SDL_FindPhysicalAudioDeviceByCallback(FindWinRTAudioDeviceCallback, (void *) devid);
|
||||
}
|
||||
|
||||
class SDL_WasapiDeviceEventHandler
|
||||
{
|
||||
public:
|
||||
SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture);
|
||||
~SDL_WasapiDeviceEventHandler();
|
||||
void OnDeviceAdded(DeviceWatcher ^ sender, DeviceInformation ^ args);
|
||||
void OnDeviceRemoved(DeviceWatcher ^ sender, DeviceInformationUpdate ^ args);
|
||||
void OnDeviceUpdated(DeviceWatcher ^ sender, DeviceInformationUpdate ^ args);
|
||||
void OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args);
|
||||
void OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args);
|
||||
void OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args);
|
||||
void WaitForCompletion();
|
||||
|
||||
private:
|
||||
SDL_Semaphore *completed_semaphore;
|
||||
const SDL_bool iscapture;
|
||||
DeviceWatcher ^ watcher;
|
||||
Windows::Foundation::EventRegistrationToken added_handler;
|
||||
Windows::Foundation::EventRegistrationToken removed_handler;
|
||||
Windows::Foundation::EventRegistrationToken updated_handler;
|
||||
Windows::Foundation::EventRegistrationToken completed_handler;
|
||||
Windows::Foundation::EventRegistrationToken default_changed_handler;
|
||||
};
|
||||
|
||||
SDL_WasapiDeviceEventHandler::SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture)
|
||||
: iscapture(_iscapture), completed_semaphore(SDL_CreateSemaphore(0))
|
||||
{
|
||||
if (!completed_semaphore) {
|
||||
return; // uhoh.
|
||||
}
|
||||
|
||||
Platform::String ^ selector = _iscapture ? MediaDevice::GetAudioCaptureSelector() : MediaDevice::GetAudioRenderSelector();
|
||||
Platform::Collections::Vector<Platform::String ^> properties;
|
||||
properties.Append(SDL_PKEY_AudioEngine_DeviceFormat);
|
||||
watcher = DeviceInformation::CreateWatcher(selector, properties.GetView());
|
||||
if (!watcher)
|
||||
return; // uhoh.
|
||||
|
||||
// !!! FIXME: this doesn't need a lambda here, I think, if I make SDL_WasapiDeviceEventHandler a proper C++/CX class. --ryan.
|
||||
added_handler = watcher->Added += ref new TypedEventHandler<DeviceWatcher ^, DeviceInformation ^>([this](DeviceWatcher ^ sender, DeviceInformation ^ args) { OnDeviceAdded(sender, args); });
|
||||
removed_handler = watcher->Removed += ref new TypedEventHandler<DeviceWatcher ^, DeviceInformationUpdate ^>([this](DeviceWatcher ^ sender, DeviceInformationUpdate ^ args) { OnDeviceRemoved(sender, args); });
|
||||
updated_handler = watcher->Updated += ref new TypedEventHandler<DeviceWatcher ^, DeviceInformationUpdate ^>([this](DeviceWatcher ^ sender, DeviceInformationUpdate ^ args) { OnDeviceUpdated(sender, args); });
|
||||
completed_handler = watcher->EnumerationCompleted += ref new TypedEventHandler<DeviceWatcher ^, Platform::Object ^>([this](DeviceWatcher ^ sender, Platform::Object ^ args) { OnEnumerationCompleted(sender, args); });
|
||||
if (iscapture) {
|
||||
default_changed_handler = MediaDevice::DefaultAudioCaptureDeviceChanged += ref new TypedEventHandler<Platform::Object ^, DefaultAudioCaptureDeviceChangedEventArgs ^>([this](Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args) { OnDefaultCaptureDeviceChanged(sender, args); });
|
||||
} else {
|
||||
default_changed_handler = MediaDevice::DefaultAudioRenderDeviceChanged += ref new TypedEventHandler<Platform::Object ^, DefaultAudioRenderDeviceChangedEventArgs ^>([this](Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args) { OnDefaultRenderDeviceChanged(sender, args); });
|
||||
}
|
||||
watcher->Start();
|
||||
}
|
||||
|
||||
SDL_WasapiDeviceEventHandler::~SDL_WasapiDeviceEventHandler()
|
||||
{
|
||||
if (watcher) {
|
||||
watcher->Added -= added_handler;
|
||||
watcher->Removed -= removed_handler;
|
||||
watcher->Updated -= updated_handler;
|
||||
watcher->EnumerationCompleted -= completed_handler;
|
||||
watcher->Stop();
|
||||
watcher = nullptr;
|
||||
}
|
||||
|
||||
if (completed_semaphore) {
|
||||
SDL_DestroySemaphore(completed_semaphore);
|
||||
completed_semaphore = nullptr;
|
||||
}
|
||||
|
||||
if (iscapture) {
|
||||
MediaDevice::DefaultAudioCaptureDeviceChanged -= default_changed_handler;
|
||||
} else {
|
||||
MediaDevice::DefaultAudioRenderDeviceChanged -= default_changed_handler;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher ^ sender, DeviceInformation ^ info)
|
||||
{
|
||||
/* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever).
|
||||
In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for
|
||||
phones and tablets, where you might have an internal speaker and a headphone jack and expect both to be
|
||||
available and switch automatically. (!!! FIXME...?) */
|
||||
|
||||
SDL_assert(sender == this->watcher);
|
||||
char *utf8dev = WIN_StringToUTF8(info->Name->Data());
|
||||
if (utf8dev) {
|
||||
SDL_AudioSpec spec;
|
||||
SDL_zero(spec);
|
||||
|
||||
Platform::Object ^ obj = info->Properties->Lookup(SDL_PKEY_AudioEngine_DeviceFormat);
|
||||
if (obj) {
|
||||
IPropertyValue ^ property = (IPropertyValue ^) obj;
|
||||
Platform::Array<unsigned char> ^ data;
|
||||
property->GetUInt8Array(&data);
|
||||
WAVEFORMATEXTENSIBLE fmt;
|
||||
SDL_zero(fmt);
|
||||
SDL_memcpy(&fmt, data->Data, SDL_min(data->Length, sizeof(WAVEFORMATEXTENSIBLE)));
|
||||
spec.channels = (Uint8)fmt.Format.nChannels;
|
||||
spec.freq = fmt.Format.nSamplesPerSec;
|
||||
spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)&fmt);
|
||||
}
|
||||
|
||||
LPWSTR devid = SDL_wcsdup(info->Id->Data());
|
||||
if (devid) {
|
||||
SDL_AddAudioDevice(this->iscapture, utf8dev, spec.channels ? &spec : NULL, devid);
|
||||
}
|
||||
SDL_free(utf8dev);
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_WasapiDeviceEventHandler::OnDeviceRemoved(DeviceWatcher ^ sender, DeviceInformationUpdate ^ info)
|
||||
{
|
||||
SDL_assert(sender == this->watcher);
|
||||
WASAPI_DisconnectDevice(FindWinRTAudioDevice(info->Id->Data()));
|
||||
}
|
||||
|
||||
void SDL_WasapiDeviceEventHandler::OnDeviceUpdated(DeviceWatcher ^ sender, DeviceInformationUpdate ^ args)
|
||||
{
|
||||
SDL_assert(sender == this->watcher);
|
||||
}
|
||||
|
||||
void SDL_WasapiDeviceEventHandler::OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args)
|
||||
{
|
||||
SDL_assert(sender == this->watcher);
|
||||
if (this->completed_semaphore) {
|
||||
SDL_PostSemaphore(this->completed_semaphore);
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_WasapiDeviceEventHandler::OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args)
|
||||
{
|
||||
SDL_assert(!this->iscapture);
|
||||
SDL_DefaultAudioDeviceChanged(FindWinRTAudioDevice(args->Id->Data()));
|
||||
}
|
||||
|
||||
void SDL_WasapiDeviceEventHandler::OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args)
|
||||
{
|
||||
SDL_assert(this->iscapture);
|
||||
SDL_DefaultAudioDeviceChanged(FindWinRTAudioDevice(args->Id->Data()));
|
||||
}
|
||||
|
||||
void SDL_WasapiDeviceEventHandler::WaitForCompletion()
|
||||
{
|
||||
if (this->completed_semaphore) {
|
||||
SDL_WaitSemaphore(this->completed_semaphore);
|
||||
SDL_DestroySemaphore(this->completed_semaphore);
|
||||
this->completed_semaphore = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_WasapiDeviceEventHandler *playback_device_event_handler;
|
||||
static SDL_WasapiDeviceEventHandler *capture_device_event_handler;
|
||||
|
||||
int WASAPI_PlatformInit(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void StopWasapiHotplug(void)
|
||||
{
|
||||
delete playback_device_event_handler;
|
||||
playback_device_event_handler = nullptr;
|
||||
delete capture_device_event_handler;
|
||||
capture_device_event_handler = nullptr;
|
||||
}
|
||||
|
||||
void WASAPI_PlatformDeinit(void)
|
||||
{
|
||||
StopWasapiHotplug();
|
||||
}
|
||||
|
||||
void WASAPI_PlatformDeinitializeStart(void)
|
||||
{
|
||||
StopWasapiHotplug();
|
||||
}
|
||||
|
||||
|
||||
void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
|
||||
{
|
||||
Platform::String ^ defdevid;
|
||||
|
||||
// DeviceWatchers will fire an Added event for each existing device at
|
||||
// startup, so we don't need to enumerate them separately before
|
||||
// listening for updates.
|
||||
playback_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_FALSE);
|
||||
playback_device_event_handler->WaitForCompletion();
|
||||
defdevid = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
|
||||
if (defdevid) {
|
||||
*default_output = FindWinRTAudioDevice(defdevid->Data());
|
||||
}
|
||||
|
||||
capture_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_TRUE);
|
||||
capture_device_event_handler->WaitForCompletion();
|
||||
defdevid = MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default);
|
||||
if (defdevid) {
|
||||
*default_capture = FindWinRTAudioDevice(defdevid->Data());
|
||||
}
|
||||
}
|
||||
|
||||
class SDL_WasapiActivationHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>, FtmBase, IActivateAudioInterfaceCompletionHandler>
|
||||
{
|
||||
public:
|
||||
SDL_WasapiActivationHandler() : completion_semaphore(SDL_CreateSemaphore(0)) { SDL_assert(completion_semaphore != NULL); }
|
||||
STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation *operation);
|
||||
void WaitForCompletion();
|
||||
private:
|
||||
SDL_Semaphore *completion_semaphore;
|
||||
};
|
||||
|
||||
void SDL_WasapiActivationHandler::WaitForCompletion()
|
||||
{
|
||||
if (completion_semaphore) {
|
||||
SDL_WaitSemaphore(completion_semaphore);
|
||||
SDL_DestroySemaphore(completion_semaphore);
|
||||
completion_semaphore = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT
|
||||
SDL_WasapiActivationHandler::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *async)
|
||||
{
|
||||
// Just set a flag, since we're probably in a different thread. We'll pick it up and init everything on our own thread to prevent races.
|
||||
SDL_PostSemaphore(completion_semaphore);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void WASAPI_PlatformDeleteActivationHandler(void *handler)
|
||||
{
|
||||
((SDL_WasapiActivationHandler *)handler)->Release();
|
||||
}
|
||||
|
||||
int WASAPI_ActivateDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
LPCWSTR devid = (LPCWSTR) device->handle;
|
||||
SDL_assert(devid != NULL);
|
||||
|
||||
ComPtr<SDL_WasapiActivationHandler> handler = Make<SDL_WasapiActivationHandler>();
|
||||
if (handler == nullptr) {
|
||||
return SDL_SetError("Failed to allocate WASAPI activation handler");
|
||||
}
|
||||
|
||||
handler.Get()->AddRef(); // we hold a reference after ComPtr destructs on return, causing a Release, and Release ourselves in WASAPI_PlatformDeleteActivationHandler(), etc.
|
||||
device->hidden->activation_handler = handler.Get();
|
||||
|
||||
IActivateAudioInterfaceAsyncOperation *async = nullptr;
|
||||
const HRESULT ret = ActivateAudioInterfaceAsync(devid, __uuidof(IAudioClient), nullptr, handler.Get(), &async);
|
||||
|
||||
if (FAILED(ret) || async == nullptr) {
|
||||
if (async != nullptr) {
|
||||
async->Release();
|
||||
}
|
||||
handler.Get()->Release();
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't activate requested audio endpoint", ret);
|
||||
}
|
||||
|
||||
// !!! FIXME: the problems in SDL2 that needed this to be synchronous are _probably_ solved by SDL3, and this can block indefinitely if a user prompt is shown to get permission to use a microphone.
|
||||
handler.Get()->WaitForCompletion(); // block here until we have an answer, so this is synchronous to us after all.
|
||||
|
||||
HRESULT activateRes = S_OK;
|
||||
IUnknown *iunknown = nullptr;
|
||||
const HRESULT getActivateRes = async->GetActivateResult(&activateRes, &iunknown);
|
||||
async->Release();
|
||||
if (FAILED(getActivateRes)) {
|
||||
return WIN_SetErrorFromHRESULT("Failed to get WASAPI activate result", getActivateRes);
|
||||
} else if (FAILED(activateRes)) {
|
||||
return WIN_SetErrorFromHRESULT("Failed to activate WASAPI device", activateRes);
|
||||
}
|
||||
|
||||
iunknown->QueryInterface(IID_PPV_ARGS(&device->hidden->client));
|
||||
if (!device->hidden->client) {
|
||||
return SDL_SetError("Failed to query WASAPI client interface");
|
||||
}
|
||||
|
||||
if (WASAPI_PrepDevice(device) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WASAPI_PlatformThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
// !!! FIXME: set this thread to "Pro Audio" priority.
|
||||
SDL_SetThreadPriority(device->iscapture ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
|
||||
}
|
||||
|
||||
void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device)
|
||||
{
|
||||
// !!! FIXME: set this thread to "Pro Audio" priority.
|
||||
}
|
||||
|
||||
void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device)
|
||||
{
|
||||
SDL_free(device->handle);
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_WASAPI && defined(SDL_PLATFORM_WINRT)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "../SDL_internal.h"
|
||||
|
||||
#ifndef SDL_camera_c_h_
|
||||
#define SDL_camera_c_h_
|
||||
|
||||
// Initialize the camera subsystem
|
||||
int SDL_CameraInit(const char *driver_name);
|
||||
|
||||
// Shutdown the camera subsystem
|
||||
void SDL_QuitCamera(void);
|
||||
|
||||
// "Pump" the event queue.
|
||||
extern void SDL_UpdateCamera(void);
|
||||
|
||||
#endif // SDL_camera_c_h_
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "../SDL_internal.h"
|
||||
|
||||
#ifndef SDL_syscamera_h_
|
||||
#define SDL_syscamera_h_
|
||||
|
||||
#include "../SDL_hashtable.h"
|
||||
|
||||
#define DEBUG_CAMERA 0
|
||||
|
||||
typedef struct SDL_CameraDevice SDL_CameraDevice;
|
||||
|
||||
/* Backends should call this as devices are added to the system (such as
|
||||
a USB camera being plugged in), and should also be called for
|
||||
for every device found during DetectDevices(). */
|
||||
extern SDL_CameraDevice *SDL_AddCameraDevice(const char *name, SDL_CameraPosition position, int num_specs, const SDL_CameraSpec *specs, void *handle);
|
||||
|
||||
/* Backends should call this if an opened camera device is lost.
|
||||
This can happen due to i/o errors, or a device being unplugged, etc. */
|
||||
extern void SDL_CameraDeviceDisconnected(SDL_CameraDevice *device);
|
||||
|
||||
// Find an SDL_CameraDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
|
||||
extern SDL_CameraDevice *SDL_FindPhysicalCameraDeviceByCallback(SDL_bool (*callback)(SDL_CameraDevice *device, void *userdata), void *userdata);
|
||||
|
||||
// Backends should call this when the user has approved/denied access to a camera.
|
||||
extern void SDL_CameraDevicePermissionOutcome(SDL_CameraDevice *device, SDL_bool approved);
|
||||
|
||||
// Backends can call this to get a standardized name for a thread to power a specific camera device.
|
||||
extern char *SDL_GetCameraThreadName(SDL_CameraDevice *device, char *buf, size_t buflen);
|
||||
|
||||
// Backends can call these to change a device's refcount.
|
||||
extern void RefPhysicalCameraDevice(SDL_CameraDevice *device);
|
||||
extern void UnrefPhysicalCameraDevice(SDL_CameraDevice *device);
|
||||
|
||||
// These functions are the heart of the camera threads. Backends can call them directly if they aren't using the SDL-provided thread.
|
||||
extern void SDL_CameraThreadSetup(SDL_CameraDevice *device);
|
||||
extern SDL_bool SDL_CameraThreadIterate(SDL_CameraDevice *device);
|
||||
extern void SDL_CameraThreadShutdown(SDL_CameraDevice *device);
|
||||
|
||||
// common utility functionality to gather up camera specs. Not required!
|
||||
typedef struct CameraFormatAddData
|
||||
{
|
||||
SDL_CameraSpec *specs;
|
||||
int num_specs;
|
||||
int allocated_specs;
|
||||
} CameraFormatAddData;
|
||||
|
||||
int SDL_AddCameraFormat(CameraFormatAddData *data, SDL_PixelFormatEnum fmt, int w, int h, int interval_numerator, int interval_denominator);
|
||||
|
||||
typedef struct SurfaceList
|
||||
{
|
||||
SDL_Surface *surface;
|
||||
Uint64 timestampNS;
|
||||
struct SurfaceList *next;
|
||||
} SurfaceList;
|
||||
|
||||
// Define the SDL camera driver structure
|
||||
struct SDL_CameraDevice
|
||||
{
|
||||
// A mutex for locking
|
||||
SDL_Mutex *lock;
|
||||
|
||||
// Human-readable device name.
|
||||
char *name;
|
||||
|
||||
// Position of camera (front-facing, back-facing, etc).
|
||||
SDL_CameraPosition position;
|
||||
|
||||
// When refcount hits zero, we destroy the device object.
|
||||
SDL_AtomicInt refcount;
|
||||
|
||||
// These are, initially, set from camera_driver, but we might swap them out with Zombie versions on disconnect/failure.
|
||||
int (*WaitDevice)(SDL_CameraDevice *device);
|
||||
int (*AcquireFrame)(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS);
|
||||
void (*ReleaseFrame)(SDL_CameraDevice *device, SDL_Surface *frame);
|
||||
|
||||
// All supported formats/dimensions for this device.
|
||||
SDL_CameraSpec *all_specs;
|
||||
|
||||
// Elements in all_specs.
|
||||
int num_specs;
|
||||
|
||||
// The device's actual specification that the camera is outputting, before conversion.
|
||||
SDL_CameraSpec actual_spec;
|
||||
|
||||
// The device's current camera specification, after conversions.
|
||||
SDL_CameraSpec spec;
|
||||
|
||||
// Unique value assigned at creation time.
|
||||
SDL_CameraDeviceID instance_id;
|
||||
|
||||
// Driver-specific hardware data on how to open device (`hidden` is driver-specific data _when opened_).
|
||||
void *handle;
|
||||
|
||||
// Dropping the first frame(s) after open seems to help timing on some platforms.
|
||||
int drop_frames;
|
||||
|
||||
// Backend timestamp of first acquired frame, so we can keep these meaningful regardless of epoch.
|
||||
Uint64 base_timestamp;
|
||||
|
||||
// SDL timestamp of first acquired frame, so we can roughly convert to SDL ticks.
|
||||
Uint64 adjust_timestamp;
|
||||
|
||||
// Pixel data flows from the driver into these, then gets converted for the app if necessary.
|
||||
SDL_Surface *acquire_surface;
|
||||
|
||||
// acquire_surface converts or scales to this surface before landing in output_surfaces, if necessary.
|
||||
SDL_Surface *conversion_surface;
|
||||
|
||||
// A queue of surfaces that buffer converted/scaled frames of video until the app claims them.
|
||||
SurfaceList output_surfaces[8];
|
||||
SurfaceList filled_output_surfaces; // this is FIFO
|
||||
SurfaceList empty_output_surfaces; // this is LIFO
|
||||
SurfaceList app_held_output_surfaces;
|
||||
|
||||
// A fake video frame we allocate if the camera fails/disconnects.
|
||||
Uint8 *zombie_pixels;
|
||||
|
||||
// non-zero if acquire_surface needs to be scaled for final output.
|
||||
int needs_scaling; // -1: downscale, 0: no scaling, 1: upscale
|
||||
|
||||
// SDL_TRUE if acquire_surface needs to be converted for final output.
|
||||
SDL_bool needs_conversion;
|
||||
|
||||
// Current state flags
|
||||
SDL_AtomicInt shutdown;
|
||||
SDL_AtomicInt zombie;
|
||||
|
||||
// A thread to feed the camera device
|
||||
SDL_Thread *thread;
|
||||
|
||||
// Optional properties.
|
||||
SDL_PropertiesID props;
|
||||
|
||||
// -1: user denied permission, 0: waiting for user response, 1: user approved permission.
|
||||
int permission;
|
||||
|
||||
// Data private to this driver, used when device is opened and running.
|
||||
struct SDL_PrivateCameraData *hidden;
|
||||
};
|
||||
|
||||
typedef struct SDL_CameraDriverImpl
|
||||
{
|
||||
void (*DetectDevices)(void);
|
||||
int (*OpenDevice)(SDL_CameraDevice *device, const SDL_CameraSpec *spec);
|
||||
void (*CloseDevice)(SDL_CameraDevice *device);
|
||||
int (*WaitDevice)(SDL_CameraDevice *device);
|
||||
int (*AcquireFrame)(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS); // set frame->pixels, frame->pitch, and *timestampNS!
|
||||
void (*ReleaseFrame)(SDL_CameraDevice *device, SDL_Surface *frame); // Reclaim frame->pixels and frame->pitch!
|
||||
void (*FreeDeviceHandle)(SDL_CameraDevice *device); // SDL is done with this device; free the handle from SDL_AddCameraDevice()
|
||||
void (*Deinitialize)(void);
|
||||
|
||||
SDL_bool ProvidesOwnCallbackThread;
|
||||
} SDL_CameraDriverImpl;
|
||||
|
||||
typedef struct SDL_PendingCameraDeviceEvent
|
||||
{
|
||||
Uint32 type;
|
||||
SDL_CameraDeviceID devid;
|
||||
struct SDL_PendingCameraDeviceEvent *next;
|
||||
} SDL_PendingCameraDeviceEvent;
|
||||
|
||||
typedef struct SDL_CameraDriver
|
||||
{
|
||||
const char *name; // The name of this camera driver
|
||||
const char *desc; // The description of this camera driver
|
||||
SDL_CameraDriverImpl impl; // the backend's interface
|
||||
|
||||
SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash`
|
||||
SDL_HashTable *device_hash; // the collection of currently-available camera devices
|
||||
SDL_PendingCameraDeviceEvent pending_events;
|
||||
SDL_PendingCameraDeviceEvent *pending_events_tail;
|
||||
|
||||
SDL_AtomicInt device_count;
|
||||
SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs.
|
||||
} SDL_CameraDriver;
|
||||
|
||||
typedef struct CameraBootStrap
|
||||
{
|
||||
const char *name;
|
||||
const char *desc;
|
||||
SDL_bool (*init)(SDL_CameraDriverImpl *impl);
|
||||
SDL_bool demand_only; // if SDL_TRUE: request explicitly, or it won't be available.
|
||||
} CameraBootStrap;
|
||||
|
||||
// Not all of these are available in a given build. Use #ifdefs, etc.
|
||||
extern CameraBootStrap DUMMYCAMERA_bootstrap;
|
||||
extern CameraBootStrap V4L2_bootstrap;
|
||||
extern CameraBootStrap COREMEDIA_bootstrap;
|
||||
extern CameraBootStrap ANDROIDCAMERA_bootstrap;
|
||||
extern CameraBootStrap EMSCRIPTENCAMERA_bootstrap;
|
||||
extern CameraBootStrap MEDIAFOUNDATION_bootstrap;
|
||||
|
||||
#endif // SDL_syscamera_h_
|
||||
@@ -0,0 +1,891 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "../SDL_syscamera.h"
|
||||
#include "../SDL_camera_c.h"
|
||||
#include "../../video/SDL_pixels_c.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
|
||||
#ifdef SDL_CAMERA_DRIVER_ANDROID
|
||||
|
||||
/*
|
||||
* AndroidManifest.xml:
|
||||
* <uses-permission android:name="android.permission.CAMERA"></uses-permission>
|
||||
* <uses-feature android:name="android.hardware.camera" />
|
||||
*
|
||||
* Very likely SDL must be build with YUV support (done by default)
|
||||
*
|
||||
* https://developer.android.com/reference/android/hardware/camera2/CameraManager
|
||||
* "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
|
||||
* before configuring sessions on any of the camera devices."
|
||||
*/
|
||||
|
||||
// this is kinda gross, but on older NDK headers all the camera stuff is
|
||||
// gated behind __ANDROID_API__. We'll dlopen() it at runtime, so we'll do
|
||||
// the right thing on pre-Android 7.0 devices, but we still
|
||||
// need the struct declarations and such in those headers.
|
||||
// The other option is to make a massive jump in minimum Android version we
|
||||
// support--going from ancient to merely really old--but this seems less
|
||||
// distasteful and using dlopen matches practices on other SDL platforms.
|
||||
// We'll see if it works out.
|
||||
#if __ANDROID_API__ < 24
|
||||
#undef __ANDROID_API__
|
||||
#define __ANDROID_API__ 24
|
||||
#endif
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <camera/NdkCameraDevice.h>
|
||||
#include <camera/NdkCameraManager.h>
|
||||
#include <media/NdkImage.h>
|
||||
#include <media/NdkImageReader.h>
|
||||
|
||||
#include "../../core/android/SDL_android.h"
|
||||
|
||||
static void *libcamera2ndk = NULL;
|
||||
typedef ACameraManager* (*pfnACameraManager_create)(void);
|
||||
typedef camera_status_t (*pfnACameraManager_registerAvailabilityCallback)(ACameraManager*, const ACameraManager_AvailabilityCallbacks*);
|
||||
typedef camera_status_t (*pfnACameraManager_unregisterAvailabilityCallback)(ACameraManager*, const ACameraManager_AvailabilityCallbacks*);
|
||||
typedef camera_status_t (*pfnACameraManager_getCameraIdList)(ACameraManager*, ACameraIdList**);
|
||||
typedef void (*pfnACameraManager_deleteCameraIdList)(ACameraIdList*);
|
||||
typedef void (*pfnACameraCaptureSession_close)(ACameraCaptureSession*);
|
||||
typedef void (*pfnACaptureRequest_free)(ACaptureRequest*);
|
||||
typedef void (*pfnACameraOutputTarget_free)(ACameraOutputTarget*);
|
||||
typedef camera_status_t (*pfnACameraDevice_close)(ACameraDevice*);
|
||||
typedef void (*pfnACameraManager_delete)(ACameraManager*);
|
||||
typedef void (*pfnACaptureSessionOutputContainer_free)(ACaptureSessionOutputContainer*);
|
||||
typedef void (*pfnACaptureSessionOutput_free)(ACaptureSessionOutput*);
|
||||
typedef camera_status_t (*pfnACameraManager_openCamera)(ACameraManager*, const char*, ACameraDevice_StateCallbacks*, ACameraDevice**);
|
||||
typedef camera_status_t (*pfnACameraDevice_createCaptureRequest)(const ACameraDevice*, ACameraDevice_request_template, ACaptureRequest**);
|
||||
typedef camera_status_t (*pfnACameraDevice_createCaptureSession)(ACameraDevice*, const ACaptureSessionOutputContainer*, const ACameraCaptureSession_stateCallbacks*,ACameraCaptureSession**);
|
||||
typedef camera_status_t (*pfnACameraManager_getCameraCharacteristics)(ACameraManager*, const char*, ACameraMetadata**);
|
||||
typedef void (*pfnACameraMetadata_free)(ACameraMetadata*);
|
||||
typedef camera_status_t (*pfnACameraMetadata_getConstEntry)(const ACameraMetadata*, uint32_t tag, ACameraMetadata_const_entry*);
|
||||
typedef camera_status_t (*pfnACameraCaptureSession_setRepeatingRequest)(ACameraCaptureSession*, ACameraCaptureSession_captureCallbacks*, int numRequests, ACaptureRequest**, int*);
|
||||
typedef camera_status_t (*pfnACameraOutputTarget_create)(ACameraWindowType*,ACameraOutputTarget**);
|
||||
typedef camera_status_t (*pfnACaptureRequest_addTarget)(ACaptureRequest*, const ACameraOutputTarget*);
|
||||
typedef camera_status_t (*pfnACaptureSessionOutputContainer_add)(ACaptureSessionOutputContainer*, const ACaptureSessionOutput*);
|
||||
typedef camera_status_t (*pfnACaptureSessionOutputContainer_create)(ACaptureSessionOutputContainer**);
|
||||
typedef camera_status_t (*pfnACaptureSessionOutput_create)(ACameraWindowType*, ACaptureSessionOutput**);
|
||||
static pfnACameraManager_create pACameraManager_create = NULL;
|
||||
static pfnACameraManager_registerAvailabilityCallback pACameraManager_registerAvailabilityCallback = NULL;
|
||||
static pfnACameraManager_unregisterAvailabilityCallback pACameraManager_unregisterAvailabilityCallback = NULL;
|
||||
static pfnACameraManager_getCameraIdList pACameraManager_getCameraIdList = NULL;
|
||||
static pfnACameraManager_deleteCameraIdList pACameraManager_deleteCameraIdList = NULL;
|
||||
static pfnACameraCaptureSession_close pACameraCaptureSession_close = NULL;
|
||||
static pfnACaptureRequest_free pACaptureRequest_free = NULL;
|
||||
static pfnACameraOutputTarget_free pACameraOutputTarget_free = NULL;
|
||||
static pfnACameraDevice_close pACameraDevice_close = NULL;
|
||||
static pfnACameraManager_delete pACameraManager_delete = NULL;
|
||||
static pfnACaptureSessionOutputContainer_free pACaptureSessionOutputContainer_free = NULL;
|
||||
static pfnACaptureSessionOutput_free pACaptureSessionOutput_free = NULL;
|
||||
static pfnACameraManager_openCamera pACameraManager_openCamera = NULL;
|
||||
static pfnACameraDevice_createCaptureRequest pACameraDevice_createCaptureRequest = NULL;
|
||||
static pfnACameraDevice_createCaptureSession pACameraDevice_createCaptureSession = NULL;
|
||||
static pfnACameraManager_getCameraCharacteristics pACameraManager_getCameraCharacteristics = NULL;
|
||||
static pfnACameraMetadata_free pACameraMetadata_free = NULL;
|
||||
static pfnACameraMetadata_getConstEntry pACameraMetadata_getConstEntry = NULL;
|
||||
static pfnACameraCaptureSession_setRepeatingRequest pACameraCaptureSession_setRepeatingRequest = NULL;
|
||||
static pfnACameraOutputTarget_create pACameraOutputTarget_create = NULL;
|
||||
static pfnACaptureRequest_addTarget pACaptureRequest_addTarget = NULL;
|
||||
static pfnACaptureSessionOutputContainer_add pACaptureSessionOutputContainer_add = NULL;
|
||||
static pfnACaptureSessionOutputContainer_create pACaptureSessionOutputContainer_create = NULL;
|
||||
static pfnACaptureSessionOutput_create pACaptureSessionOutput_create = NULL;
|
||||
|
||||
static void *libmediandk = NULL;
|
||||
typedef void (*pfnAImage_delete)(AImage*);
|
||||
typedef media_status_t (*pfnAImage_getTimestamp)(const AImage*, int64_t*);
|
||||
typedef media_status_t (*pfnAImage_getNumberOfPlanes)(const AImage*, int32_t*);
|
||||
typedef media_status_t (*pfnAImage_getPlaneRowStride)(const AImage*, int, int32_t*);
|
||||
typedef media_status_t (*pfnAImage_getPlaneData)(const AImage*, int, uint8_t**, int*);
|
||||
typedef media_status_t (*pfnAImageReader_acquireNextImage)(AImageReader*, AImage**);
|
||||
typedef void (*pfnAImageReader_delete)(AImageReader*);
|
||||
typedef media_status_t (*pfnAImageReader_setImageListener)(AImageReader*, AImageReader_ImageListener*);
|
||||
typedef media_status_t (*pfnAImageReader_getWindow)(AImageReader*, ANativeWindow**);
|
||||
typedef media_status_t (*pfnAImageReader_new)(int32_t, int32_t, int32_t, int32_t, AImageReader**);
|
||||
static pfnAImage_delete pAImage_delete = NULL;
|
||||
static pfnAImage_getTimestamp pAImage_getTimestamp = NULL;
|
||||
static pfnAImage_getNumberOfPlanes pAImage_getNumberOfPlanes = NULL;
|
||||
static pfnAImage_getPlaneRowStride pAImage_getPlaneRowStride = NULL;
|
||||
static pfnAImage_getPlaneData pAImage_getPlaneData = NULL;
|
||||
static pfnAImageReader_acquireNextImage pAImageReader_acquireNextImage = NULL;
|
||||
static pfnAImageReader_delete pAImageReader_delete = NULL;
|
||||
static pfnAImageReader_setImageListener pAImageReader_setImageListener = NULL;
|
||||
static pfnAImageReader_getWindow pAImageReader_getWindow = NULL;
|
||||
static pfnAImageReader_new pAImageReader_new = NULL;
|
||||
|
||||
typedef media_status_t (*pfnAImage_getWidth)(const AImage*, int32_t*);
|
||||
typedef media_status_t (*pfnAImage_getHeight)(const AImage*, int32_t*);
|
||||
static pfnAImage_getWidth pAImage_getWidth = NULL;
|
||||
static pfnAImage_getHeight pAImage_getHeight = NULL;
|
||||
|
||||
struct SDL_PrivateCameraData
|
||||
{
|
||||
ACameraDevice *device;
|
||||
AImageReader *reader;
|
||||
ANativeWindow *window;
|
||||
ACaptureSessionOutput *sessionOutput;
|
||||
ACaptureSessionOutputContainer *sessionOutputContainer;
|
||||
ACameraOutputTarget *outputTarget;
|
||||
ACaptureRequest *request;
|
||||
ACameraCaptureSession *session;
|
||||
SDL_CameraSpec requested_spec;
|
||||
};
|
||||
|
||||
static int SetErrorStr(const char *what, const char *errstr, const int rc)
|
||||
{
|
||||
char errbuf[128];
|
||||
if (!errstr) {
|
||||
SDL_snprintf(errbuf, sizeof (errbuf), "Unknown error #%d", rc);
|
||||
errstr = errbuf;
|
||||
}
|
||||
return SDL_SetError("%s: %s", what, errstr);
|
||||
}
|
||||
|
||||
static const char *CameraStatusStr(const camera_status_t rc)
|
||||
{
|
||||
switch (rc) {
|
||||
case ACAMERA_OK: return "no error";
|
||||
case ACAMERA_ERROR_UNKNOWN: return "unknown error";
|
||||
case ACAMERA_ERROR_INVALID_PARAMETER: return "invalid parameter";
|
||||
case ACAMERA_ERROR_CAMERA_DISCONNECTED: return "camera disconnected";
|
||||
case ACAMERA_ERROR_NOT_ENOUGH_MEMORY: return "not enough memory";
|
||||
case ACAMERA_ERROR_METADATA_NOT_FOUND: return "metadata not found";
|
||||
case ACAMERA_ERROR_CAMERA_DEVICE: return "camera device error";
|
||||
case ACAMERA_ERROR_CAMERA_SERVICE: return "camera service error";
|
||||
case ACAMERA_ERROR_SESSION_CLOSED: return "session closed";
|
||||
case ACAMERA_ERROR_INVALID_OPERATION: return "invalid operation";
|
||||
case ACAMERA_ERROR_STREAM_CONFIGURE_FAIL: return "configure failure";
|
||||
case ACAMERA_ERROR_CAMERA_IN_USE: return "camera in use";
|
||||
case ACAMERA_ERROR_MAX_CAMERA_IN_USE: return "max cameras in use";
|
||||
case ACAMERA_ERROR_CAMERA_DISABLED: return "camera disabled";
|
||||
case ACAMERA_ERROR_PERMISSION_DENIED: return "permission denied";
|
||||
case ACAMERA_ERROR_UNSUPPORTED_OPERATION: return "unsupported operation";
|
||||
default: break;
|
||||
}
|
||||
|
||||
return NULL; // unknown error
|
||||
}
|
||||
|
||||
static int SetCameraError(const char *what, const camera_status_t rc)
|
||||
{
|
||||
return SetErrorStr(what, CameraStatusStr(rc), (int) rc);
|
||||
}
|
||||
|
||||
static const char *MediaStatusStr(const media_status_t rc)
|
||||
{
|
||||
switch (rc) {
|
||||
case AMEDIA_OK: return "no error";
|
||||
case AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE: return "insuffient resources";
|
||||
case AMEDIACODEC_ERROR_RECLAIMED: return "reclaimed";
|
||||
case AMEDIA_ERROR_UNKNOWN: return "unknown error";
|
||||
case AMEDIA_ERROR_MALFORMED: return "malformed";
|
||||
case AMEDIA_ERROR_UNSUPPORTED: return "unsupported";
|
||||
case AMEDIA_ERROR_INVALID_OBJECT: return "invalid object";
|
||||
case AMEDIA_ERROR_INVALID_PARAMETER: return "invalid parameter";
|
||||
case AMEDIA_ERROR_INVALID_OPERATION: return "invalid operation";
|
||||
case AMEDIA_ERROR_END_OF_STREAM: return "end of stream";
|
||||
case AMEDIA_ERROR_IO: return "i/o error";
|
||||
case AMEDIA_ERROR_WOULD_BLOCK: return "operation would block";
|
||||
case AMEDIA_DRM_NOT_PROVISIONED: return "DRM not provisioned";
|
||||
case AMEDIA_DRM_RESOURCE_BUSY: return "DRM resource busy";
|
||||
case AMEDIA_DRM_DEVICE_REVOKED: return "DRM device revoked";
|
||||
case AMEDIA_DRM_SHORT_BUFFER: return "DRM short buffer";
|
||||
case AMEDIA_DRM_SESSION_NOT_OPENED: return "DRM session not opened";
|
||||
case AMEDIA_DRM_TAMPER_DETECTED: return "DRM tampering detected";
|
||||
case AMEDIA_DRM_VERIFY_FAILED: return "DRM verify failed";
|
||||
case AMEDIA_DRM_NEED_KEY: return "DRM need key";
|
||||
case AMEDIA_DRM_LICENSE_EXPIRED: return "DRM license expired";
|
||||
case AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE: return "no buffer available";
|
||||
case AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED: return "maximum images acquired";
|
||||
case AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE: return "cannot lock image";
|
||||
case AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE: return "cannot unlock image";
|
||||
case AMEDIA_IMGREADER_IMAGE_NOT_LOCKED: return "image not locked";
|
||||
default: break;
|
||||
}
|
||||
|
||||
return NULL; // unknown error
|
||||
}
|
||||
|
||||
static int SetMediaError(const char *what, const media_status_t rc)
|
||||
{
|
||||
return SetErrorStr(what, MediaStatusStr(rc), (int) rc);
|
||||
}
|
||||
|
||||
|
||||
static ACameraManager *cameraMgr = NULL;
|
||||
|
||||
static int CreateCameraManager(void)
|
||||
{
|
||||
SDL_assert(cameraMgr == NULL);
|
||||
|
||||
cameraMgr = pACameraManager_create();
|
||||
if (!cameraMgr) {
|
||||
return SDL_SetError("Error creating ACameraManager");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void DestroyCameraManager(void)
|
||||
{
|
||||
if (cameraMgr) {
|
||||
pACameraManager_delete(cameraMgr);
|
||||
cameraMgr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static Uint32 format_android_to_sdl(Uint32 fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(x, y) case x: return y
|
||||
CASE(AIMAGE_FORMAT_YUV_420_888, SDL_PIXELFORMAT_NV12);
|
||||
CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565);
|
||||
CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_XRGB8888);
|
||||
CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888);
|
||||
CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888);
|
||||
//CASE(AIMAGE_FORMAT_RGBA_FP16, SDL_PIXELFORMAT_UNKNOWN); // 64bits
|
||||
//CASE(AIMAGE_FORMAT_RAW_PRIVATE, SDL_PIXELFORMAT_UNKNOWN);
|
||||
//CASE(AIMAGE_FORMAT_JPEG, SDL_PIXELFORMAT_UNKNOWN);
|
||||
#undef CASE
|
||||
default: break;
|
||||
}
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
//SDL_Log("Unknown format AIMAGE_FORMAT '%d'", fmt);
|
||||
#endif
|
||||
|
||||
return SDL_PIXELFORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
static Uint32 format_sdl_to_android(Uint32 fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(x, y) case y: return x
|
||||
CASE(AIMAGE_FORMAT_YUV_420_888, SDL_PIXELFORMAT_NV12);
|
||||
CASE(AIMAGE_FORMAT_RGB_565, SDL_PIXELFORMAT_RGB565);
|
||||
CASE(AIMAGE_FORMAT_RGB_888, SDL_PIXELFORMAT_XRGB8888);
|
||||
CASE(AIMAGE_FORMAT_RGBA_8888, SDL_PIXELFORMAT_RGBA8888);
|
||||
CASE(AIMAGE_FORMAT_RGBX_8888, SDL_PIXELFORMAT_RGBX8888);
|
||||
#undef CASE
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int ANDROIDCAMERA_WaitDevice(SDL_CameraDevice *device)
|
||||
{
|
||||
return 0; // this isn't used atm, since we run our own thread via onImageAvailable callbacks.
|
||||
}
|
||||
|
||||
static int ANDROIDCAMERA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
|
||||
{
|
||||
int retval = 1;
|
||||
media_status_t res;
|
||||
AImage *image = NULL;
|
||||
|
||||
res = pAImageReader_acquireNextImage(device->hidden->reader, &image);
|
||||
// We could also use this one:
|
||||
//res = AImageReader_acquireLatestImage(device->hidden->reader, &image);
|
||||
|
||||
SDL_assert(res != AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE); // we should only be here if onImageAvailable was called.
|
||||
|
||||
if (res != AMEDIA_OK) {
|
||||
return SetMediaError("Error AImageReader_acquireNextImage", res);
|
||||
}
|
||||
|
||||
int64_t atimestamp = 0;
|
||||
if (pAImage_getTimestamp(image, &atimestamp) == AMEDIA_OK) {
|
||||
*timestampNS = (Uint64) atimestamp;
|
||||
} else {
|
||||
*timestampNS = 0;
|
||||
}
|
||||
|
||||
// !!! FIXME: this currently copies the data to the surface (see FIXME about non-contiguous planar surfaces, but in theory we could just keep this locked until ReleaseFrame...
|
||||
int32_t num_planes = 0;
|
||||
pAImage_getNumberOfPlanes(image, &num_planes);
|
||||
|
||||
if ((num_planes == 3) && (device->spec.format == SDL_PIXELFORMAT_NV12)) {
|
||||
num_planes--; // treat the interleaved planes as one.
|
||||
}
|
||||
|
||||
// !!! FIXME: we have an open issue in SDL3 to allow SDL_Surface to support non-contiguous planar data, but we don't have it yet.
|
||||
size_t buflen = 0;
|
||||
for (int i = 0; (i < num_planes) && (i < 3); i++) {
|
||||
uint8_t *data = NULL;
|
||||
int32_t datalen = 0;
|
||||
pAImage_getPlaneData(image, i, &data, &datalen);
|
||||
buflen += (int) datalen;
|
||||
}
|
||||
|
||||
frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
|
||||
if (frame->pixels == NULL) {
|
||||
retval = -1;
|
||||
} else {
|
||||
int32_t row_stride = 0;
|
||||
Uint8 *dst = frame->pixels;
|
||||
pAImage_getPlaneRowStride(image, 0, &row_stride);
|
||||
frame->pitch = (int) row_stride; // this is what SDL3 currently expects, probably incorrectly.
|
||||
|
||||
for (int i = 0; (i < num_planes) && (i < 3); i++) {
|
||||
uint8_t *data = NULL;
|
||||
int32_t datalen = 0;
|
||||
pAImage_getPlaneData(image, i, &data, &datalen);
|
||||
const void *src = data;
|
||||
SDL_memcpy(dst, src, datalen);
|
||||
dst += datalen;
|
||||
}
|
||||
}
|
||||
|
||||
pAImage_delete(image);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void ANDROIDCAMERA_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
|
||||
{
|
||||
// !!! FIXME: this currently copies the data to the surface, but in theory we could just keep the AImage until ReleaseFrame...
|
||||
SDL_aligned_free(frame->pixels);
|
||||
}
|
||||
|
||||
static void onImageAvailable(void *context, AImageReader *reader)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onImageAvailable");
|
||||
#endif
|
||||
SDL_CameraDevice *device = (SDL_CameraDevice *) context;
|
||||
SDL_CameraThreadIterate(device);
|
||||
}
|
||||
|
||||
static void onDisconnected(void *context, ACameraDevice *device)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onDisconnected");
|
||||
#endif
|
||||
SDL_CameraDeviceDisconnected((SDL_CameraDevice *) context);
|
||||
}
|
||||
|
||||
static void onError(void *context, ACameraDevice *device, int error)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onError");
|
||||
#endif
|
||||
SDL_CameraDeviceDisconnected((SDL_CameraDevice *) context);
|
||||
}
|
||||
|
||||
static void onClosed(void* context, ACameraCaptureSession *session)
|
||||
{
|
||||
// SDL_CameraDevice *_this = (SDL_CameraDevice *) context;
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onClosed");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onReady(void* context, ACameraCaptureSession *session)
|
||||
{
|
||||
// SDL_CameraDevice *_this = (SDL_CameraDevice *) context;
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onReady");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void onActive(void* context, ACameraCaptureSession *session)
|
||||
{
|
||||
// SDL_CameraDevice *_this = (SDL_CameraDevice *) context;
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onActive");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ANDROIDCAMERA_CloseDevice(SDL_CameraDevice *device)
|
||||
{
|
||||
if (device && device->hidden) {
|
||||
struct SDL_PrivateCameraData *hidden = device->hidden;
|
||||
device->hidden = NULL;
|
||||
|
||||
if (hidden->reader) {
|
||||
pAImageReader_setImageListener(hidden->reader, NULL);
|
||||
}
|
||||
|
||||
if (hidden->session) {
|
||||
pACameraCaptureSession_close(hidden->session);
|
||||
}
|
||||
|
||||
if (hidden->request) {
|
||||
pACaptureRequest_free(hidden->request);
|
||||
}
|
||||
|
||||
if (hidden->outputTarget) {
|
||||
pACameraOutputTarget_free(hidden->outputTarget);
|
||||
}
|
||||
|
||||
if (hidden->sessionOutputContainer) {
|
||||
pACaptureSessionOutputContainer_free(hidden->sessionOutputContainer);
|
||||
}
|
||||
|
||||
if (hidden->sessionOutput) {
|
||||
pACaptureSessionOutput_free(hidden->sessionOutput);
|
||||
}
|
||||
|
||||
// we don't free hidden->window here, it'll be cleaned up by AImageReader_delete.
|
||||
|
||||
if (hidden->reader) {
|
||||
pAImageReader_delete(hidden->reader);
|
||||
}
|
||||
|
||||
if (hidden->device) {
|
||||
pACameraDevice_close(hidden->device);
|
||||
}
|
||||
|
||||
SDL_free(hidden);
|
||||
}
|
||||
}
|
||||
|
||||
// this is where the "opening" of the camera happens, after permission is granted.
|
||||
static int PrepareCamera(SDL_CameraDevice *device)
|
||||
{
|
||||
SDL_assert(device->hidden != NULL);
|
||||
|
||||
camera_status_t res;
|
||||
media_status_t res2;
|
||||
|
||||
ACameraDevice_StateCallbacks dev_callbacks;
|
||||
SDL_zero(dev_callbacks);
|
||||
dev_callbacks.context = device;
|
||||
dev_callbacks.onDisconnected = onDisconnected;
|
||||
dev_callbacks.onError = onError;
|
||||
|
||||
ACameraCaptureSession_stateCallbacks capture_callbacks;
|
||||
SDL_zero(capture_callbacks);
|
||||
capture_callbacks.context = device;
|
||||
capture_callbacks.onClosed = onClosed;
|
||||
capture_callbacks.onReady = onReady;
|
||||
capture_callbacks.onActive = onActive;
|
||||
|
||||
AImageReader_ImageListener imglistener;
|
||||
SDL_zero(imglistener);
|
||||
imglistener.context = device;
|
||||
imglistener.onImageAvailable = onImageAvailable;
|
||||
|
||||
// just in case SDL_OpenCameraDevice is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy.
|
||||
const SDL_CameraSpec *spec = &device->hidden->requested_spec;
|
||||
|
||||
if ((res = pACameraManager_openCamera(cameraMgr, (const char *) device->handle, &dev_callbacks, &device->hidden->device)) != ACAMERA_OK) {
|
||||
return SetCameraError("Failed to open camera", res);
|
||||
} else if ((res2 = pAImageReader_new(spec->width, spec->height, format_sdl_to_android(spec->format), 10 /* nb buffers */, &device->hidden->reader)) != AMEDIA_OK) {
|
||||
return SetMediaError("Error AImageReader_new", res2);
|
||||
} else if ((res2 = pAImageReader_getWindow(device->hidden->reader, &device->hidden->window)) != AMEDIA_OK) {
|
||||
return SetMediaError("Error AImageReader_getWindow", res2);
|
||||
} else if ((res = pACaptureSessionOutput_create(device->hidden->window, &device->hidden->sessionOutput)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACaptureSessionOutput_create", res);
|
||||
} else if ((res = pACaptureSessionOutputContainer_create(&device->hidden->sessionOutputContainer)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACaptureSessionOutputContainer_create", res);
|
||||
} else if ((res = pACaptureSessionOutputContainer_add(device->hidden->sessionOutputContainer, device->hidden->sessionOutput)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACaptureSessionOutputContainer_add", res);
|
||||
} else if ((res = pACameraOutputTarget_create(device->hidden->window, &device->hidden->outputTarget)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACameraOutputTarget_create", res);
|
||||
} else if ((res = pACameraDevice_createCaptureRequest(device->hidden->device, TEMPLATE_RECORD, &device->hidden->request)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACameraDevice_createCaptureRequest", res);
|
||||
} else if ((res = pACaptureRequest_addTarget(device->hidden->request, device->hidden->outputTarget)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACaptureRequest_addTarget", res);
|
||||
} else if ((res = pACameraDevice_createCaptureSession(device->hidden->device, device->hidden->sessionOutputContainer, &capture_callbacks, &device->hidden->session)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACameraDevice_createCaptureSession", res);
|
||||
} else if ((res = pACameraCaptureSession_setRepeatingRequest(device->hidden->session, NULL, 1, &device->hidden->request, NULL)) != ACAMERA_OK) {
|
||||
return SetCameraError("Error ACameraCaptureSession_setRepeatingRequest", res);
|
||||
} else if ((res2 = pAImageReader_setImageListener(device->hidden->reader, &imglistener)) != AMEDIA_OK) {
|
||||
return SetMediaError("Error AImageReader_setImageListener", res2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void SDLCALL CameraPermissionCallback(void *userdata, const char *permission, SDL_bool granted)
|
||||
{
|
||||
SDL_CameraDevice *device = (SDL_CameraDevice *) userdata;
|
||||
if (device->hidden != NULL) { // if device was already closed, don't send an event.
|
||||
if (!granted) {
|
||||
SDL_CameraDevicePermissionOutcome(device, SDL_FALSE); // sorry, permission denied.
|
||||
} else if (PrepareCamera(device) < 0) { // permission given? Actually open the camera now.
|
||||
// uhoh, setup failed; since the app thinks we already "opened" the device, mark it as disconnected and don't report the permission.
|
||||
SDL_CameraDeviceDisconnected(device);
|
||||
} else {
|
||||
// okay! We have permission to use the camera _and_ opening the hardware worked out, report that the camera is usable!
|
||||
SDL_CameraDevicePermissionOutcome(device, SDL_TRUE); // go go go!
|
||||
}
|
||||
}
|
||||
|
||||
UnrefPhysicalCameraDevice(device); // we ref'd this in OpenDevice, release the extra reference.
|
||||
}
|
||||
|
||||
|
||||
static int ANDROIDCAMERA_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
|
||||
{
|
||||
#if 0 // !!! FIXME: for now, we'll just let this fail if it is going to fail, without checking for this
|
||||
/* Cannot open a second camera, while the first one is opened.
|
||||
* If you want to play several camera, they must all be opened first, then played.
|
||||
*
|
||||
* https://developer.android.com/reference/android/hardware/camera2/CameraManager
|
||||
* "All camera devices intended to be operated concurrently, must be opened using openCamera(String, CameraDevice.StateCallback, Handler),
|
||||
* before configuring sessions on any of the camera devices. * "
|
||||
*
|
||||
*/
|
||||
if (CheckDevicePlaying()) {
|
||||
return SDL_SetError("A camera is already playing");
|
||||
}
|
||||
#endif
|
||||
|
||||
device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
|
||||
if (device->hidden == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
RefPhysicalCameraDevice(device); // ref'd until permission callback fires.
|
||||
|
||||
// just in case SDL_OpenCameraDevice is overwriting device->spec as CameraPermissionCallback runs, we work from a different copy.
|
||||
SDL_copyp(&device->hidden->requested_spec, spec);
|
||||
if (SDL_AndroidRequestPermission("android.permission.CAMERA", CameraPermissionCallback, device) < 0) {
|
||||
UnrefPhysicalCameraDevice(device);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0; // we don't open the camera until permission is granted, so always succeed for now.
|
||||
}
|
||||
|
||||
static void ANDROIDCAMERA_FreeDeviceHandle(SDL_CameraDevice *device)
|
||||
{
|
||||
if (device) {
|
||||
SDL_free(device->handle);
|
||||
}
|
||||
}
|
||||
|
||||
static void GatherCameraSpecs(const char *devid, CameraFormatAddData *add_data, char **fullname, SDL_CameraPosition *position)
|
||||
{
|
||||
SDL_zerop(add_data);
|
||||
|
||||
ACameraMetadata *metadata = NULL;
|
||||
ACameraMetadata_const_entry cfgentry;
|
||||
ACameraMetadata_const_entry durentry;
|
||||
ACameraMetadata_const_entry infoentry;
|
||||
|
||||
// This can fail with an "unknown error" (with `adb logcat` reporting "no such file or directory")
|
||||
// for "LEGACY" level cameras. I saw this happen on a 30-dollar budget phone I have for testing
|
||||
// (but a different brand budget phone worked, so it's not strictly the low-end of Android devices).
|
||||
// LEGACY devices are seen by onCameraAvailable, but are not otherwise accessible through
|
||||
// libcamera2ndk. The Java camera2 API apparently _can_ access these cameras, but we're going on
|
||||
// without them here for now, in hopes that such hardware is a dying breed.
|
||||
if (pACameraManager_getCameraCharacteristics(cameraMgr, devid, &metadata) != ACAMERA_OK) {
|
||||
return; // oh well.
|
||||
} else if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &cfgentry) != ACAMERA_OK) {
|
||||
pACameraMetadata_free(metadata);
|
||||
return; // oh well.
|
||||
} else if (pACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_MIN_FRAME_DURATIONS, &durentry) != ACAMERA_OK) {
|
||||
pACameraMetadata_free(metadata);
|
||||
return; // oh well.
|
||||
}
|
||||
|
||||
*fullname = NULL;
|
||||
if (pACameraMetadata_getConstEntry(metadata, ACAMERA_INFO_VERSION, &infoentry) == ACAMERA_OK) {
|
||||
*fullname = (char *) SDL_malloc(infoentry.count + 1);
|
||||
if (*fullname) {
|
||||
SDL_strlcpy(*fullname, (const char *) infoentry.data.u8, infoentry.count + 1);
|
||||
}
|
||||
}
|
||||
|
||||
ACameraMetadata_const_entry posentry;
|
||||
if (pACameraMetadata_getConstEntry(metadata, ACAMERA_LENS_FACING, &posentry) == ACAMERA_OK) { // ignore this if it fails.
|
||||
if (*posentry.data.u8 == ACAMERA_LENS_FACING_FRONT) {
|
||||
*position = SDL_CAMERA_POSITION_FRONT_FACING;
|
||||
if (!*fullname) {
|
||||
*fullname = SDL_strdup("Front-facing camera");
|
||||
}
|
||||
} else if (*posentry.data.u8 == ACAMERA_LENS_FACING_BACK) {
|
||||
*position = SDL_CAMERA_POSITION_BACK_FACING;
|
||||
if (!*fullname) {
|
||||
*fullname = SDL_strdup("Back-facing camera");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!*fullname) {
|
||||
*fullname = SDL_strdup("Generic camera"); // we tried.
|
||||
}
|
||||
|
||||
const int32_t *i32ptr = cfgentry.data.i32;
|
||||
for (int i = 0; i < cfgentry.count; i++, i32ptr += 4) {
|
||||
const int32_t fmt = i32ptr[0];
|
||||
const int w = (int) i32ptr[1];
|
||||
const int h = (int) i32ptr[2];
|
||||
const int32_t type = i32ptr[3];
|
||||
Uint32 sdlfmt;
|
||||
|
||||
if (type == ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) {
|
||||
continue;
|
||||
} else if ((w <= 0) || (h <= 0)) {
|
||||
continue;
|
||||
} else if ((sdlfmt = format_android_to_sdl(fmt)) == SDL_PIXELFORMAT_UNKNOWN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#if 0 // !!! FIXME: these all come out with 0 durations on my test phone. :(
|
||||
const int64_t *i64ptr = durentry.data.i64;
|
||||
for (int j = 0; j < durentry.count; j++, i64ptr += 4) {
|
||||
const int32_t fpsfmt = (int32_t) i64ptr[0];
|
||||
const int fpsw = (int) i64ptr[1];
|
||||
const int fpsh = (int) i64ptr[2];
|
||||
const long long duration = (long long) i64ptr[3];
|
||||
SDL_Log("CAMERA: possible fps %s %dx%d duration=%lld", SDL_GetPixelFormatName(format_android_to_sdl(fpsfmt)), fpsw, fpsh, duration);
|
||||
if ((duration > 0) && (fpsfmt == fmt) && (fpsw == w) && (fpsh == h)) {
|
||||
SDL_AddCameraFormat(add_data, sdlfmt, w, h, duration, 1000000000);
|
||||
}
|
||||
}
|
||||
#else
|
||||
SDL_AddCameraFormat(add_data, sdlfmt, w, h, 1, 30);
|
||||
#endif
|
||||
}
|
||||
|
||||
pACameraMetadata_free(metadata);
|
||||
}
|
||||
|
||||
static SDL_bool FindAndroidCameraDeviceByID(SDL_CameraDevice *device, void *userdata)
|
||||
{
|
||||
const char *devid = (const char *) userdata;
|
||||
return (SDL_strcmp(devid, (const char *) device->handle) == 0);
|
||||
}
|
||||
|
||||
static void MaybeAddDevice(const char *devid)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: MaybeAddDevice('%s')", devid);
|
||||
#endif
|
||||
|
||||
if (SDL_FindPhysicalCameraDeviceByCallback(FindAndroidCameraDeviceByID, (void *) devid)) {
|
||||
return; // already have this one.
|
||||
}
|
||||
|
||||
SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
|
||||
char *fullname = NULL;
|
||||
CameraFormatAddData add_data;
|
||||
GatherCameraSpecs(devid, &add_data, &fullname, &position);
|
||||
if (add_data.num_specs > 0) {
|
||||
char *namecpy = SDL_strdup(devid);
|
||||
if (namecpy) {
|
||||
SDL_CameraDevice *device = SDL_AddCameraDevice(fullname, position, add_data.num_specs, add_data.specs, namecpy);
|
||||
if (!device) {
|
||||
SDL_free(namecpy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(fullname);
|
||||
SDL_free(add_data.specs);
|
||||
}
|
||||
|
||||
// note that camera "availability" covers both hotplugging and whether another
|
||||
// has the device opened, but for something like Android, it's probably fine
|
||||
// to treat both unplugging and loss of access as disconnection events. When
|
||||
// the other app closes the camera, we get an available event as if it was
|
||||
// just plugged back in.
|
||||
|
||||
static void onCameraAvailable(void *context, const char *cameraId)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onCameraAvailable('%s')", cameraId);
|
||||
#endif
|
||||
SDL_assert(cameraId != NULL);
|
||||
MaybeAddDevice(cameraId);
|
||||
}
|
||||
|
||||
static void onCameraUnavailable(void *context, const char *cameraId)
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: CB onCameraUnvailable('%s')", cameraId);
|
||||
#endif
|
||||
|
||||
SDL_assert(cameraId != NULL);
|
||||
|
||||
// THIS CALLBACK FIRES WHEN YOU OPEN THE DEVICE YOURSELF. :(
|
||||
// Make sure we don't have the device opened, in which case onDisconnected will fire instead if actually lost.
|
||||
SDL_CameraDevice *device = SDL_FindPhysicalCameraDeviceByCallback(FindAndroidCameraDeviceByID, (void *) cameraId);
|
||||
if (device && !device->hidden) {
|
||||
SDL_CameraDeviceDisconnected(device);
|
||||
}
|
||||
}
|
||||
|
||||
static const ACameraManager_AvailabilityCallbacks camera_availability_listener = {
|
||||
NULL,
|
||||
onCameraAvailable,
|
||||
onCameraUnavailable
|
||||
};
|
||||
|
||||
static void ANDROIDCAMERA_DetectDevices(void)
|
||||
{
|
||||
ACameraIdList *list = NULL;
|
||||
camera_status_t res = pACameraManager_getCameraIdList(cameraMgr, &list);
|
||||
|
||||
if ((res == ACAMERA_OK) && list) {
|
||||
const int total = list->numCameras;
|
||||
for (int i = 0; i < total; i++) {
|
||||
MaybeAddDevice(list->cameraIds[i]);
|
||||
}
|
||||
|
||||
pACameraManager_deleteCameraIdList(list);
|
||||
}
|
||||
|
||||
pACameraManager_registerAvailabilityCallback(cameraMgr, &camera_availability_listener);
|
||||
}
|
||||
|
||||
static void ANDROIDCAMERA_Deinitialize(void)
|
||||
{
|
||||
pACameraManager_unregisterAvailabilityCallback(cameraMgr, &camera_availability_listener);
|
||||
DestroyCameraManager();
|
||||
|
||||
dlclose(libcamera2ndk);
|
||||
libcamera2ndk = NULL;
|
||||
pACameraManager_create = NULL;
|
||||
pACameraManager_registerAvailabilityCallback = NULL;
|
||||
pACameraManager_unregisterAvailabilityCallback = NULL;
|
||||
pACameraManager_getCameraIdList = NULL;
|
||||
pACameraManager_deleteCameraIdList = NULL;
|
||||
pACameraCaptureSession_close = NULL;
|
||||
pACaptureRequest_free = NULL;
|
||||
pACameraOutputTarget_free = NULL;
|
||||
pACameraDevice_close = NULL;
|
||||
pACameraManager_delete = NULL;
|
||||
pACaptureSessionOutputContainer_free = NULL;
|
||||
pACaptureSessionOutput_free = NULL;
|
||||
pACameraManager_openCamera = NULL;
|
||||
pACameraDevice_createCaptureRequest = NULL;
|
||||
pACameraDevice_createCaptureSession = NULL;
|
||||
pACameraManager_getCameraCharacteristics = NULL;
|
||||
pACameraMetadata_free = NULL;
|
||||
pACameraMetadata_getConstEntry = NULL;
|
||||
pACameraCaptureSession_setRepeatingRequest = NULL;
|
||||
pACameraOutputTarget_create = NULL;
|
||||
pACaptureRequest_addTarget = NULL;
|
||||
pACaptureSessionOutputContainer_add = NULL;
|
||||
pACaptureSessionOutputContainer_create = NULL;
|
||||
pACaptureSessionOutput_create = NULL;
|
||||
|
||||
dlclose(libmediandk);
|
||||
libmediandk = NULL;
|
||||
pAImage_delete = NULL;
|
||||
pAImage_getTimestamp = NULL;
|
||||
pAImage_getNumberOfPlanes = NULL;
|
||||
pAImage_getPlaneRowStride = NULL;
|
||||
pAImage_getPlaneData = NULL;
|
||||
pAImageReader_acquireNextImage = NULL;
|
||||
pAImageReader_delete = NULL;
|
||||
pAImageReader_setImageListener = NULL;
|
||||
pAImageReader_getWindow = NULL;
|
||||
pAImageReader_new = NULL;
|
||||
}
|
||||
|
||||
static SDL_bool ANDROIDCAMERA_Init(SDL_CameraDriverImpl *impl)
|
||||
{
|
||||
// !!! FIXME: slide this off into a subroutine
|
||||
// system libraries are in android-24 and later; we currently target android-16 and later, so check if they exist at runtime.
|
||||
void *libcamera2 = dlopen("libcamera2ndk.so", RTLD_NOW | RTLD_LOCAL);
|
||||
if (!libcamera2) {
|
||||
SDL_Log("CAMERA: libcamera2ndk.so can't be loaded: %s", dlerror());
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
void *libmedia = dlopen("libmediandk.so", RTLD_NOW | RTLD_LOCAL);
|
||||
if (!libmedia) {
|
||||
SDL_Log("CAMERA: libmediandk.so can't be loaded: %s", dlerror());
|
||||
dlclose(libcamera2);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
SDL_bool okay = SDL_TRUE;
|
||||
#define LOADSYM(lib, fn) if (okay) { p##fn = (pfn##fn) dlsym(lib, #fn); if (!p##fn) { SDL_Log("CAMERA: symbol '%s' can't be found in %s: %s", #fn, #lib "ndk.so", dlerror()); okay = SDL_FALSE; } }
|
||||
//#define LOADSYM(lib, fn) p##fn = (pfn##fn) fn
|
||||
LOADSYM(libcamera2, ACameraManager_create);
|
||||
LOADSYM(libcamera2, ACameraManager_registerAvailabilityCallback);
|
||||
LOADSYM(libcamera2, ACameraManager_unregisterAvailabilityCallback);
|
||||
LOADSYM(libcamera2, ACameraManager_getCameraIdList);
|
||||
LOADSYM(libcamera2, ACameraManager_deleteCameraIdList);
|
||||
LOADSYM(libcamera2, ACameraCaptureSession_close);
|
||||
LOADSYM(libcamera2, ACaptureRequest_free);
|
||||
LOADSYM(libcamera2, ACameraOutputTarget_free);
|
||||
LOADSYM(libcamera2, ACameraDevice_close);
|
||||
LOADSYM(libcamera2, ACameraManager_delete);
|
||||
LOADSYM(libcamera2, ACaptureSessionOutputContainer_free);
|
||||
LOADSYM(libcamera2, ACaptureSessionOutput_free);
|
||||
LOADSYM(libcamera2, ACameraManager_openCamera);
|
||||
LOADSYM(libcamera2, ACameraDevice_createCaptureRequest);
|
||||
LOADSYM(libcamera2, ACameraDevice_createCaptureSession);
|
||||
LOADSYM(libcamera2, ACameraManager_getCameraCharacteristics);
|
||||
LOADSYM(libcamera2, ACameraMetadata_free);
|
||||
LOADSYM(libcamera2, ACameraMetadata_getConstEntry);
|
||||
LOADSYM(libcamera2, ACameraCaptureSession_setRepeatingRequest);
|
||||
LOADSYM(libcamera2, ACameraOutputTarget_create);
|
||||
LOADSYM(libcamera2, ACaptureRequest_addTarget);
|
||||
LOADSYM(libcamera2, ACaptureSessionOutputContainer_add);
|
||||
LOADSYM(libcamera2, ACaptureSessionOutputContainer_create);
|
||||
LOADSYM(libcamera2, ACaptureSessionOutput_create);
|
||||
LOADSYM(libmedia, AImage_delete);
|
||||
LOADSYM(libmedia, AImage_getTimestamp);
|
||||
LOADSYM(libmedia, AImage_getNumberOfPlanes);
|
||||
LOADSYM(libmedia, AImage_getPlaneRowStride);
|
||||
LOADSYM(libmedia, AImage_getPlaneData);
|
||||
LOADSYM(libmedia, AImageReader_acquireNextImage);
|
||||
LOADSYM(libmedia, AImageReader_delete);
|
||||
LOADSYM(libmedia, AImageReader_setImageListener);
|
||||
LOADSYM(libmedia, AImageReader_getWindow);
|
||||
LOADSYM(libmedia, AImageReader_new);
|
||||
LOADSYM(libmedia, AImage_getWidth);
|
||||
LOADSYM(libmedia, AImage_getHeight);
|
||||
|
||||
#undef LOADSYM
|
||||
|
||||
if (!okay) {
|
||||
dlclose(libmedia);
|
||||
dlclose(libcamera2);
|
||||
}
|
||||
|
||||
if (CreateCameraManager() < 0) {
|
||||
dlclose(libmedia);
|
||||
dlclose(libcamera2);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
libcamera2ndk = libcamera2;
|
||||
libmediandk = libmedia;
|
||||
|
||||
impl->DetectDevices = ANDROIDCAMERA_DetectDevices;
|
||||
impl->OpenDevice = ANDROIDCAMERA_OpenDevice;
|
||||
impl->CloseDevice = ANDROIDCAMERA_CloseDevice;
|
||||
impl->WaitDevice = ANDROIDCAMERA_WaitDevice;
|
||||
impl->AcquireFrame = ANDROIDCAMERA_AcquireFrame;
|
||||
impl->ReleaseFrame = ANDROIDCAMERA_ReleaseFrame;
|
||||
impl->FreeDeviceHandle = ANDROIDCAMERA_FreeDeviceHandle;
|
||||
impl->Deinitialize = ANDROIDCAMERA_Deinitialize;
|
||||
|
||||
impl->ProvidesOwnCallbackThread = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
CameraBootStrap ANDROIDCAMERA_bootstrap = {
|
||||
"android", "SDL Android camera driver", ANDROIDCAMERA_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_CAMERA_DRIVER_COREMEDIA
|
||||
|
||||
#include "../SDL_syscamera.h"
|
||||
#include "../SDL_camera_c.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <CoreMedia/CoreMedia.h>
|
||||
|
||||
/*
|
||||
* Need to link with:: CoreMedia CoreVideo
|
||||
*
|
||||
* Add in pInfo.list:
|
||||
* <key>NSCameraUsageDescription</key> <string>Access camera</string>
|
||||
*
|
||||
*
|
||||
* MACOSX:
|
||||
* Add to the Code Sign Entitlement file:
|
||||
* <key>com.apple.security.device.camera</key> <true/>
|
||||
*/
|
||||
|
||||
static Uint32 CoreMediaFormatToSDL(FourCharCode fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(x, y) case x: return y
|
||||
// the 16LE ones should use 16BE if we're on a Bigendian system like PowerPC,
|
||||
// but at current time there is no bigendian Apple platform that has CoreMedia.
|
||||
CASE(kCMPixelFormat_16LE555, SDL_PIXELFORMAT_RGB555);
|
||||
CASE(kCMPixelFormat_16LE5551, SDL_PIXELFORMAT_RGBA5551);
|
||||
CASE(kCMPixelFormat_16LE565, SDL_PIXELFORMAT_RGB565);
|
||||
CASE(kCMPixelFormat_24RGB, SDL_PIXELFORMAT_RGB24);
|
||||
CASE(kCMPixelFormat_32ARGB, SDL_PIXELFORMAT_ARGB32);
|
||||
CASE(kCMPixelFormat_32BGRA, SDL_PIXELFORMAT_BGRA32);
|
||||
CASE(kCMPixelFormat_422YpCbCr8, SDL_PIXELFORMAT_YUY2);
|
||||
CASE(kCMPixelFormat_422YpCbCr8_yuvs, SDL_PIXELFORMAT_UYVY);
|
||||
#undef CASE
|
||||
default:
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: Unknown format FourCharCode '%d'", (int) fmt);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
return SDL_PIXELFORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
@class SDLCaptureVideoDataOutputSampleBufferDelegate;
|
||||
|
||||
// just a simple wrapper to help ARC manage memory...
|
||||
@interface SDLPrivateCameraData : NSObject
|
||||
@property(nonatomic, retain) AVCaptureSession *session;
|
||||
@property(nonatomic, retain) SDLCaptureVideoDataOutputSampleBufferDelegate *delegate;
|
||||
@property(nonatomic, assign) CMSampleBufferRef current_sample;
|
||||
@end
|
||||
|
||||
@implementation SDLPrivateCameraData
|
||||
@end
|
||||
|
||||
|
||||
static SDL_bool CheckCameraPermissions(SDL_CameraDevice *device)
|
||||
{
|
||||
if (device->permission == 0) { // still expecting a permission result.
|
||||
if (@available(macOS 14, *)) {
|
||||
const AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
||||
if (status != AVAuthorizationStatusNotDetermined) { // NotDetermined == still waiting for an answer from the user.
|
||||
SDL_CameraDevicePermissionOutcome(device, (status == AVAuthorizationStatusAuthorized) ? SDL_TRUE : SDL_FALSE);
|
||||
}
|
||||
} else {
|
||||
SDL_CameraDevicePermissionOutcome(device, SDL_TRUE); // always allowed (or just unqueryable...?) on older macOS.
|
||||
}
|
||||
}
|
||||
|
||||
return (device->permission > 0);
|
||||
}
|
||||
|
||||
// this delegate just receives new video frames on a Grand Central Dispatch queue, and fires off the
|
||||
// main device thread iterate function directly to consume it.
|
||||
@interface SDLCaptureVideoDataOutputSampleBufferDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
|
||||
@property SDL_CameraDevice *device;
|
||||
-(id) init:(SDL_CameraDevice *) dev;
|
||||
-(void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
|
||||
@end
|
||||
|
||||
@implementation SDLCaptureVideoDataOutputSampleBufferDelegate
|
||||
|
||||
-(id) init:(SDL_CameraDevice *) dev {
|
||||
if ( self = [super init] ) {
|
||||
_device = dev;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
|
||||
{
|
||||
SDL_CameraDevice *device = self.device;
|
||||
if (!device || !device->hidden) {
|
||||
return; // oh well.
|
||||
}
|
||||
|
||||
if (!CheckCameraPermissions(device)) {
|
||||
return; // nothing to do right now, dump what is probably a completely black frame.
|
||||
}
|
||||
|
||||
SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
|
||||
hidden.current_sample = sampleBuffer;
|
||||
SDL_CameraThreadIterate(device);
|
||||
hidden.current_sample = NULL;
|
||||
}
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)output didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
|
||||
{
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: Drop frame.");
|
||||
#endif
|
||||
}
|
||||
@end
|
||||
|
||||
static int COREMEDIA_WaitDevice(SDL_CameraDevice *device)
|
||||
{
|
||||
return 0; // this isn't used atm, since we run our own thread out of Grand Central Dispatch.
|
||||
}
|
||||
|
||||
static int COREMEDIA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
|
||||
{
|
||||
int retval = 1;
|
||||
SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
|
||||
CMSampleBufferRef sample_buffer = hidden.current_sample;
|
||||
hidden.current_sample = NULL;
|
||||
SDL_assert(sample_buffer != NULL); // should only have been called from our delegate with a new frame.
|
||||
|
||||
CMSampleTimingInfo timinginfo;
|
||||
if (CMSampleBufferGetSampleTimingInfo(sample_buffer, 0, &timinginfo) == noErr) {
|
||||
*timestampNS = (Uint64) (CMTimeGetSeconds(timinginfo.presentationTimeStamp) * ((Float64) SDL_NS_PER_SECOND));
|
||||
} else {
|
||||
SDL_assert(!"this shouldn't happen, I think.");
|
||||
*timestampNS = 0;
|
||||
}
|
||||
|
||||
CVImageBufferRef image = CMSampleBufferGetImageBuffer(sample_buffer); // does not retain `image` (and we don't want it to).
|
||||
const int numPlanes = (int) CVPixelBufferGetPlaneCount(image);
|
||||
const int planar = (int) CVPixelBufferIsPlanar(image);
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
const int w = (int) CVPixelBufferGetWidth(image);
|
||||
const int h = (int) CVPixelBufferGetHeight(image);
|
||||
const int sz = (int) CVPixelBufferGetDataSize(image);
|
||||
const int pitch = (int) CVPixelBufferGetBytesPerRow(image);
|
||||
SDL_Log("CAMERA: buffer planar=%d numPlanes=%d %d x %d sz=%d pitch=%d", planar, numPlanes, w, h, sz, pitch);
|
||||
#endif
|
||||
|
||||
// !!! FIXME: this currently copies the data to the surface (see FIXME about non-contiguous planar surfaces, but in theory we could just keep this locked until ReleaseFrame...
|
||||
CVPixelBufferLockBaseAddress(image, 0);
|
||||
|
||||
if ((planar == 0) && (numPlanes == 0)) {
|
||||
const int pitch = (int) CVPixelBufferGetBytesPerRow(image);
|
||||
const size_t buflen = pitch * frame->h;
|
||||
frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
|
||||
if (frame->pixels == NULL) {
|
||||
retval = -1;
|
||||
} else {
|
||||
frame->pitch = pitch;
|
||||
SDL_memcpy(frame->pixels, CVPixelBufferGetBaseAddress(image), buflen);
|
||||
}
|
||||
} else {
|
||||
// !!! FIXME: we have an open issue in SDL3 to allow SDL_Surface to support non-contiguous planar data, but we don't have it yet.
|
||||
size_t buflen = 0;
|
||||
for (int i = 0; (i < numPlanes) && (i < 3); i++) {
|
||||
buflen += CVPixelBufferGetBytesPerRowOfPlane(image, i);
|
||||
}
|
||||
buflen *= frame->h;
|
||||
|
||||
frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
|
||||
if (frame->pixels == NULL) {
|
||||
retval = -1;
|
||||
} else {
|
||||
Uint8 *dst = frame->pixels;
|
||||
frame->pitch = (int) CVPixelBufferGetBytesPerRowOfPlane(image, 0); // this is what SDL3 currently expects, probably incorrectly.
|
||||
for (int i = 0; (i < numPlanes) && (i < 3); i++) {
|
||||
const void *src = CVPixelBufferGetBaseAddressOfPlane(image, i);
|
||||
const size_t pitch = CVPixelBufferGetBytesPerRowOfPlane(image, i);
|
||||
SDL_memcpy(dst, src, pitch * frame->h);
|
||||
dst += pitch * frame->h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CVPixelBufferUnlockBaseAddress(image, 0);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void COREMEDIA_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
|
||||
{
|
||||
// !!! FIXME: this currently copies the data to the surface, but in theory we could just keep this locked until ReleaseFrame...
|
||||
SDL_aligned_free(frame->pixels);
|
||||
}
|
||||
|
||||
static void COREMEDIA_CloseDevice(SDL_CameraDevice *device)
|
||||
{
|
||||
if (device && device->hidden) {
|
||||
SDLPrivateCameraData *hidden = (SDLPrivateCameraData *) CFBridgingRelease(device->hidden);
|
||||
device->hidden = NULL;
|
||||
|
||||
AVCaptureSession *session = hidden.session;
|
||||
if (session) {
|
||||
hidden.session = nil;
|
||||
[session stopRunning];
|
||||
[session removeInput:[session.inputs objectAtIndex:0]];
|
||||
[session removeOutput:(AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0]];
|
||||
session = nil;
|
||||
}
|
||||
|
||||
hidden.delegate = NULL;
|
||||
hidden.current_sample = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int COREMEDIA_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
|
||||
{
|
||||
AVCaptureDevice *avdevice = (__bridge AVCaptureDevice *) device->handle;
|
||||
|
||||
// Pick format that matches the spec
|
||||
const Uint32 sdlfmt = spec->format;
|
||||
const int w = spec->width;
|
||||
const int h = spec->height;
|
||||
const int rate = spec->interval_denominator;
|
||||
AVCaptureDeviceFormat *spec_format = nil;
|
||||
NSArray<AVCaptureDeviceFormat *> *formats = [avdevice formats];
|
||||
for (AVCaptureDeviceFormat *format in formats) {
|
||||
CMFormatDescriptionRef formatDescription = [format formatDescription];
|
||||
if (CoreMediaFormatToSDL(CMFormatDescriptionGetMediaSubType(formatDescription)) != sdlfmt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDescription);
|
||||
if ( ((int) dim.width != w) || (((int) dim.height) != h) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (AVFrameRateRange *framerate in format.videoSupportedFrameRateRanges) {
|
||||
if ((rate == (int) SDL_ceil((double) framerate.minFrameRate)) || (rate == (int) SDL_floor((double) framerate.maxFrameRate))) {
|
||||
spec_format = format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (spec_format != nil) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (spec_format == nil) {
|
||||
return SDL_SetError("camera spec format not available");
|
||||
} else if (![avdevice lockForConfiguration:NULL]) {
|
||||
return SDL_SetError("Cannot lockForConfiguration");
|
||||
}
|
||||
|
||||
avdevice.activeFormat = spec_format;
|
||||
[avdevice unlockForConfiguration];
|
||||
|
||||
AVCaptureSession *session = [[AVCaptureSession alloc] init];
|
||||
if (session == nil) {
|
||||
return SDL_SetError("Failed to allocate/init AVCaptureSession");
|
||||
}
|
||||
|
||||
session.sessionPreset = AVCaptureSessionPresetHigh;
|
||||
|
||||
NSError *error = nil;
|
||||
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:avdevice error:&error];
|
||||
if (!input) {
|
||||
return SDL_SetError("Cannot create AVCaptureDeviceInput");
|
||||
}
|
||||
|
||||
AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
|
||||
if (!output) {
|
||||
return SDL_SetError("Cannot create AVCaptureVideoDataOutput");
|
||||
}
|
||||
|
||||
char threadname[64];
|
||||
SDL_GetCameraThreadName(device, threadname, sizeof (threadname));
|
||||
dispatch_queue_t queue = dispatch_queue_create(threadname, NULL);
|
||||
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
if (!queue) {
|
||||
return SDL_SetError("dispatch_queue_create() failed");
|
||||
}
|
||||
|
||||
SDLCaptureVideoDataOutputSampleBufferDelegate *delegate = [[SDLCaptureVideoDataOutputSampleBufferDelegate alloc] init:device];
|
||||
if (delegate == nil) {
|
||||
return SDL_SetError("Cannot create SDLCaptureVideoDataOutputSampleBufferDelegate");
|
||||
}
|
||||
[output setSampleBufferDelegate:delegate queue:queue];
|
||||
|
||||
if (![session canAddInput:input]) {
|
||||
return SDL_SetError("Cannot add AVCaptureDeviceInput");
|
||||
}
|
||||
[session addInput:input];
|
||||
|
||||
if (![session canAddOutput:output]) {
|
||||
return SDL_SetError("Cannot add AVCaptureVideoDataOutput");
|
||||
}
|
||||
[session addOutput:output];
|
||||
|
||||
[session commitConfiguration];
|
||||
|
||||
SDLPrivateCameraData *hidden = [[SDLPrivateCameraData alloc] init];
|
||||
if (hidden == nil) {
|
||||
return SDL_SetError("Cannot create SDLPrivateCameraData");
|
||||
}
|
||||
|
||||
hidden.session = session;
|
||||
hidden.delegate = delegate;
|
||||
hidden.current_sample = NULL;
|
||||
device->hidden = (struct SDL_PrivateCameraData *)CFBridgingRetain(hidden);
|
||||
|
||||
[session startRunning]; // !!! FIXME: docs say this can block while camera warms up and shouldn't be done on main thread. Maybe push through `queue`?
|
||||
|
||||
CheckCameraPermissions(device); // check right away, in case the process is already granted permission.
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void COREMEDIA_FreeDeviceHandle(SDL_CameraDevice *device)
|
||||
{
|
||||
if (device && device->handle) {
|
||||
CFBridgingRelease(device->handle);
|
||||
}
|
||||
}
|
||||
|
||||
static void GatherCameraSpecs(AVCaptureDevice *device, CameraFormatAddData *add_data)
|
||||
{
|
||||
SDL_zerop(add_data);
|
||||
|
||||
for (AVCaptureDeviceFormat *fmt in device.formats) {
|
||||
if (CMFormatDescriptionGetMediaType(fmt.formatDescription) != kCMMediaType_Video) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Uint32 sdlfmt = CoreMediaFormatToSDL(CMFormatDescriptionGetMediaSubType(fmt.formatDescription));
|
||||
if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const CMVideoDimensions dims = CMVideoFormatDescriptionGetDimensions(fmt.formatDescription);
|
||||
const int w = (int) dims.width;
|
||||
const int h = (int) dims.height;
|
||||
for (AVFrameRateRange *framerate in fmt.videoSupportedFrameRateRanges) {
|
||||
int rate;
|
||||
|
||||
rate = (int) SDL_ceil((double) framerate.minFrameRate);
|
||||
if (rate) {
|
||||
SDL_AddCameraFormat(add_data, sdlfmt, w, h, 1, rate);
|
||||
}
|
||||
rate = (int) SDL_floor((double) framerate.maxFrameRate);
|
||||
if (rate) {
|
||||
SDL_AddCameraFormat(add_data, sdlfmt, w, h, 1, rate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_bool FindCoreMediaCameraDeviceByUniqueID(SDL_CameraDevice *device, void *userdata)
|
||||
{
|
||||
NSString *uniqueid = (__bridge NSString *) userdata;
|
||||
AVCaptureDevice *avdev = (__bridge AVCaptureDevice *) device->handle;
|
||||
return ([uniqueid isEqualToString:avdev.uniqueID]) ? SDL_TRUE : SDL_FALSE;
|
||||
}
|
||||
|
||||
static void MaybeAddDevice(AVCaptureDevice *avdevice)
|
||||
{
|
||||
if (!avdevice.connected) {
|
||||
return; // not connected.
|
||||
} else if (![avdevice hasMediaType:AVMediaTypeVideo]) {
|
||||
return; // not a camera.
|
||||
} else if (SDL_FindPhysicalCameraDeviceByCallback(FindCoreMediaCameraDeviceByUniqueID, (__bridge void *) avdevice.uniqueID)) {
|
||||
return; // already have this one.
|
||||
}
|
||||
|
||||
CameraFormatAddData add_data;
|
||||
GatherCameraSpecs(avdevice, &add_data);
|
||||
if (add_data.num_specs > 0) {
|
||||
SDL_CameraPosition position = SDL_CAMERA_POSITION_UNKNOWN;
|
||||
if (avdevice.position == AVCaptureDevicePositionFront) {
|
||||
position = SDL_CAMERA_POSITION_FRONT_FACING;
|
||||
} else if (avdevice.position == AVCaptureDevicePositionBack) {
|
||||
position = SDL_CAMERA_POSITION_BACK_FACING;
|
||||
}
|
||||
SDL_AddCameraDevice(avdevice.localizedName.UTF8String, position, add_data.num_specs, add_data.specs, (void *) CFBridgingRetain(avdevice));
|
||||
}
|
||||
|
||||
SDL_free(add_data.specs);
|
||||
}
|
||||
|
||||
static void COREMEDIA_DetectDevices(void)
|
||||
{
|
||||
NSArray<AVCaptureDevice *> *devices = nil;
|
||||
|
||||
if (@available(macOS 10.15, iOS 13, *)) {
|
||||
// kind of annoying that there isn't a "give me anything that looks like a camera" option,
|
||||
// so this list will need to be updated when Apple decides to add
|
||||
// AVCaptureDeviceTypeBuiltInQuadrupleCamera some day.
|
||||
NSArray *device_types = @[
|
||||
#ifdef SDL_PLATFORM_IOS
|
||||
AVCaptureDeviceTypeBuiltInTelephotoCamera,
|
||||
AVCaptureDeviceTypeBuiltInDualCamera,
|
||||
AVCaptureDeviceTypeBuiltInDualWideCamera,
|
||||
AVCaptureDeviceTypeBuiltInTripleCamera,
|
||||
AVCaptureDeviceTypeBuiltInUltraWideCamera,
|
||||
#else
|
||||
AVCaptureDeviceTypeExternalUnknown,
|
||||
#endif
|
||||
AVCaptureDeviceTypeBuiltInWideAngleCamera
|
||||
];
|
||||
|
||||
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
|
||||
discoverySessionWithDeviceTypes:device_types
|
||||
mediaType:AVMediaTypeVideo
|
||||
position:AVCaptureDevicePositionUnspecified];
|
||||
|
||||
devices = discoverySession.devices;
|
||||
// !!! FIXME: this can use Key Value Observation to get hotplug events.
|
||||
} else {
|
||||
// this is deprecated but works back to macOS 10.7; 10.15 added AVCaptureDeviceDiscoverySession as a replacement.
|
||||
devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
||||
// !!! FIXME: this can use AVCaptureDeviceWasConnectedNotification and AVCaptureDeviceWasDisconnectedNotification with NSNotificationCenter to get hotplug events.
|
||||
}
|
||||
|
||||
for (AVCaptureDevice *device in devices) {
|
||||
MaybeAddDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
static void COREMEDIA_Deinitialize(void)
|
||||
{
|
||||
// !!! FIXME: disable hotplug.
|
||||
}
|
||||
|
||||
static SDL_bool COREMEDIA_Init(SDL_CameraDriverImpl *impl)
|
||||
{
|
||||
impl->DetectDevices = COREMEDIA_DetectDevices;
|
||||
impl->OpenDevice = COREMEDIA_OpenDevice;
|
||||
impl->CloseDevice = COREMEDIA_CloseDevice;
|
||||
impl->WaitDevice = COREMEDIA_WaitDevice;
|
||||
impl->AcquireFrame = COREMEDIA_AcquireFrame;
|
||||
impl->ReleaseFrame = COREMEDIA_ReleaseFrame;
|
||||
impl->FreeDeviceHandle = COREMEDIA_FreeDeviceHandle;
|
||||
impl->Deinitialize = COREMEDIA_Deinitialize;
|
||||
|
||||
impl->ProvidesOwnCallbackThread = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
CameraBootStrap COREMEDIA_bootstrap = {
|
||||
"coremedia", "SDL Apple CoreMedia camera driver", COREMEDIA_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_CAMERA_DRIVER_COREMEDIA
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_CAMERA_DRIVER_DUMMY
|
||||
|
||||
#include "../SDL_syscamera.h"
|
||||
|
||||
static int DUMMYCAMERA_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static void DUMMYCAMERA_CloseDevice(SDL_CameraDevice *device)
|
||||
{
|
||||
}
|
||||
|
||||
static int DUMMYCAMERA_WaitDevice(SDL_CameraDevice *device)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static int DUMMYCAMERA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
static void DUMMYCAMERA_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
|
||||
{
|
||||
}
|
||||
|
||||
static void DUMMYCAMERA_DetectDevices(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void DUMMYCAMERA_FreeDeviceHandle(SDL_CameraDevice *device)
|
||||
{
|
||||
}
|
||||
|
||||
static void DUMMYCAMERA_Deinitialize(void)
|
||||
{
|
||||
}
|
||||
|
||||
static SDL_bool DUMMYCAMERA_Init(SDL_CameraDriverImpl *impl)
|
||||
{
|
||||
impl->DetectDevices = DUMMYCAMERA_DetectDevices;
|
||||
impl->OpenDevice = DUMMYCAMERA_OpenDevice;
|
||||
impl->CloseDevice = DUMMYCAMERA_CloseDevice;
|
||||
impl->WaitDevice = DUMMYCAMERA_WaitDevice;
|
||||
impl->AcquireFrame = DUMMYCAMERA_AcquireFrame;
|
||||
impl->ReleaseFrame = DUMMYCAMERA_ReleaseFrame;
|
||||
impl->FreeDeviceHandle = DUMMYCAMERA_FreeDeviceHandle;
|
||||
impl->Deinitialize = DUMMYCAMERA_Deinitialize;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
CameraBootStrap DUMMYCAMERA_bootstrap = {
|
||||
"dummy", "SDL dummy camera driver", DUMMYCAMERA_Init, SDL_TRUE
|
||||
};
|
||||
|
||||
#endif // SDL_CAMERA_DRIVER_DUMMY
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_CAMERA_DRIVER_EMSCRIPTEN
|
||||
|
||||
#include "../SDL_syscamera.h"
|
||||
#include "../SDL_camera_c.h"
|
||||
#include "../../video/SDL_pixels_c.h"
|
||||
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
// just turn off clang-format for this whole file, this INDENT_OFF stuff on
|
||||
// each EM_ASM section is ugly.
|
||||
/* *INDENT-OFF* */ /* clang-format off */
|
||||
|
||||
EM_JS_DEPS(sdlcamera, "$dynCall");
|
||||
|
||||
static int EMSCRIPTENCAMERA_WaitDevice(SDL_CameraDevice *device)
|
||||
{
|
||||
SDL_assert(!"This shouldn't be called"); // we aren't using SDL's internal thread.
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int EMSCRIPTENCAMERA_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
|
||||
{
|
||||
void *rgba = SDL_malloc(device->actual_spec.width * device->actual_spec.height * 4);
|
||||
if (!rgba) {
|
||||
return SDL_OutOfMemory();
|
||||
}
|
||||
|
||||
*timestampNS = SDL_GetTicksNS(); // best we can do here.
|
||||
|
||||
const int rc = MAIN_THREAD_EM_ASM_INT({
|
||||
const w = $0;
|
||||
const h = $1;
|
||||
const rgba = $2;
|
||||
const SDL3 = Module['SDL3'];
|
||||
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.ctx2d) === 'undefined')) {
|
||||
return 0; // don't have something we need, oh well.
|
||||
}
|
||||
|
||||
SDL3.camera.ctx2d.drawImage(SDL3.camera.video, 0, 0, w, h);
|
||||
const imgrgba = SDL3.camera.ctx2d.getImageData(0, 0, w, h).data;
|
||||
Module.HEAPU8.set(imgrgba, rgba);
|
||||
|
||||
return 1;
|
||||
}, device->actual_spec.width, device->actual_spec.height, rgba);
|
||||
|
||||
if (!rc) {
|
||||
SDL_free(rgba);
|
||||
return 0; // something went wrong, maybe shutting down; just don't return a frame.
|
||||
}
|
||||
|
||||
frame->pixels = rgba;
|
||||
frame->pitch = device->actual_spec.width * 4;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void EMSCRIPTENCAMERA_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
|
||||
{
|
||||
SDL_free(frame->pixels);
|
||||
}
|
||||
|
||||
static void EMSCRIPTENCAMERA_CloseDevice(SDL_CameraDevice *device)
|
||||
{
|
||||
if (device) {
|
||||
MAIN_THREAD_EM_ASM({
|
||||
const SDL3 = Module['SDL3'];
|
||||
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) {
|
||||
return; // camera was closed and/or subsystem was shut down, we're already done.
|
||||
}
|
||||
SDL3.camera.stream.getTracks().forEach(track => track.stop()); // stop all recording.
|
||||
_SDL_free(SDL3.camera.rgba);
|
||||
SDL3.camera = {}; // dump our references to everything.
|
||||
});
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDLEmscriptenCameraDevicePermissionOutcome(SDL_CameraDevice *device, int approved, int w, int h, int fps)
|
||||
{
|
||||
device->spec.width = device->actual_spec.width = w;
|
||||
device->spec.height = device->actual_spec.height = h;
|
||||
device->spec.interval_numerator = device->actual_spec.interval_numerator = 1;
|
||||
device->spec.interval_denominator = device->actual_spec.interval_denominator = fps;
|
||||
SDL_CameraDevicePermissionOutcome(device, approved ? SDL_TRUE : SDL_FALSE);
|
||||
}
|
||||
|
||||
static int EMSCRIPTENCAMERA_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
|
||||
{
|
||||
MAIN_THREAD_EM_ASM({
|
||||
// Since we can't get actual specs until we make a move that prompts the user for
|
||||
// permission, we don't list any specs for the device and wrangle it during device open.
|
||||
const device = $0;
|
||||
const w = $1;
|
||||
const h = $2;
|
||||
const interval_numerator = $3;
|
||||
const interval_denominator = $4;
|
||||
const outcome = $5;
|
||||
const iterate = $6;
|
||||
|
||||
const constraints = {};
|
||||
if ((w <= 0) || (h <= 0)) {
|
||||
constraints.video = true; // didn't ask for anything, let the system choose.
|
||||
} else {
|
||||
constraints.video = {}; // asked for a specific thing: request it as "ideal" but take closest hardware will offer.
|
||||
constraints.video.width = w;
|
||||
constraints.video.height = h;
|
||||
}
|
||||
|
||||
if ((interval_numerator > 0) && (interval_denominator > 0)) {
|
||||
var fps = interval_denominator / interval_numerator;
|
||||
constraints.video.frameRate = { ideal: fps };
|
||||
}
|
||||
|
||||
function grabNextCameraFrame() { // !!! FIXME: this (currently) runs as a requestAnimationFrame callback, for lack of a better option.
|
||||
const SDL3 = Module['SDL3'];
|
||||
if ((typeof(SDL3) === 'undefined') || (typeof(SDL3.camera) === 'undefined') || (typeof(SDL3.camera.stream) === 'undefined')) {
|
||||
return; // camera was closed and/or subsystem was shut down, stop iterating here.
|
||||
}
|
||||
|
||||
// time for a new frame from the camera?
|
||||
const nextframems = SDL3.camera.next_frame_time;
|
||||
const now = performance.now();
|
||||
if (now >= nextframems) {
|
||||
dynCall('vi', iterate, [device]); // calls SDL_CameraThreadIterate, which will call our AcquireFrame implementation.
|
||||
|
||||
// bump ahead but try to stay consistent on timing, in case we dropped frames.
|
||||
while (SDL3.camera.next_frame_time < now) {
|
||||
SDL3.camera.next_frame_time += SDL3.camera.fpsincrms;
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(grabNextCameraFrame); // run this function again at the display framerate. (!!! FIXME: would this be better as requestIdleCallback?)
|
||||
}
|
||||
|
||||
navigator.mediaDevices.getUserMedia(constraints)
|
||||
.then((stream) => {
|
||||
const settings = stream.getVideoTracks()[0].getSettings();
|
||||
const actualw = settings.width;
|
||||
const actualh = settings.height;
|
||||
const actualfps = settings.frameRate;
|
||||
console.log("Camera is opened! Actual spec: (" + actualw + "x" + actualh + "), fps=" + actualfps);
|
||||
|
||||
dynCall('viiiii', outcome, [device, 1, actualw, actualh, actualfps]);
|
||||
|
||||
const video = document.createElement("video");
|
||||
video.width = actualw;
|
||||
video.height = actualh;
|
||||
video.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels.
|
||||
video.srcObject = stream;
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = actualw;
|
||||
canvas.height = actualh;
|
||||
canvas.style.display = 'none'; // we need to attach this to a hidden video node so we can read it as pixels.
|
||||
|
||||
const ctx2d = canvas.getContext('2d');
|
||||
|
||||
const SDL3 = Module['SDL3'];
|
||||
SDL3.camera.width = actualw;
|
||||
SDL3.camera.height = actualh;
|
||||
SDL3.camera.fps = actualfps;
|
||||
SDL3.camera.fpsincrms = 1000.0 / actualfps;
|
||||
SDL3.camera.stream = stream;
|
||||
SDL3.camera.video = video;
|
||||
SDL3.camera.canvas = canvas;
|
||||
SDL3.camera.ctx2d = ctx2d;
|
||||
SDL3.camera.rgba = 0;
|
||||
SDL3.camera.next_frame_time = performance.now();
|
||||
|
||||
video.play();
|
||||
video.addEventListener('loadedmetadata', () => {
|
||||
grabNextCameraFrame(); // start this loop going.
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Tried to open camera but it threw an error! " + err.name + ": " + err.message);
|
||||
dynCall('viiiii', outcome, [device, 0, 0, 0, 0]); // we call this a permission error, because it probably is.
|
||||
});
|
||||
}, device, spec->width, spec->height, spec->interval_numerator, spec->interval_denominator, SDLEmscriptenCameraDevicePermissionOutcome, SDL_CameraThreadIterate);
|
||||
|
||||
return 0; // the real work waits until the user approves a camera.
|
||||
}
|
||||
|
||||
static void EMSCRIPTENCAMERA_FreeDeviceHandle(SDL_CameraDevice *device)
|
||||
{
|
||||
// no-op.
|
||||
}
|
||||
|
||||
static void EMSCRIPTENCAMERA_Deinitialize(void)
|
||||
{
|
||||
MAIN_THREAD_EM_ASM({
|
||||
if (typeof(Module['SDL3']) !== 'undefined') {
|
||||
Module['SDL3'].camera = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void EMSCRIPTENCAMERA_DetectDevices(void)
|
||||
{
|
||||
// `navigator.mediaDevices` is not defined if unsupported or not in a secure context!
|
||||
const int supported = MAIN_THREAD_EM_ASM_INT({ return (navigator.mediaDevices === undefined) ? 0 : 1; });
|
||||
|
||||
// if we have support at all, report a single generic camera with no specs.
|
||||
// We'll find out if there really _is_ a camera when we try to open it, but querying it for real here
|
||||
// will pop up a user permission dialog warning them we're trying to access the camera, and we generally
|
||||
// don't want that during SDL_Init().
|
||||
if (supported) {
|
||||
SDL_AddCameraDevice("Web browser's camera", SDL_CAMERA_POSITION_UNKNOWN, 0, NULL, (void *) (size_t) 0x1);
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_bool EMSCRIPTENCAMERA_Init(SDL_CameraDriverImpl *impl)
|
||||
{
|
||||
MAIN_THREAD_EM_ASM({
|
||||
if (typeof(Module['SDL3']) === 'undefined') {
|
||||
Module['SDL3'] = {};
|
||||
}
|
||||
Module['SDL3'].camera = {};
|
||||
});
|
||||
|
||||
impl->DetectDevices = EMSCRIPTENCAMERA_DetectDevices;
|
||||
impl->OpenDevice = EMSCRIPTENCAMERA_OpenDevice;
|
||||
impl->CloseDevice = EMSCRIPTENCAMERA_CloseDevice;
|
||||
impl->WaitDevice = EMSCRIPTENCAMERA_WaitDevice;
|
||||
impl->AcquireFrame = EMSCRIPTENCAMERA_AcquireFrame;
|
||||
impl->ReleaseFrame = EMSCRIPTENCAMERA_ReleaseFrame;
|
||||
impl->FreeDeviceHandle = EMSCRIPTENCAMERA_FreeDeviceHandle;
|
||||
impl->Deinitialize = EMSCRIPTENCAMERA_Deinitialize;
|
||||
|
||||
impl->ProvidesOwnCallbackThread = SDL_TRUE;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
CameraBootStrap EMSCRIPTENCAMERA_bootstrap = {
|
||||
"emscripten", "SDL Emscripten MediaStream camera driver", EMSCRIPTENCAMERA_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
/* *INDENT-ON* */ /* clang-format on */
|
||||
|
||||
#endif // SDL_CAMERA_DRIVER_EMSCRIPTEN
|
||||
|
||||
@@ -0,0 +1,933 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// the Windows Media Foundation API
|
||||
|
||||
#ifdef SDL_CAMERA_DRIVER_MEDIAFOUNDATION
|
||||
|
||||
#define COBJMACROS
|
||||
|
||||
// this seems to be a bug in mfidl.h, just define this to avoid the problem section.
|
||||
#define __IMFVideoProcessorControl3_INTERFACE_DEFINED__
|
||||
|
||||
#include "../../core/windows/SDL_windows.h"
|
||||
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
|
||||
#include "../SDL_syscamera.h"
|
||||
#include "../SDL_camera_c.h"
|
||||
|
||||
static const IID SDL_IID_IMFMediaSource = { 0x279a808d, 0xaec7, 0x40c8, { 0x9c, 0x6b, 0xa6, 0xb4, 0x92, 0xc7, 0x8a, 0x66 } };
|
||||
static const IID SDL_IID_IMF2DBuffer = { 0x7dc9d5f9, 0x9ed9, 0x44ec, { 0x9b, 0xbf, 0x06, 0x00, 0xbb, 0x58, 0x9f, 0xbb } };
|
||||
static const IID SDL_IID_IMF2DBuffer2 = { 0x33ae5ea6, 0x4316, 0x436f, { 0x8d, 0xdd, 0xd7, 0x3d, 0x22, 0xf8, 0x29, 0xec } };
|
||||
static const GUID SDL_MF_MT_DEFAULT_STRIDE = { 0x644b4e48, 0x1e02, 0x4516, { 0xb0, 0xeb, 0xc0, 0x1c, 0xa9, 0xd4, 0x9a, 0xc6 } };
|
||||
static const GUID SDL_MF_MT_MAJOR_TYPE = { 0x48eba18e, 0xf8c9, 0x4687, { 0xbf, 0x11, 0x0a, 0x74, 0xc9, 0xf9, 0x6a, 0x8f } };
|
||||
static const GUID SDL_MF_MT_SUBTYPE = { 0xf7e34c9a, 0x42e8, 0x4714, { 0xb7, 0x4b, 0xcb, 0x29, 0xd7, 0x2c, 0x35, 0xe5 } };
|
||||
static const GUID SDL_MF_MT_FRAME_SIZE = { 0x1652c33d, 0xd6b2, 0x4012, { 0xb8, 0x34, 0x72, 0x03, 0x08, 0x49, 0xa3, 0x7d } };
|
||||
static const GUID SDL_MF_MT_FRAME_RATE = { 0xc459a2e8, 0x3d2c, 0x4e44, { 0xb1, 0x32, 0xfe, 0xe5, 0x15, 0x6c, 0x7b, 0xb0 } };
|
||||
static const GUID SDL_MFMediaType_Video = { 0x73646976, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } };
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmultichar"
|
||||
#endif
|
||||
|
||||
#define SDL_DEFINE_MEDIATYPE_GUID(name, fmt) static const GUID SDL_##name = { fmt, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB555, 24);
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB565, 23);
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB24, 20);
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_RGB32, 22);
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_ARGB32, 21);
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_A2R10G10B10, 31);
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YV12, FCC('YV12'));
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_IYUV, FCC('IYUV'));
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YUY2, FCC('YUY2'));
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_UYVY, FCC('UYVY'));
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_YVYU, FCC('YVYU'));
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_NV12, FCC('NV12'));
|
||||
SDL_DEFINE_MEDIATYPE_GUID(MFVideoFormat_NV21, FCC('NV21'));
|
||||
#undef SDL_DEFINE_MEDIATYPE_GUID
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
static const struct
|
||||
{
|
||||
const GUID *guid;
|
||||
const SDL_PixelFormatEnum sdlfmt;
|
||||
} fmtmappings[] = {
|
||||
// This is not every possible format, just popular ones that SDL can reasonably handle.
|
||||
// (and we should probably trim this list more.)
|
||||
{ &SDL_MFVideoFormat_RGB555, SDL_PIXELFORMAT_XRGB1555 },
|
||||
{ &SDL_MFVideoFormat_RGB565, SDL_PIXELFORMAT_RGB565 },
|
||||
{ &SDL_MFVideoFormat_RGB24, SDL_PIXELFORMAT_RGB24 },
|
||||
{ &SDL_MFVideoFormat_RGB32, SDL_PIXELFORMAT_XRGB8888 },
|
||||
{ &SDL_MFVideoFormat_ARGB32, SDL_PIXELFORMAT_ARGB8888 },
|
||||
{ &SDL_MFVideoFormat_A2R10G10B10, SDL_PIXELFORMAT_ARGB2101010 },
|
||||
{ &SDL_MFVideoFormat_YV12, SDL_PIXELFORMAT_YV12 },
|
||||
{ &SDL_MFVideoFormat_IYUV, SDL_PIXELFORMAT_IYUV },
|
||||
{ &SDL_MFVideoFormat_YUY2, SDL_PIXELFORMAT_YUY2 },
|
||||
{ &SDL_MFVideoFormat_UYVY, SDL_PIXELFORMAT_UYVY },
|
||||
{ &SDL_MFVideoFormat_YVYU, SDL_PIXELFORMAT_YVYU },
|
||||
{ &SDL_MFVideoFormat_NV12, SDL_PIXELFORMAT_NV12 },
|
||||
{ &SDL_MFVideoFormat_NV21, SDL_PIXELFORMAT_NV21 }
|
||||
};
|
||||
|
||||
static SDL_PixelFormatEnum MFVidFmtGuidToSDLFmt(const GUID *guid)
|
||||
{
|
||||
for (size_t i = 0; i < SDL_arraysize(fmtmappings); i++) {
|
||||
if (WIN_IsEqualGUID(guid, fmtmappings[i].guid)) {
|
||||
return fmtmappings[i].sdlfmt;
|
||||
}
|
||||
}
|
||||
return SDL_PIXELFORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
static const GUID *SDLFmtToMFVidFmtGuid(SDL_PixelFormatEnum sdlfmt)
|
||||
{
|
||||
for (size_t i = 0; i < SDL_arraysize(fmtmappings); i++) {
|
||||
if (fmtmappings[i].sdlfmt == sdlfmt) {
|
||||
return fmtmappings[i].guid;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// handle to Media Foundation libs--Vista and later!--for access to the Media Foundation API.
|
||||
|
||||
// mf.dll ...
|
||||
static HMODULE libmf = NULL;
|
||||
typedef HRESULT(WINAPI *pfnMFEnumDeviceSources)(IMFAttributes *,IMFActivate ***,UINT32 *);
|
||||
typedef HRESULT(WINAPI *pfnMFCreateDeviceSource)(IMFAttributes *, IMFMediaSource **);
|
||||
static pfnMFEnumDeviceSources pMFEnumDeviceSources = NULL;
|
||||
static pfnMFCreateDeviceSource pMFCreateDeviceSource = NULL;
|
||||
|
||||
// mfplat.dll ...
|
||||
static HMODULE libmfplat = NULL;
|
||||
typedef HRESULT(WINAPI *pfnMFStartup)(ULONG, DWORD);
|
||||
typedef HRESULT(WINAPI *pfnMFShutdown)(void);
|
||||
typedef HRESULT(WINAPI *pfnMFCreateAttributes)(IMFAttributes **, UINT32);
|
||||
typedef HRESULT(WINAPI *pfnMFCreateMediaType)(IMFMediaType **);
|
||||
typedef HRESULT(WINAPI *pfnMFGetStrideForBitmapInfoHeader)(DWORD, DWORD, LONG *);
|
||||
|
||||
static pfnMFStartup pMFStartup = NULL;
|
||||
static pfnMFShutdown pMFShutdown = NULL;
|
||||
static pfnMFCreateAttributes pMFCreateAttributes = NULL;
|
||||
static pfnMFCreateMediaType pMFCreateMediaType = NULL;
|
||||
static pfnMFGetStrideForBitmapInfoHeader pMFGetStrideForBitmapInfoHeader = NULL;
|
||||
|
||||
// mfreadwrite.dll ...
|
||||
static HMODULE libmfreadwrite = NULL;
|
||||
typedef HRESULT(WINAPI *pfnMFCreateSourceReaderFromMediaSource)(IMFMediaSource *, IMFAttributes *, IMFSourceReader **);
|
||||
static pfnMFCreateSourceReaderFromMediaSource pMFCreateSourceReaderFromMediaSource = NULL;
|
||||
|
||||
|
||||
typedef struct SDL_PrivateCameraData
|
||||
{
|
||||
IMFSourceReader *srcreader;
|
||||
IMFSample *current_sample;
|
||||
int pitch;
|
||||
} SDL_PrivateCameraData;
|
||||
|
||||
static int MEDIAFOUNDATION_WaitDevice(SDL_CameraDevice *device)
|
||||
{
|
||||
SDL_assert(device->hidden->current_sample == NULL);
|
||||
|
||||
IMFSourceReader *srcreader = device->hidden->srcreader;
|
||||
IMFSample *sample = NULL;
|
||||
|
||||
while (!SDL_AtomicGet(&device->shutdown)) {
|
||||
DWORD stream_flags = 0;
|
||||
const HRESULT ret = IMFSourceReader_ReadSample(srcreader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, &stream_flags, NULL, &sample);
|
||||
if (FAILED(ret)) {
|
||||
return -1; // ruh roh.
|
||||
}
|
||||
|
||||
// we currently ignore stream_flags format changes, but my _hope_ is that IMFSourceReader is handling this and
|
||||
// will continue to give us the explictly-specified format we requested when opening the device, though, and
|
||||
// we don't have to manually deal with it.
|
||||
|
||||
if (sample != NULL) {
|
||||
break;
|
||||
} else if (stream_flags & (MF_SOURCE_READERF_ERROR | MF_SOURCE_READERF_ENDOFSTREAM)) {
|
||||
return -1; // apparently this camera has gone down. :/
|
||||
}
|
||||
|
||||
// otherwise, there was some minor burp, probably; just try again.
|
||||
}
|
||||
|
||||
device->hidden->current_sample = sample;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#ifdef KEEP_ACQUIRED_BUFFERS_LOCKED
|
||||
|
||||
#define PROP_SURFACE_IMFOBJS_POINTER "SDL.camera.mediafoundation.imfobjs"
|
||||
|
||||
typedef struct SDL_IMFObjects
|
||||
{
|
||||
IMF2DBuffer2 *buffer2d2;
|
||||
IMF2DBuffer *buffer2d;
|
||||
IMFMediaBuffer *buffer;
|
||||
IMFSample *sample;
|
||||
} SDL_IMFObjects;
|
||||
|
||||
static void SDLCALL CleanupIMF2DBuffer2(void *userdata, void *value)
|
||||
{
|
||||
SDL_IMFObjects *objs = (SDL_IMFObjects *)value;
|
||||
IMF2DBuffer2_Unlock2D(objs->buffer2d2);
|
||||
IMF2DBuffer2_Release(objs->buffer2d2);
|
||||
IMFMediaBuffer_Release(objs->buffer);
|
||||
IMFSample_Release(objs->sample);
|
||||
SDL_free(objs);
|
||||
}
|
||||
|
||||
static void SDLCALL CleanupIMF2DBuffer(void *userdata, void *value)
|
||||
{
|
||||
SDL_IMFObjects *objs = (SDL_IMFObjects *)value;
|
||||
IMF2DBuffer_Unlock2D(objs->buffer2d);
|
||||
IMF2DBuffer_Release(objs->buffer2d);
|
||||
IMFMediaBuffer_Release(objs->buffer);
|
||||
IMFSample_Release(objs->sample);
|
||||
SDL_free(objs);
|
||||
}
|
||||
|
||||
static void SDLCALL CleanupIMFMediaBuffer(void *userdata, void *value)
|
||||
{
|
||||
SDL_IMFObjects *objs = (SDL_IMFObjects *)value;
|
||||
IMFMediaBuffer_Unlock(objs->buffer);
|
||||
IMFMediaBuffer_Release(objs->buffer);
|
||||
IMFSample_Release(objs->sample);
|
||||
SDL_free(objs);
|
||||
}
|
||||
|
||||
static int MEDIAFOUNDATION_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
|
||||
{
|
||||
SDL_assert(device->hidden->current_sample != NULL);
|
||||
|
||||
int retval = 1;
|
||||
HRESULT ret;
|
||||
LONGLONG timestamp100NS = 0;
|
||||
SDL_IMFObjects *objs = (SDL_IMFObjects *) SDL_calloc(1, sizeof (SDL_IMFObjects));
|
||||
|
||||
if (objs == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
objs->sample = device->hidden->current_sample;
|
||||
device->hidden->current_sample = NULL;
|
||||
|
||||
const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
|
||||
if (!surfprops) {
|
||||
retval = -1;
|
||||
} else {
|
||||
ret = IMFSample_GetSampleTime(objs->sample, ×tamp100NS);
|
||||
if (FAILED(ret)) {
|
||||
retval = -1;
|
||||
}
|
||||
|
||||
*timestampNS = timestamp100NS * 100; // the timestamps are in 100-nanosecond increments; move to full nanoseconds.
|
||||
}
|
||||
|
||||
ret = (retval < 0) ? E_FAIL : IMFSample_ConvertToContiguousBuffer(objs->sample, &objs->buffer); /*IMFSample_GetBufferByIndex(objs->sample, 0, &objs->buffer);*/
|
||||
|
||||
if (FAILED(ret)) {
|
||||
SDL_free(objs);
|
||||
retval = -1;
|
||||
} else {
|
||||
BYTE *pixels = NULL;
|
||||
LONG pitch = 0;
|
||||
|
||||
if (SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer2, (void **)&objs->buffer2d2))) {
|
||||
BYTE *bufstart = NULL;
|
||||
DWORD buflen = 0;
|
||||
ret = IMF2DBuffer2_Lock2DSize(objs->buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen);
|
||||
if (FAILED(ret)) {
|
||||
retval = -1;
|
||||
CleanupIMF2DBuffer2(NULL, objs);
|
||||
} else {
|
||||
frame->pixels = pixels;
|
||||
frame->pitch = (int) pitch;
|
||||
if (SDL_SetPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer2, NULL) == -1) {
|
||||
CleanupIMF2DBuffer2(NULL, objs);
|
||||
retval = -1;
|
||||
}
|
||||
}
|
||||
} else if (SUCCEEDED(IMFMediaBuffer_QueryInterface(objs->buffer, &SDL_IID_IMF2DBuffer, (void **)&objs->buffer2d))) {
|
||||
ret = IMF2DBuffer_Lock2D(objs->buffer2d, &pixels, &pitch);
|
||||
if (FAILED(ret)) {
|
||||
CleanupIMF2DBuffer(NULL, objs);
|
||||
retval = -1;
|
||||
} else {
|
||||
frame->pixels = pixels;
|
||||
frame->pitch = (int) pitch;
|
||||
if (SDL_SetPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMF2DBuffer, NULL) == -1) {
|
||||
CleanupIMF2DBuffer(NULL, objs);
|
||||
retval = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DWORD maxlen = 0, currentlen = 0;
|
||||
ret = IMFMediaBuffer_Lock(objs->buffer, &pixels, &maxlen, ¤tlen);
|
||||
if (FAILED(ret)) {
|
||||
CleanupIMFMediaBuffer(NULL, objs);
|
||||
retval = -1;
|
||||
} else {
|
||||
pitch = (LONG) device->hidden->pitch;
|
||||
if (pitch < 0) { // image rows are reversed.
|
||||
pixels += -pitch * (frame->h - 1);
|
||||
}
|
||||
frame->pixels = pixels;
|
||||
frame->pitch = (int) pitch;
|
||||
if (SDL_SetPropertyWithCleanup(surfprops, PROP_SURFACE_IMFOBJS_POINTER, objs, CleanupIMFMediaBuffer, NULL) == -1) {
|
||||
CleanupIMFMediaBuffer(NULL, objs);
|
||||
retval = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (retval < 0) {
|
||||
*timestampNS = 0;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void MEDIAFOUNDATION_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
|
||||
{
|
||||
const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
|
||||
if (surfprops) {
|
||||
// this will release the IMFBuffer and IMFSample objects for this frame.
|
||||
SDL_ClearProperty(surfprops, PROP_SURFACE_IMFOBJS_POINTER);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static int MEDIAFOUNDATION_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
|
||||
{
|
||||
SDL_assert(device->hidden->current_sample != NULL);
|
||||
|
||||
int retval = 1;
|
||||
HRESULT ret;
|
||||
LONGLONG timestamp100NS = 0;
|
||||
|
||||
IMFSample *sample = device->hidden->current_sample;
|
||||
device->hidden->current_sample = NULL;
|
||||
|
||||
const SDL_PropertiesID surfprops = SDL_GetSurfaceProperties(frame);
|
||||
if (!surfprops) {
|
||||
retval = -1;
|
||||
} else {
|
||||
ret = IMFSample_GetSampleTime(sample, ×tamp100NS);
|
||||
if (FAILED(ret)) {
|
||||
retval = -1;
|
||||
}
|
||||
|
||||
*timestampNS = timestamp100NS * 100; // the timestamps are in 100-nanosecond increments; move to full nanoseconds.
|
||||
}
|
||||
|
||||
IMFMediaBuffer *buffer = NULL;
|
||||
ret = (retval < 0) ? E_FAIL : IMFSample_ConvertToContiguousBuffer(sample, &buffer); /*IMFSample_GetBufferByIndex(sample, 0, &buffer);*/
|
||||
|
||||
if (FAILED(ret)) {
|
||||
retval = -1;
|
||||
} else {
|
||||
IMF2DBuffer *buffer2d = NULL;
|
||||
IMF2DBuffer2 *buffer2d2 = NULL;
|
||||
BYTE *pixels = NULL;
|
||||
LONG pitch = 0;
|
||||
|
||||
if (SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer2, (void **)&buffer2d2))) {
|
||||
BYTE *bufstart = NULL;
|
||||
DWORD buflen = 0;
|
||||
ret = IMF2DBuffer2_Lock2DSize(buffer2d2, MF2DBuffer_LockFlags_Read, &pixels, &pitch, &bufstart, &buflen);
|
||||
if (FAILED(ret)) {
|
||||
retval = -1;
|
||||
} else {
|
||||
frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
|
||||
if (frame->pixels == NULL) {
|
||||
retval = -1;
|
||||
} else {
|
||||
SDL_memcpy(frame->pixels, pixels, buflen);
|
||||
frame->pitch = (int)pitch;
|
||||
}
|
||||
IMF2DBuffer2_Unlock2D(buffer2d2);
|
||||
}
|
||||
IMF2DBuffer2_Release(buffer2d2);
|
||||
} else if (SUCCEEDED(IMFMediaBuffer_QueryInterface(buffer, &SDL_IID_IMF2DBuffer, (void **)&buffer2d))) {
|
||||
ret = IMF2DBuffer_Lock2D(buffer2d, &pixels, &pitch);
|
||||
if (FAILED(ret)) {
|
||||
retval = -1;
|
||||
} else {
|
||||
BYTE *bufstart = pixels;
|
||||
const DWORD buflen = (SDL_abs((int)pitch) * frame->w) * frame->h;
|
||||
if (pitch < 0) { // image rows are reversed.
|
||||
bufstart += -pitch * (frame->h - 1);
|
||||
}
|
||||
frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
|
||||
if (frame->pixels == NULL) {
|
||||
retval = -1;
|
||||
} else {
|
||||
SDL_memcpy(frame->pixels, bufstart, buflen);
|
||||
frame->pitch = (int)pitch;
|
||||
}
|
||||
IMF2DBuffer_Unlock2D(buffer2d);
|
||||
}
|
||||
IMF2DBuffer_Release(buffer2d);
|
||||
} else {
|
||||
DWORD maxlen = 0, currentlen = 0;
|
||||
ret = IMFMediaBuffer_Lock(buffer, &pixels, &maxlen, ¤tlen);
|
||||
if (FAILED(ret)) {
|
||||
retval = -1;
|
||||
} else {
|
||||
BYTE *bufstart = pixels;
|
||||
pitch = (LONG)device->hidden->pitch;
|
||||
const DWORD buflen = (SDL_abs((int)pitch) * frame->w) * frame->h;
|
||||
if (pitch < 0) { // image rows are reversed.
|
||||
bufstart += -pitch * (frame->h - 1);
|
||||
}
|
||||
frame->pixels = SDL_aligned_alloc(SDL_SIMDGetAlignment(), buflen);
|
||||
if (frame->pixels == NULL) {
|
||||
retval = -1;
|
||||
} else {
|
||||
SDL_memcpy(frame->pixels, bufstart, buflen);
|
||||
frame->pitch = (int)pitch;
|
||||
}
|
||||
IMFMediaBuffer_Unlock(buffer);
|
||||
}
|
||||
}
|
||||
IMFMediaBuffer_Release(buffer);
|
||||
}
|
||||
|
||||
IMFSample_Release(sample);
|
||||
|
||||
if (retval < 0) {
|
||||
*timestampNS = 0;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void MEDIAFOUNDATION_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
|
||||
{
|
||||
SDL_aligned_free(frame->pixels);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void MEDIAFOUNDATION_CloseDevice(SDL_CameraDevice *device)
|
||||
{
|
||||
if (device && device->hidden) {
|
||||
if (device->hidden->srcreader) {
|
||||
IMFSourceReader_Release(device->hidden->srcreader);
|
||||
}
|
||||
if (device->hidden->current_sample) {
|
||||
IMFSample_Release(device->hidden->current_sample);
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// this function is from https://learn.microsoft.com/en-us/windows/win32/medfound/uncompressed-video-buffers
|
||||
static HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride)
|
||||
{
|
||||
LONG lStride = 0;
|
||||
|
||||
// Try to get the default stride from the media type.
|
||||
HRESULT ret = IMFMediaType_GetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32*)&lStride);
|
||||
if (FAILED(ret)) {
|
||||
// Attribute not set. Try to calculate the default stride.
|
||||
|
||||
GUID subtype = GUID_NULL;
|
||||
UINT32 width = 0;
|
||||
/* UINT32 height = 0; */
|
||||
UINT64 val = 0;
|
||||
|
||||
// Get the subtype and the image size.
|
||||
ret = IMFMediaType_GetGUID(pType, &SDL_MF_MT_SUBTYPE, &subtype);
|
||||
if (FAILED(ret)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = IMFMediaType_GetUINT64(pType, &SDL_MF_MT_FRAME_SIZE, &val);
|
||||
if (FAILED(ret)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
width = (UINT32) (val >> 32);
|
||||
/* height = (UINT32) val; */
|
||||
|
||||
ret = pMFGetStrideForBitmapInfoHeader(subtype.Data1, width, &lStride);
|
||||
if (FAILED(ret)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Set the attribute for later reference.
|
||||
IMFMediaType_SetUINT32(pType, &SDL_MF_MT_DEFAULT_STRIDE, (UINT32) lStride);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(ret)) {
|
||||
*plStride = lStride;
|
||||
}
|
||||
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int MEDIAFOUNDATION_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
|
||||
{
|
||||
const char *utf8symlink = (const char *) device->handle;
|
||||
IMFAttributes *attrs = NULL;
|
||||
LPWSTR wstrsymlink = NULL;
|
||||
IMFMediaSource *source = NULL;
|
||||
IMFMediaType *mediatype = NULL;
|
||||
IMFSourceReader *srcreader = NULL;
|
||||
#if 0
|
||||
DWORD num_streams = 0;
|
||||
#endif
|
||||
LONG lstride = 0;
|
||||
//PROPVARIANT var;
|
||||
HRESULT ret;
|
||||
|
||||
#if 0
|
||||
IMFStreamDescriptor *streamdesc = NULL;
|
||||
IMFPresentationDescriptor *presentdesc = NULL;
|
||||
IMFMediaTypeHandler *handler = NULL;
|
||||
#endif
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: opening device with symlink of '%s'", utf8symlink);
|
||||
#endif
|
||||
|
||||
wstrsymlink = WIN_UTF8ToString(utf8symlink);
|
||||
if (!wstrsymlink) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
#define CHECK_HRESULT(what, r) if (FAILED(r)) { WIN_SetErrorFromHRESULT(what " failed", r); goto failed; }
|
||||
|
||||
ret = pMFCreateAttributes(&attrs, 1);
|
||||
CHECK_HRESULT("MFCreateAttributes", ret);
|
||||
|
||||
ret = IMFAttributes_SetGUID(attrs, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
|
||||
CHECK_HRESULT("IMFAttributes_SetGUID(srctype)", ret);
|
||||
|
||||
ret = IMFAttributes_SetString(attrs, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, wstrsymlink);
|
||||
CHECK_HRESULT("IMFAttributes_SetString(symlink)", ret);
|
||||
|
||||
ret = pMFCreateDeviceSource(attrs, &source);
|
||||
CHECK_HRESULT("MFCreateDeviceSource", ret);
|
||||
|
||||
IMFAttributes_Release(attrs);
|
||||
SDL_free(wstrsymlink);
|
||||
attrs = NULL;
|
||||
wstrsymlink = NULL;
|
||||
|
||||
// !!! FIXME: I think it'd be nice to do this without an IMFSourceReader,
|
||||
// since it's just utility code that has to handle more complex media streams
|
||||
// than we're dealing with, but this will do for now. The docs are slightly
|
||||
// insistent that you should use one, though...Maybe it's extremely hard
|
||||
// to handle directly at the IMFMediaSource layer...?
|
||||
ret = pMFCreateSourceReaderFromMediaSource(source, NULL, &srcreader);
|
||||
CHECK_HRESULT("MFCreateSourceReaderFromMediaSource", ret);
|
||||
|
||||
// !!! FIXME: do we actually have to find the media type object in the source reader or can we just roll our own like this?
|
||||
ret = pMFCreateMediaType(&mediatype);
|
||||
CHECK_HRESULT("MFCreateMediaType", ret);
|
||||
|
||||
ret = IMFMediaType_SetGUID(mediatype, &SDL_MF_MT_MAJOR_TYPE, &SDL_MFMediaType_Video);
|
||||
CHECK_HRESULT("IMFMediaType_SetGUID(major_type)", ret);
|
||||
|
||||
ret = IMFMediaType_SetGUID(mediatype, &SDL_MF_MT_SUBTYPE, SDLFmtToMFVidFmtGuid(spec->format));
|
||||
CHECK_HRESULT("IMFMediaType_SetGUID(subtype)", ret);
|
||||
|
||||
ret = IMFMediaType_SetUINT64(mediatype, &SDL_MF_MT_FRAME_SIZE, (((UINT64)spec->width) << 32) | ((UINT64)spec->height));
|
||||
CHECK_HRESULT("MFSetAttributeSize(frame_size)", ret);
|
||||
|
||||
ret = IMFMediaType_SetUINT64(mediatype, &SDL_MF_MT_FRAME_RATE, (((UINT64)spec->interval_numerator) << 32) | ((UINT64)spec->interval_denominator));
|
||||
CHECK_HRESULT("MFSetAttributeRatio(frame_rate)", ret);
|
||||
|
||||
ret = IMFSourceReader_SetCurrentMediaType(srcreader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mediatype);
|
||||
CHECK_HRESULT("IMFSourceReader_SetCurrentMediaType", ret);
|
||||
|
||||
#if 0 // this (untested thing) is what we would do to get started with a IMFMediaSource that _doesn't_ use IMFSourceReader...
|
||||
ret = IMFMediaSource_CreatePresentationDescriptor(source, &presentdesc);
|
||||
CHECK_HRESULT("IMFMediaSource_CreatePresentationDescriptor", ret);
|
||||
|
||||
ret = IMFPresentationDescriptor_GetStreamDescriptorCount(presentdesc, &num_streams);
|
||||
CHECK_HRESULT("IMFPresentationDescriptor_GetStreamDescriptorCount", ret);
|
||||
|
||||
for (DWORD i = 0; i < num_streams; i++) {
|
||||
BOOL selected = FALSE;
|
||||
ret = IMFPresentationDescriptor_GetStreamDescriptorByIndex(presentdesc, i, &selected, &streamdesc);
|
||||
CHECK_HRESULT("IMFPresentationDescriptor_GetStreamDescriptorByIndex", ret);
|
||||
|
||||
if (selected) {
|
||||
ret = IMFStreamDescriptor_GetMediaTypeHandler(streamdesc, &handler);
|
||||
CHECK_HRESULT("IMFStreamDescriptor_GetMediaTypeHandler", ret);
|
||||
IMFMediaTypeHandler_SetCurrentMediaType(handler, mediatype);
|
||||
IMFMediaTypeHandler_Release(handler);
|
||||
handler = NULL;
|
||||
}
|
||||
|
||||
IMFStreamDescriptor_Release(streamdesc);
|
||||
streamdesc = NULL;
|
||||
}
|
||||
|
||||
PropVariantInit(&var);
|
||||
var.vt = VT_EMPTY;
|
||||
ret = IMFMediaSource_Start(source, presentdesc, NULL, &var);
|
||||
PropVariantClear(&var);
|
||||
CHECK_HRESULT("IMFMediaSource_Start", ret);
|
||||
|
||||
IMFPresentationDescriptor_Release(presentdesc);
|
||||
presentdesc = NULL;
|
||||
#endif
|
||||
|
||||
ret = GetDefaultStride(mediatype, &lstride);
|
||||
CHECK_HRESULT("GetDefaultStride", ret);
|
||||
|
||||
IMFMediaType_Release(mediatype);
|
||||
mediatype = NULL;
|
||||
|
||||
device->hidden = (SDL_PrivateCameraData *) SDL_calloc(1, sizeof (SDL_PrivateCameraData));
|
||||
if (!device->hidden) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
device->hidden->pitch = (int) lstride;
|
||||
device->hidden->srcreader = srcreader;
|
||||
IMFMediaSource_Release(source); // srcreader is holding a reference to this.
|
||||
|
||||
// There is no user permission prompt for camera access (I think?)
|
||||
SDL_CameraDevicePermissionOutcome(device, SDL_TRUE);
|
||||
|
||||
#undef CHECK_HRESULT
|
||||
|
||||
return 0;
|
||||
|
||||
failed:
|
||||
|
||||
if (srcreader) {
|
||||
IMFSourceReader_Release(srcreader);
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (handler) {
|
||||
IMFMediaTypeHandler_Release(handler);
|
||||
}
|
||||
|
||||
if (streamdesc) {
|
||||
IMFStreamDescriptor_Release(streamdesc);
|
||||
}
|
||||
|
||||
if (presentdesc) {
|
||||
IMFPresentationDescriptor_Release(presentdesc);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (source) {
|
||||
IMFMediaSource_Shutdown(source);
|
||||
IMFMediaSource_Release(source);
|
||||
}
|
||||
|
||||
if (mediatype) {
|
||||
IMFMediaType_Release(mediatype);
|
||||
}
|
||||
|
||||
if (attrs) {
|
||||
IMFAttributes_Release(attrs);
|
||||
}
|
||||
SDL_free(wstrsymlink);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void MEDIAFOUNDATION_FreeDeviceHandle(SDL_CameraDevice *device)
|
||||
{
|
||||
if (device) {
|
||||
SDL_free(device->handle); // the device's symlink string.
|
||||
}
|
||||
}
|
||||
|
||||
static char *QueryActivationObjectString(IMFActivate *activation, const GUID *pguid)
|
||||
{
|
||||
LPWSTR wstr = NULL;
|
||||
UINT32 wlen = 0;
|
||||
HRESULT ret = IMFActivate_GetAllocatedString(activation, pguid, &wstr, &wlen);
|
||||
if (FAILED(ret)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *utf8str = WIN_StringToUTF8(wstr);
|
||||
CoTaskMemFree(wstr);
|
||||
return utf8str;
|
||||
}
|
||||
|
||||
static void GatherCameraSpecs(IMFMediaSource *source, CameraFormatAddData *add_data)
|
||||
{
|
||||
HRESULT ret;
|
||||
|
||||
// this has like a thousand steps. :/
|
||||
|
||||
SDL_zerop(add_data);
|
||||
|
||||
IMFPresentationDescriptor *presentdesc = NULL;
|
||||
ret = IMFMediaSource_CreatePresentationDescriptor(source, &presentdesc);
|
||||
if (FAILED(ret) || !presentdesc) {
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD num_streams = 0;
|
||||
ret = IMFPresentationDescriptor_GetStreamDescriptorCount(presentdesc, &num_streams);
|
||||
if (FAILED(ret)) {
|
||||
num_streams = 0;
|
||||
}
|
||||
|
||||
for (DWORD i = 0; i < num_streams; i++) {
|
||||
IMFStreamDescriptor *streamdesc = NULL;
|
||||
BOOL selected = FALSE;
|
||||
ret = IMFPresentationDescriptor_GetStreamDescriptorByIndex(presentdesc, i, &selected, &streamdesc);
|
||||
if (FAILED(ret) || !streamdesc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
IMFMediaTypeHandler *handler = NULL;
|
||||
ret = IMFStreamDescriptor_GetMediaTypeHandler(streamdesc, &handler);
|
||||
if (SUCCEEDED(ret) && handler) {
|
||||
DWORD num_mediatype = 0;
|
||||
ret = IMFMediaTypeHandler_GetMediaTypeCount(handler, &num_mediatype);
|
||||
if (FAILED(ret)) {
|
||||
num_mediatype = 0;
|
||||
}
|
||||
|
||||
for (DWORD j = 0; j < num_mediatype; j++) {
|
||||
IMFMediaType *mediatype = NULL;
|
||||
ret = IMFMediaTypeHandler_GetMediaTypeByIndex(handler, j, &mediatype);
|
||||
if (SUCCEEDED(ret) && mediatype) {
|
||||
GUID type;
|
||||
ret = IMFMediaType_GetGUID(mediatype, &SDL_MF_MT_MAJOR_TYPE, &type);
|
||||
if (SUCCEEDED(ret) && WIN_IsEqualGUID(&type, &SDL_MFMediaType_Video)) {
|
||||
ret = IMFMediaType_GetGUID(mediatype, &SDL_MF_MT_SUBTYPE, &type);
|
||||
if (SUCCEEDED(ret)) {
|
||||
const SDL_PixelFormatEnum sdlfmt = MFVidFmtGuidToSDLFmt(&type);
|
||||
if (sdlfmt != SDL_PIXELFORMAT_UNKNOWN) {
|
||||
UINT64 val = 0;
|
||||
UINT32 w = 0, h = 0;
|
||||
ret = IMFMediaType_GetUINT64(mediatype, &SDL_MF_MT_FRAME_SIZE, &val);
|
||||
w = (UINT32)(val >> 32);
|
||||
h = (UINT32)val;
|
||||
if (SUCCEEDED(ret) && w && h) {
|
||||
UINT32 interval_numerator = 0, interval_denominator = 0;
|
||||
ret = IMFMediaType_GetUINT64(mediatype, &SDL_MF_MT_FRAME_RATE, &val);
|
||||
interval_numerator = (UINT32)(val >> 32);
|
||||
interval_denominator = (UINT32)val;
|
||||
if (SUCCEEDED(ret) && interval_numerator && interval_denominator) {
|
||||
SDL_AddCameraFormat(add_data, sdlfmt, (int) w, (int) h, (int) interval_numerator, (int) interval_denominator); // whew.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
IMFMediaType_Release(mediatype);
|
||||
}
|
||||
}
|
||||
IMFMediaTypeHandler_Release(handler);
|
||||
}
|
||||
}
|
||||
IMFStreamDescriptor_Release(streamdesc);
|
||||
}
|
||||
|
||||
IMFPresentationDescriptor_Release(presentdesc);
|
||||
}
|
||||
|
||||
static SDL_bool FindMediaFoundationCameraDeviceBySymlink(SDL_CameraDevice *device, void *userdata)
|
||||
{
|
||||
return (SDL_strcmp((const char *) device->handle, (const char *) userdata) == 0);
|
||||
}
|
||||
|
||||
static void MaybeAddDevice(IMFActivate *activation)
|
||||
{
|
||||
char *symlink = QueryActivationObjectString(activation, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK);
|
||||
|
||||
if (SDL_FindPhysicalCameraDeviceByCallback(FindMediaFoundationCameraDeviceBySymlink, symlink)) {
|
||||
SDL_free(symlink);
|
||||
return; // already have this one.
|
||||
}
|
||||
|
||||
char *name = QueryActivationObjectString(activation, &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME);
|
||||
if (name && symlink) {
|
||||
IMFMediaSource *source = NULL;
|
||||
// "activating" here only creates an object, it doesn't open the actual camera hardware or start recording.
|
||||
HRESULT ret = IMFActivate_ActivateObject(activation, &SDL_IID_IMFMediaSource, (void**)&source);
|
||||
if (SUCCEEDED(ret) && source) {
|
||||
CameraFormatAddData add_data;
|
||||
GatherCameraSpecs(source, &add_data);
|
||||
if (add_data.num_specs > 0) {
|
||||
SDL_AddCameraDevice(name, SDL_CAMERA_POSITION_UNKNOWN, add_data.num_specs, add_data.specs, symlink);
|
||||
}
|
||||
SDL_free(add_data.specs);
|
||||
IMFActivate_ShutdownObject(activation);
|
||||
IMFMediaSource_Release(source);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(name);
|
||||
}
|
||||
|
||||
static void MEDIAFOUNDATION_DetectDevices(void)
|
||||
{
|
||||
// !!! FIXME: use CM_Register_Notification (Win8+) to get device notifications.
|
||||
// !!! FIXME: Earlier versions can use RegisterDeviceNotification, but I'm not bothering: no hotplug for you!
|
||||
HRESULT ret;
|
||||
|
||||
IMFAttributes *attrs = NULL;
|
||||
ret = pMFCreateAttributes(&attrs, 1);
|
||||
if (FAILED(ret)) {
|
||||
return; // oh well, no cameras for you.
|
||||
}
|
||||
|
||||
// !!! FIXME: We need these GUIDs hardcoded in this file.
|
||||
ret = IMFAttributes_SetGUID(attrs, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
|
||||
if (FAILED(ret)) {
|
||||
IMFAttributes_Release(attrs);
|
||||
return; // oh well, no cameras for you.
|
||||
}
|
||||
|
||||
IMFActivate **activations = NULL;
|
||||
UINT32 total = 0;
|
||||
ret = pMFEnumDeviceSources(attrs, &activations, &total);
|
||||
IMFAttributes_Release(attrs);
|
||||
if (FAILED(ret)) {
|
||||
return; // oh well, no cameras for you.
|
||||
}
|
||||
|
||||
for (UINT32 i = 0; i < total; i++) {
|
||||
MaybeAddDevice(activations[i]);
|
||||
IMFActivate_Release(activations[i]);
|
||||
}
|
||||
|
||||
CoTaskMemFree(activations);
|
||||
}
|
||||
|
||||
static void MEDIAFOUNDATION_Deinitialize(void)
|
||||
{
|
||||
pMFShutdown();
|
||||
|
||||
FreeLibrary(libmfreadwrite);
|
||||
libmfreadwrite = NULL;
|
||||
FreeLibrary(libmfplat);
|
||||
libmfplat = NULL;
|
||||
FreeLibrary(libmf);
|
||||
libmf = NULL;
|
||||
|
||||
pMFEnumDeviceSources = NULL;
|
||||
pMFCreateDeviceSource = NULL;
|
||||
pMFStartup = NULL;
|
||||
pMFShutdown = NULL;
|
||||
pMFCreateAttributes = NULL;
|
||||
pMFCreateMediaType = NULL;
|
||||
pMFCreateSourceReaderFromMediaSource = NULL;
|
||||
pMFGetStrideForBitmapInfoHeader = NULL;
|
||||
}
|
||||
|
||||
static SDL_bool MEDIAFOUNDATION_Init(SDL_CameraDriverImpl *impl)
|
||||
{
|
||||
// !!! FIXME: slide this off into a subroutine
|
||||
HMODULE mf = LoadLibrary(TEXT("Mf.dll")); // this library is available in Vista and later, but also can be on XP with service packs and Windows
|
||||
if (!mf) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
HMODULE mfplat = LoadLibrary(TEXT("Mfplat.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
|
||||
if (!mfplat) {
|
||||
FreeLibrary(mf);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
HMODULE mfreadwrite = LoadLibrary(TEXT("Mfreadwrite.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
|
||||
if (!mfreadwrite) {
|
||||
FreeLibrary(mfplat);
|
||||
FreeLibrary(mf);
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
SDL_bool okay = SDL_TRUE;
|
||||
#define LOADSYM(lib, fn) if (okay) { p##fn = (pfn##fn) GetProcAddress(lib, #fn); if (!p##fn) { okay = SDL_FALSE; } }
|
||||
LOADSYM(mf, MFEnumDeviceSources);
|
||||
LOADSYM(mf, MFCreateDeviceSource);
|
||||
LOADSYM(mfplat, MFStartup);
|
||||
LOADSYM(mfplat, MFShutdown);
|
||||
LOADSYM(mfplat, MFCreateAttributes);
|
||||
LOADSYM(mfplat, MFCreateMediaType);
|
||||
LOADSYM(mfplat, MFGetStrideForBitmapInfoHeader);
|
||||
LOADSYM(mfreadwrite, MFCreateSourceReaderFromMediaSource);
|
||||
#undef LOADSYM
|
||||
|
||||
if (!okay) {
|
||||
FreeLibrary(mfreadwrite);
|
||||
FreeLibrary(mfplat);
|
||||
FreeLibrary(mf);
|
||||
}
|
||||
|
||||
libmf = mf;
|
||||
libmfplat = mfplat;
|
||||
libmfreadwrite = mfreadwrite;
|
||||
|
||||
const HRESULT ret = pMFStartup(MF_VERSION, MFSTARTUP_LITE);
|
||||
if (FAILED(ret)) {
|
||||
FreeLibrary(libmfplat);
|
||||
libmfplat = NULL;
|
||||
FreeLibrary(libmf);
|
||||
libmf = NULL;
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
impl->DetectDevices = MEDIAFOUNDATION_DetectDevices;
|
||||
impl->OpenDevice = MEDIAFOUNDATION_OpenDevice;
|
||||
impl->CloseDevice = MEDIAFOUNDATION_CloseDevice;
|
||||
impl->WaitDevice = MEDIAFOUNDATION_WaitDevice;
|
||||
impl->AcquireFrame = MEDIAFOUNDATION_AcquireFrame;
|
||||
impl->ReleaseFrame = MEDIAFOUNDATION_ReleaseFrame;
|
||||
impl->FreeDeviceHandle = MEDIAFOUNDATION_FreeDeviceHandle;
|
||||
impl->Deinitialize = MEDIAFOUNDATION_Deinitialize;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
CameraBootStrap MEDIAFOUNDATION_bootstrap = {
|
||||
"mediafoundation", "SDL Windows Media Foundation camera driver", MEDIAFOUNDATION_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_CAMERA_DRIVER_MEDIAFOUNDATION
|
||||
|
||||
@@ -0,0 +1,890 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_CAMERA_DRIVER_V4L2
|
||||
|
||||
#include <stddef.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h> // low-level i/o
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#ifndef V4L2_CAP_DEVICE_CAPS
|
||||
// device_caps was added to struct v4l2_capability as of kernel 3.4.
|
||||
#define device_caps reserved[0]
|
||||
SDL_COMPILE_TIME_ASSERT(v4l2devicecaps, offsetof(struct v4l2_capability,device_caps) == offsetof(struct v4l2_capability,capabilities) + 4);
|
||||
#endif
|
||||
|
||||
#include "../SDL_syscamera.h"
|
||||
#include "../SDL_camera_c.h"
|
||||
#include "../../video/SDL_pixels_c.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
#include "../../core/linux/SDL_evdev_capabilities.h"
|
||||
#include "../../core/linux/SDL_udev.h"
|
||||
|
||||
#ifndef SDL_USE_LIBUDEV
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
typedef struct V4L2DeviceHandle
|
||||
{
|
||||
char *bus_info;
|
||||
char *path;
|
||||
} V4L2DeviceHandle;
|
||||
|
||||
|
||||
typedef enum io_method {
|
||||
IO_METHOD_INVALID,
|
||||
IO_METHOD_READ,
|
||||
IO_METHOD_MMAP,
|
||||
IO_METHOD_USERPTR
|
||||
} io_method;
|
||||
|
||||
struct buffer {
|
||||
void *start;
|
||||
size_t length;
|
||||
int available; // Is available in userspace
|
||||
};
|
||||
|
||||
struct SDL_PrivateCameraData
|
||||
{
|
||||
int fd;
|
||||
io_method io;
|
||||
int nb_buffers;
|
||||
struct buffer *buffers;
|
||||
int driver_pitch;
|
||||
};
|
||||
|
||||
static int xioctl(int fh, int request, void *arg)
|
||||
{
|
||||
int r;
|
||||
|
||||
do {
|
||||
r = ioctl(fh, request, arg);
|
||||
} while ((r == -1) && (errno == EINTR));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int V4L2_WaitDevice(SDL_CameraDevice *device)
|
||||
{
|
||||
const int fd = device->hidden->fd;
|
||||
|
||||
int retval;
|
||||
|
||||
do {
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 100 * 1000;
|
||||
|
||||
retval = select(fd + 1, &fds, NULL, NULL, &tv);
|
||||
if ((retval == -1) && (errno == EINTR)) {
|
||||
retval = 0; // pretend it was a timeout, keep looping.
|
||||
}
|
||||
} while (retval == 0);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int V4L2_AcquireFrame(SDL_CameraDevice *device, SDL_Surface *frame, Uint64 *timestampNS)
|
||||
{
|
||||
const int fd = device->hidden->fd;
|
||||
const io_method io = device->hidden->io;
|
||||
size_t size = device->hidden->buffers[0].length;
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
if (read(fd, device->hidden->buffers[0].start, size) == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
return 0;
|
||||
|
||||
case EIO:
|
||||
// Could ignore EIO, see spec.
|
||||
// fall through
|
||||
|
||||
default:
|
||||
return SDL_SetError("read");
|
||||
}
|
||||
}
|
||||
|
||||
*timestampNS = SDL_GetTicksNS(); // oh well, close enough.
|
||||
frame->pixels = device->hidden->buffers[0].start;
|
||||
frame->pitch = device->hidden->driver_pitch;
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
return 0;
|
||||
|
||||
case EIO:
|
||||
// Could ignore EIO, see spec.
|
||||
// fall through
|
||||
|
||||
default:
|
||||
return SDL_SetError("VIDIOC_DQBUF: %d", errno);
|
||||
}
|
||||
}
|
||||
|
||||
if ((int)buf.index < 0 || (int)buf.index >= device->hidden->nb_buffers) {
|
||||
return SDL_SetError("invalid buffer index");
|
||||
}
|
||||
|
||||
frame->pixels = device->hidden->buffers[buf.index].start;
|
||||
frame->pitch = device->hidden->driver_pitch;
|
||||
device->hidden->buffers[buf.index].available = 1;
|
||||
|
||||
*timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec);
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: debug mmap: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void*)frame->pixels);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_USERPTR;
|
||||
|
||||
if (xioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
return 0;
|
||||
|
||||
case EIO:
|
||||
// Could ignore EIO, see spec.
|
||||
|
||||
// fall through
|
||||
|
||||
default:
|
||||
return SDL_SetError("VIDIOC_DQBUF");
|
||||
}
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
if (buf.m.userptr == (unsigned long)device->hidden->buffers[i].start && buf.length == size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= device->hidden->nb_buffers) {
|
||||
return SDL_SetError("invalid buffer index");
|
||||
}
|
||||
|
||||
frame->pixels = (void*)buf.m.userptr;
|
||||
frame->pitch = device->hidden->driver_pitch;
|
||||
device->hidden->buffers[i].available = 1;
|
||||
|
||||
*timestampNS = (((Uint64) buf.timestamp.tv_sec) * SDL_NS_PER_SECOND) + SDL_US_TO_NS(buf.timestamp.tv_usec);
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: debug userptr: image %d/%d data[0]=%p", buf.index, device->hidden->nb_buffers, (void*)frame->pixels);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case IO_METHOD_INVALID:
|
||||
SDL_assert(!"Shouldn't have hit this");
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void V4L2_ReleaseFrame(SDL_CameraDevice *device, SDL_Surface *frame)
|
||||
{
|
||||
struct v4l2_buffer buf;
|
||||
const int fd = device->hidden->fd;
|
||||
const io_method io = device->hidden->io;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
if (frame->pixels == device->hidden->buffers[i].start) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= device->hidden->nb_buffers) {
|
||||
return; // oh well, we didn't own this.
|
||||
}
|
||||
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
// !!! FIXME: disconnect the device.
|
||||
return; //SDL_SetError("VIDIOC_QBUF");
|
||||
}
|
||||
device->hidden->buffers[i].available = 0;
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_USERPTR;
|
||||
buf.index = i;
|
||||
buf.m.userptr = (unsigned long)frame->pixels;
|
||||
buf.length = (int) device->hidden->buffers[i].length;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
// !!! FIXME: disconnect the device.
|
||||
return; //SDL_SetError("VIDIOC_QBUF");
|
||||
}
|
||||
device->hidden->buffers[i].available = 0;
|
||||
break;
|
||||
|
||||
case IO_METHOD_INVALID:
|
||||
SDL_assert(!"Shouldn't have hit this");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int EnqueueBuffers(SDL_CameraDevice *device)
|
||||
{
|
||||
const int fd = device->hidden->fd;
|
||||
const io_method io = device->hidden->io;
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
for (int i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
if (device->hidden->buffers[i].available == 0) {
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
SDL_zero(buf);
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
return SDL_SetError("VIDIOC_QBUF");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
for (int i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
if (device->hidden->buffers[i].available == 0) {
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
SDL_zero(buf);
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_USERPTR;
|
||||
buf.index = i;
|
||||
buf.m.userptr = (unsigned long)device->hidden->buffers[i].start;
|
||||
buf.length = (int) device->hidden->buffers[i].length;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QBUF, &buf) == -1) {
|
||||
return SDL_SetError("VIDIOC_QBUF");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IO_METHOD_INVALID: SDL_assert(!"Shouldn't have hit this"); break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int AllocBufferRead(SDL_CameraDevice *device, size_t buffer_size)
|
||||
{
|
||||
device->hidden->buffers[0].length = buffer_size;
|
||||
device->hidden->buffers[0].start = SDL_calloc(1, buffer_size);
|
||||
return device->hidden->buffers[0].start ? 0 : -1;
|
||||
}
|
||||
|
||||
static int AllocBufferMmap(SDL_CameraDevice *device)
|
||||
{
|
||||
const int fd = device->hidden->fd;
|
||||
int i;
|
||||
for (i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
SDL_zero(buf);
|
||||
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
|
||||
if (xioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
|
||||
return SDL_SetError("VIDIOC_QUERYBUF");
|
||||
}
|
||||
|
||||
device->hidden->buffers[i].length = buf.length;
|
||||
device->hidden->buffers[i].start =
|
||||
mmap(NULL /* start anywhere */,
|
||||
buf.length,
|
||||
PROT_READ | PROT_WRITE /* required */,
|
||||
MAP_SHARED /* recommended */,
|
||||
fd, buf.m.offset);
|
||||
|
||||
if (MAP_FAILED == device->hidden->buffers[i].start) {
|
||||
return SDL_SetError("mmap");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int AllocBufferUserPtr(SDL_CameraDevice *device, size_t buffer_size)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
device->hidden->buffers[i].length = buffer_size;
|
||||
device->hidden->buffers[i].start = SDL_calloc(1, buffer_size);
|
||||
|
||||
if (!device->hidden->buffers[i].start) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Uint32 format_v4l2_to_sdl(Uint32 fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(x, y) case x: return y
|
||||
CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2);
|
||||
CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN);
|
||||
#undef CASE
|
||||
default:
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: Unknown format V4L2_PIX_FORMAT '%d'", fmt);
|
||||
#endif
|
||||
return SDL_PIXELFORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static Uint32 format_sdl_to_v4l2(Uint32 fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define CASE(y, x) case x: return y
|
||||
CASE(V4L2_PIX_FMT_YUYV, SDL_PIXELFORMAT_YUY2);
|
||||
CASE(V4L2_PIX_FMT_MJPEG, SDL_PIXELFORMAT_UNKNOWN);
|
||||
#undef CASE
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void V4L2_CloseDevice(SDL_CameraDevice *device)
|
||||
{
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (device->hidden) {
|
||||
const io_method io = device->hidden->io;
|
||||
const int fd = device->hidden->fd;
|
||||
|
||||
if ((io == IO_METHOD_MMAP) || (io == IO_METHOD_USERPTR)) {
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
xioctl(fd, VIDIOC_STREAMOFF, &type);
|
||||
}
|
||||
|
||||
if (device->hidden->buffers) {
|
||||
switch (io) {
|
||||
case IO_METHOD_INVALID:
|
||||
break;
|
||||
|
||||
case IO_METHOD_READ:
|
||||
SDL_free(device->hidden->buffers[0].start);
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
for (int i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
if (munmap(device->hidden->buffers[i].start, device->hidden->buffers[i].length) == -1) {
|
||||
SDL_SetError("munmap");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
for (int i = 0; i < device->hidden->nb_buffers; ++i) {
|
||||
SDL_free(device->hidden->buffers[i].start);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_free(device->hidden->buffers);
|
||||
}
|
||||
|
||||
if (fd != -1) {
|
||||
close(fd);
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int V4L2_OpenDevice(SDL_CameraDevice *device, const SDL_CameraSpec *spec)
|
||||
{
|
||||
const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
|
||||
struct stat st;
|
||||
struct v4l2_capability cap;
|
||||
const int fd = open(handle->path, O_RDWR /* required */ | O_NONBLOCK, 0);
|
||||
|
||||
// most of this probably shouldn't fail unless the filesystem node changed out from under us since MaybeAddDevice().
|
||||
if (fd == -1) {
|
||||
return SDL_SetError("Cannot open '%s': %d, %s", handle->path, errno, strerror(errno));
|
||||
} else if (fstat(fd, &st) == -1) {
|
||||
close(fd);
|
||||
return SDL_SetError("Cannot identify '%s': %d, %s", handle->path, errno, strerror(errno));
|
||||
} else if (!S_ISCHR(st.st_mode)) {
|
||||
close(fd);
|
||||
return SDL_SetError("%s is not a character device", handle->path);
|
||||
} else if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
|
||||
const int err = errno;
|
||||
close(fd);
|
||||
if (err == EINVAL) {
|
||||
return SDL_SetError("%s is unexpectedly not a V4L2 device", handle->path);
|
||||
}
|
||||
return SDL_SetError("Error VIDIOC_QUERYCAP errno=%d device%s is no V4L2 device", err, handle->path);
|
||||
} else if ((cap.device_caps & V4L2_CAP_VIDEO_CAPTURE) == 0) {
|
||||
close(fd);
|
||||
return SDL_SetError("%s is unexpectedly not a video capture device", handle->path);
|
||||
}
|
||||
|
||||
device->hidden = (struct SDL_PrivateCameraData *) SDL_calloc(1, sizeof (struct SDL_PrivateCameraData));
|
||||
if (device->hidden == NULL) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
device->hidden->fd = fd;
|
||||
device->hidden->io = IO_METHOD_INVALID;
|
||||
|
||||
// Select video input, video standard and tune here.
|
||||
// errors in the crop code are not fatal.
|
||||
struct v4l2_cropcap cropcap;
|
||||
SDL_zero(cropcap);
|
||||
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (xioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0) {
|
||||
struct v4l2_crop crop;
|
||||
SDL_zero(crop);
|
||||
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
crop.c = cropcap.defrect; // reset to default
|
||||
xioctl(fd, VIDIOC_S_CROP, &crop);
|
||||
}
|
||||
|
||||
struct v4l2_format fmt;
|
||||
SDL_zero(fmt);
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmt.fmt.pix.width = spec->width;
|
||||
fmt.fmt.pix.height = spec->height;
|
||||
fmt.fmt.pix.pixelformat = format_sdl_to_v4l2(spec->format);
|
||||
//fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_ANY;
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: set SDL format %s", SDL_GetPixelFormatName(spec->format));
|
||||
{ const Uint32 f = fmt.fmt.pix.pixelformat; SDL_Log("CAMERA: set format V4L2_format=%d %c%c%c%c", f, (f >> 0) & 0xff, (f >> 8) & 0xff, (f >> 16) & 0xff, (f >> 24) & 0xff); }
|
||||
#endif
|
||||
|
||||
if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
|
||||
return SDL_SetError("Error VIDIOC_S_FMT");
|
||||
}
|
||||
|
||||
if (spec->interval_numerator && spec->interval_denominator) {
|
||||
struct v4l2_streamparm setfps;
|
||||
SDL_zero(setfps);
|
||||
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (xioctl(fd, VIDIOC_G_PARM, &setfps) == 0) {
|
||||
if ( (setfps.parm.capture.timeperframe.numerator != spec->interval_numerator) ||
|
||||
(setfps.parm.capture.timeperframe.denominator = spec->interval_denominator) ) {
|
||||
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
setfps.parm.capture.timeperframe.numerator = spec->interval_numerator;
|
||||
setfps.parm.capture.timeperframe.denominator = spec->interval_denominator;
|
||||
if (xioctl(fd, VIDIOC_S_PARM, &setfps) == -1) {
|
||||
return SDL_SetError("Error VIDIOC_S_PARM");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_zero(fmt);
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (xioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
|
||||
return SDL_SetError("Error VIDIOC_G_FMT");
|
||||
}
|
||||
device->hidden->driver_pitch = fmt.fmt.pix.bytesperline;
|
||||
|
||||
io_method io = IO_METHOD_INVALID;
|
||||
if ((io == IO_METHOD_INVALID) && (cap.device_caps & V4L2_CAP_STREAMING)) {
|
||||
struct v4l2_requestbuffers req;
|
||||
SDL_zero(req);
|
||||
req.count = 8;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
if ((xioctl(fd, VIDIOC_REQBUFS, &req) == 0) && (req.count >= 2)) {
|
||||
io = IO_METHOD_MMAP;
|
||||
device->hidden->nb_buffers = req.count;
|
||||
} else { // mmap didn't work out? Try USERPTR.
|
||||
SDL_zero(req);
|
||||
req.count = 8;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_USERPTR;
|
||||
if (xioctl(fd, VIDIOC_REQBUFS, &req) == 0) {
|
||||
io = IO_METHOD_USERPTR;
|
||||
device->hidden->nb_buffers = 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((io == IO_METHOD_INVALID) && (cap.device_caps & V4L2_CAP_READWRITE)) {
|
||||
io = IO_METHOD_READ;
|
||||
device->hidden->nb_buffers = 1;
|
||||
}
|
||||
|
||||
if (io == IO_METHOD_INVALID) {
|
||||
return SDL_SetError("Don't have a way to talk to this device");
|
||||
}
|
||||
|
||||
device->hidden->io = io;
|
||||
|
||||
device->hidden->buffers = SDL_calloc(device->hidden->nb_buffers, sizeof(*device->hidden->buffers));
|
||||
if (!device->hidden->buffers) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t size, pitch;
|
||||
SDL_CalculateSurfaceSize(device->spec.format, device->spec.width, device->spec.height, &size, &pitch, SDL_FALSE);
|
||||
|
||||
int rc = 0;
|
||||
switch (io) {
|
||||
case IO_METHOD_READ:
|
||||
rc = AllocBufferRead(device, size);
|
||||
break;
|
||||
|
||||
case IO_METHOD_MMAP:
|
||||
rc = AllocBufferMmap(device);
|
||||
break;
|
||||
|
||||
case IO_METHOD_USERPTR:
|
||||
rc = AllocBufferUserPtr(device, size);
|
||||
break;
|
||||
|
||||
case IO_METHOD_INVALID:
|
||||
SDL_assert(!"Shouldn't have hit this");
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
} else if (EnqueueBuffers(device) < 0) {
|
||||
return -1;
|
||||
} else if (io != IO_METHOD_READ) {
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (xioctl(fd, VIDIOC_STREAMON, &type) == -1) {
|
||||
return SDL_SetError("VIDIOC_STREAMON");
|
||||
}
|
||||
}
|
||||
|
||||
// Currently there is no user permission prompt for camera access, but maybe there will be a D-Bus portal interface at some point.
|
||||
SDL_CameraDevicePermissionOutcome(device, SDL_TRUE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SDL_bool FindV4L2CameraDeviceByBusInfoCallback(SDL_CameraDevice *device, void *userdata)
|
||||
{
|
||||
const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
|
||||
return (SDL_strcmp(handle->bus_info, (const char *) userdata) == 0);
|
||||
}
|
||||
|
||||
static int AddCameraFormat(const int fd, CameraFormatAddData *data, Uint32 sdlfmt, Uint32 v4l2fmt, int w, int h)
|
||||
{
|
||||
struct v4l2_frmivalenum frmivalenum;
|
||||
SDL_zero(frmivalenum);
|
||||
frmivalenum.pixel_format = v4l2fmt;
|
||||
frmivalenum.width = (Uint32) w;
|
||||
frmivalenum.height = (Uint32) h;
|
||||
|
||||
while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmivalenum) == 0) {
|
||||
if (frmivalenum.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
|
||||
const int numerator = (int) frmivalenum.discrete.numerator;
|
||||
const int denominator = (int) frmivalenum.discrete.denominator;
|
||||
#if DEBUG_CAMERA
|
||||
const float fps = (float) denominator / (float) numerator;
|
||||
SDL_Log("CAMERA: * Has discrete frame interval (%d / %d), fps=%f", numerator, denominator, fps);
|
||||
#endif
|
||||
if (SDL_AddCameraFormat(data, sdlfmt, w, h, numerator, denominator) == -1) {
|
||||
return -1; // Probably out of memory; we'll go with what we have, if anything.
|
||||
}
|
||||
frmivalenum.index++; // set up for the next one.
|
||||
} else if ((frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) || (frmivalenum.type == V4L2_FRMIVAL_TYPE_CONTINUOUS)) {
|
||||
int d = frmivalenum.stepwise.min.denominator;
|
||||
// !!! FIXME: should we step by the numerator...?
|
||||
for (int n = (int) frmivalenum.stepwise.min.numerator; n <= (int) frmivalenum.stepwise.max.numerator; n += (int) frmivalenum.stepwise.step.numerator) {
|
||||
#if DEBUG_CAMERA
|
||||
const float fps = (float) d / (float) n;
|
||||
SDL_Log("CAMERA: * Has %s frame interval (%d / %d), fps=%f", (frmivalenum.type == V4L2_FRMIVAL_TYPE_STEPWISE) ? "stepwise" : "continuous", n, d, fps);
|
||||
#endif
|
||||
if (SDL_AddCameraFormat(data, sdlfmt, w, h, n, d) == -1) {
|
||||
return -1; // Probably out of memory; we'll go with what we have, if anything.
|
||||
}
|
||||
d += (int) frmivalenum.stepwise.step.denominator;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void MaybeAddDevice(const char *path)
|
||||
{
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
const int fd = open(path, O_RDWR /* required */ | O_NONBLOCK, 0);
|
||||
if (fd == -1) {
|
||||
return; // can't open it? skip it.
|
||||
} else if (fstat(fd, &st) == -1) {
|
||||
close(fd);
|
||||
return; // can't stat it? skip it.
|
||||
} else if (!S_ISCHR(st.st_mode)) {
|
||||
close(fd);
|
||||
return; // not a character device.
|
||||
}
|
||||
|
||||
struct v4l2_capability vcap;
|
||||
const int rc = ioctl(fd, VIDIOC_QUERYCAP, &vcap);
|
||||
if (rc != 0) {
|
||||
close(fd);
|
||||
return; // probably not a v4l2 device at all.
|
||||
} else if ((vcap.device_caps & V4L2_CAP_VIDEO_CAPTURE) == 0) {
|
||||
close(fd);
|
||||
return; // not a video capture device.
|
||||
} else if (SDL_FindPhysicalCameraDeviceByCallback(FindV4L2CameraDeviceByBusInfoCallback, vcap.bus_info)) {
|
||||
close(fd);
|
||||
return; // already have it.
|
||||
}
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: V4L2 camera path='%s' bus_info='%s' name='%s'", path, (const char *) vcap.bus_info, vcap.card);
|
||||
#endif
|
||||
|
||||
CameraFormatAddData add_data;
|
||||
SDL_zero(add_data);
|
||||
|
||||
struct v4l2_fmtdesc fmtdesc;
|
||||
SDL_zero(fmtdesc);
|
||||
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
|
||||
const Uint32 sdlfmt = format_v4l2_to_sdl(fmtdesc.pixelformat);
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: - Has format '%s'%s%s", SDL_GetPixelFormatName(sdlfmt),
|
||||
(fmtdesc.flags & V4L2_FMT_FLAG_EMULATED) ? " [EMULATED]" : "",
|
||||
(fmtdesc.flags & V4L2_FMT_FLAG_COMPRESSED) ? " [COMPRESSED]" : "");
|
||||
#endif
|
||||
|
||||
fmtdesc.index++; // prepare for next iteration.
|
||||
|
||||
if (sdlfmt == SDL_PIXELFORMAT_UNKNOWN) {
|
||||
continue; // unsupported by SDL atm.
|
||||
}
|
||||
|
||||
struct v4l2_frmsizeenum frmsizeenum;
|
||||
SDL_zero(frmsizeenum);
|
||||
frmsizeenum.pixel_format = fmtdesc.pixelformat;
|
||||
|
||||
while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == 0) {
|
||||
if (frmsizeenum.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
|
||||
const int w = (int) frmsizeenum.discrete.width;
|
||||
const int h = (int) frmsizeenum.discrete.height;
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: * Has discrete size %dx%d", w, h);
|
||||
#endif
|
||||
if (AddCameraFormat(fd, &add_data, sdlfmt, fmtdesc.pixelformat, w, h) == -1) {
|
||||
break; // Probably out of memory; we'll go with what we have, if anything.
|
||||
}
|
||||
frmsizeenum.index++; // set up for the next one.
|
||||
} else if ((frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) || (frmsizeenum.type == V4L2_FRMSIZE_TYPE_CONTINUOUS)) {
|
||||
const int minw = (int) frmsizeenum.stepwise.min_width;
|
||||
const int minh = (int) frmsizeenum.stepwise.min_height;
|
||||
const int maxw = (int) frmsizeenum.stepwise.max_width;
|
||||
const int maxh = (int) frmsizeenum.stepwise.max_height;
|
||||
const int stepw = (int) frmsizeenum.stepwise.step_width;
|
||||
const int steph = (int) frmsizeenum.stepwise.step_height;
|
||||
for (int w = minw; w <= maxw; w += stepw) {
|
||||
for (int h = minh; w <= maxh; w += steph) {
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: * Has %s size %dx%d", (frmsizeenum.type == V4L2_FRMSIZE_TYPE_STEPWISE) ? "stepwise" : "continuous", w, h);
|
||||
#endif
|
||||
if (AddCameraFormat(fd, &add_data, sdlfmt, fmtdesc.pixelformat, w, h) == -1) {
|
||||
break; // Probably out of memory; we'll go with what we have, if anything.
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
#if DEBUG_CAMERA
|
||||
SDL_Log("CAMERA: (total specs: %d)", add_data.num_specs);
|
||||
#endif
|
||||
|
||||
if (add_data.num_specs > 0) {
|
||||
V4L2DeviceHandle *handle = (V4L2DeviceHandle *) SDL_calloc(1, sizeof (V4L2DeviceHandle));
|
||||
if (handle) {
|
||||
handle->path = SDL_strdup(path);
|
||||
if (handle->path) {
|
||||
handle->bus_info = SDL_strdup((char *)vcap.bus_info);
|
||||
if (handle->bus_info) {
|
||||
if (SDL_AddCameraDevice((const char *) vcap.card, SDL_CAMERA_POSITION_UNKNOWN, add_data.num_specs, add_data.specs, handle)) {
|
||||
SDL_free(add_data.specs);
|
||||
return; // good to go.
|
||||
}
|
||||
SDL_free(handle->bus_info);
|
||||
}
|
||||
SDL_free(handle->path);
|
||||
}
|
||||
SDL_free(handle);
|
||||
}
|
||||
}
|
||||
SDL_free(add_data.specs);
|
||||
}
|
||||
|
||||
static void V4L2_FreeDeviceHandle(SDL_CameraDevice *device)
|
||||
{
|
||||
if (device) {
|
||||
V4L2DeviceHandle *handle = (V4L2DeviceHandle *) device->handle;
|
||||
SDL_free(handle->path);
|
||||
SDL_free(handle->bus_info);
|
||||
SDL_free(handle);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SDL_USE_LIBUDEV
|
||||
static SDL_bool FindV4L2CameraDeviceByPathCallback(SDL_CameraDevice *device, void *userdata)
|
||||
{
|
||||
const V4L2DeviceHandle *handle = (const V4L2DeviceHandle *) device->handle;
|
||||
return (SDL_strcmp(handle->path, (const char *) userdata) == 0);
|
||||
}
|
||||
|
||||
static void MaybeRemoveDevice(const char *path)
|
||||
{
|
||||
if (path) {
|
||||
SDL_CameraDeviceDisconnected(SDL_FindPhysicalCameraDeviceByCallback(FindV4L2CameraDeviceByPathCallback, (void *) path));
|
||||
}
|
||||
}
|
||||
|
||||
static void CameraUdevCallback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath)
|
||||
{
|
||||
if (devpath && (udev_class & SDL_UDEV_DEVICE_VIDEO_CAPTURE)) {
|
||||
if (udev_type == SDL_UDEV_DEVICEADDED) {
|
||||
MaybeAddDevice(devpath);
|
||||
} else if (udev_type == SDL_UDEV_DEVICEREMOVED) {
|
||||
MaybeRemoveDevice(devpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // SDL_USE_LIBUDEV
|
||||
|
||||
static void V4L2_Deinitialize(void)
|
||||
{
|
||||
#ifdef SDL_USE_LIBUDEV
|
||||
SDL_UDEV_DelCallback(CameraUdevCallback);
|
||||
SDL_UDEV_Quit();
|
||||
#endif // SDL_USE_LIBUDEV
|
||||
}
|
||||
|
||||
static void V4L2_DetectDevices(void)
|
||||
{
|
||||
#ifdef SDL_USE_LIBUDEV
|
||||
if (SDL_UDEV_Init() == 0) {
|
||||
if (SDL_UDEV_AddCallback(CameraUdevCallback) == 0) {
|
||||
SDL_UDEV_Scan(); // Force a scan to build the initial device list
|
||||
}
|
||||
}
|
||||
#else
|
||||
DIR *dirp = opendir("/dev");
|
||||
if (dirp) {
|
||||
struct dirent *dent;
|
||||
while ((dent = readdir(dirp)) != NULL) {
|
||||
int num = 0;
|
||||
if (SDL_sscanf(dent->d_name, "video%d", &num) == 1) {
|
||||
char fullpath[64];
|
||||
SDL_snprintf(fullpath, sizeof (fullpath), "/dev/video%d", num);
|
||||
MaybeAddDevice(fullpath);
|
||||
}
|
||||
}
|
||||
closedir(dirp);
|
||||
}
|
||||
#endif // SDL_USE_LIBUDEV
|
||||
}
|
||||
|
||||
static SDL_bool V4L2_Init(SDL_CameraDriverImpl *impl)
|
||||
{
|
||||
impl->DetectDevices = V4L2_DetectDevices;
|
||||
impl->OpenDevice = V4L2_OpenDevice;
|
||||
impl->CloseDevice = V4L2_CloseDevice;
|
||||
impl->WaitDevice = V4L2_WaitDevice;
|
||||
impl->AcquireFrame = V4L2_AcquireFrame;
|
||||
impl->ReleaseFrame = V4L2_ReleaseFrame;
|
||||
impl->FreeDeviceHandle = V4L2_FreeDeviceHandle;
|
||||
impl->Deinitialize = V4L2_Deinitialize;
|
||||
|
||||
return SDL_TRUE;
|
||||
}
|
||||
|
||||
CameraBootStrap V4L2_bootstrap = {
|
||||
"v4l2", "SDL Video4Linux2 camera driver", V4L2_Init, SDL_FALSE
|
||||
};
|
||||
|
||||
#endif // SDL_CAMERA_DRIVER_V4L2
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_VIDEO_DRIVER_X11
|
||||
|
||||
DECLSPEC void SDLCALL SDL_SetX11EventHook(SDL_X11EventHook callback, void *userdata)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef SDL_PLATFORM_LINUX
|
||||
|
||||
DECLSPEC int SDLCALL SDL_LinuxSetThreadPriority(Sint64 threadID, int priority);
|
||||
int SDL_LinuxSetThreadPriority(Sint64 threadID, int priority)
|
||||
{
|
||||
(void)threadID;
|
||||
(void)priority;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
DECLSPEC int SDLCALL SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy);
|
||||
int SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy)
|
||||
{
|
||||
(void)threadID;
|
||||
(void)sdlPriority;
|
||||
(void)schedPolicy;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef SDL_PLATFORM_GDK
|
||||
|
||||
DECLSPEC void SDLCALL SDL_GDKSuspendComplete(void);
|
||||
void SDL_GDKSuspendComplete(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
}
|
||||
|
||||
DECLSPEC int SDLCALL SDL_GDKGetDefaultUser(void *outUserHandle); /* XUserHandle *outUserHandle */
|
||||
int SDL_GDKGetDefaultUser(void *outUserHandle)
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if !(defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINRT) || defined(SDL_PLATFORM_GDK))
|
||||
|
||||
DECLSPEC int SDLCALL SDL_RegisterApp(const char *name, Uint32 style, void *hInst);
|
||||
int SDL_RegisterApp(const char *name, Uint32 style, void *hInst)
|
||||
{
|
||||
(void)name;
|
||||
(void)style;
|
||||
(void)hInst;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
DECLSPEC void SDLCALL SDL_SetWindowsMessageHook(void *callback, void *userdata); /* SDL_WindowsMessageHook callback */
|
||||
void SDL_SetWindowsMessageHook(void *callback, void *userdata)
|
||||
{
|
||||
(void)callback;
|
||||
(void)userdata;
|
||||
SDL_Unsupported();
|
||||
}
|
||||
|
||||
DECLSPEC void SDLCALL SDL_UnregisterApp(void);
|
||||
void SDL_UnregisterApp(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef SDL_PLATFORM_WINRT
|
||||
|
||||
/* Returns SDL_WinRT_DeviceFamily enum */
|
||||
DECLSPEC int SDLCALL SDL_WinRTGetDeviceFamily(void);
|
||||
int SDL_WinRTGetDeviceFamily()
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return 0; /* SDL_WINRT_DEVICEFAMILY_UNKNOWN */
|
||||
}
|
||||
|
||||
DECLSPEC const wchar_t *SDLCALL SDL_WinRTGetFSPathUNICODE(int pathType); /* SDL_WinRT_Path pathType */
|
||||
const wchar_t *SDL_WinRTGetFSPathUNICODE(int pathType)
|
||||
{
|
||||
(void)pathType;
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DECLSPEC const char *SDLCALL SDL_WinRTGetFSPathUTF8(int pathType); /* SDL_WinRT_Path pathType */
|
||||
const char *SDL_WinRTGetFSPathUTF8(int pathType)
|
||||
{
|
||||
(void)pathType;
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SDL_PLATFORM_ANDROID
|
||||
|
||||
DECLSPEC void SDLCALL SDL_AndroidBackButton(void);
|
||||
void SDL_AndroidBackButton()
|
||||
{
|
||||
SDL_Unsupported();
|
||||
}
|
||||
|
||||
DECLSPEC void *SDLCALL SDL_AndroidGetActivity(void);
|
||||
void *SDL_AndroidGetActivity()
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DECLSPEC const char *SDLCALL SDL_AndroidGetExternalStoragePath(void);
|
||||
const char* SDL_AndroidGetExternalStoragePath()
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DECLSPEC int SDLCALL SDL_AndroidGetExternalStorageState(Uint32 *state);
|
||||
int SDL_AndroidGetExternalStorageState(Uint32 *state)
|
||||
{
|
||||
(void)state;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
DECLSPEC const char *SDLCALL SDL_AndroidGetInternalStoragePath(void);
|
||||
const char *SDL_AndroidGetInternalStoragePath()
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DECLSPEC void *SDLCALL SDL_AndroidGetJNIEnv(void);
|
||||
void *SDL_AndroidGetJNIEnv()
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef void (SDLCALL *SDL_AndroidRequestPermissionCallback)(void *userdata, const char *permission, SDL_bool granted);
|
||||
DECLSPEC int SDLCALL SDL_AndroidRequestPermission(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata);
|
||||
int SDL_AndroidRequestPermission(const char *permission, SDL_AndroidRequestPermissionCallback cb, void *userdata)
|
||||
{
|
||||
(void)permission;
|
||||
(void)cb;
|
||||
(void)userdata;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
DECLSPEC int SDLCALL SDL_AndroidSendMessage(Uint32 command, int param);
|
||||
int SDL_AndroidSendMessage(Uint32 command, int param)
|
||||
{
|
||||
(void)command;
|
||||
(void)param;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
DECLSPEC int SDLCALL SDL_AndroidShowToast(const char* message, int duration, int gravity, int xoffset, int yoffset);
|
||||
int SDL_AndroidShowToast(const char* message, int duration, int gravity, int xoffset, int yoffset)
|
||||
{
|
||||
(void)message;
|
||||
(void)duration;
|
||||
(void)gravity;
|
||||
(void)xoffset;
|
||||
(void)yoffset;
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
DECLSPEC int SDLCALL SDL_GetAndroidSDKVersion(void);
|
||||
int SDL_GetAndroidSDKVersion()
|
||||
{
|
||||
return SDL_Unsupported();
|
||||
}
|
||||
|
||||
DECLSPEC SDL_bool SDLCALL SDL_IsAndroidTV(void);
|
||||
SDL_bool SDL_IsAndroidTV()
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
DECLSPEC SDL_bool SDLCALL SDL_IsChromebook(void);
|
||||
SDL_bool SDL_IsChromebook()
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
DECLSPEC SDL_bool SDLCALL SDL_IsDeXMode(void);
|
||||
SDL_bool SDL_IsDeXMode(void)
|
||||
{
|
||||
SDL_Unsupported();
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
DECLSPEC Sint32 SDLCALL JNI_OnLoad(void *vm, void *reserved);
|
||||
Sint32 JNI_OnLoad(void *vm, void *reserved)
|
||||
{
|
||||
(void)vm;
|
||||
(void)reserved;
|
||||
SDL_Unsupported();
|
||||
return -1; /* JNI_ERR */
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
|
||||
char *SDL_GetUserFolder(SDL_Folder folder)
|
||||
{
|
||||
(void)folder;
|
||||
SDL_Unsupported();
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
/* Most platforms that use/need SDL_main have their own SDL_RunApp() implementation.
|
||||
* If not, you can special case it here by appending || defined(__YOUR_PLATFORM__) */
|
||||
#if ( !defined(SDL_MAIN_NEEDED) && !defined(SDL_MAIN_AVAILABLE) ) || defined(SDL_PLATFORM_ANDROID)
|
||||
|
||||
DECLSPEC int
|
||||
SDL_RunApp(int argc, char* argv[], SDL_main_func mainFunction, void * reserved)
|
||||
{
|
||||
(void)reserved;
|
||||
|
||||
if(!argv)
|
||||
{
|
||||
/* make sure argv isn't NULL, in case some user code doesn't like that */
|
||||
static char dummyargv0[] = { 'S', 'D', 'L', '_', 'a', 'p', 'p', '\0' };
|
||||
static char* argvdummy[2] = { dummyargv0, NULL };
|
||||
argc = 1;
|
||||
argv = argvdummy;
|
||||
}
|
||||
|
||||
return mainFunction(argc, argv);
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
/* Set up for C function definitions, even when using C++ */
|
||||
#ifdef __cplusplus
|
||||
/* *INDENT-OFF* */
|
||||
extern "C" {
|
||||
/* *INDENT-ON* */
|
||||
#endif
|
||||
|
||||
#include <EGL/eglplatform.h>
|
||||
#include <android/native_window_jni.h>
|
||||
|
||||
#include "../../audio/SDL_sysaudio.h"
|
||||
|
||||
// this appears to be broken right now (on Android, not SDL, I think...?).
|
||||
#define ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES 0
|
||||
|
||||
/* Interface from the SDL library into the Android Java activity */
|
||||
extern void Android_JNI_SetActivityTitle(const char *title);
|
||||
extern void Android_JNI_SetWindowStyle(SDL_bool fullscreen);
|
||||
extern void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint);
|
||||
extern void Android_JNI_MinizeWindow(void);
|
||||
extern SDL_bool Android_JNI_ShouldMinimizeOnFocusLoss(void);
|
||||
|
||||
extern SDL_bool Android_JNI_GetAccelerometerValues(float values[3]);
|
||||
extern void Android_JNI_ShowScreenKeyboard(SDL_Rect *inputRect);
|
||||
extern void Android_JNI_HideScreenKeyboard(void);
|
||||
extern SDL_bool Android_JNI_IsScreenKeyboardShown(void);
|
||||
extern ANativeWindow *Android_JNI_GetNativeWindow(void);
|
||||
|
||||
extern SDL_DisplayOrientation Android_JNI_GetDisplayNaturalOrientation(void);
|
||||
extern SDL_DisplayOrientation Android_JNI_GetDisplayCurrentOrientation(void);
|
||||
|
||||
/* Audio support */
|
||||
void Android_StartAudioHotplug(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture);
|
||||
void Android_StopAudioHotplug(void);
|
||||
extern void Android_AudioThreadInit(SDL_AudioDevice *device);
|
||||
extern int Android_JNI_OpenAudioDevice(SDL_AudioDevice *device);
|
||||
extern void *Android_JNI_GetAudioBuffer(void);
|
||||
extern void Android_JNI_WriteAudioBuffer(void);
|
||||
extern int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen);
|
||||
extern void Android_JNI_FlushCapturedAudio(void);
|
||||
extern void Android_JNI_CloseAudioDevice(const int iscapture);
|
||||
|
||||
/* Detecting device type */
|
||||
extern SDL_bool Android_IsDeXMode(void);
|
||||
extern SDL_bool Android_IsChromebook(void);
|
||||
|
||||
int Android_JNI_FileOpen(void **puserdata, const char *fileName, const char *mode);
|
||||
Sint64 Android_JNI_FileSize(void *userdata);
|
||||
Sint64 Android_JNI_FileSeek(void *userdata, Sint64 offset, int whence);
|
||||
size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOStatus *status);
|
||||
size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status);
|
||||
int Android_JNI_FileClose(void *userdata);
|
||||
|
||||
/* Environment support */
|
||||
void Android_JNI_GetManifestEnvironmentVariables(void);
|
||||
|
||||
/* Clipboard support */
|
||||
int Android_JNI_SetClipboardText(const char *text);
|
||||
char *Android_JNI_GetClipboardText(void);
|
||||
SDL_bool Android_JNI_HasClipboardText(void);
|
||||
|
||||
/* Power support */
|
||||
int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent);
|
||||
|
||||
/* Joystick support */
|
||||
void Android_JNI_PollInputDevices(void);
|
||||
|
||||
/* Haptic support */
|
||||
void Android_JNI_PollHapticDevices(void);
|
||||
void Android_JNI_HapticRun(int device_id, float intensity, int length);
|
||||
void Android_JNI_HapticStop(int device_id);
|
||||
|
||||
/* Video */
|
||||
int Android_JNI_SuspendScreenSaver(SDL_bool suspend);
|
||||
|
||||
/* Touch support */
|
||||
void Android_JNI_InitTouch(void);
|
||||
|
||||
/* Threads */
|
||||
#include <jni.h>
|
||||
JNIEnv *Android_JNI_GetEnv(void);
|
||||
int Android_JNI_SetupThread(void);
|
||||
|
||||
/* Locale */
|
||||
int Android_JNI_GetLocale(char *buf, size_t buflen);
|
||||
|
||||
/* Generic messages */
|
||||
int Android_JNI_SendMessage(int command, int param);
|
||||
|
||||
/* Init */
|
||||
JNIEXPORT void JNICALL SDL_Android_Init(JNIEnv *mEnv, jclass cls);
|
||||
|
||||
/* MessageBox */
|
||||
int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID);
|
||||
|
||||
/* Cursor support */
|
||||
int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y);
|
||||
void Android_JNI_DestroyCustomCursor(int cursorID);
|
||||
SDL_bool Android_JNI_SetCustomCursor(int cursorID);
|
||||
SDL_bool Android_JNI_SetSystemCursor(int cursorID);
|
||||
|
||||
/* Relative mouse support */
|
||||
SDL_bool Android_JNI_SupportsRelativeMouse(void);
|
||||
SDL_bool Android_JNI_SetRelativeMouseEnabled(SDL_bool enabled);
|
||||
|
||||
/* Show toast notification */
|
||||
int Android_JNI_ShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset);
|
||||
|
||||
int Android_JNI_OpenURL(const char *url);
|
||||
|
||||
int SDL_GetAndroidSDKVersion(void);
|
||||
|
||||
SDL_bool SDL_IsAndroidTablet(void);
|
||||
SDL_bool SDL_IsAndroidTV(void);
|
||||
SDL_bool SDL_IsChromebook(void);
|
||||
SDL_bool SDL_IsDeXMode(void);
|
||||
|
||||
void Android_ActivityMutex_Lock(void);
|
||||
void Android_ActivityMutex_Unlock(void);
|
||||
void Android_ActivityMutex_Lock_Running(void);
|
||||
|
||||
/* Ends C function definitions when using C++ */
|
||||
#ifdef __cplusplus
|
||||
/* *INDENT-OFF* */
|
||||
}
|
||||
/* *INDENT-ON* */
|
||||
#endif
|
||||
@@ -0,0 +1,167 @@
|
||||
#include <sys/kbio.h>
|
||||
|
||||
/* *INDENT-OFF* */ /* clang-format off */
|
||||
/*
|
||||
* Automatically generated from /usr/share/vt/keymaps/us.acc.kbd.
|
||||
* DO NOT EDIT!
|
||||
*/
|
||||
static keymap_t keymap_default_us_acc = { 0x6d, {
|
||||
/* alt
|
||||
* scan cntrl alt alt cntrl
|
||||
* code base shift cntrl shift alt shift cntrl shift spcl flgs
|
||||
* ---------------------------------------------------------------------------
|
||||
*/
|
||||
/*00*/{{ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, }, 0xFF,0x00 },
|
||||
/*01*/{{ 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, DBG, DBG, }, 0x03,0x00 },
|
||||
/*02*/{{ '1', '!', NOP, NOP, '1', '!', NOP, NOP, }, 0x33,0x00 },
|
||||
/*03*/{{ '2', '@', 0x00, 0x00, '2', '@', 0x00, 0x00, }, 0x00,0x00 },
|
||||
/*04*/{{ '3', '#', NOP, NOP, '3', '#', NOP, NOP, }, 0x33,0x00 },
|
||||
/*05*/{{ '4', '$', NOP, NOP, '4', '$', NOP, NOP, }, 0x33,0x00 },
|
||||
/*06*/{{ '5', '%', NOP, NOP, '5', '%', NOP, NOP, }, 0x33,0x00 },
|
||||
/*07*/{{ '6', '^', 0x1E, 0x1E, '6', DCIR, 0x1E, 0x1E, }, 0x04,0x00 },
|
||||
/*08*/{{ '7', '&', NOP, NOP, '7', '&', NOP, NOP, }, 0x33,0x00 },
|
||||
/*09*/{{ '8', '*', NOP, NOP, '8', DRIN, NOP, NOP, }, 0x37,0x00 },
|
||||
/*0a*/{{ '9', '(', NOP, NOP, '9', '(', NOP, NOP, }, 0x33,0x00 },
|
||||
/*0b*/{{ '0', ')', NOP, NOP, '0', ')', NOP, NOP, }, 0x33,0x00 },
|
||||
/*0c*/{{ '-', '_', 0x1F, 0x1F, '-', '_', 0x1F, 0x1F, }, 0x00,0x00 },
|
||||
/*0d*/{{ '=', '+', NOP, NOP, '=', '+', NOP, NOP, }, 0x33,0x00 },
|
||||
/*0e*/{{ 0x08, 0x08, 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, }, 0x00,0x00 },
|
||||
/*0f*/{{ 0x09, BTAB, NEXT, NEXT, 0x09, BTAB, NOP, NOP, }, 0x77,0x00 },
|
||||
/*10*/{{ 'q', 'Q', 0x11, 0x11, 'q', 'Q', 0x11, 0x11, }, 0x00,0x01 },
|
||||
/*11*/{{ 'w', 'W', 0x17, 0x17, 'w', 'W', 0x17, 0x17, }, 0x00,0x01 },
|
||||
/*12*/{{ 'e', 'E', 0x05, 0x05, 'e', 'E', 0x05, 0x05, }, 0x00,0x01 },
|
||||
/*13*/{{ 'r', 'R', 0x12, 0x12, 'r', 'R', 0x12, 0x12, }, 0x00,0x01 },
|
||||
/*14*/{{ 't', 'T', 0x14, 0x14, 't', 'T', 0x14, 0x14, }, 0x00,0x01 },
|
||||
/*15*/{{ 'y', 'Y', 0x19, 0x19, 'y', 'Y', 0x19, 0x19, }, 0x00,0x01 },
|
||||
/*16*/{{ 'u', 'U', 0x15, 0x15, 'u', 'U', 0x15, 0x15, }, 0x00,0x01 },
|
||||
/*17*/{{ 'i', 'I', 0x09, 0x09, 'i', 'I', 0x09, 0x09, }, 0x00,0x01 },
|
||||
/*18*/{{ 'o', 'O', 0x0F, 0x0F, 'o', 'O', 0x0F, 0x0F, }, 0x00,0x01 },
|
||||
/*19*/{{ 'p', 'P', 0x10, 0x10, 'p', 'P', 0x10, 0x10, }, 0x00,0x01 },
|
||||
/*1a*/{{ '[', '{', 0x1B, 0x1B, '[', '{', 0x1B, 0x1B, }, 0x00,0x00 },
|
||||
/*1b*/{{ ']', '}', 0x1D, 0x1D, ']', '}', 0x1D, 0x1D, }, 0x00,0x00 },
|
||||
/*1c*/{{ 0x0D, 0x0D, 0x0A, 0x0A, 0x0D, 0x0D, 0x0A, 0x0A, }, 0x00,0x00 },
|
||||
/*1d*/{{ LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, }, 0xFF,0x00 },
|
||||
/*1e*/{{ 'a', 'A', 0x01, 0x01, 'a', 'A', 0x01, 0x01, }, 0x00,0x01 },
|
||||
/*1f*/{{ 's', 'S', 0x13, 0x13, 's', 'S', 0x13, 0x13, }, 0x00,0x01 },
|
||||
/*20*/{{ 'd', 'D', 0x04, 0x04, 'd', 'D', 0x04, 0x04, }, 0x00,0x01 },
|
||||
/*21*/{{ 'f', 'F', 0x06, 0x06, 'f', 'F', 0x06, 0x06, }, 0x00,0x01 },
|
||||
/*22*/{{ 'g', 'G', 0x07, 0x07, 'g', 'G', 0x07, 0x07, }, 0x00,0x01 },
|
||||
/*23*/{{ 'h', 'H', 0x08, 0x08, 'h', 'H', 0x08, 0x08, }, 0x00,0x01 },
|
||||
/*24*/{{ 'j', 'J', 0x0A, 0x0A, 'j', 'J', 0x0A, 0x0A, }, 0x00,0x01 },
|
||||
/*25*/{{ 'k', 'K', 0x0B, 0x0B, 'k', 'K', 0x0B, 0x0B, }, 0x00,0x01 },
|
||||
/*26*/{{ 'l', 'L', 0x0C, 0x0C, 'l', 'L', 0x0C, 0x0C, }, 0x00,0x01 },
|
||||
/*27*/{{ ';', ':', NOP, NOP, ';', ':', NOP, NOP, }, 0x33,0x00 },
|
||||
/*28*/{{ '\'', '"', NOP, NOP, DACU, DUML, NOP, NOP, }, 0x3F,0x00 },
|
||||
/*29*/{{ '`', '~', NOP, NOP, DGRA, DTIL, NOP, NOP, }, 0x3F,0x00 },
|
||||
/*2a*/{{ LSH, LSH, LSH, LSH, LSH, LSH, LSH, LSH, }, 0xFF,0x00 },
|
||||
/*2b*/{{ '\\', '|', 0x1C, 0x1C, '\\', '|', 0x1C, 0x1C, }, 0x00,0x00 },
|
||||
/*2c*/{{ 'z', 'Z', 0x1A, 0x1A, 'z', 'Z', 0x1A, 0x1A, }, 0x00,0x01 },
|
||||
/*2d*/{{ 'x', 'X', 0x18, 0x18, 'x', 'X', 0x18, 0x18, }, 0x00,0x01 },
|
||||
/*2e*/{{ 'c', 'C', 0x03, 0x03, 'c', 'C', 0x03, 0x03, }, 0x00,0x01 },
|
||||
/*2f*/{{ 'v', 'V', 0x16, 0x16, 'v', 'V', 0x16, 0x16, }, 0x00,0x01 },
|
||||
/*30*/{{ 'b', 'B', 0x02, 0x02, 'b', 'B', 0x02, 0x02, }, 0x00,0x01 },
|
||||
/*31*/{{ 'n', 'N', 0x0E, 0x0E, 'n', 'N', 0x0E, 0x0E, }, 0x00,0x01 },
|
||||
/*32*/{{ 'm', 'M', 0x0D, 0x0D, 'm', 'M', 0x0D, 0x0D, }, 0x00,0x01 },
|
||||
/*33*/{{ ',', '<', NOP, NOP, DCED, '<', NOP, NOP, }, 0x3B,0x00 },
|
||||
/*34*/{{ '.', '>', NOP, NOP, '.', '>', NOP, NOP, }, 0x33,0x00 },
|
||||
/*35*/{{ '/', '?', NOP, NOP, '/', '?', NOP, NOP, }, 0x33,0x00 },
|
||||
/*36*/{{ RSH, RSH, RSH, RSH, RSH, RSH, RSH, RSH, }, 0xFF,0x00 },
|
||||
/*37*/{{ '*', '*', '*', '*', '*', '*', '*', '*', }, 0x00,0x00 },
|
||||
/*38*/{{ LALT, LALT, LALT, LALT, LALT, LALT, LALT, LALT, }, 0xFF,0x00 },
|
||||
/*39*/{{ ' ', ' ', 0x00, 0x00, ' ', ' ', SUSP, SUSP, }, 0x03,0x00 },
|
||||
/*3a*/{{ CLK, CLK, CLK, CLK, CLK, CLK, CLK, CLK, }, 0xFF,0x00 },
|
||||
/*3b*/{{ F( 1), F(13), F(25), F(37), S( 1), S(11), S( 1), S(11),}, 0xFF,0x00 },
|
||||
/*3c*/{{ F( 2), F(14), F(26), F(38), S( 2), S(12), S( 2), S(12),}, 0xFF,0x00 },
|
||||
/*3d*/{{ F( 3), F(15), F(27), F(39), S( 3), S(13), S( 3), S(13),}, 0xFF,0x00 },
|
||||
/*3e*/{{ F( 4), F(16), F(28), F(40), S( 4), S(14), S( 4), S(14),}, 0xFF,0x00 },
|
||||
/*3f*/{{ F( 5), F(17), F(29), F(41), S( 5), S(15), S( 5), S(15),}, 0xFF,0x00 },
|
||||
/*40*/{{ F( 6), F(18), F(30), F(42), S( 6), S(16), S( 6), S(16),}, 0xFF,0x00 },
|
||||
/*41*/{{ F( 7), F(19), F(31), F(43), S( 7), S( 7), S( 7), S( 7),}, 0xFF,0x00 },
|
||||
/*42*/{{ F( 8), F(20), F(32), F(44), S( 8), S( 8), S( 8), S( 8),}, 0xFF,0x00 },
|
||||
/*43*/{{ F( 9), F(21), F(33), F(45), S( 9), S( 9), S( 9), S( 9),}, 0xFF,0x00 },
|
||||
/*44*/{{ F(10), F(22), F(34), F(46), S(10), S(10), S(10), S(10),}, 0xFF,0x00 },
|
||||
/*45*/{{ NLK, NLK, NLK, NLK, NLK, NLK, NLK, NLK, }, 0xFF,0x00 },
|
||||
/*46*/{{ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, }, 0xFF,0x00 },
|
||||
/*47*/{{ F(49), '7', '7', '7', '7', '7', '7', '7', }, 0x80,0x02 },
|
||||
/*48*/{{ F(50), '8', '8', '8', '8', '8', '8', '8', }, 0x80,0x02 },
|
||||
/*49*/{{ F(51), '9', '9', '9', '9', '9', '9', '9', }, 0x80,0x02 },
|
||||
/*4a*/{{ F(52), '-', '-', '-', '-', '-', '-', '-', }, 0x80,0x02 },
|
||||
/*4b*/{{ F(53), '4', '4', '4', '4', '4', '4', '4', }, 0x80,0x02 },
|
||||
/*4c*/{{ F(54), '5', '5', '5', '5', '5', '5', '5', }, 0x80,0x02 },
|
||||
/*4d*/{{ F(55), '6', '6', '6', '6', '6', '6', '6', }, 0x80,0x02 },
|
||||
/*4e*/{{ F(56), '+', '+', '+', '+', '+', '+', '+', }, 0x80,0x02 },
|
||||
/*4f*/{{ F(57), '1', '1', '1', '1', '1', '1', '1', }, 0x80,0x02 },
|
||||
/*50*/{{ F(58), '2', '2', '2', '2', '2', '2', '2', }, 0x80,0x02 },
|
||||
/*51*/{{ F(59), '3', '3', '3', '3', '3', '3', '3', }, 0x80,0x02 },
|
||||
/*52*/{{ F(60), '0', '0', '0', '0', '0', '0', '0', }, 0x80,0x02 },
|
||||
/*53*/{{ 0x7F, '.', '.', '.', '.', '.', RBT, RBT, }, 0x03,0x02 },
|
||||
/*54*/{{ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, }, 0xFF,0x00 },
|
||||
/*55*/{{ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, }, 0xFF,0x00 },
|
||||
/*56*/{{ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, }, 0xFF,0x00 },
|
||||
/*57*/{{ F(11), F(23), F(35), F(47), S(11), S(11), S(11), S(11),}, 0xFF,0x00 },
|
||||
/*58*/{{ F(12), F(24), F(36), F(48), S(12), S(12), S(12), S(12),}, 0xFF,0x00 },
|
||||
/*59*/{{ 0x0D, 0x0D, 0x0A, 0x0A, 0x0D, 0x0D, 0x0A, 0x0A, }, 0x00,0x00 },
|
||||
/*5a*/{{ RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, }, 0xFF,0x00 },
|
||||
/*5b*/{{ '/', '/', '/', '/', '/', '/', '/', '/', }, 0x00,0x02 },
|
||||
/*5c*/{{ NEXT, NEXT, NOP, NOP, DBG, DBG, DBG, DBG, }, 0xFF,0x00 },
|
||||
/*5d*/{{ RALT, RALT, RALT, RALT, RALT, RALT, RALT, RALT, }, 0xFF,0x00 },
|
||||
/*5e*/{{ F(49), F(49), F(49), F(49), F(49), F(49), F(49), F(49),}, 0xFF,0x00 },
|
||||
/*5f*/{{ F(50), F(50), F(50), F(50), F(50), F(50), F(50), F(50),}, 0xFF,0x00 },
|
||||
/*60*/{{ F(51), F(51), F(51), F(51), F(51), F(51), F(51), F(51),}, 0xFF,0x00 },
|
||||
/*61*/{{ F(53), F(53), F(53), F(53), F(53), F(53), F(53), F(53),}, 0xFF,0x00 },
|
||||
/*62*/{{ F(55), F(55), F(55), F(55), F(55), F(55), F(55), F(55),}, 0xFF,0x00 },
|
||||
/*63*/{{ F(57), F(57), F(57), F(57), F(57), F(57), F(57), F(57),}, 0xFF,0x00 },
|
||||
/*64*/{{ F(58), F(58), F(58), F(58), F(58), F(58), F(58), F(58),}, 0xFF,0x00 },
|
||||
/*65*/{{ F(59), F(59), F(59), F(59), F(59), F(59), F(59), F(59),}, 0xFF,0x00 },
|
||||
/*66*/{{ F(60), F(60), F(60), F(60), F(60), F(60), F(60), F(60),}, 0xFF,0x00 },
|
||||
/*67*/{{ F(61), F(61), F(61), F(61), F(61), F(61), RBT, F(61),}, 0xFF,0x00 },
|
||||
/*68*/{{ SPSC, SPSC, SUSP, SUSP, NOP, NOP, SUSP, SUSP, }, 0xFF,0x00 },
|
||||
/*69*/{{ F(62), F(62), F(62), F(62), F(62), F(62), F(62), F(62),}, 0xFF,0x00 },
|
||||
/*6a*/{{ F(63), F(63), F(63), F(63), F(63), F(63), F(63), F(63),}, 0xFF,0x00 },
|
||||
/*6b*/{{ F(64), F(64), F(64), F(64), F(64), F(64), F(64), F(64),}, 0xFF,0x00 },
|
||||
/*6c*/{{ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, }, 0xFF,0x00 },
|
||||
} };
|
||||
|
||||
static accentmap_t accentmap_default_us_acc = { 11, {
|
||||
/* dgra=0 */
|
||||
{ '`', { { 'a',0xe0 }, { 'A',0xc0 }, { 'e',0xe8 }, { 'E',0xc8 },
|
||||
{ 'i',0xec }, { 'I',0xcc }, { 'o',0xf2 }, { 'O',0xd2 },
|
||||
{ 'u',0xf9 }, { 'U',0xd9 }, }, },
|
||||
/* dacu=1 */
|
||||
{ 0xb4, { { 'a',0xe1 }, { 'A',0xc1 }, { 'e',0xe9 }, { 'E',0xc9 },
|
||||
{ 'i',0xed }, { 'I',0xcd }, { 'o',0xf3 }, { 'O',0xd3 },
|
||||
{ 'u',0xfa }, { 'U',0xda }, { 'y',0xfd }, { 'Y',0xdd }, }, },
|
||||
/* dcir=2 */
|
||||
{ '^', { { 'a',0xe2 }, { 'A',0xc2 }, { 'e',0xea }, { 'E',0xca },
|
||||
{ 'i',0xee }, { 'I',0xce }, { 'o',0xf4 }, { 'O',0xd4 },
|
||||
{ 'u',0xfb }, { 'U',0xdb }, }, },
|
||||
/* dtil=3 */
|
||||
{ '~', { { 'a',0xe3 }, { 'A',0xc3 }, { 'n',0xf1 }, { 'N',0xd1 },
|
||||
{ 'o',0xf5 }, { 'O',0xd5 }, }, },
|
||||
/* dmac=4 */
|
||||
{ 0x00 },
|
||||
/* dbre=5 */
|
||||
{ 0x00 },
|
||||
/* ddot=6 */
|
||||
{ 0x00 },
|
||||
/* duml=7 */
|
||||
{ 0xa8, { { 'a',0xe4 }, { 'A',0xc4 }, { 'e',0xeb }, { 'E',0xcb },
|
||||
{ 'i',0xef }, { 'I',0xcf }, { 'o',0xf6 }, { 'O',0xd6 },
|
||||
{ 'u',0xfc }, { 'U',0xdc }, { 'y',0xff }, }, },
|
||||
/* dsla=8 */
|
||||
{ 0x00 },
|
||||
/* drin=9 */
|
||||
{ 0xb0, { { 'a',0xe5 }, { 'A',0xc5 }, }, },
|
||||
/* dced=10 */
|
||||
{ 0xb8, { { 'c',0xe7 }, { 'C',0xc7 }, }, },
|
||||
/* dapo=11 */
|
||||
{ 0x00 },
|
||||
/* ddac=12 */
|
||||
{ 0x00 },
|
||||
/* dogo=13 */
|
||||
{ 0x00 },
|
||||
/* dcar=14 */
|
||||
{ 0x00 },
|
||||
} };
|
||||
|
||||
/* *INDENT-ON* */ /* clang-format on */
|
||||
@@ -0,0 +1,613 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "../linux/SDL_evdev_kbd.h"
|
||||
|
||||
#ifdef SDL_INPUT_FBSDKBIO
|
||||
|
||||
/* This logic is adapted from drivers/tty/vt/keyboard.c in the Linux kernel source, slightly modified to work with FreeBSD */
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/kbio.h>
|
||||
#include <sys/consio.h>
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include "../../events/SDL_events_c.h"
|
||||
#include "SDL_evdev_kbd_default_keyaccmap.h"
|
||||
|
||||
typedef void(fn_handler_fn)(SDL_EVDEV_keyboard_state *kbd);
|
||||
|
||||
/*
|
||||
* Keyboard State
|
||||
*/
|
||||
|
||||
struct SDL_EVDEV_keyboard_state
|
||||
{
|
||||
int console_fd;
|
||||
int keyboard_fd;
|
||||
unsigned long old_kbd_mode;
|
||||
unsigned short **key_maps;
|
||||
keymap_t *key_map;
|
||||
keyboard_info_t *kbInfo;
|
||||
unsigned char shift_down[4]; /* shift state counters.. */
|
||||
SDL_bool dead_key_next;
|
||||
int npadch; /* -1 or number assembled on pad */
|
||||
accentmap_t *accents;
|
||||
unsigned int diacr;
|
||||
SDL_bool rep; /* flag telling character repeat */
|
||||
unsigned char lockstate;
|
||||
unsigned char ledflagstate;
|
||||
char shift_state;
|
||||
char text[128];
|
||||
unsigned int text_len;
|
||||
};
|
||||
|
||||
static int SDL_EVDEV_kbd_load_keymaps(SDL_EVDEV_keyboard_state *kbd)
|
||||
{
|
||||
return ioctl(kbd->keyboard_fd, GIO_KEYMAP, kbd->key_map) >= 0;
|
||||
}
|
||||
|
||||
static SDL_EVDEV_keyboard_state *kbd_cleanup_state = NULL;
|
||||
static int kbd_cleanup_sigactions_installed = 0;
|
||||
static int kbd_cleanup_atexit_installed = 0;
|
||||
|
||||
static struct sigaction old_sigaction[NSIG];
|
||||
|
||||
static int fatal_signals[] = {
|
||||
/* Handlers for SIGTERM and SIGINT are installed in SDL_InitQuit. */
|
||||
SIGHUP, SIGQUIT, SIGILL, SIGABRT,
|
||||
SIGFPE, SIGSEGV, SIGPIPE, SIGBUS,
|
||||
SIGSYS
|
||||
};
|
||||
|
||||
static void kbd_cleanup(void)
|
||||
{
|
||||
struct mouse_info mData;
|
||||
SDL_EVDEV_keyboard_state *kbd = kbd_cleanup_state;
|
||||
if (!kbd) {
|
||||
return;
|
||||
}
|
||||
kbd_cleanup_state = NULL;
|
||||
SDL_zero(mData);
|
||||
mData.operation = MOUSE_SHOW;
|
||||
ioctl(kbd->keyboard_fd, KDSKBMODE, kbd->old_kbd_mode);
|
||||
if (kbd->keyboard_fd != kbd->console_fd) {
|
||||
close(kbd->keyboard_fd);
|
||||
}
|
||||
ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index));
|
||||
ioctl(kbd->console_fd, CONS_MOUSECTL, &mData);
|
||||
}
|
||||
|
||||
void SDL_EVDEV_kbd_reraise_signal(int sig)
|
||||
{
|
||||
raise(sig);
|
||||
}
|
||||
|
||||
siginfo_t *SDL_EVDEV_kdb_cleanup_siginfo = NULL;
|
||||
void *SDL_EVDEV_kdb_cleanup_ucontext = NULL;
|
||||
|
||||
static void kbd_cleanup_signal_action(int signum, siginfo_t *info, void *ucontext)
|
||||
{
|
||||
struct sigaction *old_action_p = &(old_sigaction[signum]);
|
||||
sigset_t sigset;
|
||||
|
||||
/* Restore original signal handler before going any further. */
|
||||
sigaction(signum, old_action_p, NULL);
|
||||
|
||||
/* Unmask current signal. */
|
||||
sigemptyset(&sigset);
|
||||
sigaddset(&sigset, signum);
|
||||
sigprocmask(SIG_UNBLOCK, &sigset, NULL);
|
||||
|
||||
/* Save original signal info and context for archeologists. */
|
||||
SDL_EVDEV_kdb_cleanup_siginfo = info;
|
||||
SDL_EVDEV_kdb_cleanup_ucontext = ucontext;
|
||||
|
||||
/* Restore keyboard. */
|
||||
kbd_cleanup();
|
||||
|
||||
/* Reraise signal. */
|
||||
SDL_EVDEV_kbd_reraise_signal(signum);
|
||||
}
|
||||
|
||||
static void kbd_unregister_emerg_cleanup()
|
||||
{
|
||||
int tabidx;
|
||||
|
||||
kbd_cleanup_state = NULL;
|
||||
|
||||
if (!kbd_cleanup_sigactions_installed) {
|
||||
return;
|
||||
}
|
||||
kbd_cleanup_sigactions_installed = 0;
|
||||
|
||||
for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) {
|
||||
struct sigaction *old_action_p;
|
||||
struct sigaction cur_action;
|
||||
int signum = fatal_signals[tabidx];
|
||||
old_action_p = &(old_sigaction[signum]);
|
||||
|
||||
/* Examine current signal action */
|
||||
if (sigaction(signum, NULL, &cur_action)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check if action installed and not modified */
|
||||
if (!(cur_action.sa_flags & SA_SIGINFO) || cur_action.sa_sigaction != &kbd_cleanup_signal_action) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Restore original action */
|
||||
sigaction(signum, old_action_p, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void kbd_cleanup_atexit(void)
|
||||
{
|
||||
/* Restore keyboard. */
|
||||
kbd_cleanup();
|
||||
|
||||
/* Try to restore signal handlers in case shared library is being unloaded */
|
||||
kbd_unregister_emerg_cleanup();
|
||||
}
|
||||
|
||||
static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd)
|
||||
{
|
||||
int tabidx;
|
||||
|
||||
if (kbd_cleanup_state) {
|
||||
return;
|
||||
}
|
||||
kbd_cleanup_state = kbd;
|
||||
|
||||
if (!kbd_cleanup_atexit_installed) {
|
||||
/* Since glibc 2.2.3, atexit() (and on_exit(3)) can be used within a shared library to establish
|
||||
* functions that are called when the shared library is unloaded.
|
||||
* -- man atexit(3)
|
||||
*/
|
||||
atexit(kbd_cleanup_atexit);
|
||||
kbd_cleanup_atexit_installed = 1;
|
||||
}
|
||||
|
||||
if (kbd_cleanup_sigactions_installed) {
|
||||
return;
|
||||
}
|
||||
kbd_cleanup_sigactions_installed = 1;
|
||||
|
||||
for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) {
|
||||
struct sigaction *old_action_p;
|
||||
struct sigaction new_action;
|
||||
int signum = fatal_signals[tabidx];
|
||||
old_action_p = &(old_sigaction[signum]);
|
||||
if (sigaction(signum, NULL, old_action_p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Skip SIGHUP and SIGPIPE if handler is already installed
|
||||
* - assume the handler will do the cleanup
|
||||
*/
|
||||
if ((signum == SIGHUP || signum == SIGPIPE) && (old_action_p->sa_handler != SIG_DFL || (void (*)(int))old_action_p->sa_sigaction != SIG_DFL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
new_action = *old_action_p;
|
||||
new_action.sa_flags |= SA_SIGINFO;
|
||||
new_action.sa_sigaction = &kbd_cleanup_signal_action;
|
||||
sigaction(signum, &new_action, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void)
|
||||
{
|
||||
SDL_EVDEV_keyboard_state *kbd;
|
||||
struct mouse_info mData;
|
||||
char flag_state;
|
||||
char *devicePath;
|
||||
|
||||
SDL_zero(mData);
|
||||
mData.operation = MOUSE_HIDE;
|
||||
kbd = (SDL_EVDEV_keyboard_state *)SDL_calloc(1, sizeof(SDL_EVDEV_keyboard_state));
|
||||
if (!kbd) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
kbd->npadch = -1;
|
||||
|
||||
/* This might fail if we're not connected to a tty (e.g. on the Steam Link) */
|
||||
kbd->keyboard_fd = kbd->console_fd = open("/dev/tty", O_RDONLY | O_CLOEXEC);
|
||||
|
||||
kbd->shift_state = 0;
|
||||
|
||||
kbd->accents = SDL_calloc(sizeof(accentmap_t), 1);
|
||||
kbd->key_map = SDL_calloc(sizeof(keymap_t), 1);
|
||||
kbd->kbInfo = SDL_calloc(sizeof(keyboard_info_t), 1);
|
||||
|
||||
ioctl(kbd->console_fd, KDGKBINFO, kbd->kbInfo);
|
||||
ioctl(kbd->console_fd, CONS_MOUSECTL, &mData);
|
||||
|
||||
if (ioctl(kbd->console_fd, KDGKBSTATE, &flag_state) == 0) {
|
||||
kbd->ledflagstate = flag_state;
|
||||
}
|
||||
|
||||
if (ioctl(kbd->console_fd, GIO_DEADKEYMAP, kbd->accents) < 0) {
|
||||
SDL_free(kbd->accents);
|
||||
kbd->accents = &accentmap_default_us_acc;
|
||||
}
|
||||
|
||||
if (ioctl(kbd->console_fd, KDGKBMODE, &kbd->old_kbd_mode) == 0) {
|
||||
/* Set the keyboard in XLATE mode and load the keymaps */
|
||||
ioctl(kbd->console_fd, KDSKBMODE, (unsigned long)(K_XLATE));
|
||||
if (!SDL_EVDEV_kbd_load_keymaps(kbd)) {
|
||||
SDL_free(kbd->key_map);
|
||||
kbd->key_map = &keymap_default_us_acc;
|
||||
}
|
||||
/* Allow inhibiting keyboard mute with env. variable for debugging etc. */
|
||||
if (SDL_getenv("SDL_INPUT_FREEBSD_KEEP_KBD") == NULL) {
|
||||
/* Take keyboard from console and open the actual keyboard device.
|
||||
* Ensures that the keystrokes do not leak through to the console.
|
||||
*/
|
||||
ioctl(kbd->console_fd, CONS_RELKBD, 1ul);
|
||||
SDL_asprintf(&devicePath, "/dev/kbd%d", kbd->kbInfo->kb_index);
|
||||
kbd->keyboard_fd = open(devicePath, O_WRONLY | O_CLOEXEC);
|
||||
if (kbd->keyboard_fd == -1) {
|
||||
// Give keyboard back.
|
||||
ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index));
|
||||
kbd->keyboard_fd = kbd->console_fd;
|
||||
}
|
||||
|
||||
/* Make sure to restore keyboard if application fails to call
|
||||
* SDL_Quit before exit or fatal signal is raised.
|
||||
*/
|
||||
if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, SDL_FALSE)) {
|
||||
kbd_register_emerg_cleanup(kbd);
|
||||
}
|
||||
SDL_free(devicePath);
|
||||
} else
|
||||
kbd->keyboard_fd = kbd->console_fd;
|
||||
}
|
||||
|
||||
return kbd;
|
||||
}
|
||||
|
||||
void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *kbd)
|
||||
{
|
||||
struct mouse_info mData;
|
||||
|
||||
if (!kbd) {
|
||||
return;
|
||||
}
|
||||
SDL_zero(mData);
|
||||
mData.operation = MOUSE_SHOW;
|
||||
ioctl(kbd->console_fd, CONS_MOUSECTL, &mData);
|
||||
|
||||
kbd_unregister_emerg_cleanup();
|
||||
|
||||
if (kbd->keyboard_fd >= 0) {
|
||||
/* Restore the original keyboard mode */
|
||||
ioctl(kbd->keyboard_fd, KDSKBMODE, kbd->old_kbd_mode);
|
||||
|
||||
close(kbd->keyboard_fd);
|
||||
if (kbd->console_fd != kbd->keyboard_fd && kbd->console_fd >= 0) {
|
||||
// Give back keyboard.
|
||||
ioctl(kbd->console_fd, CONS_SETKBD, (unsigned long)(kbd->kbInfo->kb_index));
|
||||
}
|
||||
kbd->console_fd = kbd->keyboard_fd = -1;
|
||||
}
|
||||
|
||||
SDL_free(kbd);
|
||||
}
|
||||
|
||||
void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, SDL_bool muted)
|
||||
{
|
||||
}
|
||||
|
||||
void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data)
|
||||
{
|
||||
}
|
||||
|
||||
void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper Functions.
|
||||
*/
|
||||
static void put_queue(SDL_EVDEV_keyboard_state *kbd, uint c)
|
||||
{
|
||||
/* c is already part of a UTF-8 sequence and safe to add as a character */
|
||||
if (kbd->text_len < (sizeof(kbd->text) - 1)) {
|
||||
kbd->text[kbd->text_len++] = (char)c;
|
||||
}
|
||||
}
|
||||
|
||||
static void put_utf8(SDL_EVDEV_keyboard_state *kbd, uint c)
|
||||
{
|
||||
if (c < 0x80)
|
||||
/* 0******* */
|
||||
put_queue(kbd, c);
|
||||
else if (c < 0x800) {
|
||||
/* 110***** 10****** */
|
||||
put_queue(kbd, 0xc0 | (c >> 6));
|
||||
put_queue(kbd, 0x80 | (c & 0x3f));
|
||||
} else if (c < 0x10000) {
|
||||
if (c >= 0xD800 && c < 0xE000) {
|
||||
return;
|
||||
}
|
||||
if (c == 0xFFFF) {
|
||||
return;
|
||||
}
|
||||
/* 1110**** 10****** 10****** */
|
||||
put_queue(kbd, 0xe0 | (c >> 12));
|
||||
put_queue(kbd, 0x80 | ((c >> 6) & 0x3f));
|
||||
put_queue(kbd, 0x80 | (c & 0x3f));
|
||||
} else if (c < 0x110000) {
|
||||
/* 11110*** 10****** 10****** 10****** */
|
||||
put_queue(kbd, 0xf0 | (c >> 18));
|
||||
put_queue(kbd, 0x80 | ((c >> 12) & 0x3f));
|
||||
put_queue(kbd, 0x80 | ((c >> 6) & 0x3f));
|
||||
put_queue(kbd, 0x80 | (c & 0x3f));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We have a combining character DIACR here, followed by the character CH.
|
||||
* If the combination occurs in the table, return the corresponding value.
|
||||
* Otherwise, if CH is a space or equals DIACR, return DIACR.
|
||||
* Otherwise, conclude that DIACR was not combining after all,
|
||||
* queue it and return CH.
|
||||
*/
|
||||
static unsigned int handle_diacr(SDL_EVDEV_keyboard_state *kbd, unsigned int ch)
|
||||
{
|
||||
unsigned int d = kbd->diacr;
|
||||
unsigned int i, j;
|
||||
|
||||
kbd->diacr = 0;
|
||||
|
||||
for (i = 0; i < kbd->accents->n_accs; i++) {
|
||||
if (kbd->accents->acc[i].accchar == d) {
|
||||
for (j = 0; j < NUM_ACCENTCHARS; ++j) {
|
||||
if (kbd->accents->acc[i].map[j][0] == 0) { /* end of table */
|
||||
break;
|
||||
}
|
||||
if (kbd->accents->acc[i].map[j][0] == ch) {
|
||||
return kbd->accents->acc[i].map[j][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ch == ' ' || ch == d) {
|
||||
put_utf8(kbd, d);
|
||||
return 0;
|
||||
}
|
||||
put_utf8(kbd, d);
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
static int vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
|
||||
{
|
||||
return (kbd->ledflagstate & flag) != 0;
|
||||
}
|
||||
|
||||
static void chg_vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
|
||||
{
|
||||
kbd->ledflagstate ^= flag;
|
||||
ioctl(kbd->keyboard_fd, KDSKBSTATE, (unsigned long)(kbd->ledflagstate));
|
||||
}
|
||||
|
||||
/*
|
||||
* Special function handlers
|
||||
*/
|
||||
|
||||
static void k_self(SDL_EVDEV_keyboard_state *kbd, unsigned int value, char up_flag)
|
||||
{
|
||||
if (up_flag) {
|
||||
return; /* no action, if this is a key release */
|
||||
}
|
||||
|
||||
if (kbd->diacr) {
|
||||
value = handle_diacr(kbd, value);
|
||||
}
|
||||
|
||||
if (kbd->dead_key_next) {
|
||||
kbd->dead_key_next = SDL_FALSE;
|
||||
kbd->diacr = value;
|
||||
return;
|
||||
}
|
||||
put_utf8(kbd, value);
|
||||
}
|
||||
|
||||
static void k_deadunicode(SDL_EVDEV_keyboard_state *kbd, unsigned int value, char up_flag)
|
||||
{
|
||||
if (up_flag)
|
||||
return;
|
||||
|
||||
kbd->diacr = (kbd->diacr ? handle_diacr(kbd, value) : value);
|
||||
}
|
||||
|
||||
static void k_shift(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
|
||||
{
|
||||
int old_state = kbd->shift_state;
|
||||
|
||||
if (kbd->rep)
|
||||
return;
|
||||
|
||||
if (up_flag) {
|
||||
/*
|
||||
* handle the case that two shift or control
|
||||
* keys are depressed simultaneously
|
||||
*/
|
||||
if (kbd->shift_down[value]) {
|
||||
kbd->shift_down[value]--;
|
||||
}
|
||||
} else
|
||||
kbd->shift_down[value]++;
|
||||
|
||||
if (kbd->shift_down[value])
|
||||
kbd->shift_state |= (1 << value);
|
||||
else
|
||||
kbd->shift_state &= ~(1 << value);
|
||||
|
||||
/* kludge */
|
||||
if (up_flag && kbd->shift_state != old_state && kbd->npadch != -1) {
|
||||
put_utf8(kbd, kbd->npadch);
|
||||
kbd->npadch = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *kbd, unsigned int keycode, int down)
|
||||
{
|
||||
keymap_t key_map;
|
||||
struct keyent_t keysym;
|
||||
unsigned int final_key_state;
|
||||
unsigned int map_from_key_sym;
|
||||
|
||||
if (!kbd) {
|
||||
return;
|
||||
}
|
||||
|
||||
key_map = *kbd->key_map;
|
||||
|
||||
kbd->rep = (down == 2);
|
||||
|
||||
if (keycode < NUM_KEYS) {
|
||||
if (keycode >= 89 && keycode <= 95) {
|
||||
/* These constitute unprintable language-related keys, so ignore them. */
|
||||
return;
|
||||
}
|
||||
if (keycode > 95) {
|
||||
keycode -= 7;
|
||||
}
|
||||
if (vc_kbd_led(kbd, ALKED) || (kbd->shift_state & 0x8)) {
|
||||
keycode += ALTGR_OFFSET;
|
||||
}
|
||||
keysym = key_map.key[keycode];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
final_key_state = kbd->shift_state & 0x7;
|
||||
if ((keysym.flgs & FLAG_LOCK_C) && vc_kbd_led(kbd, LED_CAP)) {
|
||||
final_key_state ^= 0x1;
|
||||
}
|
||||
if ((keysym.flgs & FLAG_LOCK_N) && vc_kbd_led(kbd, LED_NUM)) {
|
||||
final_key_state ^= 0x1;
|
||||
}
|
||||
|
||||
map_from_key_sym = keysym.map[final_key_state];
|
||||
if ((keysym.spcl & (0x80 >> final_key_state)) || (map_from_key_sym & SPCLKEY)) {
|
||||
/* Special function.*/
|
||||
if (map_from_key_sym == 0)
|
||||
return; /* Nothing to do. */
|
||||
if (map_from_key_sym & SPCLKEY) {
|
||||
map_from_key_sym &= ~SPCLKEY;
|
||||
}
|
||||
if (map_from_key_sym >= F_ACC && map_from_key_sym <= L_ACC) {
|
||||
/* Accent function.*/
|
||||
unsigned int accent_index = map_from_key_sym - F_ACC;
|
||||
if (kbd->accents->acc[accent_index].accchar != 0) {
|
||||
k_deadunicode(kbd, kbd->accents->acc[accent_index].accchar, !down);
|
||||
}
|
||||
} else {
|
||||
switch (map_from_key_sym) {
|
||||
case ASH: /* alt/meta shift */
|
||||
k_shift(kbd, 3, down == 0);
|
||||
break;
|
||||
case LSHA: /* left shift + alt lock */
|
||||
case RSHA: /* right shift + alt lock */
|
||||
if (down == 0) {
|
||||
chg_vc_kbd_led(kbd, ALKED);
|
||||
}
|
||||
SDL_FALLTHROUGH;
|
||||
case LSH: /* left shift */
|
||||
case RSH: /* right shift */
|
||||
k_shift(kbd, 0, down == 0);
|
||||
break;
|
||||
case LCTRA: /* left ctrl + alt lock */
|
||||
case RCTRA: /* right ctrl + alt lock */
|
||||
if (down == 0) {
|
||||
chg_vc_kbd_led(kbd, ALKED);
|
||||
}
|
||||
SDL_FALLTHROUGH;
|
||||
case LCTR: /* left ctrl */
|
||||
case RCTR: /* right ctrl */
|
||||
k_shift(kbd, 1, down == 0);
|
||||
break;
|
||||
case LALTA: /* left alt + alt lock */
|
||||
case RALTA: /* right alt + alt lock */
|
||||
if (down == 0) {
|
||||
chg_vc_kbd_led(kbd, ALKED);
|
||||
}
|
||||
SDL_FALLTHROUGH;
|
||||
case LALT: /* left alt */
|
||||
case RALT: /* right alt */
|
||||
k_shift(kbd, 2, down == 0);
|
||||
break;
|
||||
case ALK: /* alt lock */
|
||||
if (down == 1) {
|
||||
chg_vc_kbd_led(kbd, ALKED);
|
||||
}
|
||||
break;
|
||||
case CLK: /* caps lock*/
|
||||
if (down == 1) {
|
||||
chg_vc_kbd_led(kbd, CLKED);
|
||||
}
|
||||
break;
|
||||
case NLK: /* num lock */
|
||||
if (down == 1) {
|
||||
chg_vc_kbd_led(kbd, NLKED);
|
||||
}
|
||||
break;
|
||||
case SLK: /* scroll lock */
|
||||
if (down == 1) {
|
||||
chg_vc_kbd_led(kbd, SLKED);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (map_from_key_sym == '\n' || map_from_key_sym == '\r') {
|
||||
if (kbd->diacr) {
|
||||
kbd->diacr = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (map_from_key_sym >= ' ' && map_from_key_sym != 127) {
|
||||
k_self(kbd, map_from_key_sym, !down);
|
||||
}
|
||||
}
|
||||
|
||||
if (kbd->text_len > 0) {
|
||||
kbd->text[kbd->text_len] = '\0';
|
||||
SDL_SendKeyboardText(kbd->text);
|
||||
kbd->text_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* SDL_INPUT_FBSDKBIO */
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
extern "C" {
|
||||
#include "../windows/SDL_windows.h"
|
||||
#include "../../events/SDL_events_c.h"
|
||||
}
|
||||
#include <XGameRuntime.h>
|
||||
#include <xsapi-c/services_c.h>
|
||||
#include <shellapi.h> /* CommandLineToArgvW() */
|
||||
#include <appnotify.h>
|
||||
|
||||
static XTaskQueueHandle GDK_GlobalTaskQueue;
|
||||
|
||||
PAPPSTATE_REGISTRATION hPLM = {};
|
||||
HANDLE plmSuspendComplete = nullptr;
|
||||
|
||||
extern "C" DECLSPEC int
|
||||
SDL_GDKGetTaskQueue(XTaskQueueHandle *outTaskQueue)
|
||||
{
|
||||
/* If this is the first call, first create the global task queue. */
|
||||
if (!GDK_GlobalTaskQueue) {
|
||||
HRESULT hr;
|
||||
|
||||
hr = XTaskQueueCreate(XTaskQueueDispatchMode::ThreadPool,
|
||||
XTaskQueueDispatchMode::Manual,
|
||||
&GDK_GlobalTaskQueue);
|
||||
if (FAILED(hr)) {
|
||||
return SDL_SetError("[GDK] Could not create global task queue");
|
||||
}
|
||||
|
||||
/* The initial call gets the non-duplicated handle so they can clean it up */
|
||||
*outTaskQueue = GDK_GlobalTaskQueue;
|
||||
} else {
|
||||
/* Duplicate the global task queue handle into outTaskQueue */
|
||||
if (FAILED(XTaskQueueDuplicateHandle(GDK_GlobalTaskQueue, outTaskQueue))) {
|
||||
return SDL_SetError("[GDK] Unable to acquire global task queue");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" void
|
||||
GDK_DispatchTaskQueue(void)
|
||||
{
|
||||
/* If there is no global task queue, don't do anything.
|
||||
* This gives the option to opt-out for those who want to handle everything themselves.
|
||||
*/
|
||||
if (GDK_GlobalTaskQueue) {
|
||||
/* Dispatch any callbacks which are ready. */
|
||||
while (XTaskQueueDispatch(GDK_GlobalTaskQueue, XTaskQueuePort::Completion, 0))
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
/* Pop up an out of memory message, returns to Windows */
|
||||
extern "C" static BOOL
|
||||
OutOfMemory(void)
|
||||
{
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "Out of memory - aborting", NULL);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Gets the arguments with GetCommandLine, converts them to argc and argv
|
||||
and calls SDL_main */
|
||||
extern "C" DECLSPEC int
|
||||
SDL_RunApp(int, char**, SDL_main_func mainFunction, void *reserved)
|
||||
{
|
||||
LPWSTR *argvw;
|
||||
char **argv;
|
||||
int i, argc, result;
|
||||
HRESULT hr;
|
||||
XTaskQueueHandle taskQueue;
|
||||
|
||||
argvw = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
if (argvw == NULL) {
|
||||
return OutOfMemory();
|
||||
}
|
||||
|
||||
/* Note that we need to be careful about how we allocate/free memory here.
|
||||
* If the application calls SDL_SetMemoryFunctions(), we can't rely on
|
||||
* SDL_free() to use the same allocator after SDL_main() returns.
|
||||
*/
|
||||
|
||||
/* Parse it into argv and argc */
|
||||
argv = (char **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (argc + 1) * sizeof(*argv));
|
||||
if (argv == NULL) {
|
||||
return OutOfMemory();
|
||||
}
|
||||
for (i = 0; i < argc; ++i) {
|
||||
DWORD len;
|
||||
char *arg = WIN_StringToUTF8W(argvw[i]);
|
||||
if (arg == NULL) {
|
||||
return OutOfMemory();
|
||||
}
|
||||
len = (DWORD)SDL_strlen(arg);
|
||||
argv[i] = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len + 1);
|
||||
if (!argv[i]) {
|
||||
return OutOfMemory();
|
||||
}
|
||||
SDL_memcpy(argv[i], arg, len);
|
||||
SDL_free(arg);
|
||||
}
|
||||
argv[i] = NULL;
|
||||
LocalFree(argvw);
|
||||
|
||||
hr = XGameRuntimeInitialize();
|
||||
|
||||
if (SUCCEEDED(hr) && SDL_GDKGetTaskQueue(&taskQueue) == 0) {
|
||||
Uint32 titleid = 0;
|
||||
char scidBuffer[64];
|
||||
XblInitArgs xblArgs;
|
||||
|
||||
XTaskQueueSetCurrentProcessTaskQueue(taskQueue);
|
||||
|
||||
/* Try to get the title ID and initialize Xbox Live */
|
||||
hr = XGameGetXboxTitleId(&titleid);
|
||||
if (SUCCEEDED(hr)) {
|
||||
SDL_zero(xblArgs);
|
||||
xblArgs.queue = taskQueue;
|
||||
SDL_snprintf(scidBuffer, 64, "00000000-0000-0000-0000-0000%08X", titleid);
|
||||
xblArgs.scid = scidBuffer;
|
||||
hr = XblInitialize(&xblArgs);
|
||||
} else {
|
||||
SDL_SetError("[GDK] Unable to get titleid. Will not call XblInitialize. Check MicrosoftGame.config!");
|
||||
}
|
||||
|
||||
SDL_SetMainReady();
|
||||
|
||||
/* Register suspend/resume handling */
|
||||
plmSuspendComplete = CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE);
|
||||
if (!plmSuspendComplete) {
|
||||
SDL_SetError("[GDK] Unable to create plmSuspendComplete event");
|
||||
return -1;
|
||||
}
|
||||
auto rascn = [](BOOLEAN quiesced, PVOID context) {
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "[GDK] in RegisterAppStateChangeNotification handler");
|
||||
if (quiesced) {
|
||||
ResetEvent(plmSuspendComplete);
|
||||
SDL_SendAppEvent(SDL_EVENT_DID_ENTER_BACKGROUND);
|
||||
|
||||
// To defer suspension, we must wait to exit this callback.
|
||||
// IMPORTANT: The app must call SDL_GDKSuspendComplete() to release this lock.
|
||||
(void)WaitForSingleObject(plmSuspendComplete, INFINITE);
|
||||
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "[GDK] in RegisterAppStateChangeNotification handler: plmSuspendComplete event signaled.");
|
||||
} else {
|
||||
SDL_SendAppEvent(SDL_EVENT_WILL_ENTER_FOREGROUND);
|
||||
}
|
||||
};
|
||||
if (RegisterAppStateChangeNotification(rascn, NULL, &hPLM)) {
|
||||
SDL_SetError("[GDK] Unable to call RegisterAppStateChangeNotification");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Run the application main() code */
|
||||
result = mainFunction(argc, argv);
|
||||
|
||||
/* Unregister suspend/resume handling */
|
||||
UnregisterAppStateChangeNotification(hPLM);
|
||||
CloseHandle(plmSuspendComplete);
|
||||
|
||||
/* !!! FIXME: This follows the docs exactly, but for some reason still leaks handles on exit? */
|
||||
/* Terminate the task queue and dispatch any pending tasks */
|
||||
XTaskQueueTerminate(taskQueue, false, nullptr, nullptr);
|
||||
while (XTaskQueueDispatch(taskQueue, XTaskQueuePort::Completion, 0))
|
||||
;
|
||||
|
||||
XTaskQueueCloseHandle(taskQueue);
|
||||
|
||||
XGameRuntimeUninitialize();
|
||||
} else {
|
||||
#ifdef SDL_PLATFORM_WINGDK
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal Error", "[GDK] Could not initialize - aborting", NULL);
|
||||
#else
|
||||
SDL_assert_always(0 && "[GDK] Could not initialize - aborting");
|
||||
#endif
|
||||
result = -1;
|
||||
}
|
||||
|
||||
/* Free argv, to avoid memory leak */
|
||||
for (i = 0; i < argc; ++i) {
|
||||
HeapFree(GetProcessHeap(), 0, argv[i]);
|
||||
}
|
||||
HeapFree(GetProcessHeap(), 0, argv);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
extern "C" DECLSPEC void
|
||||
SDL_GDKSuspendComplete()
|
||||
{
|
||||
if (plmSuspendComplete) {
|
||||
SetEvent(plmSuspendComplete);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" DECLSPEC int
|
||||
SDL_GDKGetDefaultUser(XUserHandle *outUserHandle)
|
||||
{
|
||||
XAsyncBlock block = { 0 };
|
||||
HRESULT result;
|
||||
|
||||
if (FAILED(result = XUserAddAsync(XUserAddOptions::AddDefaultUserAllowingUI, &block))) {
|
||||
return WIN_SetErrorFromHRESULT("XUserAddAsync", result);
|
||||
}
|
||||
|
||||
do {
|
||||
result = XUserAddResult(&block, outUserHandle);
|
||||
} while (result == E_PENDING);
|
||||
if (FAILED(result)) {
|
||||
return WIN_SetErrorFromHRESULT("XUserAddResult", result);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user