diff --git a/examples/microphone.rs b/examples/microphone.rs index 003ee348..dee5b9b5 100644 --- a/examples/microphone.rs +++ b/examples/microphone.rs @@ -33,7 +33,7 @@ fn ask_sink_id() -> String { let input = std::io::stdin().lines().next().unwrap().unwrap(); match input.trim() { - "0" => "none".to_string(), + "0" => "".to_string(), i => i.to_string(), } } diff --git a/src/io/cpal.rs b/src/io/cpal.rs index 08e6fb8b..35e90d1c 100644 --- a/src/io/cpal.rs +++ b/src/io/cpal.rs @@ -8,6 +8,7 @@ use cpal::{ BuildStreamError, Device, OutputCallbackInfo, SampleFormat, Stream, StreamConfig, SupportedBufferSize, }; +use crossbeam_channel::Receiver; use super::{AudioBackendManager, RenderThreadInit}; @@ -18,8 +19,6 @@ use crate::media_devices::{MediaDeviceInfo, MediaDeviceInfoKind}; use crate::render::RenderThread; use crate::{AtomicF64, MAX_CHANNELS}; -use crossbeam_channel::Receiver; - // I doubt this construct is entirely safe. Stream is not Send/Sync (probably for a good reason) so // it should be managed from a single thread instead. // @@ -403,29 +402,49 @@ impl AudioBackendManager for CpalBackend { { let host = get_host(); - let input_devices = host - .input_devices() - .unwrap() - .map(|d| (d, MediaDeviceInfoKind::AudioInput)); - - let output_devices = host - .output_devices() - .unwrap() - .map(|d| (d, MediaDeviceInfoKind::AudioOutput)); - - input_devices - .chain(output_devices) - .enumerate() - .map(|(index, (device, kind))| { - MediaDeviceInfo::new( - (index + 1).to_string(), - None, + let input_devices = host.input_devices().unwrap().map(|d| { + let num_channels = d.default_input_config().unwrap().channels(); + (d, MediaDeviceInfoKind::AudioInput, num_channels) + }); + + let output_devices = host.output_devices().unwrap().map(|d| { + let num_channels = d.default_output_config().unwrap().channels(); + (d, MediaDeviceInfoKind::AudioOutput, num_channels) + }); + + // cf. https://github.com/orottier/web-audio-api-rs/issues/356 + let mut list = Vec::::new(); + + for (device, kind, num_channels) in input_devices.chain(output_devices) { + let mut index = 0; + + loop { + let device_id = crate::media_devices::DeviceId::as_string( kind, + "cpal".to_string(), device.name().unwrap(), - Box::new(device), - ) - }) - .collect() + num_channels, + index, + ); + + if !list.iter().any(|d| d.device_id() == device_id) { + let device = MediaDeviceInfo::new( + device_id, + None, + kind, + device.name().unwrap(), + Box::new(device), + ); + + list.push(device); + break; + } else { + index += 1; + } + } + } + + list } } diff --git a/src/io/cubeb.rs b/src/io/cubeb.rs index ad95f833..370559a0 100644 --- a/src/io/cubeb.rs +++ b/src/io/cubeb.rs @@ -394,46 +394,47 @@ impl AudioBackendManager for CubebBackend { where Self: Sized, { - let mut index = 0; + let context = Context::init(None, None).unwrap(); - let mut inputs: Vec = Context::init(None, None) - .unwrap() - .enumerate_devices(DeviceType::INPUT) - .unwrap() - .iter() - .map(|d| { - index += 1; - - MediaDeviceInfo::new( - format!("{}", index), - d.group_id().map(str::to_string), - MediaDeviceInfoKind::AudioInput, - d.friendly_name().unwrap().into(), - Box::new(d.devid()), - ) - }) - .collect(); + let inputs = context.enumerate_devices(DeviceType::INPUT).unwrap(); + let input_devices = inputs.iter().map(|d| (d, MediaDeviceInfoKind::AudioInput)); - let mut outputs: Vec = Context::init(None, None) - .unwrap() - .enumerate_devices(DeviceType::OUTPUT) - .unwrap() + let outputs = context.enumerate_devices(DeviceType::OUTPUT).unwrap(); + let output_devices = outputs .iter() - .map(|d| { - index += 1; - - MediaDeviceInfo::new( - format!("{}", index), - d.group_id().map(str::to_string), - MediaDeviceInfoKind::AudioOutput, - d.friendly_name().unwrap().into(), - Box::new(d.devid()), - ) - }) - .collect(); - - inputs.append(&mut outputs); + .map(|d| (d, MediaDeviceInfoKind::AudioOutput)); + + let mut list = Vec::::new(); + + for (device, kind) in input_devices.chain(output_devices) { + let mut index = 0; + + loop { + let device_id = crate::media_devices::DeviceId::as_string( + kind, + "cubeb".to_string(), + device.friendly_name().unwrap().into(), + device.max_channels().try_into().unwrap(), + index, + ); + + if !list.iter().any(|d| d.device_id() == device_id) { + let device = MediaDeviceInfo::new( + device_id, + device.group_id().map(str::to_string), + kind, + device.friendly_name().unwrap().into(), + Box::new(device.devid()), + ); + + list.push(device); + break; + } else { + index += 1; + } + } + } - inputs + list } } diff --git a/src/media_devices/mod.rs b/src/media_devices/mod.rs index 33e946db..0ce54326 100644 --- a/src/media_devices/mod.rs +++ b/src/media_devices/mod.rs @@ -4,6 +4,9 @@ //! //! +use rustc_hash::FxHasher; +use std::hash::{Hash, Hasher}; + use crate::context::{AudioContextLatencyCategory, AudioContextOptions}; use crate::media_streams::MediaStream; @@ -24,8 +27,41 @@ pub fn enumerate_devices_sync() -> Vec { crate::io::enumerate_devices_sync() } +// Internal struct to derive a stable id for a given input / output device +// cf. https://github.com/orottier/web-audio-api-rs/issues/356 +#[derive(Hash)] +pub(crate) struct DeviceId { + kind: MediaDeviceInfoKind, + host: String, + device_name: String, + num_channels: u16, + index: u8, +} + +impl DeviceId { + pub(crate) fn as_string( + kind: MediaDeviceInfoKind, + host: String, + device_name: String, + num_channels: u16, + index: u8, + ) -> String { + let device_info = Self { + kind, + host, + device_name, + num_channels, + index, + }; + + let mut hasher = FxHasher::default(); + device_info.hash(&mut hasher); + format!("{}", hasher.finish()) + } +} + /// Describes input/output type of a media device -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum MediaDeviceInfoKind { VideoInput, AudioInput,