change build system to CMake and use SDL3 for everything
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_audio_c_h_
|
||||
#define SDL_audio_c_h_
|
||||
|
||||
extern void SDL_UpdateAudio(void);
|
||||
|
||||
#endif // SDL_audio_c_h_
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// Get the name of the audio device we use for output
|
||||
|
||||
#if defined(SDL_AUDIO_DRIVER_NETBSD) || defined(SDL_AUDIO_DRIVER_OSS)
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h> // For close()
|
||||
|
||||
#include "SDL_audiodev_c.h"
|
||||
|
||||
#ifndef SDL_PATH_DEV_DSP
|
||||
#if defined(SDL_PLATFORM_NETBSD) || defined(SDL_PLATFORM_OPENBSD)
|
||||
#define SDL_PATH_DEV_DSP "/dev/audio"
|
||||
#else
|
||||
#define SDL_PATH_DEV_DSP "/dev/dsp"
|
||||
#endif
|
||||
#endif
|
||||
#ifndef SDL_PATH_DEV_DSP24
|
||||
#define SDL_PATH_DEV_DSP24 "/dev/sound/dsp"
|
||||
#endif
|
||||
#ifndef SDL_PATH_DEV_AUDIO
|
||||
#define SDL_PATH_DEV_AUDIO "/dev/audio"
|
||||
#endif
|
||||
|
||||
static void test_device(const bool recording, const char *fname, int flags, bool (*test)(int fd))
|
||||
{
|
||||
struct stat sb;
|
||||
const int audio_fd = open(fname, flags | O_CLOEXEC, 0);
|
||||
if (audio_fd >= 0) {
|
||||
if ((fstat(audio_fd, &sb) == 0) && (S_ISCHR(sb.st_mode))) {
|
||||
const bool okay = test(audio_fd);
|
||||
close(audio_fd);
|
||||
if (okay) {
|
||||
static size_t dummyhandle = 0;
|
||||
dummyhandle++;
|
||||
SDL_assert(dummyhandle != 0);
|
||||
|
||||
/* Note that spec is NULL; while we are opening the device
|
||||
* endpoint here, the endpoint does not provide any mix format
|
||||
* information, making this information inaccessible at
|
||||
* enumeration time
|
||||
*/
|
||||
SDL_AddAudioDevice(recording, fname, NULL, (void *)(uintptr_t)dummyhandle);
|
||||
}
|
||||
} else {
|
||||
close(audio_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool test_stub(int fd)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void SDL_EnumUnixAudioDevices_Internal(const bool recording, const bool classic, bool (*test)(int))
|
||||
{
|
||||
const int flags = recording ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT;
|
||||
const char *audiodev;
|
||||
char audiopath[1024];
|
||||
|
||||
if (!test) {
|
||||
test = test_stub;
|
||||
}
|
||||
|
||||
// Figure out what our audio device is
|
||||
audiodev = SDL_getenv("AUDIODEV");
|
||||
if (!audiodev) {
|
||||
if (classic) {
|
||||
audiodev = SDL_PATH_DEV_AUDIO;
|
||||
} else {
|
||||
struct stat sb;
|
||||
|
||||
// Added support for /dev/sound/\* in Linux 2.4
|
||||
if (((stat("/dev/sound", &sb) == 0) && S_ISDIR(sb.st_mode)) && ((stat(SDL_PATH_DEV_DSP24, &sb) == 0) && S_ISCHR(sb.st_mode))) {
|
||||
audiodev = SDL_PATH_DEV_DSP24;
|
||||
} else {
|
||||
audiodev = SDL_PATH_DEV_DSP;
|
||||
}
|
||||
}
|
||||
}
|
||||
test_device(recording, audiodev, flags, test);
|
||||
|
||||
if (SDL_strlen(audiodev) < (sizeof(audiopath) - 3)) {
|
||||
int instance = 0;
|
||||
while (instance <= 64) {
|
||||
(void)SDL_snprintf(audiopath, SDL_arraysize(audiopath),
|
||||
"%s%d", audiodev, instance);
|
||||
instance++;
|
||||
test_device(recording, audiopath, flags, test);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_EnumUnixAudioDevices(const bool classic, bool (*test)(int))
|
||||
{
|
||||
SDL_EnumUnixAudioDevices_Internal(true, classic, test);
|
||||
SDL_EnumUnixAudioDevices_Internal(false, classic, test);
|
||||
}
|
||||
|
||||
#endif // Audio device selection
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_audiodev_c_h_
|
||||
#define SDL_audiodev_c_h_
|
||||
|
||||
#include "SDL_internal.h"
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
// Open the audio device for playback, and don't block if busy
|
||||
//#define USE_BLOCKING_WRITES
|
||||
|
||||
#ifdef USE_BLOCKING_WRITES
|
||||
#define OPEN_FLAGS_OUTPUT O_WRONLY
|
||||
#define OPEN_FLAGS_INPUT O_RDONLY
|
||||
#else
|
||||
#define OPEN_FLAGS_OUTPUT (O_WRONLY | O_NONBLOCK)
|
||||
#define OPEN_FLAGS_INPUT (O_RDONLY | O_NONBLOCK)
|
||||
#endif
|
||||
|
||||
extern void SDL_EnumUnixAudioDevices(const bool classic, bool (*test)(int));
|
||||
|
||||
#endif // SDL_audiodev_c_h_
|
||||
@@ -0,0 +1,652 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_audioqueue.h"
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
typedef struct SDL_MemoryPool SDL_MemoryPool;
|
||||
|
||||
struct SDL_MemoryPool
|
||||
{
|
||||
void *free_blocks;
|
||||
size_t block_size;
|
||||
size_t num_free;
|
||||
size_t max_free;
|
||||
};
|
||||
|
||||
struct SDL_AudioTrack
|
||||
{
|
||||
SDL_AudioSpec spec;
|
||||
int *chmap;
|
||||
bool flushed;
|
||||
SDL_AudioTrack *next;
|
||||
|
||||
void *userdata;
|
||||
SDL_ReleaseAudioBufferCallback callback;
|
||||
|
||||
Uint8 *data;
|
||||
size_t head;
|
||||
size_t tail;
|
||||
size_t capacity;
|
||||
|
||||
int chmap_storage[SDL_MAX_CHANNELMAP_CHANNELS]; // !!! FIXME: this needs to grow if SDL ever supports more channels. But if it grows, we should probably be more clever about allocations.
|
||||
};
|
||||
|
||||
struct SDL_AudioQueue
|
||||
{
|
||||
SDL_AudioTrack *head;
|
||||
SDL_AudioTrack *tail;
|
||||
|
||||
Uint8 *history_buffer;
|
||||
size_t history_length;
|
||||
size_t history_capacity;
|
||||
|
||||
SDL_MemoryPool track_pool;
|
||||
SDL_MemoryPool chunk_pool;
|
||||
};
|
||||
|
||||
// Allocate a new block, avoiding checking for ones already in the pool
|
||||
static void *AllocNewMemoryPoolBlock(const SDL_MemoryPool *pool)
|
||||
{
|
||||
return SDL_malloc(pool->block_size);
|
||||
}
|
||||
|
||||
// Allocate a new block, first checking if there are any in the pool
|
||||
static void *AllocMemoryPoolBlock(SDL_MemoryPool *pool)
|
||||
{
|
||||
if (pool->num_free == 0) {
|
||||
return AllocNewMemoryPoolBlock(pool);
|
||||
}
|
||||
|
||||
void *block = pool->free_blocks;
|
||||
pool->free_blocks = *(void **)block;
|
||||
--pool->num_free;
|
||||
return block;
|
||||
}
|
||||
|
||||
// Free a block, or add it to the pool if there's room
|
||||
static void FreeMemoryPoolBlock(SDL_MemoryPool *pool, void *block)
|
||||
{
|
||||
if (pool->num_free < pool->max_free) {
|
||||
*(void **)block = pool->free_blocks;
|
||||
pool->free_blocks = block;
|
||||
++pool->num_free;
|
||||
} else {
|
||||
SDL_free(block);
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy a pool and all of its blocks
|
||||
static void DestroyMemoryPool(SDL_MemoryPool *pool)
|
||||
{
|
||||
void *block = pool->free_blocks;
|
||||
pool->free_blocks = NULL;
|
||||
pool->num_free = 0;
|
||||
|
||||
while (block) {
|
||||
void *next = *(void **)block;
|
||||
SDL_free(block);
|
||||
block = next;
|
||||
}
|
||||
}
|
||||
|
||||
// Keeping a list of free chunks reduces memory allocations,
|
||||
// But also increases the amount of work to perform when freeing the track.
|
||||
static void InitMemoryPool(SDL_MemoryPool *pool, size_t block_size, size_t max_free)
|
||||
{
|
||||
SDL_zerop(pool);
|
||||
|
||||
SDL_assert(block_size >= sizeof(void *));
|
||||
pool->block_size = block_size;
|
||||
pool->max_free = max_free;
|
||||
}
|
||||
|
||||
// Allocates a number of blocks and adds them to the pool
|
||||
static bool ReserveMemoryPoolBlocks(SDL_MemoryPool *pool, size_t num_blocks)
|
||||
{
|
||||
for (; num_blocks; --num_blocks) {
|
||||
void *block = AllocNewMemoryPoolBlock(pool);
|
||||
|
||||
if (block == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*(void **)block = pool->free_blocks;
|
||||
pool->free_blocks = block;
|
||||
++pool->num_free;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDL_DestroyAudioQueue(SDL_AudioQueue *queue)
|
||||
{
|
||||
SDL_ClearAudioQueue(queue);
|
||||
|
||||
DestroyMemoryPool(&queue->track_pool);
|
||||
DestroyMemoryPool(&queue->chunk_pool);
|
||||
SDL_aligned_free(queue->history_buffer);
|
||||
|
||||
SDL_free(queue);
|
||||
}
|
||||
|
||||
SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size)
|
||||
{
|
||||
SDL_AudioQueue *queue = (SDL_AudioQueue *)SDL_calloc(1, sizeof(*queue));
|
||||
|
||||
if (!queue) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
InitMemoryPool(&queue->track_pool, sizeof(SDL_AudioTrack), 8);
|
||||
InitMemoryPool(&queue->chunk_pool, chunk_size, 4);
|
||||
|
||||
if (!ReserveMemoryPoolBlocks(&queue->track_pool, 2)) {
|
||||
SDL_DestroyAudioQueue(queue);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
static void DestroyAudioTrack(SDL_AudioQueue *queue, SDL_AudioTrack *track)
|
||||
{
|
||||
track->callback(track->userdata, track->data, (int)track->capacity);
|
||||
|
||||
FreeMemoryPoolBlock(&queue->track_pool, track);
|
||||
}
|
||||
|
||||
void SDL_ClearAudioQueue(SDL_AudioQueue *queue)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
queue->head = NULL;
|
||||
queue->tail = NULL;
|
||||
queue->history_length = 0;
|
||||
|
||||
while (track) {
|
||||
SDL_AudioTrack *next = track->next;
|
||||
DestroyAudioTrack(queue, track);
|
||||
track = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void FlushAudioTrack(SDL_AudioTrack *track)
|
||||
{
|
||||
track->flushed = true;
|
||||
}
|
||||
|
||||
void SDL_FlushAudioQueue(SDL_AudioQueue *queue)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->tail;
|
||||
|
||||
if (track) {
|
||||
FlushAudioTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
for (;;) {
|
||||
bool flushed = track->flushed;
|
||||
|
||||
SDL_AudioTrack *next = track->next;
|
||||
DestroyAudioTrack(queue, track);
|
||||
track = next;
|
||||
|
||||
if (flushed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
queue->head = track;
|
||||
queue->history_length = 0;
|
||||
|
||||
if (!track) {
|
||||
queue->tail = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_AudioTrack *SDL_CreateAudioTrack(
|
||||
SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap,
|
||||
Uint8 *data, size_t len, size_t capacity,
|
||||
SDL_ReleaseAudioBufferCallback callback, void *userdata)
|
||||
{
|
||||
SDL_AudioTrack *track = (SDL_AudioTrack *)AllocMemoryPoolBlock(&queue->track_pool);
|
||||
|
||||
if (!track) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_zerop(track);
|
||||
|
||||
if (chmap) {
|
||||
SDL_assert(SDL_arraysize(track->chmap_storage) >= spec->channels);
|
||||
SDL_memcpy(track->chmap_storage, chmap, sizeof (*chmap) * spec->channels);
|
||||
track->chmap = track->chmap_storage;
|
||||
}
|
||||
|
||||
SDL_copyp(&track->spec, spec);
|
||||
|
||||
track->userdata = userdata;
|
||||
track->callback = callback;
|
||||
track->data = data;
|
||||
track->head = 0;
|
||||
track->tail = len;
|
||||
track->capacity = capacity;
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
static void SDLCALL FreeChunkedAudioBuffer(void *userdata, const void *buf, int len)
|
||||
{
|
||||
SDL_AudioQueue *queue = (SDL_AudioQueue *)userdata;
|
||||
|
||||
FreeMemoryPoolBlock(&queue->chunk_pool, (void *)buf);
|
||||
}
|
||||
|
||||
static SDL_AudioTrack *CreateChunkedAudioTrack(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap)
|
||||
{
|
||||
Uint8 *chunk = (Uint8 *)AllocMemoryPoolBlock(&queue->chunk_pool);
|
||||
|
||||
if (!chunk) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t capacity = queue->chunk_pool.block_size;
|
||||
capacity -= capacity % SDL_AUDIO_FRAMESIZE(*spec);
|
||||
|
||||
SDL_AudioTrack *track = SDL_CreateAudioTrack(queue, spec, chmap, chunk, 0, capacity, FreeChunkedAudioBuffer, queue);
|
||||
|
||||
if (!track) {
|
||||
FreeMemoryPoolBlock(&queue->chunk_pool, chunk);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track)
|
||||
{
|
||||
SDL_AudioTrack *tail = queue->tail;
|
||||
|
||||
if (tail) {
|
||||
// If the spec has changed, make sure to flush the previous track
|
||||
if (!SDL_AudioSpecsEqual(&tail->spec, &track->spec, tail->chmap, track->chmap)) {
|
||||
FlushAudioTrack(tail);
|
||||
}
|
||||
|
||||
tail->next = track;
|
||||
} else {
|
||||
queue->head = track;
|
||||
}
|
||||
|
||||
queue->tail = track;
|
||||
}
|
||||
|
||||
static size_t WriteToAudioTrack(SDL_AudioTrack *track, const Uint8 *data, size_t len)
|
||||
{
|
||||
if (track->flushed || track->tail >= track->capacity) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
len = SDL_min(len, track->capacity - track->tail);
|
||||
SDL_memcpy(&track->data[track->tail], data, len);
|
||||
track->tail += len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
bool SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap, const Uint8 *data, size_t len)
|
||||
{
|
||||
if (len == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SDL_AudioTrack *track = queue->tail;
|
||||
|
||||
if (track) {
|
||||
if (!SDL_AudioSpecsEqual(&track->spec, spec, track->chmap, chmap)) {
|
||||
FlushAudioTrack(track);
|
||||
}
|
||||
} else {
|
||||
SDL_assert(!queue->head);
|
||||
track = CreateChunkedAudioTrack(queue, spec, chmap);
|
||||
|
||||
if (!track) {
|
||||
return false;
|
||||
}
|
||||
|
||||
queue->head = track;
|
||||
queue->tail = track;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
const size_t written = WriteToAudioTrack(track, data, len);
|
||||
data += written;
|
||||
len -= written;
|
||||
|
||||
if (len == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_AudioTrack *new_track = CreateChunkedAudioTrack(queue, spec, chmap);
|
||||
|
||||
if (!new_track) {
|
||||
return false;
|
||||
}
|
||||
|
||||
track->next = new_track;
|
||||
queue->tail = new_track;
|
||||
track = new_track;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue)
|
||||
{
|
||||
return queue->head;
|
||||
}
|
||||
|
||||
size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed)
|
||||
{
|
||||
SDL_AudioTrack *iter = (SDL_AudioTrack *)(*inout_iter);
|
||||
SDL_assert(iter != NULL);
|
||||
|
||||
SDL_copyp(out_spec, &iter->spec);
|
||||
*out_chmap = iter->chmap;
|
||||
|
||||
bool flushed = false;
|
||||
size_t queued_bytes = 0;
|
||||
|
||||
while (iter) {
|
||||
SDL_AudioTrack *track = iter;
|
||||
iter = iter->next;
|
||||
|
||||
size_t avail = track->tail - track->head;
|
||||
|
||||
if (avail >= SDL_SIZE_MAX - queued_bytes) {
|
||||
queued_bytes = SDL_SIZE_MAX;
|
||||
flushed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
queued_bytes += avail;
|
||||
flushed = track->flushed;
|
||||
|
||||
if (flushed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*inout_iter = iter;
|
||||
*out_flushed = flushed;
|
||||
|
||||
return queued_bytes;
|
||||
}
|
||||
|
||||
static const Uint8 *PeekIntoAudioQueuePast(SDL_AudioQueue *queue, Uint8 *data, size_t len)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
if (track->head >= len) {
|
||||
return &track->data[track->head - len];
|
||||
}
|
||||
|
||||
size_t past = len - track->head;
|
||||
|
||||
if (past > queue->history_length) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_memcpy(data, &queue->history_buffer[queue->history_length - past], past);
|
||||
SDL_memcpy(&data[past], track->data, track->head);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void UpdateAudioQueueHistory(SDL_AudioQueue *queue,
|
||||
const Uint8 *data, size_t len)
|
||||
{
|
||||
Uint8 *history_buffer = queue->history_buffer;
|
||||
size_t history_bytes = queue->history_length;
|
||||
|
||||
if (len >= history_bytes) {
|
||||
SDL_memcpy(history_buffer, &data[len - history_bytes], history_bytes);
|
||||
} else {
|
||||
size_t preserve = history_bytes - len;
|
||||
SDL_memmove(history_buffer, &history_buffer[len], preserve);
|
||||
SDL_memcpy(&history_buffer[preserve], data, len);
|
||||
}
|
||||
}
|
||||
|
||||
static const Uint8 *ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
if (track->tail - track->head >= len) {
|
||||
const Uint8 *ptr = &track->data[track->head];
|
||||
track->head += len;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
|
||||
for (;;) {
|
||||
size_t avail = SDL_min(len - total, track->tail - track->head);
|
||||
SDL_memcpy(&data[total], &track->data[track->head], avail);
|
||||
track->head += avail;
|
||||
total += avail;
|
||||
|
||||
if (total == len) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (track->flushed) {
|
||||
SDL_SetError("Reading past end of flushed track");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_AudioTrack *next = track->next;
|
||||
|
||||
if (!next) {
|
||||
SDL_SetError("Reading past end of incomplete track");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
UpdateAudioQueueHistory(queue, track->data, track->tail);
|
||||
|
||||
queue->head = next;
|
||||
DestroyAudioTrack(queue, track);
|
||||
track = next;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static const Uint8 *PeekIntoAudioQueueFuture(SDL_AudioQueue *queue, Uint8 *data, size_t len)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
if (track->tail - track->head >= len) {
|
||||
return &track->data[track->head];
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
|
||||
for (;;) {
|
||||
size_t avail = SDL_min(len - total, track->tail - track->head);
|
||||
SDL_memcpy(&data[total], &track->data[track->head], avail);
|
||||
total += avail;
|
||||
|
||||
if (total == len) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (track->flushed) {
|
||||
// If we have run out of data, fill the rest with silence.
|
||||
SDL_memset(&data[total], SDL_GetSilenceValueForFormat(track->spec.format), len - total);
|
||||
break;
|
||||
}
|
||||
|
||||
track = track->next;
|
||||
|
||||
if (!track) {
|
||||
SDL_SetError("Peeking past end of incomplete track");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
|
||||
Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
|
||||
int past_frames, int present_frames, int future_frames,
|
||||
Uint8 *scratch, float gain)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
if (!track) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_AudioFormat src_format = track->spec.format;
|
||||
int src_channels = track->spec.channels;
|
||||
const int *src_map = track->chmap;
|
||||
|
||||
size_t src_frame_size = SDL_AUDIO_BYTESIZE(src_format) * src_channels;
|
||||
size_t dst_frame_size = SDL_AUDIO_BYTESIZE(dst_format) * dst_channels;
|
||||
|
||||
size_t src_past_bytes = past_frames * src_frame_size;
|
||||
size_t src_present_bytes = present_frames * src_frame_size;
|
||||
size_t src_future_bytes = future_frames * src_frame_size;
|
||||
|
||||
size_t dst_past_bytes = past_frames * dst_frame_size;
|
||||
size_t dst_present_bytes = present_frames * dst_frame_size;
|
||||
size_t dst_future_bytes = future_frames * dst_frame_size;
|
||||
|
||||
const bool convert = (src_format != dst_format) || (src_channels != dst_channels) || (gain != 1.0f);
|
||||
|
||||
if (convert && !dst) {
|
||||
// The user didn't ask for the data to be copied, but we need to convert it, so store it in the scratch buffer
|
||||
dst = scratch;
|
||||
}
|
||||
|
||||
// Can we get all of the data straight from this track?
|
||||
if ((track->head >= src_past_bytes) && ((track->tail - track->head) >= (src_present_bytes + src_future_bytes))) {
|
||||
const Uint8 *ptr = &track->data[track->head - src_past_bytes];
|
||||
track->head += src_present_bytes;
|
||||
|
||||
// Do we still need to copy/convert the data?
|
||||
if (dst) {
|
||||
ConvertAudio(past_frames + present_frames + future_frames, ptr,
|
||||
src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
|
||||
ptr = dst;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
if (!dst) {
|
||||
// The user didn't ask for the data to be copied, but we need to, so store it in the scratch buffer
|
||||
dst = scratch;
|
||||
} else if (!convert) {
|
||||
// We are only copying, not converting, so copy straight into the dst buffer
|
||||
scratch = dst;
|
||||
}
|
||||
|
||||
Uint8 *ptr = dst;
|
||||
|
||||
if (src_past_bytes) {
|
||||
ConvertAudio(past_frames, PeekIntoAudioQueuePast(queue, scratch, src_past_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
|
||||
dst += dst_past_bytes;
|
||||
scratch += dst_past_bytes;
|
||||
}
|
||||
|
||||
if (src_present_bytes) {
|
||||
ConvertAudio(present_frames, ReadFromAudioQueue(queue, scratch, src_present_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
|
||||
dst += dst_present_bytes;
|
||||
scratch += dst_present_bytes;
|
||||
}
|
||||
|
||||
if (src_future_bytes) {
|
||||
ConvertAudio(future_frames, PeekIntoAudioQueueFuture(queue, scratch, src_future_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain);
|
||||
dst += dst_future_bytes;
|
||||
scratch += dst_future_bytes;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue)
|
||||
{
|
||||
size_t total = 0;
|
||||
void *iter = SDL_BeginAudioQueueIter(queue);
|
||||
|
||||
while (iter) {
|
||||
SDL_AudioSpec src_spec;
|
||||
int *src_chmap;
|
||||
bool flushed;
|
||||
|
||||
size_t avail = SDL_NextAudioQueueIter(queue, &iter, &src_spec, &src_chmap, &flushed);
|
||||
|
||||
if (avail >= SDL_SIZE_MAX - total) {
|
||||
total = SDL_SIZE_MAX;
|
||||
break;
|
||||
}
|
||||
|
||||
total += avail;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
bool SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames)
|
||||
{
|
||||
SDL_AudioTrack *track = queue->head;
|
||||
|
||||
if (!track) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t length = num_frames * SDL_AUDIO_FRAMESIZE(track->spec);
|
||||
Uint8 *history_buffer = queue->history_buffer;
|
||||
|
||||
if (queue->history_capacity < length) {
|
||||
history_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), length);
|
||||
if (!history_buffer) {
|
||||
return false;
|
||||
}
|
||||
SDL_aligned_free(queue->history_buffer);
|
||||
queue->history_buffer = history_buffer;
|
||||
queue->history_capacity = length;
|
||||
}
|
||||
|
||||
queue->history_length = length;
|
||||
SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(track->spec.format), length);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_audioqueue_h_
|
||||
#define SDL_audioqueue_h_
|
||||
|
||||
// Internal functions used by SDL_AudioStream for queueing audio.
|
||||
|
||||
typedef void (SDLCALL *SDL_ReleaseAudioBufferCallback)(void *userdata, const void *buffer, int buflen);
|
||||
|
||||
typedef struct SDL_AudioQueue SDL_AudioQueue;
|
||||
typedef struct SDL_AudioTrack SDL_AudioTrack;
|
||||
|
||||
// Create a new audio queue
|
||||
extern SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size);
|
||||
|
||||
// Destroy an audio queue
|
||||
extern void SDL_DestroyAudioQueue(SDL_AudioQueue *queue);
|
||||
|
||||
// Completely clear the queue
|
||||
extern void SDL_ClearAudioQueue(SDL_AudioQueue *queue);
|
||||
|
||||
// Mark the last track as flushed
|
||||
extern void SDL_FlushAudioQueue(SDL_AudioQueue *queue);
|
||||
|
||||
// Pop the current head track
|
||||
// REQUIRES: The head track must exist, and must have been flushed
|
||||
extern void SDL_PopAudioQueueHead(SDL_AudioQueue *queue);
|
||||
|
||||
// Write data to the end of queue
|
||||
// REQUIRES: If the spec has changed, the last track must have been flushed
|
||||
extern bool SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap, const Uint8 *data, size_t len);
|
||||
|
||||
// Create a track where the input data is owned by the caller
|
||||
extern SDL_AudioTrack *SDL_CreateAudioTrack(SDL_AudioQueue *queue,
|
||||
const SDL_AudioSpec *spec, const int *chmap, Uint8 *data, size_t len, size_t capacity,
|
||||
SDL_ReleaseAudioBufferCallback callback, void *userdata);
|
||||
|
||||
// Add a track to the end of the queue
|
||||
// REQUIRES: `track != NULL`
|
||||
extern void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track);
|
||||
|
||||
// Iterate over the tracks in the queue
|
||||
extern void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue);
|
||||
|
||||
// Query and update the track iterator
|
||||
// REQUIRES: `*inout_iter != NULL` (a valid iterator)
|
||||
extern size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed);
|
||||
|
||||
extern const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue,
|
||||
Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
|
||||
int past_frames, int present_frames, int future_frames,
|
||||
Uint8 *scratch, float gain);
|
||||
|
||||
// Get the total number of bytes currently queued
|
||||
extern size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue);
|
||||
|
||||
extern bool SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames);
|
||||
|
||||
#endif // SDL_audioqueue_h_
|
||||
@@ -0,0 +1,695 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
#include "SDL_audioresample.h"
|
||||
|
||||
// SDL's resampler uses a "bandlimited interpolation" algorithm:
|
||||
// https://ccrma.stanford.edu/~jos/resample/
|
||||
|
||||
// TODO: Support changing this at runtime?
|
||||
#if defined(SDL_SSE_INTRINSICS) || defined(SDL_NEON_INTRINSICS)
|
||||
// In <current year>, SSE is basically mandatory anyway
|
||||
// We want RESAMPLER_SAMPLES_PER_FRAME to be a multiple of 4, to make SIMD easier
|
||||
#define RESAMPLER_ZERO_CROSSINGS 6
|
||||
#else
|
||||
#define RESAMPLER_ZERO_CROSSINGS 5
|
||||
#endif
|
||||
|
||||
#define RESAMPLER_SAMPLES_PER_FRAME (RESAMPLER_ZERO_CROSSINGS * 2)
|
||||
|
||||
// For a given srcpos, `srcpos + frame` are sampled, where `-RESAMPLER_ZERO_CROSSINGS < frame <= RESAMPLER_ZERO_CROSSINGS`.
|
||||
// Note, when upsampling, it is also possible to start sampling from `srcpos = -1`.
|
||||
#define RESAMPLER_MAX_PADDING_FRAMES (RESAMPLER_ZERO_CROSSINGS + 1)
|
||||
|
||||
// More bits gives more precision, at the cost of a larger table.
|
||||
#define RESAMPLER_BITS_PER_ZERO_CROSSING 3
|
||||
#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << RESAMPLER_BITS_PER_ZERO_CROSSING)
|
||||
#define RESAMPLER_FILTER_INTERP_BITS (32 - RESAMPLER_BITS_PER_ZERO_CROSSING)
|
||||
#define RESAMPLER_FILTER_INTERP_RANGE (1 << RESAMPLER_FILTER_INTERP_BITS)
|
||||
|
||||
// ResampleFrame is just a vector/matrix/matrix multiplication.
|
||||
// It performs cubic interpolation of the filter, then multiplies that with the input.
|
||||
// dst = [1, frac, frac^2, frac^3] * filter * src
|
||||
|
||||
// Cubic Polynomial
|
||||
typedef union Cubic
|
||||
{
|
||||
float v[4];
|
||||
|
||||
#ifdef SDL_SSE_INTRINSICS
|
||||
// Aligned loads can be used directly as memory operands for mul/add
|
||||
__m128 v128;
|
||||
#endif
|
||||
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
float32x4_t v128;
|
||||
#endif
|
||||
|
||||
} Cubic;
|
||||
|
||||
static void ResampleFrame_Generic(const float *src, float *dst, const Cubic *filter, float frac, int chans)
|
||||
{
|
||||
const float frac2 = frac * frac;
|
||||
const float frac3 = frac * frac2;
|
||||
|
||||
int i, chan;
|
||||
float scales[RESAMPLER_SAMPLES_PER_FRAME];
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i, ++filter) {
|
||||
scales[i] = filter->v[0] + (filter->v[1] * frac) + (filter->v[2] * frac2) + (filter->v[3] * frac3);
|
||||
}
|
||||
|
||||
for (chan = 0; chan < chans; ++chan) {
|
||||
float out = 0.0f;
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i) {
|
||||
out += src[i * chans + chan] * scales[i];
|
||||
}
|
||||
|
||||
dst[chan] = out;
|
||||
}
|
||||
}
|
||||
|
||||
static void ResampleFrame_Mono(const float *src, float *dst, const Cubic *filter, float frac, int chans)
|
||||
{
|
||||
const float frac2 = frac * frac;
|
||||
const float frac3 = frac * frac2;
|
||||
|
||||
int i;
|
||||
float out = 0.0f;
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i, ++filter) {
|
||||
// Interpolate between the nearest two filters
|
||||
const float scale = filter->v[0] + (filter->v[1] * frac) + (filter->v[2] * frac2) + (filter->v[3] * frac3);
|
||||
|
||||
out += src[i] * scale;
|
||||
}
|
||||
|
||||
dst[0] = out;
|
||||
}
|
||||
|
||||
static void ResampleFrame_Stereo(const float *src, float *dst, const Cubic *filter, float frac, int chans)
|
||||
{
|
||||
const float frac2 = frac * frac;
|
||||
const float frac3 = frac * frac2;
|
||||
|
||||
int i;
|
||||
float out0 = 0.0f;
|
||||
float out1 = 0.0f;
|
||||
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; ++i, ++filter) {
|
||||
// Interpolate between the nearest two filters
|
||||
const float scale = filter->v[0] + (filter->v[1] * frac) + (filter->v[2] * frac2) + (filter->v[3] * frac3);
|
||||
|
||||
out0 += src[i * 2 + 0] * scale;
|
||||
out1 += src[i * 2 + 1] * scale;
|
||||
}
|
||||
|
||||
dst[0] = out0;
|
||||
dst[1] = out1;
|
||||
}
|
||||
|
||||
#ifdef SDL_SSE_INTRINSICS
|
||||
#define sdl_madd_ps(a, b, c) _mm_add_ps(a, _mm_mul_ps(b, c)) // Not-so-fused multiply-add
|
||||
|
||||
static void SDL_TARGETING("sse") ResampleFrame_Generic_SSE(const float *src, float *dst, const Cubic *filter, float frac, int chans)
|
||||
{
|
||||
#if RESAMPLER_SAMPLES_PER_FRAME != 12
|
||||
#error Invalid samples per frame
|
||||
#endif
|
||||
|
||||
__m128 f0, f1, f2;
|
||||
|
||||
{
|
||||
const __m128 frac1 = _mm_set1_ps(frac);
|
||||
const __m128 frac2 = _mm_mul_ps(frac1, frac1);
|
||||
const __m128 frac3 = _mm_mul_ps(frac1, frac2);
|
||||
|
||||
// Transposed in SetupAudioResampler
|
||||
// Explicitly use _mm_load_ps to workaround ICE in GCC 4.9.4 accessing Cubic.v128
|
||||
#define X(out) \
|
||||
out = _mm_load_ps(filter[0].v); \
|
||||
out = sdl_madd_ps(out, frac1, _mm_load_ps(filter[1].v)); \
|
||||
out = sdl_madd_ps(out, frac2, _mm_load_ps(filter[2].v)); \
|
||||
out = sdl_madd_ps(out, frac3, _mm_load_ps(filter[3].v)); \
|
||||
filter += 4
|
||||
|
||||
X(f0);
|
||||
X(f1);
|
||||
X(f2);
|
||||
|
||||
#undef X
|
||||
}
|
||||
|
||||
if (chans == 2) {
|
||||
// Duplicate each of the filter elements and multiply by the input
|
||||
// Use two accumulators to improve throughput
|
||||
__m128 out0 = _mm_mul_ps(_mm_loadu_ps(src + 0), _mm_unpacklo_ps(f0, f0));
|
||||
__m128 out1 = _mm_mul_ps(_mm_loadu_ps(src + 4), _mm_unpackhi_ps(f0, f0));
|
||||
out0 = sdl_madd_ps(out0, _mm_loadu_ps(src + 8), _mm_unpacklo_ps(f1, f1));
|
||||
out1 = sdl_madd_ps(out1, _mm_loadu_ps(src + 12), _mm_unpackhi_ps(f1, f1));
|
||||
out0 = sdl_madd_ps(out0, _mm_loadu_ps(src + 16), _mm_unpacklo_ps(f2, f2));
|
||||
out1 = sdl_madd_ps(out1, _mm_loadu_ps(src + 20), _mm_unpackhi_ps(f2, f2));
|
||||
|
||||
// Add the accumulators together
|
||||
__m128 out = _mm_add_ps(out0, out1);
|
||||
|
||||
// Add the lower and upper pairs together
|
||||
out = _mm_add_ps(out, _mm_movehl_ps(out, out));
|
||||
|
||||
// Store the result
|
||||
_mm_storel_pi((__m64 *)dst, out);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chans == 1) {
|
||||
// Multiply the filter by the input
|
||||
__m128 out = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
|
||||
out = sdl_madd_ps(out, f1, _mm_loadu_ps(src + 4));
|
||||
out = sdl_madd_ps(out, f2, _mm_loadu_ps(src + 8));
|
||||
|
||||
// Horizontal sum
|
||||
__m128 shuf = _mm_shuffle_ps(out, out, _MM_SHUFFLE(2, 3, 0, 1));
|
||||
out = _mm_add_ps(out, shuf);
|
||||
out = _mm_add_ss(out, _mm_movehl_ps(shuf, out));
|
||||
|
||||
_mm_store_ss(dst, out);
|
||||
return;
|
||||
}
|
||||
|
||||
int chan = 0;
|
||||
|
||||
// Process 4 channels at once
|
||||
for (; chan + 4 <= chans; chan += 4) {
|
||||
const float *in = &src[chan];
|
||||
__m128 out0 = _mm_setzero_ps();
|
||||
__m128 out1 = _mm_setzero_ps();
|
||||
|
||||
#define X(a, b, out) \
|
||||
out = sdl_madd_ps(out, _mm_loadu_ps(in), _mm_shuffle_ps(a, a, _MM_SHUFFLE(b, b, b, b))); \
|
||||
in += chans
|
||||
|
||||
#define Y(a) \
|
||||
X(a, 0, out0); \
|
||||
X(a, 1, out1); \
|
||||
X(a, 2, out0); \
|
||||
X(a, 3, out1)
|
||||
|
||||
Y(f0);
|
||||
Y(f1);
|
||||
Y(f2);
|
||||
|
||||
#undef X
|
||||
#undef Y
|
||||
|
||||
// Add the accumulators together
|
||||
__m128 out = _mm_add_ps(out0, out1);
|
||||
|
||||
_mm_storeu_ps(&dst[chan], out);
|
||||
}
|
||||
|
||||
// Process the remaining channels one at a time.
|
||||
// Channel counts 1,2,4,8 are already handled above, leaving 3,5,6,7 to deal with (looping 3,1,2,3 times).
|
||||
// Without vgatherdps (AVX2), this gets quite messy.
|
||||
for (; chan < chans; ++chan) {
|
||||
const float *in = &src[chan];
|
||||
__m128 v0, v1, v2;
|
||||
|
||||
#define X(x) \
|
||||
x = _mm_unpacklo_ps(_mm_load_ss(in), _mm_load_ss(in + chans)); \
|
||||
in += chans + chans; \
|
||||
x = _mm_movelh_ps(x, _mm_unpacklo_ps(_mm_load_ss(in), _mm_load_ss(in + chans))); \
|
||||
in += chans + chans
|
||||
|
||||
X(v0);
|
||||
X(v1);
|
||||
X(v2);
|
||||
|
||||
#undef X
|
||||
|
||||
__m128 out = _mm_mul_ps(f0, v0);
|
||||
out = sdl_madd_ps(out, f1, v1);
|
||||
out = sdl_madd_ps(out, f2, v2);
|
||||
|
||||
// Horizontal sum
|
||||
__m128 shuf = _mm_shuffle_ps(out, out, _MM_SHUFFLE(2, 3, 0, 1));
|
||||
out = _mm_add_ps(out, shuf);
|
||||
out = _mm_add_ss(out, _mm_movehl_ps(shuf, out));
|
||||
|
||||
_mm_store_ss(&dst[chan], out);
|
||||
}
|
||||
}
|
||||
|
||||
#undef sdl_madd_ps
|
||||
#endif
|
||||
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
static void ResampleFrame_Generic_NEON(const float *src, float *dst, const Cubic *filter, float frac, int chans)
|
||||
{
|
||||
#if RESAMPLER_SAMPLES_PER_FRAME != 12
|
||||
#error Invalid samples per frame
|
||||
#endif
|
||||
|
||||
float32x4_t f0, f1, f2;
|
||||
|
||||
{
|
||||
const float32x4_t frac1 = vdupq_n_f32(frac);
|
||||
const float32x4_t frac2 = vmulq_f32(frac1, frac1);
|
||||
const float32x4_t frac3 = vmulq_f32(frac1, frac2);
|
||||
|
||||
// Transposed in SetupAudioResampler
|
||||
#define X(out) \
|
||||
out = vmlaq_f32(vmlaq_f32(vmlaq_f32(filter[0].v128, filter[1].v128, frac1), filter[2].v128, frac2), filter[3].v128, frac3); \
|
||||
filter += 4
|
||||
|
||||
X(f0);
|
||||
X(f1);
|
||||
X(f2);
|
||||
|
||||
#undef X
|
||||
}
|
||||
|
||||
if (chans == 2) {
|
||||
float32x4x2_t g0 = vzipq_f32(f0, f0);
|
||||
float32x4x2_t g1 = vzipq_f32(f1, f1);
|
||||
float32x4x2_t g2 = vzipq_f32(f2, f2);
|
||||
|
||||
// Duplicate each of the filter elements and multiply by the input
|
||||
// Use two accumulators to improve throughput
|
||||
float32x4_t out0 = vmulq_f32(vld1q_f32(src + 0), g0.val[0]);
|
||||
float32x4_t out1 = vmulq_f32(vld1q_f32(src + 4), g0.val[1]);
|
||||
out0 = vmlaq_f32(out0, vld1q_f32(src + 8), g1.val[0]);
|
||||
out1 = vmlaq_f32(out1, vld1q_f32(src + 12), g1.val[1]);
|
||||
out0 = vmlaq_f32(out0, vld1q_f32(src + 16), g2.val[0]);
|
||||
out1 = vmlaq_f32(out1, vld1q_f32(src + 20), g2.val[1]);
|
||||
|
||||
// Add the accumulators together
|
||||
out0 = vaddq_f32(out0, out1);
|
||||
|
||||
// Add the lower and upper pairs together
|
||||
float32x2_t out = vadd_f32(vget_low_f32(out0), vget_high_f32(out0));
|
||||
|
||||
// Store the result
|
||||
vst1_f32(dst, out);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chans == 1) {
|
||||
// Multiply the filter by the input
|
||||
float32x4_t out = vmulq_f32(f0, vld1q_f32(src + 0));
|
||||
out = vmlaq_f32(out, f1, vld1q_f32(src + 4));
|
||||
out = vmlaq_f32(out, f2, vld1q_f32(src + 8));
|
||||
|
||||
// Horizontal sum
|
||||
float32x2_t sum = vadd_f32(vget_low_f32(out), vget_high_f32(out));
|
||||
sum = vpadd_f32(sum, sum);
|
||||
|
||||
vst1_lane_f32(dst, sum, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int chan = 0;
|
||||
|
||||
// Process 4 channels at once
|
||||
for (; chan + 4 <= chans; chan += 4) {
|
||||
const float *in = &src[chan];
|
||||
float32x4_t out0 = vdupq_n_f32(0);
|
||||
float32x4_t out1 = vdupq_n_f32(0);
|
||||
|
||||
#define X(a, b, out) \
|
||||
out = vmlaq_f32(out, vld1q_f32(in), vdupq_lane_f32(a, b)); \
|
||||
in += chans
|
||||
|
||||
#define Y(a) \
|
||||
X(vget_low_f32(a), 0, out0); \
|
||||
X(vget_low_f32(a), 1, out1); \
|
||||
X(vget_high_f32(a), 0, out0); \
|
||||
X(vget_high_f32(a), 1, out1)
|
||||
|
||||
Y(f0);
|
||||
Y(f1);
|
||||
Y(f2);
|
||||
|
||||
#undef X
|
||||
#undef Y
|
||||
|
||||
// Add the accumulators together
|
||||
float32x4_t out = vaddq_f32(out0, out1);
|
||||
|
||||
vst1q_f32(&dst[chan], out);
|
||||
}
|
||||
|
||||
// Process the remaining channels one at a time.
|
||||
// Channel counts 1,2,4,8 are already handled above, leaving 3,5,6,7 to deal with (looping 3,1,2,3 times).
|
||||
for (; chan < chans; ++chan) {
|
||||
const float *in = &src[chan];
|
||||
float32x4_t v0, v1, v2;
|
||||
|
||||
#define X(x) \
|
||||
x = vld1q_dup_f32(in); \
|
||||
in += chans; \
|
||||
x = vld1q_lane_f32(in, x, 1); \
|
||||
in += chans; \
|
||||
x = vld1q_lane_f32(in, x, 2); \
|
||||
in += chans; \
|
||||
x = vld1q_lane_f32(in, x, 3); \
|
||||
in += chans
|
||||
|
||||
X(v0);
|
||||
X(v1);
|
||||
X(v2);
|
||||
|
||||
#undef X
|
||||
|
||||
float32x4_t out = vmulq_f32(f0, v0);
|
||||
out = vmlaq_f32(out, f1, v1);
|
||||
out = vmlaq_f32(out, f2, v2);
|
||||
|
||||
// Horizontal sum
|
||||
float32x2_t sum = vadd_f32(vget_low_f32(out), vget_high_f32(out));
|
||||
sum = vpadd_f32(sum, sum);
|
||||
|
||||
vst1_lane_f32(&dst[chan], sum, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Calculate the cubic equation which passes through all four points.
|
||||
// https://en.wikipedia.org/wiki/Ordinary_least_squares
|
||||
// https://en.wikipedia.org/wiki/Polynomial_regression
|
||||
static void CubicLeastSquares(Cubic *coeffs, float y0, float y1, float y2, float y3)
|
||||
{
|
||||
// Least squares matrix for xs = [0, 1/3, 2/3, 1]
|
||||
// [ 1.0 0.0 0.0 0.0 ]
|
||||
// [ -5.5 9.0 -4.5 1.0 ]
|
||||
// [ 9.0 -22.5 18.0 -4.5 ]
|
||||
// [ -4.5 13.5 -13.5 4.5 ]
|
||||
|
||||
coeffs->v[0] = y0;
|
||||
coeffs->v[1] = -5.5f * y0 + 9.0f * y1 - 4.5f * y2 + y3;
|
||||
coeffs->v[2] = 9.0f * y0 - 22.5f * y1 + 18.0f * y2 - 4.5f * y3;
|
||||
coeffs->v[3] = -4.5f * y0 + 13.5f * y1 - 13.5f * y2 + 4.5f * y3;
|
||||
}
|
||||
|
||||
// Zeroth-order modified Bessel function of the first kind
|
||||
// https://mathworld.wolfram.com/ModifiedBesselFunctionoftheFirstKind.html
|
||||
static float BesselI0(float x)
|
||||
{
|
||||
float sum = 0.0f;
|
||||
float i = 1.0f;
|
||||
float t = 1.0f;
|
||||
x *= x * 0.25f;
|
||||
|
||||
while (t >= sum * SDL_FLT_EPSILON) {
|
||||
sum += t;
|
||||
t *= x / (i * i);
|
||||
++i;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Pre-calculate 180 degrees of sin(pi * x) / pi
|
||||
// The speedup from this isn't huge, but it also avoids precision issues.
|
||||
// If sinf isn't available, SDL_sinf just calls SDL_sin.
|
||||
// Know what SDL_sin(SDL_PI_F) equals? Not quite zero.
|
||||
static void SincTable(float *table, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
table[i] = SDL_sinf(i * (SDL_PI_F / len)) / SDL_PI_F;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate Sinc(x/y), using a lookup table
|
||||
static float Sinc(float *table, int x, int y)
|
||||
{
|
||||
float s = table[x % y];
|
||||
s = ((x / y) & 1) ? -s : s;
|
||||
return (s * y) / x;
|
||||
}
|
||||
|
||||
static Cubic ResamplerFilter[RESAMPLER_SAMPLES_PER_ZERO_CROSSING][RESAMPLER_SAMPLES_PER_FRAME];
|
||||
|
||||
static void GenerateResamplerFilter(void)
|
||||
{
|
||||
enum
|
||||
{
|
||||
// Generate samples at 3x the target resolution, so that we have samples at [0, 1/3, 2/3, 1] of each position
|
||||
TABLE_SAMPLES_PER_ZERO_CROSSING = RESAMPLER_SAMPLES_PER_ZERO_CROSSING * 3,
|
||||
TABLE_SIZE = RESAMPLER_ZERO_CROSSINGS * TABLE_SAMPLES_PER_ZERO_CROSSING,
|
||||
};
|
||||
|
||||
// if dB > 50, beta=(0.1102 * (dB - 8.7)), according to Matlab.
|
||||
const float dB = 80.0f;
|
||||
const float beta = 0.1102f * (dB - 8.7f);
|
||||
const float bessel_beta = BesselI0(beta);
|
||||
const float lensqr = TABLE_SIZE * TABLE_SIZE;
|
||||
|
||||
int i, j;
|
||||
|
||||
float sinc[TABLE_SAMPLES_PER_ZERO_CROSSING];
|
||||
SincTable(sinc, TABLE_SAMPLES_PER_ZERO_CROSSING);
|
||||
|
||||
// Generate one wing of the filter
|
||||
// https://en.wikipedia.org/wiki/Kaiser_window
|
||||
// https://en.wikipedia.org/wiki/Whittaker%E2%80%93Shannon_interpolation_formula
|
||||
float filter[TABLE_SIZE + 1];
|
||||
filter[0] = 1.0f;
|
||||
|
||||
for (i = 1; i <= TABLE_SIZE; ++i) {
|
||||
float b = BesselI0(beta * SDL_sqrtf((lensqr - (i * i)) / lensqr)) / bessel_beta;
|
||||
float s = Sinc(sinc, i, TABLE_SAMPLES_PER_ZERO_CROSSING);
|
||||
filter[i] = b * s;
|
||||
}
|
||||
|
||||
// Generate the coefficients for each point
|
||||
// When interpolating, the fraction represents how far we are between input samples,
|
||||
// so we need to align the filter by "moving" it to the right.
|
||||
//
|
||||
// For the left wing, this means interpolating "forwards" (away from the center)
|
||||
// For the right wing, this means interpolating "backwards" (towards the center)
|
||||
//
|
||||
// The center of the filter is at the end of the left wing (RESAMPLER_ZERO_CROSSINGS - 1)
|
||||
// The left wing is the filter, but reversed
|
||||
// The right wing is the filter, but offset by 1
|
||||
//
|
||||
// Since the right wing is offset by 1, this just means we interpolate backwards
|
||||
// between the same points, instead of forwards
|
||||
// interp(p[n], p[n+1], t) = interp(p[n+1], p[n+1-1], 1 - t) = interp(p[n+1], p[n], 1 - t)
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
|
||||
for (j = 0; j < RESAMPLER_ZERO_CROSSINGS; ++j) {
|
||||
const float *ys = &filter[((j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING) + i) * 3];
|
||||
|
||||
Cubic *fwd = &ResamplerFilter[i][RESAMPLER_ZERO_CROSSINGS - j - 1];
|
||||
Cubic *rev = &ResamplerFilter[RESAMPLER_SAMPLES_PER_ZERO_CROSSING - i - 1][RESAMPLER_ZERO_CROSSINGS + j];
|
||||
|
||||
// Calculate the cubic equation of the 4 points
|
||||
CubicLeastSquares(fwd, ys[0], ys[1], ys[2], ys[3]);
|
||||
CubicLeastSquares(rev, ys[3], ys[2], ys[1], ys[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef void (*ResampleFrameFunc)(const float *src, float *dst, const Cubic *filter, float frac, int chans);
|
||||
static ResampleFrameFunc ResampleFrame[8];
|
||||
|
||||
// Transpose 4x4 floats
|
||||
static void Transpose4x4(Cubic *data)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
Cubic temp[4] = { data[0], data[1], data[2], data[3] };
|
||||
|
||||
for (i = 0; i < 4; ++i) {
|
||||
for (j = 0; j < 4; ++j) {
|
||||
data[i].v[j] = temp[j].v[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void SetupAudioResampler(void)
|
||||
{
|
||||
int i, j;
|
||||
bool transpose = false;
|
||||
|
||||
GenerateResamplerFilter();
|
||||
|
||||
#ifdef SDL_SSE_INTRINSICS
|
||||
if (SDL_HasSSE()) {
|
||||
for (i = 0; i < 8; ++i) {
|
||||
ResampleFrame[i] = ResampleFrame_Generic_SSE;
|
||||
}
|
||||
transpose = true;
|
||||
} else
|
||||
#endif
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
if (SDL_HasNEON()) {
|
||||
for (i = 0; i < 8; ++i) {
|
||||
ResampleFrame[i] = ResampleFrame_Generic_NEON;
|
||||
}
|
||||
transpose = true;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
for (i = 0; i < 8; ++i) {
|
||||
ResampleFrame[i] = ResampleFrame_Generic;
|
||||
}
|
||||
|
||||
ResampleFrame[0] = ResampleFrame_Mono;
|
||||
ResampleFrame[1] = ResampleFrame_Stereo;
|
||||
}
|
||||
|
||||
if (transpose) {
|
||||
// Transpose each set of 4 coefficients, to reduce work when resampling
|
||||
for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
|
||||
for (j = 0; j + 4 <= RESAMPLER_SAMPLES_PER_FRAME; j += 4) {
|
||||
Transpose4x4(&ResamplerFilter[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_SetupAudioResampler(void)
|
||||
{
|
||||
static SDL_InitState init;
|
||||
|
||||
if (SDL_ShouldInit(&init)) {
|
||||
SetupAudioResampler();
|
||||
SDL_SetInitialized(&init, true);
|
||||
}
|
||||
}
|
||||
|
||||
Sint64 SDL_GetResampleRate(int src_rate, int dst_rate)
|
||||
{
|
||||
SDL_assert(src_rate > 0);
|
||||
SDL_assert(dst_rate > 0);
|
||||
|
||||
Sint64 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 bool ResamplerAdd(Sint64 a, Sint64 b, Sint64 *ret)
|
||||
{
|
||||
if ((b > 0) && (a > SDL_MAX_SINT64 - b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*ret = a + b;
|
||||
return true;
|
||||
}
|
||||
|
||||
SDL_FORCE_INLINE bool ResamplerMul(Sint64 a, Sint64 b, Sint64 *ret)
|
||||
{
|
||||
if ((b > 0) && (a > SDL_MAX_SINT64 / b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*ret = a * b;
|
||||
return true;
|
||||
}
|
||||
|
||||
Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset)
|
||||
{
|
||||
// Calculate the index of the last input frame, then add 1.
|
||||
// ((((output_frames - 1) * resample_rate) + resample_offset) >> 32) + 1
|
||||
|
||||
Sint64 output_offset;
|
||||
if (!ResamplerMul(output_frames, resample_rate, &output_offset) ||
|
||||
!ResamplerAdd(output_offset, -resample_rate + resample_offset + 0x100000000, &output_offset)) {
|
||||
output_offset = SDL_MAX_SINT64;
|
||||
}
|
||||
|
||||
Sint64 input_frames = (Sint64)(Sint32)(output_offset >> 32);
|
||||
input_frames = SDL_max(input_frames, 0);
|
||||
|
||||
return input_frames;
|
||||
}
|
||||
|
||||
Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset)
|
||||
{
|
||||
Sint64 resample_offset = *inout_resample_offset;
|
||||
|
||||
// input_offset = (input_frames << 32) - resample_offset;
|
||||
Sint64 input_offset;
|
||||
if (!ResamplerMul(input_frames, 0x100000000, &input_offset) ||
|
||||
!ResamplerAdd(input_offset, -resample_offset, &input_offset)) {
|
||||
input_offset = SDL_MAX_SINT64;
|
||||
}
|
||||
|
||||
// output_frames = div_ceil(input_offset, resample_rate)
|
||||
Sint64 output_frames = (input_offset > 0) ? ((input_offset + resample_rate * 3 / 4) / resample_rate) : 0;
|
||||
|
||||
*inout_resample_offset = (output_frames * resample_rate) - input_offset;
|
||||
|
||||
return output_frames;
|
||||
}
|
||||
|
||||
void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
|
||||
Sint64 resample_rate, Sint64 *inout_resample_offset)
|
||||
{
|
||||
int i;
|
||||
Sint64 srcpos = *inout_resample_offset;
|
||||
ResampleFrameFunc resample_frame = ResampleFrame[chans - 1];
|
||||
|
||||
SDL_assert(resample_rate > 0);
|
||||
|
||||
src -= (RESAMPLER_ZERO_CROSSINGS - 1) * chans;
|
||||
|
||||
for (i = 0; i < outframes; ++i) {
|
||||
int srcindex = (int)(Sint32)(srcpos >> 32);
|
||||
Uint32 srcfraction = (Uint32)(srcpos & 0xFFFFFFFF);
|
||||
srcpos += resample_rate;
|
||||
|
||||
SDL_assert(srcindex >= -1 && srcindex < inframes);
|
||||
|
||||
const Cubic *filter = ResamplerFilter[srcfraction >> RESAMPLER_FILTER_INTERP_BITS];
|
||||
const float frac = (float)(srcfraction & (RESAMPLER_FILTER_INTERP_RANGE - 1)) * (1.0f / RESAMPLER_FILTER_INTERP_RANGE);
|
||||
|
||||
const float *frame = &src[srcindex * chans];
|
||||
resample_frame(frame, dst, filter, frac, chans);
|
||||
|
||||
dst += chans;
|
||||
}
|
||||
|
||||
*inout_resample_offset = srcpos - ((Sint64)inframes << 32);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_audioresample_h_
|
||||
#define SDL_audioresample_h_
|
||||
|
||||
// Internal functions used by SDL_AudioStream for resampling audio.
|
||||
// The resampler uses 32:32 fixed-point arithmetic to track its position.
|
||||
|
||||
Sint64 SDL_GetResampleRate(int src_rate, int dst_rate);
|
||||
|
||||
int SDL_GetResamplerHistoryFrames(void);
|
||||
int SDL_GetResamplerPaddingFrames(Sint64 resample_rate);
|
||||
|
||||
Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset);
|
||||
Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset);
|
||||
|
||||
// Resample some audio.
|
||||
// REQUIRES: `inframes >= SDL_GetResamplerInputFrames(outframes)`
|
||||
// REQUIRES: At least `SDL_GetResamplerPaddingFrames(...)` extra frames to the left of src, and right of src+inframes
|
||||
void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
|
||||
Sint64 resample_rate, Sint64 *inout_resample_offset);
|
||||
|
||||
#endif // SDL_audioresample_h_
|
||||
@@ -0,0 +1,925 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
#define DIVBY2147483648 0.0000000004656612873077392578125f // 0x1p-31f
|
||||
|
||||
// start fallback scalar converters
|
||||
|
||||
// This code requires that floats are in the IEEE-754 binary32 format
|
||||
SDL_COMPILE_TIME_ASSERT(float_bits, sizeof(float) == sizeof(Uint32));
|
||||
|
||||
union float_bits {
|
||||
Uint32 u32;
|
||||
float f32;
|
||||
};
|
||||
|
||||
static void SDL_Convert_S8_to_F32_Scalar(float *dst, const Sint8 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S8", "F32");
|
||||
|
||||
for (i = num_samples - 1; i >= 0; --i) {
|
||||
/* 1) Construct a float in the range [65536.0, 65538.0)
|
||||
* 2) Shift the float range to [-1.0, 1.0) */
|
||||
union float_bits x;
|
||||
x.u32 = (Uint8)src[i] ^ 0x47800080u;
|
||||
dst[i] = x.f32 - 65537.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_U8_to_F32_Scalar(float *dst, const Uint8 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("U8", "F32");
|
||||
|
||||
for (i = num_samples - 1; i >= 0; --i) {
|
||||
/* 1) Construct a float in the range [65536.0, 65538.0)
|
||||
* 2) Shift the float range to [-1.0, 1.0) */
|
||||
union float_bits x;
|
||||
x.u32 = src[i] ^ 0x47800000u;
|
||||
dst[i] = x.f32 - 65537.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_S16_to_F32_Scalar(float *dst, const Sint16 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S16", "F32");
|
||||
|
||||
for (i = num_samples - 1; i >= 0; --i) {
|
||||
/* 1) Construct a float in the range [256.0, 258.0)
|
||||
* 2) Shift the float range to [-1.0, 1.0) */
|
||||
union float_bits x;
|
||||
x.u32 = (Uint16)src[i] ^ 0x43808000u;
|
||||
dst[i] = x.f32 - 257.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_S32_to_F32_Scalar(float *dst, const Sint32 *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S32", "F32");
|
||||
|
||||
for (i = num_samples - 1; i >= 0; --i) {
|
||||
dst[i] = (float)src[i] * DIVBY2147483648;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a bit-mask based on the sign-bit. Should optimize to a single arithmetic-shift-right
|
||||
#define SIGNMASK(x) (Uint32)(0u - ((Uint32)(x) >> 31))
|
||||
|
||||
static void SDL_Convert_F32_to_S8_Scalar(Sint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S8");
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
|
||||
* 2) Shift the integer range from [0x47BFFF80, 0x47C00080] to [-128, 128]
|
||||
* 3) Clamp the value to [-128, 127] */
|
||||
union float_bits x;
|
||||
x.f32 = src[i] + 98304.0f;
|
||||
|
||||
Uint32 y = x.u32 - 0x47C00000u;
|
||||
Uint32 z = 0x7Fu - (y ^ SIGNMASK(y));
|
||||
y = y ^ (z & SIGNMASK(z));
|
||||
|
||||
dst[i] = (Sint8)(y & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_U8_Scalar(Uint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "U8");
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
|
||||
* 2) Shift the integer range from [0x47BFFF80, 0x47C00080] to [-128, 128]
|
||||
* 3) Clamp the value to [-128, 127]
|
||||
* 4) Shift the integer range from [-128, 127] to [0, 255] */
|
||||
union float_bits x;
|
||||
x.f32 = src[i] + 98304.0f;
|
||||
|
||||
Uint32 y = x.u32 - 0x47C00000u;
|
||||
Uint32 z = 0x7Fu - (y ^ SIGNMASK(y));
|
||||
y = (y ^ 0x80u) ^ (z & SIGNMASK(z));
|
||||
|
||||
dst[i] = (Uint8)(y & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S16_Scalar(Sint16 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S16");
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [383.0, 385.0]
|
||||
* 2) Shift the integer range from [0x43BF8000, 0x43C08000] to [-32768, 32768]
|
||||
* 3) Clamp values outside the [-32768, 32767] range */
|
||||
union float_bits x;
|
||||
x.f32 = src[i] + 384.0f;
|
||||
|
||||
Uint32 y = x.u32 - 0x43C00000u;
|
||||
Uint32 z = 0x7FFFu - (y ^ SIGNMASK(y));
|
||||
y = y ^ (z & SIGNMASK(z));
|
||||
|
||||
dst[i] = (Sint16)(y & 0xFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S32_Scalar(Sint32 *dst, const float *src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S32");
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [-2147483648.0, 2147483648.0]
|
||||
* 2) Set values outside the [-2147483648.0, 2147483647.0] range to -2147483648.0
|
||||
* 3) Convert the float to an integer, and fixup values outside the valid range */
|
||||
union float_bits x;
|
||||
x.f32 = src[i];
|
||||
|
||||
Uint32 y = x.u32 + 0x0F800000u;
|
||||
Uint32 z = y - 0xCF000000u;
|
||||
z &= SIGNMASK(y ^ z);
|
||||
x.u32 = y - z;
|
||||
|
||||
dst[i] = (Sint32)x.f32 ^ (Sint32)SIGNMASK(z);
|
||||
}
|
||||
}
|
||||
|
||||
#undef SIGNMASK
|
||||
|
||||
static void SDL_Convert_Swap16_Scalar(Uint16* dst, const Uint16* src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
dst[i] = SDL_Swap16(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_Convert_Swap32_Scalar(Uint32* dst, const Uint32* src, int num_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_samples; ++i) {
|
||||
dst[i] = SDL_Swap32(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// end fallback scalar converters
|
||||
|
||||
// Convert forwards, when sizeof(*src) >= sizeof(*dst)
|
||||
#define CONVERT_16_FWD(CVT1, CVT16) \
|
||||
int i = 0; \
|
||||
if (num_samples >= 16) { \
|
||||
while ((uintptr_t)(&dst[i]) & 15) { CVT1 ++i; } \
|
||||
while ((i + 16) <= num_samples) { CVT16 i += 16; } \
|
||||
} \
|
||||
while (i < num_samples) { CVT1 ++i; }
|
||||
|
||||
// Convert backwards, when sizeof(*src) <= sizeof(*dst)
|
||||
#define CONVERT_16_REV(CVT1, CVT16) \
|
||||
int i = num_samples; \
|
||||
if (i >= 16) { \
|
||||
while ((uintptr_t)(&dst[i]) & 15) { --i; CVT1 } \
|
||||
while (i >= 16) { i -= 16; CVT16 } \
|
||||
} \
|
||||
while (i > 0) { --i; CVT1 }
|
||||
|
||||
#ifdef SDL_SSE2_INTRINSICS
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_S8_to_F32_SSE2(float *dst, const Sint8 *src, int num_samples)
|
||||
{
|
||||
/* 1) Flip the sign bit to convert from S8 to U8 format
|
||||
* 2) Construct a float in the range [65536.0, 65538.0)
|
||||
* 3) Shift the float range to [-1.0, 1.0)
|
||||
* dst[i] = i2f((src[i] ^ 0x80) | 0x47800000) - 65537.0 */
|
||||
const __m128i zero = _mm_setzero_si128();
|
||||
const __m128i flipper = _mm_set1_epi8(-0x80);
|
||||
const __m128i caster = _mm_set1_epi16(0x4780 /* 0x47800000 = f2i(65536.0) */);
|
||||
const __m128 offset = _mm_set1_ps(-65537.0);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using SSE2)");
|
||||
|
||||
CONVERT_16_REV({
|
||||
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint8)src[i] ^ 0x47800080u)), offset));
|
||||
}, {
|
||||
const __m128i bytes = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i]), flipper);
|
||||
|
||||
const __m128i shorts0 = _mm_unpacklo_epi8(bytes, zero);
|
||||
const __m128i shorts1 = _mm_unpackhi_epi8(bytes, zero);
|
||||
|
||||
const __m128 floats0 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts0, caster)), offset);
|
||||
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts0, caster)), offset);
|
||||
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
|
||||
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
|
||||
|
||||
_mm_store_ps(&dst[i], floats0);
|
||||
_mm_store_ps(&dst[i + 4], floats1);
|
||||
_mm_store_ps(&dst[i + 8], floats2);
|
||||
_mm_store_ps(&dst[i + 12], floats3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_U8_to_F32_SSE2(float *dst, const Uint8 *src, int num_samples)
|
||||
{
|
||||
/* 1) Construct a float in the range [65536.0, 65538.0)
|
||||
* 2) Shift the float range to [-1.0, 1.0)
|
||||
* dst[i] = i2f(src[i] | 0x47800000) - 65537.0 */
|
||||
const __m128i zero = _mm_setzero_si128();
|
||||
const __m128i caster = _mm_set1_epi16(0x4780 /* 0x47800000 = f2i(65536.0) */);
|
||||
const __m128 offset = _mm_set1_ps(-65537.0);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using SSE2)");
|
||||
|
||||
CONVERT_16_REV({
|
||||
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint8)src[i] ^ 0x47800000u)), offset));
|
||||
}, {
|
||||
const __m128i bytes = _mm_loadu_si128((const __m128i *)&src[i]);
|
||||
|
||||
const __m128i shorts0 = _mm_unpacklo_epi8(bytes, zero);
|
||||
const __m128i shorts1 = _mm_unpackhi_epi8(bytes, zero);
|
||||
|
||||
const __m128 floats0 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts0, caster)), offset);
|
||||
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts0, caster)), offset);
|
||||
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
|
||||
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
|
||||
|
||||
_mm_store_ps(&dst[i], floats0);
|
||||
_mm_store_ps(&dst[i + 4], floats1);
|
||||
_mm_store_ps(&dst[i + 8], floats2);
|
||||
_mm_store_ps(&dst[i + 12], floats3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_S16_to_F32_SSE2(float *dst, const Sint16 *src, int num_samples)
|
||||
{
|
||||
/* 1) Flip the sign bit to convert from S16 to U16 format
|
||||
* 2) Construct a float in the range [256.0, 258.0)
|
||||
* 3) Shift the float range to [-1.0, 1.0)
|
||||
* dst[i] = i2f((src[i] ^ 0x8000) | 0x43800000) - 257.0 */
|
||||
const __m128i flipper = _mm_set1_epi16(-0x8000);
|
||||
const __m128i caster = _mm_set1_epi16(0x4380 /* 0x43800000 = f2i(256.0) */);
|
||||
const __m128 offset = _mm_set1_ps(-257.0f);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using SSE2)");
|
||||
|
||||
CONVERT_16_REV({
|
||||
_mm_store_ss(&dst[i], _mm_add_ss(_mm_castsi128_ps(_mm_cvtsi32_si128((Uint16)src[i] ^ 0x43808000u)), offset));
|
||||
}, {
|
||||
const __m128i shorts0 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i]), flipper);
|
||||
const __m128i shorts1 = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&src[i + 8]), flipper);
|
||||
|
||||
const __m128 floats0 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts0, caster)), offset);
|
||||
const __m128 floats1 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts0, caster)), offset);
|
||||
const __m128 floats2 = _mm_add_ps(_mm_castsi128_ps(_mm_unpacklo_epi16(shorts1, caster)), offset);
|
||||
const __m128 floats3 = _mm_add_ps(_mm_castsi128_ps(_mm_unpackhi_epi16(shorts1, caster)), offset);
|
||||
|
||||
_mm_store_ps(&dst[i], floats0);
|
||||
_mm_store_ps(&dst[i + 4], floats1);
|
||||
_mm_store_ps(&dst[i + 8], floats2);
|
||||
_mm_store_ps(&dst[i + 12], floats3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_S32_to_F32_SSE2(float *dst, const Sint32 *src, int num_samples)
|
||||
{
|
||||
// dst[i] = f32(src[i]) / f32(0x80000000)
|
||||
const __m128 scaler = _mm_set1_ps(DIVBY2147483648);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using SSE2)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
_mm_store_ss(&dst[i], _mm_mul_ss(_mm_cvt_si2ss(_mm_setzero_ps(), src[i]), scaler));
|
||||
}, {
|
||||
const __m128i ints0 = _mm_loadu_si128((const __m128i *)&src[i]);
|
||||
const __m128i ints1 = _mm_loadu_si128((const __m128i *)&src[i + 4]);
|
||||
const __m128i ints2 = _mm_loadu_si128((const __m128i *)&src[i + 8]);
|
||||
const __m128i ints3 = _mm_loadu_si128((const __m128i *)&src[i + 12]);
|
||||
|
||||
const __m128 floats0 = _mm_mul_ps(_mm_cvtepi32_ps(ints0), scaler);
|
||||
const __m128 floats1 = _mm_mul_ps(_mm_cvtepi32_ps(ints1), scaler);
|
||||
const __m128 floats2 = _mm_mul_ps(_mm_cvtepi32_ps(ints2), scaler);
|
||||
const __m128 floats3 = _mm_mul_ps(_mm_cvtepi32_ps(ints3), scaler);
|
||||
|
||||
_mm_store_ps(&dst[i], floats0);
|
||||
_mm_store_ps(&dst[i + 4], floats1);
|
||||
_mm_store_ps(&dst[i + 8], floats2);
|
||||
_mm_store_ps(&dst[i + 12], floats3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S8_SSE2(Sint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [98303.0, 98305.0]
|
||||
* 2) Extract the lowest 16 bits and clamp to [-128, 127]
|
||||
* Overflow is correctly handled for inputs between roughly [-255.0, 255.0]
|
||||
* dst[i] = clamp(i16(f2i(src[i] + 98304.0) & 0xFFFF), -128, 127) */
|
||||
const __m128 offset = _mm_set1_ps(98304.0f);
|
||||
const __m128i mask = _mm_set1_epi16(0xFF);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using SSE2)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
const __m128i ints = _mm_castps_si128(_mm_add_ss(_mm_load_ss(&src[i]), offset));
|
||||
dst[i] = (Sint8)(_mm_cvtsi128_si32(_mm_packs_epi16(ints, ints)) & 0xFF);
|
||||
}, {
|
||||
const __m128 floats0 = _mm_loadu_ps(&src[i]);
|
||||
const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
|
||||
const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
|
||||
const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
|
||||
|
||||
const __m128i ints0 = _mm_castps_si128(_mm_add_ps(floats0, offset));
|
||||
const __m128i ints1 = _mm_castps_si128(_mm_add_ps(floats1, offset));
|
||||
const __m128i ints2 = _mm_castps_si128(_mm_add_ps(floats2, offset));
|
||||
const __m128i ints3 = _mm_castps_si128(_mm_add_ps(floats3, offset));
|
||||
|
||||
const __m128i shorts0 = _mm_and_si128(_mm_packs_epi16(ints0, ints1), mask);
|
||||
const __m128i shorts1 = _mm_and_si128(_mm_packs_epi16(ints2, ints3), mask);
|
||||
|
||||
const __m128i bytes = _mm_packus_epi16(shorts0, shorts1);
|
||||
|
||||
_mm_store_si128((__m128i*)&dst[i], bytes);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_U8_SSE2(Uint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [98304.0, 98306.0]
|
||||
* 2) Extract the lowest 16 bits and clamp to [0, 255]
|
||||
* Overflow is correctly handled for inputs between roughly [-254.0, 254.0]
|
||||
* dst[i] = clamp(i16(f2i(src[i] + 98305.0) & 0xFFFF), 0, 255) */
|
||||
const __m128 offset = _mm_set1_ps(98305.0f);
|
||||
const __m128i mask = _mm_set1_epi16(0xFF);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using SSE2)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
const __m128i ints = _mm_castps_si128(_mm_add_ss(_mm_load_ss(&src[i]), offset));
|
||||
dst[i] = (Uint8)(_mm_cvtsi128_si32(_mm_packus_epi16(ints, ints)) & 0xFF);
|
||||
}, {
|
||||
const __m128 floats0 = _mm_loadu_ps(&src[i]);
|
||||
const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
|
||||
const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
|
||||
const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
|
||||
|
||||
const __m128i ints0 = _mm_castps_si128(_mm_add_ps(floats0, offset));
|
||||
const __m128i ints1 = _mm_castps_si128(_mm_add_ps(floats1, offset));
|
||||
const __m128i ints2 = _mm_castps_si128(_mm_add_ps(floats2, offset));
|
||||
const __m128i ints3 = _mm_castps_si128(_mm_add_ps(floats3, offset));
|
||||
|
||||
const __m128i shorts0 = _mm_and_si128(_mm_packus_epi16(ints0, ints1), mask);
|
||||
const __m128i shorts1 = _mm_and_si128(_mm_packus_epi16(ints2, ints3), mask);
|
||||
|
||||
const __m128i bytes = _mm_packus_epi16(shorts0, shorts1);
|
||||
|
||||
_mm_store_si128((__m128i*)&dst[i], bytes);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S16_SSE2(Sint16 *dst, const float *src, int num_samples)
|
||||
{
|
||||
/* 1) Shift the float range from [-1.0, 1.0] to [256.0, 258.0]
|
||||
* 2) Shift the int range from [0x43800000, 0x43810000] to [-32768,32768]
|
||||
* 3) Clamp to range [-32768,32767]
|
||||
* Overflow is correctly handled for inputs between roughly [-257.0, +inf)
|
||||
* dst[i] = clamp(f2i(src[i] + 257.0) - 0x43808000, -32768, 32767) */
|
||||
const __m128 offset = _mm_set1_ps(257.0f);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using SSE2)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
const __m128i ints = _mm_sub_epi32(_mm_castps_si128(_mm_add_ss(_mm_load_ss(&src[i]), offset)), _mm_castps_si128(offset));
|
||||
dst[i] = (Sint16)(_mm_cvtsi128_si32(_mm_packs_epi32(ints, ints)) & 0xFFFF);
|
||||
}, {
|
||||
const __m128 floats0 = _mm_loadu_ps(&src[i]);
|
||||
const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
|
||||
const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
|
||||
const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
|
||||
|
||||
const __m128i ints0 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats0, offset)), _mm_castps_si128(offset));
|
||||
const __m128i ints1 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats1, offset)), _mm_castps_si128(offset));
|
||||
const __m128i ints2 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats2, offset)), _mm_castps_si128(offset));
|
||||
const __m128i ints3 = _mm_sub_epi32(_mm_castps_si128(_mm_add_ps(floats3, offset)), _mm_castps_si128(offset));
|
||||
|
||||
const __m128i shorts0 = _mm_packs_epi32(ints0, ints1);
|
||||
const __m128i shorts1 = _mm_packs_epi32(ints2, ints3);
|
||||
|
||||
_mm_store_si128((__m128i*)&dst[i], shorts0);
|
||||
_mm_store_si128((__m128i*)&dst[i + 8], shorts1);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("sse2") SDL_Convert_F32_to_S32_SSE2(Sint32 *dst, const float *src, int num_samples)
|
||||
{
|
||||
/* 1) Scale the float range from [-1.0, 1.0] to [-2147483648.0, 2147483648.0]
|
||||
* 2) Convert to integer (values too small/large become 0x80000000 = -2147483648)
|
||||
* 3) Fixup values which were too large (0x80000000 ^ 0xFFFFFFFF = 2147483647)
|
||||
* dst[i] = i32(src[i] * 2147483648.0) ^ ((src[i] >= 2147483648.0) ? 0xFFFFFFFF : 0x00000000) */
|
||||
const __m128 limit = _mm_set1_ps(2147483648.0f);
|
||||
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using SSE2)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
const __m128 floats = _mm_load_ss(&src[i]);
|
||||
const __m128 values = _mm_mul_ss(floats, limit);
|
||||
const __m128i ints = _mm_xor_si128(_mm_cvttps_epi32(values), _mm_castps_si128(_mm_cmpge_ss(values, limit)));
|
||||
dst[i] = (Sint32)_mm_cvtsi128_si32(ints);
|
||||
}, {
|
||||
const __m128 floats0 = _mm_loadu_ps(&src[i]);
|
||||
const __m128 floats1 = _mm_loadu_ps(&src[i + 4]);
|
||||
const __m128 floats2 = _mm_loadu_ps(&src[i + 8]);
|
||||
const __m128 floats3 = _mm_loadu_ps(&src[i + 12]);
|
||||
|
||||
const __m128 values1 = _mm_mul_ps(floats0, limit);
|
||||
const __m128 values2 = _mm_mul_ps(floats1, limit);
|
||||
const __m128 values3 = _mm_mul_ps(floats2, limit);
|
||||
const __m128 values4 = _mm_mul_ps(floats3, limit);
|
||||
|
||||
const __m128i ints0 = _mm_xor_si128(_mm_cvttps_epi32(values1), _mm_castps_si128(_mm_cmpge_ps(values1, limit)));
|
||||
const __m128i ints1 = _mm_xor_si128(_mm_cvttps_epi32(values2), _mm_castps_si128(_mm_cmpge_ps(values2, limit)));
|
||||
const __m128i ints2 = _mm_xor_si128(_mm_cvttps_epi32(values3), _mm_castps_si128(_mm_cmpge_ps(values3, limit)));
|
||||
const __m128i ints3 = _mm_xor_si128(_mm_cvttps_epi32(values4), _mm_castps_si128(_mm_cmpge_ps(values4, limit)));
|
||||
|
||||
_mm_store_si128((__m128i*)&dst[i], ints0);
|
||||
_mm_store_si128((__m128i*)&dst[i + 4], ints1);
|
||||
_mm_store_si128((__m128i*)&dst[i + 8], ints2);
|
||||
_mm_store_si128((__m128i*)&dst[i + 12], ints3);
|
||||
})
|
||||
}
|
||||
#endif
|
||||
|
||||
// FIXME: SDL doesn't have SSSE3 detection, so use the next one up
|
||||
#ifdef SDL_SSE4_1_INTRINSICS
|
||||
static void SDL_TARGETING("ssse3") SDL_Convert_Swap16_SSSE3(Uint16* dst, const Uint16* src, int num_samples)
|
||||
{
|
||||
const __m128i shuffle = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);
|
||||
|
||||
CONVERT_16_FWD({
|
||||
dst[i] = SDL_Swap16(src[i]);
|
||||
}, {
|
||||
__m128i ints0 = _mm_loadu_si128((const __m128i*)&src[i]);
|
||||
__m128i ints1 = _mm_loadu_si128((const __m128i*)&src[i + 8]);
|
||||
|
||||
ints0 = _mm_shuffle_epi8(ints0, shuffle);
|
||||
ints1 = _mm_shuffle_epi8(ints1, shuffle);
|
||||
|
||||
_mm_store_si128((__m128i*)&dst[i], ints0);
|
||||
_mm_store_si128((__m128i*)&dst[i + 8], ints1);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_TARGETING("ssse3") SDL_Convert_Swap32_SSSE3(Uint32* dst, const Uint32* src, int num_samples)
|
||||
{
|
||||
const __m128i shuffle = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
|
||||
|
||||
CONVERT_16_FWD({
|
||||
dst[i] = SDL_Swap32(src[i]);
|
||||
}, {
|
||||
__m128i ints0 = _mm_loadu_si128((const __m128i*)&src[i]);
|
||||
__m128i ints1 = _mm_loadu_si128((const __m128i*)&src[i + 4]);
|
||||
__m128i ints2 = _mm_loadu_si128((const __m128i*)&src[i + 8]);
|
||||
__m128i ints3 = _mm_loadu_si128((const __m128i*)&src[i + 12]);
|
||||
|
||||
ints0 = _mm_shuffle_epi8(ints0, shuffle);
|
||||
ints1 = _mm_shuffle_epi8(ints1, shuffle);
|
||||
ints2 = _mm_shuffle_epi8(ints2, shuffle);
|
||||
ints3 = _mm_shuffle_epi8(ints3, shuffle);
|
||||
|
||||
_mm_store_si128((__m128i*)&dst[i], ints0);
|
||||
_mm_store_si128((__m128i*)&dst[i + 4], ints1);
|
||||
_mm_store_si128((__m128i*)&dst[i + 8], ints2);
|
||||
_mm_store_si128((__m128i*)&dst[i + 12], ints3);
|
||||
})
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
static void SDL_Convert_S8_to_F32_NEON(float *dst, const Sint8 *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("S8", "F32 (using NEON)");
|
||||
|
||||
CONVERT_16_REV({
|
||||
vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32(src[i]), 7), 0);
|
||||
}, {
|
||||
int8x16_t bytes = vld1q_s8(&src[i]);
|
||||
|
||||
int16x8_t shorts0 = vmovl_s8(vget_low_s8(bytes));
|
||||
int16x8_t shorts1 = vmovl_s8(vget_high_s8(bytes));
|
||||
|
||||
float32x4_t floats0 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts0)), 7);
|
||||
float32x4_t floats1 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts0)), 7);
|
||||
float32x4_t floats2 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts1)), 7);
|
||||
float32x4_t floats3 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts1)), 7);
|
||||
|
||||
vst1q_f32(&dst[i], floats0);
|
||||
vst1q_f32(&dst[i + 4], floats1);
|
||||
vst1q_f32(&dst[i + 8], floats2);
|
||||
vst1q_f32(&dst[i + 12], floats3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_Convert_U8_to_F32_NEON(float *dst, const Uint8 *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("U8", "F32 (using NEON)");
|
||||
|
||||
uint8x16_t flipper = vdupq_n_u8(0x80);
|
||||
|
||||
CONVERT_16_REV({
|
||||
vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32((Sint8)(src[i] ^ 0x80)), 7), 0);
|
||||
}, {
|
||||
int8x16_t bytes = vreinterpretq_s8_u8(veorq_u8(vld1q_u8(&src[i]), flipper));
|
||||
|
||||
int16x8_t shorts0 = vmovl_s8(vget_low_s8(bytes));
|
||||
int16x8_t shorts1 = vmovl_s8(vget_high_s8(bytes));
|
||||
|
||||
float32x4_t floats0 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts0)), 7);
|
||||
float32x4_t floats1 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts0)), 7);
|
||||
float32x4_t floats2 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts1)), 7);
|
||||
float32x4_t floats3 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts1)), 7);
|
||||
|
||||
vst1q_f32(&dst[i], floats0);
|
||||
vst1q_f32(&dst[i + 4], floats1);
|
||||
vst1q_f32(&dst[i + 8], floats2);
|
||||
vst1q_f32(&dst[i + 12], floats3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_Convert_S16_to_F32_NEON(float *dst, const Sint16 *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("S16", "F32 (using NEON)");
|
||||
|
||||
CONVERT_16_REV({
|
||||
vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vdup_n_s32(src[i]), 15), 0);
|
||||
}, {
|
||||
int16x8_t shorts0 = vld1q_s16(&src[i]);
|
||||
int16x8_t shorts1 = vld1q_s16(&src[i + 8]);
|
||||
|
||||
float32x4_t floats0 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts0)), 15);
|
||||
float32x4_t floats1 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts0)), 15);
|
||||
float32x4_t floats2 = vcvtq_n_f32_s32(vmovl_s16(vget_low_s16(shorts1)), 15);
|
||||
float32x4_t floats3 = vcvtq_n_f32_s32(vmovl_s16(vget_high_s16(shorts1)), 15);
|
||||
|
||||
vst1q_f32(&dst[i], floats0);
|
||||
vst1q_f32(&dst[i + 4], floats1);
|
||||
vst1q_f32(&dst[i + 8], floats2);
|
||||
vst1q_f32(&dst[i + 12], floats3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_Convert_S32_to_F32_NEON(float *dst, const Sint32 *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("S32", "F32 (using NEON)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
vst1_lane_f32(&dst[i], vcvt_n_f32_s32(vld1_dup_s32(&src[i]), 31), 0);
|
||||
}, {
|
||||
int32x4_t ints0 = vld1q_s32(&src[i]);
|
||||
int32x4_t ints1 = vld1q_s32(&src[i + 4]);
|
||||
int32x4_t ints2 = vld1q_s32(&src[i + 8]);
|
||||
int32x4_t ints3 = vld1q_s32(&src[i + 12]);
|
||||
|
||||
float32x4_t floats0 = vcvtq_n_f32_s32(ints0, 31);
|
||||
float32x4_t floats1 = vcvtq_n_f32_s32(ints1, 31);
|
||||
float32x4_t floats2 = vcvtq_n_f32_s32(ints2, 31);
|
||||
float32x4_t floats3 = vcvtq_n_f32_s32(ints3, 31);
|
||||
|
||||
vst1q_f32(&dst[i], floats0);
|
||||
vst1q_f32(&dst[i + 4], floats1);
|
||||
vst1q_f32(&dst[i + 8], floats2);
|
||||
vst1q_f32(&dst[i + 12], floats3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S8_NEON(Sint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S8 (using NEON)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
vst1_lane_s8(&dst[i], vreinterpret_s8_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)), 3);
|
||||
}, {
|
||||
float32x4_t floats0 = vld1q_f32(&src[i]);
|
||||
float32x4_t floats1 = vld1q_f32(&src[i + 4]);
|
||||
float32x4_t floats2 = vld1q_f32(&src[i + 8]);
|
||||
float32x4_t floats3 = vld1q_f32(&src[i + 12]);
|
||||
|
||||
int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
|
||||
int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
|
||||
int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
|
||||
int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
|
||||
|
||||
int16x8_t shorts0 = vcombine_s16(vshrn_n_s32(ints0, 16), vshrn_n_s32(ints1, 16));
|
||||
int16x8_t shorts1 = vcombine_s16(vshrn_n_s32(ints2, 16), vshrn_n_s32(ints3, 16));
|
||||
|
||||
int8x16_t bytes = vcombine_s8(vshrn_n_s16(shorts0, 8), vshrn_n_s16(shorts1, 8));
|
||||
|
||||
vst1q_s8(&dst[i], bytes);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_U8_NEON(Uint8 *dst, const float *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "U8 (using NEON)");
|
||||
|
||||
uint8x16_t flipper = vdupq_n_u8(0x80);
|
||||
|
||||
CONVERT_16_FWD({
|
||||
vst1_lane_u8(&dst[i],
|
||||
veor_u8(vreinterpret_u8_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)),
|
||||
vget_low_u8(flipper)), 3);
|
||||
}, {
|
||||
float32x4_t floats0 = vld1q_f32(&src[i]);
|
||||
float32x4_t floats1 = vld1q_f32(&src[i + 4]);
|
||||
float32x4_t floats2 = vld1q_f32(&src[i + 8]);
|
||||
float32x4_t floats3 = vld1q_f32(&src[i + 12]);
|
||||
|
||||
int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
|
||||
int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
|
||||
int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
|
||||
int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
|
||||
|
||||
int16x8_t shorts0 = vcombine_s16(vshrn_n_s32(ints0, 16), vshrn_n_s32(ints1, 16));
|
||||
int16x8_t shorts1 = vcombine_s16(vshrn_n_s32(ints2, 16), vshrn_n_s32(ints3, 16));
|
||||
|
||||
uint8x16_t bytes = veorq_u8(vreinterpretq_u8_s8(
|
||||
vcombine_s8(vshrn_n_s16(shorts0, 8), vshrn_n_s16(shorts1, 8))),
|
||||
flipper);
|
||||
|
||||
vst1q_u8(&dst[i], bytes);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S16_NEON(Sint16 *dst, const float *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S16 (using NEON)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
vst1_lane_s16(&dst[i], vreinterpret_s16_s32(vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31)), 1);
|
||||
}, {
|
||||
float32x4_t floats0 = vld1q_f32(&src[i]);
|
||||
float32x4_t floats1 = vld1q_f32(&src[i + 4]);
|
||||
float32x4_t floats2 = vld1q_f32(&src[i + 8]);
|
||||
float32x4_t floats3 = vld1q_f32(&src[i + 12]);
|
||||
|
||||
int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
|
||||
int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
|
||||
int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
|
||||
int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
|
||||
|
||||
int16x8_t shorts0 = vcombine_s16(vshrn_n_s32(ints0, 16), vshrn_n_s32(ints1, 16));
|
||||
int16x8_t shorts1 = vcombine_s16(vshrn_n_s32(ints2, 16), vshrn_n_s32(ints3, 16));
|
||||
|
||||
vst1q_s16(&dst[i], shorts0);
|
||||
vst1q_s16(&dst[i + 8], shorts1);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_Convert_F32_to_S32_NEON(Sint32 *dst, const float *src, int num_samples)
|
||||
{
|
||||
LOG_DEBUG_AUDIO_CONVERT("F32", "S32 (using NEON)");
|
||||
|
||||
CONVERT_16_FWD({
|
||||
vst1_lane_s32(&dst[i], vcvt_n_s32_f32(vld1_dup_f32(&src[i]), 31), 0);
|
||||
}, {
|
||||
float32x4_t floats0 = vld1q_f32(&src[i]);
|
||||
float32x4_t floats1 = vld1q_f32(&src[i + 4]);
|
||||
float32x4_t floats2 = vld1q_f32(&src[i + 8]);
|
||||
float32x4_t floats3 = vld1q_f32(&src[i + 12]);
|
||||
|
||||
int32x4_t ints0 = vcvtq_n_s32_f32(floats0, 31);
|
||||
int32x4_t ints1 = vcvtq_n_s32_f32(floats1, 31);
|
||||
int32x4_t ints2 = vcvtq_n_s32_f32(floats2, 31);
|
||||
int32x4_t ints3 = vcvtq_n_s32_f32(floats3, 31);
|
||||
|
||||
vst1q_s32(&dst[i], ints0);
|
||||
vst1q_s32(&dst[i + 4], ints1);
|
||||
vst1q_s32(&dst[i + 8], ints2);
|
||||
vst1q_s32(&dst[i + 12], ints3);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_Convert_Swap16_NEON(Uint16* dst, const Uint16* src, int num_samples)
|
||||
{
|
||||
CONVERT_16_FWD({
|
||||
dst[i] = SDL_Swap16(src[i]);
|
||||
}, {
|
||||
uint8x16_t ints0 = vld1q_u8((const Uint8*)&src[i]);
|
||||
uint8x16_t ints1 = vld1q_u8((const Uint8*)&src[i + 8]);
|
||||
|
||||
ints0 = vrev16q_u8(ints0);
|
||||
ints1 = vrev16q_u8(ints1);
|
||||
|
||||
vst1q_u8((Uint8*)&dst[i], ints0);
|
||||
vst1q_u8((Uint8*)&dst[i + 8], ints1);
|
||||
})
|
||||
}
|
||||
|
||||
static void SDL_Convert_Swap32_NEON(Uint32* dst, const Uint32* src, int num_samples)
|
||||
{
|
||||
CONVERT_16_FWD({
|
||||
dst[i] = SDL_Swap32(src[i]);
|
||||
}, {
|
||||
uint8x16_t ints0 = vld1q_u8((const Uint8*)&src[i]);
|
||||
uint8x16_t ints1 = vld1q_u8((const Uint8*)&src[i + 4]);
|
||||
uint8x16_t ints2 = vld1q_u8((const Uint8*)&src[i + 8]);
|
||||
uint8x16_t ints3 = vld1q_u8((const Uint8*)&src[i + 12]);
|
||||
|
||||
ints0 = vrev32q_u8(ints0);
|
||||
ints1 = vrev32q_u8(ints1);
|
||||
ints2 = vrev32q_u8(ints2);
|
||||
ints3 = vrev32q_u8(ints3);
|
||||
|
||||
vst1q_u8((Uint8*)&dst[i], ints0);
|
||||
vst1q_u8((Uint8*)&dst[i + 4], ints1);
|
||||
vst1q_u8((Uint8*)&dst[i + 8], ints2);
|
||||
vst1q_u8((Uint8*)&dst[i + 12], ints3);
|
||||
})
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef CONVERT_16_FWD
|
||||
#undef CONVERT_16_REV
|
||||
|
||||
// Function pointers set to a CPU-specific implementation.
|
||||
static void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_S32_to_F32)(float *dst, const Sint32 *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_F32_to_S8)(Sint8 *dst, const float *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples) = NULL;
|
||||
|
||||
static void (*SDL_Convert_Swap16)(Uint16* dst, const Uint16* src, int num_samples) = NULL;
|
||||
static void (*SDL_Convert_Swap32)(Uint32* dst, const Uint32* src, int num_samples) = NULL;
|
||||
|
||||
void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt)
|
||||
{
|
||||
switch (src_fmt) {
|
||||
case SDL_AUDIO_S8:
|
||||
SDL_Convert_S8_to_F32(dst, (const Sint8 *) src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_U8:
|
||||
SDL_Convert_U8_to_F32(dst, (const Uint8 *) src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16:
|
||||
SDL_Convert_S16_to_F32(dst, (const Sint16 *) src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
|
||||
SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) src, num_samples);
|
||||
SDL_Convert_S16_to_F32(dst, (const Sint16 *) dst, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32:
|
||||
SDL_Convert_S32_to_F32(dst, (const Sint32 *) src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
|
||||
SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples);
|
||||
SDL_Convert_S32_to_F32(dst, (const Sint32 *) dst, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
|
||||
SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples);
|
||||
break;
|
||||
|
||||
default: SDL_assert(!"Unexpected audio format!"); break;
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt)
|
||||
{
|
||||
switch (dst_fmt) {
|
||||
case SDL_AUDIO_S8:
|
||||
SDL_Convert_F32_to_S8((Sint8 *) dst, src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_U8:
|
||||
SDL_Convert_F32_to_U8((Uint8 *) dst, src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16:
|
||||
SDL_Convert_F32_to_S16((Sint16 *) dst, src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
|
||||
SDL_Convert_F32_to_S16((Sint16 *) dst, src, num_samples);
|
||||
SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) dst, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32:
|
||||
SDL_Convert_F32_to_S32((Sint32 *) dst, src, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
|
||||
SDL_Convert_F32_to_S32((Sint32 *) dst, src, num_samples);
|
||||
SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) dst, num_samples);
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_F32 ^ SDL_AUDIO_MASK_BIG_ENDIAN:
|
||||
SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples);
|
||||
break;
|
||||
|
||||
default: SDL_assert(!"Unexpected audio format!"); break;
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize)
|
||||
{
|
||||
switch (bitsize) {
|
||||
case 16: SDL_Convert_Swap16((Uint16*) dst, (const Uint16*) src, num_samples); break;
|
||||
case 32: SDL_Convert_Swap32((Uint32*) dst, (const Uint32*) src, num_samples); break;
|
||||
default: SDL_assert(!"Unexpected audio format!"); break;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_ChooseAudioConverters(void)
|
||||
{
|
||||
static bool converters_chosen = false;
|
||||
if (converters_chosen) {
|
||||
return;
|
||||
}
|
||||
|
||||
#define SET_CONVERTER_FUNCS(fntype) \
|
||||
SDL_Convert_Swap16 = SDL_Convert_Swap16_##fntype; \
|
||||
SDL_Convert_Swap32 = SDL_Convert_Swap32_##fntype;
|
||||
|
||||
#ifdef SDL_SSE4_1_INTRINSICS
|
||||
if (SDL_HasSSE41()) {
|
||||
SET_CONVERTER_FUNCS(SSSE3);
|
||||
} else
|
||||
#endif
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
if (SDL_HasNEON()) {
|
||||
SET_CONVERTER_FUNCS(NEON);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
SET_CONVERTER_FUNCS(Scalar);
|
||||
}
|
||||
|
||||
#undef SET_CONVERTER_FUNCS
|
||||
|
||||
#define SET_CONVERTER_FUNCS(fntype) \
|
||||
SDL_Convert_S8_to_F32 = SDL_Convert_S8_to_F32_##fntype; \
|
||||
SDL_Convert_U8_to_F32 = SDL_Convert_U8_to_F32_##fntype; \
|
||||
SDL_Convert_S16_to_F32 = SDL_Convert_S16_to_F32_##fntype; \
|
||||
SDL_Convert_S32_to_F32 = SDL_Convert_S32_to_F32_##fntype; \
|
||||
SDL_Convert_F32_to_S8 = SDL_Convert_F32_to_S8_##fntype; \
|
||||
SDL_Convert_F32_to_U8 = SDL_Convert_F32_to_U8_##fntype; \
|
||||
SDL_Convert_F32_to_S16 = SDL_Convert_F32_to_S16_##fntype; \
|
||||
SDL_Convert_F32_to_S32 = SDL_Convert_F32_to_S32_##fntype; \
|
||||
|
||||
#ifdef SDL_SSE2_INTRINSICS
|
||||
if (SDL_HasSSE2()) {
|
||||
SET_CONVERTER_FUNCS(SSE2);
|
||||
} else
|
||||
#endif
|
||||
#ifdef SDL_NEON_INTRINSICS
|
||||
if (SDL_HasNEON()) {
|
||||
SET_CONVERTER_FUNCS(NEON);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
SET_CONVERTER_FUNCS(Scalar);
|
||||
}
|
||||
|
||||
#undef SET_CONVERTER_FUNCS
|
||||
|
||||
converters_chosen = true;
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// This provides the default mixing callback for the SDL audio routines
|
||||
|
||||
#include "SDL_sysaudio.h"
|
||||
|
||||
/* This table is used to add two sound values together and pin
|
||||
* the value to avoid overflow. (used with permission from ARDI)
|
||||
*/
|
||||
static const Uint8 mix8[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03,
|
||||
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
|
||||
0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24,
|
||||
0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
|
||||
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
|
||||
0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
|
||||
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B,
|
||||
0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
|
||||
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71,
|
||||
0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C,
|
||||
0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
||||
0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92,
|
||||
0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
|
||||
0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8,
|
||||
0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3,
|
||||
0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
|
||||
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9,
|
||||
0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4,
|
||||
0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
|
||||
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA,
|
||||
0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5,
|
||||
0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
||||
};
|
||||
|
||||
// The volume ranges from 0 - 128
|
||||
#define MIX_MAXVOLUME 128
|
||||
#define ADJUST_VOLUME(type, s, v) ((s) = (type)(((s) * (v)) / MIX_MAXVOLUME))
|
||||
#define ADJUST_VOLUME_U8(s, v) ((s) = (Uint8)(((((s) - 128) * (v)) / MIX_MAXVOLUME) + 128))
|
||||
|
||||
// !!! FIXME: This needs some SIMD magic.
|
||||
// !!! FIXME: Add fast-path for volume = 1
|
||||
// !!! FIXME: Use larger scales for 16-bit/32-bit integers
|
||||
|
||||
bool SDL_MixAudio(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, Uint32 len, float fvolume)
|
||||
{
|
||||
int volume = (int)SDL_roundf(fvolume * MIX_MAXVOLUME);
|
||||
|
||||
if (volume == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
|
||||
case SDL_AUDIO_U8:
|
||||
{
|
||||
Uint8 src_sample;
|
||||
|
||||
while (len--) {
|
||||
src_sample = *src;
|
||||
ADJUST_VOLUME_U8(src_sample, volume);
|
||||
*dst = mix8[*dst + src_sample];
|
||||
++dst;
|
||||
++src;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S8:
|
||||
{
|
||||
Sint8 *dst8, *src8;
|
||||
Sint8 src_sample;
|
||||
int dst_sample;
|
||||
const int max_audioval = SDL_MAX_SINT8;
|
||||
const int min_audioval = SDL_MIN_SINT8;
|
||||
|
||||
src8 = (Sint8 *)src;
|
||||
dst8 = (Sint8 *)dst;
|
||||
while (len--) {
|
||||
src_sample = *src8;
|
||||
ADJUST_VOLUME(Sint8, src_sample, volume);
|
||||
dst_sample = *dst8 + src_sample;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*dst8 = (Sint8)dst_sample;
|
||||
++dst8;
|
||||
++src8;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S16LE:
|
||||
{
|
||||
Sint16 src1, src2;
|
||||
int dst_sample;
|
||||
const int max_audioval = SDL_MAX_SINT16;
|
||||
const int min_audioval = SDL_MIN_SINT16;
|
||||
|
||||
len /= 2;
|
||||
while (len--) {
|
||||
src1 = SDL_Swap16LE(*(Sint16 *)src);
|
||||
ADJUST_VOLUME(Sint16, src1, volume);
|
||||
src2 = SDL_Swap16LE(*(Sint16 *)dst);
|
||||
src += 2;
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(Sint16 *)dst = SDL_Swap16LE((Sint16)dst_sample);
|
||||
dst += 2;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S16BE:
|
||||
{
|
||||
Sint16 src1, src2;
|
||||
int dst_sample;
|
||||
const int max_audioval = SDL_MAX_SINT16;
|
||||
const int min_audioval = SDL_MIN_SINT16;
|
||||
|
||||
len /= 2;
|
||||
while (len--) {
|
||||
src1 = SDL_Swap16BE(*(Sint16 *)src);
|
||||
ADJUST_VOLUME(Sint16, src1, volume);
|
||||
src2 = SDL_Swap16BE(*(Sint16 *)dst);
|
||||
src += 2;
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(Sint16 *)dst = SDL_Swap16BE((Sint16)dst_sample);
|
||||
dst += 2;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S32LE:
|
||||
{
|
||||
const Uint32 *src32 = (Uint32 *)src;
|
||||
Uint32 *dst32 = (Uint32 *)dst;
|
||||
Sint64 src1, src2;
|
||||
Sint64 dst_sample;
|
||||
const Sint64 max_audioval = SDL_MAX_SINT32;
|
||||
const Sint64 min_audioval = SDL_MIN_SINT32;
|
||||
|
||||
len /= 4;
|
||||
while (len--) {
|
||||
src1 = (Sint64)((Sint32)SDL_Swap32LE(*src32));
|
||||
src32++;
|
||||
ADJUST_VOLUME(Sint64, src1, volume);
|
||||
src2 = (Sint64)((Sint32)SDL_Swap32LE(*dst32));
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(dst32++) = SDL_Swap32LE((Uint32)((Sint32)dst_sample));
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_S32BE:
|
||||
{
|
||||
const Uint32 *src32 = (Uint32 *)src;
|
||||
Uint32 *dst32 = (Uint32 *)dst;
|
||||
Sint64 src1, src2;
|
||||
Sint64 dst_sample;
|
||||
const Sint64 max_audioval = SDL_MAX_SINT32;
|
||||
const Sint64 min_audioval = SDL_MIN_SINT32;
|
||||
|
||||
len /= 4;
|
||||
while (len--) {
|
||||
src1 = (Sint64)((Sint32)SDL_Swap32BE(*src32));
|
||||
src32++;
|
||||
ADJUST_VOLUME(Sint64, src1, volume);
|
||||
src2 = (Sint64)((Sint32)SDL_Swap32BE(*dst32));
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(dst32++) = SDL_Swap32BE((Uint32)((Sint32)dst_sample));
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_F32LE:
|
||||
{
|
||||
const float *src32 = (float *)src;
|
||||
float *dst32 = (float *)dst;
|
||||
float src1, src2;
|
||||
float dst_sample;
|
||||
const float max_audioval = 1.0f;
|
||||
const float min_audioval = -1.0f;
|
||||
|
||||
len /= 4;
|
||||
while (len--) {
|
||||
src1 = SDL_SwapFloatLE(*src32) * fvolume;
|
||||
src2 = SDL_SwapFloatLE(*dst32);
|
||||
src32++;
|
||||
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(dst32++) = SDL_SwapFloatLE(dst_sample);
|
||||
}
|
||||
} break;
|
||||
|
||||
case SDL_AUDIO_F32BE:
|
||||
{
|
||||
const float *src32 = (float *)src;
|
||||
float *dst32 = (float *)dst;
|
||||
float src1, src2;
|
||||
float dst_sample;
|
||||
const float max_audioval = 1.0f;
|
||||
const float min_audioval = -1.0f;
|
||||
|
||||
len /= 4;
|
||||
while (len--) {
|
||||
src1 = SDL_SwapFloatBE(*src32) * fvolume;
|
||||
src2 = SDL_SwapFloatBE(*dst32);
|
||||
src32++;
|
||||
|
||||
dst_sample = src1 + src2;
|
||||
if (dst_sample > max_audioval) {
|
||||
dst_sample = max_audioval;
|
||||
} else if (dst_sample < min_audioval) {
|
||||
dst_sample = min_audioval;
|
||||
}
|
||||
*(dst32++) = SDL_SwapFloatBE(dst_sample);
|
||||
}
|
||||
} break;
|
||||
|
||||
default: // If this happens... FIXME!
|
||||
return SDL_SetError("SDL_MixAudio(): unknown audio format");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_sysaudio_h_
|
||||
#define SDL_sysaudio_h_
|
||||
|
||||
#define DEBUG_AUDIOSTREAM 0
|
||||
#define DEBUG_AUDIO_CONVERT 0
|
||||
|
||||
#if DEBUG_AUDIO_CONVERT
|
||||
#define LOG_DEBUG_AUDIO_CONVERT(from, to) SDL_Log("SDL_AUDIO_CONVERT: Converting %s to %s.", from, to);
|
||||
#else
|
||||
#define LOG_DEBUG_AUDIO_CONVERT(from, to)
|
||||
#endif
|
||||
|
||||
// !!! FIXME: These are wordy and unlocalized...
|
||||
#define DEFAULT_PLAYBACK_DEVNAME "System audio playback device"
|
||||
#define DEFAULT_RECORDING_DEVNAME "System audio recording device"
|
||||
|
||||
// these are used when no better specifics are known. We default to CD audio quality.
|
||||
#define DEFAULT_AUDIO_PLAYBACK_FORMAT SDL_AUDIO_S16
|
||||
#define DEFAULT_AUDIO_PLAYBACK_CHANNELS 2
|
||||
#define DEFAULT_AUDIO_PLAYBACK_FREQUENCY 44100
|
||||
|
||||
#define DEFAULT_AUDIO_RECORDING_FORMAT SDL_AUDIO_S16
|
||||
#define DEFAULT_AUDIO_RECORDING_CHANNELS 1
|
||||
#define DEFAULT_AUDIO_RECORDING_FREQUENCY 44100
|
||||
|
||||
#define SDL_MAX_CHANNELMAP_CHANNELS 8 // !!! FIXME: if SDL ever supports more channels, clean this out and make those parts dynamic.
|
||||
|
||||
typedef struct SDL_AudioDevice SDL_AudioDevice;
|
||||
typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice;
|
||||
|
||||
// Used by src/SDL.c to initialize a particular audio driver.
|
||||
extern bool SDL_InitAudio(const char *driver_name);
|
||||
|
||||
// Used by src/SDL.c to shut down previously-initialized audio.
|
||||
extern void SDL_QuitAudio(void);
|
||||
|
||||
// Function to get a list of audio formats, ordered most similar to `format` to least, 0-terminated. Don't free results.
|
||||
const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format);
|
||||
|
||||
// Must be called at least once before using converters.
|
||||
extern void SDL_ChooseAudioConverters(void);
|
||||
extern void SDL_SetupAudioResampler(void);
|
||||
|
||||
/* Backends should call this as devices are added to the system (such as
|
||||
a USB headset being plugged in), and should also be called for
|
||||
for every device found during DetectDevices(). */
|
||||
extern SDL_AudioDevice *SDL_AddAudioDevice(bool recording, const char *name, const SDL_AudioSpec *spec, void *handle);
|
||||
|
||||
/* Backends should call this if an opened audio device is lost.
|
||||
This can happen due to i/o errors, or a device being unplugged, etc. */
|
||||
extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device);
|
||||
|
||||
// Backends should call this if the system default device changes.
|
||||
extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device);
|
||||
|
||||
// Backends should call this if a device's format is changing (opened or not); SDL will update state and carry on with the new format.
|
||||
extern bool SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
|
||||
|
||||
// Same as above, but assume the device is already locked.
|
||||
extern bool SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
|
||||
|
||||
// Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. DOES NOT LOCK THE DEVICE.
|
||||
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle);
|
||||
|
||||
// Find an SDL_AudioDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
|
||||
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata);
|
||||
|
||||
// Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct.
|
||||
extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device);
|
||||
|
||||
// Backends can call this to get a reasonable default sample frame count for a device's sample rate.
|
||||
int SDL_GetDefaultSampleFramesFromFreq(const int freq);
|
||||
|
||||
// Backends can call this to get a standardized name for a thread to power a specific audio device.
|
||||
extern char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen);
|
||||
|
||||
// Backends can call these to change a device's refcount.
|
||||
extern void RefPhysicalAudioDevice(SDL_AudioDevice *device);
|
||||
extern void UnrefPhysicalAudioDevice(SDL_AudioDevice *device);
|
||||
|
||||
// These functions are the heart of the audio threads. Backends can call them directly if they aren't using the SDL-provided thread.
|
||||
extern void SDL_PlaybackAudioThreadSetup(SDL_AudioDevice *device);
|
||||
extern bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device);
|
||||
extern void SDL_PlaybackAudioThreadShutdown(SDL_AudioDevice *device);
|
||||
extern void SDL_RecordingAudioThreadSetup(SDL_AudioDevice *device);
|
||||
extern bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device);
|
||||
extern void SDL_RecordingAudioThreadShutdown(SDL_AudioDevice *device);
|
||||
extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device);
|
||||
|
||||
extern void ConvertAudioToFloat(float *dst, const void *src, int num_samples, SDL_AudioFormat src_fmt);
|
||||
extern void ConvertAudioFromFloat(void *dst, const float *src, int num_samples, SDL_AudioFormat dst_fmt);
|
||||
extern void ConvertAudioSwapEndian(void* dst, const void* src, int num_samples, int bitsize);
|
||||
|
||||
extern bool SDL_ChannelMapIsDefault(const int *map, int channels);
|
||||
extern bool SDL_ChannelMapIsBogus(const int *map, int channels);
|
||||
|
||||
// this gets used from the audio device threads. It has rules, don't use this if you don't know how to use it!
|
||||
extern void ConvertAudio(int num_frames,
|
||||
const void *src, SDL_AudioFormat src_format, int src_channels, const int *src_map,
|
||||
void *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map,
|
||||
void* scratch, float gain);
|
||||
|
||||
// Compare two SDL_AudioSpecs, return true if they match exactly.
|
||||
// Using SDL_memcmp directly isn't safe, since potential padding might not be initialized.
|
||||
// either channel map can be NULL for the default (and both should be if you don't care about them).
|
||||
extern bool SDL_AudioSpecsEqual(const SDL_AudioSpec *a, const SDL_AudioSpec *b, const int *channel_map_a, const int *channel_map_b);
|
||||
|
||||
// See if two channel maps match
|
||||
// either channel map can be NULL for the default (and both should be if you don't care about them).
|
||||
extern bool SDL_AudioChannelMapsEqual(int channels, const int *channel_map_a, const int *channel_map_b);
|
||||
|
||||
// allocate+copy a channel map.
|
||||
extern int *SDL_ChannelMapDup(const int *origchmap, int channels);
|
||||
|
||||
// Special case to let something in SDL_audiocvt.c access something in SDL_audio.c. Don't use this.
|
||||
extern void OnAudioStreamCreated(SDL_AudioStream *stream);
|
||||
extern void OnAudioStreamDestroy(SDL_AudioStream *stream);
|
||||
|
||||
// This just lets audio playback apply logical device gain at the same time as audiostream gain, so it's one multiplication instead of thousands.
|
||||
extern int SDL_GetAudioStreamDataAdjustGain(SDL_AudioStream *stream, void *voidbuf, int len, float extra_gain);
|
||||
|
||||
// This is the bulk of `SDL_SetAudioStream*putChannelMap`'s work, but it lets you skip the check about changing the device end of a stream if isinput==-1.
|
||||
extern bool SetAudioStreamChannelMap(SDL_AudioStream *stream, const SDL_AudioSpec *spec, int **stream_chmap, const int *chmap, int channels, int isinput);
|
||||
|
||||
|
||||
typedef struct SDL_AudioDriverImpl
|
||||
{
|
||||
void (*DetectDevices)(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording);
|
||||
bool (*OpenDevice)(SDL_AudioDevice *device);
|
||||
void (*ThreadInit)(SDL_AudioDevice *device); // Called by audio thread at start
|
||||
void (*ThreadDeinit)(SDL_AudioDevice *device); // Called by audio thread at end
|
||||
bool (*WaitDevice)(SDL_AudioDevice *device);
|
||||
bool (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen); // buffer and buflen are always from GetDeviceBuf, passed here for convenience.
|
||||
Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
|
||||
bool (*WaitRecordingDevice)(SDL_AudioDevice *device);
|
||||
int (*RecordDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
|
||||
void (*FlushRecording)(SDL_AudioDevice *device);
|
||||
void (*CloseDevice)(SDL_AudioDevice *device);
|
||||
void (*FreeDeviceHandle)(SDL_AudioDevice *device); // SDL is done with this device; free the handle from SDL_AddAudioDevice()
|
||||
void (*DeinitializeStart)(void); // SDL calls this, then starts destroying objects, then calls Deinitialize. This is a good place to stop hotplug detection.
|
||||
void (*Deinitialize)(void);
|
||||
|
||||
// Some flags to push duplicate code into the core and reduce #ifdefs.
|
||||
bool ProvidesOwnCallbackThread; // !!! FIXME: rename this, it's not a callback thread anymore.
|
||||
bool HasRecordingSupport;
|
||||
bool OnlyHasDefaultPlaybackDevice;
|
||||
bool OnlyHasDefaultRecordingDevice; // !!! FIXME: is there ever a time where you'd have a default playback and not a default recording (or vice versa)?
|
||||
} SDL_AudioDriverImpl;
|
||||
|
||||
|
||||
typedef struct SDL_PendingAudioDeviceEvent
|
||||
{
|
||||
Uint32 type;
|
||||
SDL_AudioDeviceID devid;
|
||||
struct SDL_PendingAudioDeviceEvent *next;
|
||||
} SDL_PendingAudioDeviceEvent;
|
||||
|
||||
typedef struct SDL_AudioDriver
|
||||
{
|
||||
const char *name; // The name of this audio driver
|
||||
const char *desc; // The description of this audio driver
|
||||
SDL_AudioDriverImpl impl; // the backend's interface
|
||||
SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash`
|
||||
SDL_HashTable *device_hash; // the collection of currently-available audio devices (recording, playback, logical and physical!)
|
||||
SDL_AudioStream *existing_streams; // a list of all existing SDL_AudioStreams.
|
||||
SDL_AudioDeviceID default_playback_device_id;
|
||||
SDL_AudioDeviceID default_recording_device_id;
|
||||
SDL_PendingAudioDeviceEvent pending_events;
|
||||
SDL_PendingAudioDeviceEvent *pending_events_tail;
|
||||
|
||||
// !!! FIXME: most (all?) of these don't have to be atomic.
|
||||
SDL_AtomicInt playback_device_count;
|
||||
SDL_AtomicInt recording_device_count;
|
||||
SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs.
|
||||
} SDL_AudioDriver;
|
||||
|
||||
struct SDL_AudioQueue; // forward decl.
|
||||
|
||||
struct SDL_AudioStream
|
||||
{
|
||||
SDL_Mutex* lock;
|
||||
|
||||
SDL_PropertiesID props;
|
||||
|
||||
SDL_AudioStreamCallback get_callback;
|
||||
void *get_callback_userdata;
|
||||
SDL_AudioStreamCallback put_callback;
|
||||
void *put_callback_userdata;
|
||||
|
||||
SDL_AudioSpec src_spec;
|
||||
SDL_AudioSpec dst_spec;
|
||||
int *src_chmap;
|
||||
int *dst_chmap;
|
||||
float freq_ratio;
|
||||
float gain;
|
||||
|
||||
struct SDL_AudioQueue* queue;
|
||||
|
||||
SDL_AudioSpec input_spec; // The spec of input data currently being processed
|
||||
int *input_chmap;
|
||||
int input_chmap_storage[SDL_MAX_CHANNELMAP_CHANNELS]; // !!! FIXME: this needs to grow if SDL ever supports more channels. But if it grows, we should probably be more clever about allocations.
|
||||
Sint64 resample_offset;
|
||||
|
||||
Uint8 *work_buffer; // used for scratch space during data conversion/resampling.
|
||||
size_t work_buffer_allocation;
|
||||
|
||||
bool simplified; // true if created via SDL_OpenAudioDeviceStream
|
||||
|
||||
SDL_LogicalAudioDevice *bound_device;
|
||||
SDL_AudioStream *next_binding;
|
||||
SDL_AudioStream *prev_binding;
|
||||
|
||||
SDL_AudioStream *prev; // linked list of all existing streams (so we can free them on shutdown).
|
||||
SDL_AudioStream *next; // linked list of all existing streams (so we can free them on shutdown).
|
||||
};
|
||||
|
||||
/* Logical devices are an abstraction in SDL3; you can open the same physical
|
||||
device multiple times, and each will result in an object with its own set
|
||||
of bound audio streams, etc, even though internally these are all processed
|
||||
as a group when mixing the final output for the physical device. */
|
||||
struct SDL_LogicalAudioDevice
|
||||
{
|
||||
// the unique instance ID of this device.
|
||||
SDL_AudioDeviceID instance_id;
|
||||
|
||||
// The physical device associated with this opened device.
|
||||
SDL_AudioDevice *physical_device;
|
||||
|
||||
// If whole logical device is paused (process no streams bound to this device).
|
||||
SDL_AtomicInt paused;
|
||||
|
||||
// Volume of the device output.
|
||||
float gain;
|
||||
|
||||
// double-linked list of all audio streams currently bound to this opened device.
|
||||
SDL_AudioStream *bound_streams;
|
||||
|
||||
// true if this was opened as a default device.
|
||||
bool opened_as_default;
|
||||
|
||||
// true if device was opened with SDL_OpenAudioDeviceStream (so it forbids binding changes, etc).
|
||||
bool simplified;
|
||||
|
||||
// If non-NULL, callback into the app that lets them access the final postmix buffer.
|
||||
SDL_AudioPostmixCallback postmix;
|
||||
|
||||
// App-supplied pointer for postmix callback.
|
||||
void *postmix_userdata;
|
||||
|
||||
// double-linked list of opened devices on the same physical device.
|
||||
SDL_LogicalAudioDevice *next;
|
||||
SDL_LogicalAudioDevice *prev;
|
||||
};
|
||||
|
||||
struct SDL_AudioDevice
|
||||
{
|
||||
// A mutex for locking access to this struct
|
||||
SDL_Mutex *lock;
|
||||
|
||||
// A condition variable to protect device close, where we can't hold the device lock forever.
|
||||
SDL_Condition *close_cond;
|
||||
|
||||
// Reference count of the device; logical devices, device threads, etc, add to this.
|
||||
SDL_AtomicInt refcount;
|
||||
|
||||
// These are, initially, set from current_audio, but we might swap them out with Zombie versions on disconnect/failure.
|
||||
bool (*WaitDevice)(SDL_AudioDevice *device);
|
||||
bool (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen);
|
||||
Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
|
||||
bool (*WaitRecordingDevice)(SDL_AudioDevice *device);
|
||||
int (*RecordDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
|
||||
void (*FlushRecording)(SDL_AudioDevice *device);
|
||||
|
||||
// human-readable name of the device. ("SoundBlaster Pro 16")
|
||||
char *name;
|
||||
|
||||
// the unique instance ID of this device.
|
||||
SDL_AudioDeviceID instance_id;
|
||||
|
||||
// a way for the backend to identify this device _when not opened_
|
||||
void *handle;
|
||||
|
||||
// The device's current audio specification
|
||||
SDL_AudioSpec spec;
|
||||
|
||||
// The size, in bytes, of the device's playback/recording buffer.
|
||||
int buffer_size;
|
||||
|
||||
// The device's channel map, or NULL for SDL default layout.
|
||||
int *chmap;
|
||||
|
||||
// The device's default audio specification
|
||||
SDL_AudioSpec default_spec;
|
||||
|
||||
// Number of sample frames the devices wants per-buffer.
|
||||
int sample_frames;
|
||||
|
||||
// Value to use for SDL_memset to silence a buffer in this device's format
|
||||
int silence_value;
|
||||
|
||||
// non-zero if we are signaling the audio thread to end.
|
||||
SDL_AtomicInt shutdown;
|
||||
|
||||
// non-zero if this was a disconnected device and we're waiting for it to be decommissioned.
|
||||
SDL_AtomicInt zombie;
|
||||
|
||||
// true if this is a recording device instead of an playback device
|
||||
bool recording;
|
||||
|
||||
// true if audio thread can skip silence/mix/convert stages and just do a basic memcpy.
|
||||
bool simple_copy;
|
||||
|
||||
// Scratch buffers used for mixing.
|
||||
Uint8 *work_buffer;
|
||||
Uint8 *mix_buffer;
|
||||
float *postmix_buffer;
|
||||
|
||||
// Size of work_buffer (and mix_buffer) in bytes.
|
||||
int work_buffer_size;
|
||||
|
||||
// A thread to feed the audio device
|
||||
SDL_Thread *thread;
|
||||
|
||||
// true if this physical device is currently opened by the backend.
|
||||
bool currently_opened;
|
||||
|
||||
// Data private to this driver
|
||||
struct SDL_PrivateAudioData *hidden;
|
||||
|
||||
// All logical devices associated with this physical device.
|
||||
SDL_LogicalAudioDevice *logical_devices;
|
||||
};
|
||||
|
||||
typedef struct AudioBootStrap
|
||||
{
|
||||
const char *name;
|
||||
const char *desc;
|
||||
bool (*init)(SDL_AudioDriverImpl *impl);
|
||||
bool demand_only; // if true: request explicitly, or it won't be available.
|
||||
} AudioBootStrap;
|
||||
|
||||
// Not all of these are available in a given build. Use #ifdefs, etc.
|
||||
extern AudioBootStrap PRIVATEAUDIO_bootstrap;
|
||||
extern AudioBootStrap PIPEWIRE_PREFERRED_bootstrap;
|
||||
extern AudioBootStrap PIPEWIRE_bootstrap;
|
||||
extern AudioBootStrap PULSEAUDIO_bootstrap;
|
||||
extern AudioBootStrap ALSA_bootstrap;
|
||||
extern AudioBootStrap JACK_bootstrap;
|
||||
extern AudioBootStrap SNDIO_bootstrap;
|
||||
extern AudioBootStrap NETBSDAUDIO_bootstrap;
|
||||
extern AudioBootStrap DSP_bootstrap;
|
||||
extern AudioBootStrap WASAPI_bootstrap;
|
||||
extern AudioBootStrap DSOUND_bootstrap;
|
||||
extern AudioBootStrap WINMM_bootstrap;
|
||||
extern AudioBootStrap HAIKUAUDIO_bootstrap;
|
||||
extern AudioBootStrap COREAUDIO_bootstrap;
|
||||
extern AudioBootStrap DISKAUDIO_bootstrap;
|
||||
extern AudioBootStrap DUMMYAUDIO_bootstrap;
|
||||
extern AudioBootStrap AAUDIO_bootstrap;
|
||||
extern AudioBootStrap OPENSLES_bootstrap;
|
||||
extern AudioBootStrap PS2AUDIO_bootstrap;
|
||||
extern AudioBootStrap PSPAUDIO_bootstrap;
|
||||
extern AudioBootStrap VITAAUD_bootstrap;
|
||||
extern AudioBootStrap N3DSAUDIO_bootstrap;
|
||||
extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
|
||||
extern AudioBootStrap QSAAUDIO_bootstrap;
|
||||
|
||||
#endif // SDL_sysaudio_h_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// RIFF WAVE files are little-endian
|
||||
|
||||
/*******************************************/
|
||||
// Define values for Microsoft WAVE format
|
||||
/*******************************************/
|
||||
// FOURCC
|
||||
#define RIFF 0x46464952 // "RIFF"
|
||||
#define WAVE 0x45564157 // "WAVE"
|
||||
#define FACT 0x74636166 // "fact"
|
||||
#define LIST 0x5453494c // "LIST"
|
||||
#define BEXT 0x74786562 // "bext"
|
||||
#define JUNK 0x4B4E554A // "JUNK"
|
||||
#define FMT 0x20746D66 // "fmt "
|
||||
#define DATA 0x61746164 // "data"
|
||||
// Format tags
|
||||
#define UNKNOWN_CODE 0x0000
|
||||
#define PCM_CODE 0x0001
|
||||
#define MS_ADPCM_CODE 0x0002
|
||||
#define IEEE_FLOAT_CODE 0x0003
|
||||
#define ALAW_CODE 0x0006
|
||||
#define MULAW_CODE 0x0007
|
||||
#define IMA_ADPCM_CODE 0x0011
|
||||
#define MPEG_CODE 0x0050
|
||||
#define MPEGLAYER3_CODE 0x0055
|
||||
#define EXTENSIBLE_CODE 0xFFFE
|
||||
|
||||
// Stores the WAVE format information.
|
||||
typedef struct WaveFormat
|
||||
{
|
||||
Uint16 formattag; // Raw value of the first field in the fmt chunk data.
|
||||
Uint16 encoding; // Actual encoding, possibly from the extensible header.
|
||||
Uint16 channels; // Number of channels.
|
||||
Uint32 frequency; // Sampling rate in Hz.
|
||||
Uint32 byterate; // Average bytes per second.
|
||||
Uint16 blockalign; // Bytes per block.
|
||||
Uint16 bitspersample; // Currently supported are 8, 16, 24, 32, and 4 for ADPCM.
|
||||
|
||||
/* Extra information size. Number of extra bytes starting at byte 18 in the
|
||||
* fmt chunk data. This is at least 22 for the extensible header.
|
||||
*/
|
||||
Uint16 extsize;
|
||||
|
||||
// Extensible WAVE header fields
|
||||
Uint16 validsamplebits;
|
||||
Uint32 samplesperblock; // For compressed formats. Can be zero. Actually 16 bits in the header.
|
||||
Uint32 channelmask;
|
||||
Uint8 subformat[16]; // A format GUID.
|
||||
} WaveFormat;
|
||||
|
||||
// Stores information on the fact chunk.
|
||||
typedef struct WaveFact
|
||||
{
|
||||
/* Represents the state of the fact chunk in the WAVE file.
|
||||
* Set to -1 if the fact chunk is invalid.
|
||||
* Set to 0 if the fact chunk is not present
|
||||
* Set to 1 if the fact chunk is present and valid.
|
||||
* Set to 2 if samplelength is going to be used as the number of sample frames.
|
||||
*/
|
||||
Sint32 status;
|
||||
|
||||
/* Version 1 of the RIFF specification calls the field in the fact chunk
|
||||
* dwFileSize. The Standards Update then calls it dwSampleLength and specifies
|
||||
* that it is 'the length of the data in samples'. WAVE files from Windows
|
||||
* with this chunk have it set to the samples per channel (sample frames).
|
||||
* This is useful to truncate compressed audio to a specific sample count
|
||||
* because a compressed block is usually decoded to a fixed number of
|
||||
* sample frames.
|
||||
*/
|
||||
Uint32 samplelength; // Raw sample length value from the fact chunk.
|
||||
} WaveFact;
|
||||
|
||||
// Generic struct for the chunks in the WAVE file.
|
||||
typedef struct WaveChunk
|
||||
{
|
||||
Uint32 fourcc; // FOURCC of the chunk.
|
||||
Uint32 length; // Size of the chunk data.
|
||||
Sint64 position; // Position of the data in the stream.
|
||||
Uint8 *data; // When allocated, this points to the chunk data. length is used for the memory allocation size.
|
||||
size_t size; // Number of bytes in data that could be read from the stream. Can be smaller than length.
|
||||
} WaveChunk;
|
||||
|
||||
// Controls how the size of the RIFF chunk affects the loading of a WAVE file.
|
||||
typedef enum WaveRiffSizeHint
|
||||
{
|
||||
RiffSizeNoHint,
|
||||
RiffSizeForce,
|
||||
RiffSizeIgnoreZero,
|
||||
RiffSizeIgnore,
|
||||
RiffSizeMaximum
|
||||
} WaveRiffSizeHint;
|
||||
|
||||
// Controls how a truncated WAVE file is handled.
|
||||
typedef enum WaveTruncationHint
|
||||
{
|
||||
TruncNoHint,
|
||||
TruncVeryStrict,
|
||||
TruncStrict,
|
||||
TruncDropFrame,
|
||||
TruncDropBlock
|
||||
} WaveTruncationHint;
|
||||
|
||||
// Controls how the fact chunk affects the loading of a WAVE file.
|
||||
typedef enum WaveFactChunkHint
|
||||
{
|
||||
FactNoHint,
|
||||
FactTruncate,
|
||||
FactStrict,
|
||||
FactIgnoreZero,
|
||||
FactIgnore
|
||||
} WaveFactChunkHint;
|
||||
|
||||
typedef struct WaveFile
|
||||
{
|
||||
WaveChunk chunk;
|
||||
WaveFormat format;
|
||||
WaveFact fact;
|
||||
|
||||
/* Number of sample frames that will be decoded. Calculated either with the
|
||||
* size of the data chunk or, if the appropriate hint is enabled, with the
|
||||
* sample length value from the fact chunk.
|
||||
*/
|
||||
Sint64 sampleframes;
|
||||
|
||||
void *decoderdata; // Some decoders require extra data for a state.
|
||||
|
||||
WaveRiffSizeHint riffhint;
|
||||
WaveTruncationHint trunchint;
|
||||
WaveFactChunkHint facthint;
|
||||
} WaveFile;
|
||||
@@ -0,0 +1,551 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_AAUDIO
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_aaudio.h"
|
||||
|
||||
#include "../../core/android/SDL_android.h"
|
||||
#include <aaudio/AAudio.h>
|
||||
|
||||
#if __ANDROID_API__ < 31
|
||||
#define AAUDIO_FORMAT_PCM_I32 4
|
||||
#endif
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
AAudioStream *stream;
|
||||
int num_buffers;
|
||||
Uint8 *mixbuf; // Raw mixing buffer
|
||||
size_t mixbuf_bytes; // num_buffers * device->buffer_size
|
||||
size_t callback_bytes;
|
||||
size_t processed_bytes;
|
||||
SDL_Semaphore *semaphore;
|
||||
SDL_AtomicInt error_callback_triggered;
|
||||
};
|
||||
|
||||
// Debug
|
||||
#if 0
|
||||
#define LOGI(...) SDL_Log(__VA_ARGS__);
|
||||
#else
|
||||
#define LOGI(...)
|
||||
#endif
|
||||
|
||||
#define LIB_AAUDIO_SO "libaaudio.so"
|
||||
|
||||
typedef struct AAUDIO_Data
|
||||
{
|
||||
SDL_SharedObject *handle;
|
||||
#define SDL_PROC(ret, func, params) ret (*func) params;
|
||||
#include "SDL_aaudiofuncs.h"
|
||||
} AAUDIO_Data;
|
||||
static AAUDIO_Data ctx;
|
||||
|
||||
static bool AAUDIO_LoadFunctions(AAUDIO_Data *data)
|
||||
{
|
||||
#define SDL_PROC(ret, func, params) \
|
||||
do { \
|
||||
data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \
|
||||
if (!data->func) { \
|
||||
return SDL_SetError("Couldn't load AAUDIO function %s: %s", #func, SDL_GetError()); \
|
||||
} \
|
||||
} while (0);
|
||||
#include "SDL_aaudiofuncs.h"
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error)
|
||||
{
|
||||
LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error));
|
||||
|
||||
// You MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here.
|
||||
// Just flag the device so we can kill it in PlayDevice instead.
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
|
||||
SDL_SetAtomicInt(&device->hidden->error_callback_triggered, (int) error); // AAUDIO_OK is zero, so !triggered means no error.
|
||||
SDL_SignalSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice.
|
||||
}
|
||||
|
||||
static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
size_t framesize = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
size_t callback_bytes = numFrames * framesize;
|
||||
size_t old_buffer_index = hidden->callback_bytes / device->buffer_size;
|
||||
|
||||
if (device->recording) {
|
||||
const Uint8 *input = (const Uint8 *)audioData;
|
||||
size_t available_bytes = hidden->mixbuf_bytes - (hidden->callback_bytes - hidden->processed_bytes);
|
||||
size_t size = SDL_min(available_bytes, callback_bytes);
|
||||
size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
|
||||
size_t end = (offset + size) % hidden->mixbuf_bytes;
|
||||
SDL_assert(size <= hidden->mixbuf_bytes);
|
||||
|
||||
//LOGI("Recorded %zu frames, %zu available, %zu max (%zu written, %zu read)", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->callback_bytes / framesize, hidden->processed_bytes / framesize);
|
||||
|
||||
if (offset <= end) {
|
||||
SDL_memcpy(&hidden->mixbuf[offset], input, size);
|
||||
} else {
|
||||
size_t partial = (hidden->mixbuf_bytes - offset);
|
||||
SDL_memcpy(&hidden->mixbuf[offset], &input[0], partial);
|
||||
SDL_memcpy(&hidden->mixbuf[0], &input[partial], end);
|
||||
}
|
||||
|
||||
SDL_MemoryBarrierRelease();
|
||||
hidden->callback_bytes += size;
|
||||
|
||||
if (size < callback_bytes) {
|
||||
LOGI("Audio recording overflow, dropped %zu frames", (callback_bytes - size) / framesize);
|
||||
}
|
||||
} else {
|
||||
Uint8 *output = (Uint8 *)audioData;
|
||||
size_t available_bytes = (hidden->processed_bytes - hidden->callback_bytes);
|
||||
size_t size = SDL_min(available_bytes, callback_bytes);
|
||||
size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
|
||||
size_t end = (offset + size) % hidden->mixbuf_bytes;
|
||||
SDL_assert(size <= hidden->mixbuf_bytes);
|
||||
|
||||
//LOGI("Playing %zu frames, %zu available, %zu max (%zu written, %zu read)", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->processed_bytes / framesize, hidden->callback_bytes / framesize);
|
||||
|
||||
SDL_MemoryBarrierAcquire();
|
||||
if (offset <= end) {
|
||||
SDL_memcpy(output, &hidden->mixbuf[offset], size);
|
||||
} else {
|
||||
size_t partial = (hidden->mixbuf_bytes - offset);
|
||||
SDL_memcpy(&output[0], &hidden->mixbuf[offset], partial);
|
||||
SDL_memcpy(&output[partial], &hidden->mixbuf[0], end);
|
||||
}
|
||||
hidden->callback_bytes += size;
|
||||
|
||||
if (size < callback_bytes) {
|
||||
LOGI("Audio playback underflow, missed %zu frames", (callback_bytes - size) / framesize);
|
||||
SDL_memset(&output[size], device->silence_value, (callback_bytes - size));
|
||||
}
|
||||
}
|
||||
|
||||
size_t new_buffer_index = hidden->callback_bytes / device->buffer_size;
|
||||
while (old_buffer_index < new_buffer_index) {
|
||||
// Trigger audio processing
|
||||
SDL_SignalSemaphore(hidden->semaphore);
|
||||
++old_buffer_index;
|
||||
}
|
||||
|
||||
return AAUDIO_CALLBACK_RESULT_CONTINUE;
|
||||
}
|
||||
|
||||
static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
|
||||
return &hidden->mixbuf[offset];
|
||||
}
|
||||
|
||||
static bool AAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
// this semaphore won't fire when the app is in the background (AAUDIO_PauseDevices was called).
|
||||
if (SDL_WaitSemaphoreTimeout(device->hidden->semaphore, 100)) {
|
||||
return true; // semaphore was signaled, let's go!
|
||||
}
|
||||
// Still waiting on the semaphore (or the system), check other things then wait again.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool BuildAAudioStream(SDL_AudioDevice *device);
|
||||
|
||||
static bool RecoverAAudioDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
|
||||
// attempt to build a new stream, in case there's a new default device.
|
||||
ctx.AAudioStream_requestStop(hidden->stream);
|
||||
ctx.AAudioStream_close(hidden->stream);
|
||||
hidden->stream = NULL;
|
||||
|
||||
SDL_aligned_free(hidden->mixbuf);
|
||||
hidden->mixbuf = NULL;
|
||||
|
||||
SDL_DestroySemaphore(hidden->semaphore);
|
||||
hidden->semaphore = NULL;
|
||||
|
||||
const int prev_sample_frames = device->sample_frames;
|
||||
SDL_AudioSpec prevspec;
|
||||
SDL_copyp(&prevspec, &device->spec);
|
||||
|
||||
if (!BuildAAudioStream(device)) {
|
||||
return false; // oh well, we tried.
|
||||
}
|
||||
|
||||
// we don't know the new device spec until we open the new device, so we saved off the old one and force it back
|
||||
// so SDL_AudioDeviceFormatChanged can set up all the important state if necessary and then set it back to the new spec.
|
||||
const int new_sample_frames = device->sample_frames;
|
||||
SDL_AudioSpec newspec;
|
||||
SDL_copyp(&newspec, &device->spec);
|
||||
|
||||
device->sample_frames = prev_sample_frames;
|
||||
SDL_copyp(&device->spec, &prevspec);
|
||||
if (!SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames)) {
|
||||
return false; // ugh
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
|
||||
// AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
|
||||
const aaudio_result_t err = (aaudio_result_t) SDL_GetAtomicInt(&hidden->error_callback_triggered);
|
||||
if (err) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "aaudio: Audio device triggered error %d (%s)", (int) err, ctx.AAudio_convertResultToText(err));
|
||||
|
||||
if (!RecoverAAudioDevice(device)) {
|
||||
return false; // oh well, we went down hard.
|
||||
}
|
||||
} else {
|
||||
SDL_MemoryBarrierRelease();
|
||||
hidden->processed_bytes += buflen;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int AAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
|
||||
// AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
|
||||
if (SDL_GetAtomicInt(&hidden->error_callback_triggered)) {
|
||||
SDL_SetAtomicInt(&hidden->error_callback_triggered, 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_assert(buflen == device->buffer_size); // If this isn't true, we need to change semaphore trigger logic and account for wrapping copies here
|
||||
size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
|
||||
SDL_MemoryBarrierAcquire();
|
||||
SDL_memcpy(buffer, &hidden->mixbuf[offset], buflen);
|
||||
hidden->processed_bytes += buflen;
|
||||
return buflen;
|
||||
}
|
||||
|
||||
static void AAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
LOGI(__func__);
|
||||
|
||||
if (hidden) {
|
||||
if (hidden->stream) {
|
||||
ctx.AAudioStream_requestStop(hidden->stream);
|
||||
// !!! FIXME: do we have to wait for the state to change to make sure all buffered audio has played, or will close do this (or will the system do this after the close)?
|
||||
// !!! FIXME: also, will this definitely wait for a running data callback to finish, and then stop the callback from firing again?
|
||||
ctx.AAudioStream_close(hidden->stream);
|
||||
}
|
||||
|
||||
if (hidden->semaphore) {
|
||||
SDL_DestroySemaphore(hidden->semaphore);
|
||||
}
|
||||
|
||||
SDL_aligned_free(hidden->mixbuf);
|
||||
SDL_free(hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool BuildAAudioStream(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
const bool recording = device->recording;
|
||||
aaudio_result_t res;
|
||||
|
||||
SDL_SetAtomicInt(&hidden->error_callback_triggered, 0);
|
||||
|
||||
AAudioStreamBuilder *builder = NULL;
|
||||
res = ctx.AAudio_createStreamBuilder(&builder);
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudio_createStreamBuilder %d", res);
|
||||
return SDL_SetError("SDL Failed AAudio_createStreamBuilder %d", res);
|
||||
} else if (!builder) {
|
||||
LOGI("SDL Failed AAudio_createStreamBuilder - builder NULL");
|
||||
return SDL_SetError("SDL Failed AAudio_createStreamBuilder - builder NULL");
|
||||
}
|
||||
|
||||
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||
const int aaudio_device_id = (int) ((size_t) device->handle);
|
||||
LOGI("Opening device id %d", aaudio_device_id);
|
||||
ctx.AAudioStreamBuilder_setDeviceId(builder, aaudio_device_id);
|
||||
#endif
|
||||
|
||||
aaudio_format_t format;
|
||||
if ((device->spec.format == SDL_AUDIO_S32) && (SDL_GetAndroidSDKVersion() >= 31)) {
|
||||
format = AAUDIO_FORMAT_PCM_I32;
|
||||
} else if (device->spec.format == SDL_AUDIO_F32) {
|
||||
format = AAUDIO_FORMAT_PCM_FLOAT;
|
||||
} else {
|
||||
format = AAUDIO_FORMAT_PCM_I16; // sint16 is a safe bet for everything else.
|
||||
}
|
||||
ctx.AAudioStreamBuilder_setFormat(builder, format);
|
||||
ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq);
|
||||
ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels);
|
||||
|
||||
const aaudio_direction_t direction = (recording ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT);
|
||||
ctx.AAudioStreamBuilder_setDirection(builder, direction);
|
||||
ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, device);
|
||||
ctx.AAudioStreamBuilder_setDataCallback(builder, AAUDIO_dataCallback, device);
|
||||
// Some devices have flat sounding audio when low latency mode is enabled, but this is a better experience for most people
|
||||
if (SDL_GetHintBoolean(SDL_HINT_ANDROID_LOW_LATENCY_AUDIO, true)) {
|
||||
SDL_Log("Low latency audio enabled");
|
||||
ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
|
||||
} else {
|
||||
SDL_Log("Low latency audio disabled");
|
||||
}
|
||||
|
||||
LOGI("AAudio Try to open %u hz %s %u channels samples %u",
|
||||
device->spec.freq, SDL_GetAudioFormatName(device->spec.format),
|
||||
device->spec.channels, device->sample_frames);
|
||||
|
||||
res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream);
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res);
|
||||
ctx.AAudioStreamBuilder_delete(builder);
|
||||
return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
|
||||
}
|
||||
ctx.AAudioStreamBuilder_delete(builder);
|
||||
|
||||
device->sample_frames = (int)ctx.AAudioStream_getFramesPerDataCallback(hidden->stream);
|
||||
if (device->sample_frames == AAUDIO_UNSPECIFIED) {
|
||||
// We'll get variable frames in the callback, make sure we have at least half a buffer available
|
||||
device->sample_frames = (int)ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 2;
|
||||
}
|
||||
|
||||
device->spec.freq = ctx.AAudioStream_getSampleRate(hidden->stream);
|
||||
device->spec.channels = ctx.AAudioStream_getChannelCount(hidden->stream);
|
||||
|
||||
format = ctx.AAudioStream_getFormat(hidden->stream);
|
||||
if (format == AAUDIO_FORMAT_PCM_I16) {
|
||||
device->spec.format = SDL_AUDIO_S16;
|
||||
} else if (format == AAUDIO_FORMAT_PCM_I32) {
|
||||
device->spec.format = SDL_AUDIO_S32;
|
||||
} else if (format == AAUDIO_FORMAT_PCM_FLOAT) {
|
||||
device->spec.format = SDL_AUDIO_F32;
|
||||
} else {
|
||||
return SDL_SetError("Got unexpected audio format %d from AAudioStream_getFormat", (int) format);
|
||||
}
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
// Allocate a triple buffered mixing buffer
|
||||
// Two buffers can be in the process of being filled while the third is being read
|
||||
hidden->num_buffers = 3;
|
||||
hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size);
|
||||
hidden->mixbuf = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), hidden->mixbuf_bytes);
|
||||
if (!hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
hidden->processed_bytes = 0;
|
||||
hidden->callback_bytes = 0;
|
||||
|
||||
hidden->semaphore = SDL_CreateSemaphore(recording ? 0 : hidden->num_buffers);
|
||||
if (!hidden->semaphore) {
|
||||
LOGI("SDL Failed SDL_CreateSemaphore %s recording:%d", SDL_GetError(), recording);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("AAudio Actually opened %u hz %s %u channels samples %u, buffers %d",
|
||||
device->spec.freq, SDL_GetAudioFormatName(device->spec.format),
|
||||
device->spec.channels, device->sample_frames, hidden->num_buffers);
|
||||
|
||||
res = ctx.AAudioStream_requestStart(hidden->stream);
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudioStream_requestStart %d recording:%d", res, recording);
|
||||
return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
|
||||
}
|
||||
|
||||
LOGI("SDL AAudioStream_requestStart OK");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// !!! FIXME: make this non-blocking!
|
||||
static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted)
|
||||
{
|
||||
SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1);
|
||||
}
|
||||
|
||||
static bool AAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||
SDL_assert(device->handle); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero.
|
||||
#endif
|
||||
|
||||
LOGI(__func__);
|
||||
|
||||
if (device->recording) {
|
||||
// !!! FIXME: make this non-blocking!
|
||||
SDL_AtomicInt permission_response;
|
||||
SDL_SetAtomicInt(&permission_response, 0);
|
||||
if (!SDL_RequestAndroidPermission("android.permission.RECORD_AUDIO", RequestAndroidPermissionBlockingCallback, &permission_response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (SDL_GetAtomicInt(&permission_response) == 0) {
|
||||
SDL_Delay(10);
|
||||
}
|
||||
|
||||
if (SDL_GetAtomicInt(&permission_response) < 0) {
|
||||
LOGI("This app doesn't have RECORD_AUDIO permission");
|
||||
return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
|
||||
}
|
||||
}
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return BuildAAudioStream(device);
|
||||
}
|
||||
|
||||
static bool PauseOneDevice(SDL_AudioDevice *device, void *userdata)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden;
|
||||
if (hidden) {
|
||||
if (hidden->stream) {
|
||||
aaudio_result_t res;
|
||||
|
||||
if (device->recording) {
|
||||
// Pause() isn't implemented for recording, use Stop()
|
||||
res = ctx.AAudioStream_requestStop(hidden->stream);
|
||||
} else {
|
||||
res = ctx.AAudioStream_requestPause(hidden->stream);
|
||||
}
|
||||
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudioStream_requestPause %d", res);
|
||||
SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
|
||||
}
|
||||
}
|
||||
}
|
||||
return false; // keep enumerating.
|
||||
}
|
||||
|
||||
// Pause (block) all non already paused audio devices by taking their mixer lock
|
||||
void AAUDIO_PauseDevices(void)
|
||||
{
|
||||
if (ctx.handle) { // AAUDIO driver is used?
|
||||
(void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneDevice, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Resume (unblock) all non already paused audio devices by releasing their mixer lock
|
||||
static bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata)
|
||||
{
|
||||
struct SDL_PrivateAudioData *hidden = device->hidden;
|
||||
if (hidden) {
|
||||
if (hidden->stream) {
|
||||
aaudio_result_t res = ctx.AAudioStream_requestStart(hidden->stream);
|
||||
if (res != AAUDIO_OK) {
|
||||
LOGI("SDL Failed AAudioStream_requestStart %d", res);
|
||||
SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
|
||||
}
|
||||
}
|
||||
}
|
||||
return false; // keep enumerating.
|
||||
}
|
||||
|
||||
void AAUDIO_ResumeDevices(void)
|
||||
{
|
||||
if (ctx.handle) { // AAUDIO driver is used?
|
||||
(void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneDevice, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void AAUDIO_Deinitialize(void)
|
||||
{
|
||||
Android_StopAudioHotplug();
|
||||
|
||||
LOGI(__func__);
|
||||
if (ctx.handle) {
|
||||
SDL_UnloadObject(ctx.handle);
|
||||
}
|
||||
SDL_zero(ctx);
|
||||
LOGI("End AAUDIO %s", SDL_GetError());
|
||||
}
|
||||
|
||||
|
||||
static bool AAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
LOGI(__func__);
|
||||
|
||||
/* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release,
|
||||
* so don't use it until 8.1.
|
||||
*
|
||||
* See https://github.com/google/oboe/issues/40 for more information.
|
||||
*/
|
||||
if (SDL_GetAndroidSDKVersion() < 27) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_zero(ctx);
|
||||
|
||||
ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO);
|
||||
if (!ctx.handle) {
|
||||
LOGI("SDL couldn't find " LIB_AAUDIO_SO);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AAUDIO_LoadFunctions(&ctx)) {
|
||||
SDL_UnloadObject(ctx.handle);
|
||||
SDL_zero(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
impl->ThreadInit = Android_AudioThreadInit;
|
||||
impl->Deinitialize = AAUDIO_Deinitialize;
|
||||
impl->OpenDevice = AAUDIO_OpenDevice;
|
||||
impl->CloseDevice = AAUDIO_CloseDevice;
|
||||
impl->WaitDevice = AAUDIO_WaitDevice;
|
||||
impl->PlayDevice = AAUDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = AAUDIO_GetDeviceBuf;
|
||||
impl->WaitRecordingDevice = AAUDIO_WaitDevice;
|
||||
impl->RecordDevice = AAUDIO_RecordDevice;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
|
||||
impl->DetectDevices = Android_StartAudioHotplug;
|
||||
#else
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
impl->OnlyHasDefaultRecordingDevice = true;
|
||||
#endif
|
||||
|
||||
LOGI("SDL AAUDIO_Init OK");
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap AAUDIO_bootstrap = {
|
||||
"AAudio", "AAudio audio driver", AAUDIO_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_AAUDIO
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_aaudio_h_
|
||||
#define SDL_aaudio_h_
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_AAUDIO
|
||||
|
||||
extern void AAUDIO_ResumeDevices(void);
|
||||
extern void AAUDIO_PauseDevices(void);
|
||||
|
||||
#else
|
||||
|
||||
#define AAUDIO_ResumeDevices()
|
||||
#define AAUDIO_PauseDevices()
|
||||
|
||||
#endif
|
||||
|
||||
#endif // SDL_aaudio_h_
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright , (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#define SDL_PROC_UNUSED(ret, func, params)
|
||||
|
||||
SDL_PROC(const char *, AAudio_convertResultToText, (aaudio_result_t returnCode))
|
||||
SDL_PROC(const char *, AAudio_convertStreamStateToText, (aaudio_stream_state_t state))
|
||||
SDL_PROC(aaudio_result_t, AAudio_createStreamBuilder, (AAudioStreamBuilder * *builder))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setDeviceId, (AAudioStreamBuilder * builder, int32_t deviceId))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setSampleRate, (AAudioStreamBuilder * builder, int32_t sampleRate))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setChannelCount, (AAudioStreamBuilder * builder, int32_t channelCount))
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSamplesPerFrame, (AAudioStreamBuilder * builder, int32_t samplesPerFrame))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aaudio_format_t format))
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction))
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode))
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) // API 29
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) // API 28
|
||||
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) // API 30
|
||||
SDL_PROC(void, AAudioStreamBuilder_setDataCallback, (AAudioStreamBuilder * builder, AAudioStream_dataCallback callback, void *userData))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setFramesPerDataCallback, (AAudioStreamBuilder * builder, int32_t numFrames))
|
||||
SDL_PROC(void, AAudioStreamBuilder_setErrorCallback, (AAudioStreamBuilder * builder, AAudioStream_errorCallback callback, void *userData))
|
||||
SDL_PROC(aaudio_result_t, AAudioStreamBuilder_openStream, (AAudioStreamBuilder * builder, AAudioStream **stream))
|
||||
SDL_PROC(aaudio_result_t, AAudioStreamBuilder_delete, (AAudioStreamBuilder * builder))
|
||||
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_release, (AAudioStream * stream)) // API 30
|
||||
SDL_PROC(aaudio_result_t, AAudioStream_close, (AAudioStream * stream))
|
||||
SDL_PROC(aaudio_result_t, AAudioStream_requestStart, (AAudioStream * stream))
|
||||
SDL_PROC(aaudio_result_t, AAudioStream_requestPause, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_requestFlush, (AAudioStream * stream))
|
||||
SDL_PROC(aaudio_result_t, AAudioStream_requestStop, (AAudioStream * stream))
|
||||
SDL_PROC(aaudio_stream_state_t, AAudioStream_getState, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_waitForStateChange, (AAudioStream * stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds))
|
||||
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_read, (AAudioStream * stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
|
||||
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_write, (AAudioStream * stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
|
||||
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_setBufferSizeInFrames, (AAudioStream * stream, int32_t numFrames))
|
||||
SDL_PROC_UNUSED(int32_t, AAudioStream_getBufferSizeInFrames, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(int32_t, AAudioStream_getFramesPerBurst, (AAudioStream * stream))
|
||||
SDL_PROC(int32_t, AAudioStream_getBufferCapacityInFrames, (AAudioStream * stream))
|
||||
SDL_PROC(int32_t, AAudioStream_getFramesPerDataCallback, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(int32_t, AAudioStream_getXRunCount, (AAudioStream * stream))
|
||||
SDL_PROC(int32_t, AAudioStream_getSampleRate, (AAudioStream * stream))
|
||||
SDL_PROC(int32_t, AAudioStream_getChannelCount, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(int32_t, AAudioStream_getSamplesPerFrame, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(int32_t, AAudioStream_getDeviceId, (AAudioStream * stream))
|
||||
SDL_PROC(aaudio_format_t, AAudioStream_getFormat, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(aaudio_sharing_mode_t, AAudioStream_getSharingMode, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(aaudio_performance_mode_t, AAudioStream_getPerformanceMode, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(aaudio_direction_t, AAudioStream_getDirection, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesWritten, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesRead, (AAudioStream * stream))
|
||||
SDL_PROC_UNUSED(aaudio_session_id_t, AAudioStream_getSessionId, (AAudioStream * stream)) // API 28
|
||||
SDL_PROC(aaudio_result_t, AAudioStream_getTimestamp, (AAudioStream * stream, clockid_t clockid, int64_t *framePosition, int64_t *timeNanoseconds))
|
||||
SDL_PROC_UNUSED(aaudio_usage_t, AAudioStream_getUsage, (AAudioStream * stream)) // API 28
|
||||
SDL_PROC_UNUSED(aaudio_content_type_t, AAudioStream_getContentType, (AAudioStream * stream)) // API 28
|
||||
SDL_PROC_UNUSED(aaudio_input_preset_t, AAudioStream_getInputPreset, (AAudioStream * stream)) // API 28
|
||||
SDL_PROC_UNUSED(aaudio_allowed_capture_policy_t, AAudioStream_getAllowedCapturePolicy, (AAudioStream * stream)) // API 29
|
||||
SDL_PROC_UNUSED(bool, AAudioStream_isPrivacySensitive, (AAudioStream * stream)) // API 30
|
||||
|
||||
#undef SDL_PROC
|
||||
#undef SDL_PROC_UNUSED
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_ALSA_audio_h_
|
||||
#define SDL_ALSA_audio_h_
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#define SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX 8
|
||||
#define SDL_AUDIO_ALSA__SDL_CHMAPS_N 9 // from 0 channels to 8 channels
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The audio device handle
|
||||
snd_pcm_t *pcm;
|
||||
|
||||
// Raw mixing buffer
|
||||
Uint8 *mixbuf;
|
||||
};
|
||||
|
||||
#endif // SDL_ALSA_audio_h_
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_coreaudio_h_
|
||||
#define SDL_coreaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#ifndef SDL_PLATFORM_IOS
|
||||
#define MACOSX_COREAUDIO
|
||||
#endif
|
||||
|
||||
#ifdef MACOSX_COREAUDIO
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
#else
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <UIKit/UIApplication.h>
|
||||
#endif
|
||||
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
|
||||
// Things named "Master" were renamed to "Main" in macOS 12.0's SDK.
|
||||
#ifdef MACOSX_COREAUDIO
|
||||
#include <AvailabilityMacros.h>
|
||||
#ifndef MAC_OS_VERSION_12_0
|
||||
#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
SDL_Thread *thread;
|
||||
AudioQueueRef audioQueue;
|
||||
int numAudioBuffers;
|
||||
AudioQueueBufferRef *audioBuffer;
|
||||
AudioQueueBufferRef current_buffer;
|
||||
AudioStreamBasicDescription strdesc;
|
||||
SDL_Semaphore *ready_semaphore;
|
||||
char *thread_error;
|
||||
#ifdef MACOSX_COREAUDIO
|
||||
AudioDeviceID deviceID;
|
||||
#else
|
||||
bool interrupted;
|
||||
CFTypeRef interruption_listener;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // SDL_coreaudio_h_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,680 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_DSOUND
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_directsound.h"
|
||||
#include <mmreg.h>
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
#include "../../core/windows/SDL_immdevice.h"
|
||||
#endif
|
||||
|
||||
#ifndef WAVE_FORMAT_IEEE_FLOAT
|
||||
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
|
||||
#endif
|
||||
|
||||
// For Vista+, we can enumerate DSound devices with IMMDevice
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
static bool SupportsIMMDevice = false;
|
||||
#endif
|
||||
|
||||
// DirectX function pointers for audio
|
||||
static SDL_SharedObject *DSoundDLL = NULL;
|
||||
typedef HRESULT(WINAPI *fnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
|
||||
typedef HRESULT(WINAPI *fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
|
||||
typedef HRESULT(WINAPI *fnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN);
|
||||
typedef HRESULT(WINAPI *fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
|
||||
typedef HRESULT(WINAPI *fnGetDeviceID)(LPCGUID, LPGUID);
|
||||
static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL;
|
||||
static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL;
|
||||
static fnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL;
|
||||
static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL;
|
||||
static fnGetDeviceID pGetDeviceID = NULL;
|
||||
|
||||
#include <initguid.h>
|
||||
DEFINE_GUID(SDL_DSDEVID_DefaultPlayback, 0xdef00000, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
|
||||
DEFINE_GUID(SDL_DSDEVID_DefaultCapture, 0xdef00001, 0x9c6d, 0x47ed, 0xaa, 0xf1, 0x4d, 0xda, 0x8f, 0x2b, 0x5c, 0x03);
|
||||
|
||||
static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
|
||||
static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
|
||||
|
||||
static void DSOUND_Unload(void)
|
||||
{
|
||||
pDirectSoundCreate8 = NULL;
|
||||
pDirectSoundEnumerateW = NULL;
|
||||
pDirectSoundCaptureCreate8 = NULL;
|
||||
pDirectSoundCaptureEnumerateW = NULL;
|
||||
pGetDeviceID = NULL;
|
||||
|
||||
if (DSoundDLL) {
|
||||
SDL_UnloadObject(DSoundDLL);
|
||||
DSoundDLL = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool DSOUND_Load(void)
|
||||
{
|
||||
bool loaded = false;
|
||||
|
||||
DSOUND_Unload();
|
||||
|
||||
DSoundDLL = SDL_LoadObject("DSOUND.DLL");
|
||||
if (!DSoundDLL) {
|
||||
SDL_SetError("DirectSound: failed to load DSOUND.DLL");
|
||||
} else {
|
||||
// Now make sure we have DirectX 8 or better...
|
||||
#define DSOUNDLOAD(f) \
|
||||
{ \
|
||||
p##f = (fn##f)SDL_LoadFunction(DSoundDLL, #f); \
|
||||
if (!p##f) \
|
||||
loaded = false; \
|
||||
}
|
||||
loaded = true; // will reset if necessary.
|
||||
DSOUNDLOAD(DirectSoundCreate8);
|
||||
DSOUNDLOAD(DirectSoundEnumerateW);
|
||||
DSOUNDLOAD(DirectSoundCaptureCreate8);
|
||||
DSOUNDLOAD(DirectSoundCaptureEnumerateW);
|
||||
DSOUNDLOAD(GetDeviceID);
|
||||
#undef DSOUNDLOAD
|
||||
|
||||
if (!loaded) {
|
||||
SDL_SetError("DirectSound: System doesn't appear to have DX8.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
DSOUND_Unload();
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
static bool SetDSerror(const char *function, int code)
|
||||
{
|
||||
const char *error;
|
||||
|
||||
switch (code) {
|
||||
case E_NOINTERFACE:
|
||||
error = "Unsupported interface -- Is DirectX 8.0 or later installed?";
|
||||
break;
|
||||
case DSERR_ALLOCATED:
|
||||
error = "Audio device in use";
|
||||
break;
|
||||
case DSERR_BADFORMAT:
|
||||
error = "Unsupported audio format";
|
||||
break;
|
||||
case DSERR_BUFFERLOST:
|
||||
error = "Mixing buffer was lost";
|
||||
break;
|
||||
case DSERR_CONTROLUNAVAIL:
|
||||
error = "Control requested is not available";
|
||||
break;
|
||||
case DSERR_INVALIDCALL:
|
||||
error = "Invalid call for the current state";
|
||||
break;
|
||||
case DSERR_INVALIDPARAM:
|
||||
error = "Invalid parameter";
|
||||
break;
|
||||
case DSERR_NODRIVER:
|
||||
error = "No audio device found";
|
||||
break;
|
||||
case DSERR_OUTOFMEMORY:
|
||||
error = "Out of memory";
|
||||
break;
|
||||
case DSERR_PRIOLEVELNEEDED:
|
||||
error = "Caller doesn't have priority";
|
||||
break;
|
||||
case DSERR_UNSUPPORTED:
|
||||
error = "Function not supported";
|
||||
break;
|
||||
default:
|
||||
error = "Unknown DirectSound error";
|
||||
break;
|
||||
}
|
||||
|
||||
return SDL_SetError("%s: %s (0x%x)", function, error, code);
|
||||
}
|
||||
|
||||
static void DSOUND_FreeDeviceHandle(SDL_AudioDevice *device)
|
||||
{
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
if (SupportsIMMDevice) {
|
||||
SDL_IMMDevice_FreeDeviceHandle(device);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
SDL_free(device->handle);
|
||||
}
|
||||
}
|
||||
|
||||
// FindAllDevs is presumably only used on WinXP; Vista and later can use IMMDevice for better results.
|
||||
typedef struct FindAllDevsData
|
||||
{
|
||||
bool recording;
|
||||
SDL_AudioDevice **default_device;
|
||||
LPCGUID default_device_guid;
|
||||
} FindAllDevsData;
|
||||
|
||||
static BOOL CALLBACK FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID userdata)
|
||||
{
|
||||
FindAllDevsData *data = (FindAllDevsData *) userdata;
|
||||
if (guid != NULL) { // skip default device
|
||||
char *str = WIN_LookupAudioDeviceName(desc, guid);
|
||||
if (str) {
|
||||
LPGUID cpyguid = (LPGUID)SDL_malloc(sizeof(GUID));
|
||||
if (cpyguid) {
|
||||
SDL_copyp(cpyguid, guid);
|
||||
|
||||
/* Note that spec is NULL, because we are required to connect to the
|
||||
* device before getting the channel mask and output format, making
|
||||
* this information inaccessible at enumeration time
|
||||
*/
|
||||
SDL_AudioDevice *device = SDL_AddAudioDevice(data->recording, str, NULL, cpyguid);
|
||||
if (device && data->default_device && data->default_device_guid) {
|
||||
if (SDL_memcmp(cpyguid, data->default_device_guid, sizeof (GUID)) == 0) {
|
||||
*data->default_device = device;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_free(str); // SDL_AddAudioDevice() makes a copy of this string.
|
||||
}
|
||||
}
|
||||
return TRUE; // keep enumerating.
|
||||
}
|
||||
|
||||
static void DSOUND_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
if (SupportsIMMDevice) {
|
||||
SDL_IMMDevice_EnumerateEndpoints(default_playback, default_recording);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
// Without IMMDevice, you can enumerate devices and figure out the default devices,
|
||||
// but you won't get device hotplug or default device change notifications. But this is
|
||||
// only for WinXP; Windows Vista and later should be using IMMDevice.
|
||||
FindAllDevsData data;
|
||||
GUID guid;
|
||||
|
||||
data.recording = true;
|
||||
data.default_device = default_recording;
|
||||
data.default_device_guid = (pGetDeviceID(&SDL_DSDEVID_DefaultCapture, &guid) == DS_OK) ? &guid : NULL;
|
||||
pDirectSoundCaptureEnumerateW(FindAllDevs, &data);
|
||||
|
||||
data.recording = false;
|
||||
data.default_device = default_playback;
|
||||
data.default_device_guid = (pGetDeviceID(&SDL_DSDEVID_DefaultPlayback, &guid) == DS_OK) ? &guid : NULL;
|
||||
pDirectSoundEnumerateW(FindAllDevs, &data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static bool DSOUND_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
/* Semi-busy wait, since we have no way of getting play notification
|
||||
on a primary mixing buffer located in hardware (DirectX 5.0)
|
||||
*/
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
DWORD status = 0;
|
||||
DWORD cursor = 0;
|
||||
DWORD junk = 0;
|
||||
HRESULT result = DS_OK;
|
||||
|
||||
// Try to restore a lost sound buffer
|
||||
IDirectSoundBuffer_GetStatus(device->hidden->mixbuf, &status);
|
||||
if (status & DSBSTATUS_BUFFERLOST) {
|
||||
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
|
||||
} else if (!(status & DSBSTATUS_PLAYING)) {
|
||||
result = IDirectSoundBuffer_Play(device->hidden->mixbuf, 0, 0, DSBPLAY_LOOPING);
|
||||
} else {
|
||||
// Find out where we are playing
|
||||
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, &junk, &cursor);
|
||||
if ((result == DS_OK) && ((cursor / device->buffer_size) != device->hidden->lastchunk)) {
|
||||
break; // ready for next chunk!
|
||||
}
|
||||
}
|
||||
|
||||
if ((result != DS_OK) && (result != DSERR_BUFFERLOST)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Delay(1); // not ready yet; sleep a bit.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DSOUND_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
// Unlock the buffer, allowing it to play
|
||||
SDL_assert(buflen == device->buffer_size);
|
||||
if (IDirectSoundBuffer_Unlock(device->hidden->mixbuf, (LPVOID) buffer, buflen, NULL, 0) != DS_OK) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *DSOUND_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
DWORD cursor = 0;
|
||||
DWORD junk = 0;
|
||||
HRESULT result = DS_OK;
|
||||
|
||||
SDL_assert(*buffer_size == device->buffer_size);
|
||||
|
||||
// Figure out which blocks to fill next
|
||||
device->hidden->locked_buf = NULL;
|
||||
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf,
|
||||
&junk, &cursor);
|
||||
if (result == DSERR_BUFFERLOST) {
|
||||
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
|
||||
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf,
|
||||
&junk, &cursor);
|
||||
}
|
||||
if (result != DS_OK) {
|
||||
SetDSerror("DirectSound GetCurrentPosition", result);
|
||||
return NULL;
|
||||
}
|
||||
cursor /= device->buffer_size;
|
||||
#ifdef DEBUG_SOUND
|
||||
// Detect audio dropouts
|
||||
{
|
||||
DWORD spot = cursor;
|
||||
if (spot < device->hidden->lastchunk) {
|
||||
spot += device->hidden->num_buffers;
|
||||
}
|
||||
if (spot > device->hidden->lastchunk + 1) {
|
||||
fprintf(stderr, "Audio dropout, missed %d fragments\n",
|
||||
(spot - (device->hidden->lastchunk + 1)));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
device->hidden->lastchunk = cursor;
|
||||
cursor = (cursor + 1) % device->hidden->num_buffers;
|
||||
cursor *= device->buffer_size;
|
||||
|
||||
// Lock the audio buffer
|
||||
DWORD rawlen = 0;
|
||||
result = IDirectSoundBuffer_Lock(device->hidden->mixbuf, cursor,
|
||||
device->buffer_size,
|
||||
(LPVOID *)&device->hidden->locked_buf,
|
||||
&rawlen, NULL, &junk, 0);
|
||||
if (result == DSERR_BUFFERLOST) {
|
||||
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
|
||||
result = IDirectSoundBuffer_Lock(device->hidden->mixbuf, cursor,
|
||||
device->buffer_size,
|
||||
(LPVOID *)&device->hidden->locked_buf, &rawlen, NULL,
|
||||
&junk, 0);
|
||||
}
|
||||
if (result != DS_OK) {
|
||||
SetDSerror("DirectSound Lock", result);
|
||||
return NULL;
|
||||
}
|
||||
return device->hidden->locked_buf;
|
||||
}
|
||||
|
||||
static bool DSOUND_WaitRecordingDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
DWORD junk, cursor;
|
||||
if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) {
|
||||
return false;
|
||||
} else if ((cursor / device->buffer_size) != h->lastchunk) {
|
||||
break;
|
||||
}
|
||||
SDL_Delay(1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int DSOUND_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
DWORD ptr1len, ptr2len;
|
||||
VOID *ptr1, *ptr2;
|
||||
|
||||
SDL_assert(buflen == device->buffer_size);
|
||||
|
||||
if (IDirectSoundCaptureBuffer_Lock(h->capturebuf, h->lastchunk * buflen, buflen, &ptr1, &ptr1len, &ptr2, &ptr2len, 0) != DS_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
SDL_assert(ptr1len == (DWORD)buflen);
|
||||
SDL_assert(ptr2 == NULL);
|
||||
SDL_assert(ptr2len == 0);
|
||||
|
||||
SDL_memcpy(buffer, ptr1, ptr1len);
|
||||
|
||||
if (IDirectSoundCaptureBuffer_Unlock(h->capturebuf, ptr1, ptr1len, ptr2, ptr2len) != DS_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
h->lastchunk = (h->lastchunk + 1) % h->num_buffers;
|
||||
|
||||
return (int) ptr1len;
|
||||
}
|
||||
|
||||
static void DSOUND_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
DWORD junk, cursor;
|
||||
if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) == DS_OK) {
|
||||
h->lastchunk = cursor / device->buffer_size;
|
||||
}
|
||||
}
|
||||
|
||||
static void DSOUND_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->mixbuf) {
|
||||
IDirectSoundBuffer_Stop(device->hidden->mixbuf);
|
||||
IDirectSoundBuffer_Release(device->hidden->mixbuf);
|
||||
}
|
||||
if (device->hidden->sound) {
|
||||
IDirectSound_Release(device->hidden->sound);
|
||||
}
|
||||
if (device->hidden->capturebuf) {
|
||||
IDirectSoundCaptureBuffer_Stop(device->hidden->capturebuf);
|
||||
IDirectSoundCaptureBuffer_Release(device->hidden->capturebuf);
|
||||
}
|
||||
if (device->hidden->capture) {
|
||||
IDirectSoundCapture_Release(device->hidden->capture);
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* This function tries to create a secondary audio buffer, and returns the
|
||||
number of audio chunks available in the created buffer. This is for
|
||||
playback devices, not recording.
|
||||
*/
|
||||
static bool CreateSecondary(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt)
|
||||
{
|
||||
LPDIRECTSOUND sndObj = device->hidden->sound;
|
||||
LPDIRECTSOUNDBUFFER *sndbuf = &device->hidden->mixbuf;
|
||||
HRESULT result = DS_OK;
|
||||
DSBUFFERDESC format;
|
||||
LPVOID pvAudioPtr1, pvAudioPtr2;
|
||||
DWORD dwAudioBytes1, dwAudioBytes2;
|
||||
|
||||
// Try to create the secondary buffer
|
||||
SDL_zero(format);
|
||||
format.dwSize = sizeof(format);
|
||||
format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
|
||||
format.dwFlags |= DSBCAPS_GLOBALFOCUS;
|
||||
format.dwBufferBytes = bufsize;
|
||||
format.lpwfxFormat = wfmt;
|
||||
result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSound CreateSoundBuffer", result);
|
||||
}
|
||||
IDirectSoundBuffer_SetFormat(*sndbuf, wfmt);
|
||||
|
||||
// Silence the initial audio buffer
|
||||
result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes,
|
||||
(LPVOID *)&pvAudioPtr1, &dwAudioBytes1,
|
||||
(LPVOID *)&pvAudioPtr2, &dwAudioBytes2,
|
||||
DSBLOCK_ENTIREBUFFER);
|
||||
if (result == DS_OK) {
|
||||
SDL_memset(pvAudioPtr1, device->silence_value, dwAudioBytes1);
|
||||
IDirectSoundBuffer_Unlock(*sndbuf,
|
||||
(LPVOID)pvAudioPtr1, dwAudioBytes1,
|
||||
(LPVOID)pvAudioPtr2, dwAudioBytes2);
|
||||
}
|
||||
|
||||
return true; // We're ready to go
|
||||
}
|
||||
|
||||
/* This function tries to create a capture buffer, and returns the
|
||||
number of audio chunks available in the created buffer. This is for
|
||||
recording devices, not playback.
|
||||
*/
|
||||
static bool CreateCaptureBuffer(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt)
|
||||
{
|
||||
LPDIRECTSOUNDCAPTURE capture = device->hidden->capture;
|
||||
LPDIRECTSOUNDCAPTUREBUFFER *capturebuf = &device->hidden->capturebuf;
|
||||
DSCBUFFERDESC format;
|
||||
HRESULT result;
|
||||
|
||||
SDL_zero(format);
|
||||
format.dwSize = sizeof(format);
|
||||
format.dwFlags = DSCBCAPS_WAVEMAPPED;
|
||||
format.dwBufferBytes = bufsize;
|
||||
format.lpwfxFormat = wfmt;
|
||||
|
||||
result = IDirectSoundCapture_CreateCaptureBuffer(capture, &format, capturebuf, NULL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSound CreateCaptureBuffer", result);
|
||||
}
|
||||
|
||||
result = IDirectSoundCaptureBuffer_Start(*capturebuf, DSCBSTART_LOOPING);
|
||||
if (result != DS_OK) {
|
||||
IDirectSoundCaptureBuffer_Release(*capturebuf);
|
||||
return SetDSerror("DirectSound Start", result);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// presumably this starts at zero, but just in case...
|
||||
result = IDirectSoundCaptureBuffer_GetCurrentPosition(*capturebuf, &junk, &cursor);
|
||||
if (result != DS_OK) {
|
||||
IDirectSoundCaptureBuffer_Stop(*capturebuf);
|
||||
IDirectSoundCaptureBuffer_Release(*capturebuf);
|
||||
return SetDSerror("DirectSound GetCurrentPosition", result);
|
||||
}
|
||||
|
||||
device->hidden->lastchunk = cursor / device->buffer_size;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DSOUND_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the audio device
|
||||
LPGUID guid;
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
if (SupportsIMMDevice) {
|
||||
guid = SDL_IMMDevice_GetDirectSoundGUID(device);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
guid = (LPGUID) device->handle;
|
||||
}
|
||||
|
||||
SDL_assert(guid != NULL);
|
||||
|
||||
HRESULT result;
|
||||
if (device->recording) {
|
||||
result = pDirectSoundCaptureCreate8(guid, &device->hidden->capture, NULL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSoundCaptureCreate8", result);
|
||||
}
|
||||
} else {
|
||||
result = pDirectSoundCreate8(guid, &device->hidden->sound, NULL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSoundCreate8", result);
|
||||
}
|
||||
result = IDirectSound_SetCooperativeLevel(device->hidden->sound,
|
||||
GetDesktopWindow(),
|
||||
DSSCL_NORMAL);
|
||||
if (result != DS_OK) {
|
||||
return SetDSerror("DirectSound SetCooperativeLevel", result);
|
||||
}
|
||||
}
|
||||
|
||||
const DWORD numchunks = 8;
|
||||
DWORD bufsize;
|
||||
bool tried_format = false;
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_U8:
|
||||
case SDL_AUDIO_S16:
|
||||
case SDL_AUDIO_S32:
|
||||
case SDL_AUDIO_F32:
|
||||
tried_format = true;
|
||||
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
bufsize = numchunks * device->buffer_size;
|
||||
if ((bufsize < DSBSIZE_MIN) || (bufsize > DSBSIZE_MAX)) {
|
||||
SDL_SetError("Sound buffer size must be between %d and %d",
|
||||
(int)((DSBSIZE_MIN < numchunks) ? 1 : DSBSIZE_MIN / numchunks),
|
||||
(int)(DSBSIZE_MAX / numchunks));
|
||||
} else {
|
||||
WAVEFORMATEXTENSIBLE wfmt;
|
||||
SDL_zero(wfmt);
|
||||
if (device->spec.channels > 2) {
|
||||
wfmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
wfmt.Format.cbSize = sizeof(wfmt) - sizeof(WAVEFORMATEX);
|
||||
|
||||
if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
|
||||
SDL_memcpy(&wfmt.SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID));
|
||||
} else {
|
||||
SDL_memcpy(&wfmt.SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID));
|
||||
}
|
||||
wfmt.Samples.wValidBitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
|
||||
switch (device->spec.channels) {
|
||||
case 3: // 3.0 (or 2.1)
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER;
|
||||
break;
|
||||
case 4: // 4.0
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case 5: // 5.0 (or 4.1)
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case 6: // 5.1
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case 7: // 6.1
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_BACK_CENTER;
|
||||
break;
|
||||
case 8: // 7.1
|
||||
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
|
||||
break;
|
||||
default:
|
||||
SDL_assert(!"Unsupported channel count!");
|
||||
break;
|
||||
}
|
||||
} else if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
|
||||
wfmt.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
|
||||
} else {
|
||||
wfmt.Format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
}
|
||||
|
||||
wfmt.Format.wBitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
wfmt.Format.nChannels = (WORD)device->spec.channels;
|
||||
wfmt.Format.nSamplesPerSec = device->spec.freq;
|
||||
wfmt.Format.nBlockAlign = wfmt.Format.nChannels * (wfmt.Format.wBitsPerSample / 8);
|
||||
wfmt.Format.nAvgBytesPerSec = wfmt.Format.nSamplesPerSec * wfmt.Format.nBlockAlign;
|
||||
|
||||
const bool rc = device->recording ? CreateCaptureBuffer(device, bufsize, (WAVEFORMATEX *)&wfmt) : CreateSecondary(device, bufsize, (WAVEFORMATEX *)&wfmt);
|
||||
if (rc) {
|
||||
device->hidden->num_buffers = numchunks;
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
if (tried_format) {
|
||||
return false; // CreateSecondary() should have called SDL_SetError().
|
||||
}
|
||||
return SDL_SetError("%s: Unsupported audio format", "directsound");
|
||||
}
|
||||
|
||||
// Playback buffers will auto-start playing in DSOUND_WaitDevice()
|
||||
|
||||
return true; // good to go.
|
||||
}
|
||||
|
||||
static void DSOUND_DeinitializeStart(void)
|
||||
{
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
if (SupportsIMMDevice) {
|
||||
SDL_IMMDevice_Quit();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void DSOUND_Deinitialize(void)
|
||||
{
|
||||
DSOUND_Unload();
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
SupportsIMMDevice = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool DSOUND_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!DSOUND_Load()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAVE_MMDEVICEAPI_H
|
||||
SupportsIMMDevice = SDL_IMMDevice_Init(NULL);
|
||||
#endif
|
||||
|
||||
impl->DetectDevices = DSOUND_DetectDevices;
|
||||
impl->OpenDevice = DSOUND_OpenDevice;
|
||||
impl->PlayDevice = DSOUND_PlayDevice;
|
||||
impl->WaitDevice = DSOUND_WaitDevice;
|
||||
impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
|
||||
impl->WaitRecordingDevice = DSOUND_WaitRecordingDevice;
|
||||
impl->RecordDevice = DSOUND_RecordDevice;
|
||||
impl->FlushRecording = DSOUND_FlushRecording;
|
||||
impl->CloseDevice = DSOUND_CloseDevice;
|
||||
impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle;
|
||||
impl->DeinitializeStart = DSOUND_DeinitializeStart;
|
||||
impl->Deinitialize = DSOUND_Deinitialize;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap DSOUND_bootstrap = {
|
||||
"directsound", "DirectSound", DSOUND_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_DSOUND
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_directsound_h_
|
||||
#define SDL_directsound_h_
|
||||
|
||||
#include "../../core/windows/SDL_directx.h"
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
// The DirectSound objects
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// !!! FIXME: make this a union with capture/playback sections?
|
||||
LPDIRECTSOUND sound;
|
||||
LPDIRECTSOUNDBUFFER mixbuf;
|
||||
LPDIRECTSOUNDCAPTURE capture;
|
||||
LPDIRECTSOUNDCAPTUREBUFFER capturebuf;
|
||||
int num_buffers;
|
||||
DWORD lastchunk;
|
||||
Uint8 *locked_buf;
|
||||
};
|
||||
|
||||
#endif // SDL_directsound_h_
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_DISK
|
||||
|
||||
// Output raw audio data to a file.
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_diskaudio.h"
|
||||
|
||||
#define DISKDEFAULT_OUTFILE "sdlaudio.raw"
|
||||
#define DISKDEFAULT_INFILE "sdlaudio-in.raw"
|
||||
|
||||
static bool DISKAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
SDL_Delay(device->hidden->io_delay);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DISKAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
const int written = (int)SDL_WriteIO(device->hidden->io, buffer, (size_t)buffer_size);
|
||||
if (written != buffer_size) { // If we couldn't write, assume fatal error for now
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
SDL_Log("DISKAUDIO: Wrote %d bytes of audio data", (int) written);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *DISKAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int DISKAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
const int origbuflen = buflen;
|
||||
|
||||
if (h->io) {
|
||||
const int br = (int)SDL_ReadIO(h->io, buffer, (size_t)buflen);
|
||||
buflen -= br;
|
||||
buffer = ((Uint8 *)buffer) + br;
|
||||
if (buflen > 0) { // EOF (or error, but whatever).
|
||||
SDL_CloseIO(h->io);
|
||||
h->io = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// if we ran out of file, just write silence.
|
||||
SDL_memset(buffer, device->silence_value, buflen);
|
||||
|
||||
return origbuflen;
|
||||
}
|
||||
|
||||
static void DISKAUDIO_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
// no op...we don't advance the file pointer or anything.
|
||||
}
|
||||
|
||||
static void DISKAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->io) {
|
||||
SDL_CloseIO(device->hidden->io);
|
||||
}
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *get_filename(const bool recording)
|
||||
{
|
||||
const char *devname = SDL_GetHint(recording ? SDL_HINT_AUDIO_DISK_INPUT_FILE : SDL_HINT_AUDIO_DISK_OUTPUT_FILE);
|
||||
if (!devname) {
|
||||
devname = recording ? DISKDEFAULT_INFILE : DISKDEFAULT_OUTFILE;
|
||||
}
|
||||
return devname;
|
||||
}
|
||||
|
||||
static bool DISKAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
bool recording = device->recording;
|
||||
const char *fname = get_filename(recording);
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq);
|
||||
|
||||
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DISK_TIMESCALE);
|
||||
if (hint) {
|
||||
double scale = SDL_atof(hint);
|
||||
if (scale >= 0.0) {
|
||||
device->hidden->io_delay = (Uint32)SDL_round(device->hidden->io_delay * scale);
|
||||
}
|
||||
}
|
||||
|
||||
// Open the "audio device"
|
||||
device->hidden->io = SDL_IOFromFile(fname, recording ? "rb" : "wb");
|
||||
if (!device->hidden->io) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate mixing buffer
|
||||
if (!recording) {
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, "You are using the SDL disk i/o audio driver!");
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s].", recording ? "Reading from" : "Writing to", fname);
|
||||
|
||||
return true; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static void DISKAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
*default_playback = SDL_AddAudioDevice(false, DEFAULT_PLAYBACK_DEVNAME, NULL, (void *)0x1);
|
||||
*default_recording = SDL_AddAudioDevice(true, DEFAULT_RECORDING_DEVNAME, NULL, (void *)0x2);
|
||||
}
|
||||
|
||||
static bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = DISKAUDIO_OpenDevice;
|
||||
impl->WaitDevice = DISKAUDIO_WaitDevice;
|
||||
impl->WaitRecordingDevice = DISKAUDIO_WaitDevice;
|
||||
impl->PlayDevice = DISKAUDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = DISKAUDIO_GetDeviceBuf;
|
||||
impl->RecordDevice = DISKAUDIO_RecordDevice;
|
||||
impl->FlushRecording = DISKAUDIO_FlushRecording;
|
||||
impl->CloseDevice = DISKAUDIO_CloseDevice;
|
||||
impl->DetectDevices = DISKAUDIO_DetectDevices;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap DISKAUDIO_bootstrap = {
|
||||
"disk", "direct-to-disk audio", DISKAUDIO_Init, true
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_DISK
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_diskaudio_h_
|
||||
#define SDL_diskaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The file descriptor for the audio device
|
||||
SDL_IOStream *io;
|
||||
Uint32 io_delay;
|
||||
Uint8 *mixbuf;
|
||||
};
|
||||
|
||||
#endif // SDL_diskaudio_h_
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// !!! FIXME: clean out perror and fprintf calls in here.
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_OSS
|
||||
|
||||
#include <stdio.h> // For perror()
|
||||
#include <string.h> // For strerror()
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <sys/soundcard.h>
|
||||
|
||||
#include "../SDL_audiodev_c.h"
|
||||
#include "SDL_dspaudio.h"
|
||||
|
||||
static void DSP_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
SDL_EnumUnixAudioDevices(false, NULL);
|
||||
}
|
||||
|
||||
static void DSP_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->audio_fd >= 0) {
|
||||
close(device->hidden->audio_fd);
|
||||
}
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
}
|
||||
}
|
||||
|
||||
static bool DSP_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Make sure fragment size stays a power of 2, or OSS fails.
|
||||
// (I don't know which of these are actually legal values, though...)
|
||||
if (device->spec.channels > 8) {
|
||||
device->spec.channels = 8;
|
||||
} else if (device->spec.channels > 4) {
|
||||
device->spec.channels = 4;
|
||||
} else if (device->spec.channels > 2) {
|
||||
device->spec.channels = 2;
|
||||
}
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
|
||||
const int flags = ((device->recording) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT);
|
||||
device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC, 0);
|
||||
if (device->hidden->audio_fd < 0) {
|
||||
return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno));
|
||||
}
|
||||
|
||||
// Make the file descriptor use blocking i/o with fcntl()
|
||||
{
|
||||
const long ctlflags = fcntl(device->hidden->audio_fd, F_GETFL) & ~O_NONBLOCK;
|
||||
if (fcntl(device->hidden->audio_fd, F_SETFL, ctlflags) < 0) {
|
||||
return SDL_SetError("Couldn't set audio blocking mode");
|
||||
}
|
||||
}
|
||||
|
||||
// Get a list of supported hardware formats
|
||||
int value;
|
||||
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_GETFMTS, &value) < 0) {
|
||||
perror("SNDCTL_DSP_GETFMTS");
|
||||
return SDL_SetError("Couldn't get audio format list");
|
||||
}
|
||||
|
||||
// Try for a closest match on audio format
|
||||
int format = 0;
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
|
||||
#endif
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_U8:
|
||||
if (value & AFMT_U8) {
|
||||
format = AFMT_U8;
|
||||
}
|
||||
break;
|
||||
case SDL_AUDIO_S16LE:
|
||||
if (value & AFMT_S16_LE) {
|
||||
format = AFMT_S16_LE;
|
||||
}
|
||||
break;
|
||||
case SDL_AUDIO_S16BE:
|
||||
if (value & AFMT_S16_BE) {
|
||||
format = AFMT_S16_BE;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (format == 0) {
|
||||
return SDL_SetError("Couldn't find any hardware audio formats");
|
||||
}
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Set the audio format
|
||||
value = format;
|
||||
if ((ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFMT, &value) < 0) ||
|
||||
(value != format)) {
|
||||
perror("SNDCTL_DSP_SETFMT");
|
||||
return SDL_SetError("Couldn't set audio format");
|
||||
}
|
||||
|
||||
// Set the number of channels of output
|
||||
value = device->spec.channels;
|
||||
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_CHANNELS, &value) < 0) {
|
||||
perror("SNDCTL_DSP_CHANNELS");
|
||||
return SDL_SetError("Cannot set the number of channels");
|
||||
}
|
||||
device->spec.channels = value;
|
||||
|
||||
// Set the DSP frequency
|
||||
value = device->spec.freq;
|
||||
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SPEED, &value) < 0) {
|
||||
perror("SNDCTL_DSP_SPEED");
|
||||
return SDL_SetError("Couldn't set audio frequency");
|
||||
}
|
||||
device->spec.freq = value;
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
/* Determine the power of two of the fragment size
|
||||
Since apps don't control this in SDL3, and this driver only accepts 8, 16
|
||||
bit formats and 1, 2, 4, 8 channels, this should always be a power of 2 already. */
|
||||
SDL_assert(SDL_powerof2(device->buffer_size) == device->buffer_size);
|
||||
|
||||
int frag_spec = 0;
|
||||
while ((0x01U << frag_spec) < device->buffer_size) {
|
||||
frag_spec++;
|
||||
}
|
||||
frag_spec |= 0x00020000; // two fragments, for low latency
|
||||
|
||||
// Set the audio buffering parameters
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Requesting %d fragments of size %d\n",
|
||||
(frag_spec >> 16), 1 << (frag_spec & 0xFFFF));
|
||||
#endif
|
||||
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0) {
|
||||
perror("SNDCTL_DSP_SETFRAGMENT");
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
{
|
||||
audio_buf_info info;
|
||||
ioctl(device->hidden->audio_fd, SNDCTL_DSP_GETOSPACE, &info);
|
||||
fprintf(stderr, "fragments = %d\n", info.fragments);
|
||||
fprintf(stderr, "fragstotal = %d\n", info.fragstotal);
|
||||
fprintf(stderr, "fragsize = %d\n", info.fragsize);
|
||||
fprintf(stderr, "bytes = %d\n", info.bytes);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Allocate mixing buffer
|
||||
if (!device->recording) {
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
return true; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static bool DSP_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const unsigned long ioctlreq = device->recording ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE;
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
audio_buf_info info;
|
||||
const int rc = ioctl(h->audio_fd, ioctlreq, &info);
|
||||
if (rc < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
continue;
|
||||
}
|
||||
// Hmm, not much we can do - abort
|
||||
fprintf(stderr, "dsp WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno));
|
||||
return false;
|
||||
} else if (info.bytes < device->buffer_size) {
|
||||
SDL_Delay(10);
|
||||
} else {
|
||||
break; // ready to go!
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DSP_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
if (write(h->audio_fd, buffer, buflen) == -1) {
|
||||
perror("Audio write");
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Wrote %d bytes of audio data\n", h->mixlen);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *DSP_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int DSP_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
return (int)read(device->hidden->audio_fd, buffer, buflen);
|
||||
}
|
||||
|
||||
static void DSP_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
audio_buf_info info;
|
||||
if (ioctl(h->audio_fd, SNDCTL_DSP_GETISPACE, &info) == 0) {
|
||||
while (info.bytes > 0) {
|
||||
char buf[512];
|
||||
const size_t len = SDL_min(sizeof(buf), info.bytes);
|
||||
const ssize_t br = read(h->audio_fd, buf, len);
|
||||
if (br <= 0) {
|
||||
break;
|
||||
}
|
||||
info.bytes -= br;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool InitTimeDevicesExist = false;
|
||||
static bool look_for_devices_test(int fd)
|
||||
{
|
||||
InitTimeDevicesExist = true; // note that _something_ exists.
|
||||
// Don't add to the device list, we're just seeing if any devices exist.
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool DSP_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
InitTimeDevicesExist = false;
|
||||
SDL_EnumUnixAudioDevices(false, look_for_devices_test);
|
||||
if (!InitTimeDevicesExist) {
|
||||
SDL_SetError("dsp: No such audio device");
|
||||
return false; // maybe try a different backend.
|
||||
}
|
||||
|
||||
impl->DetectDevices = DSP_DetectDevices;
|
||||
impl->OpenDevice = DSP_OpenDevice;
|
||||
impl->WaitDevice = DSP_WaitDevice;
|
||||
impl->PlayDevice = DSP_PlayDevice;
|
||||
impl->GetDeviceBuf = DSP_GetDeviceBuf;
|
||||
impl->CloseDevice = DSP_CloseDevice;
|
||||
impl->WaitRecordingDevice = DSP_WaitDevice;
|
||||
impl->RecordDevice = DSP_RecordDevice;
|
||||
impl->FlushRecording = DSP_FlushRecording;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap DSP_bootstrap = {
|
||||
"dsp", "Open Sound System (/dev/dsp)", DSP_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_OSS
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_dspaudio_h_
|
||||
#define SDL_dspaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The file descriptor for the audio device
|
||||
int audio_fd;
|
||||
|
||||
// Raw mixing buffer
|
||||
Uint8 *mixbuf;
|
||||
};
|
||||
|
||||
#endif // SDL_dspaudio_h_
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
// Output audio to nowhere...
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_dummyaudio.h"
|
||||
|
||||
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
|
||||
#include <emscripten/emscripten.h>
|
||||
#endif
|
||||
|
||||
static bool DUMMYAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
SDL_Delay(device->hidden->io_delay);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!device->recording) {
|
||||
device->hidden->mixbuf = (Uint8 *) SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq);
|
||||
|
||||
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DUMMY_TIMESCALE);
|
||||
if (hint) {
|
||||
double scale = SDL_atof(hint);
|
||||
if (scale >= 0.0) {
|
||||
device->hidden->io_delay = (Uint32)SDL_round(device->hidden->io_delay * scale);
|
||||
}
|
||||
}
|
||||
|
||||
// on Emscripten without threads, we just fire a repeating timer to consume audio.
|
||||
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var a = Module['SDL3'].dummy_audio;
|
||||
if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); }
|
||||
a.timers[$0] = setInterval(function() { dynCall('vi', $3, [$4]); }, ($1 / $2) * 1000);
|
||||
}, device->recording ? 1 : 0, device->sample_frames, device->spec.freq, device->recording ? SDL_RecordingAudioThreadIterate : SDL_PlaybackAudioThreadIterate, device);
|
||||
#endif
|
||||
|
||||
return true; // we're good; don't change reported device format.
|
||||
}
|
||||
|
||||
static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
// on Emscripten without threads, we just fire a repeating timer to consume audio.
|
||||
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var a = Module['SDL3'].dummy_audio;
|
||||
if (a.timers[$0] !== undefined) { clearInterval(a.timers[$0]); }
|
||||
a.timers[$0] = undefined;
|
||||
}, device->recording ? 1 : 0);
|
||||
#endif
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static Uint8 *DUMMYAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int DUMMYAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
// always return a full buffer of silence.
|
||||
SDL_memset(buffer, device->silence_value, buflen);
|
||||
return buflen;
|
||||
}
|
||||
|
||||
static bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = DUMMYAUDIO_OpenDevice;
|
||||
impl->CloseDevice = DUMMYAUDIO_CloseDevice;
|
||||
impl->WaitDevice = DUMMYAUDIO_WaitDevice;
|
||||
impl->GetDeviceBuf = DUMMYAUDIO_GetDeviceBuf;
|
||||
impl->WaitRecordingDevice = DUMMYAUDIO_WaitDevice;
|
||||
impl->RecordDevice = DUMMYAUDIO_RecordDevice;
|
||||
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
impl->OnlyHasDefaultRecordingDevice = true;
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
// on Emscripten without threads, we just fire a repeating timer to consume audio.
|
||||
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
|
||||
MAIN_THREAD_EM_ASM({
|
||||
if (typeof(Module['SDL3']) === 'undefined') {
|
||||
Module['SDL3'] = {};
|
||||
}
|
||||
Module['SDL3'].dummy_audio = {};
|
||||
Module['SDL3'].dummy_audio.timers = [];
|
||||
Module['SDL3'].dummy_audio.timers[0] = undefined;
|
||||
Module['SDL3'].dummy_audio.timers[1] = undefined;
|
||||
});
|
||||
impl->ProvidesOwnCallbackThread = true;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap DUMMYAUDIO_bootstrap = {
|
||||
"dummy", "SDL dummy audio driver", DUMMYAUDIO_Init, true
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_dummyaudio_h_
|
||||
#define SDL_dummyaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
Uint8 *mixbuf; // The file descriptor for the audio device
|
||||
Uint32 io_delay; // milliseconds to sleep in WaitDevice.
|
||||
};
|
||||
|
||||
#endif // SDL_dummyaudio_h_
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_emscriptenaudio.h"
|
||||
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
// just turn off clang-format for this whole file, this INDENT_OFF stuff on
|
||||
// each EM_ASM section is ugly.
|
||||
/* *INDENT-OFF* */ // clang-format off
|
||||
|
||||
static Uint8 *EMSCRIPTENAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static bool EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
const int framelen = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
MAIN_THREAD_EM_ASM({
|
||||
/* Convert incoming buf pointer to a HEAPF32 offset. */
|
||||
#ifdef __wasm64__
|
||||
var buf = $0 / 4;
|
||||
#else
|
||||
var buf = $0 >>> 2;
|
||||
#endif
|
||||
|
||||
var SDL3 = Module['SDL3'];
|
||||
var numChannels = SDL3.audio_playback.currentPlaybackBuffer['numberOfChannels'];
|
||||
for (var c = 0; c < numChannels; ++c) {
|
||||
var channelData = SDL3.audio_playback.currentPlaybackBuffer['getChannelData'](c);
|
||||
if (channelData.length != $1) {
|
||||
throw 'Web Audio playback buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
|
||||
}
|
||||
|
||||
for (var j = 0; j < $1; ++j) {
|
||||
channelData[j] = HEAPF32[buf + (j*numChannels + c)];
|
||||
}
|
||||
}
|
||||
}, buffer, buffer_size / framelen);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void EMSCRIPTENAUDIO_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
// Do nothing, the new data will just be dropped.
|
||||
}
|
||||
|
||||
static int EMSCRIPTENAUDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
var numChannels = SDL3.audio_recording.currentRecordingBuffer.numberOfChannels;
|
||||
for (var c = 0; c < numChannels; ++c) {
|
||||
var channelData = SDL3.audio_recording.currentRecordingBuffer.getChannelData(c);
|
||||
if (channelData.length != $1) {
|
||||
throw 'Web Audio recording buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
|
||||
}
|
||||
|
||||
if (numChannels == 1) { // fastpath this a little for the common (mono) case.
|
||||
for (var j = 0; j < $1; ++j) {
|
||||
setValue($0 + (j * 4), channelData[j], 'float');
|
||||
}
|
||||
} else {
|
||||
for (var j = 0; j < $1; ++j) {
|
||||
setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, buffer, (buflen / sizeof(float)) / device->spec.channels);
|
||||
|
||||
return buflen;
|
||||
}
|
||||
|
||||
static void EMSCRIPTENAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (!device->hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
if ($0) {
|
||||
if (SDL3.audio_recording.silenceTimer !== undefined) {
|
||||
clearInterval(SDL3.audio_recording.silenceTimer);
|
||||
}
|
||||
if (SDL3.audio_recording.stream !== undefined) {
|
||||
var tracks = SDL3.audio_recording.stream.getAudioTracks();
|
||||
for (var i = 0; i < tracks.length; i++) {
|
||||
SDL3.audio_recording.stream.removeTrack(tracks[i]);
|
||||
}
|
||||
}
|
||||
if (SDL3.audio_recording.scriptProcessorNode !== undefined) {
|
||||
SDL3.audio_recording.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {};
|
||||
SDL3.audio_recording.scriptProcessorNode.disconnect();
|
||||
}
|
||||
if (SDL3.audio_recording.mediaStreamNode !== undefined) {
|
||||
SDL3.audio_recording.mediaStreamNode.disconnect();
|
||||
}
|
||||
SDL3.audio_recording = undefined;
|
||||
} else {
|
||||
if (SDL3.audio_playback.scriptProcessorNode != undefined) {
|
||||
SDL3.audio_playback.scriptProcessorNode.disconnect();
|
||||
}
|
||||
if (SDL3.audio_playback.silenceTimer !== undefined) {
|
||||
clearInterval(SDL3.audio_playback.silenceTimer);
|
||||
}
|
||||
SDL3.audio_playback = undefined;
|
||||
}
|
||||
if ((SDL3.audioContext !== undefined) && (SDL3.audio_playback === undefined) && (SDL3.audio_recording === undefined)) {
|
||||
SDL3.audioContext.close();
|
||||
SDL3.audioContext = undefined;
|
||||
}
|
||||
}, device->recording);
|
||||
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
|
||||
SDL_AudioThreadFinalize(device);
|
||||
}
|
||||
|
||||
EM_JS_DEPS(sdlaudio, "$autoResumeAudioContext,$dynCall");
|
||||
|
||||
static bool EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// based on parts of library_sdl.js
|
||||
|
||||
// create context
|
||||
const bool result = MAIN_THREAD_EM_ASM_INT({
|
||||
if (typeof(Module['SDL3']) === 'undefined') {
|
||||
Module['SDL3'] = {};
|
||||
}
|
||||
var SDL3 = Module['SDL3'];
|
||||
if (!$0) {
|
||||
SDL3.audio_playback = {};
|
||||
} else {
|
||||
SDL3.audio_recording = {};
|
||||
}
|
||||
|
||||
if (!SDL3.audioContext) {
|
||||
if (typeof(AudioContext) !== 'undefined') {
|
||||
SDL3.audioContext = new AudioContext();
|
||||
} else if (typeof(webkitAudioContext) !== 'undefined') {
|
||||
SDL3.audioContext = new webkitAudioContext();
|
||||
}
|
||||
if (SDL3.audioContext) {
|
||||
if ((typeof navigator.userActivation) === 'undefined') {
|
||||
autoResumeAudioContext(SDL3.audioContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (SDL3.audioContext !== undefined);
|
||||
}, device->recording);
|
||||
|
||||
if (!result) {
|
||||
return SDL_SetError("Web Audio API is not available!");
|
||||
}
|
||||
|
||||
device->spec.format = SDL_AUDIO_F32; // web audio only supports floats
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// limit to native freq
|
||||
device->spec.freq = EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; });
|
||||
device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq) * 2; // double the buffer size, some browsers need more, and we'll just have to live with the latency.
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
if (!device->recording) {
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
if (device->recording) {
|
||||
/* The idea is to take the recording media stream, hook it up to an
|
||||
audio graph where we can pass it through a ScriptProcessorNode
|
||||
to access the raw PCM samples and push them to the SDL app's
|
||||
callback. From there, we "process" the audio data into silence
|
||||
and forget about it.
|
||||
|
||||
This should, strictly speaking, use MediaRecorder for recording, but
|
||||
this API is cleaner to use and better supported, and fires a
|
||||
callback whenever there's enough data to fire down into the app.
|
||||
The downside is that we are spending CPU time silencing a buffer
|
||||
that the audiocontext uselessly mixes into any playback. On the
|
||||
upside, both of those things are not only run in native code in
|
||||
the browser, they're probably SIMD code, too. MediaRecorder
|
||||
feels like it's a pretty inefficient tapdance in similar ways,
|
||||
to be honest. */
|
||||
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
var have_microphone = function(stream) {
|
||||
//console.log('SDL audio recording: we have a microphone! Replacing silence callback.');
|
||||
if (SDL3.audio_recording.silenceTimer !== undefined) {
|
||||
clearInterval(SDL3.audio_recording.silenceTimer);
|
||||
SDL3.audio_recording.silenceTimer = undefined;
|
||||
SDL3.audio_recording.silenceBuffer = undefined
|
||||
}
|
||||
SDL3.audio_recording.mediaStreamNode = SDL3.audioContext.createMediaStreamSource(stream);
|
||||
SDL3.audio_recording.scriptProcessorNode = SDL3.audioContext.createScriptProcessor($1, $0, 1);
|
||||
SDL3.audio_recording.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {
|
||||
if ((SDL3 === undefined) || (SDL3.audio_recording === undefined)) { return; }
|
||||
audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0);
|
||||
SDL3.audio_recording.currentRecordingBuffer = audioProcessingEvent.inputBuffer;
|
||||
dynCall('ii', $2, [$3]);
|
||||
};
|
||||
SDL3.audio_recording.mediaStreamNode.connect(SDL3.audio_recording.scriptProcessorNode);
|
||||
SDL3.audio_recording.scriptProcessorNode.connect(SDL3.audioContext.destination);
|
||||
SDL3.audio_recording.stream = stream;
|
||||
};
|
||||
|
||||
var no_microphone = function(error) {
|
||||
//console.log('SDL audio recording: we DO NOT have a microphone! (' + error.name + ')...leaving silence callback running.');
|
||||
};
|
||||
|
||||
// we write silence to the audio callback until the microphone is available (user approves use, etc).
|
||||
SDL3.audio_recording.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
|
||||
SDL3.audio_recording.silenceBuffer.getChannelData(0).fill(0.0);
|
||||
var silence_callback = function() {
|
||||
SDL3.audio_recording.currentRecordingBuffer = SDL3.audio_recording.silenceBuffer;
|
||||
dynCall('ii', $2, [$3]);
|
||||
};
|
||||
|
||||
SDL3.audio_recording.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
|
||||
|
||||
if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) {
|
||||
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone);
|
||||
} else if (navigator.webkitGetUserMedia !== undefined) {
|
||||
navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone);
|
||||
}
|
||||
}, device->spec.channels, device->sample_frames, SDL_RecordingAudioThreadIterate, device);
|
||||
} else {
|
||||
// setup a ScriptProcessorNode
|
||||
MAIN_THREAD_EM_ASM({
|
||||
var SDL3 = Module['SDL3'];
|
||||
SDL3.audio_playback.scriptProcessorNode = SDL3.audioContext['createScriptProcessor']($1, 0, $0);
|
||||
SDL3.audio_playback.scriptProcessorNode['onaudioprocess'] = function (e) {
|
||||
if ((SDL3 === undefined) || (SDL3.audio_playback === undefined)) { return; }
|
||||
// if we're actually running the node, we don't need the fake callback anymore, so kill it.
|
||||
if (SDL3.audio_playback.silenceTimer !== undefined) {
|
||||
clearInterval(SDL3.audio_playback.silenceTimer);
|
||||
SDL3.audio_playback.silenceTimer = undefined;
|
||||
SDL3.audio_playback.silenceBuffer = undefined;
|
||||
}
|
||||
SDL3.audio_playback.currentPlaybackBuffer = e['outputBuffer'];
|
||||
dynCall('ii', $2, [$3]);
|
||||
};
|
||||
|
||||
SDL3.audio_playback.scriptProcessorNode['connect'](SDL3.audioContext['destination']);
|
||||
|
||||
if (SDL3.audioContext.state === 'suspended') { // uhoh, autoplay is blocked.
|
||||
SDL3.audio_playback.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
|
||||
SDL3.audio_playback.silenceBuffer.getChannelData(0).fill(0.0);
|
||||
var silence_callback = function() {
|
||||
if ((typeof navigator.userActivation) !== 'undefined') {
|
||||
if (navigator.userActivation.hasBeenActive) {
|
||||
SDL3.audioContext.resume();
|
||||
}
|
||||
}
|
||||
|
||||
// the buffer that gets filled here just gets ignored, so the app can make progress
|
||||
// and/or avoid flooding audio queues until we can actually play audio.
|
||||
SDL3.audio_playback.currentPlaybackBuffer = SDL3.audio_playback.silenceBuffer;
|
||||
dynCall('ii', $2, [$3]);
|
||||
SDL3.audio_playback.currentPlaybackBuffer = undefined;
|
||||
};
|
||||
|
||||
SDL3.audio_playback.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
|
||||
}
|
||||
}, device->spec.channels, device->sample_frames, SDL_PlaybackAudioThreadIterate, device);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
bool available, recording_available;
|
||||
|
||||
impl->OpenDevice = EMSCRIPTENAUDIO_OpenDevice;
|
||||
impl->CloseDevice = EMSCRIPTENAUDIO_CloseDevice;
|
||||
impl->GetDeviceBuf = EMSCRIPTENAUDIO_GetDeviceBuf;
|
||||
impl->PlayDevice = EMSCRIPTENAUDIO_PlayDevice;
|
||||
impl->FlushRecording = EMSCRIPTENAUDIO_FlushRecording;
|
||||
impl->RecordDevice = EMSCRIPTENAUDIO_RecordDevice;
|
||||
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
|
||||
// technically, this is just runs in idle time in the main thread, but it's close enough to a "thread" for our purposes.
|
||||
impl->ProvidesOwnCallbackThread = true;
|
||||
|
||||
// check availability
|
||||
available = MAIN_THREAD_EM_ASM_INT({
|
||||
if (typeof(AudioContext) !== 'undefined') {
|
||||
return true;
|
||||
} else if (typeof(webkitAudioContext) !== 'undefined') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!available) {
|
||||
SDL_SetError("No audio context available");
|
||||
}
|
||||
|
||||
recording_available = available && MAIN_THREAD_EM_ASM_INT({
|
||||
if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) {
|
||||
return true;
|
||||
} else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
impl->HasRecordingSupport = recording_available;
|
||||
impl->OnlyHasDefaultRecordingDevice = recording_available;
|
||||
|
||||
return available;
|
||||
}
|
||||
|
||||
AudioBootStrap EMSCRIPTENAUDIO_bootstrap = {
|
||||
"emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, false
|
||||
};
|
||||
|
||||
/* *INDENT-ON* */ // clang-format on
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_EMSCRIPTEN
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_emscriptenaudio_h_
|
||||
#define SDL_emscriptenaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
Uint8 *mixbuf;
|
||||
};
|
||||
|
||||
#endif // SDL_emscriptenaudio_h_
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_HAIKU
|
||||
|
||||
// Allow access to the audio stream on Haiku
|
||||
|
||||
#include <SoundPlayer.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "../../core/haiku/SDL_BeApp.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_haikuaudio.h"
|
||||
|
||||
}
|
||||
|
||||
static Uint8 *HAIKUAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
SDL_assert(device->hidden->current_buffer != NULL);
|
||||
SDL_assert(device->hidden->current_buffer_len > 0);
|
||||
*buffer_size = device->hidden->current_buffer_len;
|
||||
return device->hidden->current_buffer;
|
||||
}
|
||||
|
||||
static bool HAIKUAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
// We already wrote our output right into the BSoundPlayer's callback's stream. Just clean up our stuff.
|
||||
SDL_assert(device->hidden->current_buffer != NULL);
|
||||
SDL_assert(device->hidden->current_buffer_len > 0);
|
||||
device->hidden->current_buffer = NULL;
|
||||
device->hidden->current_buffer_len = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The Haiku callback for handling the audio buffer
|
||||
static void FillSound(void *data, void *stream, size_t len, const media_raw_audio_format & format)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *)data;
|
||||
SDL_assert(device->hidden->current_buffer == NULL);
|
||||
SDL_assert(device->hidden->current_buffer_len == 0);
|
||||
device->hidden->current_buffer = (Uint8 *) stream;
|
||||
device->hidden->current_buffer_len = (int) len;
|
||||
SDL_PlaybackAudioThreadIterate(device);
|
||||
}
|
||||
|
||||
static void HAIKUAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->audio_obj) {
|
||||
device->hidden->audio_obj->Stop();
|
||||
delete device->hidden->audio_obj;
|
||||
}
|
||||
delete device->hidden;
|
||||
device->hidden = NULL;
|
||||
SDL_AudioThreadFinalize(device);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const int sig_list[] = {
|
||||
SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGALRM, SIGTERM, SIGWINCH, 0
|
||||
};
|
||||
|
||||
static inline void MaskSignals(sigset_t * omask)
|
||||
{
|
||||
sigset_t mask;
|
||||
int i;
|
||||
|
||||
sigemptyset(&mask);
|
||||
for (i = 0; sig_list[i]; ++i) {
|
||||
sigaddset(&mask, sig_list[i]);
|
||||
}
|
||||
sigprocmask(SIG_BLOCK, &mask, omask);
|
||||
}
|
||||
|
||||
static inline void UnmaskSignals(sigset_t * omask)
|
||||
{
|
||||
sigprocmask(SIG_SETMASK, omask, NULL);
|
||||
}
|
||||
|
||||
|
||||
static bool HAIKUAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = new SDL_PrivateAudioData;
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
SDL_zerop(device->hidden);
|
||||
|
||||
// Parse the audio format and fill the Be raw audio format
|
||||
media_raw_audio_format format;
|
||||
SDL_zero(format);
|
||||
format.byte_order = B_MEDIA_LITTLE_ENDIAN;
|
||||
format.frame_rate = (float) device->spec.freq;
|
||||
format.channel_count = device->spec.channels; // !!! FIXME: support > 2?
|
||||
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_S8:
|
||||
format.format = media_raw_audio_format::B_AUDIO_CHAR;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_U8:
|
||||
format.format = media_raw_audio_format::B_AUDIO_UCHAR;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16LE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_SHORT;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S16BE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_SHORT;
|
||||
format.byte_order = B_MEDIA_BIG_ENDIAN;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32LE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_INT;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_S32BE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_INT;
|
||||
format.byte_order = B_MEDIA_BIG_ENDIAN;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_F32LE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_FLOAT;
|
||||
break;
|
||||
|
||||
case SDL_AUDIO_F32BE:
|
||||
format.format = media_raw_audio_format::B_AUDIO_FLOAT;
|
||||
format.byte_order = B_MEDIA_BIG_ENDIAN;
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!test_format) { // shouldn't happen, but just in case...
|
||||
return SDL_SetError("HAIKU: Unsupported audio format");
|
||||
}
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
format.buffer_size = device->buffer_size;
|
||||
|
||||
// Subscribe to the audio stream (creates a new thread)
|
||||
sigset_t omask;
|
||||
MaskSignals(&omask);
|
||||
device->hidden->audio_obj = new BSoundPlayer(&format, "SDL Audio",
|
||||
FillSound, NULL, device);
|
||||
UnmaskSignals(&omask);
|
||||
|
||||
if (device->hidden->audio_obj->Start() == B_NO_ERROR) {
|
||||
device->hidden->audio_obj->SetHasData(true);
|
||||
} else {
|
||||
return SDL_SetError("Unable to start Haiku audio");
|
||||
}
|
||||
|
||||
return true; // We're running!
|
||||
}
|
||||
|
||||
static void HAIKUAUDIO_Deinitialize(void)
|
||||
{
|
||||
SDL_QuitBeApp();
|
||||
}
|
||||
|
||||
static bool HAIKUAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!SDL_InitBeApp()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the function pointers
|
||||
impl->OpenDevice = HAIKUAUDIO_OpenDevice;
|
||||
impl->GetDeviceBuf = HAIKUAUDIO_GetDeviceBuf;
|
||||
impl->PlayDevice = HAIKUAUDIO_PlayDevice;
|
||||
impl->CloseDevice = HAIKUAUDIO_CloseDevice;
|
||||
impl->Deinitialize = HAIKUAUDIO_Deinitialize;
|
||||
impl->ProvidesOwnCallbackThread = true;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
extern "C" { extern AudioBootStrap HAIKUAUDIO_bootstrap; }
|
||||
|
||||
AudioBootStrap HAIKUAUDIO_bootstrap = {
|
||||
"haiku", "Haiku BSoundPlayer", HAIKUAUDIO_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_HAIKU
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_haikuaudio_h_
|
||||
#define SDL_haikuaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
BSoundPlayer *audio_obj;
|
||||
Uint8 *current_buffer;
|
||||
int current_buffer_len;
|
||||
};
|
||||
|
||||
#endif // SDL_haikuaudio_h_
|
||||
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_JACK
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_jackaudio.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
|
||||
static jack_client_t *(*JACK_jack_client_open)(const char *, jack_options_t, jack_status_t *, ...);
|
||||
static int (*JACK_jack_client_close)(jack_client_t *);
|
||||
static void (*JACK_jack_on_shutdown)(jack_client_t *, JackShutdownCallback, void *);
|
||||
static int (*JACK_jack_activate)(jack_client_t *);
|
||||
static int (*JACK_jack_deactivate)(jack_client_t *);
|
||||
static void *(*JACK_jack_port_get_buffer)(jack_port_t *, jack_nframes_t);
|
||||
static int (*JACK_jack_port_unregister)(jack_client_t *, jack_port_t *);
|
||||
static void (*JACK_jack_free)(void *);
|
||||
static const char **(*JACK_jack_get_ports)(jack_client_t *, const char *, const char *, unsigned long);
|
||||
static jack_nframes_t (*JACK_jack_get_sample_rate)(jack_client_t *);
|
||||
static jack_nframes_t (*JACK_jack_get_buffer_size)(jack_client_t *);
|
||||
static jack_port_t *(*JACK_jack_port_register)(jack_client_t *, const char *, const char *, unsigned long, unsigned long);
|
||||
static jack_port_t *(*JACK_jack_port_by_name)(jack_client_t *, const char *);
|
||||
static const char *(*JACK_jack_port_name)(const jack_port_t *);
|
||||
static const char *(*JACK_jack_port_type)(const jack_port_t *);
|
||||
static int (*JACK_jack_connect)(jack_client_t *, const char *, const char *);
|
||||
static int (*JACK_jack_set_process_callback)(jack_client_t *, JackProcessCallback, void *);
|
||||
static int (*JACK_jack_set_sample_rate_callback)(jack_client_t *, JackSampleRateCallback, void *);
|
||||
static int (*JACK_jack_set_buffer_size_callback)(jack_client_t *, JackBufferSizeCallback, void *);
|
||||
|
||||
static bool load_jack_syms(void);
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC
|
||||
|
||||
static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC;
|
||||
static SDL_SharedObject *jack_handle = NULL;
|
||||
|
||||
// !!! FIXME: this is copy/pasted in several places now
|
||||
static bool load_jack_sym(const char *fn, void **addr)
|
||||
{
|
||||
*addr = SDL_LoadFunction(jack_handle, fn);
|
||||
if (!*addr) {
|
||||
// Don't call SDL_SetError(): SDL_LoadFunction already did.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// cast funcs to char* first, to please GCC's strict aliasing rules.
|
||||
#define SDL_JACK_SYM(x) \
|
||||
if (!load_jack_sym(#x, (void **)(char *)&JACK_##x)) \
|
||||
return false
|
||||
|
||||
static void UnloadJackLibrary(void)
|
||||
{
|
||||
if (jack_handle) {
|
||||
SDL_UnloadObject(jack_handle);
|
||||
jack_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool LoadJackLibrary(void)
|
||||
{
|
||||
bool result = true;
|
||||
if (!jack_handle) {
|
||||
jack_handle = SDL_LoadObject(jack_library);
|
||||
if (!jack_handle) {
|
||||
result = false;
|
||||
// Don't call SDL_SetError(): SDL_LoadObject already did.
|
||||
} else {
|
||||
result = load_jack_syms();
|
||||
if (!result) {
|
||||
UnloadJackLibrary();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define SDL_JACK_SYM(x) JACK_##x = x
|
||||
|
||||
static void UnloadJackLibrary(void)
|
||||
{
|
||||
}
|
||||
|
||||
static bool LoadJackLibrary(void)
|
||||
{
|
||||
load_jack_syms();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_JACK_DYNAMIC
|
||||
|
||||
static bool load_jack_syms(void)
|
||||
{
|
||||
SDL_JACK_SYM(jack_client_open);
|
||||
SDL_JACK_SYM(jack_client_close);
|
||||
SDL_JACK_SYM(jack_on_shutdown);
|
||||
SDL_JACK_SYM(jack_activate);
|
||||
SDL_JACK_SYM(jack_deactivate);
|
||||
SDL_JACK_SYM(jack_port_get_buffer);
|
||||
SDL_JACK_SYM(jack_port_unregister);
|
||||
SDL_JACK_SYM(jack_free);
|
||||
SDL_JACK_SYM(jack_get_ports);
|
||||
SDL_JACK_SYM(jack_get_sample_rate);
|
||||
SDL_JACK_SYM(jack_get_buffer_size);
|
||||
SDL_JACK_SYM(jack_port_register);
|
||||
SDL_JACK_SYM(jack_port_by_name);
|
||||
SDL_JACK_SYM(jack_port_name);
|
||||
SDL_JACK_SYM(jack_port_type);
|
||||
SDL_JACK_SYM(jack_connect);
|
||||
SDL_JACK_SYM(jack_set_process_callback);
|
||||
SDL_JACK_SYM(jack_set_sample_rate_callback);
|
||||
SDL_JACK_SYM(jack_set_buffer_size_callback);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void jackShutdownCallback(void *arg) // JACK went away; device is lost.
|
||||
{
|
||||
SDL_AudioDeviceDisconnected((SDL_AudioDevice *)arg);
|
||||
}
|
||||
|
||||
static int jackSampleRateCallback(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
//SDL_Log("JACK Sample Rate Callback! %d", (int) nframes);
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) arg;
|
||||
SDL_AudioSpec newspec;
|
||||
SDL_copyp(&newspec, &device->spec);
|
||||
newspec.freq = (int) nframes;
|
||||
if (!SDL_AudioDeviceFormatChanged(device, &newspec, device->sample_frames)) {
|
||||
SDL_AudioDeviceDisconnected(device);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jackBufferSizeCallback(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
//SDL_Log("JACK Buffer Size Callback! %d", (int) nframes);
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) arg;
|
||||
SDL_AudioSpec newspec;
|
||||
SDL_copyp(&newspec, &device->spec);
|
||||
if (!SDL_AudioDeviceFormatChanged(device, &newspec, (int) nframes)) {
|
||||
SDL_AudioDeviceDisconnected(device);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
|
||||
SDL_PlaybackAudioThreadIterate((SDL_AudioDevice *)arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool JACK_PlayDevice(SDL_AudioDevice *device, const Uint8 *ui8buffer, int buflen)
|
||||
{
|
||||
const float *buffer = (float *) ui8buffer;
|
||||
jack_port_t **ports = device->hidden->sdlports;
|
||||
const int total_channels = device->spec.channels;
|
||||
const int total_frames = device->sample_frames;
|
||||
const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames;
|
||||
|
||||
for (int channelsi = 0; channelsi < total_channels; channelsi++) {
|
||||
float *dst = (float *)JACK_jack_port_get_buffer(ports[channelsi], nframes);
|
||||
if (dst) {
|
||||
const float *src = buffer + channelsi;
|
||||
for (int framesi = 0; framesi < total_frames; framesi++) {
|
||||
*(dst++) = *src;
|
||||
src += total_channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return (Uint8 *)device->hidden->iobuffer;
|
||||
}
|
||||
|
||||
static int jackProcessRecordingCallback(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
|
||||
SDL_RecordingAudioThreadIterate((SDL_AudioDevice *)arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int JACK_RecordDevice(SDL_AudioDevice *device, void *vbuffer, int buflen)
|
||||
{
|
||||
float *buffer = (float *) vbuffer;
|
||||
jack_port_t **ports = device->hidden->sdlports;
|
||||
const int total_channels = device->spec.channels;
|
||||
const int total_frames = device->sample_frames;
|
||||
const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames;
|
||||
|
||||
for (int channelsi = 0; channelsi < total_channels; channelsi++) {
|
||||
const float *src = (const float *)JACK_jack_port_get_buffer(ports[channelsi], nframes);
|
||||
if (src) {
|
||||
float *dst = buffer + channelsi;
|
||||
for (int framesi = 0; framesi < total_frames; framesi++) {
|
||||
*dst = *(src++);
|
||||
dst += total_channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buflen;
|
||||
}
|
||||
|
||||
static void JACK_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
// do nothing, the data will just be replaced next callback.
|
||||
}
|
||||
|
||||
static void JACK_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->client) {
|
||||
JACK_jack_deactivate(device->hidden->client);
|
||||
|
||||
if (device->hidden->sdlports) {
|
||||
const int channels = device->spec.channels;
|
||||
int i;
|
||||
for (i = 0; i < channels; i++) {
|
||||
JACK_jack_port_unregister(device->hidden->client, device->hidden->sdlports[i]);
|
||||
}
|
||||
SDL_free(device->hidden->sdlports);
|
||||
}
|
||||
|
||||
JACK_jack_client_close(device->hidden->client);
|
||||
}
|
||||
|
||||
SDL_free(device->hidden->iobuffer);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
|
||||
SDL_AudioThreadFinalize(device);
|
||||
}
|
||||
}
|
||||
|
||||
// !!! FIXME: unify this (PulseAudio has a getAppName, Pipewire has a thing, etc)
|
||||
static const char *GetJackAppName(void)
|
||||
{
|
||||
return SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
|
||||
}
|
||||
|
||||
static bool JACK_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
/* Note that JACK uses "output" for recording devices (they output audio
|
||||
data to us) and "input" for playback (we input audio data to them).
|
||||
Likewise, SDL's playback port will be "output" (we write data out)
|
||||
and recording will be "input" (we read data in). */
|
||||
const bool recording = device->recording;
|
||||
const unsigned long sysportflags = recording ? JackPortIsOutput : JackPortIsInput;
|
||||
const unsigned long sdlportflags = recording ? JackPortIsInput : JackPortIsOutput;
|
||||
const JackProcessCallback callback = recording ? jackProcessRecordingCallback : jackProcessPlaybackCallback;
|
||||
const char *sdlportstr = recording ? "input" : "output";
|
||||
const char **devports = NULL;
|
||||
int *audio_ports;
|
||||
jack_client_t *client = NULL;
|
||||
jack_status_t status;
|
||||
int channels = 0;
|
||||
int ports = 0;
|
||||
int i;
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
client = JACK_jack_client_open(GetJackAppName(), JackNoStartServer, &status, NULL);
|
||||
device->hidden->client = client;
|
||||
if (!client) {
|
||||
return SDL_SetError("Can't open JACK client");
|
||||
}
|
||||
|
||||
devports = JACK_jack_get_ports(client, NULL, NULL, JackPortIsPhysical | sysportflags);
|
||||
if (!devports || !devports[0]) {
|
||||
return SDL_SetError("No physical JACK ports available");
|
||||
}
|
||||
|
||||
while (devports[++ports]) {
|
||||
// spin to count devports
|
||||
}
|
||||
|
||||
// Filter out non-audio ports
|
||||
audio_ports = SDL_calloc(ports, sizeof(*audio_ports));
|
||||
for (i = 0; i < ports; i++) {
|
||||
const jack_port_t *dport = JACK_jack_port_by_name(client, devports[i]);
|
||||
const char *type = JACK_jack_port_type(dport);
|
||||
const int len = SDL_strlen(type);
|
||||
// See if type ends with "audio"
|
||||
if (len >= 5 && !SDL_memcmp(type + len - 5, "audio", 5)) {
|
||||
audio_ports[channels++] = i;
|
||||
}
|
||||
}
|
||||
if (channels == 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("No physical JACK ports available");
|
||||
}
|
||||
|
||||
// Jack pretty much demands what it wants.
|
||||
device->spec.format = SDL_AUDIO_F32;
|
||||
device->spec.freq = JACK_jack_get_sample_rate(client);
|
||||
device->spec.channels = channels;
|
||||
device->sample_frames = JACK_jack_get_buffer_size(client);
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
if (!recording) {
|
||||
device->hidden->iobuffer = (float *)SDL_calloc(1, device->buffer_size);
|
||||
if (!device->hidden->iobuffer) {
|
||||
SDL_free(audio_ports);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Build SDL's ports, which we will connect to the device ports.
|
||||
device->hidden->sdlports = (jack_port_t **)SDL_calloc(channels, sizeof(jack_port_t *));
|
||||
if (!device->hidden->sdlports) {
|
||||
SDL_free(audio_ports);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < channels; i++) {
|
||||
char portname[32];
|
||||
(void)SDL_snprintf(portname, sizeof(portname), "sdl_jack_%s_%d", sdlportstr, i);
|
||||
device->hidden->sdlports[i] = JACK_jack_port_register(client, portname, JACK_DEFAULT_AUDIO_TYPE, sdlportflags, 0);
|
||||
if (device->hidden->sdlports[i] == NULL) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("jack_port_register failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (JACK_jack_set_buffer_size_callback(client, jackBufferSizeCallback, device) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("JACK: Couldn't set buffer size callback");
|
||||
} else if (JACK_jack_set_sample_rate_callback(client, jackSampleRateCallback, device) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("JACK: Couldn't set sample rate callback");
|
||||
} else if (JACK_jack_set_process_callback(client, callback, device) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("JACK: Couldn't set process callback");
|
||||
}
|
||||
|
||||
JACK_jack_on_shutdown(client, jackShutdownCallback, device);
|
||||
|
||||
if (JACK_jack_activate(client) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("Failed to activate JACK client");
|
||||
}
|
||||
|
||||
// once activated, we can connect all the ports.
|
||||
for (i = 0; i < channels; i++) {
|
||||
const char *sdlport = JACK_jack_port_name(device->hidden->sdlports[i]);
|
||||
const char *srcport = recording ? devports[audio_ports[i]] : sdlport;
|
||||
const char *dstport = recording ? sdlport : devports[audio_ports[i]];
|
||||
if (JACK_jack_connect(client, srcport, dstport) != 0) {
|
||||
SDL_free(audio_ports);
|
||||
return SDL_SetError("Couldn't connect JACK ports: %s => %s", srcport, dstport);
|
||||
}
|
||||
}
|
||||
|
||||
// don't need these anymore.
|
||||
JACK_jack_free(devports);
|
||||
SDL_free(audio_ports);
|
||||
|
||||
// We're ready to rock and roll. :-)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void JACK_Deinitialize(void)
|
||||
{
|
||||
UnloadJackLibrary();
|
||||
}
|
||||
|
||||
static bool JACK_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!LoadJackLibrary()) {
|
||||
return false;
|
||||
} else {
|
||||
// Make sure a JACK server is running and available.
|
||||
jack_status_t status;
|
||||
jack_client_t *client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL);
|
||||
if (!client) {
|
||||
UnloadJackLibrary();
|
||||
return SDL_SetError("Can't open JACK client");
|
||||
}
|
||||
JACK_jack_client_close(client);
|
||||
}
|
||||
|
||||
impl->OpenDevice = JACK_OpenDevice;
|
||||
impl->GetDeviceBuf = JACK_GetDeviceBuf;
|
||||
impl->PlayDevice = JACK_PlayDevice;
|
||||
impl->CloseDevice = JACK_CloseDevice;
|
||||
impl->Deinitialize = JACK_Deinitialize;
|
||||
impl->RecordDevice = JACK_RecordDevice;
|
||||
impl->FlushRecording = JACK_FlushRecording;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
impl->OnlyHasDefaultRecordingDevice = true;
|
||||
impl->HasRecordingSupport = true;
|
||||
impl->ProvidesOwnCallbackThread = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap JACK_bootstrap = {
|
||||
"jack", "JACK Audio Connection Kit", JACK_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_JACK
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#ifndef SDL_jackaudio_h_
|
||||
#define SDL_jackaudio_h_
|
||||
|
||||
#include <jack/jack.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
jack_client_t *client;
|
||||
jack_port_t **sdlports;
|
||||
float *iobuffer;
|
||||
};
|
||||
|
||||
#endif // SDL_jackaudio_h_
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_N3DS
|
||||
|
||||
// N3DS Audio driver
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_n3dsaudio.h"
|
||||
|
||||
#define N3DSAUDIO_DRIVER_NAME "n3ds"
|
||||
|
||||
static dspHookCookie dsp_hook;
|
||||
static SDL_AudioDevice *audio_device;
|
||||
|
||||
// fully local functions related to the wavebufs / DSP, not the same as the `device->lock` SDL_Mutex!
|
||||
static SDL_INLINE void contextLock(SDL_AudioDevice *device)
|
||||
{
|
||||
LightLock_Lock(&device->hidden->lock);
|
||||
}
|
||||
|
||||
static SDL_INLINE void contextUnlock(SDL_AudioDevice *device)
|
||||
{
|
||||
LightLock_Unlock(&device->hidden->lock);
|
||||
}
|
||||
|
||||
static void N3DSAUD_DspHook(DSP_HookType hook)
|
||||
{
|
||||
if (hook == DSPHOOK_ONCANCEL) {
|
||||
contextLock(audio_device);
|
||||
audio_device->hidden->isCancelled = true;
|
||||
SDL_AudioDeviceDisconnected(audio_device);
|
||||
CondVar_Broadcast(&audio_device->hidden->cv);
|
||||
contextUnlock(audio_device);
|
||||
}
|
||||
}
|
||||
|
||||
static void AudioFrameFinished(void *vdevice)
|
||||
{
|
||||
bool shouldBroadcast = false;
|
||||
unsigned i;
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *)vdevice;
|
||||
|
||||
contextLock(device);
|
||||
|
||||
for (i = 0; i < NUM_BUFFERS; i++) {
|
||||
if (device->hidden->waveBuf[i].status == NDSP_WBUF_DONE) {
|
||||
device->hidden->waveBuf[i].status = NDSP_WBUF_FREE;
|
||||
shouldBroadcast = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldBroadcast) {
|
||||
CondVar_Broadcast(&device->hidden->cv);
|
||||
}
|
||||
|
||||
contextUnlock(device);
|
||||
}
|
||||
|
||||
static bool N3DSAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
Result ndsp_init_res;
|
||||
Uint8 *data_vaddr;
|
||||
float mix[12];
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialise the DSP service
|
||||
ndsp_init_res = ndspInit();
|
||||
if (R_FAILED(ndsp_init_res)) {
|
||||
if ((R_SUMMARY(ndsp_init_res) == RS_NOTFOUND) && (R_MODULE(ndsp_init_res) == RM_DSP)) {
|
||||
return SDL_SetError("DSP init failed: dspfirm.cdc missing!");
|
||||
} else {
|
||||
return SDL_SetError("DSP init failed. Error code: 0x%lX", ndsp_init_res);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise internal state
|
||||
LightLock_Init(&device->hidden->lock);
|
||||
CondVar_Init(&device->hidden->cv);
|
||||
|
||||
if (device->spec.channels > 2) {
|
||||
device->spec.channels = 2;
|
||||
}
|
||||
|
||||
Uint32 format = 0;
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (test_format == SDL_AUDIO_S8) { // Signed 8-bit audio supported
|
||||
format = (device->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM8 : NDSP_FORMAT_MONO_PCM8;
|
||||
break;
|
||||
} else if (test_format == SDL_AUDIO_S16) { // Signed 16-bit audio supported
|
||||
format = (device->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM16 : NDSP_FORMAT_MONO_PCM16;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) { // shouldn't happen, but just in case...
|
||||
return SDL_SetError("No supported audio format found.");
|
||||
}
|
||||
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
// Allocate mixing buffer
|
||||
if (device->buffer_size >= SDL_MAX_UINT32 / 2) {
|
||||
return SDL_SetError("Mixing buffer is too large.");
|
||||
}
|
||||
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
|
||||
data_vaddr = (Uint8 *)linearAlloc(device->buffer_size * NUM_BUFFERS);
|
||||
if (!data_vaddr) {
|
||||
return SDL_OutOfMemory();
|
||||
}
|
||||
|
||||
SDL_memset(data_vaddr, 0, device->buffer_size * NUM_BUFFERS);
|
||||
DSP_FlushDataCache(data_vaddr, device->buffer_size * NUM_BUFFERS);
|
||||
|
||||
device->hidden->nextbuf = 0;
|
||||
|
||||
ndspChnReset(0);
|
||||
|
||||
ndspChnSetInterp(0, NDSP_INTERP_LINEAR);
|
||||
ndspChnSetRate(0, device->spec.freq);
|
||||
ndspChnSetFormat(0, format);
|
||||
|
||||
SDL_zeroa(mix);
|
||||
mix[0] = mix[1] = 1.0f;
|
||||
ndspChnSetMix(0, mix);
|
||||
|
||||
SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
|
||||
|
||||
const int sample_frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
for (unsigned i = 0; i < NUM_BUFFERS; i++) {
|
||||
device->hidden->waveBuf[i].data_vaddr = data_vaddr;
|
||||
device->hidden->waveBuf[i].nsamples = device->buffer_size / sample_frame_size;
|
||||
data_vaddr += device->buffer_size;
|
||||
}
|
||||
|
||||
// Setup callback
|
||||
audio_device = device;
|
||||
ndspSetCallback(AudioFrameFinished, device);
|
||||
dspHook(&dsp_hook, N3DSAUD_DspHook);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool N3DSAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
contextLock(device);
|
||||
|
||||
const size_t nextbuf = device->hidden->nextbuf;
|
||||
|
||||
if (device->hidden->isCancelled ||
|
||||
device->hidden->waveBuf[nextbuf].status != NDSP_WBUF_FREE) {
|
||||
contextUnlock(device);
|
||||
return true; // !!! FIXME: is this a fatal error? If so, this should return false.
|
||||
}
|
||||
|
||||
device->hidden->nextbuf = (nextbuf + 1) % NUM_BUFFERS;
|
||||
|
||||
contextUnlock(device);
|
||||
|
||||
SDL_memcpy((void *)device->hidden->waveBuf[nextbuf].data_vaddr, buffer, buflen);
|
||||
DSP_FlushDataCache(device->hidden->waveBuf[nextbuf].data_vaddr, buflen);
|
||||
|
||||
ndspChnWaveBufAdd(0, &device->hidden->waveBuf[nextbuf]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool N3DSAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
contextLock(device);
|
||||
while (!device->hidden->isCancelled && !SDL_GetAtomicInt(&device->shutdown) &&
|
||||
device->hidden->waveBuf[device->hidden->nextbuf].status != NDSP_WBUF_FREE) {
|
||||
CondVar_Wait(&device->hidden->cv, &device->hidden->lock);
|
||||
}
|
||||
contextUnlock(device);
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *N3DSAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static void N3DSAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (!device->hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
contextLock(device);
|
||||
|
||||
dspUnhook(&dsp_hook);
|
||||
ndspSetCallback(NULL, NULL);
|
||||
|
||||
if (!device->hidden->isCancelled) {
|
||||
ndspChnReset(0);
|
||||
SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
|
||||
CondVar_Broadcast(&device->hidden->cv);
|
||||
}
|
||||
|
||||
contextUnlock(device);
|
||||
|
||||
ndspExit();
|
||||
|
||||
if (device->hidden->waveBuf[0].data_vaddr) {
|
||||
linearFree((void *)device->hidden->waveBuf[0].data_vaddr);
|
||||
}
|
||||
|
||||
if (device->hidden->mixbuf) {
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
device->hidden->mixbuf = NULL;
|
||||
}
|
||||
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
|
||||
static void N3DSAUDIO_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
s32 current_priority = 0x30;
|
||||
svcGetThreadPriority(¤t_priority, CUR_THREAD_HANDLE);
|
||||
current_priority--;
|
||||
// 0x18 is reserved for video, 0x30 is the default for main thread
|
||||
current_priority = SDL_clamp(current_priority, 0x19, 0x2F);
|
||||
svcSetThreadPriority(CUR_THREAD_HANDLE, current_priority);
|
||||
}
|
||||
|
||||
static bool N3DSAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = N3DSAUDIO_OpenDevice;
|
||||
impl->PlayDevice = N3DSAUDIO_PlayDevice;
|
||||
impl->WaitDevice = N3DSAUDIO_WaitDevice;
|
||||
impl->GetDeviceBuf = N3DSAUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = N3DSAUDIO_CloseDevice;
|
||||
impl->ThreadInit = N3DSAUDIO_ThreadInit;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
|
||||
// Should be possible, but micInit would fail
|
||||
impl->HasRecordingSupport = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap N3DSAUDIO_bootstrap = {
|
||||
N3DSAUDIO_DRIVER_NAME,
|
||||
"SDL N3DS audio driver",
|
||||
N3DSAUDIO_Init,
|
||||
0
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_N3DS
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_n3dsaudio_h
|
||||
#define SDL_n3dsaudio_h
|
||||
|
||||
#include <3ds.h>
|
||||
|
||||
#define NUM_BUFFERS 3 // -- Minimum 2!
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// Speaker data
|
||||
Uint8 *mixbuf;
|
||||
Uint32 nextbuf;
|
||||
ndspWaveBuf waveBuf[NUM_BUFFERS];
|
||||
LightLock lock;
|
||||
CondVar cv;
|
||||
bool isCancelled;
|
||||
};
|
||||
|
||||
#endif // SDL_n3dsaudio_h
|
||||
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_NETBSD
|
||||
|
||||
// Driver for native NetBSD audio(4).
|
||||
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/audioio.h>
|
||||
|
||||
#include "../../core/unix/SDL_poll.h"
|
||||
#include "../SDL_audiodev_c.h"
|
||||
#include "SDL_netbsdaudio.h"
|
||||
|
||||
//#define DEBUG_AUDIO
|
||||
|
||||
static void NETBSDAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
SDL_EnumUnixAudioDevices(false, NULL);
|
||||
}
|
||||
|
||||
static void NETBSDAUDIO_Status(SDL_AudioDevice *device)
|
||||
{
|
||||
#ifdef DEBUG_AUDIO
|
||||
/* *INDENT-OFF* */ // clang-format off
|
||||
audio_info_t info;
|
||||
const struct audio_prinfo *prinfo;
|
||||
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
|
||||
fprintf(stderr, "AUDIO_GETINFO failed.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
prinfo = device->recording ? &info.record : &info.play;
|
||||
|
||||
fprintf(stderr, "\n"
|
||||
"[%s info]\n"
|
||||
"buffer size : %d bytes\n"
|
||||
"sample rate : %i Hz\n"
|
||||
"channels : %i\n"
|
||||
"precision : %i-bit\n"
|
||||
"encoding : 0x%x\n"
|
||||
"seek : %i\n"
|
||||
"sample count : %i\n"
|
||||
"EOF count : %i\n"
|
||||
"paused : %s\n"
|
||||
"error occurred : %s\n"
|
||||
"waiting : %s\n"
|
||||
"active : %s\n"
|
||||
"",
|
||||
device->recording ? "record" : "play",
|
||||
prinfo->buffer_size,
|
||||
prinfo->sample_rate,
|
||||
prinfo->channels,
|
||||
prinfo->precision,
|
||||
prinfo->encoding,
|
||||
prinfo->seek,
|
||||
prinfo->samples,
|
||||
prinfo->eof,
|
||||
prinfo->pause ? "yes" : "no",
|
||||
prinfo->error ? "yes" : "no",
|
||||
prinfo->waiting ? "yes" : "no",
|
||||
prinfo->active ? "yes" : "no");
|
||||
|
||||
fprintf(stderr, "\n"
|
||||
"[audio info]\n"
|
||||
"monitor_gain : %i\n"
|
||||
"hw block size : %d bytes\n"
|
||||
"hi watermark : %i\n"
|
||||
"lo watermark : %i\n"
|
||||
"audio mode : %s\n"
|
||||
"",
|
||||
info.monitor_gain,
|
||||
info.blocksize,
|
||||
info.hiwat, info.lowat,
|
||||
(info.mode == AUMODE_PLAY) ? "PLAY"
|
||||
: (info.mode == AUMODE_RECORD) ? "RECORD"
|
||||
: (info.mode == AUMODE_PLAY_ALL ? "PLAY_ALL" : "?"));
|
||||
|
||||
fprintf(stderr, "\n"
|
||||
"[audio spec]\n"
|
||||
"format : 0x%x\n"
|
||||
"size : %u\n"
|
||||
"",
|
||||
device->spec.format,
|
||||
device->buffer_size);
|
||||
/* *INDENT-ON* */ // clang-format on
|
||||
|
||||
#endif // DEBUG_AUDIO
|
||||
}
|
||||
|
||||
static bool NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const bool recording = device->recording;
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
audio_info_t info;
|
||||
const int rc = ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info);
|
||||
if (rc < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
continue;
|
||||
}
|
||||
// Hmm, not much we can do - abort
|
||||
fprintf(stderr, "netbsdaudio WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
const size_t remain = (size_t)((recording ? info.record.seek : info.play.seek) * SDL_AUDIO_BYTESIZE(device->spec.format));
|
||||
if (!recording && (remain >= device->buffer_size)) {
|
||||
SDL_Delay(10);
|
||||
} else if (recording && (remain < device->buffer_size)) {
|
||||
SDL_Delay(10);
|
||||
} else {
|
||||
break; // ready to go!
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool NETBSDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
const int written = write(h->audio_fd, buffer, buflen);
|
||||
if (written != buflen) { // Treat even partial writes as fatal errors.
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Wrote %d bytes of audio data\n", written);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *NETBSDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static int NETBSDAUDIO_RecordDevice(SDL_AudioDevice *device, void *vbuffer, int buflen)
|
||||
{
|
||||
Uint8 *buffer = (Uint8 *)vbuffer;
|
||||
const int br = read(device->hidden->audio_fd, buffer, buflen);
|
||||
if (br == -1) {
|
||||
// Non recoverable error has occurred. It should be reported!!!
|
||||
perror("audio");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Recorded %d bytes of audio data\n", br);
|
||||
#endif
|
||||
return br;
|
||||
}
|
||||
|
||||
static void NETBSDAUDIO_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *h = device->hidden;
|
||||
audio_info_t info;
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) == 0) {
|
||||
size_t remain = (size_t)(info.record.seek * SDL_AUDIO_BYTESIZE(device->spec.format));
|
||||
while (remain > 0) {
|
||||
char buf[512];
|
||||
const size_t len = SDL_min(sizeof(buf), remain);
|
||||
const ssize_t br = read(h->audio_fd, buf, len);
|
||||
if (br <= 0) {
|
||||
break;
|
||||
}
|
||||
remain -= br;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void NETBSDAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->audio_fd >= 0) {
|
||||
close(device->hidden->audio_fd);
|
||||
}
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool NETBSDAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const bool recording = device->recording;
|
||||
int encoding = AUDIO_ENCODING_NONE;
|
||||
audio_info_t info, hwinfo;
|
||||
struct audio_prinfo *prinfo = recording ? &info.record : &info.play;
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
|
||||
const int flags = ((device->recording) ? O_RDONLY : O_WRONLY);
|
||||
device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC);
|
||||
if (device->hidden->audio_fd < 0) {
|
||||
return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno));
|
||||
}
|
||||
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
#ifdef AUDIO_GETFORMAT // Introduced in NetBSD 9.0
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_GETFORMAT, &hwinfo) != -1) {
|
||||
// Use the device's native sample rate so the kernel doesn't have to resample.
|
||||
device->spec.freq = recording ? hwinfo.record.sample_rate : hwinfo.play.sample_rate;
|
||||
}
|
||||
#endif
|
||||
|
||||
prinfo->sample_rate = device->spec.freq;
|
||||
prinfo->channels = device->spec.channels;
|
||||
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
switch (test_format) {
|
||||
case SDL_AUDIO_U8:
|
||||
encoding = AUDIO_ENCODING_ULINEAR;
|
||||
break;
|
||||
case SDL_AUDIO_S8:
|
||||
encoding = AUDIO_ENCODING_SLINEAR;
|
||||
break;
|
||||
case SDL_AUDIO_S16LE:
|
||||
encoding = AUDIO_ENCODING_SLINEAR_LE;
|
||||
break;
|
||||
case SDL_AUDIO_S16BE:
|
||||
encoding = AUDIO_ENCODING_SLINEAR_BE;
|
||||
break;
|
||||
case SDL_AUDIO_S32LE:
|
||||
encoding = AUDIO_ENCODING_SLINEAR_LE;
|
||||
break;
|
||||
case SDL_AUDIO_S32BE:
|
||||
encoding = AUDIO_ENCODING_SLINEAR_BE;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("%s: Unsupported audio format", "netbsd");
|
||||
}
|
||||
prinfo->encoding = encoding;
|
||||
prinfo->precision = SDL_AUDIO_BITSIZE(test_format);
|
||||
|
||||
info.hiwat = 5;
|
||||
info.lowat = 3;
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_SETINFO, &info) < 0) {
|
||||
return SDL_SetError("AUDIO_SETINFO failed for %s: %s", device->name, strerror(errno));
|
||||
}
|
||||
|
||||
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
|
||||
return SDL_SetError("AUDIO_GETINFO failed for %s: %s", device->name, strerror(errno));
|
||||
}
|
||||
|
||||
// Final spec used for the device.
|
||||
device->spec.format = test_format;
|
||||
device->spec.freq = prinfo->sample_rate;
|
||||
device->spec.channels = prinfo->channels;
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
if (!recording) {
|
||||
// Allocate mixing buffer
|
||||
device->hidden->mixlen = device->buffer_size;
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->hidden->mixlen);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
}
|
||||
|
||||
NETBSDAUDIO_Status(device);
|
||||
|
||||
return true; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static bool NETBSDAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->DetectDevices = NETBSDAUDIO_DetectDevices;
|
||||
impl->OpenDevice = NETBSDAUDIO_OpenDevice;
|
||||
impl->WaitDevice = NETBSDAUDIO_WaitDevice;
|
||||
impl->PlayDevice = NETBSDAUDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = NETBSDAUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = NETBSDAUDIO_CloseDevice;
|
||||
impl->WaitRecordingDevice = NETBSDAUDIO_WaitDevice;
|
||||
impl->RecordDevice = NETBSDAUDIO_RecordDevice;
|
||||
impl->FlushRecording = NETBSDAUDIO_FlushRecording;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap NETBSDAUDIO_bootstrap = {
|
||||
"netbsd", "NetBSD audio", NETBSDAUDIO_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_NETBSD
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_netbsdaudio_h_
|
||||
#define SDL_netbsdaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The file descriptor for the audio device
|
||||
int audio_fd;
|
||||
|
||||
// Raw mixing buffer
|
||||
Uint8 *mixbuf;
|
||||
int mixlen;
|
||||
|
||||
// Support for audio timing using a timer, in addition to SDL_IOReady()
|
||||
float frame_ticks;
|
||||
float next_frame;
|
||||
};
|
||||
|
||||
#define FUDGE_TICKS 10 // The scheduler overhead ticks per frame
|
||||
|
||||
#endif // SDL_netbsdaudio_h_
|
||||
@@ -0,0 +1,807 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_OPENSLES
|
||||
|
||||
// For more discussion of low latency audio on Android, see this:
|
||||
// https://googlesamples.github.io/android-audio-high-performance/guides/opensl_es.html
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_openslES.h"
|
||||
|
||||
#include "../../core/android/SDL_android.h"
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include <android/log.h>
|
||||
|
||||
|
||||
#define NUM_BUFFERS 2 // -- Don't lower this!
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
Uint8 *mixbuff;
|
||||
int next_buffer;
|
||||
Uint8 *pmixbuff[NUM_BUFFERS];
|
||||
SDL_Semaphore *playsem;
|
||||
};
|
||||
|
||||
#if 0
|
||||
#define LOG_TAG "SDL_openslES"
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
//#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)
|
||||
#define LOGV(...)
|
||||
#else
|
||||
#define LOGE(...)
|
||||
#define LOGI(...)
|
||||
#define LOGV(...)
|
||||
#endif
|
||||
|
||||
/*
|
||||
#define SL_SPEAKER_FRONT_LEFT ((SLuint32) 0x00000001)
|
||||
#define SL_SPEAKER_FRONT_RIGHT ((SLuint32) 0x00000002)
|
||||
#define SL_SPEAKER_FRONT_CENTER ((SLuint32) 0x00000004)
|
||||
#define SL_SPEAKER_LOW_FREQUENCY ((SLuint32) 0x00000008)
|
||||
#define SL_SPEAKER_BACK_LEFT ((SLuint32) 0x00000010)
|
||||
#define SL_SPEAKER_BACK_RIGHT ((SLuint32) 0x00000020)
|
||||
#define SL_SPEAKER_FRONT_LEFT_OF_CENTER ((SLuint32) 0x00000040)
|
||||
#define SL_SPEAKER_FRONT_RIGHT_OF_CENTER ((SLuint32) 0x00000080)
|
||||
#define SL_SPEAKER_BACK_CENTER ((SLuint32) 0x00000100)
|
||||
#define SL_SPEAKER_SIDE_LEFT ((SLuint32) 0x00000200)
|
||||
#define SL_SPEAKER_SIDE_RIGHT ((SLuint32) 0x00000400)
|
||||
#define SL_SPEAKER_TOP_CENTER ((SLuint32) 0x00000800)
|
||||
#define SL_SPEAKER_TOP_FRONT_LEFT ((SLuint32) 0x00001000)
|
||||
#define SL_SPEAKER_TOP_FRONT_CENTER ((SLuint32) 0x00002000)
|
||||
#define SL_SPEAKER_TOP_FRONT_RIGHT ((SLuint32) 0x00004000)
|
||||
#define SL_SPEAKER_TOP_BACK_LEFT ((SLuint32) 0x00008000)
|
||||
#define SL_SPEAKER_TOP_BACK_CENTER ((SLuint32) 0x00010000)
|
||||
#define SL_SPEAKER_TOP_BACK_RIGHT ((SLuint32) 0x00020000)
|
||||
*/
|
||||
#define SL_ANDROID_SPEAKER_STEREO (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT)
|
||||
#define SL_ANDROID_SPEAKER_QUAD (SL_ANDROID_SPEAKER_STEREO | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT)
|
||||
#define SL_ANDROID_SPEAKER_5DOT1 (SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY)
|
||||
#define SL_ANDROID_SPEAKER_7DOT1 (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT)
|
||||
|
||||
// engine interfaces
|
||||
static SLObjectItf engineObject = NULL;
|
||||
static SLEngineItf engineEngine = NULL;
|
||||
|
||||
// output mix interfaces
|
||||
static SLObjectItf outputMixObject = NULL;
|
||||
|
||||
// buffer queue player interfaces
|
||||
static SLObjectItf bqPlayerObject = NULL;
|
||||
static SLPlayItf bqPlayerPlay = NULL;
|
||||
static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL;
|
||||
#if 0
|
||||
static SLVolumeItf bqPlayerVolume;
|
||||
#endif
|
||||
|
||||
// recorder interfaces
|
||||
static SLObjectItf recorderObject = NULL;
|
||||
static SLRecordItf recorderRecord = NULL;
|
||||
static SLAndroidSimpleBufferQueueItf recorderBufferQueue = NULL;
|
||||
|
||||
#if 0
|
||||
static const char *sldevaudiorecorderstr = "SLES Audio Recorder";
|
||||
static const char *sldevaudioplayerstr = "SLES Audio Player";
|
||||
|
||||
#define SLES_DEV_AUDIO_RECORDER sldevaudiorecorderstr
|
||||
#define SLES_DEV_AUDIO_PLAYER sldevaudioplayerstr
|
||||
static void OPENSLES_DetectDevices( int recording )
|
||||
{
|
||||
LOGI( "openSLES_DetectDevices()" );
|
||||
if ( recording )
|
||||
addfn( SLES_DEV_AUDIO_RECORDER );
|
||||
else
|
||||
addfn( SLES_DEV_AUDIO_PLAYER );
|
||||
}
|
||||
#endif
|
||||
|
||||
static void OPENSLES_DestroyEngine(void)
|
||||
{
|
||||
LOGI("OPENSLES_DestroyEngine()");
|
||||
|
||||
// destroy output mix object, and invalidate all associated interfaces
|
||||
if (outputMixObject != NULL) {
|
||||
(*outputMixObject)->Destroy(outputMixObject);
|
||||
outputMixObject = NULL;
|
||||
}
|
||||
|
||||
// destroy engine object, and invalidate all associated interfaces
|
||||
if (engineObject != NULL) {
|
||||
(*engineObject)->Destroy(engineObject);
|
||||
engineObject = NULL;
|
||||
engineEngine = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool OPENSLES_CreateEngine(void)
|
||||
{
|
||||
const SLInterfaceID ids[1] = { SL_IID_VOLUME };
|
||||
const SLboolean req[1] = { SL_BOOLEAN_FALSE };
|
||||
SLresult result;
|
||||
|
||||
LOGI("openSLES_CreateEngine()");
|
||||
|
||||
// create engine
|
||||
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("slCreateEngine failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
LOGI("slCreateEngine OK");
|
||||
|
||||
// realize the engine
|
||||
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RealizeEngine failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
LOGI("RealizeEngine OK");
|
||||
|
||||
// get the engine interface, which is needed in order to create other objects
|
||||
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("EngineGetInterface failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
LOGI("EngineGetInterface OK");
|
||||
|
||||
// create output mix
|
||||
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("CreateOutputMix failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
LOGI("CreateOutputMix OK");
|
||||
|
||||
// realize the output mix
|
||||
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RealizeOutputMix failed: %d", result);
|
||||
goto error;
|
||||
}
|
||||
return true;
|
||||
|
||||
error:
|
||||
OPENSLES_DestroyEngine();
|
||||
return false;
|
||||
}
|
||||
|
||||
// this callback handler is called every time a buffer finishes recording
|
||||
static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = (struct SDL_PrivateAudioData *)context;
|
||||
|
||||
LOGV("SLES: Recording Callback");
|
||||
SDL_SignalSemaphore(audiodata->playsem);
|
||||
}
|
||||
|
||||
static void OPENSLES_DestroyPCMRecorder(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
SLresult result;
|
||||
|
||||
// stop recording
|
||||
if (recorderRecord != NULL) {
|
||||
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SetRecordState stopped: %d", result);
|
||||
}
|
||||
}
|
||||
|
||||
// destroy audio recorder object, and invalidate all associated interfaces
|
||||
if (recorderObject != NULL) {
|
||||
(*recorderObject)->Destroy(recorderObject);
|
||||
recorderObject = NULL;
|
||||
recorderRecord = NULL;
|
||||
recorderBufferQueue = NULL;
|
||||
}
|
||||
|
||||
if (audiodata->playsem) {
|
||||
SDL_DestroySemaphore(audiodata->playsem);
|
||||
audiodata->playsem = NULL;
|
||||
}
|
||||
|
||||
if (audiodata->mixbuff) {
|
||||
SDL_free(audiodata->mixbuff);
|
||||
}
|
||||
}
|
||||
|
||||
// !!! FIXME: make this non-blocking!
|
||||
static void SDLCALL RequestAndroidPermissionBlockingCallback(void *userdata, const char *permission, bool granted)
|
||||
{
|
||||
SDL_SetAtomicInt((SDL_AtomicInt *) userdata, granted ? 1 : -1);
|
||||
}
|
||||
|
||||
static bool OPENSLES_CreatePCMRecorder(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
SLDataFormat_PCM format_pcm;
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
|
||||
SLDataSink audioSnk;
|
||||
SLDataLocator_IODevice loc_dev;
|
||||
SLDataSource audioSrc;
|
||||
const SLInterfaceID ids[1] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
|
||||
const SLboolean req[1] = { SL_BOOLEAN_TRUE };
|
||||
SLresult result;
|
||||
int i;
|
||||
|
||||
// !!! FIXME: make this non-blocking!
|
||||
{
|
||||
SDL_AtomicInt permission_response;
|
||||
SDL_SetAtomicInt(&permission_response, 0);
|
||||
if (!SDL_RequestAndroidPermission("android.permission.RECORD_AUDIO", RequestAndroidPermissionBlockingCallback, &permission_response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (SDL_GetAtomicInt(&permission_response) == 0) {
|
||||
SDL_Delay(10);
|
||||
}
|
||||
|
||||
if (SDL_GetAtomicInt(&permission_response) < 0) {
|
||||
LOGE("This app doesn't have RECORD_AUDIO permission");
|
||||
return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
|
||||
}
|
||||
}
|
||||
|
||||
// Just go with signed 16-bit audio as it's the most compatible
|
||||
device->spec.format = SDL_AUDIO_S16;
|
||||
device->spec.channels = 1;
|
||||
//device->spec.freq = SL_SAMPLINGRATE_16 / 1000;*/
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
LOGI("Try to open %u hz %u bit %u channels %s samples %u",
|
||||
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
|
||||
device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
|
||||
|
||||
// configure audio source
|
||||
loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
|
||||
loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT;
|
||||
loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
|
||||
loc_dev.device = NULL;
|
||||
audioSrc.pLocator = &loc_dev;
|
||||
audioSrc.pFormat = NULL;
|
||||
|
||||
// configure audio sink
|
||||
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
loc_bufq.numBuffers = NUM_BUFFERS;
|
||||
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = device->spec.channels;
|
||||
format_pcm.samplesPerSec = device->spec.freq * 1000; // / kilo Hz to milli Hz
|
||||
format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
format_pcm.containerSize = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER;
|
||||
|
||||
audioSnk.pLocator = &loc_bufq;
|
||||
audioSnk.pFormat = &format_pcm;
|
||||
|
||||
// create audio recorder
|
||||
// (requires the RECORD_AUDIO permission)
|
||||
result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, &audioSnk, 1, ids, req);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("CreateAudioRecorder failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// realize the recorder
|
||||
result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RealizeAudioPlayer failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// get the record interface
|
||||
result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_RECORD interface get failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// get the buffer queue interface
|
||||
result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_BUFFERQUEUE interface get failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// register callback on the buffer queue
|
||||
// context is '(SDL_PrivateAudioData *)device->hidden'
|
||||
result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, device->hidden);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RegisterCallback failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// Create the audio buffer semaphore
|
||||
audiodata->playsem = SDL_CreateSemaphore(0);
|
||||
if (!audiodata->playsem) {
|
||||
LOGE("cannot create Semaphore!");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// Create the sound buffers
|
||||
audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size);
|
||||
if (!audiodata->mixbuff) {
|
||||
LOGE("mixbuffer allocate - out of memory");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_BUFFERS; i++) {
|
||||
audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size;
|
||||
}
|
||||
|
||||
// in case already recording, stop recording and clear buffer queue
|
||||
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Record set state failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// enqueue empty buffers to be filled by the recorder
|
||||
for (i = 0; i < NUM_BUFFERS; i++) {
|
||||
result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[i], device->buffer_size);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Record enqueue buffers failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
// start recording
|
||||
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Record set state failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
failed:
|
||||
return SDL_SetError("Open device failed!");
|
||||
}
|
||||
|
||||
// this callback handler is called every time a buffer finishes playing
|
||||
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = (struct SDL_PrivateAudioData *)context;
|
||||
|
||||
LOGV("SLES: Playback Callback");
|
||||
SDL_SignalSemaphore(audiodata->playsem);
|
||||
}
|
||||
|
||||
static void OPENSLES_DestroyPCMPlayer(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
// set the player's state to 'stopped'
|
||||
if (bqPlayerPlay != NULL) {
|
||||
const SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SetPlayState stopped failed: %d", result);
|
||||
}
|
||||
}
|
||||
|
||||
// destroy buffer queue audio player object, and invalidate all associated interfaces
|
||||
if (bqPlayerObject != NULL) {
|
||||
(*bqPlayerObject)->Destroy(bqPlayerObject);
|
||||
|
||||
bqPlayerObject = NULL;
|
||||
bqPlayerPlay = NULL;
|
||||
bqPlayerBufferQueue = NULL;
|
||||
}
|
||||
|
||||
if (audiodata->playsem) {
|
||||
SDL_DestroySemaphore(audiodata->playsem);
|
||||
audiodata->playsem = NULL;
|
||||
}
|
||||
|
||||
if (audiodata->mixbuff) {
|
||||
SDL_free(audiodata->mixbuff);
|
||||
}
|
||||
}
|
||||
|
||||
static bool OPENSLES_CreatePCMPlayer(SDL_AudioDevice *device)
|
||||
{
|
||||
/* If we want to add floating point audio support (requires API level 21)
|
||||
it can be done as described here:
|
||||
https://developer.android.com/ndk/guides/audio/opensl/android-extensions.html#floating-point
|
||||
*/
|
||||
if (SDL_GetAndroidSDKVersion() >= 21) {
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
SDL_AudioFormat test_format;
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (SDL_AUDIO_ISSIGNED(test_format)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
// Didn't find a compatible format :
|
||||
LOGI("No compatible audio format, using signed 16-bit audio");
|
||||
test_format = SDL_AUDIO_S16;
|
||||
}
|
||||
device->spec.format = test_format;
|
||||
} else {
|
||||
// Just go with signed 16-bit audio as it's the most compatible
|
||||
device->spec.format = SDL_AUDIO_S16;
|
||||
}
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
LOGI("Try to open %u hz %s %u bit %u channels %s samples %u",
|
||||
device->spec.freq, SDL_AUDIO_ISFLOAT(device->spec.format) ? "float" : "pcm", SDL_AUDIO_BITSIZE(device->spec.format),
|
||||
device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
|
||||
|
||||
// configure audio source
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
|
||||
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
loc_bufq.numBuffers = NUM_BUFFERS;
|
||||
|
||||
SLDataFormat_PCM format_pcm;
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = device->spec.channels;
|
||||
format_pcm.samplesPerSec = device->spec.freq * 1000; // / kilo Hz to milli Hz
|
||||
format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
format_pcm.containerSize = SDL_AUDIO_BITSIZE(device->spec.format);
|
||||
|
||||
if (SDL_AUDIO_ISBIGENDIAN(device->spec.format)) {
|
||||
format_pcm.endianness = SL_BYTEORDER_BIGENDIAN;
|
||||
} else {
|
||||
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
}
|
||||
|
||||
switch (device->spec.channels) {
|
||||
case 1:
|
||||
format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT;
|
||||
break;
|
||||
case 2:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_STEREO;
|
||||
break;
|
||||
case 3:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_STEREO | SL_SPEAKER_FRONT_CENTER;
|
||||
break;
|
||||
case 4:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_QUAD;
|
||||
break;
|
||||
case 5:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER;
|
||||
break;
|
||||
case 6:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_5DOT1;
|
||||
break;
|
||||
case 7:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_BACK_CENTER;
|
||||
break;
|
||||
case 8:
|
||||
format_pcm.channelMask = SL_ANDROID_SPEAKER_7DOT1;
|
||||
break;
|
||||
default:
|
||||
// Unknown number of channels, fall back to stereo
|
||||
device->spec.channels = 2;
|
||||
format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
break;
|
||||
}
|
||||
|
||||
SLDataSink audioSnk;
|
||||
SLDataSource audioSrc;
|
||||
audioSrc.pFormat = (void *)&format_pcm;
|
||||
|
||||
SLAndroidDataFormat_PCM_EX format_pcm_ex;
|
||||
if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
|
||||
// Copy all setup into PCM EX structure
|
||||
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
format_pcm_ex.endianness = format_pcm.endianness;
|
||||
format_pcm_ex.channelMask = format_pcm.channelMask;
|
||||
format_pcm_ex.numChannels = format_pcm.numChannels;
|
||||
format_pcm_ex.sampleRate = format_pcm.samplesPerSec;
|
||||
format_pcm_ex.bitsPerSample = format_pcm.bitsPerSample;
|
||||
format_pcm_ex.containerSize = format_pcm.containerSize;
|
||||
format_pcm_ex.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
|
||||
audioSrc.pFormat = (void *)&format_pcm_ex;
|
||||
}
|
||||
|
||||
audioSrc.pLocator = &loc_bufq;
|
||||
|
||||
// configure audio sink
|
||||
SLDataLocator_OutputMix loc_outmix;
|
||||
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
|
||||
loc_outmix.outputMix = outputMixObject;
|
||||
audioSnk.pLocator = &loc_outmix;
|
||||
audioSnk.pFormat = NULL;
|
||||
|
||||
// create audio player
|
||||
const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
|
||||
const SLboolean req[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
|
||||
SLresult result;
|
||||
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("CreateAudioPlayer failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// realize the player
|
||||
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RealizeAudioPlayer failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// get the play interface
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_PLAY interface get failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// get the buffer queue interface
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bqPlayerBufferQueue);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_BUFFERQUEUE interface get failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// register callback on the buffer queue
|
||||
// context is '(SDL_PrivateAudioData *)device->hidden'
|
||||
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, device->hidden);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("RegisterCallback failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// get the volume interface
|
||||
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("SL_IID_VOLUME interface get failed: %d", result);
|
||||
// goto failed;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
// Create the audio buffer semaphore
|
||||
audiodata->playsem = SDL_CreateSemaphore(NUM_BUFFERS - 1);
|
||||
if (!audiodata->playsem) {
|
||||
LOGE("cannot create Semaphore!");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
// Create the sound buffers
|
||||
audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size);
|
||||
if (!audiodata->mixbuff) {
|
||||
LOGE("mixbuffer allocate - out of memory");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_BUFFERS; i++) {
|
||||
audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size;
|
||||
}
|
||||
|
||||
// set the player's state to playing
|
||||
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Play set state failed: %d", result);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
failed:
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool OPENSLES_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (device->recording) {
|
||||
LOGI("OPENSLES_OpenDevice() for recording");
|
||||
return OPENSLES_CreatePCMRecorder(device);
|
||||
} else {
|
||||
bool ret;
|
||||
LOGI("OPENSLES_OpenDevice() for playback");
|
||||
ret = OPENSLES_CreatePCMPlayer(device);
|
||||
if (!ret) {
|
||||
// Another attempt to open the device with a lower frequency
|
||||
if (device->spec.freq > 48000) {
|
||||
OPENSLES_DestroyPCMPlayer(device);
|
||||
device->spec.freq = 48000;
|
||||
ret = OPENSLES_CreatePCMPlayer(device);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
return SDL_SetError("Open device failed!");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool OPENSLES_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
LOGV("OPENSLES_WaitDevice()");
|
||||
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
// this semaphore won't fire when the app is in the background (OPENSLES_PauseDevices was called).
|
||||
if (SDL_WaitSemaphoreTimeout(audiodata->playsem, 100)) {
|
||||
return true; // semaphore was signaled, let's go!
|
||||
}
|
||||
// Still waiting on the semaphore (or the system), check other things then wait again.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool OPENSLES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
LOGV("======OPENSLES_PlayDevice()======");
|
||||
|
||||
// Queue it up
|
||||
const SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, buflen);
|
||||
|
||||
audiodata->next_buffer++;
|
||||
if (audiodata->next_buffer >= NUM_BUFFERS) {
|
||||
audiodata->next_buffer = 0;
|
||||
}
|
||||
|
||||
// If Enqueue fails, callback won't be called.
|
||||
// Post the semaphore, not to run out of buffer
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
SDL_SignalSemaphore(audiodata->playsem);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// n playn sem
|
||||
// getbuf 0 - 1
|
||||
// fill buff 0 - 1
|
||||
// play 0 - 0 1
|
||||
// wait 1 0 0
|
||||
// getbuf 1 0 0
|
||||
// fill buff 1 0 0
|
||||
// play 0 0 0
|
||||
// wait
|
||||
//
|
||||
// okay..
|
||||
|
||||
static Uint8 *OPENSLES_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
LOGV("OPENSLES_GetDeviceBuf()");
|
||||
return audiodata->pmixbuff[audiodata->next_buffer];
|
||||
}
|
||||
|
||||
static int OPENSLES_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
|
||||
// Copy it to the output buffer
|
||||
SDL_assert(buflen == device->buffer_size);
|
||||
SDL_memcpy(buffer, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_size);
|
||||
|
||||
// Re-enqueue the buffer
|
||||
const SLresult result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_size);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("Record enqueue buffers failed: %d", result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
audiodata->next_buffer++;
|
||||
if (audiodata->next_buffer >= NUM_BUFFERS) {
|
||||
audiodata->next_buffer = 0;
|
||||
}
|
||||
|
||||
return device->buffer_size;
|
||||
}
|
||||
|
||||
static void OPENSLES_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// struct SDL_PrivateAudioData *audiodata = device->hidden;
|
||||
if (device->hidden) {
|
||||
if (device->recording) {
|
||||
LOGI("OPENSLES_CloseDevice() for recording");
|
||||
OPENSLES_DestroyPCMRecorder(device);
|
||||
} else {
|
||||
LOGI("OPENSLES_CloseDevice() for playing");
|
||||
OPENSLES_DestroyPCMPlayer(device);
|
||||
}
|
||||
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool OPENSLES_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
LOGI("OPENSLES_Init() called");
|
||||
|
||||
if (!OPENSLES_CreateEngine()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("OPENSLES_Init() - set pointers");
|
||||
|
||||
// Set the function pointers
|
||||
// impl->DetectDevices = OPENSLES_DetectDevices;
|
||||
impl->ThreadInit = Android_AudioThreadInit;
|
||||
impl->OpenDevice = OPENSLES_OpenDevice;
|
||||
impl->WaitDevice = OPENSLES_WaitDevice;
|
||||
impl->PlayDevice = OPENSLES_PlayDevice;
|
||||
impl->GetDeviceBuf = OPENSLES_GetDeviceBuf;
|
||||
impl->WaitRecordingDevice = OPENSLES_WaitDevice;
|
||||
impl->RecordDevice = OPENSLES_RecordDevice;
|
||||
impl->CloseDevice = OPENSLES_CloseDevice;
|
||||
impl->Deinitialize = OPENSLES_DestroyEngine;
|
||||
|
||||
// and the capabilities
|
||||
impl->HasRecordingSupport = true;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
impl->OnlyHasDefaultRecordingDevice = true;
|
||||
|
||||
LOGI("OPENSLES_Init() - success");
|
||||
|
||||
// this audio target is available.
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap OPENSLES_bootstrap = {
|
||||
"openslES", "OpenSL ES audio driver", OPENSLES_Init, false
|
||||
};
|
||||
|
||||
void OPENSLES_ResumeDevices(void)
|
||||
{
|
||||
if (bqPlayerPlay != NULL) {
|
||||
// set the player's state to 'playing'
|
||||
SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("OPENSLES_ResumeDevices failed: %d", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OPENSLES_PauseDevices(void)
|
||||
{
|
||||
if (bqPlayerPlay != NULL) {
|
||||
// set the player's state to 'paused'
|
||||
SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED);
|
||||
if (SL_RESULT_SUCCESS != result) {
|
||||
LOGE("OPENSLES_PauseDevices failed: %d", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_OPENSLES
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_openslesaudio_h_
|
||||
#define SDL_openslesaudio_h_
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_OPENSLES
|
||||
|
||||
extern void OPENSLES_ResumeDevices(void);
|
||||
extern void OPENSLES_PauseDevices(void);
|
||||
|
||||
#else
|
||||
|
||||
#define OPENSLES_ResumeDevices()
|
||||
#define OPENSLES_PauseDevices()
|
||||
|
||||
#endif
|
||||
|
||||
#endif // SDL_openslesaudio_h_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_pipewire_h_
|
||||
#define SDL_pipewire_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include <pipewire/pipewire.h>
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
struct pw_thread_loop *loop;
|
||||
struct pw_stream *stream;
|
||||
struct pw_context *context;
|
||||
|
||||
Sint32 stride; // Bytes-per-frame
|
||||
int stream_init_status;
|
||||
|
||||
// Set in GetDeviceBuf, filled in AudioThreadIterate, queued in PlayDevice
|
||||
struct pw_buffer *pw_buf;
|
||||
};
|
||||
|
||||
#endif // SDL_pipewire_h_
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_ps2audio.h"
|
||||
|
||||
#include <kernel.h>
|
||||
#include <audsrv.h>
|
||||
#include <ps2_audio_driver.h>
|
||||
|
||||
static bool PS2AUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// These are the native supported audio PS2 configs
|
||||
switch (device->spec.freq) {
|
||||
case 11025:
|
||||
case 12000:
|
||||
case 22050:
|
||||
case 24000:
|
||||
case 32000:
|
||||
case 44100:
|
||||
case 48000:
|
||||
break; // acceptable value, keep it
|
||||
default:
|
||||
device->spec.freq = 48000;
|
||||
break;
|
||||
}
|
||||
|
||||
device->sample_frames = 512;
|
||||
device->spec.channels = device->spec.channels == 1 ? 1 : 2;
|
||||
device->spec.format = device->spec.format == SDL_AUDIO_S8 ? SDL_AUDIO_S8 : SDL_AUDIO_S16;
|
||||
|
||||
struct audsrv_fmt_t format;
|
||||
format.bits = device->spec.format == SDL_AUDIO_S8 ? 8 : 16;
|
||||
format.freq = device->spec.freq;
|
||||
format.channels = device->spec.channels;
|
||||
|
||||
device->hidden->channel = audsrv_set_format(&format);
|
||||
audsrv_set_volume(MAX_VOLUME);
|
||||
|
||||
if (device->hidden->channel < 0) {
|
||||
return SDL_SetError("Couldn't reserve hardware channel");
|
||||
}
|
||||
|
||||
// Update the fragment size as size in bytes.
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
/* Allocate the mixing buffer. Its size and starting address must
|
||||
be a multiple of 64 bytes. Our sample count is already a multiple of
|
||||
64, so spec->size should be a multiple of 64 as well. */
|
||||
const int mixlen = device->buffer_size * NUM_BUFFERS;
|
||||
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
|
||||
if (!device->hidden->rawbuf) {
|
||||
return SDL_SetError("Couldn't allocate mixing buffer");
|
||||
}
|
||||
|
||||
SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen);
|
||||
for (int i = 0; i < NUM_BUFFERS; i++) {
|
||||
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool PS2AUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
// this returns number of bytes accepted or a negative error. We assume anything other than buflen is a fatal error.
|
||||
return (audsrv_play_audio((char *)buffer, buflen) == buflen);
|
||||
}
|
||||
|
||||
static bool PS2AUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
audsrv_wait_audio(device->buffer_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *PS2AUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer];
|
||||
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void PS2AUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->channel >= 0) {
|
||||
audsrv_stop_audio();
|
||||
device->hidden->channel = -1;
|
||||
}
|
||||
|
||||
if (device->hidden->rawbuf) {
|
||||
SDL_aligned_free(device->hidden->rawbuf);
|
||||
device->hidden->rawbuf = NULL;
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void PS2AUDIO_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
/* Increase the priority of this audio thread by 1 to put it
|
||||
ahead of other SDL threads. */
|
||||
const int32_t thid = GetThreadId();
|
||||
ee_thread_status_t status;
|
||||
if (ReferThreadStatus(thid, &status) == 0) {
|
||||
ChangeThreadPriority(thid, status.current_priority - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void PS2AUDIO_Deinitialize(void)
|
||||
{
|
||||
deinit_audio_driver();
|
||||
}
|
||||
|
||||
static bool PS2AUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (init_audio_driver() < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
impl->OpenDevice = PS2AUDIO_OpenDevice;
|
||||
impl->PlayDevice = PS2AUDIO_PlayDevice;
|
||||
impl->WaitDevice = PS2AUDIO_WaitDevice;
|
||||
impl->GetDeviceBuf = PS2AUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = PS2AUDIO_CloseDevice;
|
||||
impl->ThreadInit = PS2AUDIO_ThreadInit;
|
||||
impl->Deinitialize = PS2AUDIO_Deinitialize;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
return true; // this audio target is available.
|
||||
}
|
||||
|
||||
AudioBootStrap PS2AUDIO_bootstrap = {
|
||||
"ps2", "PS2 audio driver", PS2AUDIO_Init, false
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_ps2audio_h_
|
||||
#define SDL_ps2audio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#define NUM_BUFFERS 2
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The hardware output channel.
|
||||
int channel;
|
||||
// The raw allocated mixing buffer.
|
||||
Uint8 *rawbuf;
|
||||
// Individual mixing buffers.
|
||||
Uint8 *mixbufs[NUM_BUFFERS];
|
||||
// Index of the next available mixing buffer.
|
||||
int next_buffer;
|
||||
};
|
||||
|
||||
#endif // SDL_ps2audio_h_
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_PSP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../SDL_audiodev_c.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_pspaudio.h"
|
||||
|
||||
#include <pspaudio.h>
|
||||
#include <pspthreadman.h>
|
||||
|
||||
static bool isBasicAudioConfig(const SDL_AudioSpec *spec)
|
||||
{
|
||||
return spec->freq == 44100;
|
||||
}
|
||||
|
||||
static bool PSPAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// device only natively supports S16LSB
|
||||
device->spec.format = SDL_AUDIO_S16LE;
|
||||
|
||||
/* PSP has some limitations with the Audio. It fully supports 44.1KHz (Mono & Stereo),
|
||||
however with frequencies different than 44.1KHz, it just supports Stereo,
|
||||
so a resampler must be done for these scenarios */
|
||||
if (isBasicAudioConfig(&device->spec)) {
|
||||
// The sample count must be a multiple of 64.
|
||||
device->sample_frames = PSP_AUDIO_SAMPLE_ALIGN(device->sample_frames);
|
||||
// The number of channels (1 or 2).
|
||||
device->spec.channels = device->spec.channels == 1 ? 1 : 2;
|
||||
const int format = (device->spec.channels == 1) ? PSP_AUDIO_FORMAT_MONO : PSP_AUDIO_FORMAT_STEREO;
|
||||
device->hidden->channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, device->sample_frames, format);
|
||||
} else {
|
||||
// 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11050, 8000
|
||||
switch (device->spec.freq) {
|
||||
case 8000:
|
||||
case 11025:
|
||||
case 12000:
|
||||
case 16000:
|
||||
case 22050:
|
||||
case 24000:
|
||||
case 32000:
|
||||
case 44100:
|
||||
case 48000:
|
||||
break; // acceptable, keep it
|
||||
default:
|
||||
device->spec.freq = 48000;
|
||||
break;
|
||||
}
|
||||
// The number of samples to output in one output call (min 17, max 4111).
|
||||
device->sample_frames = device->sample_frames < 17 ? 17 : (device->sample_frames > 4111 ? 4111 : device->sample_frames);
|
||||
device->spec.channels = 2; // we're forcing the hardware to stereo.
|
||||
device->hidden->channel = sceAudioSRCChReserve(device->sample_frames, device->spec.freq, 2);
|
||||
}
|
||||
|
||||
if (device->hidden->channel < 0) {
|
||||
return SDL_SetError("Couldn't reserve hardware channel");
|
||||
}
|
||||
|
||||
// Update the fragment size as size in bytes.
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
/* Allocate the mixing buffer. Its size and starting address must
|
||||
be a multiple of 64 bytes. Our sample count is already a multiple of
|
||||
64, so spec->size should be a multiple of 64 as well. */
|
||||
const int mixlen = device->buffer_size * NUM_BUFFERS;
|
||||
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
|
||||
if (!device->hidden->rawbuf) {
|
||||
return SDL_SetError("Couldn't allocate mixing buffer");
|
||||
}
|
||||
|
||||
SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen);
|
||||
for (int i = 0; i < NUM_BUFFERS; i++) {
|
||||
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
int rc;
|
||||
if (!isBasicAudioConfig(&device->spec)) {
|
||||
SDL_assert(device->spec.channels == 2);
|
||||
rc = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, (void *) buffer);
|
||||
} else {
|
||||
rc = sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, (void *) buffer);
|
||||
}
|
||||
return (rc == 0);
|
||||
}
|
||||
|
||||
static bool PSPAUDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
return true; // Because we block when sending audio, there's no need for this function to do anything.
|
||||
}
|
||||
|
||||
static Uint8 *PSPAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer];
|
||||
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void PSPAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->channel >= 0) {
|
||||
if (!isBasicAudioConfig(&device->spec)) {
|
||||
sceAudioSRCChRelease();
|
||||
} else {
|
||||
sceAudioChRelease(device->hidden->channel);
|
||||
}
|
||||
device->hidden->channel = -1;
|
||||
}
|
||||
|
||||
if (device->hidden->rawbuf) {
|
||||
SDL_aligned_free(device->hidden->rawbuf);
|
||||
device->hidden->rawbuf = NULL;
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void PSPAUDIO_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
/* Increase the priority of this audio thread by 1 to put it
|
||||
ahead of other SDL threads. */
|
||||
const SceUID thid = sceKernelGetThreadId();
|
||||
SceKernelThreadInfo status;
|
||||
status.size = sizeof(SceKernelThreadInfo);
|
||||
if (sceKernelReferThreadStatus(thid, &status) == 0) {
|
||||
sceKernelChangeThreadPriority(thid, status.currentPriority - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static bool PSPAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = PSPAUDIO_OpenDevice;
|
||||
impl->PlayDevice = PSPAUDIO_PlayDevice;
|
||||
impl->WaitDevice = PSPAUDIO_WaitDevice;
|
||||
impl->GetDeviceBuf = PSPAUDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = PSPAUDIO_CloseDevice;
|
||||
impl->ThreadInit = PSPAUDIO_ThreadInit;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
//impl->HasRecordingSupport = true;
|
||||
//impl->OnlyHasDefaultRecordingDevice = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap PSPAUDIO_bootstrap = {
|
||||
"psp", "PSP audio driver", PSPAUDIO_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_PSP
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_pspaudio_h_
|
||||
#define SDL_pspaudio_h_
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#define NUM_BUFFERS 2
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The hardware output channel.
|
||||
int channel;
|
||||
// The raw allocated mixing buffer.
|
||||
Uint8 *rawbuf;
|
||||
// Individual mixing buffers.
|
||||
Uint8 *mixbufs[NUM_BUFFERS];
|
||||
// Index of the next available mixing buffer.
|
||||
int next_buffer;
|
||||
};
|
||||
|
||||
#endif // SDL_pspaudio_h_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_pulseaudio_h_
|
||||
#define SDL_pulseaudio_h_
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// pulseaudio structures
|
||||
pa_stream *stream;
|
||||
|
||||
// Raw mixing buffer
|
||||
Uint8 *mixbuf;
|
||||
|
||||
int bytes_requested; // bytes of data the hardware wants _now_.
|
||||
|
||||
const Uint8 *recordingbuf;
|
||||
int recordinglen;
|
||||
};
|
||||
|
||||
#endif // SDL_pulseaudio_h_
|
||||
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
// !!! FIXME: can this target support hotplugging?
|
||||
|
||||
#include "../../SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_QNX
|
||||
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sched.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/neutrino.h>
|
||||
#include <sys/asoundlib.h>
|
||||
|
||||
#include "SDL3/SDL_timer.h"
|
||||
#include "SDL3/SDL_audio.h"
|
||||
#include "../../core/unix/SDL_poll.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_qsa_audio.h"
|
||||
|
||||
// default channel communication parameters
|
||||
#define DEFAULT_CPARAMS_RATE 44100
|
||||
#define DEFAULT_CPARAMS_VOICES 1
|
||||
|
||||
#define DEFAULT_CPARAMS_FRAG_SIZE 4096
|
||||
#define DEFAULT_CPARAMS_FRAGS_MIN 1
|
||||
#define DEFAULT_CPARAMS_FRAGS_MAX 1
|
||||
|
||||
#define QSA_MAX_NAME_LENGTH 81+16 // Hardcoded in QSA, can't be changed
|
||||
|
||||
static bool QSA_SetError(const char *fn, int status)
|
||||
{
|
||||
return SDL_SetError("QSA: %s() failed: %s", fn, snd_strerror(status));
|
||||
}
|
||||
|
||||
// !!! FIXME: does this need to be here? Does the SDL version not work?
|
||||
static void QSA_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
// Increase default 10 priority to 25 to avoid jerky sound
|
||||
struct sched_param param;
|
||||
if (SchedGet(0, 0, ¶m) != -1) {
|
||||
param.sched_priority = param.sched_curpriority + 15;
|
||||
SchedSet(0, 0, SCHED_NOCHANGE, ¶m);
|
||||
}
|
||||
}
|
||||
|
||||
// PCM channel parameters initialize function
|
||||
static void QSA_InitAudioParams(snd_pcm_channel_params_t * cpars)
|
||||
{
|
||||
SDL_zerop(cpars);
|
||||
cpars->channel = SND_PCM_CHANNEL_PLAYBACK;
|
||||
cpars->mode = SND_PCM_MODE_BLOCK;
|
||||
cpars->start_mode = SND_PCM_START_DATA;
|
||||
cpars->stop_mode = SND_PCM_STOP_STOP;
|
||||
cpars->format.format = SND_PCM_SFMT_S16_LE;
|
||||
cpars->format.interleave = 1;
|
||||
cpars->format.rate = DEFAULT_CPARAMS_RATE;
|
||||
cpars->format.voices = DEFAULT_CPARAMS_VOICES;
|
||||
cpars->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE;
|
||||
cpars->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN;
|
||||
cpars->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX;
|
||||
}
|
||||
|
||||
// This function waits until it is possible to write a full sound buffer
|
||||
static bool QSA_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Setup timeout for playing one fragment equal to 2 seconds
|
||||
// If timeout occurred than something wrong with hardware or driver
|
||||
// For example, Vortex 8820 audio driver stucks on second DAC because
|
||||
// it doesn't exist !
|
||||
const int result = SDL_IOReady(device->hidden->audio_fd,
|
||||
device->recording ? SDL_IOR_READ : SDL_IOR_WRITE,
|
||||
2 * 1000);
|
||||
switch (result) {
|
||||
case -1:
|
||||
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "QSA: SDL_IOReady() failed: %s", strerror(errno));
|
||||
return false;
|
||||
case 0:
|
||||
device->hidden->timeout_on_wait = true; // !!! FIXME: Should we just disconnect the device in this case?
|
||||
break;
|
||||
default:
|
||||
device->hidden->timeout_on_wait = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
if (SDL_GetAtomicInt(&device->shutdown) || !device->hidden) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int towrite = buflen;
|
||||
|
||||
// Write the audio data, checking for EAGAIN (buffer full) and underrun
|
||||
while ((towrite > 0) && !SDL_GetAtomicInt(&device->shutdown));
|
||||
const int bw = snd_pcm_plugin_write(device->hidden->audio_handle, buffer, towrite);
|
||||
if (bw != towrite) {
|
||||
// Check if samples playback got stuck somewhere in hardware or in the audio device driver
|
||||
if ((errno == EAGAIN) && (bw == 0)) {
|
||||
if (device->hidden->timeout_on_wait) {
|
||||
return true; // oh well, try again next time. !!! FIXME: Should we just disconnect the device in this case?
|
||||
}
|
||||
}
|
||||
|
||||
// Check for errors or conditions
|
||||
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
|
||||
SDL_Delay(1); // Let a little CPU time go by and try to write again
|
||||
|
||||
// if we wrote some data
|
||||
towrite -= bw;
|
||||
buffer += bw * device->spec.channels;
|
||||
continue;
|
||||
} else if ((errno == EINVAL) || (errno == EIO)) {
|
||||
snd_pcm_channel_status_t cstatus;
|
||||
SDL_zero(cstatus);
|
||||
cstatus.channel = device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
|
||||
|
||||
int status = snd_pcm_plugin_status(device->hidden->audio_handle, &cstatus);
|
||||
if (status < 0) {
|
||||
QSA_SetError("snd_pcm_plugin_status", status);
|
||||
return false;
|
||||
} else if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY)) {
|
||||
status = snd_pcm_plugin_prepare(device->hidden->audio_handle, device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
|
||||
if (status < 0) {
|
||||
QSA_SetError("snd_pcm_plugin_prepare", status);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// we wrote all remaining data
|
||||
towrite -= bw;
|
||||
buffer += bw * device->spec.channels;
|
||||
}
|
||||
}
|
||||
|
||||
// If we couldn't write, assume fatal error for now
|
||||
return (towrite == 0);
|
||||
}
|
||||
|
||||
static Uint8 *QSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->pcm_buf;
|
||||
}
|
||||
|
||||
static void QSA_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->audio_handle) {
|
||||
#if _NTO_VERSION < 710
|
||||
// Finish playing available samples or cancel unread samples during recording
|
||||
snd_pcm_plugin_flush(device->hidden->audio_handle, device->recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
|
||||
#endif
|
||||
snd_pcm_close(device->hidden->audio_handle);
|
||||
}
|
||||
|
||||
SDL_free(device->hidden->pcm_buf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool QSA_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->recording) {
|
||||
return SDL_SetError("SDL recording support isn't available on QNX atm"); // !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in!
|
||||
}
|
||||
|
||||
SDL_assert(device->handle != NULL); // NULL used to mean "system default device" in SDL2; it does not mean that in SDL3.
|
||||
const Uint32 sdlhandle = (Uint32) ((size_t) device->handle);
|
||||
const uint32_t cardno = (uint32_t) (sdlhandle & 0xFFFF);
|
||||
const uint32_t deviceno = (uint32_t) ((sdlhandle >> 16) & 0xFFFF);
|
||||
const bool recording = device->recording;
|
||||
int status = 0;
|
||||
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, (sizeof (struct SDL_PrivateAudioData)));
|
||||
if (device->hidden == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize channel transfer parameters to default
|
||||
snd_pcm_channel_params_t cparams;
|
||||
QSA_InitAudioParams(&cparams);
|
||||
|
||||
// Open requested audio device
|
||||
status = snd_pcm_open(&device->hidden->audio_handle, cardno, deviceno, recording ? SND_PCM_OPEN_CAPTURE : SND_PCM_OPEN_PLAYBACK);
|
||||
if (status < 0) {
|
||||
device->hidden->audio_handle = NULL;
|
||||
return QSA_SetError("snd_pcm_open", status);
|
||||
}
|
||||
|
||||
// Try for a closest match on audio format
|
||||
SDL_AudioFormat test_format = 0;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
// if match found set format to equivalent QSA format
|
||||
switch (test_format) {
|
||||
#define CHECKFMT(sdlfmt, qsafmt) case SDL_AUDIO_##sdlfmt: cparams.format.format = SND_PCM_SFMT_##qsafmt; break
|
||||
CHECKFMT(U8, U8);
|
||||
CHECKFMT(S8, S8);
|
||||
CHECKFMT(S16LSB, S16_LE);
|
||||
CHECKFMT(S16MSB, S16_BE);
|
||||
CHECKFMT(S32LSB, S32_LE);
|
||||
CHECKFMT(S32MSB, S32_BE);
|
||||
CHECKFMT(F32LSB, FLOAT_LE);
|
||||
CHECKFMT(F32MSB, FLOAT_BE);
|
||||
#undef CHECKFMT
|
||||
default: continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// assumes test_format not 0 on success
|
||||
if (test_format == 0) {
|
||||
return SDL_SetError("QSA: Couldn't find any hardware audio formats");
|
||||
}
|
||||
|
||||
device->spec.format = test_format;
|
||||
|
||||
// Set mono/stereo/4ch/6ch/8ch audio
|
||||
cparams.format.voices = device->spec.channels;
|
||||
|
||||
// Set rate
|
||||
cparams.format.rate = device->spec.freq;
|
||||
|
||||
// Setup the transfer parameters according to cparams
|
||||
status = snd_pcm_plugin_params(device->hidden->audio_handle, &cparams);
|
||||
if (status < 0) {
|
||||
return QSA_SetError("snd_pcm_plugin_params", status);
|
||||
}
|
||||
|
||||
// Make sure channel is setup right one last time
|
||||
snd_pcm_channel_setup_t csetup;
|
||||
SDL_zero(csetup);
|
||||
csetup.channel = recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
|
||||
if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) {
|
||||
return SDL_SetError("QSA: Unable to setup channel");
|
||||
}
|
||||
|
||||
device->sample_frames = csetup.buf.block.frag_size;
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
device->hidden->pcm_buf = (Uint8 *) SDL_malloc(device->buffer_size);
|
||||
if (device->hidden->pcm_buf == NULL) {
|
||||
return false;
|
||||
}
|
||||
SDL_memset(device->hidden->pcm_buf, device->silence_value, device->buffer_size);
|
||||
|
||||
// get the file descriptor
|
||||
device->hidden->audio_fd = snd_pcm_file_descriptor(device->hidden->audio_handle, csetup.channel);
|
||||
if (device->hidden->audio_fd < 0) {
|
||||
return QSA_SetError("snd_pcm_file_descriptor", device->hidden->audio_fd);
|
||||
}
|
||||
|
||||
// Prepare an audio channel
|
||||
status = snd_pcm_plugin_prepare(device->hidden->audio_handle, csetup.channel)
|
||||
if (status < 0) {
|
||||
return QSA_SetError("snd_pcm_plugin_prepare", status);
|
||||
}
|
||||
|
||||
return true; // We're really ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static SDL_AudioFormat QnxFormatToSDLFormat(const int32_t qnxfmt)
|
||||
{
|
||||
switch (qnxfmt) {
|
||||
#define CHECKFMT(sdlfmt, qsafmt) case SND_PCM_SFMT_##qsafmt: return SDL_AUDIO_##sdlfmt
|
||||
CHECKFMT(U8, U8);
|
||||
CHECKFMT(S8, S8);
|
||||
CHECKFMT(S16LSB, S16_LE);
|
||||
CHECKFMT(S16MSB, S16_BE);
|
||||
CHECKFMT(S32LSB, S32_LE);
|
||||
CHECKFMT(S32MSB, S32_BE);
|
||||
CHECKFMT(F32LSB, FLOAT_LE);
|
||||
CHECKFMT(F32MSB, FLOAT_BE);
|
||||
#undef CHECKFMT
|
||||
default: break;
|
||||
}
|
||||
return SDL_AUDIO_S16; // oh well.
|
||||
}
|
||||
|
||||
static void QSA_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
// Detect amount of available devices
|
||||
// this value can be changed in the runtime
|
||||
int num_cards = 0;
|
||||
(void) snd_cards_list(NULL, 0, &alloc_num_cards);
|
||||
bool isstack = false;
|
||||
int *cards = SDL_small_alloc(int, num_cards, &isstack);
|
||||
if (!cards) {
|
||||
return; // we're in trouble.
|
||||
}
|
||||
int overflow_cards = 0;
|
||||
const int total_num_cards = snd_cards_list(cards, num_cards, &overflow_cards);
|
||||
// if overflow_cards > 0 or total_num_cards > num_cards, it changed at the last moment; oh well, we lost some.
|
||||
num_cards = SDL_min(num_cards, total_num_cards); // ...but make sure it didn't _shrink_.
|
||||
|
||||
// If io-audio manager is not running we will get 0 as number of available audio devices
|
||||
if (num_cards == 0) { // not any available audio devices?
|
||||
SDL_small_free(cards, isstack);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find requested devices by type
|
||||
for (int it = 0; it < num_cards; it++) {
|
||||
const int card = cards[it];
|
||||
for (uint32_t deviceno = 0; ; deviceno++) {
|
||||
int32_t status;
|
||||
char name[QSA_MAX_NAME_LENGTH];
|
||||
|
||||
status = snd_card_get_longname(card, name, sizeof (name));
|
||||
if (status == EOK) {
|
||||
snd_pcm_t *handle;
|
||||
|
||||
// Add device number to device name
|
||||
char fullname[QSA_MAX_NAME_LENGTH + 32];
|
||||
SDL_snprintf(fullname, sizeof (fullname), "%s d%d", name, (int) deviceno);
|
||||
|
||||
// Check if this device id could play anything
|
||||
bool recording = false;
|
||||
status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_PLAYBACK);
|
||||
if (status != EOK) { // no? See if it's a recording device instead.
|
||||
#if 0 // !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in!
|
||||
status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_CAPTURE);
|
||||
if (status == EOK) {
|
||||
recording = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (status == EOK) {
|
||||
SDL_AudioSpec spec;
|
||||
SDL_zero(spec);
|
||||
SDL_AudioSpec *pspec = &spec;
|
||||
snd_pcm_channel_setup_t csetup;
|
||||
SDL_zero(csetup);
|
||||
csetup.channel = recording ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
|
||||
|
||||
if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) {
|
||||
pspec = NULL; // go on without spec info.
|
||||
} else {
|
||||
spec.format = QnxFormatToSDLFormat(csetup.format.format);
|
||||
spec.channels = csetup.format.channels;
|
||||
spec.freq = csetup.format.rate;
|
||||
}
|
||||
|
||||
status = snd_pcm_close(handle);
|
||||
if (status == EOK) {
|
||||
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
|
||||
SDL_assert(card <= 0xFFFF);
|
||||
SDL_assert(deviceno <= 0xFFFF);
|
||||
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
|
||||
SDL_AddAudioDevice(recording, fullname, pspec, (void *) ((size_t) sdlhandle));
|
||||
}
|
||||
} else {
|
||||
// Check if we got end of devices list
|
||||
if (status == -ENOENT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_small_free(cards, isstack);
|
||||
|
||||
// Try to open the "preferred" devices, which will tell us the card/device pairs for the default devices.
|
||||
snd_pcm_t handle;
|
||||
int cardno, deviceno;
|
||||
if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_PLAYBACK) == 0) {
|
||||
snd_pcm_close(handle);
|
||||
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
|
||||
SDL_assert(cardno <= 0xFFFF);
|
||||
SDL_assert(deviceno <= 0xFFFF);
|
||||
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
|
||||
*default_playback = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
|
||||
}
|
||||
|
||||
if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_CAPTURE) == 0) {
|
||||
snd_pcm_close(handle);
|
||||
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
|
||||
SDL_assert(cardno <= 0xFFFF);
|
||||
SDL_assert(deviceno <= 0xFFFF);
|
||||
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
|
||||
*default_recording = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
|
||||
}
|
||||
}
|
||||
|
||||
static void QSA_Deinitialize(void)
|
||||
{
|
||||
// nothing to do here atm.
|
||||
}
|
||||
|
||||
static bool QSA_Init(SDL_AudioDriverImpl * impl)
|
||||
{
|
||||
impl->DetectDevices = QSA_DetectDevices;
|
||||
impl->OpenDevice = QSA_OpenDevice;
|
||||
impl->ThreadInit = QSA_ThreadInit;
|
||||
impl->WaitDevice = QSA_WaitDevice;
|
||||
impl->PlayDevice = QSA_PlayDevice;
|
||||
impl->GetDeviceBuf = QSA_GetDeviceBuf;
|
||||
impl->CloseDevice = QSA_CloseDevice;
|
||||
impl->Deinitialize = QSA_Deinitialize;
|
||||
|
||||
// !!! FIXME: most of this code has support for recording devices, but there's no RecordDevice, etc functions. Fill them in!
|
||||
//impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap QSAAUDIO_bootstrap = {
|
||||
"qsa", "QNX QSA Audio", QSA_Init, 0
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_QNX
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "../../SDL_internal.h"
|
||||
|
||||
#ifndef __SDL_QSA_AUDIO_H__
|
||||
#define __SDL_QSA_AUDIO_H__
|
||||
|
||||
#include <sys/asoundlib.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
snd_pcm_t *audio_handle; // The audio device handle
|
||||
int audio_fd; // The audio file descriptor, for selecting on
|
||||
bool timeout_on_wait; // Select timeout status
|
||||
Uint8 *pcm_buf; // Raw mixing buffer
|
||||
};
|
||||
|
||||
#endif // __SDL_QSA_AUDIO_H__
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_SNDIO
|
||||
|
||||
// OpenBSD sndio target
|
||||
|
||||
#ifdef HAVE_STDIO_H
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SIGNAL_H
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_sndioaudio.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
#endif
|
||||
|
||||
#ifndef INFTIM
|
||||
#define INFTIM -1
|
||||
#endif
|
||||
|
||||
#ifndef SIO_DEVANY
|
||||
#define SIO_DEVANY "default"
|
||||
#endif
|
||||
|
||||
static struct sio_hdl *(*SNDIO_sio_open)(const char *, unsigned int, int);
|
||||
static void (*SNDIO_sio_close)(struct sio_hdl *);
|
||||
static int (*SNDIO_sio_setpar)(struct sio_hdl *, struct sio_par *);
|
||||
static int (*SNDIO_sio_getpar)(struct sio_hdl *, struct sio_par *);
|
||||
static int (*SNDIO_sio_start)(struct sio_hdl *);
|
||||
static int (*SNDIO_sio_stop)(struct sio_hdl *);
|
||||
static size_t (*SNDIO_sio_read)(struct sio_hdl *, void *, size_t);
|
||||
static size_t (*SNDIO_sio_write)(struct sio_hdl *, const void *, size_t);
|
||||
static int (*SNDIO_sio_nfds)(struct sio_hdl *);
|
||||
static int (*SNDIO_sio_pollfd)(struct sio_hdl *, struct pollfd *, int);
|
||||
static int (*SNDIO_sio_revents)(struct sio_hdl *, struct pollfd *);
|
||||
static int (*SNDIO_sio_eof)(struct sio_hdl *);
|
||||
static void (*SNDIO_sio_initpar)(struct sio_par *);
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
static const char *sndio_library = SDL_AUDIO_DRIVER_SNDIO_DYNAMIC;
|
||||
static SDL_SharedObject *sndio_handle = NULL;
|
||||
|
||||
static bool load_sndio_sym(const char *fn, void **addr)
|
||||
{
|
||||
*addr = SDL_LoadFunction(sndio_handle, fn);
|
||||
if (!*addr) {
|
||||
return false; // Don't call SDL_SetError(): SDL_LoadFunction already did.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// cast funcs to char* first, to please GCC's strict aliasing rules.
|
||||
#define SDL_SNDIO_SYM(x) \
|
||||
if (!load_sndio_sym(#x, (void **)(char *)&SNDIO_##x)) \
|
||||
return false
|
||||
#else
|
||||
#define SDL_SNDIO_SYM(x) SNDIO_##x = x
|
||||
#endif
|
||||
|
||||
static bool load_sndio_syms(void)
|
||||
{
|
||||
SDL_SNDIO_SYM(sio_open);
|
||||
SDL_SNDIO_SYM(sio_close);
|
||||
SDL_SNDIO_SYM(sio_setpar);
|
||||
SDL_SNDIO_SYM(sio_getpar);
|
||||
SDL_SNDIO_SYM(sio_start);
|
||||
SDL_SNDIO_SYM(sio_stop);
|
||||
SDL_SNDIO_SYM(sio_read);
|
||||
SDL_SNDIO_SYM(sio_write);
|
||||
SDL_SNDIO_SYM(sio_nfds);
|
||||
SDL_SNDIO_SYM(sio_pollfd);
|
||||
SDL_SNDIO_SYM(sio_revents);
|
||||
SDL_SNDIO_SYM(sio_eof);
|
||||
SDL_SNDIO_SYM(sio_initpar);
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef SDL_SNDIO_SYM
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
|
||||
static void UnloadSNDIOLibrary(void)
|
||||
{
|
||||
if (sndio_handle) {
|
||||
SDL_UnloadObject(sndio_handle);
|
||||
sndio_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool LoadSNDIOLibrary(void)
|
||||
{
|
||||
bool result = true;
|
||||
if (!sndio_handle) {
|
||||
sndio_handle = SDL_LoadObject(sndio_library);
|
||||
if (!sndio_handle) {
|
||||
result = false; // Don't call SDL_SetError(): SDL_LoadObject already did.
|
||||
} else {
|
||||
result = load_sndio_syms();
|
||||
if (!result) {
|
||||
UnloadSNDIOLibrary();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void UnloadSNDIOLibrary(void)
|
||||
{
|
||||
}
|
||||
|
||||
static bool LoadSNDIOLibrary(void)
|
||||
{
|
||||
load_sndio_syms();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
|
||||
|
||||
static bool SNDIO_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
const bool recording = device->recording;
|
||||
|
||||
while (!SDL_GetAtomicInt(&device->shutdown)) {
|
||||
if (SNDIO_sio_eof(device->hidden->dev)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int nfds = SNDIO_sio_pollfd(device->hidden->dev, device->hidden->pfd, recording ? POLLIN : POLLOUT);
|
||||
if (nfds <= 0 || poll(device->hidden->pfd, nfds, 10) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int revents = SNDIO_sio_revents(device->hidden->dev, device->hidden->pfd);
|
||||
if (recording && (revents & POLLIN)) {
|
||||
break;
|
||||
} else if (!recording && (revents & POLLOUT)) {
|
||||
break;
|
||||
} else if (revents & POLLHUP) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SNDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
// !!! FIXME: this should be non-blocking so we can check device->shutdown.
|
||||
// this is set to blocking, because we _have_ to send the entire buffer down, but hopefully WaitDevice took most of the delay time.
|
||||
if (SNDIO_sio_write(device->hidden->dev, buffer, buflen) != buflen) {
|
||||
return false; // If we couldn't write, assume fatal error for now
|
||||
}
|
||||
#ifdef DEBUG_AUDIO
|
||||
fprintf(stderr, "Wrote %d bytes of audio data\n", written);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static int SNDIO_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
// We set recording devices non-blocking; this can safely return 0 in SDL3, but we'll check for EOF to cause a device disconnect.
|
||||
const size_t br = SNDIO_sio_read(device->hidden->dev, buffer, buflen);
|
||||
if ((br == 0) && SNDIO_sio_eof(device->hidden->dev)) {
|
||||
return -1;
|
||||
}
|
||||
return (int) br;
|
||||
}
|
||||
|
||||
static void SNDIO_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
char buf[512];
|
||||
while (!SDL_GetAtomicInt(&device->shutdown) && (SNDIO_sio_read(device->hidden->dev, buf, sizeof(buf)) > 0)) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
static Uint8 *SNDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
return device->hidden->mixbuf;
|
||||
}
|
||||
|
||||
static void SNDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->dev) {
|
||||
SNDIO_sio_stop(device->hidden->dev);
|
||||
SNDIO_sio_close(device->hidden->dev);
|
||||
}
|
||||
SDL_free(device->hidden->pfd);
|
||||
SDL_free(device->hidden->mixbuf);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool SNDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recording devices must be non-blocking for SNDIO_FlushRecording
|
||||
device->hidden->dev = SNDIO_sio_open(SIO_DEVANY,
|
||||
device->recording ? SIO_REC : SIO_PLAY, device->recording);
|
||||
if (!device->hidden->dev) {
|
||||
return SDL_SetError("sio_open() failed");
|
||||
}
|
||||
|
||||
device->hidden->pfd = SDL_malloc(sizeof(struct pollfd) * SNDIO_sio_nfds(device->hidden->dev));
|
||||
if (!device->hidden->pfd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sio_par par;
|
||||
SNDIO_sio_initpar(&par);
|
||||
|
||||
par.rate = device->spec.freq;
|
||||
par.pchan = device->spec.channels;
|
||||
par.round = device->sample_frames;
|
||||
par.appbufsz = par.round * 2;
|
||||
|
||||
// Try for a closest match on audio format
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (!SDL_AUDIO_ISFLOAT(test_format)) {
|
||||
par.le = SDL_AUDIO_ISLITTLEENDIAN(test_format) ? 1 : 0;
|
||||
par.sig = SDL_AUDIO_ISSIGNED(test_format) ? 1 : 0;
|
||||
par.bits = SDL_AUDIO_BITSIZE(test_format);
|
||||
|
||||
if (SNDIO_sio_setpar(device->hidden->dev, &par) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (SNDIO_sio_getpar(device->hidden->dev, &par) == 0) {
|
||||
return SDL_SetError("sio_getpar() failed");
|
||||
}
|
||||
if (par.bps != SIO_BPS(par.bits)) {
|
||||
continue;
|
||||
}
|
||||
if ((par.bits == 8 * par.bps) || (par.msb)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("sndio: Unsupported audio format");
|
||||
}
|
||||
|
||||
if ((par.bps == 4) && (par.sig) && (par.le)) {
|
||||
device->spec.format = SDL_AUDIO_S32LE;
|
||||
} else if ((par.bps == 4) && (par.sig) && (!par.le)) {
|
||||
device->spec.format = SDL_AUDIO_S32BE;
|
||||
} else if ((par.bps == 2) && (par.sig) && (par.le)) {
|
||||
device->spec.format = SDL_AUDIO_S16LE;
|
||||
} else if ((par.bps == 2) && (par.sig) && (!par.le)) {
|
||||
device->spec.format = SDL_AUDIO_S16BE;
|
||||
} else if ((par.bps == 1) && (par.sig)) {
|
||||
device->spec.format = SDL_AUDIO_S8;
|
||||
} else if ((par.bps == 1) && (!par.sig)) {
|
||||
device->spec.format = SDL_AUDIO_U8;
|
||||
} else {
|
||||
return SDL_SetError("sndio: Got unsupported hardware audio format.");
|
||||
}
|
||||
|
||||
device->spec.freq = par.rate;
|
||||
device->spec.channels = par.pchan;
|
||||
device->sample_frames = par.round;
|
||||
|
||||
// Calculate the final parameters for this audio specification
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
// Allocate mixing buffer
|
||||
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
|
||||
if (!device->hidden->mixbuf) {
|
||||
return false;
|
||||
}
|
||||
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
|
||||
|
||||
if (!SNDIO_sio_start(device->hidden->dev)) {
|
||||
return SDL_SetError("sio_start() failed");
|
||||
}
|
||||
|
||||
return true; // We're ready to rock and roll. :-)
|
||||
}
|
||||
|
||||
static void SNDIO_Deinitialize(void)
|
||||
{
|
||||
UnloadSNDIOLibrary();
|
||||
}
|
||||
|
||||
static void SNDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
*default_playback = SDL_AddAudioDevice(false, DEFAULT_PLAYBACK_DEVNAME, NULL, (void *)0x1);
|
||||
*default_recording = SDL_AddAudioDevice(true, DEFAULT_RECORDING_DEVNAME, NULL, (void *)0x2);
|
||||
}
|
||||
|
||||
static bool SNDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!LoadSNDIOLibrary()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
impl->OpenDevice = SNDIO_OpenDevice;
|
||||
impl->WaitDevice = SNDIO_WaitDevice;
|
||||
impl->PlayDevice = SNDIO_PlayDevice;
|
||||
impl->GetDeviceBuf = SNDIO_GetDeviceBuf;
|
||||
impl->CloseDevice = SNDIO_CloseDevice;
|
||||
impl->WaitRecordingDevice = SNDIO_WaitDevice;
|
||||
impl->RecordDevice = SNDIO_RecordDevice;
|
||||
impl->FlushRecording = SNDIO_FlushRecording;
|
||||
impl->Deinitialize = SNDIO_Deinitialize;
|
||||
impl->DetectDevices = SNDIO_DetectDevices;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap SNDIO_bootstrap = {
|
||||
"sndio", "OpenBSD sndio", SNDIO_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_SNDIO
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_sndioaudio_h_
|
||||
#define SDL_sndioaudio_h_
|
||||
|
||||
#include <poll.h>
|
||||
#include <sndio.h>
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
struct sio_hdl *dev; // The audio device handle
|
||||
Uint8 *mixbuf; // Raw mixing buffer
|
||||
struct pollfd *pfd; // Polling structures for non-blocking sndio devices
|
||||
};
|
||||
|
||||
#endif // SDL_sndioaudio_h_
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_VITA
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../SDL_audiodev_c.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
#include "SDL_vitaaudio.h"
|
||||
|
||||
#include <psp2/kernel/threadmgr.h>
|
||||
#include <psp2/audioout.h>
|
||||
#include <psp2/audioin.h>
|
||||
|
||||
#define SCE_AUDIO_SAMPLE_ALIGN(s) (((s) + 63) & ~63)
|
||||
#define SCE_AUDIO_MAX_VOLUME 0x8000
|
||||
|
||||
static bool VITAAUD_OpenRecordingDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
device->spec.freq = 16000;
|
||||
device->spec.channels = 1;
|
||||
device->sample_frames = 512;
|
||||
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
device->hidden->port = sceAudioInOpenPort(SCE_AUDIO_IN_PORT_TYPE_VOICE, 512, 16000, SCE_AUDIO_IN_PARAM_FORMAT_S16_MONO);
|
||||
|
||||
if (device->hidden->port < 0) {
|
||||
return SDL_SetError("Couldn't open audio in port: %x", device->hidden->port);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool VITAAUD_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
int format, mixlen, i, port = SCE_AUDIO_OUT_PORT_TYPE_MAIN;
|
||||
int vols[2] = { SCE_AUDIO_MAX_VOLUME, SCE_AUDIO_MAX_VOLUME };
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts;
|
||||
|
||||
device->hidden = (struct SDL_PrivateAudioData *)
|
||||
SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (test_format == SDL_AUDIO_S16LE) {
|
||||
device->spec.format = test_format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("Unsupported audio format");
|
||||
}
|
||||
|
||||
if (device->recording) {
|
||||
return VITAAUD_OpenRecordingDevice(device);
|
||||
}
|
||||
|
||||
// The sample count must be a multiple of 64.
|
||||
device->sample_frames = SCE_AUDIO_SAMPLE_ALIGN(device->sample_frames);
|
||||
|
||||
// Update the fragment size as size in bytes.
|
||||
SDL_UpdatedAudioDeviceFormat(device);
|
||||
|
||||
/* Allocate the mixing buffer. Its size and starting address must
|
||||
be a multiple of 64 bytes. Our sample count is already a multiple of
|
||||
64, so spec->size should be a multiple of 64 as well. */
|
||||
mixlen = device->buffer_size * NUM_BUFFERS;
|
||||
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
|
||||
if (!device->hidden->rawbuf) {
|
||||
return SDL_SetError("Couldn't allocate mixing buffer");
|
||||
}
|
||||
|
||||
// Setup the hardware channel.
|
||||
if (device->spec.channels == 1) {
|
||||
format = SCE_AUDIO_OUT_MODE_MONO;
|
||||
} else {
|
||||
format = SCE_AUDIO_OUT_MODE_STEREO;
|
||||
}
|
||||
|
||||
// the main port requires 48000Hz audio, so this drops to the background music port if necessary
|
||||
if (device->spec.freq < 48000) {
|
||||
port = SCE_AUDIO_OUT_PORT_TYPE_BGM;
|
||||
}
|
||||
|
||||
device->hidden->port = sceAudioOutOpenPort(port, device->sample_frames, device->spec.freq, format);
|
||||
if (device->hidden->port < 0) {
|
||||
SDL_aligned_free(device->hidden->rawbuf);
|
||||
device->hidden->rawbuf = NULL;
|
||||
return SDL_SetError("Couldn't open audio out port: %x", device->hidden->port);
|
||||
}
|
||||
|
||||
sceAudioOutSetVolume(device->hidden->port, SCE_AUDIO_VOLUME_FLAG_L_CH | SCE_AUDIO_VOLUME_FLAG_R_CH, vols);
|
||||
|
||||
SDL_memset(device->hidden->rawbuf, 0, mixlen);
|
||||
for (i = 0; i < NUM_BUFFERS; i++) {
|
||||
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
|
||||
}
|
||||
|
||||
device->hidden->next_buffer = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
|
||||
{
|
||||
return (sceAudioOutOutput(device->hidden->port, buffer) == 0);
|
||||
}
|
||||
|
||||
// This function waits until it is possible to write a full sound buffer
|
||||
static bool VITAAUD_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// !!! FIXME: we might just need to sleep roughly as long as playback buffers take to process, based on sample rate, etc.
|
||||
while (!SDL_GetAtomicInt(&device->shutdown) && (sceAudioOutGetRestSample(device->hidden->port) >= device->buffer_size)) {
|
||||
SDL_Delay(1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
Uint8 *result = device->hidden->mixbufs[device->hidden->next_buffer];
|
||||
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void VITAAUD_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
if (device->hidden->port >= 0) {
|
||||
if (device->recording) {
|
||||
sceAudioInReleasePort(device->hidden->port);
|
||||
} else {
|
||||
sceAudioOutReleasePort(device->hidden->port);
|
||||
}
|
||||
device->hidden->port = -1;
|
||||
}
|
||||
|
||||
if (!device->recording && device->hidden->rawbuf) {
|
||||
SDL_aligned_free(device->hidden->rawbuf); // this uses SDL_aligned_alloc(), not SDL_malloc()
|
||||
device->hidden->rawbuf = NULL;
|
||||
}
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool VITAAUD_WaitRecordingDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// there's only a blocking call to obtain more data, so we'll just sleep as
|
||||
// long as a buffer would run.
|
||||
const Uint64 endticks = SDL_GetTicks() + ((device->sample_frames * 1000) / device->spec.freq);
|
||||
while (!SDL_GetAtomicInt(&device->shutdown) && (SDL_GetTicks() < endticks)) {
|
||||
SDL_Delay(1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int VITAAUD_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
int ret;
|
||||
SDL_assert(buflen == device->buffer_size);
|
||||
ret = sceAudioInInput(device->hidden->port, buffer);
|
||||
if (ret < 0) {
|
||||
SDL_SetError("Failed to record from device: %x", ret);
|
||||
return -1;
|
||||
}
|
||||
return device->buffer_size;
|
||||
}
|
||||
|
||||
static void VITAAUD_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
// just grab the latest and dump it.
|
||||
sceAudioInInput(device->hidden->port, device->work_buffer);
|
||||
}
|
||||
|
||||
static void VITAAUD_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
// Increase the priority of this audio thread by 1 to put it ahead of other SDL threads.
|
||||
SceUID thid;
|
||||
SceKernelThreadInfo info;
|
||||
thid = sceKernelGetThreadId();
|
||||
info.size = sizeof(SceKernelThreadInfo);
|
||||
if (sceKernelGetThreadInfo(thid, &info) == 0) {
|
||||
sceKernelChangeThreadPriority(thid, info.currentPriority - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static bool VITAAUD_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
impl->OpenDevice = VITAAUD_OpenDevice;
|
||||
impl->PlayDevice = VITAAUD_PlayDevice;
|
||||
impl->WaitDevice = VITAAUD_WaitDevice;
|
||||
impl->GetDeviceBuf = VITAAUD_GetDeviceBuf;
|
||||
impl->CloseDevice = VITAAUD_CloseDevice;
|
||||
impl->ThreadInit = VITAAUD_ThreadInit;
|
||||
impl->WaitRecordingDevice = VITAAUD_WaitRecordingDevice;
|
||||
impl->FlushRecording = VITAAUD_FlushRecording;
|
||||
impl->RecordDevice = VITAAUD_RecordDevice;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
impl->OnlyHasDefaultPlaybackDevice = true;
|
||||
impl->OnlyHasDefaultRecordingDevice = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap VITAAUD_bootstrap = {
|
||||
"vita", "VITA audio driver", VITAAUD_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_VITA
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef SDL_vitaaudio_h
|
||||
#define SDL_vitaaudio_h
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#define NUM_BUFFERS 2
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
// The hardware input/output port.
|
||||
int port;
|
||||
// The raw allocated mixing buffer.
|
||||
Uint8 *rawbuf;
|
||||
// Individual mixing buffers.
|
||||
Uint8 *mixbufs[NUM_BUFFERS];
|
||||
// Index of the next available mixing buffer.
|
||||
int next_buffer;
|
||||
};
|
||||
|
||||
#endif // SDL_vitaaudio_h
|
||||
@@ -0,0 +1,963 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifdef SDL_AUDIO_DRIVER_WASAPI
|
||||
|
||||
#include "../../core/windows/SDL_windows.h"
|
||||
#include "../../core/windows/SDL_immdevice.h"
|
||||
#include "../../thread/SDL_systhread.h"
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
#define COBJMACROS
|
||||
#include <audioclient.h>
|
||||
|
||||
#include "SDL_wasapi.h"
|
||||
|
||||
// These constants aren't available in older SDKs
|
||||
#ifndef AUDCLNT_STREAMFLAGS_RATEADJUST
|
||||
#define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000
|
||||
#endif
|
||||
#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
|
||||
#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
|
||||
#endif
|
||||
#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
|
||||
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
|
||||
#endif
|
||||
|
||||
// handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency).
|
||||
static HMODULE libavrt = NULL;
|
||||
typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
|
||||
typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
|
||||
static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL;
|
||||
static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL;
|
||||
|
||||
// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
|
||||
static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
|
||||
static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } };
|
||||
static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
|
||||
#ifdef __IAudioClient3_INTERFACE_DEFINED__
|
||||
static const IID SDL_IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4, { 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } };
|
||||
#endif //
|
||||
|
||||
static bool immdevice_initialized = false;
|
||||
|
||||
// WASAPI is _really_ particular about various things happening on the same thread, for COM and such,
|
||||
// so we proxy various stuff to a single background thread to manage.
|
||||
|
||||
typedef struct ManagementThreadPendingTask
|
||||
{
|
||||
ManagementThreadTask fn;
|
||||
void *userdata;
|
||||
bool result;
|
||||
SDL_Semaphore *task_complete_sem;
|
||||
char *errorstr;
|
||||
struct ManagementThreadPendingTask *next;
|
||||
} ManagementThreadPendingTask;
|
||||
|
||||
static SDL_Thread *ManagementThread = NULL;
|
||||
static ManagementThreadPendingTask *ManagementThreadPendingTasks = NULL;
|
||||
static SDL_Mutex *ManagementThreadLock = NULL;
|
||||
static SDL_Condition *ManagementThreadCondition = NULL;
|
||||
static SDL_AtomicInt ManagementThreadShutdown;
|
||||
|
||||
static void ManagementThreadMainloop(void)
|
||||
{
|
||||
SDL_LockMutex(ManagementThreadLock);
|
||||
ManagementThreadPendingTask *task;
|
||||
while (((task = (ManagementThreadPendingTask *)SDL_GetAtomicPointer((void **)&ManagementThreadPendingTasks)) != NULL) || !SDL_GetAtomicInt(&ManagementThreadShutdown)) {
|
||||
if (!task) {
|
||||
SDL_WaitCondition(ManagementThreadCondition, ManagementThreadLock); // block until there's something to do.
|
||||
} else {
|
||||
SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, task->next); // take task off the pending list.
|
||||
SDL_UnlockMutex(ManagementThreadLock); // let other things add to the list while we chew on this task.
|
||||
task->result = task->fn(task->userdata); // run this task.
|
||||
if (task->task_complete_sem) { // something waiting on result?
|
||||
task->errorstr = SDL_strdup(SDL_GetError());
|
||||
SDL_SignalSemaphore(task->task_complete_sem);
|
||||
} else { // nothing waiting, we're done, free it.
|
||||
SDL_free(task);
|
||||
}
|
||||
SDL_LockMutex(ManagementThreadLock); // regrab the lock so we can get the next task; if nothing to do, we'll release the lock in SDL_WaitCondition.
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(ManagementThreadLock); // told to shut down and out of tasks, let go of the lock and return.
|
||||
}
|
||||
|
||||
bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, bool *wait_on_result)
|
||||
{
|
||||
// We want to block for a result, but we are already running from the management thread! Just run the task now so we don't deadlock.
|
||||
if ((wait_on_result) && (SDL_GetCurrentThreadID() == SDL_GetThreadID(ManagementThread))) {
|
||||
*wait_on_result = task(userdata);
|
||||
return true; // completed!
|
||||
}
|
||||
|
||||
if (SDL_GetAtomicInt(&ManagementThreadShutdown)) {
|
||||
return SDL_SetError("Can't add task, we're shutting down");
|
||||
}
|
||||
|
||||
ManagementThreadPendingTask *pending = (ManagementThreadPendingTask *)SDL_calloc(1, sizeof(ManagementThreadPendingTask));
|
||||
if (!pending) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pending->fn = task;
|
||||
pending->userdata = userdata;
|
||||
|
||||
if (wait_on_result) {
|
||||
pending->task_complete_sem = SDL_CreateSemaphore(0);
|
||||
if (!pending->task_complete_sem) {
|
||||
SDL_free(pending);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pending->next = NULL;
|
||||
|
||||
SDL_LockMutex(ManagementThreadLock);
|
||||
|
||||
// add to end of task list.
|
||||
ManagementThreadPendingTask *prev = NULL;
|
||||
for (ManagementThreadPendingTask *i = (ManagementThreadPendingTask *)SDL_GetAtomicPointer((void **)&ManagementThreadPendingTasks); i; i = i->next) {
|
||||
prev = i;
|
||||
}
|
||||
|
||||
if (prev) {
|
||||
prev->next = pending;
|
||||
} else {
|
||||
SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, pending);
|
||||
}
|
||||
|
||||
// task is added to the end of the pending list, let management thread rip!
|
||||
SDL_SignalCondition(ManagementThreadCondition);
|
||||
SDL_UnlockMutex(ManagementThreadLock);
|
||||
|
||||
if (wait_on_result) {
|
||||
SDL_WaitSemaphore(pending->task_complete_sem);
|
||||
SDL_DestroySemaphore(pending->task_complete_sem);
|
||||
*wait_on_result = pending->result;
|
||||
if (pending->errorstr) {
|
||||
SDL_SetError("%s", pending->errorstr);
|
||||
SDL_free(pending->errorstr);
|
||||
}
|
||||
SDL_free(pending);
|
||||
}
|
||||
|
||||
return true; // successfully added (and possibly executed)!
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_AudioDeviceDisconnected(void *userdata)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
|
||||
SDL_AudioDeviceDisconnected(device);
|
||||
UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
|
||||
return true;
|
||||
}
|
||||
|
||||
static void AudioDeviceDisconnected(SDL_AudioDevice *device)
|
||||
{
|
||||
// don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
|
||||
if (device) {
|
||||
RefPhysicalAudioDevice(device); // make sure this lives until the task completes.
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_AudioDeviceDisconnected, device, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_DefaultAudioDeviceChanged(void *userdata)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
|
||||
SDL_DefaultAudioDeviceChanged(device);
|
||||
UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
|
||||
return true;
|
||||
}
|
||||
|
||||
static void DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
|
||||
{
|
||||
// don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
|
||||
if (new_default_device) {
|
||||
RefPhysicalAudioDevice(new_default_device); // make sure this lives until the task completes.
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_DefaultAudioDeviceChanged, new_default_device, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void StopWasapiHotplug(void)
|
||||
{
|
||||
if (immdevice_initialized) {
|
||||
SDL_IMMDevice_Quit();
|
||||
immdevice_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void Deinit(void)
|
||||
{
|
||||
if (libavrt) {
|
||||
FreeLibrary(libavrt);
|
||||
libavrt = NULL;
|
||||
}
|
||||
|
||||
pAvSetMmThreadCharacteristicsW = NULL;
|
||||
pAvRevertMmThreadCharacteristics = NULL;
|
||||
|
||||
StopWasapiHotplug();
|
||||
|
||||
WIN_CoUninitialize();
|
||||
}
|
||||
|
||||
static bool ManagementThreadPrepare(void)
|
||||
{
|
||||
const SDL_IMMDevice_callbacks callbacks = { AudioDeviceDisconnected, DefaultAudioDeviceChanged };
|
||||
if (FAILED(WIN_CoInitialize())) {
|
||||
return SDL_SetError("CoInitialize() failed");
|
||||
} else if (!SDL_IMMDevice_Init(&callbacks)) {
|
||||
return false; // Error string is set by SDL_IMMDevice_Init
|
||||
}
|
||||
|
||||
immdevice_initialized = true;
|
||||
|
||||
libavrt = LoadLibrary(TEXT("avrt.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
|
||||
if (libavrt) {
|
||||
pAvSetMmThreadCharacteristicsW = (pfnAvSetMmThreadCharacteristicsW)GetProcAddress(libavrt, "AvSetMmThreadCharacteristicsW");
|
||||
pAvRevertMmThreadCharacteristics = (pfnAvRevertMmThreadCharacteristics)GetProcAddress(libavrt, "AvRevertMmThreadCharacteristics");
|
||||
}
|
||||
|
||||
ManagementThreadLock = SDL_CreateMutex();
|
||||
if (!ManagementThreadLock) {
|
||||
Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
ManagementThreadCondition = SDL_CreateCondition();
|
||||
if (!ManagementThreadCondition) {
|
||||
SDL_DestroyMutex(ManagementThreadLock);
|
||||
ManagementThreadLock = NULL;
|
||||
Deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *errorstr;
|
||||
SDL_Semaphore *ready_sem;
|
||||
} ManagementThreadEntryData;
|
||||
|
||||
static int ManagementThreadEntry(void *userdata)
|
||||
{
|
||||
ManagementThreadEntryData *data = (ManagementThreadEntryData *)userdata;
|
||||
|
||||
if (!ManagementThreadPrepare()) {
|
||||
data->errorstr = SDL_strdup(SDL_GetError());
|
||||
SDL_SignalSemaphore(data->ready_sem); // unblock calling thread.
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_SignalSemaphore(data->ready_sem); // unblock calling thread.
|
||||
ManagementThreadMainloop();
|
||||
|
||||
Deinit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool InitManagementThread(void)
|
||||
{
|
||||
ManagementThreadEntryData mgmtdata;
|
||||
SDL_zero(mgmtdata);
|
||||
mgmtdata.ready_sem = SDL_CreateSemaphore(0);
|
||||
if (!mgmtdata.ready_sem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, NULL);
|
||||
SDL_SetAtomicInt(&ManagementThreadShutdown, 0);
|
||||
ManagementThread = SDL_CreateThreadWithStackSize(ManagementThreadEntry, "SDLWASAPIMgmt", 256 * 1024, &mgmtdata); // !!! FIXME: maybe even smaller stack size?
|
||||
if (!ManagementThread) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_WaitSemaphore(mgmtdata.ready_sem);
|
||||
SDL_DestroySemaphore(mgmtdata.ready_sem);
|
||||
|
||||
if (mgmtdata.errorstr) {
|
||||
SDL_WaitThread(ManagementThread, NULL);
|
||||
ManagementThread = NULL;
|
||||
SDL_SetError("%s", mgmtdata.errorstr);
|
||||
SDL_free(mgmtdata.errorstr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void DeinitManagementThread(void)
|
||||
{
|
||||
if (ManagementThread) {
|
||||
SDL_SetAtomicInt(&ManagementThreadShutdown, 1);
|
||||
SDL_LockMutex(ManagementThreadLock);
|
||||
SDL_SignalCondition(ManagementThreadCondition);
|
||||
SDL_UnlockMutex(ManagementThreadLock);
|
||||
SDL_WaitThread(ManagementThread, NULL);
|
||||
ManagementThread = NULL;
|
||||
}
|
||||
|
||||
SDL_assert(SDL_GetAtomicPointer((void **) &ManagementThreadPendingTasks) == NULL);
|
||||
|
||||
SDL_DestroyCondition(ManagementThreadCondition);
|
||||
SDL_DestroyMutex(ManagementThreadLock);
|
||||
ManagementThreadCondition = NULL;
|
||||
ManagementThreadLock = NULL;
|
||||
SDL_SetAtomicInt(&ManagementThreadShutdown, 0);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SDL_AudioDevice **default_playback;
|
||||
SDL_AudioDevice **default_recording;
|
||||
} mgmtthrtask_DetectDevicesData;
|
||||
|
||||
static bool mgmtthrtask_DetectDevices(void *userdata)
|
||||
{
|
||||
mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata;
|
||||
SDL_IMMDevice_EnumerateEndpoints(data->default_playback, data->default_recording);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WASAPI_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
|
||||
{
|
||||
bool rc;
|
||||
// this blocks because it needs to finish before the audio subsystem inits
|
||||
mgmtthrtask_DetectDevicesData data;
|
||||
data.default_playback = default_playback;
|
||||
data.default_recording = default_recording;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc);
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_DisconnectDevice(void *userdata)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
|
||||
SDL_AudioDeviceDisconnected(device);
|
||||
UnrefPhysicalAudioDevice(device);
|
||||
return true;
|
||||
}
|
||||
|
||||
void WASAPI_DisconnectDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (SDL_CompareAndSwapAtomicInt(&device->hidden->device_disconnecting, 0, 1)) {
|
||||
RefPhysicalAudioDevice(device); // will unref when the task ends.
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static bool WasapiFailed(SDL_AudioDevice *device, const HRESULT err)
|
||||
{
|
||||
if (err == S_OK) {
|
||||
return false;
|
||||
} else if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
|
||||
device->hidden->device_lost = true;
|
||||
} else {
|
||||
device->hidden->device_dead = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_StopAndReleaseClient(void *userdata)
|
||||
{
|
||||
IAudioClient *client = (IAudioClient *) userdata;
|
||||
IAudioClient_Stop(client);
|
||||
IAudioClient_Release(client);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_ReleaseCaptureClient(void *userdata)
|
||||
{
|
||||
IAudioCaptureClient_Release((IAudioCaptureClient *)userdata);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_ReleaseRenderClient(void *userdata)
|
||||
{
|
||||
IAudioRenderClient_Release((IAudioRenderClient *)userdata);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_CoTaskMemFree(void *userdata)
|
||||
{
|
||||
CoTaskMemFree(userdata);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_CloseHandle(void *userdata)
|
||||
{
|
||||
CloseHandle((HANDLE) userdata);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ResetWasapiDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (!device || !device->hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
// just queue up all the tasks in the management thread and don't block.
|
||||
// We don't care when any of these actually get free'd.
|
||||
|
||||
if (device->hidden->client) {
|
||||
IAudioClient *client = device->hidden->client;
|
||||
device->hidden->client = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_StopAndReleaseClient, client, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->render) {
|
||||
IAudioRenderClient *render = device->hidden->render;
|
||||
device->hidden->render = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseRenderClient, render, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->capture) {
|
||||
IAudioCaptureClient *capture = device->hidden->capture;
|
||||
device->hidden->capture = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseCaptureClient, capture, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->waveformat) {
|
||||
void *ptr = device->hidden->waveformat;
|
||||
device->hidden->waveformat = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_CoTaskMemFree, ptr, NULL);
|
||||
}
|
||||
|
||||
if (device->hidden->event) {
|
||||
HANDLE event = device->hidden->event;
|
||||
device->hidden->event = NULL;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_CloseHandle, (void *) event, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_ActivateDevice(void *userdata)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
|
||||
|
||||
IMMDevice *immdevice = NULL;
|
||||
if (!SDL_IMMDevice_Get(device, &immdevice, device->recording)) {
|
||||
device->hidden->client = NULL;
|
||||
return false; // This is already set by SDL_IMMDevice_Get
|
||||
}
|
||||
|
||||
// this is _not_ async in standard win32, yay!
|
||||
HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client);
|
||||
IMMDevice_Release(immdevice);
|
||||
|
||||
if (FAILED(ret)) {
|
||||
SDL_assert(device->hidden->client == NULL);
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret);
|
||||
}
|
||||
|
||||
SDL_assert(device->hidden->client != NULL);
|
||||
if (!WASAPI_PrepDevice(device)) { // not async, fire it right away.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // good to go.
|
||||
}
|
||||
|
||||
static bool ActivateWasapiDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// this blocks because we're either being notified from a background thread or we're running during device open,
|
||||
// both of which won't deadlock vs the device thread.
|
||||
bool rc = false;
|
||||
return (WASAPI_ProxyToManagementThread(mgmtthrtask_ActivateDevice, device, &rc) && rc);
|
||||
}
|
||||
|
||||
// do not call when holding the device lock!
|
||||
static bool RecoverWasapiDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
ResetWasapiDevice(device); // dump the lost device's handles.
|
||||
|
||||
// This handles a non-default device that simply had its format changed in the Windows Control Panel.
|
||||
if (!ActivateWasapiDevice(device)) {
|
||||
WASAPI_DisconnectDevice(device);
|
||||
return false;
|
||||
}
|
||||
|
||||
device->hidden->device_lost = false;
|
||||
|
||||
return true; // okay, carry on with new device details!
|
||||
}
|
||||
|
||||
// do not call when holding the device lock!
|
||||
static bool RecoverWasapiIfLost(SDL_AudioDevice *device)
|
||||
{
|
||||
if (SDL_GetAtomicInt(&device->shutdown)) {
|
||||
return false; // closing, stop trying.
|
||||
} else if (SDL_GetAtomicInt(&device->hidden->device_disconnecting)) {
|
||||
return false; // failing via the WASAPI management thread, stop trying.
|
||||
} else if (device->hidden->device_dead) { // had a fatal error elsewhere, clean up and quit
|
||||
IAudioClient_Stop(device->hidden->client);
|
||||
WASAPI_DisconnectDevice(device);
|
||||
SDL_assert(SDL_GetAtomicInt(&device->shutdown)); // so we don't come back through here.
|
||||
return false; // already failed.
|
||||
} else if (SDL_GetAtomicInt(&device->zombie)) {
|
||||
return false; // we're already dead, so just leave and let the Zombie implementations take over.
|
||||
} else if (!device->hidden->client) {
|
||||
return true; // still waiting for activation.
|
||||
}
|
||||
|
||||
return device->hidden->device_lost ? RecoverWasapiDevice(device) : true;
|
||||
}
|
||||
|
||||
static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
// get an endpoint buffer from WASAPI.
|
||||
BYTE *buffer = NULL;
|
||||
|
||||
if (device->hidden->render) {
|
||||
const HRESULT ret = IAudioRenderClient_GetBuffer(device->hidden->render, device->sample_frames, &buffer);
|
||||
if (ret == AUDCLNT_E_BUFFER_TOO_LARGE) {
|
||||
SDL_assert(buffer == NULL);
|
||||
*buffer_size = 0; // just go back to WaitDevice and try again after the hardware has consumed some more data.
|
||||
} else if (WasapiFailed(device, ret)) {
|
||||
SDL_assert(buffer == NULL);
|
||||
if (device->hidden->device_lost) { // just use an available buffer, we won't be playing it anyhow.
|
||||
*buffer_size = 0; // we'll recover during WaitDevice and try again.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (Uint8 *)buffer;
|
||||
}
|
||||
|
||||
static bool WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
|
||||
{
|
||||
if (device->hidden->render && !SDL_GetAtomicInt(&device->hidden->device_disconnecting)) { // definitely activated?
|
||||
// WasapiFailed() will mark the device for reacquisition or removal elsewhere.
|
||||
WasapiFailed(device, IAudioRenderClient_ReleaseBuffer(device->hidden->render, device->sample_frames, 0));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool WASAPI_WaitDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// WaitDevice does not hold the device lock, so check for recovery/disconnect details here.
|
||||
while (RecoverWasapiIfLost(device) && device->hidden->client && device->hidden->event) {
|
||||
if (device->recording) {
|
||||
// Recording devices should return immediately if there is any data available
|
||||
UINT32 padding = 0;
|
||||
if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
|
||||
//SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);
|
||||
if (padding > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (WaitForSingleObjectEx(device->hidden->event, 200, FALSE)) {
|
||||
case WAIT_OBJECT_0:
|
||||
case WAIT_TIMEOUT:
|
||||
break;
|
||||
|
||||
default:
|
||||
//SDL_Log("WASAPI FAILED EVENT!");
|
||||
IAudioClient_Stop(device->hidden->client);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
DWORD waitResult = WaitForSingleObjectEx(device->hidden->event, 200, FALSE);
|
||||
if (waitResult == WAIT_OBJECT_0) {
|
||||
UINT32 padding = 0;
|
||||
if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
|
||||
//SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);
|
||||
if (padding <= (UINT32)device->sample_frames) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (waitResult != WAIT_TIMEOUT) {
|
||||
//SDL_Log("WASAPI FAILED EVENT!");*/
|
||||
IAudioClient_Stop(device->hidden->client);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int WASAPI_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
|
||||
{
|
||||
BYTE *ptr = NULL;
|
||||
UINT32 frames = 0;
|
||||
DWORD flags = 0;
|
||||
|
||||
while (device->hidden->capture) {
|
||||
const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
|
||||
if (ret == AUDCLNT_S_BUFFER_EMPTY) {
|
||||
return 0; // in theory we should have waited until there was data, but oh well, we'll go back to waiting. Returning 0 is safe in SDL3.
|
||||
}
|
||||
|
||||
WasapiFailed(device, ret); // mark device lost/failed if necessary.
|
||||
|
||||
if (ret == S_OK) {
|
||||
const int total = ((int)frames) * device->hidden->framesize;
|
||||
const int cpy = SDL_min(buflen, total);
|
||||
const int leftover = total - cpy;
|
||||
const bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? true : false;
|
||||
|
||||
SDL_assert(leftover == 0); // according to MSDN, this isn't everything available, just one "packet" of data per-GetBuffer call.
|
||||
|
||||
if (silent) {
|
||||
SDL_memset(buffer, device->silence_value, cpy);
|
||||
} else {
|
||||
SDL_memcpy(buffer, ptr, cpy);
|
||||
}
|
||||
|
||||
WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames));
|
||||
|
||||
return cpy;
|
||||
}
|
||||
}
|
||||
|
||||
return -1; // unrecoverable error.
|
||||
}
|
||||
|
||||
static void WASAPI_FlushRecording(SDL_AudioDevice *device)
|
||||
{
|
||||
BYTE *ptr = NULL;
|
||||
UINT32 frames = 0;
|
||||
DWORD flags = 0;
|
||||
|
||||
// just read until we stop getting packets, throwing them away.
|
||||
while (!SDL_GetAtomicInt(&device->shutdown) && device->hidden->capture) {
|
||||
const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
|
||||
if (ret == AUDCLNT_S_BUFFER_EMPTY) {
|
||||
break; // no more buffered data; we're done.
|
||||
} else if (WasapiFailed(device, ret)) {
|
||||
break; // failed for some other reason, abort.
|
||||
} else if (WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames))) {
|
||||
break; // something broke.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void WASAPI_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
ResetWasapiDevice(device);
|
||||
SDL_free(device->hidden->devid);
|
||||
SDL_free(device->hidden);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_PrepDevice(void *userdata)
|
||||
{
|
||||
SDL_AudioDevice *device = (SDL_AudioDevice *)userdata;
|
||||
|
||||
/* !!! FIXME: we could request an exclusive mode stream, which is lower latency;
|
||||
!!! it will write into the kernel's audio buffer directly instead of
|
||||
!!! shared memory that a user-mode mixer then writes to the kernel with
|
||||
!!! everything else. Doing this means any other sound using this device will
|
||||
!!! stop playing, including the user's MP3 player and system notification
|
||||
!!! sounds. You'd probably need to release the device when the app isn't in
|
||||
!!! the foreground, to be a good citizen of the system. It's doable, but it's
|
||||
!!! more work and causes some annoyances, and I don't know what the latency
|
||||
!!! wins actually look like. Maybe add a hint to force exclusive mode at
|
||||
!!! some point. To be sure, defaulting to shared mode is the right thing to
|
||||
!!! do in any case. */
|
||||
const AUDCLNT_SHAREMODE sharemode = AUDCLNT_SHAREMODE_SHARED;
|
||||
|
||||
IAudioClient *client = device->hidden->client;
|
||||
SDL_assert(client != NULL);
|
||||
|
||||
device->hidden->event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
if (!device->hidden->event) {
|
||||
return WIN_SetError("WASAPI can't create an event handle");
|
||||
}
|
||||
|
||||
HRESULT ret;
|
||||
|
||||
WAVEFORMATEX *waveformat = NULL;
|
||||
ret = IAudioClient_GetMixFormat(client, &waveformat);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't determine mix format", ret);
|
||||
}
|
||||
SDL_assert(waveformat != NULL);
|
||||
device->hidden->waveformat = waveformat;
|
||||
|
||||
SDL_AudioSpec newspec;
|
||||
newspec.channels = (Uint8)waveformat->nChannels;
|
||||
|
||||
// Make sure we have a valid format that we can convert to whatever WASAPI wants.
|
||||
const SDL_AudioFormat wasapi_format = SDL_WaveFormatExToSDLFormat(waveformat);
|
||||
|
||||
SDL_AudioFormat test_format;
|
||||
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
|
||||
while ((test_format = *(closefmts++)) != 0) {
|
||||
if (test_format == wasapi_format) {
|
||||
newspec.format = test_format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!test_format) {
|
||||
return SDL_SetError("%s: Unsupported audio format", "wasapi");
|
||||
}
|
||||
|
||||
REFERENCE_TIME default_period = 0;
|
||||
ret = IAudioClient_GetDevicePeriod(client, &default_period, NULL);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't determine minimum device period", ret);
|
||||
}
|
||||
|
||||
DWORD streamflags = 0;
|
||||
|
||||
/* we've gotten reports that WASAPI's resampler introduces distortions, but in the short term
|
||||
it fixes some other WASAPI-specific quirks we haven't quite tracked down.
|
||||
Refer to bug #6326 for the immediate concern. */
|
||||
#if 1
|
||||
// favor WASAPI's resampler over our own
|
||||
if ((DWORD)device->spec.freq != waveformat->nSamplesPerSec) {
|
||||
streamflags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY);
|
||||
waveformat->nSamplesPerSec = device->spec.freq;
|
||||
waveformat->nAvgBytesPerSec = waveformat->nSamplesPerSec * waveformat->nChannels * (waveformat->wBitsPerSample / 8);
|
||||
}
|
||||
#endif
|
||||
|
||||
newspec.freq = waveformat->nSamplesPerSec;
|
||||
|
||||
streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
|
||||
|
||||
int new_sample_frames = 0;
|
||||
bool iaudioclient3_initialized = false;
|
||||
|
||||
#ifdef __IAudioClient3_INTERFACE_DEFINED__
|
||||
// Try querying IAudioClient3 if sharemode is AUDCLNT_SHAREMODE_SHARED
|
||||
if (sharemode == AUDCLNT_SHAREMODE_SHARED) {
|
||||
IAudioClient3 *client3 = NULL;
|
||||
ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient3, (void**)&client3);
|
||||
if (SUCCEEDED(ret)) {
|
||||
UINT32 default_period_in_frames = 0;
|
||||
UINT32 fundamental_period_in_frames = 0;
|
||||
UINT32 min_period_in_frames = 0;
|
||||
UINT32 max_period_in_frames = 0;
|
||||
ret = IAudioClient3_GetSharedModeEnginePeriod(client3, waveformat,
|
||||
&default_period_in_frames, &fundamental_period_in_frames, &min_period_in_frames, &max_period_in_frames);
|
||||
if (SUCCEEDED(ret)) {
|
||||
// IAudioClient3_InitializeSharedAudioStream only accepts the integral multiple of fundamental_period_in_frames
|
||||
UINT32 period_in_frames = fundamental_period_in_frames * (UINT32)SDL_round((double)device->sample_frames / fundamental_period_in_frames);
|
||||
period_in_frames = SDL_clamp(period_in_frames, min_period_in_frames, max_period_in_frames);
|
||||
|
||||
ret = IAudioClient3_InitializeSharedAudioStream(client3, streamflags, period_in_frames, waveformat, NULL);
|
||||
if (SUCCEEDED(ret)) {
|
||||
new_sample_frames = (int)period_in_frames;
|
||||
iaudioclient3_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
IAudioClient3_Release(client3);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!iaudioclient3_initialized)
|
||||
ret = IAudioClient_Initialize(client, sharemode, streamflags, 0, 0, waveformat, NULL);
|
||||
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't initialize audio client", ret);
|
||||
}
|
||||
|
||||
ret = IAudioClient_SetEventHandle(client, device->hidden->event);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't set event handle", ret);
|
||||
}
|
||||
|
||||
UINT32 bufsize = 0; // this is in sample frames, not samples, not bytes.
|
||||
ret = IAudioClient_GetBufferSize(client, &bufsize);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't determine buffer size", ret);
|
||||
}
|
||||
|
||||
// Match the callback size to the period size to cut down on the number of
|
||||
// interrupts waited for in each call to WaitDevice
|
||||
if (new_sample_frames <= 0) {
|
||||
const float period_millis = default_period / 10000.0f;
|
||||
const float period_frames = period_millis * newspec.freq / 1000.0f;
|
||||
new_sample_frames = (int) SDL_ceilf(period_frames);
|
||||
}
|
||||
|
||||
// regardless of what we calculated for the period size, clamp it to the expected hardware buffer size.
|
||||
if (new_sample_frames > (int) bufsize) {
|
||||
new_sample_frames = (int) bufsize;
|
||||
}
|
||||
|
||||
// Update the fragment size as size in bytes
|
||||
if (!SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device->hidden->framesize = SDL_AUDIO_FRAMESIZE(device->spec);
|
||||
|
||||
if (device->recording) {
|
||||
IAudioCaptureClient *capture = NULL;
|
||||
ret = IAudioClient_GetService(client, &SDL_IID_IAudioCaptureClient, (void **)&capture);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't get capture client service", ret);
|
||||
}
|
||||
|
||||
SDL_assert(capture != NULL);
|
||||
device->hidden->capture = capture;
|
||||
ret = IAudioClient_Start(client);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't start capture", ret);
|
||||
}
|
||||
|
||||
WASAPI_FlushRecording(device); // MSDN says you should flush the recording endpoint right after startup.
|
||||
} else {
|
||||
IAudioRenderClient *render = NULL;
|
||||
ret = IAudioClient_GetService(client, &SDL_IID_IAudioRenderClient, (void **)&render);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't get render client service", ret);
|
||||
}
|
||||
|
||||
SDL_assert(render != NULL);
|
||||
device->hidden->render = render;
|
||||
ret = IAudioClient_Start(client);
|
||||
if (FAILED(ret)) {
|
||||
return WIN_SetErrorFromHRESULT("WASAPI can't start playback", ret);
|
||||
}
|
||||
}
|
||||
|
||||
return true; // good to go.
|
||||
}
|
||||
|
||||
// This is called once a device is activated, possibly asynchronously.
|
||||
bool WASAPI_PrepDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
bool rc = true;
|
||||
return (WASAPI_ProxyToManagementThread(mgmtthrtask_PrepDevice, device, &rc) && rc);
|
||||
}
|
||||
|
||||
static bool WASAPI_OpenDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
// Initialize all variables that we clean on shutdown
|
||||
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
|
||||
if (!device->hidden) {
|
||||
return false;
|
||||
} else if (!ActivateWasapiDevice(device)) {
|
||||
return false; // already set error.
|
||||
}
|
||||
|
||||
/* Ready, but possibly waiting for async device activation.
|
||||
Until activation is successful, we will report silence from recording
|
||||
devices and ignore data on playback devices. Upon activation, we'll make
|
||||
sure any bound audio streams are adjusted for the final device format. */
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WASAPI_ThreadInit(SDL_AudioDevice *device)
|
||||
{
|
||||
// this thread uses COM.
|
||||
if (SUCCEEDED(WIN_CoInitialize())) { // can't report errors, hope it worked!
|
||||
device->hidden->coinitialized = true;
|
||||
}
|
||||
|
||||
// Set this thread to very high "Pro Audio" priority.
|
||||
if (pAvSetMmThreadCharacteristicsW) {
|
||||
DWORD idx = 0;
|
||||
device->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx);
|
||||
} else {
|
||||
SDL_SetCurrentThreadPriority(device->recording ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
static void WASAPI_ThreadDeinit(SDL_AudioDevice *device)
|
||||
{
|
||||
// Set this thread back to normal priority.
|
||||
if (device->hidden->task && pAvRevertMmThreadCharacteristics) {
|
||||
pAvRevertMmThreadCharacteristics(device->hidden->task);
|
||||
device->hidden->task = NULL;
|
||||
}
|
||||
|
||||
if (device->hidden->coinitialized) {
|
||||
WIN_CoUninitialize();
|
||||
device->hidden->coinitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_FreeDeviceHandle(void *userdata)
|
||||
{
|
||||
SDL_IMMDevice_FreeDeviceHandle((SDL_AudioDevice *) userdata);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device)
|
||||
{
|
||||
bool rc;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc);
|
||||
}
|
||||
|
||||
static bool mgmtthrtask_DeinitializeStart(void *userdata)
|
||||
{
|
||||
StopWasapiHotplug();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WASAPI_DeinitializeStart(void)
|
||||
{
|
||||
bool rc;
|
||||
WASAPI_ProxyToManagementThread(mgmtthrtask_DeinitializeStart, NULL, &rc);
|
||||
}
|
||||
|
||||
static void WASAPI_Deinitialize(void)
|
||||
{
|
||||
DeinitManagementThread();
|
||||
}
|
||||
|
||||
static bool WASAPI_Init(SDL_AudioDriverImpl *impl)
|
||||
{
|
||||
if (!InitManagementThread()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
impl->DetectDevices = WASAPI_DetectDevices;
|
||||
impl->ThreadInit = WASAPI_ThreadInit;
|
||||
impl->ThreadDeinit = WASAPI_ThreadDeinit;
|
||||
impl->OpenDevice = WASAPI_OpenDevice;
|
||||
impl->PlayDevice = WASAPI_PlayDevice;
|
||||
impl->WaitDevice = WASAPI_WaitDevice;
|
||||
impl->GetDeviceBuf = WASAPI_GetDeviceBuf;
|
||||
impl->WaitRecordingDevice = WASAPI_WaitDevice;
|
||||
impl->RecordDevice = WASAPI_RecordDevice;
|
||||
impl->FlushRecording = WASAPI_FlushRecording;
|
||||
impl->CloseDevice = WASAPI_CloseDevice;
|
||||
impl->DeinitializeStart = WASAPI_DeinitializeStart;
|
||||
impl->Deinitialize = WASAPI_Deinitialize;
|
||||
impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle;
|
||||
|
||||
impl->HasRecordingSupport = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioBootStrap WASAPI_bootstrap = {
|
||||
"wasapi", "WASAPI", WASAPI_Init, false
|
||||
};
|
||||
|
||||
#endif // SDL_AUDIO_DRIVER_WASAPI
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_wasapi_h_
|
||||
#define SDL_wasapi_h_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "../SDL_sysaudio.h"
|
||||
|
||||
struct SDL_PrivateAudioData
|
||||
{
|
||||
WCHAR *devid;
|
||||
WAVEFORMATEX *waveformat;
|
||||
IAudioClient *client;
|
||||
IAudioRenderClient *render;
|
||||
IAudioCaptureClient *capture;
|
||||
HANDLE event;
|
||||
HANDLE task;
|
||||
bool coinitialized;
|
||||
int framesize;
|
||||
SDL_AtomicInt device_disconnecting;
|
||||
bool device_lost;
|
||||
bool device_dead;
|
||||
};
|
||||
|
||||
// win32 implementation calls into these.
|
||||
bool WASAPI_PrepDevice(SDL_AudioDevice *device);
|
||||
void WASAPI_DisconnectDevice(SDL_AudioDevice *device); // don't hold the device lock when calling this!
|
||||
|
||||
|
||||
// BE CAREFUL: if you are holding the device lock and proxy to the management thread with wait_until_complete, and grab the lock again, you will deadlock.
|
||||
typedef bool (*ManagementThreadTask)(void *userdata);
|
||||
bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, bool *wait_until_complete);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SDL_wasapi_h_
|
||||
Reference in New Issue
Block a user