yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
sdl3_audio_backend.cc
Go to the documentation of this file.
1// sdl3_audio_backend.cc - SDL3 Audio Backend Implementation
2
3#ifdef YAZE_USE_SDL3
4
6
7#include <algorithm>
8#include <cstring>
9
10#include "util/log.h"
11
12namespace yaze {
13namespace emu {
14namespace audio {
15
16// ============================================================================
17// SDL3AudioBackend Implementation
18// ============================================================================
19
20SDL3AudioBackend::~SDL3AudioBackend() {
21 Shutdown();
22}
23
24bool SDL3AudioBackend::Initialize(const AudioConfig& config) {
25 if (initialized_) {
26 LOG_WARN("AudioBackend", "SDL3 backend already initialized, shutting down first");
27 Shutdown();
28 }
29
30 config_ = config;
31
32 // Set up the audio specification for SDL3
33 SDL_AudioSpec spec;
34 spec.format = (config.format == SampleFormat::INT16) ? SDL_AUDIO_S16 : SDL_AUDIO_F32;
35 spec.channels = config.channels;
36 spec.freq = config.sample_rate;
37
38 // SDL3 uses stream-based API - open audio device stream
39 audio_stream_ = SDL_OpenAudioDeviceStream(
40 SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, // Use default playback device
41 &spec, // Desired spec
42 nullptr, // Callback (nullptr for stream mode)
43 nullptr // User data
44 );
45
46 if (!audio_stream_) {
47 LOG_ERROR("AudioBackend", "SDL3: Failed to open audio stream: %s", SDL_GetError());
48 return false;
49 }
50
51 // Get the actual device ID from the stream
52 device_id_ = SDL_GetAudioStreamDevice(audio_stream_);
53 if (!device_id_) {
54 LOG_ERROR("AudioBackend", "SDL3: Failed to get audio device from stream");
55 SDL_DestroyAudioStream(audio_stream_);
56 audio_stream_ = nullptr;
57 return false;
58 }
59
60 // Get actual device format information
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());
64 // Use requested values as fallback
65 device_format_ = spec.format;
66 device_channels_ = spec.channels;
67 device_freq_ = spec.freq;
68 } else {
69 device_format_ = obtained_spec.format;
70 device_channels_ = obtained_spec.channels;
71 device_freq_ = obtained_spec.freq;
72
73 // Update config if we got different values
74 if (device_freq_ != config_.sample_rate || device_channels_ != config_.channels) {
75 LOG_WARN("AudioBackend",
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_;
80 }
81 }
82
83 LOG_INFO("AudioBackend",
84 "SDL3 audio initialized: %dHz, %d channels, format=%d",
85 device_freq_, device_channels_, device_format_);
86
87 initialized_ = true;
88 resampling_enabled_ = false;
89 native_rate_ = 0;
90 native_channels_ = 0;
91 resample_buffer_.clear();
92
93 // Start playback immediately
94 if (SDL_ResumeAudioDevice(device_id_) < 0) {
95 LOG_ERROR("AudioBackend", "SDL3: Failed to resume audio device: %s", SDL_GetError());
96 Shutdown();
97 return false;
98 }
99
100 return true;
101}
102
103void SDL3AudioBackend::Shutdown() {
104 if (!initialized_) {
105 return;
106 }
107
108 // Clean up resampling stream
109 if (resampling_stream_) {
110 SDL_DestroyAudioStream(resampling_stream_);
111 resampling_stream_ = nullptr;
112 }
113 resampling_enabled_ = false;
114 native_rate_ = 0;
115 native_channels_ = 0;
116 resample_buffer_.clear();
117
118 // Pause device before cleanup
119 if (device_id_) {
120 SDL_PauseAudioDevice(device_id_);
121 }
122
123 // Destroy main audio stream
124 if (audio_stream_) {
125 SDL_DestroyAudioStream(audio_stream_);
126 audio_stream_ = nullptr;
127 }
128
129 device_id_ = 0;
130 initialized_ = false;
131
132 LOG_INFO("AudioBackend", "SDL3 audio shut down");
133}
134
135void SDL3AudioBackend::Play() {
136 if (!initialized_ || !device_id_) {
137 return;
138 }
139 SDL_ResumeAudioDevice(device_id_);
140}
141
142void SDL3AudioBackend::Pause() {
143 if (!initialized_ || !device_id_) {
144 return;
145 }
146 SDL_PauseAudioDevice(device_id_);
147}
148
149void SDL3AudioBackend::Stop() {
150 if (!initialized_) {
151 return;
152 }
153 Clear();
154 if (device_id_) {
155 SDL_PauseAudioDevice(device_id_);
156 }
157}
158
159void SDL3AudioBackend::Clear() {
160 if (!initialized_) {
161 return;
162 }
163
164 if (audio_stream_) {
165 SDL_ClearAudioStream(audio_stream_);
166 }
167
168 if (resampling_stream_) {
169 SDL_ClearAudioStream(resampling_stream_);
170 }
171}
172
173bool SDL3AudioBackend::QueueSamples(const int16_t* samples, int num_samples) {
174 if (!initialized_ || !audio_stream_ || !samples) {
175 return false;
176 }
177
178 // Fast path: No volume adjustment needed
179 if (volume_ == 1.0f) {
180 int result = SDL_PutAudioStreamData(audio_stream_, samples,
181 num_samples * sizeof(int16_t));
182 if (result < 0) {
183 LOG_ERROR("AudioBackend", "SDL3: SDL_PutAudioStreamData failed: %s", SDL_GetError());
184 return false;
185 }
186 return true;
187 }
188
189 // Slow path: Volume scaling required
190 thread_local std::vector<int16_t> scaled_samples;
191
192 if (scaled_samples.size() < static_cast<size_t>(num_samples)) {
193 scaled_samples.resize(num_samples);
194 }
195
196 // Apply volume scaling
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));
201 }
202
203 int result = SDL_PutAudioStreamData(audio_stream_, scaled_samples.data(),
204 num_samples * sizeof(int16_t));
205 if (result < 0) {
206 LOG_ERROR("AudioBackend", "SDL3: SDL_PutAudioStreamData failed: %s", SDL_GetError());
207 return false;
208 }
209
210 return true;
211}
212
213bool SDL3AudioBackend::QueueSamples(const float* samples, int num_samples) {
214 if (!initialized_ || !audio_stream_ || !samples) {
215 return false;
216 }
217
218 // Convert float to int16 with volume scaling
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);
222 }
223
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);
228 }
229
230 return QueueSamples(int_samples.data(), num_samples);
231}
232
233bool SDL3AudioBackend::QueueSamplesNative(const int16_t* samples,
234 int frames_per_channel, int channels,
235 int native_rate) {
236 if (!initialized_ || !samples) {
237 return false;
238 }
239
240 // Check if we need to set up resampling
241 if (!resampling_enabled_ || !resampling_stream_) {
242 LOG_WARN("AudioBackend", "SDL3: Native rate resampling not enabled");
243 return false;
244 }
245
246 // Verify the resampling configuration matches
247 if (native_rate != native_rate_ || channels != native_channels_) {
248 SetAudioStreamResampling(true, native_rate, channels);
249 if (!resampling_stream_) {
250 return false;
251 }
252 }
253
254 const int bytes_in = frames_per_channel * channels * static_cast<int>(sizeof(int16_t));
255
256 // Put data into resampling stream
257 if (SDL_PutAudioStreamData(resampling_stream_, samples, bytes_in) < 0) {
258 LOG_ERROR("AudioBackend", "SDL3: Failed to put data in resampling stream: %s",
259 SDL_GetError());
260 return false;
261 }
262
263 // Get available resampled data
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",
267 SDL_GetError());
268 return false;
269 }
270
271 if (available_bytes == 0) {
272 return true; // No data ready yet
273 }
274
275 // Resize buffer if needed
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);
279 }
280
281 // Get resampled data
282 int bytes_read = SDL_GetAudioStreamData(resampling_stream_,
283 resample_buffer_.data(),
284 available_bytes);
285 if (bytes_read < 0) {
286 LOG_ERROR("AudioBackend", "SDL3: Failed to get resampled data: %s",
287 SDL_GetError());
288 return false;
289 }
290
291 // Queue the resampled data
292 int samples_read = bytes_read / static_cast<int>(sizeof(int16_t));
293 return QueueSamples(resample_buffer_.data(), samples_read);
294}
295
296AudioStatus SDL3AudioBackend::GetStatus() const {
297 AudioStatus status;
298
299 if (!initialized_) {
300 return status;
301 }
302
303 // Check if device is playing
304 status.is_playing = device_id_ && !SDL_AudioDevicePaused(device_id_);
305
306 // Get queued audio size from stream
307 if (audio_stream_) {
308 int queued_bytes = SDL_GetAudioStreamQueued(audio_stream_);
309 if (queued_bytes >= 0) {
310 status.queued_bytes = static_cast<uint32_t>(queued_bytes);
311 }
312 }
313
314 // Calculate queued frames
315 int bytes_per_frame = config_.channels *
316 (config_.format == SampleFormat::INT16 ? 2 : 4);
317 if (bytes_per_frame > 0) {
318 status.queued_frames = status.queued_bytes / bytes_per_frame;
319 }
320
321 // Check for underrun (queue too low while playing)
322 if (status.is_playing && status.queued_frames < 100) {
323 status.has_underrun = true;
324 }
325
326 return status;
327}
328
329bool SDL3AudioBackend::IsInitialized() const {
330 return initialized_;
331}
332
333AudioConfig SDL3AudioBackend::GetConfig() const {
334 return config_;
335}
336
337void SDL3AudioBackend::SetVolume(float volume) {
338 volume_ = std::clamp(volume, 0.0f, 1.0f);
339}
340
341float SDL3AudioBackend::GetVolume() const {
342 return volume_;
343}
344
345void SDL3AudioBackend::SetAudioStreamResampling(bool enable, int native_rate,
346 int channels) {
347 if (!initialized_) {
348 return;
349 }
350
351 if (!enable) {
352 // Disable resampling
353 if (resampling_stream_) {
354 SDL_DestroyAudioStream(resampling_stream_);
355 resampling_stream_ = nullptr;
356 }
357 resampling_enabled_ = false;
358 native_rate_ = 0;
359 native_channels_ = 0;
360 resample_buffer_.clear();
361 return;
362 }
363
364 // Check if we need to recreate the resampling stream
365 const bool needs_recreate = (resampling_stream_ == nullptr) ||
366 (native_rate_ != native_rate) ||
367 (native_channels_ != channels);
368
369 if (!needs_recreate) {
370 resampling_enabled_ = true;
371 return;
372 }
373
374 // Clean up existing stream
375 if (resampling_stream_) {
376 SDL_DestroyAudioStream(resampling_stream_);
377 resampling_stream_ = nullptr;
378 }
379
380 // Create new resampling stream
381 // Source spec (native rate)
382 SDL_AudioSpec src_spec;
383 src_spec.format = SDL_AUDIO_S16;
384 src_spec.channels = channels;
385 src_spec.freq = native_rate;
386
387 // Destination spec (device rate)
388 SDL_AudioSpec dst_spec;
389 dst_spec.format = device_format_;
390 dst_spec.channels = device_channels_;
391 dst_spec.freq = device_freq_;
392
393 // Create audio stream for resampling
394 resampling_stream_ = SDL_CreateAudioStream(&src_spec, &dst_spec);
395 if (!resampling_stream_) {
396 LOG_ERROR("AudioBackend", "SDL3: Failed to create resampling stream: %s",
397 SDL_GetError());
398 resampling_enabled_ = false;
399 native_rate_ = 0;
400 native_channels_ = 0;
401 return;
402 }
403
404 // Clear any existing data
405 SDL_ClearAudioStream(resampling_stream_);
406
407 // Update state
408 resampling_enabled_ = true;
409 native_rate_ = native_rate;
410 native_channels_ = channels;
411 resample_buffer_.clear();
412
413 LOG_INFO("AudioBackend",
414 "SDL3: Resampling enabled: %dHz %dch -> %dHz %dch",
415 native_rate, channels, device_freq_, device_channels_);
416}
417
418// Helper functions for volume application
419bool SDL3AudioBackend::ApplyVolume(int16_t* samples, int num_samples) const {
420 if (!samples) {
421 return false;
422 }
423
424 float vol = volume_.load();
425 if (vol == 1.0f) {
426 return true; // No change needed
427 }
428
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));
432 }
433
434 return true;
435}
436
437bool SDL3AudioBackend::ApplyVolume(float* samples, int num_samples) const {
438 if (!samples) {
439 return false;
440 }
441
442 float vol = volume_.load();
443 if (vol == 1.0f) {
444 return true; // No change needed
445 }
446
447 for (int i = 0; i < num_samples; ++i) {
448 samples[i] = std::clamp(samples[i] * vol, -1.0f, 1.0f);
449 }
450
451 return true;
452}
453
454} // namespace audio
455} // namespace emu
456} // namespace yaze
457
458#endif // YAZE_USE_SDL3
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105