diff --git a/Cargo.lock b/Cargo.lock index 008dde2..13cdb69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + [[package]] name = "autocfg" version = "1.3.0" @@ -35,12 +41,31 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys", +] + [[package]] name = "duplicate" version = "1.0.0" @@ -51,24 +76,60 @@ dependencies = [ "proc-macro-error", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "indicatif" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "interflow" version = "0.1.0" dependencies = [ "alsa", + "anyhow", "cfg_aliases", "duplicate", + "indicatif", "ndarray", "thiserror", "windows", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.155" @@ -125,12 +186,24 @@ dependencies = [ "autocfg", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "portable-atomic" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -226,6 +299,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + [[package]] name = "version_check" version = "0.9.4" @@ -296,6 +375,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/examples/enumerate_alsa.rs b/examples/enumerate_alsa.rs index 93457d5..ccbabc5 100644 --- a/examples/enumerate_alsa.rs +++ b/examples/enumerate_alsa.rs @@ -11,4 +11,3 @@ fn main() -> Result<(), Box> { fn main() { println!("ALSA driver is not available on this platform"); } - diff --git a/examples/enumerate_wasapi.rs b/examples/enumerate_wasapi.rs index 0109031..05c658c 100644 --- a/examples/enumerate_wasapi.rs +++ b/examples/enumerate_wasapi.rs @@ -11,4 +11,3 @@ fn main() -> Result<(), Box> { fn main() { println!("WASAPI driver is not available on this platform"); } - diff --git a/examples/sine_wave.rs b/examples/sine_wave.rs index 25f22d8..153f34a 100644 --- a/examples/sine_wave.rs +++ b/examples/sine_wave.rs @@ -13,13 +13,15 @@ fn main() -> Result<()> { }; assert!(device.is_config_supported(&config)); println!("Using device {}", device.name()); - let stream = device.create_output_stream( - config, - SineWave { - frequency: 440., - phase: 0., - }, - ).unwrap(); + let stream = device + .create_output_stream( + config, + SineWave { + frequency: 440., + phase: 0., + }, + ) + .unwrap(); println!("Press Enter to stop"); std::io::stdin().read_line(&mut String::new())?; stream.eject().unwrap(); @@ -33,7 +35,10 @@ struct SineWave { impl AudioOutputCallback for SineWave { fn on_output_data(&mut self, context: AudioCallbackContext, mut output: AudioOutput) { - eprintln!("Callback called, timestamp: {:2.3} s", context.timestamp.as_seconds()); + eprintln!( + "Callback called, timestamp: {:2.3} s", + context.timestamp.as_seconds() + ); let sr = context.timestamp.samplerate as f32; for i in 0..output.buffer.num_samples() { output.buffer.set_mono(i, self.next_sample(sr)); diff --git a/examples/util/enumerate.rs b/examples/util/enumerate.rs index b888927..4805b35 100644 --- a/examples/util/enumerate.rs +++ b/examples/util/enumerate.rs @@ -1,7 +1,10 @@ use interflow::{AudioDevice, AudioDriver, DeviceType}; use std::error::Error; -pub fn enumerate_devices(driver: Driver) -> Result<(), Box> where ::Error: 'static { +pub fn enumerate_devices(driver: Driver) -> Result<(), Box> +where + ::Error: 'static, +{ eprintln!("Driver name : {}", Driver::DISPLAY_NAME); eprintln!("Driver version: {}", driver.version()?); eprintln!("Default device"); @@ -19,4 +22,4 @@ pub fn enumerate_devices(driver: Driver) -> Result<(), Box< eprintln!("\t{} ({:?})", device.name(), device.device_type()); } Ok(()) -} \ No newline at end of file +} diff --git a/examples/util/mod.rs b/examples/util/mod.rs index 6ce24a3..3b5980b 100644 --- a/examples/util/mod.rs +++ b/examples/util/mod.rs @@ -1 +1 @@ -pub mod enumerate; \ No newline at end of file +pub mod enumerate; diff --git a/src/audio_buffer.rs b/src/audio_buffer.rs index b0228f8..6e8ed07 100644 --- a/src/audio_buffer.rs +++ b/src/audio_buffer.rs @@ -54,7 +54,7 @@ impl Default for AudioBufferBase { } } -impl> PartialEq> for AudioBufferBase +impl> PartialEq> for AudioBufferBase where S::Elem: PartialEq, { @@ -178,7 +178,7 @@ impl AudioBufferBase { pub fn channels_mut(&mut self) -> impl '_ + Iterator> { self.storage.rows_mut().into_iter() } - + pub fn as_interleaved_mut(&mut self) -> ArrayViewMut2 { self.storage.view_mut().reversed_axes() } @@ -374,11 +374,8 @@ impl<'a, T: Sample> AudioMut<'a, T> { } } - pub fn mix( - &mut self, - other: AudioRef, - other_amplitude: T::Float, - ) where + pub fn mix(&mut self, other: AudioRef, other_amplitude: T::Float) + where T: AddAssign, { for (mut ch_a, ch_b) in self.channels_mut().zip(other.channels()) { diff --git a/src/backends/alsa.rs b/src/backends/alsa.rs index 0c694b8..e184afd 100644 --- a/src/backends/alsa.rs +++ b/src/backends/alsa.rs @@ -1,21 +1,21 @@ use core::fmt; -use std::{borrow::Cow, convert::Infallible, ffi::CStr}; -use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::thread::JoinHandle; use std::time::Duration; +use std::{borrow::Cow, convert::Infallible, ffi::CStr}; use alsa::{device_name::HintIter, pcm, PCM}; use thiserror::Error; +use crate::audio_buffer::{AudioMut, AudioRef}; +use crate::channel_map::{Bitset, ChannelMap32}; +use crate::timestamp::Timestamp; use crate::{ AudioCallbackContext, AudioDevice, AudioDriver, AudioInput, AudioInputCallback, AudioInputDevice, AudioOutput, AudioOutputCallback, AudioOutputDevice, AudioStreamHandle, Channel, DeviceType, StreamConfig, }; -use crate::audio_buffer::{AudioMut, AudioRef}; -use crate::channel_map::{Bitset, ChannelMap32}; -use crate::timestamp::Timestamp; #[derive(Debug, Error)] #[error("ALSA error: ")] @@ -236,7 +236,7 @@ impl AlsaStream { let input = AudioInput { buffer, timestamp }; callback.on_input_data(context, input); timestamp += frames as u64; - + match device.pcm.state() { pcm::State::Suspended => { if hwp.can_resume() { diff --git a/src/backends/mod.rs b/src/backends/mod.rs index fc203b3..628d8a0 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -5,9 +5,14 @@ use crate::{ #[cfg(os_alsa)] pub mod alsa; +#[cfg(os_wasapi)] +pub mod wasapi; + pub fn default_driver() -> impl AudioDriver { #[cfg(os_alsa)] - alsa::AlsaDriver + return alsa::AlsaDriver; + #[cfg(os_wasapi)] + return wasapi::WasapiDriver; } pub fn default_input_device_from(driver: &Driver) -> Driver::Device @@ -26,7 +31,9 @@ where pub fn default_input_device() -> impl AudioInputDevice { #[cfg(os_alsa)] - default_input_device_from(&alsa::AlsaDriver) + return default_input_device_from(&alsa::AlsaDriver); + #[cfg(os_wasapi)] + return default_input_device_from(&wasapi::WasapiDriver); } pub fn default_output_device_from(driver: &Driver) -> Driver::Device @@ -42,7 +49,7 @@ where pub fn default_output_device() -> impl AudioOutputDevice { #[cfg(os_alsa)] - default_output_device_from(&alsa::AlsaDriver) + return default_output_device_from(&alsa::AlsaDriver); + #[cfg(os_wasapi)] + return default_output_device_from(&wasapi::WasapiDriver); } -#[cfg(os_wasapi)] -pub mod wasapi; diff --git a/src/backends/wasapi.rs b/src/backends/wasapi.rs index c053a8e..63d4b63 100644 --- a/src/backends/wasapi.rs +++ b/src/backends/wasapi.rs @@ -1,22 +1,28 @@ use std::{ - borrow::Cow, - ffi::OsString, - os::windows::ffi::OsStringExt, - sync::OnceLock, + borrow::Cow, ffi::OsString, marker::PhantomData, ops::Add, os::windows::ffi::OsStringExt, ptr, sync::{atomic::{AtomicBool, Ordering}, Arc, Mutex, OnceLock}, thread::JoinHandle, time::Duration }; -use crate::{AudioDevice, AudioDriver, Channel, DeviceType, StreamConfig}; +use crate::{ + prelude::{AudioMut, Timestamp}, AudioCallbackContext, AudioDevice, AudioDriver, AudioInputCallback, + AudioInputDevice, AudioOutput, AudioOutputCallback, AudioOutputDevice, AudioStreamHandle, + Channel, DeviceType, StreamConfig, +}; use thiserror::Error; -use windows:: - Win32::{ - Devices::Properties, - Media::Audio, - System::{ - Com::{self, StructuredStorage, STGM_READ}, - Variant::VT_LPWSTR, +use windows::Win32::{ + Devices::Properties, + Foundation::{CloseHandle, HANDLE}, + Media::{ + Audio::{ + self, IAudioCaptureClient, IAudioClient, IAudioClock, IAudioRenderClient, IMMDevice, AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, WAVEFORMATEXTENSIBLE, WAVEFORMATEXTENSIBLE_0 }, - }; - + KernelStreaming, Multimedia, + }, + System::{ + Com::{self, StructuredStorage, STGM_READ}, + Threading, + Variant::VT_LPWSTR, + }, +}; mod util { use std::marker::PhantomData; @@ -107,10 +113,21 @@ impl AudioDriver for WasapiDriver { } /// Type of devices available from the WASAPI driver. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct WasapiDevice { device: windows::Win32::Media::Audio::IMMDevice, device_type: DeviceType, + audio_client: Arc>>, +} + +impl WasapiDevice { + fn new(device: IMMDevice, device_type: DeviceType) -> Self { + WasapiDevice { + device, + device_type, + audio_client: Arc::new(Mutex::new(None)), + } + } } impl AudioDevice for WasapiDevice { @@ -134,25 +151,269 @@ impl AudioDevice for WasapiDevice { todo!() } - fn enumerate_configurations(&self) -> Option> { + fn enumerate_configurations(&self) -> Option> { None::<[StreamConfig; 0]> } - fn channel_map(&self) -> impl IntoIterator { + fn channel_map(&self) -> impl IntoIterator { [] } +} + +// impl AudioInputDevice for WasapiDevice { +// type StreamHandle = WasapiStream; + +// fn create_input_stream( +// &self, +// stream_config: StreamConfig, +// callback: Callback, +// ) -> Result, Self::Error> { +// Ok(WasapiStream::new_input( +// self.name.clone(), +// stream_config, +// callback, +// )) +// } +// } + +impl AudioOutputDevice for WasapiDevice { + type StreamHandle = WasapiStream; + + fn create_output_stream( + &self, + stream_config: StreamConfig, + callback: Callback, + ) -> Result, Self::Error> { + unsafe { + let audio_client: Audio::IAudioClient = + // can fail if the device has been disconnected since we enumerated it, or if + // the device doesn't support playback for some reason + self.device.Activate(Com::CLSCTX_ALL, None)?; + + let format_attempt = + config_to_waveformatextensible(&stream_config, sample_format).ok_or(err)?; + + let buffer_duration = buffer_size_to_duration( + stream_config.buffer_size_range.0.unwrap_or(0), + stream_config.samplerate.round() as u32, + ); + + audio_client.Initialize( + AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + buffer_duration, + 0, + &format_attempt.Format, + None, + )?; + + // obtaining the size of the samples buffer in number of frames + let max_frames_in_buffer = audio_client.GetBufferSize()? as usize; + + // Creating the event that will be signalled whenever we need to submit some samples. + let event_handle = { + let event_handle = + Threading::CreateEventA(None, false, false, windows::core::PCSTR(ptr::null()))?; + + audio_client.SetEventHandle(event_handle)?; + + event_handle + }; + let render_client = audio_client.GetService::()?; + + let audio_clock = audio_client.GetService::()?; + + let stream_config = StreamConfig { + samplerate: stream_config.samplerate, + channels: stream_config.channels, + buffer_size_range: (Some(max_frames_in_buffer), Some(max_frames_in_buffer)), + }; + + Ok(WasapiStream::new_output( + audio_client, + render_client, + stream_config, + callback, + )) + } + } } -impl WasapiDevice { - fn new(device: Audio::IMMDevice, device_type: DeviceType) -> Self { - WasapiDevice { - device, - device_type, +fn config_to_waveformatextensible( + config: &StreamConfig, + sample_format: SampleFormat, +) -> Option { + let format_tag = match sample_format { + SampleFormat::U8 | SampleFormat::I16 => Audio::WAVE_FORMAT_PCM, + + SampleFormat::I32 | SampleFormat::I64 | SampleFormat::F32 => { + KernelStreaming::WAVE_FORMAT_EXTENSIBLE + } + + _ => return None, + }; + let channels = config.channels as u16; + let sample_rate = config.samplerate as u32; + let sample_bytes = sample_format.sample_size() as u16; + let avg_bytes_per_sec = u32::from(channels) * sample_rate * u32::from(sample_bytes); + let block_align = channels * sample_bytes; + let bits_per_sample = 8 * sample_bytes; + + let cb_size = if format_tag == Audio::WAVE_FORMAT_PCM { + 0 + } else { + let extensible_size = std::mem::size_of::(); + let ex_size = std::mem::size_of::(); + (extensible_size - ex_size) as u16 + }; + + let waveformatex = Audio::WAVEFORMATEX { + wFormatTag: format_tag as u16, + nChannels: channels as u16, + nSamplesPerSec: sample_rate, + nAvgBytesPerSec: avg_bytes_per_sec, + nBlockAlign: block_align, + wBitsPerSample: bits_per_sample, + cbSize: cb_size, + }; + + let channel_mask = KernelStreaming::KSAUDIO_SPEAKER_DIRECTOUT; + + let sub_format = match sample_format { + SampleFormat::U8 | SampleFormat::I16 | SampleFormat::I32 | SampleFormat::I64 => { + KernelStreaming::KSDATAFORMAT_SUBTYPE_PCM + } + + SampleFormat::F32 => Multimedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, + _ => return None, + }; + + let waveformatextensible = WAVEFORMATEXTENSIBLE { + Format: waveformatex, + Samples: WAVEFORMATEXTENSIBLE_0 { + wSamplesPerBlock: bits_per_sample, + }, + dwChannelMask: channel_mask, + SubFormat: sub_format, + }; + + Some(waveformatextensible) +} + +pub enum AudioClientFlow { + Render { render_client: IAudioRenderClient }, + Capture { capture_client: IAudioCaptureClient }, +} + +pub struct WasapiStream { + pub audio_client: Audio::IAudioClient, + pub stream_config: StreamConfig, + pub event_handle: HANDLE, + pub join_handle: JoinHandle>, + _p: PhantomData<*mut Callback>, +} + +impl AudioStreamHandle for WasapiStream { + type Error = WasapiError; + + fn eject(self) -> Result { + unsafe { + CloseHandle(self.event_handle)?; + } + + self.join_handle.join().unwrap() + } +} + +// impl WasapiStream { +// fn new_input(stream_config: StreamConfig, mut callback: Callback) -> Self { + +// } +// } + +impl WasapiStream { + fn new_output( + audio_client: IAudioClient, + render_client: IAudioRenderClient, + audio_clock: IAudioClock, + stream_config: StreamConfig, + event_handle: HANDLE, + mut callback: Callback, + ) -> Self { + let eject_signal = Arc::new(AtomicBool::new(false)); + let join_handle = std::thread::spawn({ + let eject_signal = eject_signal.clone(); + move || { + set_thread_priority(); + + let _try = || loop { + if eject_signal.load(Ordering::Relaxed) { + break Ok(callback); + } + + // Get the number of available frames + let frames_available = unsafe { + let padding = audio_client.GetCurrentPadding()?; + stream_config.buffer_size_range.0.unwrap() as u32 - padding + }; + + unsafe { + let data = render_client.GetBuffer(frames_available)?; + + debug_assert!(!data.is_null()); + + let len = frames_available as usize * stream.bytes_per_frame as usize + / stream.sample_format.sample_size(); + let data: &mut [u8] = std::slice::from_raw_parts_mut(buffer, len); + let timestamp = + output_timestamp(stream, frames_available, stream_config.samplerate)?; + let context = AudioCallbackContext { + stream_config, + timestamp, + }; + let mut buffer = vec![0f32; (frames_available * stream_config.channels) as usize]; + let output = AudioOutput { + buffer: AudioMut::from_interleaved_mut( + &mut buffer, + stream_config.channels as usize, + ) + .unwrap(), + timestamp, + }; + callback.on_output_data(context, output); + + data.write_all() + + render_client.ReleaseBuffer(frames_available, 0)?; + } + }; + + _try() + } + }); + + WasapiStream { + audio_client, + stream_config, + event_handle, + join_handle, + _p: PhantomData::default(), } } } +fn set_thread_priority() { + unsafe { + let thread_id = Threading::GetCurrentThreadId(); + + let _ = Threading::SetThreadPriority( + HANDLE(thread_id as isize as _), + Threading::THREAD_PRIORITY_TIME_CRITICAL, + ); + } +} + fn get_device_name(device: &windows::Win32::Media::Audio::IMMDevice) -> Option { unsafe { // Open the device's property store. @@ -160,14 +421,15 @@ fn get_device_name(device: &windows::Win32::Media::Audio::IMMDevice) -> Option Option Option = OnceLock::new(); fn audio_device_enumerator() -> &'static AudioDeviceEnumerator { @@ -221,19 +484,19 @@ fn audio_device_enumerator() -> &'static AudioDeviceEnumerator { struct AudioDeviceEnumerator(Audio::IMMDeviceEnumerator); impl AudioDeviceEnumerator { - // Returns the default output device. - fn get_default_device(&self, device_type: DeviceType) -> Result, WasapiError> { + fn get_default_device( + &self, + device_type: DeviceType, + ) -> Result, WasapiError> { let data_flow = match device_type { DeviceType::Input => Audio::eCapture, DeviceType::Output => Audio::eRender, - _=> return Ok(None), + _ => return Ok(None), }; unsafe { - let device = self - .0 - .GetDefaultAudioEndpoint(data_flow, Audio::eConsole)?; + let device = self.0.GetDefaultAudioEndpoint(data_flow, Audio::eConsole)?; Ok(Some(WasapiDevice::new(device, DeviceType::Output))) } @@ -241,7 +504,6 @@ impl AudioDeviceEnumerator { // Returns a chained iterator of output and input devices. fn get_device_list(&self) -> Result, WasapiError> { - // Create separate collections for output and input devices and then chain them. unsafe { let output_collection = self @@ -308,4 +570,45 @@ impl Iterator for WasapiDeviceList { let rest = (self.total_count - self.next_item) as usize; (rest, Some(rest)) } +} + +fn buffer_size_to_duration(buffer_size: usize, sample_rate: u32) -> i64 { + (buffer_size as i64 / sample_rate as i64) * (1_000_000_000 / 100) +} + +fn buffer_duration_to_frames(buffer_duration: i64, sample_rate: u32) -> i64 { + (buffer_duration * sample_rate as i64) / (100 / 1_000_000_000) +} + +/// Convert the given duration in frames at the given sample rate to a `std::time::Duration`. +fn frames_to_duration(frames: u32, samplerate: f64) -> std::time::Duration { + let secsf = frames as f64 / samplerate; + let secs = secsf as u64; + let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32; + std::time::Duration::new(secs, nanos) +} + +fn stream_instant(audio_clock: &IAudioClock) -> Result { + let mut position: u64 = 0; + let mut qpc_position: u64 = 0; + unsafe { + audio_clock + .GetPosition(&mut position, Some(&mut qpc_position))?; + }; + // The `qpc_position` is in 100 nanosecond units. Convert it to nanoseconds. + let qpc_nanos = qpc_position as u64 * 100; + let instant = Duration::from_nanos(qpc_nanos); + Ok(instant) +} + +fn output_timestamp( + audio_clock: &IAudioClock, + frames_available: u32, + samplerate: f64, +) -> Result { + let callback = stream_instant(audio_clock)?; + let buffer_duration = frames_to_duration(frames_available, samplerate); + let playback = callback + .add(buffer_duration); + Ok(Timestamp::from_duration(samplerate, playback)) } \ No newline at end of file diff --git a/src/channel_map.rs b/src/channel_map.rs index 0beeadf..8ad588d 100644 --- a/src/channel_map.rs +++ b/src/channel_map.rs @@ -7,10 +7,10 @@ pub trait Bitset: Sized { fn set_index(&mut self, index: usize, value: bool); - fn indices(&self) -> impl IntoIterator { + fn indices(&self) -> impl IntoIterator { (0..self.capacity()).filter_map(|i| self.get_index(i).then_some(i)) } - + fn count(&self) -> usize { self.indices().into_iter().count() } @@ -19,8 +19,8 @@ pub trait Bitset: Sized { self.set_index(index, value); self } - - fn with_indices(mut self, indices: impl IntoIterator) -> Self { + + fn with_indices(mut self, indices: impl IntoIterator) -> Self { for ix in indices { self.set_index(ix, true); } @@ -65,13 +65,13 @@ fn get_inner_bitset_at(arr: &[T], mut index: usize) -> Option<(usize, let mut acc = 0; move |(i, b)| { return match index.checked_sub(b.capacity()) { - None => Some((i, index)), + None => Some((i, index)), Some(v) => { index = v; acc += b.capacity(); None } - } + }; } }) } @@ -151,4 +151,4 @@ mod test { let result = HashSet::<_, RandomState>::from_iter(bitrate.indices()); assert_eq!(HashSet::from_iter([0, 2, 5, 12, 14, 16]), result); } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 65e9833..7d41769 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,8 @@ use crate::timestamp::Timestamp; pub mod audio_buffer; pub mod backends; pub mod channel_map; -pub mod timestamp; pub mod prelude; +pub mod timestamp; /// Audio drivers provide access to the inputs and outputs of physical devices. /// Several drivers might provide the same accesses, some sharing it with other applications, @@ -108,9 +108,7 @@ impl SendEverywhereButOnWeb for T {} pub trait AudioInputDevice: AudioDevice { type StreamHandle: AudioStreamHandle; - fn create_input_stream< - Callback: SendEverywhereButOnWeb + AudioInputCallback, - >( + fn create_input_stream( &self, stream_config: StreamConfig, callback: Callback, @@ -120,9 +118,7 @@ pub trait AudioInputDevice: AudioDevice { pub trait AudioOutputDevice: AudioDevice { type StreamHandle: AudioStreamHandle; - fn create_output_stream< - Callback: SendEverywhereButOnWeb + AudioOutputCallback, - >( + fn create_output_stream( &self, stream_config: StreamConfig, callback: Callback, diff --git a/src/prelude.rs b/src/prelude.rs index cf86cd7..bea0c8c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,2 +1,2 @@ +pub use crate::backends::*; pub use crate::*; -pub use crate::backends::*; \ No newline at end of file