use crate::prelude::*; use std::mem::size_of; use std::path::Path; use sdl2::Sdl; use sdl2::audio::{AudioCVT, AudioFormat, AudioFormatNum, AudioQueue, AudioSpec, AudioSpecDesired}; use crate::base::ControlFlow; pub struct SimpleSdl2AudioQueueComponent { sdl_audio: sdl2::AudioSubsystem, src_freq: f64, audio_buffer: Vec, queue: Option>, must_resample: bool, started: bool, } /// Work around an idiosyncrasy of the sdl2 crate's AudioCVT API. /// /// It only deals in Vec, but from where I'm sitting it should really be abstracting away the /// "u8" part if the rest of the API deals in the appropriate sample type. pub(crate) fn resample(converter: &AudioCVT, mut samples: Vec) -> Vec { let sample_size = size_of::(); let ptr = samples.as_mut_ptr() as *mut u8; let length = samples.len() * sample_size; let capacity = samples.capacity() * sample_size; std::mem::forget(samples); let samples_u8 = unsafe { Vec::from_raw_parts(ptr, length, capacity) }; let mut resampled_u8 = converter.convert(samples_u8); let ptr = resampled_u8.as_mut_ptr() as *mut C; let length = resampled_u8.len() / sample_size; let capacity = resampled_u8.capacity() / sample_size; std::mem::forget(resampled_u8); unsafe { Vec::from_raw_parts(ptr, length, capacity) } } pub(crate) fn make_converter(src_freq: f64, dest_spec: &AudioSpec) -> Result { // note on the `* 16`: as long as the ratio between src_rate and dst_rate is right, // we should be in the clear -- this is to make up for SDL not giving us floats for // this, we can at least get some quasi-fixed-point precision going on... AudioCVT::new( AudioFormat::s16_sys(), 2, (src_freq * 16.0).round() as i32, dest_spec.format, dest_spec.channels, dest_spec.freq * 16, ) } impl RetroCallbacks for SimpleSdl2AudioQueueComponent { fn video_refresh(&mut self, _frame: &VideoFrame) { self.started = true; } fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize { if self.started { self.audio_buffer.extend(stereo_pcm); } stereo_pcm.len() / 2 } fn set_system_av_info(&mut self, av_info: &SystemAvInfo) -> Option { if let Some(queue) = &self.queue { if make_converter(av_info.timing.sample_rate, queue.spec()).is_ok() { self.src_freq = av_info.timing.sample_rate; Some(true) } else { Some(false) } } else { Some(false) } } } impl RetroComponent for SimpleSdl2AudioQueueComponent { fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> crate::base::Result<()> { let timing = retro.get_system_av_info().timing; self.src_freq = timing.sample_rate; let samples_per_frame = timing.sample_rate / timing.fps; let desired_spec = AudioSpecDesired { freq: Some(self.src_freq.round() as i32), channels: Some(2), samples: Some((2.0 * samples_per_frame).ceil() as u16), }; let queue = self.sdl_audio.open_queue(None, &desired_spec)?; if queue.spec().freq != desired_spec.freq.unwrap() { self.must_resample = true; eprintln!("warning: using naive resampling"); } queue.resume(); self.queue = Some(queue); self.started = false; Ok(()) } fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow { if self.src_freq != 0.0 { let queue = self.queue.as_mut().unwrap(); if self.must_resample { match make_converter(self.src_freq, queue.spec()) { Ok(converter) => { if !self.audio_buffer.is_empty() { let mut samples = std::mem::take(&mut self.audio_buffer); samples = resample(&converter, samples); self.audio_buffer = samples; queue.queue(&self.audio_buffer); self.audio_buffer.clear(); } ControlFlow::Continue } Err(e) => { eprintln!("Audio sample rate conversion failed: {:?}", e); ControlFlow::Break } } } else { queue.queue(self.audio_buffer.as_slice()); self.audio_buffer.clear(); ControlFlow::Continue } } else { ControlFlow::Continue } } } impl SimpleSdl2AudioQueueComponent { pub fn new(sdl_context: &mut Sdl) -> crate::base::Result { let sdl_audio = sdl_context.audio()?; Ok(SimpleSdl2AudioQueueComponent { sdl_audio, src_freq: 32040.5, // nod to the old libsnes default til load_game or set_system_av_info audio_buffer: Default::default(), queue: None, must_resample: false, started: false, }) } }