From 22eef32898d2d3080b8007970508d06c1247a4b4 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Mon, 27 Apr 2020 22:07:23 +0200 Subject: [PATCH] WIP Add timestamp implementation to ASIO backend Currently unchecked and untested, though most of the groundwork should be laid. --- asio-sys/src/bindings/mod.rs | 28 +++++++++--- src/host/asio/stream.rs | 83 +++++++++++++++++++++++++++--------- 2 files changed, 87 insertions(+), 24 deletions(-) diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index 0964685..fe08304 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -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); +struct BufferCallback(Box); /// 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(&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 @@ -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 time: &mut AsioTime = unsafe { + &mut *(time as *mut AsioTime) + }; + let callback_info = CallbackInfo { + buffer_index: double_buffer_index, + system_time: 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::(), std::mem::size_of::() ); + assert_eq!( + std::mem::size_of::(), + std::mem::size_of::(), + ); assert_eq!( std::mem::size_of::(), std::mem::size_of::() diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 766e9d2..ea430a5 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -87,7 +87,7 @@ 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 callback_id = self.driver.add_callback(move |callback_info| unsafe { // If not playing return early. if !playing.load(Ordering::SeqCst) { return; @@ -106,7 +106,8 @@ impl Device { 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 +117,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::(asio_stream, buffer_index, ch_ix); for (frame, s_asio) in interleaved.chunks_mut(n_channels).zip(asio_channel) { @@ -128,7 +131,13 @@ 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 {}; + 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 = InputStreamTimestamp { callback, capture }; + let info = InputCallbackInfo { timestamp }; callback(&data, &info); } @@ -138,7 +147,8 @@ impl Device { &mut data_callback, &mut interleaved, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, from_le, ); } @@ -147,7 +157,8 @@ impl Device { &mut data_callback, &mut interleaved, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, from_be, ); } @@ -160,7 +171,8 @@ impl Device { &mut data_callback, &mut interleaved, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, std::convert::identity::, ); } @@ -173,7 +185,8 @@ impl Device { &mut data_callback, &mut interleaved, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, from_le, ); } @@ -182,7 +195,8 @@ impl Device { &mut data_callback, &mut interleaved, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, from_be, ); } @@ -194,7 +208,8 @@ impl Device { &mut data_callback, &mut interleaved, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, std::convert::identity::, ); } @@ -254,7 +269,7 @@ 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 callback_id = self.driver.add_callback(move |callback_info| unsafe { // If not playing, return early. if !playing.load(Ordering::SeqCst) { return; @@ -298,7 +313,8 @@ impl Device { 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 +327,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 {}; + 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 = OutputStreamTimestamp { callback, playback }; + let info = OutputCallbackInfo { timestamp }; 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 +367,8 @@ impl Device { &mut interleaved, silence, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, to_le, ); } @@ -354,6 +379,7 @@ impl Device { silence, asio_stream, buffer_index as usize, + config.sample_rate, to_be, ); } @@ -367,7 +393,8 @@ impl Device { &mut interleaved, silence, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, std::convert::identity::, ); } @@ -381,7 +408,8 @@ impl Device { &mut interleaved, silence, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, to_le, ); } @@ -391,7 +419,8 @@ impl Device { &mut interleaved, silence, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, to_be, ); } @@ -404,7 +433,8 @@ impl Device { &mut interleaved, silence, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, std::convert::identity::, ); } @@ -578,6 +608,21 @@ impl AsioSample for f64 { } } +/// Asio retrieves system time via `timeGetTime` which returns the time in milliseconds. +fn system_time_to_stream_instant(system_time: ai::ASIOTimeStamp) -> crate::StreamInstant { + let secs = system_time as u64 / 1_000; + let nanos = ((system_time as u64 - secs * 1_000) * 1_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.