From b2c2c9353722a522f7a6fe5d5ff62c3e0649d8bd Mon Sep 17 00:00:00 2001 From: lifning <> Date: Sat, 11 Dec 2021 03:10:39 -0800 Subject: [PATCH] experimental dynamic rate control feature in audio component (TBD: merge into SimpleSdl2Audio as an option) --- .../examples/multifunction_emulator.rs | 2 +- .../src/provided/sdl2/audio_ratecontrol.rs | 68 +++++++++++++------ 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/ferretro_components/examples/multifunction_emulator.rs b/ferretro_components/examples/multifunction_emulator.rs index f6282e1..5f19c06 100644 --- a/ferretro_components/examples/multifunction_emulator.rs +++ b/ferretro_components/examples/multifunction_emulator.rs @@ -99,7 +99,7 @@ pub fn main() -> Result<(), Box> { } } - emu.register_component(SimpleSdl2AudioComponent::new(&mut sdl_context)?)?; + emu.register_component(Sdl2RateControlledAudioComponent::new(&mut sdl_context)?)?; emu.register_component(SimpleSdl2KeyboardComponent::new(&mut sdl_context)?)?; emu.register_component(SimpleSdl2GamepadComponent::new(&mut sdl_context))?; diff --git a/ferretro_components/src/provided/sdl2/audio_ratecontrol.rs b/ferretro_components/src/provided/sdl2/audio_ratecontrol.rs index e464355..77b9af6 100644 --- a/ferretro_components/src/provided/sdl2/audio_ratecontrol.rs +++ b/ferretro_components/src/provided/sdl2/audio_ratecontrol.rs @@ -12,13 +12,22 @@ use super::audio::resample; pub struct Sdl2RateControlledAudioComponent { src_freq: f64, + ratio: f64, audio_buffer: Vec, queue: AudioQueue, + avg_headroom: f64, + started: bool, } impl RetroCallbacks for Sdl2RateControlledAudioComponent { + fn video_refresh(&mut self, _frame: &VideoFrame) { + self.started = true; + } + fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize { - self.audio_buffer.extend(stereo_pcm); + if self.started { + self.audio_buffer.extend(stereo_pcm); + } stereo_pcm.len() / 2 } @@ -33,25 +42,47 @@ impl RetroCallbacks for Sdl2RateControlledAudioComponent { } impl RetroComponent for Sdl2RateControlledAudioComponent { - fn post_load_game(&mut self, _retro: &mut LibretroWrapper, _rom: &Path) -> Result<(), Box> { + fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> Result<(), Box> { + self.src_freq = retro.get_system_av_info().timing.sample_rate; self.queue.resume(); + self.started = false; Ok(()) } fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow { - if let Ok(converter) = Self::make_converter(self.src_freq, self.queue.spec()) { - let mut samples = std::mem::take(&mut self.audio_buffer); - samples = resample(&converter, samples); - self.audio_buffer = samples; - self.queue.queue(&self.audio_buffer); - self.audio_buffer.clear(); + if self.src_freq != 0.0 { + if self.started { + let sample_headroom = (self.queue.size() / 4) as f64; + self.avg_headroom = ((self.avg_headroom * 9.0) + sample_headroom) / 10.0; + self.avg_headroom /= 2.0; + let room = self.src_freq / 100.0; + let ratio_raw = ((room - self.avg_headroom) / room).clamp(-1.0, 1.0); + self.ratio = 1.0 + (ratio_raw * 0.01); + } + match Self::make_converter(self.src_freq * self.ratio, self.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; + self.queue.queue(&self.audio_buffer); + self.audio_buffer.clear(); + } + ControlFlow::Continue + } + Err(e) => { + eprintln!("Audio sample rate conversion failed: {:?}", e); + ControlFlow::Break + } + } + } else { + ControlFlow::Continue } - ControlFlow::Continue } } impl Sdl2RateControlledAudioComponent { - pub fn new(sdl_context: &mut Sdl, retro: &LibretroWrapper) -> Result> { + pub fn new(sdl_context: &mut Sdl) -> crate::base::Result { let audio = sdl_context.audio().unwrap(); let desired_spec = AudioSpecDesired { freq: None, @@ -61,31 +92,30 @@ impl Sdl2RateControlledAudioComponent { let queue = AudioQueue::open_queue(&audio, None, &desired_spec)?; - let mut src_freq = retro.get_system_av_info().timing.sample_rate; - // HACK: some cores don't report this 'til we get an env call to set_system_av_info, - // so we can just default to the old libsnes default value. - if src_freq == 0.0 { - src_freq = 32040.5; - } + // default to the old libsnes default value until after load_game or set_system_av_info. + let src_freq = 32040.5; Ok(Sdl2RateControlledAudioComponent { src_freq, + ratio: 1.0, audio_buffer: Default::default(), queue, + avg_headroom: 0.0, + started: false, }) } fn make_converter(src_freq: f64, dest_spec: &AudioSpec) -> Result { - // note on the `* 64`: as long as the ratio between src_rate and dst_rate is right, + // 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 * 64.0).round() as i32, + (src_freq * 16.0).round() as i32, dest_spec.format, dest_spec.channels, - dest_spec.freq * 64, + dest_spec.freq * 16, ) } }