75 lines
2.4 KiB
Rust
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))
|
|
}
|
|
}
|