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