Windows port Phase 2: cpal device enumeration #4
+1
-1
@@ -2,7 +2,7 @@ use crate::core::{CoreController, messages::{CoreCommand, UiEvent}};
|
|||||||
use crate::network::PeerState;
|
use crate::network::PeerState;
|
||||||
use crate::notify::{self, Sound};
|
use crate::notify::{self, Sound};
|
||||||
use crate::audio::eq::{EqSettings, EQ_GAIN_DB_MAX, EQ_GAIN_DB_MIN};
|
use crate::audio::eq::{EqSettings, EQ_GAIN_DB_MAX, EQ_GAIN_DB_MIN};
|
||||||
use crate::audio::pw_cli::{AudioDevice, enumerate_audio_devices};
|
use crate::audio::{AudioDevice, enumerate_audio_devices};
|
||||||
use crate::config::{AppConfig, NetworkMode, RecordingMode, RoomLayout};
|
use crate::config::{AppConfig, NetworkMode, RecordingMode, RoomLayout};
|
||||||
use crate::hotkeys::{format_binding, HotkeyAction, HotkeyContext, KeyBinding};
|
use crate::hotkeys::{format_binding, HotkeyAction, HotkeyContext, KeyBinding};
|
||||||
use crate::presence::PresenceMode;
|
use crate::presence::PresenceMode;
|
||||||
|
|||||||
+44
-1
@@ -42,7 +42,7 @@ use ringbuf::{
|
|||||||
HeapRb,
|
HeapRb,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{AudioBackend, AudioError, PLAYBACK_CHANNELS, PLAYBACK_TARGET_SAMPLES};
|
use super::{AudioBackend, AudioDevice, AudioError, PLAYBACK_CHANNELS, PLAYBACK_TARGET_SAMPLES};
|
||||||
|
|
||||||
/// The one sample rate the pipeline supports (Opus + the 20 ms frame).
|
/// The one sample rate the pipeline supports (Opus + the 20 ms frame).
|
||||||
const SAMPLE_RATE: u32 = 48_000;
|
const SAMPLE_RATE: u32 = 48_000;
|
||||||
@@ -143,6 +143,49 @@ impl AudioBackend for CpalBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Device enumeration (for the settings device pickers)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Enumerate WASAPI input/output devices via cpal, sorted by description to match
|
||||||
|
/// the PipeWire backend's stable UI ordering.
|
||||||
|
///
|
||||||
|
/// cpal exposes a single friendly name per device, which is also what [`resolve`]
|
||||||
|
/// matches `target_node` against — so `name` and `description` are the same string
|
||||||
|
/// and a saved selection round-trips. Note: WASAPI device names are less stable
|
||||||
|
/// across driver/endpoint changes than PipeWire node names, so a saved device may
|
||||||
|
/// not always be found again; selection then falls back to the system default.
|
||||||
|
pub fn enumerate_audio_devices() -> Vec<AudioDevice> {
|
||||||
|
let host = cpal::default_host();
|
||||||
|
let mut devices = Vec::new();
|
||||||
|
|
||||||
|
if let Ok(inputs) = host.input_devices() {
|
||||||
|
for device in inputs {
|
||||||
|
if let Ok(name) = device.name() {
|
||||||
|
devices.push(AudioDevice {
|
||||||
|
description: name.clone(),
|
||||||
|
name,
|
||||||
|
is_input: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(outputs) = host.output_devices() {
|
||||||
|
for device in outputs {
|
||||||
|
if let Ok(name) = device.name() {
|
||||||
|
devices.push(AudioDevice {
|
||||||
|
description: name.clone(),
|
||||||
|
name,
|
||||||
|
is_input: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
devices.sort_by(|a, b| a.description.cmp(&b.description));
|
||||||
|
devices
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Device / config selection
|
// Device / config selection
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
+27
-2
@@ -66,9 +66,35 @@ pub mod pan;
|
|||||||
pub mod pipewire_impl;
|
pub mod pipewire_impl;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub mod cpal_impl;
|
pub mod cpal_impl;
|
||||||
|
#[cfg(unix)]
|
||||||
pub mod pw_cli;
|
pub mod pw_cli;
|
||||||
pub mod recorder;
|
pub mod recorder;
|
||||||
|
|
||||||
|
/// A selectable audio device for the input/output pickers. `name` is the stable
|
||||||
|
/// identifier the backend uses to request the device (`target_node`);
|
||||||
|
/// `description` is the human-facing label shown in the UI. The two may be equal
|
||||||
|
/// (cpal/WASAPI) or differ (PipeWire node name vs. description).
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct AudioDevice {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub is_input: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for AudioDevice {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enumerate audio input/output devices for the pickers (sorted by description),
|
||||||
|
// returning the same `AudioDevice` shape regardless of platform: PipeWire
|
||||||
|
// (`pw-cli`) on Linux, cpal/WASAPI on Windows.
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub use pw_cli::enumerate_audio_devices;
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub use cpal_impl::enumerate_audio_devices;
|
||||||
|
|
||||||
/// The audio backend implementation for the current platform.
|
/// The audio backend implementation for the current platform.
|
||||||
///
|
///
|
||||||
/// The whole app constructs and threads this alias (via
|
/// The whole app constructs and threads this alias (via
|
||||||
@@ -77,8 +103,7 @@ pub mod recorder;
|
|||||||
/// [`AudioBackend`] trait, which is the only interface the core talks to.
|
/// [`AudioBackend`] trait, which is the only interface the core talks to.
|
||||||
///
|
///
|
||||||
/// - Linux/Unix → PipeWire ([`pipewire_impl::PipeWireBackend`]).
|
/// - Linux/Unix → PipeWire ([`pipewire_impl::PipeWireBackend`]).
|
||||||
/// - Windows → cpal/WASAPI ([`cpal_impl::CpalBackend`]); a no-op stub until the
|
/// - Windows → cpal/WASAPI ([`cpal_impl::CpalBackend`]).
|
||||||
/// Phase 1 capture/playback implementation lands.
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub type PlatformAudioBackend = pipewire_impl::PipeWireBackend;
|
pub type PlatformAudioBackend = pipewire_impl::PipeWireBackend;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
|||||||
+1
-13
@@ -1,18 +1,6 @@
|
|||||||
|
use super::AudioDevice;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct AudioDevice {
|
|
||||||
pub name: String,
|
|
||||||
pub description: String,
|
|
||||||
pub is_input: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for AudioDevice {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enumerate_audio_devices() -> Vec<AudioDevice> {
|
pub fn enumerate_audio_devices() -> Vec<AudioDevice> {
|
||||||
let output = Command::new("pw-cli")
|
let output = Command::new("pw-cli")
|
||||||
.arg("list-objects")
|
.arg("list-objects")
|
||||||
|
|||||||
Reference in New Issue
Block a user