sdl2: Use callbacks with ring buffer instead of audio queue.

Better latencies, and also fast-forward (aka Turbo mode) sounds normal
this way.


Former-commit-id: d1075087e847c765a871157a7973c897575ef4d7
This commit is contained in:
Michel Heily 2019-12-28 15:59:35 +02:00
parent cb54f4d1a3
commit d8545dd8cd
9 changed files with 137 additions and 62 deletions

18
Cargo.lock generated
View file

@ -212,6 +212,15 @@ dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "debug_stub_derive"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "2.0.2" version = "2.0.2"
@ -669,6 +678,11 @@ name = "rgb"
version = "0.8.13" version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ringbuf"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "rustboyadvance-ng" name = "rustboyadvance-ng"
version = "0.1.0" version = "0.1.0"
@ -682,12 +696,14 @@ dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hexdump 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "hexdump 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"minifb 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "minifb 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"ringbuf 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rustyline 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustyline 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sdl2 0.32.2 (registry+https://github.com/rust-lang/crates.io-index)", "sdl2 0.32.2 (registry+https://github.com/rust-lang/crates.io-index)",
"spin_sleep 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "spin_sleep 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1007,6 +1023,7 @@ dependencies = [
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
"checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f" "checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f"
"checksum debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "496b7f8a2f853313c3ca370641d7ff3e42c32974fdccda8f0684599ed0a3ff6b"
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" "checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
"checksum enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b90e520ec62c1864c8c78d637acbfe8baf5f63240f2fb8165b8325c07812dd" "checksum enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b90e520ec62c1864c8c78d637acbfe8baf5f63240f2fb8165b8325c07812dd"
@ -1060,6 +1077,7 @@ dependencies = [
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828" "checksum redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828"
"checksum rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "4f089652ca87f5a82a62935ec6172a534066c7b97be003cc8f702ee9a7a59c92" "checksum rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "4f089652ca87f5a82a62935ec6172a534066c7b97be003cc8f702ee9a7a59c92"
"checksum ringbuf 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7c2b29d87cfbdce39849012bb5020fff88b8f01f4f5b55846a0b6ef360774eae"
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" "checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum rustyline 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67e12e40e0240de07f0dab4f4dd01bdb15d74dc977026d4ba91666c41c679ade" "checksum rustyline 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67e12e40e0240de07f0dab4f4dd01bdb15d74dc977026d4ba91666c41c679ade"

View file

@ -26,6 +26,8 @@ zip = "0.5.3"
ctrlc = "3.1.3" ctrlc = "3.1.3"
spin_sleep="0.3.7" spin_sleep="0.3.7"
bit-set = "0.5.1" bit-set = "0.5.1"
ringbuf = "0.2.1"
debug_stub_derive = "0.3.0"
[[bin]] [[bin]]
name = "rba-sdl2" name = "rba-sdl2"

View file

@ -467,12 +467,7 @@ impl Gpu {
} }
// Returns the new gpu state // Returns the new gpu state
pub fn step( pub fn step(&mut self, cycles: usize, sb: &mut SysBus, irqs: &mut IrqBitmask) {
&mut self,
cycles: usize,
sb: &mut SysBus,
irqs: &mut IrqBitmask,
) {
self.cycles += cycles; self.cycles += cycles;
match self.state { match self.state {

View file

@ -1,37 +1,34 @@
pub type Sample = (i16, i16); use crate::{AudioInterface, StereoSample};
const PI: f32 = std::f32::consts::PI; const PI: f32 = std::f32::consts::PI;
pub trait Resampler { pub trait Resampler {
fn push_sample(&mut self, s: Sample, output: &mut Vec<i16>); fn push_sample(&mut self, s: StereoSample, audio: &mut dyn AudioInterface);
} }
#[derive(Debug)]
pub struct CosineResampler { pub struct CosineResampler {
last_in_sample: Sample, last_in_sample: StereoSample,
phase: f32, phase: f32,
pub in_freq: f32, pub in_freq: f32,
out_freq: f32, out_freq: f32,
} }
fn cosine_interpolation(y1: Sample, y2: Sample, phase: f32) -> Sample { fn cosine_interpolation(y1: i16, y2: i16, phase: f32) -> i16 {
let y1_left = y1.0 as f32; let y1 = y1 as i32 as f32;
let y1_right = y1.1 as f32; let y2 = y2 as i32 as f32;
let y2_left = y2.0 as f32;
let y2_right = y2.1 as f32;
let mu2 = (1.0 - (PI * phase).cos()) / 2.0; let mu2 = (1.0 - (PI * phase).cos()) / 2.0;
( (y2 * (1.0 - mu2) + y1 * mu2) as i16
(y2_left * (1.0 - mu2) + y1_left * mu2) as i16,
(y2_right * (1.0 - mu2) + y1_right * mu2) as i16,
)
} }
impl Resampler for CosineResampler { impl Resampler for CosineResampler {
fn push_sample(&mut self, s: Sample, output: &mut Vec<i16>) { fn push_sample(&mut self, s: StereoSample, audio: &mut dyn AudioInterface) {
while self.phase < 1.0 { while self.phase < 1.0 {
let x = cosine_interpolation(self.last_in_sample, s, self.phase); let left = cosine_interpolation(self.last_in_sample.0, s.0, self.phase);
output.push(x.0); let right = cosine_interpolation(self.last_in_sample.1, s.1, self.phase);
output.push(x.1); audio.push_sample((left, right));
self.phase += self.in_freq / self.out_freq; self.phase += self.in_freq / self.out_freq;
} }
self.phase = self.phase - 1.0; self.phase = self.phase - 1.0;

View file

@ -1,5 +1,4 @@
// TODO write tests or replace with a crate // TODO write tests or replace with a crate
const SOUND_FIFO_CAPACITY: usize = 32; const SOUND_FIFO_CAPACITY: usize = 32;
#[derive(Debug)] #[derive(Debug)]

View file

@ -31,10 +31,20 @@ struct DmaSoundChannel {
fifo: SoundFifo, fifo: SoundFifo,
} }
impl DmaSoundChannel {
fn is_stereo_channel_enabled(&self, channel: usize) -> bool {
match channel {
0 => self.enable_left,
1 => self.enable_right,
_ => unreachable!(),
}
}
}
impl Default for DmaSoundChannel { impl Default for DmaSoundChannel {
fn default() -> DmaSoundChannel { fn default() -> DmaSoundChannel {
DmaSoundChannel { DmaSoundChannel {
volume_shift: 1, volume_shift: 0,
value: 0, value: 0,
enable_right: false, enable_right: false,
enable_left: false, enable_left: false,
@ -50,8 +60,12 @@ const REG_FIFO_A_H: u32 = REG_FIFO_A + 2;
const REG_FIFO_B_L: u32 = REG_FIFO_B; const REG_FIFO_B_L: u32 = REG_FIFO_B;
const REG_FIFO_B_H: u32 = REG_FIFO_B + 2; const REG_FIFO_B_H: u32 = REG_FIFO_B + 2;
type AudioDeviceRcRefCell = Rc<RefCell<dyn AudioInterface>>;
#[derive(DebugStub)]
pub struct SoundController { pub struct SoundController {
audio_device: Rc<RefCell<dyn AudioInterface>>, #[debug_stub = "AudioDeviceRcRefCell"]
audio_device: AudioDeviceRcRefCell,
sample_rate_to_cpu_freq: usize, // how many "cycles" are a sample? sample_rate_to_cpu_freq: usize, // how many "cycles" are a sample?
last_sample_cycles: usize, // cycles count when we last provided a new sample. last_sample_cycles: usize, // cycles count when we last provided a new sample.
@ -90,7 +104,7 @@ pub struct SoundController {
} }
impl SoundController { impl SoundController {
pub fn new(audio_device: Rc<RefCell<dyn AudioInterface>>) -> SoundController { pub fn new(audio_device: AudioDeviceRcRefCell) -> SoundController {
let resampler = let resampler =
CosineResampler::new(32768_f32, audio_device.borrow().get_sample_rate() as f32); CosineResampler::new(32768_f32, audio_device.borrow().get_sample_rate() as f32);
SoundController { SoundController {
@ -250,13 +264,13 @@ impl SoundController {
} }
REG_FIFO_A_L | REG_FIFO_A_H => { REG_FIFO_A_L | REG_FIFO_A_H => {
self.dma_sound[0].fifo.write(((value >> 8) & 0xff) as i8);
self.dma_sound[0].fifo.write((value & 0xff) as i8); self.dma_sound[0].fifo.write((value & 0xff) as i8);
self.dma_sound[0].fifo.write(((value >> 8) & 0xff) as i8);
} }
REG_FIFO_B_L | REG_FIFO_B_H => { REG_FIFO_B_L | REG_FIFO_B_H => {
self.dma_sound[1].fifo.write(((value >> 8) & 0xff) as i8);
self.dma_sound[1].fifo.write((value & 0xff) as i8); self.dma_sound[1].fifo.write((value & 0xff) as i8);
self.dma_sound[1].fifo.write(((value >> 8) & 0xff) as i8);
} }
REG_SOUNDBIAS => self.sound_bias = value & 0xc3fe, REG_SOUNDBIAS => self.sound_bias = value & 0xc3fe,
@ -283,11 +297,11 @@ impl SoundController {
const FIFO_INDEX_TO_REG: [u32; 2] = [REG_FIFO_A, REG_FIFO_B]; const FIFO_INDEX_TO_REG: [u32; 2] = [REG_FIFO_A, REG_FIFO_B];
for fifo in 0..2 { for fifo in 0..2 {
let channel = &mut self.dma_sound[fifo]; let dma = &mut self.dma_sound[fifo];
if timer_id == channel.timer_select { if timer_id == dma.timer_select {
channel.value = channel.fifo.read(); dma.value = dma.fifo.read();
if channel.fifo.count() <= 16 { if dma.fifo.count() <= 16 {
dmac.notify_sound_fifo(FIFO_INDEX_TO_REG[fifo]); dmac.notify_sound_fifo(FIFO_INDEX_TO_REG[fifo]);
} }
} }
@ -310,22 +324,19 @@ impl SoundController {
// time to push a new sample! // time to push a new sample!
let mut sample = (0i16, 0i16); let mut sample = [0; 2];
for i in 0..2 {
let channel = &self.dma_sound[i]; for channel in 0..=1 {
if channel.enable_left { for dma in &mut self.dma_sound {
sample.0 += ((channel.value as i16) << 8) >> channel.volume_shift; if dma.is_stereo_channel_enabled(channel) {
sample[channel] += dma.value as i16;
} }
if channel.enable_right {
sample.1 += ((channel.value as i16) << 8) >> channel.volume_shift;
} }
} }
self.resampler.push_sample(sample, &mut self.output_buffer); let mut audio = self.audio_device.borrow_mut();
if self.output_buffer.len() >= 10000 { self.resampler
self.audio_device.borrow_mut().play(&self.output_buffer); .push_sample((sample[0], sample[1]), &mut *audio);
self.output_buffer.clear();
}
} }
} }
} }

View file

@ -2,6 +2,9 @@
#![feature(core_intrinsics)] #![feature(core_intrinsics)]
#![feature(exclusive_range_pattern)] #![feature(exclusive_range_pattern)]
#[macro_use]
extern crate debug_stub_derive;
#[macro_use] #[macro_use]
extern crate enum_primitive_derive; extern crate enum_primitive_derive;
extern crate num; extern crate num;
@ -34,11 +37,13 @@ pub trait VideoInterface {
fn render(&mut self, buffer: &[u32]); fn render(&mut self, buffer: &[u32]);
} }
pub type StereoSample = (i16, i16);
pub trait AudioInterface { pub trait AudioInterface {
fn get_sample_rate(&self) -> i32; fn get_sample_rate(&self) -> i32;
#[allow(unused_variables)] #[allow(unused_variables)]
fn play(&mut self, samples: &[i16]) {} fn push_sample(&mut self, samples: StereoSample) {}
} }
pub trait InputInterface { pub trait InputInterface {

View file

@ -1,40 +1,89 @@
use sdl2; use sdl2;
use sdl2::audio::{AudioQueue, AudioSpecDesired}; use sdl2::audio::{AudioCallback, AudioDevice, AudioSpec, AudioSpecDesired};
use rustboyadvance_ng::AudioInterface; use rustboyadvance_ng::{AudioInterface, StereoSample};
extern crate ringbuf;
use ringbuf::{Consumer, Producer, RingBuffer};
struct GbaAudioCallback {
consumer: Consumer<StereoSample>,
spec: AudioSpec,
}
pub struct Sdl2AudioPlayer { pub struct Sdl2AudioPlayer {
pub device: AudioQueue<i16>, _device: AudioDevice<GbaAudioCallback>,
producer: Producer<StereoSample>,
freq: i32, freq: i32,
} }
impl AudioCallback for GbaAudioCallback {
type Channel = i16;
fn callback(&mut self, out_samples: &mut [i16]) {
let sample_count = out_samples.len() / 2;
for i in 0..sample_count {
if let Some((left, right)) = self.consumer.pop() {
out_samples[2 * i] = left * (1 << 4);
out_samples[2 * i + 1] = right * (1 << 4);
} else {
out_samples[2 * i] = self.spec.silence as i16;
out_samples[2 * i + 1] = self.spec.silence as i16;
}
}
}
}
impl AudioInterface for Sdl2AudioPlayer { impl AudioInterface for Sdl2AudioPlayer {
fn get_sample_rate(&self) -> i32 { fn get_sample_rate(&self) -> i32 {
self.freq self.freq
} }
fn play(&mut self, samples: &[i16]) { fn push_sample(&mut self, sample: StereoSample) {
self.device.queue(&samples); #![allow(unused_must_use)]
self.producer.push(sample);
} }
} }
pub fn create_audio_player(sdl: &sdl2::Sdl) -> Sdl2AudioPlayer { pub fn create_audio_player(sdl: &sdl2::Sdl) -> Sdl2AudioPlayer {
let audio_subsystem = sdl.audio().unwrap();
let desired_spec = AudioSpecDesired { let desired_spec = AudioSpecDesired {
freq: Some(44_100), freq: Some(44_100),
channels: Some(2), // stereo channels: Some(2), // stereo
samples: None, samples: None,
}; };
let audio_subsystem = sdl.audio().unwrap();
let mut freq = 0;
let mut producer: Option<Producer<StereoSample>> = None;
let device = audio_subsystem let device = audio_subsystem
.open_queue::<i16, _>(None, &desired_spec) .open_playback(None, &desired_spec, |spec| {
println!("Found audio device: {:?}", spec);
freq = spec.freq;
// Create a thread-safe SPSC fifo
let ringbuf_size = (spec.samples as usize) * 2;
let rb = RingBuffer::<StereoSample>::new(ringbuf_size);
let (prod, cons) = rb.split();
// move producer to the outer scope
producer = Some(prod);
GbaAudioCallback {
consumer: cons,
spec,
}
})
.unwrap(); .unwrap();
println!("Found audio device: {:?}", device.spec());
let freq = device.spec().freq;
device.resume(); device.resume();
Sdl2AudioPlayer { device, freq } Sdl2AudioPlayer {
_device: device,
freq,
producer: producer.unwrap(),
}
} }

View file

@ -17,9 +17,9 @@ mod audio;
mod input; mod input;
mod video; mod video;
use audio::{create_audio_player, Sdl2AudioPlayer}; use audio::create_audio_player;
use input::{create_input, Sdl2Input}; use input::create_input;
use video::{create_video_interface, Sdl2Video}; use video::create_video_interface;
extern crate rustboyadvance_ng; extern crate rustboyadvance_ng;
use rustboyadvance_ng::prelude::*; use rustboyadvance_ng::prelude::*;
@ -85,7 +85,6 @@ fn main() {
keycode: Some(Keycode::Space), keycode: Some(Keycode::Space),
.. ..
} => { } => {
audio.borrow_mut().device.clear(); // clear audio queue
frame_limiter = true; frame_limiter = true;
} }
Event::KeyDown { Event::KeyDown {