From a69bb353fc25bf8822d450c73d63b29b6b75a7bf Mon Sep 17 00:00:00 2001 From: b-ma Date: Wed, 18 Jan 2023 08:40:05 +0100 Subject: [PATCH 1/4] fix(build-input): use default input device + align implementation on build_output --- src/io/cpal.rs | 88 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/src/io/cpal.rs b/src/io/cpal.rs index b918c002..6cceed9a 100644 --- a/src/io/cpal.rs +++ b/src/io/cpal.rs @@ -106,7 +106,8 @@ impl AudioBackendManager for CpalBackend { let supported = device .default_output_config() - .expect("error while querying configs"); + .expect("error while querying config"); + let mut prefered: StreamConfig = supported.clone().into(); // set specific sample rate if requested @@ -160,8 +161,11 @@ impl AudioBackendManager for CpalBackend { } Err(e) => { log::warn!("Output stream build failed with prefered config: {}", e); - sample_rate = supported.sample_rate().0 as f32; + let supported_config: StreamConfig = supported.clone().into(); + number_of_channels = usize::from(supported_config.channels); + sample_rate = supported_config.sample_rate.0 as f32; + log::debug!( "Attempt output stream with fallback config: {:?}", &supported_config @@ -202,76 +206,90 @@ impl AudioBackendManager for CpalBackend { where Self: Sized, { + // @todo - enable device selection, i.e. device_id from enumerate_devices + // see https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia + let host = cpal::default_host(); let device = host .default_input_device() .expect("no input device available"); + log::info!("Input device: {:?}", device.name()); - let mut supported_configs_range = device - .supported_input_configs() + let supported = device + .default_input_config() .expect("error while querying configs"); - let supported_config = supported_configs_range - .next() - .expect("no supported config?!") - .with_max_sample_rate(); + // clone the config, we may need to fall back on it later + let mut prefered: StreamConfig = supported.clone().into(); - let sample_format = supported_config.sample_format(); + // set specific sample rate if requested + if let Some(sample_rate) = options.sample_rate { + crate::assert_valid_sample_rate(sample_rate); + prefered.sample_rate.0 = sample_rate as u32; + } - // clone the config, we may need to fall back on it later - let default_config: StreamConfig = supported_config.clone().into(); + // always try to set a decent buffer size + let buffer_size = super::buffer_size_for_latency_category( + options.latency_hint, + prefered.sample_rate.0 as f32, + ) as u32; - // determine best buffer size. Spec requires RENDER_QUANTUM_SIZE, but that might not be available - let buffer_size: u32 = u32::try_from(RENDER_QUANTUM_SIZE).unwrap(); - let mut input_buffer_size = match supported_config.buffer_size() { - SupportedBufferSize::Range { min, .. } => buffer_size.max(*min), + let clamped_buffer_size: u32 = match supported.buffer_size() { SupportedBufferSize::Unknown => buffer_size, + SupportedBufferSize::Range { min, max } => buffer_size.clamp(*min, *max), }; - // make buffer_size always a multiple of RENDER_QUANTUM_SIZE, so we can still render piecewise with - // the desired number of frames. - input_buffer_size = (input_buffer_size + buffer_size - 1) / buffer_size * buffer_size; - let mut config: StreamConfig = supported_config.into(); - config.buffer_size = cpal::BufferSize::Fixed(input_buffer_size); - if let Some(sample_rate) = options.sample_rate { - config.sample_rate = CpalSampleRate(sample_rate as u32); - } + prefered.buffer_size = cpal::BufferSize::Fixed(clamped_buffer_size); - let sample_rate = config.sample_rate.0 as f32; - let channels = config.channels as usize; + let mut number_of_channels = usize::from(prefered.channels); + let mut sample_rate = prefered.sample_rate.0 as f32; let smoothing = 3; // todo, use buffering to smooth frame drops let (sender, mut receiver) = crossbeam_channel::bounded(smoothing); - let renderer = MicrophoneRender::new(channels, sample_rate, sender); + let renderer = MicrophoneRender::new(number_of_channels, sample_rate, sender); - let maybe_stream = spawn_input_stream(&device, sample_format, &config, renderer); - // our RENDER_QUANTUM_SIZEd config may not be supported, in that case, use the default config + let maybe_stream = spawn_input_stream( + &device, + supported.sample_format(), + &prefered, + renderer + ); + + // the required block size prefered config may not be supported, in that + // case, fallback the supported config let stream = match maybe_stream { Ok(stream) => stream, Err(e) => { log::warn!( "Output stream failed to build: {:?}, retry with default config {:?}", e, - default_config + prefered ); + let supported_config: StreamConfig = supported.clone().into(); + number_of_channels = usize::from(supported_config.channels); + sample_rate = supported_config.sample_rate.0 as f32; + // setup a new comms channel let (sender, receiver2) = crossbeam_channel::bounded(smoothing); receiver = receiver2; // overwrite earlier - let renderer = MicrophoneRender::new(channels, sample_rate, sender); - spawn_input_stream(&device, sample_format, &default_config, renderer) - .expect("Unable to spawn input stream with default config") + let renderer = MicrophoneRender::new(number_of_channels, sample_rate, sender); + + let spawned = spawn_input_stream( + &device, + supported.sample_format(), + &supported_config, + renderer + ); + spawned.expect("Unable to spawn input stream with default config") } }; // Required because some hosts don't play the stream automatically stream.play().expect("Input stream refused to play"); - let number_of_channels = usize::from(config.channels); - let sample_rate = config.sample_rate.0 as f32; - let backend = CpalBackend { stream: ThreadSafeClosableStream::new(stream), output_latency: Arc::new(AtomicF64::new(0.)), From 688a6000c1bfe55102f641b7bdffdbc648c55d59 Mon Sep 17 00:00:00 2001 From: b-ma Date: Wed, 18 Jan 2023 08:52:02 +0100 Subject: [PATCH 2/4] fix: remove ununsed imports --- src/io/cpal.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/io/cpal.rs b/src/io/cpal.rs index 6cceed9a..0755f217 100644 --- a/src/io/cpal.rs +++ b/src/io/cpal.rs @@ -1,17 +1,15 @@ //! Audio IO management API -use std::convert::TryFrom; use std::sync::Arc; use std::sync::Mutex; -use crate::{AtomicF64, RENDER_QUANTUM_SIZE}; - use cpal::{ traits::{DeviceTrait, HostTrait, StreamTrait}, - BuildStreamError, Device, OutputCallbackInfo, SampleFormat, SampleRate as CpalSampleRate, + BuildStreamError, Device, OutputCallbackInfo, SampleFormat, Stream, StreamConfig, SupportedBufferSize, }; use super::{AudioBackendManager, MediaDeviceInfo, MediaDeviceInfoKind, RenderThreadInit}; +use crate::{AtomicF64}; use crate::buffer::AudioBuffer; use crate::context::AudioContextOptions; use crate::media::MicrophoneRender; @@ -130,7 +128,7 @@ impl AudioBackendManager for CpalBackend { prefered.buffer_size = cpal::BufferSize::Fixed(clamped_buffer_size); let output_latency = Arc::new(AtomicF64::new(0.)); - let number_of_channels = usize::from(prefered.channels); + let mut number_of_channels = usize::from(prefered.channels); let mut sample_rate = prefered.sample_rate.0 as f32; let renderer = RenderThread::new( From 4a669943640b0725752b81638a6c2c3f86b4d623 Mon Sep 17 00:00:00 2001 From: b-ma Date: Wed, 18 Jan 2023 08:52:27 +0100 Subject: [PATCH 3/4] doc: simplify microphone example --- examples/microphone.rs | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/examples/microphone.rs b/examples/microphone.rs index bb49ee47..6fef132a 100644 --- a/examples/microphone.rs +++ b/examples/microphone.rs @@ -4,28 +4,33 @@ use web_audio_api::node::AudioNode; fn main() { env_logger::init(); - let context = AudioContext::default(); + let context = AudioContext::default(); let mic = Microphone::default(); - // register as media element in the audio context - let background = context.create_media_stream_source(mic.stream()); - // connect the node to the destination node (speakers) - background.connect(&context.destination()); - println!("Playback for 2 seconds"); - std::thread::sleep(std::time::Duration::from_secs(2)); + // create media stream source node with mic stream + let stream_source = context.create_media_stream_source(mic.stream()); + stream_source.connect(&context.destination()); + + loop { + std::thread::sleep(std::time::Duration::from_secs(2)); + } + + // note: uncomment to test controls over the mic instance, + // this is maybe not the desired public interface, see MediaDevices API - println!("Pause mic for 2 seconds"); - mic.suspend(); - std::thread::sleep(std::time::Duration::from_secs(2)); + // println!("Playback for 2 seconds"); + // std::thread::sleep(std::time::Duration::from_secs(2)); - println!("Resume mic for 2 seconds"); - mic.resume(); - std::thread::sleep(std::time::Duration::from_secs(2)); + // println!("Pause mic for 2 seconds"); + // mic.suspend(); + // std::thread::sleep(std::time::Duration::from_secs(2)); - // Closing the mic should halt the media stream source - println!("Close mic - halting stream"); - mic.close(); + // println!("Resume mic for 2 seconds"); + // mic.resume(); + // std::thread::sleep(std::time::Duration::from_secs(2)); - std::thread::sleep(std::time::Duration::from_secs(2)); + // // Closing the mic should halt the media stream source + // println!("Close mic - halting stream"); + // mic.close(); } From 8482135c84a0b38a4d5cf94c094c797e0a554e5d Mon Sep 17 00:00:00 2001 From: b-ma Date: Wed, 18 Jan 2023 08:54:04 +0100 Subject: [PATCH 4/4] chore: cargo fmt --- src/io/cpal.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/io/cpal.rs b/src/io/cpal.rs index 0755f217..cb2e924b 100644 --- a/src/io/cpal.rs +++ b/src/io/cpal.rs @@ -4,16 +4,16 @@ use std::sync::Mutex; use cpal::{ traits::{DeviceTrait, HostTrait, StreamTrait}, - BuildStreamError, Device, OutputCallbackInfo, SampleFormat, - Stream, StreamConfig, SupportedBufferSize, + BuildStreamError, Device, OutputCallbackInfo, SampleFormat, Stream, StreamConfig, + SupportedBufferSize, }; use super::{AudioBackendManager, MediaDeviceInfo, MediaDeviceInfoKind, RenderThreadInit}; -use crate::{AtomicF64}; use crate::buffer::AudioBuffer; use crate::context::AudioContextOptions; use crate::media::MicrophoneRender; use crate::render::RenderThread; +use crate::AtomicF64; use crossbeam_channel::Receiver; @@ -247,12 +247,8 @@ impl AudioBackendManager for CpalBackend { let (sender, mut receiver) = crossbeam_channel::bounded(smoothing); let renderer = MicrophoneRender::new(number_of_channels, sample_rate, sender); - let maybe_stream = spawn_input_stream( - &device, - supported.sample_format(), - &prefered, - renderer - ); + let maybe_stream = + spawn_input_stream(&device, supported.sample_format(), &prefered, renderer); // the required block size prefered config may not be supported, in that // case, fallback the supported config @@ -279,7 +275,7 @@ impl AudioBackendManager for CpalBackend { &device, supported.sample_format(), &supported_config, - renderer + renderer, ); spawned.expect("Unable to spawn input stream with default config") }