Skip to content

Commit 0f5250c

Browse files
authored
Merge pull request #360 from b-ma/fix/device-id
Fix: improve device id
2 parents 00a2606 + b11f762 commit 0f5250c

File tree

4 files changed

+118
-62
lines changed

4 files changed

+118
-62
lines changed

examples/microphone.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ fn ask_sink_id() -> String {
3333

3434
let input = std::io::stdin().lines().next().unwrap().unwrap();
3535
match input.trim() {
36-
"0" => "none".to_string(),
36+
"0" => "".to_string(),
3737
i => i.to_string(),
3838
}
3939
}

src/io/cpal.rs

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use cpal::{
88
BuildStreamError, Device, OutputCallbackInfo, SampleFormat, Stream, StreamConfig,
99
SupportedBufferSize,
1010
};
11+
use crossbeam_channel::Receiver;
1112

1213
use super::{AudioBackendManager, RenderThreadInit};
1314

@@ -18,8 +19,6 @@ use crate::media_devices::{MediaDeviceInfo, MediaDeviceInfoKind};
1819
use crate::render::RenderThread;
1920
use crate::{AtomicF64, MAX_CHANNELS};
2021

21-
use crossbeam_channel::Receiver;
22-
2322
// I doubt this construct is entirely safe. Stream is not Send/Sync (probably for a good reason) so
2423
// it should be managed from a single thread instead.
2524
// <https://github.com/orottier/web-audio-api-rs/issues/357>
@@ -403,29 +402,49 @@ impl AudioBackendManager for CpalBackend {
403402
{
404403
let host = get_host();
405404

406-
let input_devices = host
407-
.input_devices()
408-
.unwrap()
409-
.map(|d| (d, MediaDeviceInfoKind::AudioInput));
410-
411-
let output_devices = host
412-
.output_devices()
413-
.unwrap()
414-
.map(|d| (d, MediaDeviceInfoKind::AudioOutput));
415-
416-
input_devices
417-
.chain(output_devices)
418-
.enumerate()
419-
.map(|(index, (device, kind))| {
420-
MediaDeviceInfo::new(
421-
(index + 1).to_string(),
422-
None,
405+
let input_devices = host.input_devices().unwrap().map(|d| {
406+
let num_channels = d.default_input_config().unwrap().channels();
407+
(d, MediaDeviceInfoKind::AudioInput, num_channels)
408+
});
409+
410+
let output_devices = host.output_devices().unwrap().map(|d| {
411+
let num_channels = d.default_output_config().unwrap().channels();
412+
(d, MediaDeviceInfoKind::AudioOutput, num_channels)
413+
});
414+
415+
// cf. https://github.com/orottier/web-audio-api-rs/issues/356
416+
let mut list = Vec::<MediaDeviceInfo>::new();
417+
418+
for (device, kind, num_channels) in input_devices.chain(output_devices) {
419+
let mut index = 0;
420+
421+
loop {
422+
let device_id = crate::media_devices::DeviceId::as_string(
423423
kind,
424+
"cpal".to_string(),
424425
device.name().unwrap(),
425-
Box::new(device),
426-
)
427-
})
428-
.collect()
426+
num_channels,
427+
index,
428+
);
429+
430+
if !list.iter().any(|d| d.device_id() == device_id) {
431+
let device = MediaDeviceInfo::new(
432+
device_id,
433+
None,
434+
kind,
435+
device.name().unwrap(),
436+
Box::new(device),
437+
);
438+
439+
list.push(device);
440+
break;
441+
} else {
442+
index += 1;
443+
}
444+
}
445+
}
446+
447+
list
429448
}
430449
}
431450

src/io/cubeb.rs

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -394,46 +394,47 @@ impl AudioBackendManager for CubebBackend {
394394
where
395395
Self: Sized,
396396
{
397-
let mut index = 0;
397+
let context = Context::init(None, None).unwrap();
398398

399-
let mut inputs: Vec<MediaDeviceInfo> = Context::init(None, None)
400-
.unwrap()
401-
.enumerate_devices(DeviceType::INPUT)
402-
.unwrap()
403-
.iter()
404-
.map(|d| {
405-
index += 1;
406-
407-
MediaDeviceInfo::new(
408-
format!("{}", index),
409-
d.group_id().map(str::to_string),
410-
MediaDeviceInfoKind::AudioInput,
411-
d.friendly_name().unwrap().into(),
412-
Box::new(d.devid()),
413-
)
414-
})
415-
.collect();
399+
let inputs = context.enumerate_devices(DeviceType::INPUT).unwrap();
400+
let input_devices = inputs.iter().map(|d| (d, MediaDeviceInfoKind::AudioInput));
416401

417-
let mut outputs: Vec<MediaDeviceInfo> = Context::init(None, None)
418-
.unwrap()
419-
.enumerate_devices(DeviceType::OUTPUT)
420-
.unwrap()
402+
let outputs = context.enumerate_devices(DeviceType::OUTPUT).unwrap();
403+
let output_devices = outputs
421404
.iter()
422-
.map(|d| {
423-
index += 1;
424-
425-
MediaDeviceInfo::new(
426-
format!("{}", index),
427-
d.group_id().map(str::to_string),
428-
MediaDeviceInfoKind::AudioOutput,
429-
d.friendly_name().unwrap().into(),
430-
Box::new(d.devid()),
431-
)
432-
})
433-
.collect();
434-
435-
inputs.append(&mut outputs);
405+
.map(|d| (d, MediaDeviceInfoKind::AudioOutput));
406+
407+
let mut list = Vec::<MediaDeviceInfo>::new();
408+
409+
for (device, kind) in input_devices.chain(output_devices) {
410+
let mut index = 0;
411+
412+
loop {
413+
let device_id = crate::media_devices::DeviceId::as_string(
414+
kind,
415+
"cubeb".to_string(),
416+
device.friendly_name().unwrap().into(),
417+
device.max_channels().try_into().unwrap(),
418+
index,
419+
);
420+
421+
if !list.iter().any(|d| d.device_id() == device_id) {
422+
let device = MediaDeviceInfo::new(
423+
device_id,
424+
device.group_id().map(str::to_string),
425+
kind,
426+
device.friendly_name().unwrap().into(),
427+
Box::new(device.devid()),
428+
);
429+
430+
list.push(device);
431+
break;
432+
} else {
433+
index += 1;
434+
}
435+
}
436+
}
436437

437-
inputs
438+
list
438439
}
439440
}

src/media_devices/mod.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
//!
55
//! <https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices>
66
7+
use rustc_hash::FxHasher;
8+
use std::hash::{Hash, Hasher};
9+
710
use crate::context::{AudioContextLatencyCategory, AudioContextOptions};
811
use crate::media_streams::MediaStream;
912

@@ -24,8 +27,41 @@ pub fn enumerate_devices_sync() -> Vec<MediaDeviceInfo> {
2427
crate::io::enumerate_devices_sync()
2528
}
2629

30+
// Internal struct to derive a stable id for a given input / output device
31+
// cf. https://github.com/orottier/web-audio-api-rs/issues/356
32+
#[derive(Hash)]
33+
pub(crate) struct DeviceId {
34+
kind: MediaDeviceInfoKind,
35+
host: String,
36+
device_name: String,
37+
num_channels: u16,
38+
index: u8,
39+
}
40+
41+
impl DeviceId {
42+
pub(crate) fn as_string(
43+
kind: MediaDeviceInfoKind,
44+
host: String,
45+
device_name: String,
46+
num_channels: u16,
47+
index: u8,
48+
) -> String {
49+
let device_info = Self {
50+
kind,
51+
host,
52+
device_name,
53+
num_channels,
54+
index,
55+
};
56+
57+
let mut hasher = FxHasher::default();
58+
device_info.hash(&mut hasher);
59+
format!("{}", hasher.finish())
60+
}
61+
}
62+
2763
/// Describes input/output type of a media device
28-
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
64+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
2965
pub enum MediaDeviceInfoKind {
3066
VideoInput,
3167
AudioInput,

0 commit comments

Comments
 (0)