audio.rsannotatedaudio.rssource338 lines · 10.8 KB · raw
1use crate::config::CommonConfig;
2use jgenesis_common::audio::DynamicResamplingRate;
3use jgenesis_common::frontend::AudioOutput;
4use sdl3::AudioSubsystem;
5use sdl3::audio::{AudioCallback, AudioFormat, AudioSpec, AudioStream, AudioStreamWithCallback};
6use std::cell::RefCell;
7use std::collections::VecDeque;
8use std::rc::Rc;
9use std::sync::{Arc, Mutex, MutexGuard};
10use std::thread::Thread;
11use std::time::Duration;
12use std::{cmp, thread};
13use thiserror::Error;
14
15// Always output in stereo
16const CHANNELS: i32 = 2;
17
18// Number of samples to buffer before locking and pushing to the audio queue
19const INTERNAL_AUDIO_BUFFER_LEN: usize = 16;
20
21#[derive(Debug, Error)]
22pub enum AudioError {
23    #[error("Error opening SDL3 audio stream: {0}")]
24    OpenStream(sdl3::Error),
25    #[error("Error pausing SDL3 audio stream: {0}")]
26    PauseStream(sdl3::Error),
27    #[error("Error pushing audio samples to SDL3 audio stream: {0}")]
28    QueueAudio(sdl3::Error),
29}
30
31pub type AudioResult<T> = Result<T, AudioError>;
32
33struct AudioCallbackState {
34    queue: VecDeque<(f32, f32)>,
35    last_sample: (f32, f32),
36    hardware_queue_size: u32,
37    unpark_threshold: u32,
38    emulator_thread: Thread,
39    error: Option<sdl3::Error>,
40}
41
42impl AudioCallbackState {
43    fn new(config: &CommonConfig) -> Self {
44        Self {
45            queue: VecDeque::with_capacity(2 * config.audio_buffer_size as usize),
46            last_sample: (0.0, 0.0),
47            hardware_queue_size: config.audio_hardware_queue_size,
48            unpark_threshold: audio_sync_threshold(config),
49            emulator_thread: thread::current(),
50            error: None,
51        }
52    }
53}
54
55struct AudioQueueCallback {
56    state: Arc<Mutex<AudioCallbackState>>,
57}
58
59impl AudioCallback<f32> for AudioQueueCallback {
60    fn callback(&mut self, stream: &mut AudioStream, requested: i32) {
61        if requested <= 0 {
62            return;
63        }
64
65        let mut state = self.state.lock().unwrap();
66
67        let stereo_samples = cmp::max(state.hardware_queue_size, ((requested + 1) / 2) as u32);
68        for _ in 0..stereo_samples {
69            // Repeating the last sample on buffer underruns prevents awful noises for some systems (e.g. 32X)
70            let (sample_l, sample_r) = state.queue.pop_front().unwrap_or(state.last_sample);
71            state.last_sample = (sample_l, sample_r);
72
73            if let Err(err) = stream.put_data_f32(&[sample_l, sample_r]) {
74                log::error!("Error pushing audio samples: {err}");
75                state.error = Some(err);
76                state.emulator_thread.unpark();
77                break;
78            }
79        }
80
81        if state.queue.len() <= state.unpark_threshold as usize {
82            state.emulator_thread.unpark();
83        }
84    }
85}
86
87pub struct SdlAudioOutputHandle {
88    audio_subsystem: Rc<RefCell<AudioSubsystem>>,
89    audio_stream: AudioStreamWithCallback<AudioQueueCallback>,
90    callback_state: Arc<Mutex<AudioCallbackState>>,
91    output_frequency: u64,
92}
93
94pub struct SdlAudioOutput {
95    callback_state: Arc<Mutex<AudioCallbackState>>,
96    muted: bool,
97    audio_buffer: Vec<(f32, f32)>,
98    audio_sync: bool,
99    audio_sync_threshold: u32,
100    dynamic_resampling_ratio_enabled: bool,
101    dynamic_resampling_rate: DynamicResamplingRate,
102    output_frequency: u64,
103    audio_buffer_size: u32,
104    audio_gain_multiplier: f64,
105    sample_count: u64,
106    speed_multiplier: u64,
107}
108
109impl SdlAudioOutputHandle {
110    pub fn reload_config(&mut self, config: &CommonConfig) -> AudioResult<()> {
111        let freq_changed = self.output_frequency != config.audio_output_frequency;
112
113        self.output_frequency = config.audio_output_frequency;
114
115        if freq_changed {
116            // Recreate audio stream on sample rate changes
117
118            log::info!("Recreating SDL audio queue with freq {}", config.audio_output_frequency);
119
120            self.audio_stream.pause().map_err(AudioError::PauseStream)?;
121
122            self.audio_stream = open_audio_stream(
123                &self.audio_subsystem.borrow(),
124                config,
125                Arc::clone(&self.callback_state),
126            )?;
127        }
128
129        {
130            let mut state = self.callback_state.lock().unwrap();
131            state.hardware_queue_size = config.audio_hardware_queue_size;
132            state.unpark_threshold = audio_sync_threshold(config);
133
134            // Truncate audio queue on config reloads if it is way oversized OR if sample rate changed
135            if freq_changed || state.queue.len() >= (4 * config.audio_buffer_size) as usize {
136                state.queue.clear();
137            }
138        }
139
140        Ok(())
141    }
142
143    pub fn set_emulator_thread(&self, thread: Thread) {
144        self.callback_state.lock().unwrap().emulator_thread = thread;
145    }
146}
147
148impl SdlAudioOutput {
149    pub fn create_and_init(
150        audio_subsystem: Rc<RefCell<AudioSubsystem>>,
151        config: &CommonConfig,
152    ) -> AudioResult<(Self, SdlAudioOutputHandle)> {
153        let callback_state = Arc::new(Mutex::new(AudioCallbackState::new(config)));
154
155        let audio_stream =
156            open_audio_stream(&audio_subsystem.borrow(), config, Arc::clone(&callback_state))?;
157
158        let audio_output = Self {
159            muted: config.mute_audio,
160            callback_state: Arc::clone(&callback_state),
161            audio_buffer: Vec::with_capacity(INTERNAL_AUDIO_BUFFER_LEN),
162            audio_sync: config.audio_sync,
163            audio_sync_threshold: audio_sync_threshold(config),
164            dynamic_resampling_ratio_enabled: config.audio_dynamic_resampling_ratio,
165            dynamic_resampling_rate: DynamicResamplingRate::new(
166                config.audio_output_frequency as u32,
167                config.audio_buffer_size,
168            ),
169            output_frequency: config.audio_output_frequency,
170            audio_buffer_size: config.audio_buffer_size,
171            audio_gain_multiplier: decibels_to_multiplier(config.audio_gain_db),
172            sample_count: 0,
173            speed_multiplier: 1,
174        };
175
176        let handle = SdlAudioOutputHandle {
177            audio_subsystem,
178            audio_stream,
179            callback_state,
180            output_frequency: config.audio_output_frequency,
181        };
182
183        Ok((audio_output, handle))
184    }
185
186    pub fn reload_config(&mut self, config: &CommonConfig) {
187        let freq_changed = self.output_frequency != config.audio_output_frequency;
188        let buffer_size_changed = self.audio_buffer_size != config.audio_buffer_size;
189
190        self.muted = config.mute_audio;
191        self.audio_sync = config.audio_sync;
192        self.dynamic_resampling_ratio_enabled = config.audio_dynamic_resampling_ratio;
193        self.output_frequency = config.audio_output_frequency;
194        self.audio_buffer_size = config.audio_buffer_size;
195        self.audio_gain_multiplier = decibels_to_multiplier(config.audio_gain_db);
196        self.audio_sync_threshold = audio_sync_threshold(config);
197
198        if freq_changed || buffer_size_changed {
199            self.dynamic_resampling_rate
200                .update_config(self.output_frequency as u32, self.audio_buffer_size);
201        }
202    }
203
204    pub fn set_speed_multiplier(&mut self, speed_multiplier: u64) {
205        self.speed_multiplier = speed_multiplier;
206    }
207
208    pub fn adjust_dynamic_resampling_ratio(&mut self) {
209        if !self.dynamic_resampling_ratio_enabled {
210            return;
211        }
212
213        let audio_queue_len = self.callback_state.lock().unwrap().queue.len();
214        self.dynamic_resampling_rate.adjust(audio_queue_len as u32);
215    }
216
217    pub fn output_frequency(&self) -> u64 {
218        if self.dynamic_resampling_ratio_enabled {
219            self.dynamic_resampling_rate.current_output_frequency().into()
220        } else {
221            self.output_frequency
222        }
223    }
224}
225
226fn open_audio_stream(
227    audio: &AudioSubsystem,
228    config: &CommonConfig,
229    callback_state: Arc<Mutex<AudioCallbackState>>,
230) -> AudioResult<AudioStreamWithCallback<AudioQueueCallback>> {
231    let audio_callback = AudioQueueCallback { state: callback_state };
232
233    let stream = audio
234        .open_playback_stream(
235            &AudioSpec {
236                freq: Some(config.audio_output_frequency as i32),
237                channels: Some(CHANNELS),
238                format: Some(AudioFormat::f32_sys()),
239            },
240            audio_callback,
241        )
242        .map_err(AudioError::OpenStream)?;
243    stream.resume().map_err(AudioError::OpenStream)?;
244
245    Ok(stream)
246}
247
248fn decibels_to_multiplier(decibels: f64) -> f64 {
249    10.0_f64.powf(decibels / 20.0)
250}
251
252impl AudioOutput for SdlAudioOutput {
253    type Err = AudioError;
254
255    #[inline]
256    fn push_sample(&mut self, mut sample_l: f64, mut sample_r: f64) -> Result<(), Self::Err> {
257        self.sample_count += 1;
258        if !self.sample_count.is_multiple_of(self.speed_multiplier) {
259            return Ok(());
260        }
261
262        if self.muted {
263            sample_l = 0.0;
264            sample_r = 0.0;
265        }
266
267        sample_l *= self.audio_gain_multiplier;
268        sample_r *= self.audio_gain_multiplier;
269
270        self.audio_buffer.push((sample_l as f32, sample_r as f32));
271
272        if self.audio_buffer.len() < INTERNAL_AUDIO_BUFFER_LEN {
273            return Ok(());
274        }
275
276        let queue_threshold = self.audio_sync_threshold as usize;
277
278        let mut state_lock = if self.audio_sync {
279            perform_audio_sync(&self.callback_state, queue_threshold)?
280        } else {
281            let state_lock = self.callback_state.lock().unwrap();
282
283            if state_lock.queue.len() > queue_threshold {
284                // Audio queue is full; drop samples
285                log::debug!("Dropping audio samples because buffer is full");
286                self.audio_buffer.clear();
287                return Ok(());
288            }
289
290            state_lock
291        };
292
293        state_lock.queue.extend(&self.audio_buffer);
294        let callback_error = state_lock.error.take();
295
296        drop(state_lock);
297
298        self.audio_buffer.clear();
299
300        match callback_error {
301            None => Ok(()),
302            Some(err) => Err(AudioError::QueueAudio(err)),
303        }
304    }
305}
306
307fn audio_sync_threshold(config: &CommonConfig) -> u32 {
308    if config.audio_dynamic_resampling_ratio {
309        // If dynamic resampling ratio is enabled, let the audio buffer grow to double size
310        // before dropping samples because the audio buffer size is also the target length
311        // for dynamic resampling
312        2 * config.audio_buffer_size
313    } else {
314        config.audio_buffer_size
315    }
316}
317
318fn perform_audio_sync(
319    state: &Arc<Mutex<AudioCallbackState>>,
320    queue_threshold: usize,
321) -> AudioResult<MutexGuard<'_, AudioCallbackState>> {
322    // Block until audio queue is not full
323    loop {
324        {
325            let mut state = state.lock().unwrap();
326
327            if let Some(err) = state.error.take() {
328                return Err(AudioError::QueueAudio(err));
329            }
330
331            if state.queue.len() <= queue_threshold {
332                return Ok(state);
333            }
334        }
335
336        thread::park_timeout(Duration::from_secs(1));
337    }
338}