Merge pull request #6 from JoshuaBatty/timestamp_asio_fixes
Timestamp asio fixes
This commit is contained in:
commit
2a62fd1af3
|
@ -84,8 +84,15 @@ pub struct SampleRate {
|
||||||
pub rate: u32,
|
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
|
/// 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.
|
/// Input and Output streams.
|
||||||
///
|
///
|
||||||
|
@ -355,9 +362,9 @@ impl Asio {
|
||||||
|
|
||||||
impl BufferCallback {
|
impl BufferCallback {
|
||||||
/// Calls the inner callback.
|
/// Calls the inner callback.
|
||||||
fn run(&mut self, index: i32) {
|
fn run(&mut self, callback_info: &CallbackInfo) {
|
||||||
let cb = &mut self.0;
|
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.
|
/// 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
|
pub fn add_callback<F>(&self, callback: F) -> CallbackId
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(i32) + Send,
|
F: 'static + FnMut(&CallbackInfo) + Send,
|
||||||
{
|
{
|
||||||
let mut bc = BUFFER_CALLBACK.lock().unwrap();
|
let mut bc = BUFFER_CALLBACK.lock().unwrap();
|
||||||
let id = bc
|
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
|
// 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.
|
// application does not need to know about time code, the driver has less work to do.
|
||||||
// TODO: Provide an option for this?
|
// TODO: Provide an option for this?
|
||||||
0
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => 0, // Unknown/unhandled message type.
|
_ => 0, // Unknown/unhandled message type.
|
||||||
|
@ -909,8 +916,15 @@ extern "C" fn buffer_switch_time_info(
|
||||||
) -> *mut ai::ASIOTime {
|
) -> *mut ai::ASIOTime {
|
||||||
// This lock is probably unavoidable, but locks in the audio stream are not great.
|
// This lock is probably unavoidable, but locks in the audio stream are not great.
|
||||||
let mut bcs = BUFFER_CALLBACK.lock().unwrap();
|
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() {
|
for &mut (_, ref mut bc) in bcs.iter_mut() {
|
||||||
bc.run(double_buffer_index);
|
bc.run(&callback_info);
|
||||||
}
|
}
|
||||||
time
|
time
|
||||||
}
|
}
|
||||||
|
@ -952,6 +966,10 @@ fn check_type_sizes() {
|
||||||
std::mem::size_of::<AsioTimeCode>(),
|
std::mem::size_of::<AsioTimeCode>(),
|
||||||
std::mem::size_of::<ai::ASIOTimeCode>()
|
std::mem::size_of::<ai::ASIOTimeCode>()
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
std::mem::size_of::<AsioTimeInfo>(),
|
||||||
|
std::mem::size_of::<ai::AsioTimeInfo>(),
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
std::mem::size_of::<AsioTime>(),
|
std::mem::size_of::<AsioTime>(),
|
||||||
std::mem::size_of::<ai::ASIOTime>()
|
std::mem::size_of::<ai::ASIOTime>()
|
||||||
|
|
|
@ -87,7 +87,8 @@ impl Device {
|
||||||
|
|
||||||
// Set the input callback.
|
// Set the input callback.
|
||||||
// This is most performance critical part of the ASIO bindings.
|
// 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 not playing return early.
|
||||||
if !playing.load(Ordering::SeqCst) {
|
if !playing.load(Ordering::SeqCst) {
|
||||||
return;
|
return;
|
||||||
|
@ -103,10 +104,11 @@ impl Device {
|
||||||
/// 1. Write from the ASIO buffer to the interleaved CPAL buffer.
|
/// 1. Write from the ASIO buffer to the interleaved CPAL buffer.
|
||||||
/// 2. Deliver the CPAL buffer to the user callback.
|
/// 2. Deliver the CPAL buffer to the user callback.
|
||||||
unsafe fn process_input_callback<A, B, D, F>(
|
unsafe fn process_input_callback<A, B, D, F>(
|
||||||
callback: &mut D,
|
data_callback: &mut D,
|
||||||
interleaved: &mut [u8],
|
interleaved: &mut [u8],
|
||||||
asio_stream: &sys::AsioStream,
|
asio_stream: &sys::AsioStream,
|
||||||
buffer_index: usize,
|
asio_info: &sys::CallbackInfo,
|
||||||
|
sample_rate: crate::SampleRate,
|
||||||
from_endianness: F,
|
from_endianness: F,
|
||||||
) where
|
) where
|
||||||
A: AsioSample,
|
A: AsioSample,
|
||||||
|
@ -116,7 +118,9 @@ impl Device {
|
||||||
{
|
{
|
||||||
// 1. Write the ASIO channels to the CPAL buffer.
|
// 1. Write the ASIO channels to the CPAL buffer.
|
||||||
let interleaved: &mut [B] = cast_slice_mut(interleaved);
|
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 {
|
for ch_ix in 0..n_channels {
|
||||||
let asio_channel = asio_channel_slice::<A>(asio_stream, buffer_index, ch_ix);
|
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) {
|
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 data = interleaved.as_mut_ptr() as *mut ();
|
||||||
let len = interleaved.len();
|
let len = interleaved.len();
|
||||||
let data = Data::from_parts(data, len, B::FORMAT);
|
let data = Data::from_parts(data, len, B::FORMAT);
|
||||||
let info = InputCallbackInfo {};
|
let callback = system_time_to_stream_instant(asio_info.system_time);
|
||||||
callback(&data, &info);
|
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) {
|
match (&stream_type, sample_format) {
|
||||||
|
@ -138,7 +148,8 @@ impl Device {
|
||||||
&mut data_callback,
|
&mut data_callback,
|
||||||
&mut interleaved,
|
&mut interleaved,
|
||||||
asio_stream,
|
asio_stream,
|
||||||
buffer_index as usize,
|
callback_info,
|
||||||
|
config.sample_rate,
|
||||||
from_le,
|
from_le,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -147,7 +158,8 @@ impl Device {
|
||||||
&mut data_callback,
|
&mut data_callback,
|
||||||
&mut interleaved,
|
&mut interleaved,
|
||||||
asio_stream,
|
asio_stream,
|
||||||
buffer_index as usize,
|
callback_info,
|
||||||
|
config.sample_rate,
|
||||||
from_be,
|
from_be,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -160,7 +172,8 @@ impl Device {
|
||||||
&mut data_callback,
|
&mut data_callback,
|
||||||
&mut interleaved,
|
&mut interleaved,
|
||||||
asio_stream,
|
asio_stream,
|
||||||
buffer_index as usize,
|
callback_info,
|
||||||
|
config.sample_rate,
|
||||||
std::convert::identity::<f32>,
|
std::convert::identity::<f32>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -173,7 +186,8 @@ impl Device {
|
||||||
&mut data_callback,
|
&mut data_callback,
|
||||||
&mut interleaved,
|
&mut interleaved,
|
||||||
asio_stream,
|
asio_stream,
|
||||||
buffer_index as usize,
|
callback_info,
|
||||||
|
config.sample_rate,
|
||||||
from_le,
|
from_le,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -182,7 +196,8 @@ impl Device {
|
||||||
&mut data_callback,
|
&mut data_callback,
|
||||||
&mut interleaved,
|
&mut interleaved,
|
||||||
asio_stream,
|
asio_stream,
|
||||||
buffer_index as usize,
|
callback_info,
|
||||||
|
config.sample_rate,
|
||||||
from_be,
|
from_be,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -194,7 +209,8 @@ impl Device {
|
||||||
&mut data_callback,
|
&mut data_callback,
|
||||||
&mut interleaved,
|
&mut interleaved,
|
||||||
asio_stream,
|
asio_stream,
|
||||||
buffer_index as usize,
|
callback_info,
|
||||||
|
config.sample_rate,
|
||||||
std::convert::identity::<f64>,
|
std::convert::identity::<f64>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -254,7 +270,8 @@ impl Device {
|
||||||
let playing = Arc::clone(&stream_playing);
|
let playing = Arc::clone(&stream_playing);
|
||||||
let asio_streams = self.asio_streams.clone();
|
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 not playing, return early.
|
||||||
if !playing.load(Ordering::SeqCst) {
|
if !playing.load(Ordering::SeqCst) {
|
||||||
return;
|
return;
|
||||||
|
@ -273,7 +290,7 @@ impl Device {
|
||||||
// the current `buffer_index`.
|
// the current `buffer_index`.
|
||||||
//
|
//
|
||||||
// If not, we will silence it and set the opposite buffer half to unsilenced.
|
// 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 => {
|
0 if !silence_asio_buffer.first => {
|
||||||
silence_asio_buffer.first = true;
|
silence_asio_buffer.first = true;
|
||||||
silence_asio_buffer.second = false;
|
silence_asio_buffer.second = false;
|
||||||
|
@ -294,11 +311,12 @@ impl Device {
|
||||||
/// 3. Finally, write the interleaved data to the non-interleaved ASIO buffer,
|
/// 3. Finally, write the interleaved data to the non-interleaved ASIO buffer,
|
||||||
/// performing endianness conversions as necessary.
|
/// performing endianness conversions as necessary.
|
||||||
unsafe fn process_output_callback<A, B, D, F>(
|
unsafe fn process_output_callback<A, B, D, F>(
|
||||||
callback: &mut D,
|
data_callback: &mut D,
|
||||||
interleaved: &mut [u8],
|
interleaved: &mut [u8],
|
||||||
silence_asio_buffer: bool,
|
silence_asio_buffer: bool,
|
||||||
asio_stream: &sys::AsioStream,
|
asio_stream: &sys::AsioStream,
|
||||||
buffer_index: usize,
|
asio_info: &sys::CallbackInfo,
|
||||||
|
sample_rate: crate::SampleRate,
|
||||||
to_endianness: F,
|
to_endianness: F,
|
||||||
) where
|
) where
|
||||||
A: Sample,
|
A: Sample,
|
||||||
|
@ -311,11 +329,19 @@ impl Device {
|
||||||
let data = interleaved.as_mut_ptr() as *mut ();
|
let data = interleaved.as_mut_ptr() as *mut ();
|
||||||
let len = interleaved.len();
|
let len = interleaved.len();
|
||||||
let mut data = Data::from_parts(data, len, A::FORMAT);
|
let mut data = Data::from_parts(data, len, A::FORMAT);
|
||||||
let info = OutputCallbackInfo {};
|
let callback = system_time_to_stream_instant(asio_info.system_time);
|
||||||
callback(&mut data, &info);
|
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.
|
// 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 {
|
if silence_asio_buffer {
|
||||||
for ch_ix in 0..n_channels {
|
for ch_ix in 0..n_channels {
|
||||||
let asio_channel =
|
let asio_channel =
|
||||||
|
@ -343,7 +369,8 @@ impl Device {
|
||||||
&mut interleaved,
|
&mut interleaved,
|
||||||
silence,
|
silence,
|
||||||
asio_stream,
|
asio_stream,
|
||||||
buffer_index as usize,
|
callback_info,
|
||||||
|
config.sample_rate,
|
||||||
to_le,
|
to_le,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -353,7 +380,8 @@ impl Device {
|
||||||
&mut interleaved,
|
&mut interleaved,
|
||||||
silence,
|
silence,
|
||||||
asio_stream,
|
asio_stream,
|
||||||
buffer_index as usize,
|
callback_info,
|
||||||
|
config.sample_rate,
|
||||||
to_be,
|
to_be,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -367,7 +395,8 @@ impl Device {
|
||||||
&mut interleaved,
|
&mut interleaved,
|
||||||
silence,
|
silence,
|
||||||
asio_stream,
|
asio_stream,
|
||||||
buffer_index as usize,
|
callback_info,
|
||||||
|
config.sample_rate,
|
||||||
std::convert::identity::<f32>,
|
std::convert::identity::<f32>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -381,7 +410,8 @@ impl Device {
|
||||||
&mut interleaved,
|
&mut interleaved,
|
||||||
silence,
|
silence,
|
||||||
asio_stream,
|
asio_stream,
|
||||||
buffer_index as usize,
|
callback_info,
|
||||||
|
config.sample_rate,
|
||||||
to_le,
|
to_le,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -391,7 +421,8 @@ impl Device {
|
||||||
&mut interleaved,
|
&mut interleaved,
|
||||||
silence,
|
silence,
|
||||||
asio_stream,
|
asio_stream,
|
||||||
buffer_index as usize,
|
callback_info,
|
||||||
|
config.sample_rate,
|
||||||
to_be,
|
to_be,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -404,7 +435,8 @@ impl Device {
|
||||||
&mut interleaved,
|
&mut interleaved,
|
||||||
silence,
|
silence,
|
||||||
asio_stream,
|
asio_stream,
|
||||||
buffer_index as usize,
|
callback_info,
|
||||||
|
config.sample_rate,
|
||||||
std::convert::identity::<f64>,
|
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.
|
/// Check whether or not the desired config is supported by the stream.
|
||||||
///
|
///
|
||||||
/// Checks sample rate, data type and then finally the number of channels.
|
/// Checks sample rate, data type and then finally the number of channels.
|
||||||
|
|
Loading…
Reference in New Issue