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