222 lines
6.4 KiB
Markdown
222 lines
6.4 KiB
Markdown
# 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
|
|
|
|
1. **SDL3AudioBackend Class** (`src/app/emu/audio/sdl3_audio_backend.h/.cc`)
|
|
- Implements the `IAudioBackend` interface
|
|
- Uses SDL3's stream-based audio API
|
|
- Provides volume control, resampling, and playback management
|
|
|
|
2. **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`
|
|
|
|
3. **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
|
|
|
|
## 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:
|
|
|
|
```cpp
|
|
// 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:
|
|
1. Creates an audio stream using `SDL_OpenAudioDeviceStream()`
|
|
2. Extracts the device ID from the stream
|
|
3. Queries actual device format (may differ from requested)
|
|
4. 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:
|
|
|
|
1. **Setup**: Create separate resampling stream with `SDL_CreateAudioStream()`
|
|
2. **Input**: Native rate samples (e.g., 32kHz from SPC700)
|
|
3. **Process**: SDL3 handles resampling internally
|
|
4. **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:
|
|
|
|
```cmake
|
|
# 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_SDL3` to 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:
|
|
|
|
1. Build with SDL3 support:
|
|
```bash
|
|
cmake -DYAZE_USE_SDL3=ON ..
|
|
make
|
|
```
|
|
|
|
2. Run the emulator with a ROM:
|
|
```bash
|
|
./yaze --rom_file=zelda3.sfc
|
|
```
|
|
|
|
3. Verify audio playback in the emulator
|
|
|
|
## Performance Considerations
|
|
|
|
### Optimizations
|
|
|
|
1. **Volume Scaling Fast Path**
|
|
- Skip processing when volume = 1.0 (common case)
|
|
- Use thread-local buffers to avoid allocations
|
|
|
|
2. **Buffer Management**
|
|
- Reuse buffers for resampling operations
|
|
- Pre-allocate based on expected sizes
|
|
|
|
3. **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
|
|
|
|
1. **Platform Support**
|
|
- SDL3 is newer and may not be available on all platforms
|
|
- Fallback to SDL2 backend when SDL3 unavailable
|
|
|
|
2. **API Stability**
|
|
- SDL3 API may still evolve
|
|
- Monitor SDL3 releases for breaking changes
|
|
|
|
3. **Device Enumeration**
|
|
- Current implementation uses default device only
|
|
- Could be extended to support device selection
|
|
|
|
## Future Enhancements
|
|
|
|
1. **Device Selection**
|
|
- Add support for choosing specific audio devices
|
|
- Implement device change notifications
|
|
|
|
2. **Advanced Resampling**
|
|
- Expose resampling quality settings
|
|
- Support for multiple resampling streams
|
|
|
|
3. **Spatial Audio**
|
|
- Leverage SDL3's potential spatial audio capabilities
|
|
- Support for surround sound configurations
|
|
|
|
4. **Performance Monitoring**
|
|
- Add metrics for buffer underruns
|
|
- Track actual vs requested latency
|
|
|
|
## Migration from SDL2
|
|
|
|
To migrate from SDL2 to SDL3 backend:
|
|
|
|
1. Install SDL3 development libraries
|
|
2. Set `YAZE_USE_SDL3=ON` in CMake
|
|
3. Rebuild the project
|
|
4. Audio backend factory automatically selects SDL3
|
|
|
|
No code changes required in the emulator - the `IAudioBackend` interface abstracts the differences.
|
|
|
|
## References
|
|
|
|
- [SDL3 Migration Guide](https://wiki.libsdl.org/SDL3/README-migration)
|
|
- [SDL3 Audio API Documentation](https://wiki.libsdl.org/SDL3/CategoryAudio)
|
|
- [SDL_AudioStream Documentation](https://wiki.libsdl.org/SDL3/SDL_AudioStream) |