8#include <TargetConditionals.h>
40 "SDL2AudioBackend is unavailable when building with SDL3");
88 LOG_WARN(
"AudioBackend",
"Already initialized, shutting down first");
94 SDL_AudioSpec want, have;
95 SDL_memset(&want, 0,
sizeof(want));
104 want.callback =
nullptr;
115 int allowed_changes = SDL_AUDIO_ALLOW_FORMAT_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE;
116#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
117 SDL_SetHint(SDL_HINT_AUDIO_CATEGORY,
"ambient");
119 device_id_ = SDL_OpenAudioDevice(
nullptr, 0, &want, &have, allowed_changes);
122 LOG_ERROR(
"AudioBackend",
"Failed to open SDL audio device: %s",
132 if (have.freq != want.freq || have.channels != want.channels) {
134 "Audio spec mismatch - wanted %dHz %dch, got %dHz %dch", want.freq,
135 want.channels, have.freq, have.channels);
142 "SDL2 audio initialized: %dHz, %d channels, buffer: want=%d, have=%d",
143 have.freq, have.channels, want.samples, have.samples);
145 "Device actual: freq=%d, format=0x%04X, channels=%d (device_id=%u)",
182 LOG_INFO(
"AudioBackend",
"SDL2 audio shut down");
187 LOG_WARN(
"AudioBackend",
"Play() called but not initialized!");
190 SDL_AudioStatus status_before = SDL_GetAudioDeviceStatus(
device_id_);
192 SDL_AudioStatus status_after = SDL_GetAudioDeviceStatus(
device_id_);
193 LOG_INFO(
"AudioBackend",
"Play() device=%u: status %d -> %d (0=stopped,1=playing,2=paused)",
213 uint32_t before = SDL_GetQueuedAudioSize(
device_id_);
218 uint32_t after = SDL_GetQueuedAudioSize(
device_id_);
219 LOG_INFO(
"AudioBackend",
"Clear() device=%u: queue %u -> %u bytes",
device_id_, before, after);
227 static int queue_log = 0;
228 if (++queue_log % 2000 == 0) {
229 LOG_DEBUG(
"AudioBackend",
"QueueSamples: %d samples to device %u", num_samples,
device_id_);
235 SDL_QueueAudio(
device_id_, samples, num_samples *
sizeof(int16_t));
237 static int error_log = 0;
238 if (++error_log % 60 == 0) {
239 LOG_ERROR(
"AudioBackend",
"SDL_QueueAudio failed: %s", SDL_GetError());
248 thread_local std::vector<int16_t> scaled_samples;
251 if (scaled_samples.size() <
static_cast<size_t>(num_samples)) {
252 scaled_samples.resize(num_samples);
256 for (
int i = 0; i < num_samples; ++i) {
257 int32_t scaled =
static_cast<int32_t
>(samples[i] *
volume_);
261 else if (scaled < -32768)
263 scaled_samples[i] =
static_cast<int16_t
>(scaled);
266 int result = SDL_QueueAudio(
device_id_, scaled_samples.data(),
267 num_samples *
sizeof(int16_t));
269 static int error_log = 0;
270 if (++error_log % 60 == 0) {
271 LOG_ERROR(
"AudioBackend",
"SDL_QueueAudio failed: %s", SDL_GetError());
284 std::vector<int16_t> int_samples(num_samples);
285 for (
int i = 0; i < num_samples; ++i) {
286 float scaled = std::clamp(samples[i] *
volume_, -1.0f, 1.0f);
287 int_samples[i] =
static_cast<int16_t
>(scaled * 32767.0f);
294 int frames_per_channel,
int channels,
300 "QueueSamplesNative [device=%u]: frames=%d, ch=%d, native=%dHz, "
301 "stream_enabled=%d, stream=%p, device_freq=%dHz, calls=%d",
302 device_id_, frames_per_channel, channels, native_rate,
308 static int init_fail_log = 0;
309 if (++init_fail_log % 300 == 0) {
310 LOG_WARN(
"AudioBackend",
"QueueSamplesNative: FAILED (init=%d, samples=%p)",
317 static int stream_fail_log = 0;
318 if (++stream_fail_log % 600 == 0) {
319 LOG_WARN(
"AudioBackend",
"QueueSamplesNative: STREAM DISABLED (enabled=%d, stream=%p) - Audio will play at WRONG SPEED!",
325 static int native_log = 0;
326 if (++native_log % 300 == 0) {
327 LOG_DEBUG(
"AudioBackend",
"QueueSamplesNative: %d frames (Native: %d, Stream: %d)", frames_per_channel, native_rate,
stream_native_rate_);
338 frames_per_channel * channels *
static_cast<int>(
sizeof(int16_t));
340 if (SDL_AudioStreamPut(
audio_stream_, samples, bytes_in) < 0) {
341 static int put_log = 0;
342 if (++put_log % 60 == 0) {
343 LOG_ERROR(
"AudioBackend",
"SDL_AudioStreamPut failed: %s", SDL_GetError());
348 const int available_bytes = SDL_AudioStreamAvailable(
audio_stream_);
349 if (available_bytes < 0) {
350 static int avail_log = 0;
351 if (++avail_log % 60 == 0) {
352 LOG_ERROR(
"AudioBackend",
"SDL_AudioStreamAvailable failed: %s",
358 if (available_bytes == 0) {
362 const int available_samples =
363 available_bytes /
static_cast<int>(
sizeof(int16_t));
364 if (
static_cast<int>(
stream_buffer_.size()) < available_samples) {
370 if (bytes_read < 0) {
371 static int get_log = 0;
372 if (++get_log % 60 == 0) {
373 LOG_ERROR(
"AudioBackend",
"SDL_AudioStreamGet failed: %s", SDL_GetError());
379 static int log_counter = 0;
380 if (++log_counter % 600 == 0) {
382 "Resample trace: In=%d bytes (%dHz), Out=%d bytes (%dHz)", bytes_in,
396 (SDL_GetAudioDeviceStatus(
device_id_) == SDL_AUDIO_PLAYING);
400 int bytes_per_frame =
407 static int underrun_log = 0;
408 if (++underrun_log % 300 == 0) {
409 LOG_WARN(
"AudioBackend",
"Audio underrun risk: queued_frames=%u (device=%u)",
445 if (!needs_recreate) {
456 SDL_NewAudioStream(AUDIO_S16, channels, native_rate,
device_format_,
459 LOG_ERROR(
"AudioBackend",
"SDL_NewAudioStream failed: %s", SDL_GetError());
465 LOG_INFO(
"AudioBackend",
"SDL_AudioStream created: %dHz %dch -> %dHz %dch",
475 volume_ = std::clamp(volume, 0.0f, 1.0f);
499 LOG_INFO(
"AudioBackend",
"Null audio backend initialized (%dHz, %d channels)",
507 LOG_INFO(
"AudioBackend",
"Null audio backend shut down");
546 int frames_per_channel,
int channels,
577 volume_ = std::clamp(volume, 0.0f, 1.0f);
589 LOG_INFO(
"AudioBackend",
"Null backend: resampling %s (%dHz -> %dHz)",
612 return std::make_unique<SDL3AudioBackend>();
614 return std::make_unique<SDL2AudioBackend>();
619 return std::make_unique<SDL3AudioBackend>();
621 LOG_ERROR(
"AudioBackend",
"SDL3 backend requested but not compiled with SDL3 support");
622 return std::make_unique<SDL2AudioBackend>();
627 return std::make_unique<WasmAudioBackend>();
629 LOG_ERROR(
"AudioBackend",
"WASM backend requested but not compiled for Emscripten");
630 return std::make_unique<SDL2AudioBackend>();
634 return std::make_unique<NullAudioBackend>();
637 LOG_ERROR(
"AudioBackend",
"Unknown backend type, using default backend");
639 return std::make_unique<SDL3AudioBackend>();
641 return std::make_unique<SDL2AudioBackend>();
static std::unique_ptr< IAudioBackend > Create(BackendType type)
bool QueueSamplesNative(const int16_t *samples, int frames_per_channel, int channels, int native_rate) override
uint64_t total_queued_frames_
float GetVolume() const override
AudioStatus GetStatus() const override
void SetAudioStreamResampling(bool enable, int native_rate, int channels) override
bool audio_stream_enabled_
bool IsAudioStreamEnabled() const override
AudioConfig GetConfig() const override
bool IsInitialized() const override
uint64_t total_queued_samples_
bool QueueSamples(const int16_t *samples, int num_samples) override
bool Initialize(const AudioConfig &config) override
void SetVolume(float volume) override
uint64_t current_queued_bytes_
AudioConfig GetConfig() const override
SDL_AudioFormat device_format_
bool IsAudioStreamEnabled() const override
void SetVolume(float volume) override
bool Initialize(const AudioConfig &config) override
~SDL2AudioBackend() override
bool IsInitialized() const override
std::vector< int16_t > stream_buffer_
bool audio_stream_enabled_
SDL_AudioStream * audio_stream_
AudioStatus GetStatus() const override
float GetVolume() const override
bool QueueSamplesNative(const int16_t *samples, int frames_per_channel, int channels, int native_rate) override
bool QueueSamples(const int16_t *samples, int num_samples) override
void SetAudioStreamResampling(bool enable, int native_rate, int channels) override
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
SDL2/SDL3 compatibility layer.