20SDL3AudioBackend::~SDL3AudioBackend() {
24bool SDL3AudioBackend::Initialize(
const AudioConfig& config) {
26 LOG_WARN(
"AudioBackend",
"SDL3 backend already initialized, shutting down first");
35 spec.channels = config.channels;
36 spec.freq = config.sample_rate;
39 audio_stream_ = SDL_OpenAudioDeviceStream(
40 SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
47 LOG_ERROR(
"AudioBackend",
"SDL3: Failed to open audio stream: %s", SDL_GetError());
52 device_id_ = SDL_GetAudioStreamDevice(audio_stream_);
54 LOG_ERROR(
"AudioBackend",
"SDL3: Failed to get audio device from stream");
55 SDL_DestroyAudioStream(audio_stream_);
56 audio_stream_ =
nullptr;
61 SDL_AudioSpec obtained_spec;
62 if (SDL_GetAudioDeviceFormat(device_id_, &obtained_spec,
nullptr) < 0) {
63 LOG_WARN(
"AudioBackend",
"SDL3: Could not query device format: %s", SDL_GetError());
65 device_format_ = spec.format;
66 device_channels_ = spec.channels;
67 device_freq_ = spec.freq;
69 device_format_ = obtained_spec.format;
70 device_channels_ = obtained_spec.channels;
71 device_freq_ = obtained_spec.freq;
74 if (device_freq_ != config_.sample_rate || device_channels_ != config_.channels) {
76 "SDL3: Audio spec mismatch - wanted %dHz %dch, got %dHz %dch",
77 config_.sample_rate, config_.channels, device_freq_, device_channels_);
78 config_.sample_rate = device_freq_;
79 config_.channels = device_channels_;
84 "SDL3 audio initialized: %dHz, %d channels, format=%d",
85 device_freq_, device_channels_, device_format_);
88 resampling_enabled_ =
false;
91 resample_buffer_.clear();
94 if (SDL_ResumeAudioDevice(device_id_) < 0) {
95 LOG_ERROR(
"AudioBackend",
"SDL3: Failed to resume audio device: %s", SDL_GetError());
103void SDL3AudioBackend::Shutdown() {
109 if (resampling_stream_) {
110 SDL_DestroyAudioStream(resampling_stream_);
111 resampling_stream_ =
nullptr;
113 resampling_enabled_ =
false;
115 native_channels_ = 0;
116 resample_buffer_.clear();
120 SDL_PauseAudioDevice(device_id_);
125 SDL_DestroyAudioStream(audio_stream_);
126 audio_stream_ =
nullptr;
130 initialized_ =
false;
132 LOG_INFO(
"AudioBackend",
"SDL3 audio shut down");
135void SDL3AudioBackend::Play() {
136 if (!initialized_ || !device_id_) {
139 SDL_ResumeAudioDevice(device_id_);
142void SDL3AudioBackend::Pause() {
143 if (!initialized_ || !device_id_) {
146 SDL_PauseAudioDevice(device_id_);
149void SDL3AudioBackend::Stop() {
155 SDL_PauseAudioDevice(device_id_);
159void SDL3AudioBackend::Clear() {
165 SDL_ClearAudioStream(audio_stream_);
168 if (resampling_stream_) {
169 SDL_ClearAudioStream(resampling_stream_);
173bool SDL3AudioBackend::QueueSamples(
const int16_t* samples,
int num_samples) {
174 if (!initialized_ || !audio_stream_ || !samples) {
179 if (volume_ == 1.0f) {
180 int result = SDL_PutAudioStreamData(audio_stream_, samples,
181 num_samples *
sizeof(int16_t));
183 LOG_ERROR(
"AudioBackend",
"SDL3: SDL_PutAudioStreamData failed: %s", SDL_GetError());
190 thread_local std::vector<int16_t> scaled_samples;
192 if (scaled_samples.size() <
static_cast<size_t>(num_samples)) {
193 scaled_samples.resize(num_samples);
197 float vol = volume_.load();
198 for (
int i = 0; i < num_samples; ++i) {
199 int32_t scaled =
static_cast<int32_t
>(samples[i] * vol);
200 scaled_samples[i] =
static_cast<int16_t
>(std::clamp(scaled, -32768, 32767));
203 int result = SDL_PutAudioStreamData(audio_stream_, scaled_samples.data(),
204 num_samples *
sizeof(int16_t));
206 LOG_ERROR(
"AudioBackend",
"SDL3: SDL_PutAudioStreamData failed: %s", SDL_GetError());
213bool SDL3AudioBackend::QueueSamples(
const float* samples,
int num_samples) {
214 if (!initialized_ || !audio_stream_ || !samples) {
219 thread_local std::vector<int16_t> int_samples;
220 if (int_samples.size() <
static_cast<size_t>(num_samples)) {
221 int_samples.resize(num_samples);
224 float vol = volume_.load();
225 for (
int i = 0; i < num_samples; ++i) {
226 float scaled = std::clamp(samples[i] * vol, -1.0f, 1.0f);
227 int_samples[i] =
static_cast<int16_t
>(scaled * 32767.0f);
230 return QueueSamples(int_samples.data(), num_samples);
233bool SDL3AudioBackend::QueueSamplesNative(
const int16_t* samples,
234 int frames_per_channel,
int channels,
236 if (!initialized_ || !samples) {
241 if (!resampling_enabled_ || !resampling_stream_) {
242 LOG_WARN(
"AudioBackend",
"SDL3: Native rate resampling not enabled");
247 if (native_rate != native_rate_ || channels != native_channels_) {
248 SetAudioStreamResampling(
true, native_rate, channels);
249 if (!resampling_stream_) {
254 const int bytes_in = frames_per_channel * channels *
static_cast<int>(
sizeof(int16_t));
257 if (SDL_PutAudioStreamData(resampling_stream_, samples, bytes_in) < 0) {
258 LOG_ERROR(
"AudioBackend",
"SDL3: Failed to put data in resampling stream: %s",
264 int available_bytes = SDL_GetAudioStreamAvailable(resampling_stream_);
265 if (available_bytes < 0) {
266 LOG_ERROR(
"AudioBackend",
"SDL3: Failed to get available stream data: %s",
271 if (available_bytes == 0) {
276 int available_samples = available_bytes /
static_cast<int>(
sizeof(int16_t));
277 if (
static_cast<int>(resample_buffer_.size()) < available_samples) {
278 resample_buffer_.resize(available_samples);
282 int bytes_read = SDL_GetAudioStreamData(resampling_stream_,
283 resample_buffer_.data(),
285 if (bytes_read < 0) {
286 LOG_ERROR(
"AudioBackend",
"SDL3: Failed to get resampled data: %s",
292 int samples_read = bytes_read /
static_cast<int>(
sizeof(int16_t));
293 return QueueSamples(resample_buffer_.data(), samples_read);
296AudioStatus SDL3AudioBackend::GetStatus()
const {
304 status.is_playing = device_id_ && !SDL_AudioDevicePaused(device_id_);
308 int queued_bytes = SDL_GetAudioStreamQueued(audio_stream_);
309 if (queued_bytes >= 0) {
310 status.queued_bytes =
static_cast<uint32_t
>(queued_bytes);
315 int bytes_per_frame = config_.channels *
317 if (bytes_per_frame > 0) {
318 status.queued_frames = status.queued_bytes / bytes_per_frame;
322 if (status.is_playing && status.queued_frames < 100) {
323 status.has_underrun =
true;
329bool SDL3AudioBackend::IsInitialized()
const {
333AudioConfig SDL3AudioBackend::GetConfig()
const {
337void SDL3AudioBackend::SetVolume(
float volume) {
338 volume_ = std::clamp(volume, 0.0f, 1.0f);
341float SDL3AudioBackend::GetVolume()
const {
345void SDL3AudioBackend::SetAudioStreamResampling(
bool enable,
int native_rate,
353 if (resampling_stream_) {
354 SDL_DestroyAudioStream(resampling_stream_);
355 resampling_stream_ =
nullptr;
357 resampling_enabled_ =
false;
359 native_channels_ = 0;
360 resample_buffer_.clear();
365 const bool needs_recreate = (resampling_stream_ ==
nullptr) ||
366 (native_rate_ != native_rate) ||
367 (native_channels_ != channels);
369 if (!needs_recreate) {
370 resampling_enabled_ =
true;
375 if (resampling_stream_) {
376 SDL_DestroyAudioStream(resampling_stream_);
377 resampling_stream_ =
nullptr;
382 SDL_AudioSpec src_spec;
383 src_spec.format = SDL_AUDIO_S16;
384 src_spec.channels = channels;
385 src_spec.freq = native_rate;
388 SDL_AudioSpec dst_spec;
389 dst_spec.format = device_format_;
390 dst_spec.channels = device_channels_;
391 dst_spec.freq = device_freq_;
394 resampling_stream_ = SDL_CreateAudioStream(&src_spec, &dst_spec);
395 if (!resampling_stream_) {
396 LOG_ERROR(
"AudioBackend",
"SDL3: Failed to create resampling stream: %s",
398 resampling_enabled_ =
false;
400 native_channels_ = 0;
405 SDL_ClearAudioStream(resampling_stream_);
408 resampling_enabled_ =
true;
409 native_rate_ = native_rate;
410 native_channels_ = channels;
411 resample_buffer_.clear();
414 "SDL3: Resampling enabled: %dHz %dch -> %dHz %dch",
415 native_rate, channels, device_freq_, device_channels_);
419bool SDL3AudioBackend::ApplyVolume(int16_t* samples,
int num_samples)
const {
424 float vol = volume_.load();
429 for (
int i = 0; i < num_samples; ++i) {
430 int32_t scaled =
static_cast<int32_t
>(samples[i] * vol);
431 samples[i] =
static_cast<int16_t
>(std::clamp(scaled, -32768, 32767));
437bool SDL3AudioBackend::ApplyVolume(
float* samples,
int num_samples)
const {
442 float vol = volume_.load();
447 for (
int i = 0; i < num_samples; ++i) {
448 samples[i] = std::clamp(samples[i] * vol, -1.0f, 1.0f);
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)