phosphor_core/
cpal_backend.rs1use anyhow::{Context, Result};
7use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
8use cpal::{SampleFormat, Stream, StreamConfig};
9use tracing;
10
11pub struct CpalBackend {
13 stream: Option<Stream>,
14 sample_rate: u32,
15 channels: u16,
16}
17
18impl CpalBackend {
19 pub fn new(_desired_sample_rate: u32, _desired_buffer_size: u32) -> Result<Self> {
21 let host = cpal::default_host();
22 let device = host
23 .default_output_device()
24 .context("no audio output device found")?;
25
26 let name = device.name().unwrap_or_else(|_| "unknown".into());
27 tracing::info!("Audio device: {name}");
28
29 let config = device.default_output_config()?;
30 let sample_rate = config.sample_rate().0;
31 let channels = config.channels();
32
33 tracing::info!(
34 "Audio config: {}Hz, {} channels, {:?}",
35 sample_rate, channels, config.sample_format()
36 );
37
38 Ok(Self {
39 stream: None,
40 sample_rate,
41 channels,
42 })
43 }
44
45 pub fn start<F>(&mut self, mut callback: F) -> Result<()>
48 where
49 F: FnMut(&mut [f32]) + Send + 'static,
50 {
51 let host = cpal::default_host();
52 let device = host
53 .default_output_device()
54 .context("no audio output device found")?;
55
56 let supported = device.default_output_config()?;
57
58 let config = StreamConfig {
59 channels: supported.channels(),
60 sample_rate: supported.sample_rate(),
61 buffer_size: cpal::BufferSize::Default,
62 };
63
64 self.sample_rate = config.sample_rate.0;
65 self.channels = config.channels;
66
67 let stream = match supported.sample_format() {
68 SampleFormat::F32 => device.build_output_stream(
69 &config,
70 move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
71 callback(data);
72 },
73 |err| tracing::error!("Audio stream error: {err}"),
74 None,
75 )?,
76 SampleFormat::I16 => device.build_output_stream(
77 &config,
78 move |data: &mut [i16], _: &cpal::OutputCallbackInfo| {
79 let mut float_buf = vec![0.0f32; data.len()];
81 callback(&mut float_buf);
82 for (out, &inp) in data.iter_mut().zip(float_buf.iter()) {
83 *out = (inp * i16::MAX as f32) as i16;
84 }
85 },
86 |err| tracing::error!("Audio stream error: {err}"),
87 None,
88 )?,
89 format => anyhow::bail!("Unsupported sample format: {format:?}"),
90 };
91
92 stream.play()?;
93 tracing::info!("Audio stream started");
94 self.stream = Some(stream);
95 Ok(())
96 }
97
98 pub fn stop(&mut self) {
99 if let Some(stream) = self.stream.take() {
100 drop(stream);
101 tracing::info!("Audio stream stopped");
102 }
103 }
104
105 pub fn sample_rate(&self) -> u32 {
106 self.sample_rate
107 }
108
109 pub fn channels(&self) -> u16 {
110 self.channels
111 }
112}
113
114impl Drop for CpalBackend {
115 fn drop(&mut self) {
116 self.stop();
117 }
118}