Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: improve device id #360

Merged
merged 6 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/microphone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
Expand Down
65 changes: 42 additions & 23 deletions src/io/cpal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use cpal::{
BuildStreamError, Device, OutputCallbackInfo, SampleFormat, Stream, StreamConfig,
SupportedBufferSize,
};
use crossbeam_channel::Receiver;

use super::{AudioBackendManager, RenderThreadInit};

Expand All @@ -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.
// <https://github.com/orottier/web-audio-api-rs/issues/357>
Expand Down Expand Up @@ -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::<MediaDeviceInfo>::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
}
}

Expand Down
75 changes: 38 additions & 37 deletions src/io/cubeb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<MediaDeviceInfo> = 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<MediaDeviceInfo> = 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::<MediaDeviceInfo>::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
}
}
38 changes: 37 additions & 1 deletion src/media_devices/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
//!
//! <https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices>

use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher};

use crate::context::{AudioContextLatencyCategory, AudioContextOptions};
use crate::media_streams::MediaStream;

Expand All @@ -24,8 +27,41 @@ pub fn enumerate_devices_sync() -> Vec<MediaDeviceInfo> {
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,
Expand Down