6.4 KiB
SDL3 Audio Backend Implementation
Date: 2025-11-23 Author: snes-emulator-expert agent Status: Implementation Complete
Overview
This document describes the SDL3 audio backend implementation for the YAZE SNES emulator. The SDL3 backend provides a modern, stream-based audio interface that replaces the SDL2 queue-based approach.
Architecture
Key Components
-
SDL3AudioBackend Class (
src/app/emu/audio/sdl3_audio_backend.h/.cc)- Implements the
IAudioBackendinterface - Uses SDL3's stream-based audio API
- Provides volume control, resampling, and playback management
- Implements the
-
SDL Compatibility Layer (
src/app/platform/sdl_compat.h)- Provides cross-version compatibility macros
- Abstracts differences between SDL2 and SDL3 APIs
- Enables conditional compilation based on
YAZE_USE_SDL3
-
Factory Integration (
src/app/emu/audio/audio_backend.cc)- Updated
AudioBackendFactory::Create()to support SDL3 - Conditional compilation ensures SDL3 backend only available when built with SDL3
- Updated
SDL3 Audio API Changes
Major Differences from SDL2
| SDL2 API | SDL3 API | Purpose |
|---|---|---|
SDL_OpenAudioDevice() |
SDL_OpenAudioDeviceStream() |
Device initialization |
SDL_QueueAudio() |
SDL_PutAudioStreamData() |
Queue audio samples |
SDL_GetQueuedAudioSize() |
SDL_GetAudioStreamQueued() |
Get queued data size |
SDL_ClearQueuedAudio() |
SDL_ClearAudioStream() |
Clear audio buffer |
SDL_PauseAudioDevice(id, 0/1) |
SDL_ResumeAudioDevice() / SDL_PauseAudioDevice() |
Control playback |
SDL_GetAudioDeviceStatus() |
SDL_IsAudioDevicePaused() |
Check playback state |
Stream-Based Architecture
SDL3 introduces SDL_AudioStream as the primary interface for audio:
// Create stream with device
SDL_AudioStream* stream = SDL_OpenAudioDeviceStream(
SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, // Use default device
&spec, // Desired format
nullptr, // No callback
nullptr // No user data
);
// Queue audio data
SDL_PutAudioStreamData(stream, samples, size_in_bytes);
// Get device from stream
SDL_AudioDeviceID device = SDL_GetAudioStreamDevice(stream);
// Control playback through device
SDL_ResumeAudioDevice(device);
SDL_PauseAudioDevice(device);
Implementation Details
Initialization
The Initialize() method:
- Creates an audio stream using
SDL_OpenAudioDeviceStream() - Extracts the device ID from the stream
- Queries actual device format (may differ from requested)
- Starts playback immediately with
SDL_ResumeAudioDevice()
Audio Data Flow
Application → QueueSamples() → Volume Scaling → SDL_PutAudioStreamData() → SDL3 → Audio Device
Volume Control
Volume is applied during sample queueing:
- Fast path: When volume = 1.0, samples pass through unchanged
- Slow path: Samples are scaled by volume factor with clamping
Resampling Support
The backend supports native rate resampling for SPC700 emulation:
- Setup: Create separate resampling stream with
SDL_CreateAudioStream() - Input: Native rate samples (e.g., 32kHz from SPC700)
- Process: SDL3 handles resampling internally
- Output: Resampled data at device rate (e.g., 48kHz)
Thread Safety
- Volume control uses
std::atomic<float>for thread-safe access - Initialization state tracked with
std::atomic<bool> - SDL3 handles internal thread safety for audio streams
Build Configuration
CMake Integration
The SDL3 backend is conditionally compiled based on the YAZE_USE_SDL3 flag:
# In src/CMakeLists.txt
if(YAZE_USE_SDL3)
list(APPEND YAZE_APP_EMU_SRC app/emu/audio/sdl3_audio_backend.cc)
endif()
Compilation Flags
- Define
YAZE_USE_SDL3to enable SDL3 support - Include paths must contain SDL3 headers
- Link against SDL3 library (not SDL2)
Testing
Unit Tests
Located in test/unit/sdl3_audio_backend_test.cc:
- Basic initialization and shutdown
- Volume control
- Sample queueing (int16 and float)
- Playback control (play/pause/stop)
- Queue clearing
- Resampling support
- Double initialization handling
Integration Testing
To test the SDL3 audio backend in the emulator:
-
Build with SDL3 support:
cmake -DYAZE_USE_SDL3=ON .. make -
Run the emulator with a ROM:
./yaze --rom_file=zelda3.sfc -
Verify audio playback in the emulator
Performance Considerations
Optimizations
-
Volume Scaling Fast Path
- Skip processing when volume = 1.0 (common case)
- Use thread-local buffers to avoid allocations
-
Buffer Management
- Reuse buffers for resampling operations
- Pre-allocate based on expected sizes
-
Minimal Locking
- Rely on SDL3's internal thread safety
- Use lock-free atomics for shared state
Latency
SDL3's stream-based approach can provide lower latency than SDL2's queue:
- Smaller buffer sizes possible
- More direct path to audio hardware
- Better synchronization with video
Known Issues and Limitations
-
Platform Support
- SDL3 is newer and may not be available on all platforms
- Fallback to SDL2 backend when SDL3 unavailable
-
API Stability
- SDL3 API may still evolve
- Monitor SDL3 releases for breaking changes
-
Device Enumeration
- Current implementation uses default device only
- Could be extended to support device selection
Future Enhancements
-
Device Selection
- Add support for choosing specific audio devices
- Implement device change notifications
-
Advanced Resampling
- Expose resampling quality settings
- Support for multiple resampling streams
-
Spatial Audio
- Leverage SDL3's potential spatial audio capabilities
- Support for surround sound configurations
-
Performance Monitoring
- Add metrics for buffer underruns
- Track actual vs requested latency
Migration from SDL2
To migrate from SDL2 to SDL3 backend:
- Install SDL3 development libraries
- Set
YAZE_USE_SDL3=ONin CMake - Rebuild the project
- Audio backend factory automatically selects SDL3
No code changes required in the emulator - the IAudioBackend interface abstracts the differences.