replace SimpleSdl2AudioComponent with the AudioCVT-based resampler
This commit is contained in:
		
							parent
							
								
									f60b2c579c
								
							
						
					
					
						commit
						a42aa936fe
					
				
					 5 changed files with 78 additions and 96 deletions
				
			
		| 
						 | 
				
			
			@ -12,7 +12,7 @@ ferretro_base = { path = "../ferretro_base"}
 | 
			
		|||
libloading = "0.5"
 | 
			
		||||
num_enum = "0.4"
 | 
			
		||||
ffmpeg-next = { version = "4.3.8", optional = true }
 | 
			
		||||
sdl2 = { version = "0.32", optional = true }
 | 
			
		||||
sdl2 = { version = "0.32", optional = true, features = ["gfx"] }
 | 
			
		||||
gl = { version = "0.14", optional = true }
 | 
			
		||||
crossbeam-channel = { version = "0.4", optional = true }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,7 +55,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		|||
    let sdl2_ogl = SimpleSdl2OpenglComponent::new(&mut sdl_context, emu.libretro_core())?;
 | 
			
		||||
    emu.register_component(sdl2_ogl)?;
 | 
			
		||||
 | 
			
		||||
    let sdl2_audio = SimpleSdl2AudioComponent::new(&mut sdl_context, emu.libretro_core());
 | 
			
		||||
    let sdl2_audio = SimpleSdl2AudioComponent::new(&mut sdl_context, emu.libretro_core())?;
 | 
			
		||||
    emu.register_component(sdl2_audio)?;
 | 
			
		||||
 | 
			
		||||
    emu.register_component(SimpleSdl2GamepadComponent::new(&mut sdl_context))?;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,111 +1,112 @@
 | 
			
		|||
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};
 | 
			
		||||
use sdl2::audio::{AudioCVT, AudioFormat, AudioFormatNum, AudioQueue, AudioSpec, AudioSpecDesired};
 | 
			
		||||
 | 
			
		||||
struct MySdlAudio {
 | 
			
		||||
    audio_spec: AudioSpec,
 | 
			
		||||
    audio_receiver: crossbeam_channel::Receiver<Vec<i16>>,
 | 
			
		||||
}
 | 
			
		||||
use crate::base::ControlFlow;
 | 
			
		||||
 | 
			
		||||
impl AudioCallback for MySdlAudio {
 | 
			
		||||
    type Channel = i16;
 | 
			
		||||
 | 
			
		||||
    fn callback(&mut self, out: &mut [Self::Channel]) {
 | 
			
		||||
        if self.audio_spec.format == AudioFormat::S16LSB {
 | 
			
		||||
            if let Ok(samples) = self.audio_receiver.recv_timeout(Duration::from_millis(500)) {
 | 
			
		||||
                out.copy_from_slice(&samples[..out.len()]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Trivially sends the core's audio data to the SDL audio subsystem for playback.
 | 
			
		||||
pub struct SimpleSdl2AudioComponent {
 | 
			
		||||
    sample_rate: f64,
 | 
			
		||||
    src_freq: f64,
 | 
			
		||||
    audio_buffer: Vec<i16>,
 | 
			
		||||
    audio_spec: AudioSpec,
 | 
			
		||||
    audio_device: AudioDevice<MySdlAudio>,
 | 
			
		||||
    audio_sender: crossbeam_channel::Sender<Vec<i16>>,
 | 
			
		||||
    queue: AudioQueue<i16>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Work around an idiosyncrasy of the sdl2 crate's AudioCVT API.
 | 
			
		||||
///
 | 
			
		||||
/// It only deals in Vec<u8>, 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<C: AudioFormatNum>(converter: &AudioCVT, mut samples: Vec<C>) -> Vec<C> {
 | 
			
		||||
    let sample_size = size_of::<C>();
 | 
			
		||||
 | 
			
		||||
    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) }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RetroCallbacks for SimpleSdl2AudioComponent {
 | 
			
		||||
    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<bool> {
 | 
			
		||||
        self.sample_rate = av_info.timing.sample_rate;
 | 
			
		||||
        // TODO: change/reinitialize
 | 
			
		||||
        if Self::make_converter(av_info.timing.sample_rate, self.queue.spec()).is_ok() {
 | 
			
		||||
            self.src_freq = av_info.timing.sample_rate;
 | 
			
		||||
            Some(true)
 | 
			
		||||
        } else {
 | 
			
		||||
            Some(false)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RetroComponent for SimpleSdl2AudioComponent {
 | 
			
		||||
    fn post_load_game(&mut self, _retro: &mut LibretroWrapper, _rom: &Path) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        self.audio_device.resume();
 | 
			
		||||
        self.queue.resume();
 | 
			
		||||
        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();
 | 
			
		||||
        }
 | 
			
		||||
        ControlFlow::Continue
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SimpleSdl2AudioComponent {
 | 
			
		||||
    pub fn new(sdl_context: &mut Sdl, retro: &LibretroWrapper) -> Self {
 | 
			
		||||
        let mut sample_rate = 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 sample_rate == 0.0 {
 | 
			
		||||
            sample_rate = 32040.0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let (audio_sender, audio_receiver) = crossbeam_channel::bounded(2);
 | 
			
		||||
 | 
			
		||||
    pub fn new(sdl_context: &mut Sdl, retro: &LibretroWrapper) -> Result<Self, Box<dyn std::error::Error>> {
 | 
			
		||||
        let audio = sdl_context.audio().unwrap();
 | 
			
		||||
        let desired_spec = AudioSpecDesired {
 | 
			
		||||
            freq: Some(sample_rate.round() as i32),
 | 
			
		||||
            freq: None,
 | 
			
		||||
            channels: Some(2),
 | 
			
		||||
            samples: None,
 | 
			
		||||
        };
 | 
			
		||||
        let mut audio_spec = None;
 | 
			
		||||
        let audio_device = audio
 | 
			
		||||
            .open_playback(None, &desired_spec, |spec| {
 | 
			
		||||
                if spec.format != AudioFormat::s16_sys() {
 | 
			
		||||
                    panic!("unsupported audio format {:?}", spec.format);
 | 
			
		||||
                }
 | 
			
		||||
                audio_spec = Some(spec.clone());
 | 
			
		||||
                MySdlAudio {
 | 
			
		||||
                    audio_spec: spec,
 | 
			
		||||
                    audio_receiver,
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        SimpleSdl2AudioComponent {
 | 
			
		||||
            sample_rate,
 | 
			
		||||
        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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(SimpleSdl2AudioComponent {
 | 
			
		||||
            src_freq,
 | 
			
		||||
            audio_buffer: Default::default(),
 | 
			
		||||
            audio_spec: audio_spec.unwrap(),
 | 
			
		||||
            audio_device,
 | 
			
		||||
            audio_sender,
 | 
			
		||||
        }
 | 
			
		||||
            queue,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn silence_buffer(&self) {
 | 
			
		||||
        let _ = self.audio_sender.try_send(vec![0; self.audio_spec.samples as usize * self.audio_spec.channels as usize]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn send_audio_samples(&mut self) {
 | 
			
		||||
        let stereo_samples = self.audio_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.audio_sender.try_send(msg);
 | 
			
		||||
        }
 | 
			
		||||
    fn make_converter(src_freq: f64, dest_spec: &AudioSpec) -> Result<AudioCVT, String> {
 | 
			
		||||
        // note on the `* 64`: 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,
 | 
			
		||||
            dest_spec.format,
 | 
			
		||||
            dest_spec.channels,
 | 
			
		||||
            dest_spec.freq * 64,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,39 +1,21 @@
 | 
			
		|||
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::{AudioCVT, AudioFormat, AudioFormatNum, AudioQueue, AudioSpec, AudioSpecDesired};
 | 
			
		||||
use sdl2::audio::{AudioCVT, AudioFormat, AudioQueue, AudioSpec, AudioSpecDesired};
 | 
			
		||||
 | 
			
		||||
use crate::base::ControlFlow;
 | 
			
		||||
 | 
			
		||||
use super::audio::resample;
 | 
			
		||||
 | 
			
		||||
pub struct Sdl2RateControlledAudioComponent {
 | 
			
		||||
    src_freq: f64,
 | 
			
		||||
    audio_buffer: Vec<i16>,
 | 
			
		||||
    queue: AudioQueue<i16>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn resample<C: AudioFormatNum>(converter: &AudioCVT, mut samples: Vec<C>) -> Vec<C> {
 | 
			
		||||
    let sample_size = size_of::<C>();
 | 
			
		||||
 | 
			
		||||
    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 - 1)) / sample_size;
 | 
			
		||||
    let capacity = (resampled_u8.capacity() + (sample_size - 1)) / sample_size;
 | 
			
		||||
    std::mem::forget(resampled_u8);
 | 
			
		||||
    unsafe { Vec::from_raw_parts(ptr, length, capacity) }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RetroCallbacks for Sdl2RateControlledAudioComponent {
 | 
			
		||||
    fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize {
 | 
			
		||||
        self.audio_buffer.extend(stereo_pcm);
 | 
			
		||||
| 
						 | 
				
			
			@ -98,7 +80,7 @@ impl Sdl2RateControlledAudioComponent {
 | 
			
		|||
        // 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::S16LSB,
 | 
			
		||||
            AudioFormat::s16_sys(),
 | 
			
		||||
            2,
 | 
			
		||||
            (src_freq * 64.0).round() as i32,
 | 
			
		||||
            dest_spec.format,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -152,7 +152,6 @@ impl RetroCallbacks for SimpleSdl2OpenglComponent {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_geometry(&mut self, geom: &GameGeometry) -> Option<bool> {
 | 
			
		||||
        eprintln!("set_geometry({:?})", geom);
 | 
			
		||||
        let _ = self.canvas.window_mut().set_size(geom.base_width, geom.base_height).ok()?;
 | 
			
		||||
        let _ = self.canvas.set_logical_size(geom.base_width, geom.base_height).ok()?;
 | 
			
		||||
        self.window_fbo = self.current_framebuffer_binding();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue