wavy/ffi/linux/
device_list.rs1#![allow(unsafe_code)]
12
13use std::{
14 convert::TryInto,
15 ffi::CStr,
16 mem::MaybeUninit,
17 os::raw::{c_char, c_void},
18};
19
20use fon::chan::{Ch32, Channel};
21
22use super::{
23 free, pcm, Alsa, SndPcmAccess, SndPcmFormat, SndPcmMode, SndPcmStream,
24};
25
26pub(crate) const DEFAULT: &[u8] = b"default\0";
27
28pub(crate) unsafe fn reset_hwp(
30 pcm: *mut c_void,
31 hwp: *mut c_void,
32) -> Option<()> {
33 let format = if cfg!(target_endian = "little") {
34 SndPcmFormat::FloatLe
35 } else if cfg!(target_endian = "big") {
36 SndPcmFormat::FloatBe
37 } else {
38 unreachable!()
39 };
40 pcm::hw_params_any(pcm, hwp).ok()?;
41 pcm::hw_params_set_access(pcm, hwp, SndPcmAccess::RwInterleaved).ok()?;
42 pcm::hw_params_set_format(pcm, hwp, format).ok()?;
43 Some(())
44}
45
46pub(crate) fn open(
48 name: *const c_char,
49 stream: SndPcmStream,
50) -> Option<(*mut c_void, *mut c_void, u8)> {
51 unsafe {
52 let pcm = pcm::open(name, stream, SndPcmMode::Nonblock).ok()?;
53 let hwp = pcm::hw_params_malloc().ok()?;
54 let mut channels = 0;
55 reset_hwp(pcm, hwp)?;
56 for i in 1..=8 {
57 if pcm::hw_test_channels(pcm, hwp, i).is_ok() {
58 channels |= 1 << (i - 1);
59 }
60 }
61 Some((pcm, hwp, channels))
62 }
63}
64
65pub(crate) trait SoundDevice:
66 std::fmt::Display + From<AudioDevice>
67{
68 const INPUT: bool;
69
70 fn pcm(&self) -> *mut c_void;
71 fn hwp(&self) -> *mut c_void;
72}
73
74#[derive(Debug)]
76pub(crate) struct AudioDevice {
77 pub(crate) name: String,
79 pub(crate) pcm: *mut c_void,
81 pub(crate) hwp: *mut c_void,
83 pub(crate) supported: u8,
85 pub(crate) fds: Vec<smelling_salts::Device>,
87}
88
89impl AudioDevice {
90 pub(crate) fn start(&mut self) -> Option<()> {
92 assert!(self.fds.is_empty());
93 let fd_list = unsafe { pcm::poll_descriptors(self.pcm).ok()? };
95 for fd in fd_list {
97 self.fds.push(smelling_salts::Device::new(fd.fd, unsafe {
98 smelling_salts::Watcher::from_raw(fd.events as u32)
99 }));
100 }
101 Some(())
102 }
103}
104
105impl Drop for AudioDevice {
106 fn drop(&mut self) {
107 for fd in &mut self.fds {
109 fd.old();
110 }
111 unsafe {
113 pcm::hw_params_free(self.hwp);
114 pcm::close(self.pcm).unwrap();
115 }
116 }
117}
118
119pub(crate) fn device_list<D: SoundDevice, F: Fn(D) -> T, T>(
121 abstrakt: F,
122) -> Vec<T> {
123 super::ALSA.with(|alsa| {
124 if let Some(alsa) = alsa {
125 device_list_internal(&alsa, abstrakt)
126 } else {
127 Vec::new()
128 }
129 })
130}
131
132fn device_list_internal<D: SoundDevice, F: Fn(D) -> T, T>(
133 alsa: &Alsa,
134 abstrakt: F,
135) -> Vec<T> {
136 let tpcm = CStr::from_bytes_with_nul(b"pcm\0").unwrap();
137 let tname = CStr::from_bytes_with_nul(b"NAME\0").unwrap();
138 let tdesc = CStr::from_bytes_with_nul(b"DESC\0").unwrap();
139 let tioid = CStr::from_bytes_with_nul(b"IOID\0").unwrap();
140
141 let mut hints = MaybeUninit::uninit();
142 let mut devices = Vec::new();
143 unsafe {
144 if (alsa.snd_device_name_hint)(-1, tpcm.as_ptr(), hints.as_mut_ptr())
145 < 0
146 {
147 return Vec::new();
148 }
149 let hints = hints.assume_init();
150 let mut n = hints;
151 while !(*n).is_null() {
152 let pcm_name = (alsa.snd_device_name_get_hint)(*n, tname.as_ptr());
154 let io = (alsa.snd_device_name_get_hint)(*n, tioid.as_ptr());
155 debug_assert_ne!(pcm_name, std::ptr::null_mut());
156
157 let name = match CStr::from_ptr(pcm_name).to_str() {
159 Ok(x) if x.starts_with("sysdefault") => {
160 n = n.offset(1);
161 continue;
162 }
163 Ok("null") => {
164 n = n.offset(1);
166 continue;
167 }
168 Ok("default") => "Default".to_string(),
169 _a => {
170 let name =
171 (alsa.snd_device_name_get_hint)(*n, tdesc.as_ptr());
172 assert_ne!(name, std::ptr::null_mut());
173 let rust =
174 CStr::from_ptr(name).to_string_lossy().to_string();
175 free(name.cast());
176 rust.replace("\n", ": ")
177 }
178 };
179
180 let is_input = io.is_null() || *(io.cast::<u8>()) == b'I';
182 let is_output = io.is_null() || *(io.cast::<u8>()) == b'O';
183 if !io.is_null() {
184 free(io.cast());
185 }
186
187 if (D::INPUT && is_input) || (!D::INPUT && is_output) {
189 let dev = open(
191 pcm_name,
192 if D::INPUT {
193 SndPcmStream::Capture
194 } else {
195 SndPcmStream::Playback
196 },
197 );
198
199 if let Some((pcm, hwp, supported)) = dev {
200 devices.push(abstrakt(D::from(AudioDevice {
202 name,
203 pcm,
204 hwp,
205 supported,
206 fds: Vec::new(),
207 })));
208 }
209 }
210 free(pcm_name.cast());
211 n = n.offset(1);
212 }
213 (alsa.snd_device_name_free_hint)(hints);
214 }
215 devices
216}
217
218#[allow(unsafe_code)]
219pub(crate) fn pcm_hw_params(
220 device: &AudioDevice,
221 channels: u8,
222 buffer: &mut Vec<Ch32>,
223 sample_rate: &mut Option<f64>,
224 period: &mut u16,
225) -> Option<()> {
226 unsafe {
227 reset_hwp(device.pcm, device.hwp)?;
229
230 pcm::hw_params_set_rate_near(
232 device.pcm,
233 device.hwp,
234 &mut crate::consts::SAMPLE_RATE.into(),
235 &mut 0,
236 )
237 .ok()?;
238 pcm::hw_set_channels(device.pcm, device.hwp, channels).ok()?;
240 let mut period_size = crate::consts::PERIOD.into();
242 pcm::hw_params_set_period_size_near(
243 device.pcm,
244 device.hwp,
245 &mut period_size,
246 &mut 0,
247 )
248 .ok()?;
249 pcm::hw_params_set_buffer_size_near(
251 device.pcm,
252 device.hwp,
253 &mut period_size,
254 )
255 .ok()?;
256 pcm::hw_params(device.pcm, device.hwp).ok()?;
258
259 *sample_rate = Some(pcm::hw_get_rate(device.hwp)?);
262
263 *period = period_size.try_into().ok()?;
265
266 buffer.resize(*period as usize * channels as usize, Ch32::MID);
268
269 let _ = pcm::drop(device.pcm);
271 pcm::prepare(device.pcm).ok()?;
273 }
274
275 Some(())
276}