152 lines
5.1 KiB
Rust
152 lines
5.1 KiB
Rust
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<i16>,
|
|
queue: Option<AudioQueue<i16>>,
|
|
must_resample: bool,
|
|
started: bool,
|
|
}
|
|
|
|
/// 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) }
|
|
}
|
|
|
|
pub(crate) fn make_converter(src_freq: f64, dest_spec: &AudioSpec) -> Result<AudioCVT, String> {
|
|
// 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<bool> {
|
|
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<Self> {
|
|
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,
|
|
})
|
|
}
|
|
}
|