Skip to main content

cpal/
device_description.rs

1//! Device metadata and description types.
2//!
3//! This module provides structured information about audio devices including manufacturer,
4//! device type, interface type, and connection details. Not all backends provide complete
5//! information - availability depends on platform capabilities.
6
7use std::fmt;
8
9use crate::ChannelCount;
10
11/// Describes an audio device with structured metadata.
12///
13/// This type provides structured information about an audio device beyond just its name.
14/// Availability depends on the host implementation and platform capabilities.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct DeviceDescription {
17    /// Human-readable device name
18    name: String,
19
20    /// Device manufacturer or vendor name
21    manufacturer: Option<String>,
22
23    /// Driver name
24    driver: Option<String>,
25
26    /// Categorization of device type
27    device_type: DeviceType,
28
29    /// Connection/interface type
30    interface_type: InterfaceType,
31
32    /// Direction: input, output, or duplex
33    direction: DeviceDirection,
34
35    /// Physical address or connection identifier
36    address: Option<String>,
37
38    /// Additional description lines with non-structured, detailed information.
39    extended: Vec<String>,
40}
41
42/// Categorization of audio device types.
43///
44/// This describes the kind of audio device (speaker, microphone, headset, etc.)
45/// regardless of how it connects to the system.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
47#[non_exhaustive]
48pub enum DeviceType {
49    /// Speaker (built-in or external)
50    Speaker,
51
52    /// Microphone (built-in or external)
53    Microphone,
54
55    /// Headphones (audio output only)
56    Headphones,
57
58    /// Headset (combined headphones + microphone)
59    Headset,
60
61    /// Earpiece (phone-style speaker, typically for voice calls)
62    Earpiece,
63
64    /// Handset (telephone-style handset with speaker and microphone)
65    Handset,
66
67    /// Hearing aid device
68    HearingAid,
69
70    /// Docking station audio
71    Dock,
72
73    /// Radio/TV tuner
74    Tuner,
75
76    /// Virtual/loopback device (software audio routing)
77    Virtual,
78
79    /// Unknown or unclassified device type
80    #[default]
81    Unknown,
82}
83
84/// How the device connects to the system (interface/connection type).
85///
86/// This describes the physical or logical connection between the audio device
87/// and the computer system.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
89#[non_exhaustive]
90pub enum InterfaceType {
91    /// Built-in to the system (integrated audio chipset)
92    BuiltIn,
93
94    /// USB connection
95    Usb,
96
97    /// Bluetooth wireless connection
98    Bluetooth,
99
100    /// PCI or PCIe card (internal sound card)
101    Pci,
102
103    /// FireWire connection (IEEE 1394)
104    FireWire,
105
106    /// Thunderbolt connection
107    Thunderbolt,
108
109    /// HDMI connection
110    Hdmi,
111
112    /// Line-level analog connection (line in/out, aux)
113    Line,
114
115    /// S/PDIF digital audio interface
116    Spdif,
117
118    /// Network connection (Dante, AVB, AirPlay, IP audio, etc.)
119    Network,
120
121    /// Virtual/loopback connection (software audio routing, not physical hardware)
122    Virtual,
123
124    /// DisplayPort audio
125    DisplayPort,
126
127    /// Aggregate device (combines multiple devices)
128    Aggregate,
129
130    /// Unknown connection type
131    #[default]
132    Unknown,
133}
134
135/// The direction(s) that a device supports.
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
137#[non_exhaustive]
138pub enum DeviceDirection {
139    /// Input only (capture/recording)
140    Input,
141
142    /// Output only (playback/rendering)
143    Output,
144
145    /// Both input and output
146    Duplex,
147
148    /// Direction unknown or not yet determined
149    #[default]
150    Unknown,
151}
152
153impl DeviceDescription {
154    /// Returns the human-readable device name.
155    ///
156    /// This is always available and is the primary user-facing identifier.
157    pub fn name(&self) -> &str {
158        &self.name
159    }
160
161    /// Returns the manufacturer/vendor name if available.
162    pub fn manufacturer(&self) -> Option<&str> {
163        self.manufacturer.as_deref()
164    }
165
166    /// Returns the driver name if available.
167    pub fn driver(&self) -> Option<&str> {
168        self.driver.as_deref()
169    }
170
171    /// Returns the device type categorization.
172    pub fn device_type(&self) -> DeviceType {
173        self.device_type
174    }
175
176    /// Returns the interface/connection type.
177    pub fn interface_type(&self) -> InterfaceType {
178        self.interface_type
179    }
180
181    /// Returns the device direction.
182    pub fn direction(&self) -> DeviceDirection {
183        self.direction
184    }
185
186    /// Returns whether this device supports audio input (capture).
187    ///
188    /// This is a convenience method that checks if direction is `Input` or `Duplex`.
189    pub fn supports_input(&self) -> bool {
190        matches!(
191            self.direction,
192            DeviceDirection::Input | DeviceDirection::Duplex
193        )
194    }
195
196    /// Returns whether this device supports audio output (playback).
197    ///
198    /// This is a convenience method that checks if direction is `Output` or `Duplex`.
199    pub fn supports_output(&self) -> bool {
200        matches!(
201            self.direction,
202            DeviceDirection::Output | DeviceDirection::Duplex
203        )
204    }
205
206    /// Returns the physical address or connection identifier if available.
207    pub fn address(&self) -> Option<&str> {
208        self.address.as_deref()
209    }
210
211    /// Returns additional description lines with detailed information.
212    pub fn extended(&self) -> &[String] {
213        &self.extended
214    }
215}
216
217impl fmt::Display for DeviceDescription {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        write!(f, "{}", self.name)?;
220
221        if let Some(mfr) = &self.manufacturer {
222            write!(f, " ({})", mfr)?;
223        }
224
225        if self.device_type != DeviceType::Unknown {
226            write!(f, " [{}]", self.device_type)?;
227        }
228
229        if self.interface_type != InterfaceType::Unknown {
230            write!(f, " via {}", self.interface_type)?;
231        }
232
233        Ok(())
234    }
235}
236
237impl fmt::Display for DeviceType {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        match self {
240            DeviceType::Speaker => write!(f, "Speaker"),
241            DeviceType::Microphone => write!(f, "Microphone"),
242            DeviceType::Headphones => write!(f, "Headphones"),
243            DeviceType::Headset => write!(f, "Headset"),
244            DeviceType::Earpiece => write!(f, "Earpiece"),
245            DeviceType::Handset => write!(f, "Handset"),
246            DeviceType::HearingAid => write!(f, "Hearing Aid"),
247            DeviceType::Dock => write!(f, "Dock"),
248            DeviceType::Tuner => write!(f, "Tuner"),
249            DeviceType::Virtual => write!(f, "Virtual"),
250            _ => write!(f, "Unknown"),
251        }
252    }
253}
254
255impl fmt::Display for InterfaceType {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        match self {
258            InterfaceType::BuiltIn => write!(f, "Built-in"),
259            InterfaceType::Usb => write!(f, "USB"),
260            InterfaceType::Bluetooth => write!(f, "Bluetooth"),
261            InterfaceType::Pci => write!(f, "PCI"),
262            InterfaceType::FireWire => write!(f, "FireWire"),
263            InterfaceType::Thunderbolt => write!(f, "Thunderbolt"),
264            InterfaceType::Hdmi => write!(f, "HDMI"),
265            InterfaceType::Line => write!(f, "Line"),
266            InterfaceType::Spdif => write!(f, "S/PDIF"),
267            InterfaceType::Network => write!(f, "Network"),
268            InterfaceType::Virtual => write!(f, "Virtual"),
269            InterfaceType::DisplayPort => write!(f, "DisplayPort"),
270            InterfaceType::Aggregate => write!(f, "Aggregate"),
271            _ => write!(f, "Unknown"),
272        }
273    }
274}
275
276impl fmt::Display for DeviceDirection {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        match self {
279            DeviceDirection::Input => write!(f, "Input"),
280            DeviceDirection::Output => write!(f, "Output"),
281            DeviceDirection::Duplex => write!(f, "Duplex"),
282            _ => write!(f, "Unknown"),
283        }
284    }
285}
286
287/// Builder for constructing a `DeviceDescription`.
288///
289/// This is primarily used by host implementations and custom hosts
290/// to gradually build up device descriptions with available metadata.
291#[derive(Debug, Clone)]
292pub struct DeviceDescriptionBuilder {
293    name: String,
294    manufacturer: Option<String>,
295    driver: Option<String>,
296    device_type: DeviceType,
297    interface_type: InterfaceType,
298    direction: DeviceDirection,
299    address: Option<String>,
300    extended: Vec<String>,
301}
302
303impl DeviceDescriptionBuilder {
304    /// Creates a new builder with the device name (required).
305    pub fn new(name: impl Into<String>) -> Self {
306        Self {
307            name: name.into(),
308            manufacturer: None,
309            driver: None,
310            device_type: DeviceType::default(),
311            interface_type: InterfaceType::default(),
312            direction: DeviceDirection::default(),
313            address: None,
314            extended: Vec::new(),
315        }
316    }
317
318    /// Sets the manufacturer name.
319    pub fn manufacturer(mut self, manufacturer: impl Into<String>) -> Self {
320        self.manufacturer = Some(manufacturer.into());
321        self
322    }
323
324    /// Sets the driver name.
325    pub fn driver(mut self, driver: impl Into<String>) -> Self {
326        self.driver = Some(driver.into());
327        self
328    }
329
330    /// Sets the device type.
331    pub fn device_type(mut self, device_type: DeviceType) -> Self {
332        self.device_type = device_type;
333        self
334    }
335
336    /// Sets the interface type.
337    pub fn interface_type(mut self, interface_type: InterfaceType) -> Self {
338        self.interface_type = interface_type;
339        self
340    }
341
342    /// Sets the device direction.
343    pub fn direction(mut self, direction: DeviceDirection) -> Self {
344        self.direction = direction;
345        self
346    }
347
348    /// Sets the physical address.
349    pub fn address(mut self, address: impl Into<String>) -> Self {
350        self.address = Some(address.into());
351        self
352    }
353
354    /// Sets the description lines.
355    pub fn extended(mut self, lines: Vec<String>) -> Self {
356        self.extended = lines;
357        self
358    }
359
360    /// Adds a single description line.
361    pub fn add_extended_line(mut self, line: impl Into<String>) -> Self {
362        self.extended.push(line.into());
363        self
364    }
365
366    /// Builds the [`DeviceDescription`].
367    pub fn build(self) -> DeviceDescription {
368        DeviceDescription {
369            name: self.name,
370            manufacturer: self.manufacturer,
371            driver: self.driver,
372            device_type: self.device_type,
373            interface_type: self.interface_type,
374            direction: self.direction,
375            address: self.address,
376            extended: self.extended,
377        }
378    }
379}
380
381/// Determines device direction from input/output capabilities.
382pub(crate) fn direction_from_caps(has_input: bool, has_output: bool) -> DeviceDirection {
383    match (has_input, has_output) {
384        (true, true) => DeviceDirection::Duplex,
385        (true, false) => DeviceDirection::Input,
386        (false, true) => DeviceDirection::Output,
387        (false, false) => DeviceDirection::Unknown,
388    }
389}
390
391/// Determines device direction from input/output channel counts.
392#[allow(dead_code)]
393pub(crate) fn direction_from_counts(
394    input_channels: Option<ChannelCount>,
395    output_channels: Option<ChannelCount>,
396) -> DeviceDirection {
397    let has_input = input_channels.map(|n| n > 0).unwrap_or(false);
398    let has_output = output_channels.map(|n| n > 0).unwrap_or(false);
399    direction_from_caps(has_input, has_output)
400}