aria_kit/audio/capture/microphone/
stream.rs1use std::collections::VecDeque;
5use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::mpsc::{Receiver, SyncSender, sync_channel};
8use std::time::Duration;
9
10use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
11use cpal::{FromSample, Sample, SampleFormat, SizedSample, SupportedStreamConfig};
12use rtrb::{Consumer, Producer, RingBuffer};
13
14use crate::audio::samples_at;
15
16use super::resampling::SampleRateConverter;
17use super::worker::IdleObservation;
18use super::{MicrophoneError, Result};
19
20const SAMPLE_RING_DURATION: Duration = Duration::from_millis(500);
21const MAX_INPUT_SAMPLE_RATE: u32 = 384_000;
22pub(super) const DRAIN_BUFFER_SAMPLES: usize = 1_024;
23const STREAM_INITIALIZATION_TIMEOUT: Duration = Duration::from_secs(2);
24
25pub(super) struct LiveStream {
27 _stream: cpal::Stream,
29 consumer: Consumer<i16>,
30 status: Arc<CaptureStatus>,
31 converter: SampleRateConverter,
32 errors: Receiver<cpal::Error>,
33}
34
35pub fn check_default_input_device() -> Result<String> {
39 let device = cpal::default_host()
40 .default_input_device()
41 .ok_or(MicrophoneError::NoInputDevice)?;
42 let description = device
45 .description()
46 .map_err(|source| MicrophoneError::DeviceDescription(Box::new(source)))?;
47 select_input_config(&device)?;
48 Ok(description.name().to_owned())
49}
50
51impl LiveStream {
52 pub(super) fn open() -> Result<Self> {
53 let host = cpal::default_host();
54 let device = host
55 .default_input_device()
56 .ok_or(MicrophoneError::NoInputDevice)?;
57 let config = select_input_config(&device)?;
58 let sample_rate = config.sample_rate();
59 let (producer, consumer, status) = sample_ring(sample_rate, config.channels())?;
60 let converter = SampleRateConverter::new(sample_rate)?;
61 let (error_sender, errors) = sync_channel(1);
62 let stream = build_stream(&device, config, producer, error_sender)?;
63
64 stream
65 .play()
66 .map_err(|source| MicrophoneError::StartStream(Box::new(source)))?;
67 Ok(Self {
68 _stream: stream,
69 consumer,
70 status,
71 converter,
72 errors,
73 })
74 }
75
76 pub(super) fn pop<'samples>(
77 &mut self,
78 destination: &'samples mut [i16],
79 ) -> Result<&'samples mut [i16]> {
80 Ok(self.converter.pop(&mut self.consumer, destination)?)
81 }
82
83 pub(super) fn check_state(&self) -> Result<()> {
84 self.check_failures()?;
85 if self.consumer.is_abandoned() {
86 return Err(MicrophoneError::StreamEnded.into());
87 }
88 Ok(())
89 }
90
91 pub(super) fn check_failures(&self) -> Result<()> {
92 if self.status.incomplete_frame.load(Ordering::Acquire) {
93 return Err(MicrophoneError::IncompleteFrame.into());
94 }
95 if self.status.overrun.load(Ordering::Acquire) {
96 return Err(MicrophoneError::Overrun.into());
97 }
98 if let Ok(error) = self.errors.try_recv() {
99 return Err(MicrophoneError::Stream(Box::new(error)).into());
100 }
101 Ok(())
102 }
103
104 pub(super) fn drain_into(
107 &mut self,
108 pre_press: &mut VecDeque<i16>,
109 capacity: usize,
110 mut observe: impl FnMut(&[i16]) -> IdleObservation,
111 ) -> Result<()> {
112 let mut scratch = [0_i16; DRAIN_BUFFER_SAMPLES];
113 loop {
114 self.check_state()?;
115 let samples = self.pop(&mut scratch)?;
116 if samples.is_empty() {
117 return Ok(());
118 }
119 let kept = match observe(samples) {
120 IdleObservation::Continue => &samples[..],
121 IdleObservation::Detected { command_start } => {
124 pre_press.clear();
125 &samples[command_start..]
126 }
127 };
128 pre_press.extend(kept.iter().copied());
129 if pre_press.len() > capacity {
130 pre_press.drain(..pre_press.len() - capacity);
131 }
132 }
133 }
134}
135
136struct CaptureProducer {
137 samples: Producer<i16>,
138 channels: u16,
139 status: Arc<CaptureStatus>,
140}
141
142impl CaptureProducer {
143 fn push<T>(&mut self, input: &[T])
144 where
145 T: Sample,
146 i16: FromSample<T>,
147 {
148 let mut frames = input.chunks_exact(usize::from(self.channels));
149 if !frames.remainder().is_empty() {
150 self.status.incomplete_frame.store(true, Ordering::Release);
151 return;
152 }
153
154 let frame_count = frames.len();
155 if self.samples.slots() < frame_count {
156 self.status.overrun.store(true, Ordering::Release);
157 return;
158 }
159
160 let mut converted = [0_i16; DRAIN_BUFFER_SAMPLES];
161 let mut converted_count = 0;
162 for frame in &mut frames {
163 converted[converted_count] = downmix(frame, self.channels);
164 converted_count += 1;
165
166 if converted_count == converted.len() {
167 self.push_converted(&converted);
168 converted_count = 0;
169 }
170 }
171 self.push_converted(&converted[..converted_count]);
172 }
173
174 fn push_converted(&mut self, samples: &[i16]) {
175 if self.samples.push_entire_slice(samples).is_err() {
176 self.status.overrun.store(true, Ordering::Release);
177 }
178 }
179}
180
181#[derive(Default)]
182struct CaptureStatus {
183 incomplete_frame: AtomicBool,
184 overrun: AtomicBool,
185}
186
187fn sample_ring(
188 sample_rate: u32,
189 channels: u16,
190) -> Result<(CaptureProducer, Consumer<i16>, Arc<CaptureStatus>)> {
191 let capacity = duration_sample_count(SAMPLE_RING_DURATION, sample_rate)?;
192 let (producer, consumer) = RingBuffer::new(capacity);
193 let status = Arc::new(CaptureStatus::default());
194
195 Ok((
196 CaptureProducer {
197 samples: producer,
198 channels,
199 status: Arc::clone(&status),
200 },
201 consumer,
202 status,
203 ))
204}
205
206fn select_input_config(device: &cpal::Device) -> Result<SupportedStreamConfig> {
207 let config = device
208 .default_input_config()
209 .map_err(|source| MicrophoneError::DefaultConfiguration(Box::new(source)))?;
210 if config.channels() == 0 {
211 return Err(MicrophoneError::NoInputChannels.into());
212 }
213 let sample_rate = config.sample_rate();
214 if sample_rate == 0 {
215 return Err(MicrophoneError::ZeroInputSampleRate.into());
216 }
217 if sample_rate > MAX_INPUT_SAMPLE_RATE {
218 return Err(MicrophoneError::InputSampleRateTooHigh {
219 actual: sample_rate,
220 maximum: MAX_INPUT_SAMPLE_RATE,
221 }
222 .into());
223 }
224 if !supported_sample_format(config.sample_format()) {
225 return Err(
226 MicrophoneError::UnsupportedSampleFormat(config.sample_format().to_string()).into(),
227 );
228 }
229 tracing::debug!(
230 sample_rate,
231 channels = config.channels(),
232 sample_format = ?config.sample_format(),
233 "selected native microphone configuration"
234 );
235 Ok(config)
236}
237
238fn duration_sample_count(duration: Duration, sample_rate: u32) -> Result<usize> {
239 usize::try_from(samples_at(duration, sample_rate))
240 .map_err(|_error| MicrophoneError::SampleCountOverflow.into())
241}
242
243fn supported_sample_format(format: SampleFormat) -> bool {
244 matches!(
245 format,
246 SampleFormat::I16 | SampleFormat::F32 | SampleFormat::U16
247 )
248}
249
250fn build_stream(
251 device: &cpal::Device,
252 config: SupportedStreamConfig,
253 producer: CaptureProducer,
254 errors: SyncSender<cpal::Error>,
255) -> Result<cpal::Stream> {
256 match config.sample_format() {
257 SampleFormat::I16 => build_typed_stream::<i16>(device, config, producer, errors),
258 SampleFormat::F32 => build_typed_stream::<f32>(device, config, producer, errors),
259 SampleFormat::U16 => build_typed_stream::<u16>(device, config, producer, errors),
260 format => Err(MicrophoneError::UnsupportedSampleFormat(format.to_string()).into()),
261 }
262}
263
264fn build_typed_stream<T>(
265 device: &cpal::Device,
266 config: SupportedStreamConfig,
267 mut producer: CaptureProducer,
268 errors: SyncSender<cpal::Error>,
269) -> Result<cpal::Stream>
270where
271 T: Sample + SizedSample,
272 i16: FromSample<T>,
273{
274 device
275 .build_input_stream(
276 config.config(),
277 move |input: &[T], _info| producer.push(input),
278 move |error| {
279 errors.try_send(error).ok();
280 },
281 Some(STREAM_INITIALIZATION_TIMEOUT),
282 )
283 .map_err(|source| MicrophoneError::BuildStream(Box::new(source)).into())
284}
285
286fn downmix<T>(frame: &[T], channels: u16) -> i16
287where
288 T: Sample,
289 i16: FromSample<T>,
290{
291 let sum = frame
292 .iter()
293 .map(|&sample| i64::from(i16::from_sample(sample)))
294 .sum::<i64>();
295 let average = sum / i64::from(channels);
296 match i16::try_from(average) {
297 Ok(sample) => sample,
298 Err(_error) if average.is_negative() => i16::MIN,
299 Err(_error) => i16::MAX,
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn downmix_averages_the_channels() {
309 assert_eq!(downmix(&[1_000_i16, 3_000], 2), 2_000);
310 assert_eq!(downmix(&[-400_i16, 0, 400, -800], 4), -200);
311 }
312}