ferretro/ferretro_components/src/provided/sdl2/audio_thread.rs

110 lines
3.4 KiB
Rust

use crate::prelude::*;
use std::path::Path;
use sdl2::Sdl;
use sdl2::audio::{AudioCallback, AudioDevice, AudioSpecDesired};
use ringbuf::{Consumer, Producer, RingBuffer};
use ferretro_base::retro::ffi::SystemTiming;
pub struct SimpleSdl2AudioThreadComponent {
sdl_audio: sdl2::AudioSubsystem,
device: Option<AudioDevice<MySdlAudio>>,
writer: Option<Producer<i16>>,
}
struct MySdlAudio {
must_resample: bool,
reader: Consumer<i16>,
}
impl AudioCallback for MySdlAudio {
type Channel = i16;
fn callback(&mut self, data: &mut [Self::Channel]) {
if self.must_resample {
todo!("correct software resampling")
}
let buf_len = data.len();
let mut filled = self.reader.pop_slice(data);
if filled == 0 {
data.fill(0);
} else {
while filled < buf_len {
let remaining = buf_len - filled;
if remaining < filled {
data.copy_within(..remaining, filled);
} else {
data.copy_within(..filled, filled);
}
filled *= 2;
}
}
}
}
impl RetroCallbacks for SimpleSdl2AudioThreadComponent {
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize {
if let Some(writer) = &mut self.writer {
writer.push_slice(stereo_pcm);
}
stereo_pcm.len() / 2
}
fn set_system_av_info(&mut self, av_info: &SystemAvInfo) -> Option<bool> {
let timing = &av_info.timing;
let (device, writer) = Self::new_device_from_timing(&self.sdl_audio, timing).ok()?;
self.device = Some(device);
self.writer = Some(writer);
Some(true)
}
}
impl RetroComponent for SimpleSdl2AudioThreadComponent {
fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> crate::base::Result<()> {
let timing = &retro.get_system_av_info().timing;
let (device, writer) = Self::new_device_from_timing(&self.sdl_audio, timing)?;
self.device = Some(device);
self.writer = Some(writer);
Ok(())
}
}
impl SimpleSdl2AudioThreadComponent {
pub fn new(sdl_context: &mut Sdl) -> crate::base::Result<Self> {
Ok(SimpleSdl2AudioThreadComponent {
sdl_audio: sdl_context.audio()?,
device: None,
writer: None,
})
}
fn new_device_from_timing(audio: &sdl2::AudioSubsystem, timing: &SystemTiming) -> crate::base::Result<(AudioDevice<MySdlAudio>, Producer<i16>)> {
const CHANNELS: usize = 2;
const BUF_FRAMES: usize = 4;
let samples_per_frame = timing.sample_rate / timing.fps;
let buf_len = samples_per_frame.ceil() as usize * CHANNELS * BUF_FRAMES * 2;
let (writer, reader) = RingBuffer::new(buf_len).split();
let desired_spec = AudioSpecDesired {
freq: Some(timing.sample_rate.round() as i32),
channels: Some(CHANNELS as u8),
samples: Some(samples_per_frame.ceil() as u16 * BUF_FRAMES as u16),
};
let device = audio.open_playback(
None,
&desired_spec,
move |spec| {
let must_resample = { spec.freq != desired_spec.freq.unwrap() };
MySdlAudio {
must_resample, // todo: include freqs from & to
reader,
}
})?;
device.resume();
Ok((device, writer))
}
}