rtaudio/
device_info.rs

1use std::ffi::CStr;
2
3use crate::NativeFormats;
4
5#[cfg(all(feature = "log", not(feature = "tracing")))]
6use log::{error, warn};
7#[cfg(feature = "tracing")]
8use tracing::{error, warn};
9
10/// A unique identifier for a device for the current session.
11///
12/// Note, this is *NOT* gauranteed to persist across reboots.
13pub type SessionID = u32;
14
15/// A unique identifier for an audio device that persists across reboots.
16///
17/// This also contains the name of the device.
18#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct DeviceID {
21    /// The name of the device.
22    #[cfg_attr(feature = "serde", serde(default))]
23    pub name: String,
24    /// A unique identifier for the device in this current session.
25    /// (Note, this value is *NOT* gauranteed to persist across reboots.)
26    #[cfg_attr(feature = "serde", serde(default))]
27    pub session_id: SessionID,
28}
29
30impl DeviceID {
31    /// Get a human-editable ID as a single string for serialization in
32    /// a config file.
33    ///
34    /// The format is "{name}:{session_id}" (the name and session id
35    /// are separated by a colon).
36    pub fn as_serialized_string(&self) -> String {
37        format!("{}", self)
38    }
39
40    /// Retrieve the serialized device ID generated by
41    /// [`DeviceID::as_serialized_string`] or manually edited by a
42    /// user.
43    ///
44    /// The format is "{name}:{session_id}" (the name and session id
45    /// are separated by a colon).
46    pub fn from_serialized_string(s: &str) -> Self {
47        if let Some((name, session_id)) = s.rsplit_once(':') {
48            let session_id: u32 = match session_id.trim().parse() {
49                Ok(id) => id,
50                Err(e) => {
51                    #[cfg(not(any(feature = "tracing", feature = "log")))]
52                    let _ = e;
53
54                    #[cfg(any(feature = "tracing", feature = "log"))]
55                    warn!("Failed to parse session ID for audio device: {}", e);
56                    0
57                }
58            };
59
60            Self {
61                name: name.trim().to_string(),
62                session_id,
63            }
64        } else {
65            Self {
66                name: s.trim().to_string(),
67                session_id: 0,
68            }
69        }
70    }
71}
72
73impl std::fmt::Display for DeviceID {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        write!(f, "{}:{}", &self.name, self.session_id)
76    }
77}
78
79/// Queried information about a device.
80#[derive(Debug, Clone, PartialEq)]
81#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82pub struct DeviceInfo {
83    /// The unique identifier of this device.
84    pub id: DeviceID,
85    /// The number of output channels on this device.
86    pub output_channels: u32,
87    /// The number of input channels on this device.
88    pub input_channels: u32,
89    /// The number of duplex channels on this device.
90    pub duplex_channels: u32,
91
92    /// Whether or not this device is the default output device.
93    pub is_default_output: bool,
94    /// Whether or not this device is the default input device.
95    pub is_default_input: bool,
96
97    /// The native sample formats that this device supports. (bitflags)
98    ///
99    /// Note you can still start a stream with any format. RtAudio will
100    /// just automatically convert to/from the best native format.
101    pub native_formats: NativeFormats,
102
103    /// The device's preferred sample rate.
104    pub preferred_sample_rate: u32,
105    /// The available sample rates for this device.
106    pub sample_rates: Vec<u32>,
107}
108
109impl DeviceInfo {
110    /// The name of the device.
111    pub fn name(&self) -> &str {
112        &self.id.name
113    }
114
115    pub fn from_raw(d: rtaudio_sys::rtaudio_device_info_t) -> Self {
116        let mut sample_rates = Vec::new();
117        for sr in d.sample_rates.iter() {
118            if *sr <= 0 {
119                break;
120            }
121
122            sample_rates.push(*sr as u32);
123        }
124
125        // Safety: i8 and u8 have the same size, and we are correctly
126        // using the length of the array `d.name`.
127        let name_slice: &[u8] =
128            unsafe { std::slice::from_raw_parts(d.name.as_ptr() as *const u8, d.name.len()) };
129
130        let name = match CStr::from_bytes_until_nul(&name_slice) {
131            Ok(n) => n.to_string_lossy().to_string(),
132            Err(e) => {
133                #[cfg(not(any(feature = "tracing", feature = "log")))]
134                let _ = e;
135
136                #[cfg(any(feature = "tracing", feature = "log"))]
137                error!("RtAudio: Failed to parse audio device name: {}", e);
138
139                String::from("error")
140            }
141        };
142
143        Self {
144            id: DeviceID {
145                name,
146                session_id: d.id as u32,
147            },
148            output_channels: d.output_channels as u32,
149            input_channels: d.input_channels as u32,
150            duplex_channels: d.duplex_channels as u32,
151            is_default_output: d.is_default_output != 0,
152            is_default_input: d.is_default_input != 0,
153            native_formats: NativeFormats::from_bits_truncate(d.native_formats),
154            preferred_sample_rate: d.preferred_sample_rate as u32,
155            sample_rates,
156        }
157    }
158}