From a1e28e98381e4dd9cc92608f094e933344dbb126 Mon Sep 17 00:00:00 2001 From: lifning <> Date: Tue, 2 Nov 2021 20:24:36 -0700 Subject: [PATCH] WIP figuring out DRC audio component impl --- .../src/provided/sdl2/audio_ratecontrol.rs | 168 ++++++++++++++++++ ferretro_components/src/provided/sdl2/fps.rs | 2 - ferretro_components/src/provided/sdl2/mod.rs | 1 + 3 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 ferretro_components/src/provided/sdl2/audio_ratecontrol.rs diff --git a/ferretro_components/src/provided/sdl2/audio_ratecontrol.rs b/ferretro_components/src/provided/sdl2/audio_ratecontrol.rs new file mode 100644 index 0000000..2b604d1 --- /dev/null +++ b/ferretro_components/src/provided/sdl2/audio_ratecontrol.rs @@ -0,0 +1,168 @@ +use crate::prelude::*; + +use std::error::Error; +use std::mem::size_of; +use std::path::Path; +use core::time::Duration; + +use sdl2::Sdl; +use sdl2::audio::{AudioCallback, AudioDevice, AudioFormat, AudioSpec, AudioSpecDesired, AudioCVT, AudioFormatNum}; + +/// Trivially sends the core's audio data to the SDL audio subsystem for playback. +pub struct Sdl2RateControlledAudioComponent { + sample_rate: f64, + audio_buffer: Vec, + dest_spec: AudioSpec, + audio_device: AudioDevice<>, + cvt_send: crossbeam_channel::Sender, + send: crossbeam_channel::Sender>, +} + +impl RetroCallbacks for Sdl2RateControlledAudioComponent { + fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize { + self.audio_buffer.extend(stereo_pcm); + self.send_audio_samples(); + stereo_pcm.len() / 2 + } + + fn set_system_av_info(&mut self, av_info: &SystemAvInfo) -> Option { + match Self::make_converter(av_info.timing.sample_rate, &self.dest_spec) { + Ok(converter) => { + self.cvt_send.send(converter); + self.sample_rate = av_info.timing.sample_rate; + Some(true) + } + Err(_) => Some(false), + } + } +} + +impl RetroComponent for Sdl2RateControlledAudioComponent { + fn post_load_game(&mut self, _retro: &mut LibretroWrapper, _rom: &Path) -> Result<(), Box> { + self.audio_device.resume(); + Ok(()) + } +} + +impl Sdl2RateControlledAudioComponent { + pub fn new(sdl_context: &mut Sdl, retro: &LibretroWrapper) -> Result> { + 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... + // which is too late for this constructor to pass along to SDL, so... + // less than ideal, but default to the old libsnes default value i guess. + if src_freq == 0.0 { + src_freq = 32040.5; + } + + let (send, recv) = crossbeam_channel::bounded(4); + let (cvt_send, cvt_recv) = crossbeam_channel::bounded(4); + + let audio = sdl_context.audio().unwrap(); + let desired_spec = AudioSpecDesired { + freq: None, + channels: Some(2), + samples: None, + }; + let mut dest_spec_opt = None; + let audio_device = audio + .open_playback(None, &desired_spec, |dest_spec| { + dest_spec_opt = Some(dest_spec.clone()); + let converter = Self::make_converter(src_freq, &dest_spec).unwrap(); + let callback: Box = match dest_spec.format { + AudioFormat::U8 => Box::new( + MySdlAudio:: { converter, dest_spec, cvt_recv, recv, } + ), + AudioFormat::S8 => Box::new( + MySdlAudio:: { converter, dest_spec, cvt_recv, recv, } + ), + AudioFormat::U16LSB | AudioFormat::U16MSB => Box::new( + MySdlAudio:: { converter, dest_spec, cvt_recv, recv, } + ), + AudioFormat::S16LSB | AudioFormat::S16MSB => Box::new( + MySdlAudio:: { converter, dest_spec, cvt_recv, recv, } + ), + AudioFormat::S32LSB | AudioFormat::S32MSB => Box::new( + MySdlAudio:: { converter, dest_spec, cvt_recv, recv, } + ), + AudioFormat::F32LSB | AudioFormat::F32MSB => Box::new( + MySdlAudio:: { converter, dest_spec, cvt_recv, recv, } + ), + }; + callback + }) + .unwrap(); + + Ok(Sdl2RateControlledAudioComponent { + sample_rate: src_freq, + audio_buffer: Default::default(), + dest_spec: dest_spec_opt.unwrap(), + audio_device, + cvt_send, + send, + }) + } + + fn make_converter(src_freq: f64, dest_spec: &AudioSpec) -> Result { + AudioCVT::new( + AudioFormat::S16LSB, + 2, + (src_freq * 1000).round() as i32, + dest_spec.format, + dest_spec.channels, + dest_spec.freq * 1000, + ) + } + + pub fn silence_buffer(&self) { + let _ = self.send.try_send(vec![0; self.dest_spec.samples as usize * self.dest_spec.channels as usize]); + } + + fn send_audio_samples(&mut self) { + let stereo_samples = self.dest_spec.samples as usize * 2; + while self.audio_buffer.len() >= stereo_samples { + let remainder = self.audio_buffer.split_off(stereo_samples); + let msg = std::mem::replace(&mut self.audio_buffer, remainder); + let _ = self.send.try_send(msg); + } + } +} + +struct MySdlAudio { + converter: AudioCVT, + dest_spec: AudioSpec, + cvt_recv: crossbeam_channel::Receiver, + recv: crossbeam_channel::Receiver>, +} + +impl MySdlAudio { + const BYTE_SIZE: usize = size_of::(); +} + +impl AudioCallback for MySdlAudio { + type Channel = C; + + fn callback(&mut self, out: &mut [Self::Channel]) { + if let Ok(cvt) = self.cvt_recv.try_recv() { + self.converter = cvt; + } + if let Ok(mut samples) = self.recv.recv_timeout(Duration::from_millis(500)) { + + let ptr = samples.as_mut_ptr() as *mut u8; + let length = samples.len() * Self::BYTE_SIZE; + let capacity = samples.capacity() * Self::BYTE_SIZE; + std::mem::forget(samples); + let mut samples_u8 = unsafe { Vec::from_raw_parts(ptr, length, capacity) }; + + let mut resampled_u8 = self.converter.convert(samples_u8); + + let ptr = resampled_u8.as_mut_ptr() as *mut Self::Channel; + let length = (resampled_u8.len() + (Self::BYTE_SIZE - 1)) / Self::BYTE_SIZE; + let capacity = (resampled_u8.capacity() + (Self::BYTE_SIZE - 1)) / Self::BYTE_SIZE; + std::mem::forget(resampled_u8); + let resampled = unsafe { Vec::from_raw_parts(ptr, length, capacity) }; + + out.copy_from_slice(&resampled[..out.len()]); + } + } +} diff --git a/ferretro_components/src/provided/sdl2/fps.rs b/ferretro_components/src/provided/sdl2/fps.rs index bc1fd84..ff5f91a 100644 --- a/ferretro_components/src/provided/sdl2/fps.rs +++ b/ferretro_components/src/provided/sdl2/fps.rs @@ -1,8 +1,6 @@ use crate::base::ControlFlow; use crate::prelude::*; -use std::os::raw::c_uint; - use sdl2::gfx::framerate::FPSManager; pub struct SimpleSdl2FramerateLimitComponent { diff --git a/ferretro_components/src/provided/sdl2/mod.rs b/ferretro_components/src/provided/sdl2/mod.rs index e724a2c..8c91708 100644 --- a/ferretro_components/src/provided/sdl2/mod.rs +++ b/ferretro_components/src/provided/sdl2/mod.rs @@ -2,6 +2,7 @@ //! with SDL2. mod audio; +mod audio_ratecontrol; mod canvas; mod fps; mod gamepad;