Force queue to play sources on frame boundaries (#455)

As I understand it, this is the issue:
  
     * Previously, when a queue has `keep_alive_if_empty` set to true, and it becomes empty, then it will push a silence lasting 10ms onto the queue.
 
     * This is an issue because `current_frame_len` would have returned the worst case, `512`, and the silence lasts less than that.
 
     * This means that unless the source is added immediately to the queue, and so a silence is never played, then the first actual source could start playing at a frame that is not aligned to its channels, or play at the wrong sample rate.
 
     * This is only determined by when the source is added to the queue after its initialization. This explains why the issue was inconsistent, as it relied on the speed of execution of code which is basically random.

Solution
 
     * Change the functionality of `Zero` to add a method to create a silence with a certain number of frames.
 
     * Replace the 10ms silence with a silence the length of `THRESHOLD`
 
     * Change queue's `current_frame_len` to return `THRESHOLD` if a silence will be played next.
This commit is contained in:
dis-da-moe 2022-11-23 03:36:57 +03:00 committed by GitHub
parent 8efccbbf39
commit 268dddae64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 79 additions and 27 deletions

BIN
assets/RL.ogg Normal file

Binary file not shown.

View file

@ -2,33 +2,51 @@ use std::io::BufReader;
use std::thread;
use std::time::Duration;
use rodio::Source;
fn main() {
let iter_duration = Duration::from_secs(5);
let iter_distance = 5.;
let refresh_duration = Duration::from_millis(10);
let num_steps = iter_duration.as_secs_f32() / refresh_duration.as_secs_f32();
let step_distance = iter_distance / num_steps;
let num_steps = num_steps as u32;
let repeats = 5;
let total_duration = iter_duration * 2 * repeats;
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::SpatialSink::try_new(
&handle,
[-10.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[-1.0, 0.0, 0.0],
)
.unwrap();
let mut positions = ([0., 0., 0.], [-1., 0., 0.], [1., 0., 0.]);
let sink = rodio::SpatialSink::try_new(&handle, positions.0, positions.1, positions.2).unwrap();
let file = std::fs::File::open("assets/music.ogg").unwrap();
let source = rodio::Decoder::new(BufReader::new(file)).unwrap();
let source = rodio::Decoder::new(BufReader::new(file))
.unwrap()
.repeat_infinite()
.take_duration(total_duration);
sink.append(source);
// A sound emitter playing the music starting at the left gradually moves to the right
// eventually passing through the listener, then it continues on to the right for a distance
// A sound emitter playing the music starting at the centre gradually moves to the right
// until it stops and begins traveling to the left, it will eventually pass through the
// listener again.
// listener again and go to the far left.
// This is repeated 5 times.
for _ in 0..5 {
for i in 1..1001 {
thread::sleep(Duration::from_millis(5));
sink.set_emitter_position([(i - 500) as f32 / 50.0, 0.0, 0.0]);
for _ in 0..repeats {
for _ in 0..num_steps {
thread::sleep(refresh_duration);
positions.0[0] += step_distance;
sink.set_emitter_position(positions.0);
}
for i in 1..1001 {
thread::sleep(Duration::from_millis(5));
sink.set_emitter_position([-(i - 500) as f32 / 50.0, 0.0, 0.0]);
for _ in 0..(num_steps * 2) {
thread::sleep(refresh_duration);
positions.0[0] -= step_distance;
sink.set_emitter_position(positions.0);
}
for _ in 0..num_steps {
thread::sleep(refresh_duration);
positions.0[0] += step_distance;
sink.set_emitter_position(positions.0);
}
}
sink.sleep_until_end();

12
examples/stereo.rs Normal file
View file

@ -0,0 +1,12 @@
//! Plays a tone alternating between right and left ears, with right being first.
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
let file = std::fs::File::open("assets/RL.ogg").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
sink.sleep_until_end();
}

View file

@ -100,6 +100,7 @@ pub struct SourcesQueueOutput<S> {
input: Arc<SourcesQueueInput<S>>,
}
const THRESHOLD: usize = 512;
impl<S> Source for SourcesQueueOutput<S>
where
S: Sample + Send + 'static,
@ -116,12 +117,16 @@ where
// If the `size_hint` is `None` as well, we are in the worst case scenario. To handle this
// situation we force a frame to have a maximum number of samples indicate by this
// constant.
const THRESHOLD: usize = 512;
// Try the current `current_frame_len`.
if let Some(val) = self.current.current_frame_len() {
if val != 0 {
return Some(val);
} else if self.input.keep_alive_if_empty.load(Ordering::Acquire)
&& self.input.next_sounds.lock().unwrap().is_empty()
{
// The next source will be a filler silence which will have the length of `THRESHOLD`
return Some(THRESHOLD);
}
}
@ -198,13 +203,10 @@ where
let mut next = self.input.next_sounds.lock().unwrap();
if next.len() == 0 {
let silence = Box::new(Zero::<S>::new_samples(1, 44100, THRESHOLD)) as Box<_>;
if self.input.keep_alive_if_empty.load(Ordering::Acquire) {
// Play a short silence in order to avoid spinlocking.
let silence = Zero::<S>::new(1, 44100); // TODO: meh
(
Box::new(silence.take_duration(Duration::from_millis(10))) as Box<_>,
None,
)
(silence, None)
} else {
return Err(());
}

View file

@ -8,6 +8,7 @@ use crate::{Sample, Source};
pub struct Zero<S> {
channels: u16,
sample_rate: u32,
num_samples: Option<usize>,
marker: PhantomData<S>,
}
@ -17,6 +18,16 @@ impl<S> Zero<S> {
Zero {
channels,
sample_rate,
num_samples: None,
marker: PhantomData,
}
}
#[inline]
pub fn new_samples(channels: u16, sample_rate: u32, num_samples: usize) -> Zero<S> {
Zero {
channels,
sample_rate,
num_samples: Some(num_samples),
marker: PhantomData,
}
}
@ -30,7 +41,16 @@ where
#[inline]
fn next(&mut self) -> Option<S> {
Some(S::zero_value())
if let Some(num_samples) = self.num_samples {
if num_samples > 0 {
self.num_samples = Some(num_samples - 1);
Some(S::zero_value())
} else {
None
}
} else {
Some(S::zero_value())
}
}
}
@ -40,7 +60,7 @@ where
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
None
self.num_samples
}
#[inline]