1pub trait AudioOut: Send {
9 fn configure(&mut self, rate: u32, channels: u32);
11 fn push_le_f32(&mut self, bytes: &[u8]);
13}
14
15pub struct Resampler {
17 dst_rate: u32,
18 src_rate: u32,
19 channels: u32,
20 t: f64,
21 prev: [f32; 2],
22}
23
24impl Resampler {
25 pub fn new(dst_rate: u32) -> Resampler {
26 Resampler { dst_rate, src_rate: 44100, channels: 2, t: 0.0, prev: [0.0; 2] }
27 }
28
29 pub fn configure(&mut self, rate: u32, channels: u32) {
30 self.src_rate = rate;
31 self.channels = channels;
32 }
33
34 pub fn process(&mut self, bytes: &[u8], out: &mut Vec<f32>) {
36 let step = self.src_rate as f64 / self.dst_rate as f64;
37 let ch = self.channels as usize;
38 out.reserve(((bytes.len() / 4 / ch) as f64 / step) as usize * 2 + 4);
39 for frame in bytes.chunks_exact(4 * ch) {
40 let s = |i: usize| f32::from_le_bytes([frame[i * 4], frame[i * 4 + 1], frame[i * 4 + 2], frame[i * 4 + 3]]);
41 let cur = if ch == 1 { [s(0), s(0)] } else { [s(0), s(1)] };
42 while self.t < 1.0 {
43 let t = self.t as f32;
44 out.push(self.prev[0] + (cur[0] - self.prev[0]) * t);
45 out.push(self.prev[1] + (cur[1] - self.prev[1]) * t);
46 self.t += step;
47 }
48 self.t -= 1.0;
49 self.prev = cur;
50 }
51 }
52}
53
54#[cfg(feature = "window")]
55pub use sink::{AudioSink, AudioStream};
56
57#[cfg(feature = "window")]
58mod sink {
59 use std::sync::Arc;
60 use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
61 use std::time::{Duration, Instant};
62
63 use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
64 use cpal::{FromSample, SizedSample};
65
66 use super::{AudioOut, Resampler};
67
68 const TARGET_LATENCY_S: f32 = 0.06;
70 const TARGET_STEP_S: f32 = 0.03;
73 const TARGET_MAX_S: f32 = 0.15;
74 const HEADROOM_S: f32 = 0.14;
76
77 struct Ring {
80 slots: Box<[AtomicU32]>,
81 head: AtomicUsize,
83 tail: AtomicUsize,
84 trim: AtomicBool,
87 target: AtomicUsize,
89 underruns: AtomicUsize,
91 }
92
93 impl Ring {
94 fn new(capacity: usize) -> Ring {
95 Ring {
96 slots: (0..capacity).map(|_| AtomicU32::new(0)).collect(),
97 head: AtomicUsize::new(0),
98 tail: AtomicUsize::new(0),
99 trim: AtomicBool::new(false),
100 target: AtomicUsize::new(0),
101 underruns: AtomicUsize::new(0),
102 }
103 }
104
105 fn len(&self) -> usize {
106 self.head.load(Ordering::Acquire) - self.tail.load(Ordering::Acquire)
107 }
108
109 fn push(&self, samples: &[f32]) {
111 let head = self.head.load(Ordering::Relaxed);
112 let free = self.slots.len() - (head - self.tail.load(Ordering::Acquire));
113 let n = samples.len().min(free) & !1;
114 for (i, s) in samples[..n].iter().enumerate() {
115 self.slots[(head + i) % self.slots.len()].store(s.to_bits(), Ordering::Relaxed);
116 }
117 self.head.store(head + n, Ordering::Release);
118 }
119
120 fn pop(&self) -> Option<(f32, f32)> {
122 let tail = self.tail.load(Ordering::Relaxed);
123 if self.head.load(Ordering::Acquire) - tail < 2 {
124 return None;
125 }
126 let get = |i: usize| f32::from_bits(self.slots[i % self.slots.len()].load(Ordering::Relaxed));
127 let frame = (get(tail), get(tail + 1));
128 self.tail.store(tail + 2, Ordering::Release);
129 Some(frame)
130 }
131
132 fn skip_to(&self, keep: usize) {
134 let tail = self.tail.load(Ordering::Relaxed);
135 let len = self.head.load(Ordering::Acquire) - tail;
136 if len > keep {
137 self.tail.store(tail + ((len - keep) & !1), Ordering::Release);
138 }
139 }
140 }
141
142 pub struct AudioSink {
143 ring: Arc<Ring>,
144 device_rate: u32,
145 resampler: Resampler,
146 scratch: Vec<f32>,
147 per_second: usize,
149 underruns_seen: usize,
151 last_underrun: Option<Instant>,
152 }
153
154 pub struct AudioStream {
159 stream: cpal::Stream,
160 ring: Arc<Ring>,
161 device_name: Option<String>,
162 device_rate: u32,
163 broken: Arc<AtomicBool>,
164 last_check: Instant,
165 }
166
167 fn build<T: SizedSample + FromSample<f32>>(
168 device: &cpal::Device,
169 config: &cpal::StreamConfig,
170 ring: Arc<Ring>,
171 broken: Arc<AtomicBool>,
172 ) -> Result<cpal::Stream, cpal::Error> {
173 let channels = config.channels as usize;
174 let mut primed = false;
175 device.build_output_stream::<T, _, _>(
176 *config,
177 move |out: &mut [T], _| {
178 let target = ring.target.load(Ordering::Relaxed);
179 if ring.trim.swap(false, Ordering::AcqRel) {
180 ring.skip_to(target);
181 }
182 if !primed && ring.len() >= target {
183 primed = true;
184 }
185 for frame in out.chunks_mut(channels) {
186 let (l, r) = match ring.pop().filter(|_| primed) {
187 Some(s) => s,
188 None => {
189 if primed {
190 ring.underruns.fetch_add(1, Ordering::Relaxed);
191 }
192 primed = false; (0.0, 0.0)
194 }
195 };
196 match frame.len() {
197 1 => frame[0] = T::from_sample(0.5 * (l + r)),
198 _ => {
199 frame[0] = T::from_sample(l);
200 frame[1] = T::from_sample(r);
201 for s in &mut frame[2..] {
202 *s = T::from_sample(0.0);
203 }
204 }
205 }
206 }
207 },
208 move |e| {
209 eprintln!("[audio] stream error: {e}");
211 broken.store(true, Ordering::Release);
212 },
213 None,
214 )
215 }
216
217 fn open_stream(ring: &Arc<Ring>, broken: &Arc<AtomicBool>) -> Result<(cpal::Stream, Option<String>, u32), String> {
219 let host = cpal::default_host();
220 let device = host.default_output_device().ok_or("no audio output device")?;
221 let name = device.id().ok().map(|i| i.to_string());
223 if let Ok(d) = device.description() {
224 eprintln!("[audio] output: {}", d.name());
225 }
226 let supported = device.default_output_config().map_err(|e| e.to_string())?;
227 let format = supported.sample_format();
228 let config = supported.config();
229 let (r, b) = (ring.clone(), broken.clone());
230 use cpal::SampleFormat as F;
231 let stream = match format {
232 F::F32 => build::<f32>(&device, &config, r, b),
233 F::F64 => build::<f64>(&device, &config, r, b),
234 F::I16 => build::<i16>(&device, &config, r, b),
235 F::U16 => build::<u16>(&device, &config, r, b),
236 F::I32 => build::<i32>(&device, &config, r, b),
237 F::U32 => build::<u32>(&device, &config, r, b),
238 F::I8 => build::<i8>(&device, &config, r, b),
239 F::U8 => build::<u8>(&device, &config, r, b),
240 other => return Err(format!("unsupported device sample format {other:?}")),
241 }
242 .map_err(|e| e.to_string())?;
243 stream.play().map_err(|e| e.to_string())?;
244 Ok((stream, name, config.sample_rate))
245 }
246
247 impl AudioStream {
248 pub fn check(&mut self) {
252 if self.last_check.elapsed() < Duration::from_secs(1) {
253 return;
254 }
255 self.last_check = Instant::now();
256 let now = cpal::default_host().default_output_device().and_then(|d| d.id().ok().map(|i| i.to_string()));
257 let changed = now.is_some() && now != self.device_name;
258 if !self.broken.load(Ordering::Acquire) && !changed {
259 return;
260 }
261 self.broken.store(false, Ordering::Release);
262 match open_stream(&self.ring, &self.broken) {
263 Ok((stream, name, rate)) if rate == self.device_rate => (self.stream, self.device_name) = (stream, name),
264 Ok((_, name, rate)) => {
265 eprintln!("[audio] the new output runs at {rate} Hz, not {} Hz: staying on the old one", self.device_rate);
266 self.device_name = name;
267 }
268 Err(e) => eprintln!("[audio] couldn't reopen the output: {e}"),
269 }
270 }
271 }
272
273 impl AudioSink {
274 pub fn open() -> Result<(AudioSink, AudioStream), String> {
275 let device_rate = cpal::default_host()
276 .default_output_device()
277 .ok_or("no audio output device")?
278 .default_output_config()
279 .map_err(|e| e.to_string())?
280 .config()
281 .sample_rate;
282 let per_second = device_rate as usize * 2;
283 let max = (per_second as f32 * (TARGET_MAX_S + HEADROOM_S)) as usize;
284 let ring = Arc::new(Ring::new(max * 2));
285 ring.target.store((per_second as f32 * TARGET_LATENCY_S) as usize & !1, Ordering::Relaxed);
286 let broken = Arc::new(AtomicBool::new(false));
287 let (stream, device_name, device_rate) = open_stream(&ring, &broken)?;
288 let sink = AudioSink {
289 ring: ring.clone(), device_rate, resampler: Resampler::new(device_rate), scratch: Vec::new(),
290 per_second, underruns_seen: 0, last_underrun: None,
291 };
292 Ok((sink, AudioStream { stream, ring, device_name, device_rate, broken, last_check: Instant::now() }))
293 }
294
295 pub fn device_rate(&self) -> u32 {
296 self.device_rate
297 }
298 }
299
300 impl AudioOut for AudioSink {
301 fn configure(&mut self, rate: u32, channels: u32) {
302 self.resampler.configure(rate, channels);
303 }
304
305 fn push_le_f32(&mut self, bytes: &[u8]) {
306 self.scratch.clear();
307 self.resampler.process(bytes, &mut self.scratch);
308 self.ring.push(&self.scratch);
309 let underruns = self.ring.underruns.load(Ordering::Relaxed);
311 if underruns != self.underruns_seen {
312 self.underruns_seen = underruns;
313 let again = self.last_underrun.is_some_and(|t| t.elapsed() < Duration::from_secs(5));
314 self.last_underrun = Some(Instant::now());
315 let (target, max) = (self.ring.target.load(Ordering::Relaxed), (self.per_second as f32 * TARGET_MAX_S) as usize);
316 if again && target < max {
317 let more = (target + (self.per_second as f32 * TARGET_STEP_S) as usize).min(max) & !1;
318 self.ring.target.store(more, Ordering::Relaxed);
319 eprintln!("[audio] underruns: buffering {} ms", more * 1000 / self.per_second);
320 }
321 }
322 let target = self.ring.target.load(Ordering::Relaxed);
323 if self.ring.len() > target + (self.per_second as f32 * HEADROOM_S) as usize {
324 self.ring.trim.store(true, Ordering::Release);
325 }
326 }
327 }
328}