amico_hal/os/common/audio/
control.rs1use cpal::traits::{HostTrait, StreamTrait};
2use lame::Lame;
3use rodio::{Decoder, OutputStream};
4use rodio::{DeviceTrait, Sink};
5use std::fs::File;
6use std::io::{BufReader, BufWriter, Write};
7use std::path::Path;
8use std::sync::{Arc, Mutex};
9use std::thread;
10use std::time::Duration;
11
12#[derive(Debug, thiserror::Error)]
13pub enum AudioPlaybackError {
14 #[error("Failed to create audio file")]
15 CreateError(#[from] std::io::Error),
16
17 #[error("Failed to create output stream")]
18 StreamError(#[from] rodio::StreamError),
19
20 #[error("Failed to decode audio file")]
21 DecodeError(#[from] rodio::decoder::DecoderError),
22
23 #[error("Failed to play audio source")]
24 PlayError(#[from] rodio::PlayError),
25
26 #[error("Failed to join playback handle")]
27 SpawnError(#[from] tokio::task::JoinError),
28}
29
30pub async fn playback(filepath: &str) -> Result<(), AudioPlaybackError> {
31 let filepath = filepath.to_string();
32 tokio::task::spawn_blocking(move || {
34 let (_stream, stream_handle) =
36 OutputStream::try_default().map_err(AudioPlaybackError::StreamError)?;
37
38 let file = BufReader::new(File::open(filepath).map_err(AudioPlaybackError::CreateError)?);
40
41 let source = Decoder::new(file).map_err(AudioPlaybackError::DecodeError)?;
43
44 let sink = Sink::try_new(&stream_handle).map_err(AudioPlaybackError::PlayError)?;
46 sink.append(source);
47
48 sink.sleep_until_end();
51
52 Ok(())
53 })
54 .await
55 .map_err(AudioPlaybackError::SpawnError)?
56}
57
58#[derive(Debug, thiserror::Error)]
59pub enum AudioRecordingError {
60 #[error("Failed to join recording handle")]
61 SpawnError(#[from] tokio::task::JoinError),
62
63 #[error("Failed to create MP3 encoder")]
64 EncoderError(String),
65
66 #[error("Failed to write sample")]
67 SampleWriteError,
68
69 #[error("Failed to finalize MP3 file")]
70 FinalizationError,
71
72 #[error("IO error: {0}")]
73 IoError(#[from] std::io::Error),
74}
75
76pub async fn record_blocking(filepath: &str) -> Result<(), AudioRecordingError> {
79 let temp_wav_path = format!("{}.temp.wav", filepath);
81
82 record_to_wav(&temp_wav_path)?;
84
85 convert_wav_to_mp3(&temp_wav_path, filepath)?;
87
88 std::fs::remove_file(&temp_wav_path).map_err(AudioRecordingError::IoError)?;
90
91 Ok(())
92}
93
94fn record_to_wav(filepath: &str) -> Result<(), AudioRecordingError> {
96 let host = cpal::default_host();
97 let device = host
98 .default_input_device()
99 .expect("Failed to get input device");
100 let config = device
101 .default_input_config()
102 .expect("Failed to get default input config");
103
104 let sample_rate = config.sample_rate().0;
105 let channels = config.channels() as usize;
106
107 if let Some(parent) = Path::new(filepath).parent() {
109 std::fs::create_dir_all(parent).map_err(AudioRecordingError::IoError)?;
110 }
111
112 let samples = Arc::new(Mutex::new(Vec::<f32>::new()));
114 let samples_clone = Arc::clone(&samples);
115
116 let err_fn = |err| eprintln!("Stream error: {}", err);
117
118 let stream = device
119 .build_input_stream(
120 &config.into(),
121 move |data: &[f32], _: &cpal::InputCallbackInfo| {
122 if let Ok(mut samples) = samples_clone.lock() {
123 samples.extend_from_slice(data);
124 }
125 },
126 err_fn,
127 None,
128 )
129 .expect("Failed to build input stream");
130
131 println!("Recording for 3 seconds...");
132 stream.play().unwrap();
133
134 thread::sleep(Duration::from_secs(3));
136
137 drop(stream);
138
139 let samples = Arc::try_unwrap(samples)
141 .map_err(|_| AudioRecordingError::FinalizationError)?
142 .into_inner()
143 .map_err(|_| AudioRecordingError::FinalizationError)?;
144
145 let spec = hound::WavSpec {
147 channels: channels as u16,
148 sample_rate,
149 bits_per_sample: 32,
150 sample_format: hound::SampleFormat::Float,
151 };
152
153 let mut writer = hound::WavWriter::create(filepath, spec).map_err(|_| {
154 AudioRecordingError::EncoderError("Failed to create WAV writer".to_string())
155 })?;
156
157 for sample in samples {
158 writer
159 .write_sample(sample)
160 .map_err(|_| AudioRecordingError::SampleWriteError)?;
161 }
162
163 writer
164 .finalize()
165 .map_err(|_| AudioRecordingError::FinalizationError)?;
166
167 Ok(())
168}
169
170fn convert_wav_to_mp3(wav_path: &str, mp3_path: &str) -> Result<(), AudioRecordingError> {
172 let mut reader = hound::WavReader::open(wav_path)
174 .map_err(|_| AudioRecordingError::EncoderError("Failed to open WAV file".to_string()))?;
175
176 let spec = reader.spec();
177 let channels = spec.channels as usize;
178 let sample_rate = spec.sample_rate;
179
180 let mut lame = Lame::new().ok_or_else(|| {
182 AudioRecordingError::EncoderError("Failed to create LAME encoder".to_string())
183 })?;
184
185 lame.set_channels(channels as u8)
187 .map_err(|_| AudioRecordingError::EncoderError("Failed to set channels".to_string()))?;
188 lame.set_sample_rate(sample_rate)
189 .map_err(|_| AudioRecordingError::EncoderError("Failed to set sample rate".to_string()))?;
190 lame.set_quality(5)
191 .map_err(|_| AudioRecordingError::EncoderError("Failed to set quality".to_string()))?;
192 lame.init_params().map_err(|_| {
193 AudioRecordingError::EncoderError("Failed to initialize parameters".to_string())
194 })?;
195
196 let mp3_file = File::create(mp3_path).map_err(AudioRecordingError::IoError)?;
198 let mut mp3_writer = BufWriter::new(mp3_file);
199
200 let samples: Vec<i16> = reader
202 .samples::<f32>()
203 .filter_map(Result::ok)
204 .map(|sample| (sample * 32767.0) as i16) .collect();
206
207 let chunk_size = 1024 * channels;
209 for chunk in samples.chunks(chunk_size) {
210 let mut left = Vec::with_capacity(chunk.len() / channels);
212 let mut right = Vec::with_capacity(chunk.len() / channels);
213
214 for i in (0..chunk.len()).step_by(channels) {
215 left.push(chunk[i]);
216 right.push(if channels > 1 { chunk[i + 1] } else { chunk[i] });
217 }
218
219 let mut mp3_buffer = vec![0u8; chunk.len() * 4]; let encoded_size = lame
222 .encode(&left, &right, &mut mp3_buffer)
223 .map_err(|_| AudioRecordingError::EncoderError("Failed to encode MP3".to_string()))?;
224
225 if encoded_size > 0 {
227 mp3_writer
228 .write_all(&mp3_buffer[..encoded_size])
229 .map_err(AudioRecordingError::IoError)?;
230 }
231 }
232
233 let mut mp3_buffer = vec![0u8; 7200]; let encoded_size = lame.encode(&[], &[], &mut mp3_buffer).map_err(|_| {
236 AudioRecordingError::EncoderError("Failed to flush MP3 encoder".to_string())
237 })?;
238
239 if encoded_size > 0 {
240 mp3_writer
241 .write_all(&mp3_buffer[..encoded_size])
242 .map_err(AudioRecordingError::IoError)?;
243 }
244
245 mp3_writer.flush().map_err(AudioRecordingError::IoError)?;
247
248 Ok(())
249}