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)",
]
[[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]]
name = "dirs"
version = "2.0.2"
@ -669,6 +678,11 @@ name = "rgb"
version = "0.8.13"
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]]
name = "rustboyadvance-ng"
version = "0.1.0"
@ -682,12 +696,14 @@ dependencies = [
"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)",
"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)",
"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)",
"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-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)",
"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)",
@ -1007,6 +1023,7 @@ dependencies = [
"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 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-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"
@ -1060,6 +1077,7 @@ dependencies = [
"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 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_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"

View file

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

View file

@ -467,12 +467,7 @@ impl Gpu {
}
// Returns the new gpu state
pub fn step(
&mut self,
cycles: usize,
sb: &mut SysBus,
irqs: &mut IrqBitmask,
) {
pub fn step(&mut self, cycles: usize, sb: &mut SysBus, irqs: &mut IrqBitmask) {
self.cycles += cycles;
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;
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 {
last_in_sample: Sample,
last_in_sample: StereoSample,
phase: f32,
pub in_freq: f32,
out_freq: f32,
}
fn cosine_interpolation(y1: Sample, y2: Sample, phase: f32) -> Sample {
let y1_left = y1.0 as f32;
let y1_right = y1.1 as f32;
let y2_left = y2.0 as f32;
let y2_right = y2.1 as f32;
fn cosine_interpolation(y1: i16, y2: i16, phase: f32) -> i16 {
let y1 = y1 as i32 as f32;
let y2 = y2 as i32 as f32;
let mu2 = (1.0 - (PI * phase).cos()) / 2.0;
(
(y2_left * (1.0 - mu2) + y1_left * mu2) as i16,
(y2_right * (1.0 - mu2) + y1_right * mu2) as i16,
)
(y2 * (1.0 - mu2) + y1 * mu2) as i16
}
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 {
let x = cosine_interpolation(self.last_in_sample, s, self.phase);
output.push(x.0);
output.push(x.1);
let left = cosine_interpolation(self.last_in_sample.0, s.0, self.phase);
let right = cosine_interpolation(self.last_in_sample.1, s.1, self.phase);
audio.push_sample((left, right));
self.phase += self.in_freq / self.out_freq;
}
self.phase = self.phase - 1.0;

View file

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

View file

@ -31,10 +31,20 @@ struct DmaSoundChannel {
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 {
fn default() -> DmaSoundChannel {
DmaSoundChannel {
volume_shift: 1,
volume_shift: 0,
value: 0,
enable_right: 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_H: u32 = REG_FIFO_B + 2;
type AudioDeviceRcRefCell = Rc<RefCell<dyn AudioInterface>>;
#[derive(DebugStub)]
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?
last_sample_cycles: usize, // cycles count when we last provided a new sample.
@ -90,7 +104,7 @@ pub struct SoundController {
}
impl SoundController {
pub fn new(audio_device: Rc<RefCell<dyn AudioInterface>>) -> SoundController {
pub fn new(audio_device: AudioDeviceRcRefCell) -> SoundController {
let resampler =
CosineResampler::new(32768_f32, audio_device.borrow().get_sample_rate() as f32);
SoundController {
@ -250,13 +264,13 @@ impl SoundController {
}
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 >> 8) & 0xff) as i8);
}
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 >> 8) & 0xff) as i8);
}
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];
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 {
channel.value = channel.fifo.read();
if channel.fifo.count() <= 16 {
if timer_id == dma.timer_select {
dma.value = dma.fifo.read();
if dma.fifo.count() <= 16 {
dmac.notify_sound_fifo(FIFO_INDEX_TO_REG[fifo]);
}
}
@ -310,22 +324,19 @@ impl SoundController {
// time to push a new sample!
let mut sample = (0i16, 0i16);
for i in 0..2 {
let channel = &self.dma_sound[i];
if channel.enable_left {
sample.0 += ((channel.value as i16) << 8) >> channel.volume_shift;
}
if channel.enable_right {
sample.1 += ((channel.value as i16) << 8) >> channel.volume_shift;
let mut sample = [0; 2];
for channel in 0..=1 {
for dma in &mut self.dma_sound {
if dma.is_stereo_channel_enabled(channel) {
sample[channel] += dma.value as i16;
}
}
}
self.resampler.push_sample(sample, &mut self.output_buffer);
if self.output_buffer.len() >= 10000 {
self.audio_device.borrow_mut().play(&self.output_buffer);
self.output_buffer.clear();
}
let mut audio = self.audio_device.borrow_mut();
self.resampler
.push_sample((sample[0], sample[1]), &mut *audio);
}
}
}

View file

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

View file

@ -1,40 +1,89 @@
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 device: AudioQueue<i16>,
_device: AudioDevice<GbaAudioCallback>,
producer: Producer<StereoSample>,
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 {
fn get_sample_rate(&self) -> i32 {
self.freq
}
fn play(&mut self, samples: &[i16]) {
self.device.queue(&samples);
fn push_sample(&mut self, sample: StereoSample) {
#![allow(unused_must_use)]
self.producer.push(sample);
}
}
pub fn create_audio_player(sdl: &sdl2::Sdl) -> Sdl2AudioPlayer {
let audio_subsystem = sdl.audio().unwrap();
let desired_spec = AudioSpecDesired {
freq: Some(44_100),
channels: Some(2), // stereo
samples: None,
};
let audio_subsystem = sdl.audio().unwrap();
let mut freq = 0;
let mut producer: Option<Producer<StereoSample>> = None;
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();
println!("Found audio device: {:?}", device.spec());
let freq = device.spec().freq;
device.resume();
Sdl2AudioPlayer { device, freq }
Sdl2AudioPlayer {
_device: device,
freq,
producer: producer.unwrap(),
}
}

View file

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