ferretro/ferretro_components/src/provided/cpal/mod.rs

75 lines
2.4 KiB
Rust

use crate::prelude::*;
use std::path::Path;
use cpal::{SampleFormat, SampleRate, Stream};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use ringbuf::{Producer, RingBuffer};
use ferretro_base::retro::ffi::SystemTiming;
#[derive(Default)]
pub struct CpalAudioComponent {
stream: Option<Stream>,
writer: Option<Producer<i16>>,
}
impl RetroCallbacks for CpalAudioComponent {
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 (stream, writer) = Self::new_stream_from_timing(&av_info.timing).ok()?;
self.stream = Some(stream);
self.writer = Some(writer);
Some(true)
}
}
impl RetroComponent for CpalAudioComponent {
fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> crate::base::Result<()> {
let timing = retro.get_system_av_info().timing;
let (stream, writer) = Self::new_stream_from_timing(&timing)?;
self.stream = Some(stream);
self.writer = Some(writer);
Ok(())
}
}
impl CpalAudioComponent {
fn new_stream_from_timing(timing: &SystemTiming) -> crate::base::Result<(Stream, Producer<i16>)> {
let samples_per_frame = timing.sample_rate / timing.fps;
let buf_len = samples_per_frame.ceil() as usize * 4;
let host = cpal::default_host();
let device = host.default_output_device().ok_or("cpal: no output device available")?;
let supported_config = device
.supported_output_configs()?
.filter(|cfg| cfg.channels() == 2 && cfg.sample_format() == SampleFormat::I16)
.next()
.ok_or("cpal: no supported stream config for signed 16-bit stereo")?
.with_sample_rate(SampleRate(timing.sample_rate.round() as u32));
let (writer, mut reader) = RingBuffer::new(buf_len).split();
let stream = device.build_output_stream(
&supported_config.config(),
move |data: &mut [i16], _output_callback_info| {
reader.pop_slice(data);
},
move |err| {
eprintln!("cpal: {:?}", err);
}
)?;
stream.play()?;
Ok((stream, writer))
}
}