aria_kit/audio/capture/
mod.rs1#[cfg(feature = "microphone")]
4mod microphone;
5mod wav_replay;
6
7use std::time::Duration;
8
9use crate::audio::{CaptureError, RecordingReader, ResampleError, StopSignal};
10use crate::voice_activity::VoiceActivityError;
11
12#[cfg(feature = "microphone")]
13pub use microphone::{
14 Microphone, MicrophoneBuilder, PreparedMicrophone, WakeWordControl, WakeWordEvent,
15 WakeWordFailure, WakeWordMonitor, WakeWordSuspension, check_default_input_device,
16};
17pub use wav_replay::{ReplayPace, WavReplayCapture, WavReplayError};
18
19const MIN_SILENCE_DURATION: Duration = Duration::from_millis(100);
20const MAX_SILENCE_DURATION: Duration = Duration::from_secs(10);
21const DEFAULT_SILENCE_DURATION: Duration = Duration::from_millis(1_500);
22
23pub trait AudioCapture: Send + Sync + 'static {
26 fn start(&self, mode: RecordingMode, stop: StopSignal)
33 -> Result<RecordingReader, CaptureError>;
34}
35
36impl<C: AudioCapture> AudioCapture for std::sync::Arc<C> {
39 fn start(
40 &self,
41 mode: RecordingMode,
42 stop: StopSignal,
43 ) -> Result<RecordingReader, CaptureError> {
44 (**self).start(mode, stop)
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum RecordingMode {
50 StopOnSilence { trailing_silence: SilenceDuration },
51 UntilStopped,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct SilenceDuration(Duration);
56
57impl SilenceDuration {
58 pub fn new(duration: Duration) -> Result<Self, SilenceDurationError> {
59 if !(MIN_SILENCE_DURATION..=MAX_SILENCE_DURATION).contains(&duration) {
60 return Err(SilenceDurationError::OutOfRange {
61 actual: duration,
62 minimum: MIN_SILENCE_DURATION,
63 maximum: MAX_SILENCE_DURATION,
64 });
65 }
66 if !duration.subsec_nanos().is_multiple_of(1_000_000) {
67 return Err(SilenceDurationError::SubmillisecondPrecision { actual: duration });
68 }
69 Ok(Self(duration))
70 }
71
72 pub fn get(self) -> Duration {
73 self.0
74 }
75}
76
77impl Default for SilenceDuration {
78 fn default() -> Self {
79 Self(DEFAULT_SILENCE_DURATION)
80 }
81}
82
83#[derive(Debug, thiserror::Error)]
84pub enum SilenceDurationError {
85 #[error("silence duration must be between {minimum:?} and {maximum:?}; got {actual:?}")]
86 OutOfRange {
87 actual: Duration,
88 minimum: Duration,
89 maximum: Duration,
90 },
91
92 #[error("silence duration must use whole-millisecond precision; got {actual:?}")]
93 SubmillisecondPrecision { actual: Duration },
94}
95
96#[derive(Debug, thiserror::Error)]
97pub enum MicrophoneError {
98 #[error("failed to prepare the recording spool directory")]
99 SpoolDirectory(#[source] crate::primitives::private_fs::PrivateDirectoryError),
100
101 #[error("no default microphone is available")]
102 NoInputDevice,
103
104 #[error("failed to read the default microphone configuration")]
105 DefaultConfiguration(#[source] Box<dyn std::error::Error + Send + Sync>),
106
107 #[error("failed to read the default microphone description")]
108 DeviceDescription(#[source] Box<dyn std::error::Error + Send + Sync>),
109
110 #[error("microphone input configuration has no channels")]
111 NoInputChannels,
112
113 #[error("microphone input configuration has a zero sample rate")]
114 ZeroInputSampleRate,
115
116 #[error(
117 "microphone input sample rate {actual} Hz exceeds the supported maximum of {maximum} Hz"
118 )]
119 InputSampleRateTooHigh { actual: u32, maximum: u32 },
120
121 #[error("microphone sample format {0} is not supported")]
122 UnsupportedSampleFormat(String),
123
124 #[error("failed to build the microphone input stream")]
125 BuildStream(#[source] Box<dyn std::error::Error + Send + Sync>),
126
127 #[error("failed to start the microphone input stream")]
128 StartStream(#[source] Box<dyn std::error::Error + Send + Sync>),
129
130 #[error("the microphone input stream failed")]
131 Stream(#[source] Box<dyn std::error::Error + Send + Sync>),
132
133 #[error("the microphone input stream ended unexpectedly")]
134 StreamEnded,
135
136 #[error("the microphone callback supplied an incomplete audio frame")]
137 IncompleteFrame,
138
139 #[error("the microphone worker fell behind and lost audio")]
140 Overrun,
141
142 #[error("failed to convert microphone audio")]
143 Resample(#[from] ResampleError),
144
145 #[error("microphone sample count exceeds the supported recording bound")]
146 SampleCountOverflow,
147
148 #[error("failed to start the microphone worker thread")]
149 SpawnWorker(#[source] std::io::Error),
150
151 #[error("the microphone worker thread is not running")]
152 WorkerGone,
153
154 #[error("the microphone worker recovered from an internal panic")]
155 WorkerPanicked,
156
157 #[error("voice-activity detection failed")]
158 VoiceActivity(#[source] VoiceActivityError),
159}