1use anyhow::{Result, anyhow};
2use audio_codec::{PcmBuf, samples_to_bytes};
3use futures::StreamExt;
4use serde::{Deserialize, Serialize};
5use std::{
6 path::Path,
7 sync::{
8 Mutex,
9 atomic::{AtomicUsize, Ordering},
10 },
11 time::Duration,
12 u32,
13};
14use tokio::{
15 fs::File,
16 io::{AsyncSeekExt, AsyncWriteExt},
17 select,
18 sync::mpsc::UnboundedReceiver,
19};
20use tokio_stream::wrappers::IntervalStream;
21use tokio_util::sync::CancellationToken;
22use tracing::{info, warn};
23
24use crate::media::{AudioFrame, Samples};
25
26#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
27#[serde(rename_all = "lowercase")]
28pub enum RecorderFormat {
29 Wav,
30 Pcm,
31 Pcmu,
32 Pcma,
33 G722,
34}
35
36impl RecorderFormat {
37 pub fn extension(&self) -> &'static str {
38 "wav"
39 }
40
41 pub fn is_supported(&self) -> bool {
42 true
43 }
44
45 pub fn effective(&self) -> RecorderFormat {
46 *self
47 }
48}
49
50impl Default for RecorderFormat {
51 fn default() -> Self {
52 RecorderFormat::Wav
53 }
54}
55
56#[derive(Debug, Deserialize, Serialize, Clone)]
57#[serde(rename_all = "camelCase")]
58#[serde(default)]
59pub struct RecorderOption {
60 #[serde(default)]
61 pub recorder_file: String,
62 #[serde(default)]
63 pub samplerate: u32,
64 #[serde(default)]
65 pub ptime: u32,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub format: Option<RecorderFormat>,
68}
69
70impl RecorderOption {
71 pub fn new(recorder_file: String) -> Self {
72 Self {
73 recorder_file,
74 ..Default::default()
75 }
76 }
77
78 pub fn resolved_format(&self, default: RecorderFormat) -> RecorderFormat {
79 self.format.unwrap_or(default).effective()
80 }
81
82 pub fn ensure_path_extension(&mut self, fallback_format: RecorderFormat) {
83 let effective_format = self.format.unwrap_or(fallback_format).effective();
84 self.format = Some(effective_format);
85
86 if self.recorder_file.is_empty() {
87 return;
88 }
89
90 let extension = effective_format.extension();
91 if !self
92 .recorder_file
93 .to_lowercase()
94 .ends_with(&format!(".{}", extension.to_lowercase()))
95 {
96 self.recorder_file = format!("{}.{}", self.recorder_file, extension);
97 }
98 }
99}
100
101impl Default for RecorderOption {
102 fn default() -> Self {
103 Self {
104 recorder_file: "".to_string(),
105 samplerate: 16000,
106 ptime: 200,
107 format: None,
108 }
109 }
110}
111
112pub struct Recorder {
113 session_id: String,
114 option: RecorderOption,
115 samples_written: AtomicUsize,
116 cancel_token: CancellationToken,
117 stereo_buf: Mutex<PcmBuf>,
118 mono_buf: Mutex<PcmBuf>,
119}
120
121impl Recorder {
122 pub fn new(
123 cancel_token: CancellationToken,
124 session_id: String,
125 option: RecorderOption,
126 ) -> Self {
127 Self {
128 session_id,
129 option,
130 samples_written: AtomicUsize::new(0),
131 cancel_token,
132 stereo_buf: Mutex::new(Vec::new()),
133 mono_buf: Mutex::new(Vec::new()),
134 }
135 }
136
137 async fn update_wav_header(&self, file: &mut File, payload_type: Option<u8>) -> Result<()> {
138 let total = self.samples_written.load(Ordering::SeqCst);
139
140 let (format_tag, sample_rate, channels, bits_per_sample, data_size): (
141 u16,
142 u32,
143 u16,
144 u16,
145 usize,
146 ) = match payload_type {
147 Some(pt) => {
148 let (tag, rate, chan): (u16, u32, u16) = match pt {
149 0 => (0x0007, 8000, 1), 8 => (0x0006, 8000, 1), 9 => (0x0064, 16000, 1), 10 => (0x0001, 44100, 2), 11 => (0x0001, 44100, 1), _ => (0x0001, 16000, 1), };
156 let bits: u16 = match pt {
157 9 => 4,
158 0 | 8 => 8,
159 _ => 16,
160 };
161 (tag, rate, chan, bits, total)
162 }
163 None => (0x0001, self.option.samplerate, 2, 16, total),
164 };
165
166 let mut header_buf = Vec::new();
167 header_buf.extend_from_slice(b"RIFF");
168 let file_size = data_size + 36;
169 header_buf.extend_from_slice(&(file_size as u32).to_le_bytes());
170 header_buf.extend_from_slice(b"WAVE");
171
172 header_buf.extend_from_slice(b"fmt ");
173 header_buf.extend_from_slice(&16u32.to_le_bytes());
174 header_buf.extend_from_slice(&format_tag.to_le_bytes());
175 header_buf.extend_from_slice(&(channels as u16).to_le_bytes());
176 header_buf.extend_from_slice(&sample_rate.to_le_bytes());
177
178 let bytes_per_sec: u32 = match format_tag {
179 0x0064 => 8000, _ => sample_rate * (channels as u32) * (bits_per_sample as u32 / 8),
181 };
182 header_buf.extend_from_slice(&bytes_per_sec.to_le_bytes());
183
184 let block_align: u16 = match format_tag {
185 0x0064 | 0x0007 | 0x0006 => 1 * channels,
186 _ => (bits_per_sample / 8) * channels,
187 };
188 header_buf.extend_from_slice(&block_align.to_le_bytes());
189 header_buf.extend_from_slice(&bits_per_sample.to_le_bytes());
190
191 header_buf.extend_from_slice(b"data");
192 header_buf.extend_from_slice(&(data_size as u32).to_le_bytes());
193
194 file.seek(std::io::SeekFrom::Start(0)).await?;
195 file.write_all(&header_buf).await?;
196 file.seek(std::io::SeekFrom::End(0)).await?;
197
198 Ok(())
199 }
200
201 pub async fn process_recording(
202 &self,
203 file_path: &Path,
204 mut receiver: UnboundedReceiver<AudioFrame>,
205 ) -> Result<()> {
206 let first_frame = match receiver.recv().await {
207 Some(f) => f,
208 None => return Ok(()),
209 };
210
211 if let Samples::RTP { .. } = first_frame.samples {
212 return self
213 .process_recording_rtp(file_path, receiver, first_frame)
214 .await;
215 }
216
217 let _requested_format = self.option.format.unwrap_or(RecorderFormat::Wav);
218
219 self.process_recording_wav(file_path, receiver, first_frame)
220 .await
221 }
222
223 fn ensure_parent_dir(&self, file_path: &Path) -> Result<()> {
224 if let Some(parent) = file_path.parent() {
225 if !parent.exists() {
226 if let Err(e) = std::fs::create_dir_all(parent) {
227 warn!(
228 "Failed to create recording file parent directory: {} {}",
229 e,
230 file_path.display()
231 );
232 return Err(anyhow!("Failed to create recording file parent directory"));
233 }
234 }
235 }
236 Ok(())
237 }
238
239 async fn create_output_file(&self, file_path: &Path) -> Result<File> {
240 self.ensure_parent_dir(file_path)?;
241 match File::create(file_path).await {
242 Ok(file) => {
243 info!(
244 session_id = self.session_id,
245 "recorder: created recording file: {}",
246 file_path.display()
247 );
248 Ok(file)
249 }
250 Err(e) => {
251 warn!(
252 "Failed to create recording file: {} {}",
253 e,
254 file_path.display()
255 );
256 Err(anyhow!("Failed to create recording file"))
257 }
258 }
259 }
260
261 async fn process_recording_rtp(
262 &self,
263 file_path: &Path,
264 mut receiver: UnboundedReceiver<AudioFrame>,
265 first_frame: AudioFrame,
266 ) -> Result<()> {
267 let (payload_type, mut file) =
268 if let Samples::RTP { payload_type, .. } = &first_frame.samples {
269 let file = self.create_output_file(file_path).await?;
270 (*payload_type, file)
271 } else {
272 return Err(anyhow!("Invalid frame type for RTP recording"));
273 };
274
275 self.update_wav_header(&mut file, Some(payload_type))
276 .await?;
277
278 if let Samples::RTP { payload, .. } = first_frame.samples {
279 file.write_all(&payload).await?;
280 self.samples_written
281 .fetch_add(payload.len(), Ordering::SeqCst);
282 }
283
284 loop {
285 match receiver.recv().await {
286 Some(frame) => {
287 if let Samples::RTP { payload, .. } = frame.samples {
288 file.write_all(&payload).await?;
289 self.samples_written
290 .fetch_add(payload.len(), Ordering::SeqCst);
291 }
292 }
293 None => break,
294 }
295 }
296
297 self.update_wav_header(&mut file, Some(payload_type))
298 .await?;
299
300 file.sync_all().await?;
301
302 Ok(())
303 }
304
305 async fn process_recording_wav(
306 &self,
307 file_path: &Path,
308 mut receiver: UnboundedReceiver<AudioFrame>,
309 first_frame: AudioFrame,
310 ) -> Result<()> {
311 let mut file = self.create_output_file(file_path).await?;
312 self.update_wav_header(&mut file, None).await?;
313
314 self.append_frame(first_frame).await.ok();
315
316 let chunk_size = (self.option.samplerate / 1000 * self.option.ptime) as usize;
317 info!(
318 session_id = self.session_id,
319 format = "wav",
320 "Recording to {} ptime: {}ms chunk_size: {}",
321 file_path.display(),
322 self.option.ptime,
323 chunk_size
324 );
325
326 let mut interval = IntervalStream::new(tokio::time::interval(Duration::from_millis(
327 self.option.ptime as u64,
328 )));
329 loop {
330 select! {
331 Some(frame) = receiver.recv() => {
332 self.append_frame(frame).await.ok();
333 }
334 _ = interval.next() => {
335 let (mono_buf, stereo_buf) = self.pop(chunk_size).await;
336 self.process_buffers(&mut file, mono_buf, stereo_buf).await?;
337 self.update_wav_header(&mut file, None).await?;
338 }
339 _ = self.cancel_token.cancelled() => {
340 self.flush_buffers(&mut file).await?;
341 self.update_wav_header(&mut file, None).await?;
342 return Ok(());
343 }
344 }
345 }
346 }
347
348 fn get_channel_index(&self, track_id: &str) -> usize {
349 if track_id == self.session_id.as_str() {
350 0
351 } else {
352 1
353 }
354 }
355
356 async fn append_frame(&self, frame: AudioFrame) -> Result<()> {
357 let buffer = match frame.samples {
358 Samples::PCM { samples } => samples,
359 _ => return Ok(()), };
361
362 if buffer.is_empty() {
363 return Ok(());
364 }
365
366 let channel_idx = self.get_channel_index(&frame.track_id);
367 match channel_idx {
368 0 => {
369 let mut mono_buf = self.mono_buf.lock().unwrap();
370 mono_buf.extend(buffer.iter());
371 }
372 1 => {
373 let mut stereo_buf = self.stereo_buf.lock().unwrap();
374 stereo_buf.extend(buffer.iter());
375 }
376 _ => {}
377 }
378
379 Ok(())
380 }
381
382 pub(crate) fn extract_samples(buffer: &mut PcmBuf, extract_size: usize) -> PcmBuf {
383 if extract_size > 0 && !buffer.is_empty() {
384 let take_size = extract_size.min(buffer.len());
385 buffer.drain(..take_size).collect()
386 } else {
387 Vec::new()
388 }
389 }
390
391 async fn pop(&self, chunk_size: usize) -> (PcmBuf, PcmBuf) {
392 let mut mono_buf = self.mono_buf.lock().unwrap();
393 let mut stereo_buf = self.stereo_buf.lock().unwrap();
394
395 let safe_chunk_size = chunk_size.min(16000 * 10);
396
397 let mono_result = if mono_buf.len() >= safe_chunk_size {
398 Self::extract_samples(&mut mono_buf, safe_chunk_size)
399 } else if !mono_buf.is_empty() {
400 let available_len = mono_buf.len();
401 let mut result = Self::extract_samples(&mut mono_buf, available_len);
402 if chunk_size != usize::MAX {
403 result.resize(safe_chunk_size, 0);
404 }
405 result
406 } else {
407 if chunk_size != usize::MAX {
408 vec![0; safe_chunk_size]
409 } else {
410 Vec::new()
411 }
412 };
413
414 let stereo_result = if stereo_buf.len() >= safe_chunk_size {
415 Self::extract_samples(&mut stereo_buf, safe_chunk_size)
416 } else if !stereo_buf.is_empty() {
417 let available_len = stereo_buf.len();
418 let mut result = Self::extract_samples(&mut stereo_buf, available_len);
419 if chunk_size != usize::MAX {
420 result.resize(safe_chunk_size, 0);
421 }
422 result
423 } else {
424 if chunk_size != usize::MAX {
425 vec![0; safe_chunk_size]
426 } else {
427 Vec::new()
428 }
429 };
430
431 if chunk_size == usize::MAX {
432 let max_len = mono_result.len().max(stereo_result.len());
433 let mut mono_final = mono_result;
434 let mut stereo_final = stereo_result;
435 mono_final.resize(max_len, 0);
436 stereo_final.resize(max_len, 0);
437 (mono_final, stereo_final)
438 } else {
439 (mono_result, stereo_result)
440 }
441 }
442
443 pub fn stop_recording(&self) -> Result<()> {
444 self.cancel_token.cancel();
445 Ok(())
446 }
447
448 pub(crate) fn mix_buffers(mono_buf: &PcmBuf, stereo_buf: &PcmBuf) -> Vec<i16> {
449 assert_eq!(
450 mono_buf.len(),
451 stereo_buf.len(),
452 "Buffer lengths must be equal after pop()"
453 );
454
455 let len = mono_buf.len();
456 let mut mix_buff = Vec::with_capacity(len * 2);
457
458 for i in 0..len {
459 mix_buff.push(mono_buf[i]);
460 mix_buff.push(stereo_buf[i]);
461 }
462
463 mix_buff
464 }
465
466 async fn write_audio_data(
467 &self,
468 file: &mut File,
469 mono_buf: &PcmBuf,
470 stereo_buf: &PcmBuf,
471 ) -> Result<usize> {
472 let max_len = mono_buf.len().max(stereo_buf.len());
473 if max_len == 0 {
474 return Ok(0);
475 }
476
477 let mix_buff = Self::mix_buffers(mono_buf, stereo_buf);
478
479 file.seek(std::io::SeekFrom::End(0)).await?;
480 file.write_all(&samples_to_bytes(&mix_buff)).await?;
481
482 Ok(max_len)
483 }
484
485 async fn process_buffers(
486 &self,
487 file: &mut File,
488 mono_buf: PcmBuf,
489 stereo_buf: PcmBuf,
490 ) -> Result<()> {
491 if mono_buf.is_empty() && stereo_buf.is_empty() {
492 return Ok(());
493 }
494 let samples_written = self.write_audio_data(file, &mono_buf, &stereo_buf).await?;
495 if samples_written > 0 {
496 self.samples_written
497 .fetch_add(samples_written * 4, Ordering::SeqCst);
498 }
499 Ok(())
500 }
501
502 async fn flush_buffers(&self, file: &mut File) -> Result<()> {
503 loop {
504 let (mono_buf, stereo_buf) = self.pop(usize::MAX).await;
505
506 if mono_buf.is_empty() && stereo_buf.is_empty() {
507 break;
508 }
509
510 let samples_written = self.write_audio_data(file, &mono_buf, &stereo_buf).await?;
511 if samples_written > 0 {
512 self.samples_written
513 .fetch_add(samples_written * 4, Ordering::SeqCst);
514 }
515 }
516
517 Ok(())
518 }
519}