Merge pull request #6 from JoshuaBatty/timestamp_asio_fixes

Timestamp asio fixes
This commit is contained in:
mitchmindtree 2020-05-01 14:07:42 +02:00 committed by GitHub
commit 2a62fd1af3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 104 additions and 31 deletions

View File

@ -84,8 +84,15 @@ pub struct SampleRate {
pub rate: u32,
}
/// Information provided to the BufferCallback.
#[derive(Debug)]
pub struct CallbackInfo {
pub buffer_index: i32,
pub system_time: ai::ASIOTimeStamp,
}
/// Holds the pointer to the callbacks that come from cpal
struct BufferCallback(Box<dyn FnMut(i32) + Send>);
struct BufferCallback(Box<dyn FnMut(&CallbackInfo) + Send>);
/// Input and Output streams.
///
@ -355,9 +362,9 @@ impl Asio {
impl BufferCallback {
/// Calls the inner callback.
fn run(&mut self, index: i32) {
fn run(&mut self, callback_info: &CallbackInfo) {
let cb = &mut self.0;
cb(index);
cb(callback_info);
}
}
@ -610,7 +617,7 @@ impl Driver {
/// Returns an ID uniquely associated with the given callback so that it may be removed later.
pub fn add_callback<F>(&self, callback: F) -> CallbackId
where
F: 'static + FnMut(i32) + Send,
F: 'static + FnMut(&CallbackInfo) + Send,
{
let mut bc = BUFFER_CALLBACK.lock().unwrap();
let id = bc
@ -889,7 +896,7 @@ extern "C" fn asio_message(
// Informs the driver whether the application is interested in time code info. If an
// application does not need to know about time code, the driver has less work to do.
// TODO: Provide an option for this?
0
1
}
_ => 0, // Unknown/unhandled message type.
@ -909,8 +916,15 @@ extern "C" fn buffer_switch_time_info(
) -> *mut ai::ASIOTime {
// This lock is probably unavoidable, but locks in the audio stream are not great.
let mut bcs = BUFFER_CALLBACK.lock().unwrap();
let asio_time: &mut AsioTime = unsafe {
&mut *(time as *mut AsioTime)
};
let callback_info = CallbackInfo {
buffer_index: double_buffer_index,
system_time: asio_time.time_info.system_time,
};
for &mut (_, ref mut bc) in bcs.iter_mut() {
bc.run(double_buffer_index);
bc.run(&callback_info);
}
time
}
@ -952,6 +966,10 @@ fn check_type_sizes() {
std::mem::size_of::<AsioTimeCode>(),
std::mem::size_of::<ai::ASIOTimeCode>()
);
assert_eq!(
std::mem::size_of::<AsioTimeInfo>(),
std::mem::size_of::<ai::AsioTimeInfo>(),
);
assert_eq!(
std::mem::size_of::<AsioTime>(),
std::mem::size_of::<ai::ASIOTime>()

View File

@ -87,7 +87,8 @@ impl Device {
// Set the input callback.
// This is most performance critical part of the ASIO bindings.
let callback_id = self.driver.add_callback(move |buffer_index| unsafe {
let config = config.clone();
let callback_id = self.driver.add_callback(move |callback_info| unsafe {
// If not playing return early.
if !playing.load(Ordering::SeqCst) {
return;
@ -103,10 +104,11 @@ impl Device {
/// 1. Write from the ASIO buffer to the interleaved CPAL buffer.
/// 2. Deliver the CPAL buffer to the user callback.
unsafe fn process_input_callback<A, B, D, F>(
callback: &mut D,
data_callback: &mut D,
interleaved: &mut [u8],
asio_stream: &sys::AsioStream,
buffer_index: usize,
asio_info: &sys::CallbackInfo,
sample_rate: crate::SampleRate,
from_endianness: F,
) where
A: AsioSample,
@ -116,7 +118,9 @@ impl Device {
{
// 1. Write the ASIO channels to the CPAL buffer.
let interleaved: &mut [B] = cast_slice_mut(interleaved);
let n_channels = interleaved.len() / asio_stream.buffer_size as usize;
let n_frames = asio_stream.buffer_size as usize;
let n_channels = interleaved.len() / n_frames;
let buffer_index = asio_info.buffer_index as usize;
for ch_ix in 0..n_channels {
let asio_channel = asio_channel_slice::<A>(asio_stream, buffer_index, ch_ix);
for (frame, s_asio) in interleaved.chunks_mut(n_channels).zip(asio_channel) {
@ -128,8 +132,14 @@ impl Device {
let data = interleaved.as_mut_ptr() as *mut ();
let len = interleaved.len();
let data = Data::from_parts(data, len, B::FORMAT);
let info = InputCallbackInfo {};
callback(&data, &info);
let callback = system_time_to_stream_instant(asio_info.system_time);
let delay = frames_to_duration(n_frames, sample_rate);
let capture = callback
.sub(delay)
.expect("`capture` occurs before origin of alsa `StreamInstant`");
let timestamp = crate::InputStreamTimestamp { callback, capture };
let info = InputCallbackInfo { timestamp };
data_callback(&data, &info);
}
match (&stream_type, sample_format) {
@ -138,7 +148,8 @@ impl Device {
&mut data_callback,
&mut interleaved,
asio_stream,
buffer_index as usize,
callback_info,
config.sample_rate,
from_le,
);
}
@ -147,7 +158,8 @@ impl Device {
&mut data_callback,
&mut interleaved,
asio_stream,
buffer_index as usize,
callback_info,
config.sample_rate,
from_be,
);
}
@ -160,7 +172,8 @@ impl Device {
&mut data_callback,
&mut interleaved,
asio_stream,
buffer_index as usize,
callback_info,
config.sample_rate,
std::convert::identity::<f32>,
);
}
@ -173,7 +186,8 @@ impl Device {
&mut data_callback,
&mut interleaved,
asio_stream,
buffer_index as usize,
callback_info,
config.sample_rate,
from_le,
);
}
@ -182,7 +196,8 @@ impl Device {
&mut data_callback,
&mut interleaved,
asio_stream,
buffer_index as usize,
callback_info,
config.sample_rate,
from_be,
);
}
@ -194,7 +209,8 @@ impl Device {
&mut data_callback,
&mut interleaved,
asio_stream,
buffer_index as usize,
callback_info,
config.sample_rate,
std::convert::identity::<f64>,
);
}
@ -254,7 +270,8 @@ impl Device {
let playing = Arc::clone(&stream_playing);
let asio_streams = self.asio_streams.clone();
let callback_id = self.driver.add_callback(move |buffer_index| unsafe {
let config = config.clone();
let callback_id = self.driver.add_callback(move |callback_info| unsafe {
// If not playing, return early.
if !playing.load(Ordering::SeqCst) {
return;
@ -273,7 +290,7 @@ impl Device {
// the current `buffer_index`.
//
// If not, we will silence it and set the opposite buffer half to unsilenced.
let silence = match buffer_index {
let silence = match callback_info.buffer_index {
0 if !silence_asio_buffer.first => {
silence_asio_buffer.first = true;
silence_asio_buffer.second = false;
@ -294,11 +311,12 @@ impl Device {
/// 3. Finally, write the interleaved data to the non-interleaved ASIO buffer,
/// performing endianness conversions as necessary.
unsafe fn process_output_callback<A, B, D, F>(
callback: &mut D,
data_callback: &mut D,
interleaved: &mut [u8],
silence_asio_buffer: bool,
asio_stream: &sys::AsioStream,
buffer_index: usize,
asio_info: &sys::CallbackInfo,
sample_rate: crate::SampleRate,
to_endianness: F,
) where
A: Sample,
@ -311,11 +329,19 @@ impl Device {
let data = interleaved.as_mut_ptr() as *mut ();
let len = interleaved.len();
let mut data = Data::from_parts(data, len, A::FORMAT);
let info = OutputCallbackInfo {};
callback(&mut data, &info);
let callback = system_time_to_stream_instant(asio_info.system_time);
let n_frames = asio_stream.buffer_size as usize;
let delay = frames_to_duration(n_frames, sample_rate);
let playback = callback
.add(delay)
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
let timestamp = crate::OutputStreamTimestamp { callback, playback };
let info = OutputCallbackInfo { timestamp };
data_callback(&mut data, &info);
// 2. Silence ASIO channels if necessary.
let n_channels = interleaved.len() / asio_stream.buffer_size as usize;
let n_channels = interleaved.len() / n_frames;
let buffer_index = asio_info.buffer_index as usize;
if silence_asio_buffer {
for ch_ix in 0..n_channels {
let asio_channel =
@ -343,7 +369,8 @@ impl Device {
&mut interleaved,
silence,
asio_stream,
buffer_index as usize,
callback_info,
config.sample_rate,
to_le,
);
}
@ -353,7 +380,8 @@ impl Device {
&mut interleaved,
silence,
asio_stream,
buffer_index as usize,
callback_info,
config.sample_rate,
to_be,
);
}
@ -367,7 +395,8 @@ impl Device {
&mut interleaved,
silence,
asio_stream,
buffer_index as usize,
callback_info,
config.sample_rate,
std::convert::identity::<f32>,
);
}
@ -381,7 +410,8 @@ impl Device {
&mut interleaved,
silence,
asio_stream,
buffer_index as usize,
callback_info,
config.sample_rate,
to_le,
);
}
@ -391,7 +421,8 @@ impl Device {
&mut interleaved,
silence,
asio_stream,
buffer_index as usize,
callback_info,
config.sample_rate,
to_be,
);
}
@ -404,7 +435,8 @@ impl Device {
&mut interleaved,
silence,
asio_stream,
buffer_index as usize,
callback_info,
config.sample_rate,
std::convert::identity::<f64>,
);
}
@ -578,6 +610,29 @@ impl AsioSample for f64 {
}
}
fn asio_ns_to_double(val: sys::bindings::asio_import::ASIOTimeStamp) -> f64 {
let two_raised_to_32 = 4294967296.0;
val.lo as f64 + val.hi as f64 * two_raised_to_32
}
/// Asio retrieves system time via `timeGetTime` which returns the time in milliseconds.
fn system_time_to_stream_instant(
system_time: sys::bindings::asio_import::ASIOTimeStamp,
) -> crate::StreamInstant {
let systime_ns = asio_ns_to_double(system_time);
let secs = systime_ns as i64 / 1_000_000_000;
let nanos = (systime_ns as i64 - secs * 1_000_000_000) as u32;
crate::StreamInstant::new(secs, nanos)
}
/// Convert the given duration in frames at the given sample rate to a `std::time::Duration`.
fn frames_to_duration(frames: usize, rate: crate::SampleRate) -> std::time::Duration {
let secsf = frames as f64 / rate.0 as f64;
let secs = secsf as u64;
let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32;
std::time::Duration::new(secs, nanos)
}
/// Check whether or not the desired config is supported by the stream.
///
/// Checks sample rate, data type and then finally the number of channels.