1use anyhow::{Result, anyhow};
2use audio_codec::{BoxedResampler, PcmBuf, Sample, samples_to_bytes};
3use futures::StreamExt;
4use serde::{Deserialize, Serialize};
5use std::{
6 collections::HashMap,
7 path::Path,
8 sync::{
9 Mutex,
10 atomic::{AtomicU32, AtomicUsize, Ordering},
11 },
12 time::Duration,
13 u32,
14};
15use tokio::{
16 fs::File,
17 io::{AsyncSeekExt, AsyncWriteExt},
18 select,
19 sync::mpsc::UnboundedReceiver,
20};
21use tokio_stream::wrappers::IntervalStream;
22use tokio_util::sync::CancellationToken;
23use tracing::{info, warn};
24
25use crate::media::processor::convert_to_mono;
26use crate::media::{AudioFrame, Samples};
27
28#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
29#[serde(rename_all = "lowercase")]
30pub enum RecorderFormat {
31 Wav,
32 Pcm,
33 Pcmu,
34 Pcma,
35 G722,
36}
37
38impl RecorderFormat {
39 pub fn extension(&self) -> &'static str {
40 "wav"
41 }
42
43 pub fn is_supported(&self) -> bool {
44 true
45 }
46
47 pub fn effective(&self) -> RecorderFormat {
48 *self
49 }
50}
51
52impl Default for RecorderFormat {
53 fn default() -> Self {
54 RecorderFormat::Wav
55 }
56}
57
58#[derive(Debug, Deserialize, Serialize, Clone)]
59#[serde(rename_all = "camelCase")]
60#[serde(default)]
61pub struct RecorderOption {
62 #[serde(default)]
63 pub recorder_file: String,
64 #[serde(default)]
65 pub samplerate: u32,
66 #[serde(default)]
67 pub ptime: u32,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub format: Option<RecorderFormat>,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub native_samplerate: Option<bool>,
76}
77
78impl RecorderOption {
79 pub fn new(recorder_file: String) -> Self {
80 Self {
81 recorder_file,
82 ..Default::default()
83 }
84 }
85
86 pub fn resolved_format(&self, default: RecorderFormat) -> RecorderFormat {
87 self.format.unwrap_or(default).effective()
88 }
89
90 pub fn ensure_path_extension(&mut self, fallback_format: RecorderFormat) {
91 let effective_format = self.format.unwrap_or(fallback_format).effective();
92 self.format = Some(effective_format);
93
94 if self.recorder_file.is_empty() {
95 return;
96 }
97
98 let extension = effective_format.extension();
99 if !self
100 .recorder_file
101 .to_lowercase()
102 .ends_with(&format!(".{}", extension.to_lowercase()))
103 {
104 self.recorder_file = format!("{}.{}", self.recorder_file, extension);
105 }
106 }
107}
108
109impl Default for RecorderOption {
110 fn default() -> Self {
111 Self {
112 recorder_file: "".to_string(),
113 samplerate: 16000,
114 ptime: 200,
115 format: None,
116 native_samplerate: None,
117 }
118 }
119}
120
121const NATIVE_PENDING_FRAME_LIMIT: usize = 250;
124
125#[derive(Debug)]
126struct PendingRaw {
127 channel: usize,
128 samples: PcmBuf,
129 sample_rate: u32,
130}
131
132pub struct Recorder {
133 session_id: String,
134 option: RecorderOption,
135 samples_written: AtomicUsize,
136 cancel_token: CancellationToken,
137 stereo_buf: Mutex<PcmBuf>,
138 mono_buf: Mutex<PcmBuf>,
139 native_samplerate: bool,
140 target_rate: AtomicU32,
143 resamplers: Mutex<HashMap<(usize, u32), BoxedResampler>>,
146 pending: Mutex<Vec<PendingRaw>>,
148}
149
150impl Recorder {
151 pub fn new(
152 cancel_token: CancellationToken,
153 session_id: String,
154 option: RecorderOption,
155 ) -> Self {
156 let native_samplerate = option.native_samplerate.unwrap_or(false);
157 Self {
158 session_id,
159 option,
160 samples_written: AtomicUsize::new(0),
161 cancel_token,
162 stereo_buf: Mutex::new(Vec::new()),
163 mono_buf: Mutex::new(Vec::new()),
164 native_samplerate,
165 target_rate: AtomicU32::new(0),
166 resamplers: Mutex::new(HashMap::new()),
167 pending: Mutex::new(Vec::new()),
168 }
169 }
170
171 fn effective_samplerate(&self) -> u32 {
174 if self.native_samplerate {
175 let latched = self.target_rate.load(Ordering::SeqCst);
176 if latched > 0 {
177 return latched;
178 }
179 }
180 if self.option.samplerate > 0 {
181 self.option.samplerate
182 } else {
183 16000
184 }
185 }
186
187 fn compute_chunk_size(&self) -> usize {
188 if self.native_samplerate && self.target_rate.load(Ordering::SeqCst) == 0 {
189 return 0;
190 }
191 let rate = self.effective_samplerate();
192 (rate / 1000 * self.option.ptime.max(1)) as usize
193 }
194
195 async fn update_wav_header(&self, file: &mut File, payload_type: Option<u8>) -> Result<()> {
196 let total = self.samples_written.load(Ordering::SeqCst);
197
198 let (format_tag, sample_rate, channels, bits_per_sample, data_size): (
199 u16,
200 u32,
201 u16,
202 u16,
203 usize,
204 ) = match payload_type {
205 Some(pt) => {
206 let (tag, rate, chan): (u16, u32, u16) = match pt {
207 0 => (0x0007, 8000, 1), 8 => (0x0006, 8000, 1), 9 => (0x0064, 16000, 1), 10 => (0x0001, 44100, 2), 11 => (0x0001, 44100, 1), _ => (0x0001, 16000, 1), };
214 let bits: u16 = match pt {
215 9 => 4,
216 0 | 8 => 8,
217 _ => 16,
218 };
219 (tag, rate, chan, bits, total)
220 }
221 None => (0x0001, self.effective_samplerate(), 2, 16, total),
222 };
223
224 let mut header_buf = Vec::new();
225 header_buf.extend_from_slice(b"RIFF");
226 let file_size = data_size + 36;
227 header_buf.extend_from_slice(&(file_size as u32).to_le_bytes());
228 header_buf.extend_from_slice(b"WAVE");
229
230 header_buf.extend_from_slice(b"fmt ");
231 header_buf.extend_from_slice(&16u32.to_le_bytes());
232 header_buf.extend_from_slice(&format_tag.to_le_bytes());
233 header_buf.extend_from_slice(&(channels as u16).to_le_bytes());
234 header_buf.extend_from_slice(&sample_rate.to_le_bytes());
235
236 let bytes_per_sec: u32 = match format_tag {
237 0x0064 => 8000, _ => sample_rate * (channels as u32) * (bits_per_sample as u32 / 8),
239 };
240 header_buf.extend_from_slice(&bytes_per_sec.to_le_bytes());
241
242 let block_align: u16 = match format_tag {
243 0x0064 | 0x0007 | 0x0006 => 1 * channels,
244 _ => (bits_per_sample / 8) * channels,
245 };
246 header_buf.extend_from_slice(&block_align.to_le_bytes());
247 header_buf.extend_from_slice(&bits_per_sample.to_le_bytes());
248
249 header_buf.extend_from_slice(b"data");
250 header_buf.extend_from_slice(&(data_size as u32).to_le_bytes());
251
252 file.seek(std::io::SeekFrom::Start(0)).await?;
253 file.write_all(&header_buf).await?;
254 file.seek(std::io::SeekFrom::End(0)).await?;
255
256 Ok(())
257 }
258
259 pub async fn process_recording(
260 &self,
261 file_path: &Path,
262 mut receiver: UnboundedReceiver<AudioFrame>,
263 ) -> Result<()> {
264 let first_frame = match receiver.recv().await {
265 Some(f) => f,
266 None => return Ok(()),
267 };
268
269 if let Samples::RTP { .. } = first_frame.samples {
270 return self
271 .process_recording_rtp(file_path, receiver, first_frame)
272 .await;
273 }
274
275 let _requested_format = self.option.format.unwrap_or(RecorderFormat::Wav);
276
277 self.process_recording_wav(file_path, receiver, first_frame)
278 .await
279 }
280
281 fn ensure_parent_dir(&self, file_path: &Path) -> Result<()> {
282 if let Some(parent) = file_path.parent() {
283 if !parent.exists() {
284 if let Err(e) = std::fs::create_dir_all(parent) {
285 warn!(
286 "Failed to create recording file parent directory: {} {}",
287 e,
288 file_path.display()
289 );
290 return Err(anyhow!("Failed to create recording file parent directory"));
291 }
292 }
293 }
294 Ok(())
295 }
296
297 async fn create_output_file(&self, file_path: &Path) -> Result<File> {
298 self.ensure_parent_dir(file_path)?;
299 match File::create(file_path).await {
300 Ok(file) => {
301 info!(
302 session_id = self.session_id,
303 "recorder: created recording file: {}",
304 file_path.display()
305 );
306 Ok(file)
307 }
308 Err(e) => {
309 warn!(
310 "Failed to create recording file: {} {}",
311 e,
312 file_path.display()
313 );
314 Err(anyhow!("Failed to create recording file"))
315 }
316 }
317 }
318
319 async fn process_recording_rtp(
320 &self,
321 file_path: &Path,
322 mut receiver: UnboundedReceiver<AudioFrame>,
323 first_frame: AudioFrame,
324 ) -> Result<()> {
325 let (payload_type, mut file) =
326 if let Samples::RTP { payload_type, .. } = &first_frame.samples {
327 let file = self.create_output_file(file_path).await?;
328 (*payload_type, file)
329 } else {
330 return Err(anyhow!("Invalid frame type for RTP recording"));
331 };
332
333 self.update_wav_header(&mut file, Some(payload_type))
334 .await?;
335
336 if let Samples::RTP { payload, .. } = first_frame.samples {
337 file.write_all(&payload).await?;
338 self.samples_written
339 .fetch_add(payload.len(), Ordering::SeqCst);
340 }
341
342 loop {
343 match receiver.recv().await {
344 Some(frame) => {
345 if let Samples::RTP { payload, .. } = frame.samples {
346 file.write_all(&payload).await?;
347 self.samples_written
348 .fetch_add(payload.len(), Ordering::SeqCst);
349 }
350 }
351 None => break,
352 }
353 }
354
355 self.update_wav_header(&mut file, Some(payload_type))
356 .await?;
357
358 file.sync_all().await?;
359
360 Ok(())
361 }
362
363 async fn process_recording_wav(
364 &self,
365 file_path: &Path,
366 mut receiver: UnboundedReceiver<AudioFrame>,
367 first_frame: AudioFrame,
368 ) -> Result<()> {
369 let mut file = self.create_output_file(file_path).await?;
370 self.update_wav_header(&mut file, None).await?;
371
372 self.append_frame(first_frame).await.ok();
373
374 if self.native_samplerate {
375 info!(
376 session_id = self.session_id,
377 format = "wav",
378 "Recording to {} in native samplerate mode (rate detected from caller leg)",
379 file_path.display()
380 );
381 } else {
382 let chunk_size = self.compute_chunk_size();
383 info!(
384 session_id = self.session_id,
385 format = "wav",
386 "Recording to {} ptime: {}ms chunk_size: {}",
387 file_path.display(),
388 self.option.ptime,
389 chunk_size
390 );
391 }
392
393 let mut interval = IntervalStream::new(tokio::time::interval(Duration::from_millis(
394 self.option.ptime.max(1) as u64,
395 )));
396 loop {
397 select! {
398 Some(frame) = receiver.recv() => {
399 self.append_frame(frame).await.ok();
400 }
401 _ = interval.next() => {
402 let chunk_size = self.compute_chunk_size();
403 if chunk_size == 0 {
404 continue;
405 }
406 let (mono_buf, stereo_buf) = self.pop(chunk_size).await;
407 self.process_buffers(&mut file, mono_buf, stereo_buf).await?;
408 self.update_wav_header(&mut file, None).await?;
409 }
410 _ = self.cancel_token.cancelled() => {
411 self.flush_buffers(&mut file).await?;
412 self.update_wav_header(&mut file, None).await?;
413 return Ok(());
414 }
415 }
416 }
417 }
418
419 fn get_channel_index(&self, track_id: &str) -> usize {
420 if track_id == self.session_id.as_str() {
421 0
422 } else {
423 1
424 }
425 }
426
427 fn normalize_samples(
430 &self,
431 channel: usize,
432 samples: PcmBuf,
433 src_rate: u32,
434 target: u32,
435 ) -> PcmBuf {
436 if src_rate == target || samples.is_empty() {
437 return samples;
438 }
439 let mut resamplers = self.resamplers.lock().unwrap();
440 let resampler = resamplers.entry((channel, src_rate)).or_insert_with(|| {
441 BoxedResampler::new(src_rate as usize, target as usize)
442 .expect("invalid sample rate for resampler")
443 });
444 resampler.resample(&samples)
445 }
446
447 fn push_channel(&self, channel_idx: usize, samples: &[Sample]) {
448 match channel_idx {
449 0 => {
450 let mut mono_buf = self.mono_buf.lock().unwrap();
451 mono_buf.extend_from_slice(samples);
452 }
453 1 => {
454 let mut stereo_buf = self.stereo_buf.lock().unwrap();
455 stereo_buf.extend_from_slice(samples);
456 }
457 _ => {}
458 }
459 }
460
461 fn flush_pending(&self, target: u32) {
463 let pending = {
464 let mut pending = self.pending.lock().unwrap();
465 std::mem::take(&mut *pending)
466 };
467 for frame in pending {
468 let normalized =
469 self.normalize_samples(frame.channel, frame.samples, frame.sample_rate, target);
470 self.push_channel(frame.channel, &normalized);
471 }
472 }
473
474 async fn append_frame(&self, frame: AudioFrame) -> Result<()> {
475 let mut samples = match frame.samples {
476 Samples::PCM { samples } => samples,
477 _ => return Ok(()), };
479
480 if samples.is_empty() {
481 return Ok(());
482 }
483
484 if frame.channels == 2 {
485 convert_to_mono(&mut samples, 2);
486 }
487
488 let channel_idx = self.get_channel_index(&frame.track_id);
489 let src_rate = if frame.sample_rate > 0 {
490 frame.sample_rate
491 } else {
492 16000
493 };
494
495 if self.native_samplerate {
496 let mut target = self.target_rate.load(Ordering::SeqCst);
497 if target == 0 {
498 let should_latch = channel_idx == 0 || {
502 self.pending.lock().unwrap().len() >= NATIVE_PENDING_FRAME_LIMIT
503 };
504 if should_latch {
505 target = src_rate;
506 self.target_rate.store(target, Ordering::SeqCst);
507 info!(
508 session_id = self.session_id,
509 native_samplerate = target,
510 "recorder: detected native samplerate"
511 );
512 self.flush_pending(target);
513 } else {
514 let mut pending = self.pending.lock().unwrap();
515 pending.push(PendingRaw {
516 channel: channel_idx,
517 samples,
518 sample_rate: src_rate,
519 });
520 return Ok(());
521 }
522 }
523 let normalized = self.normalize_samples(channel_idx, samples, src_rate, target);
524 self.push_channel(channel_idx, &normalized);
525 return Ok(());
526 }
527
528 self.push_channel(channel_idx, &samples);
529
530 Ok(())
531 }
532
533 pub(crate) fn extract_samples(buffer: &mut PcmBuf, extract_size: usize) -> PcmBuf {
534 if extract_size > 0 && !buffer.is_empty() {
535 let take_size = extract_size.min(buffer.len());
536 buffer.drain(..take_size).collect()
537 } else {
538 Vec::new()
539 }
540 }
541
542 async fn pop(&self, chunk_size: usize) -> (PcmBuf, PcmBuf) {
543 let mut mono_buf = self.mono_buf.lock().unwrap();
544 let mut stereo_buf = self.stereo_buf.lock().unwrap();
545
546 let safe_chunk_size = chunk_size.min((self.effective_samplerate() as usize) * 10);
548
549 let mono_result = if mono_buf.len() >= safe_chunk_size {
550 Self::extract_samples(&mut mono_buf, safe_chunk_size)
551 } else if !mono_buf.is_empty() {
552 let available_len = mono_buf.len();
553 let mut result = Self::extract_samples(&mut mono_buf, available_len);
554 if chunk_size != usize::MAX {
555 result.resize(safe_chunk_size, 0);
556 }
557 result
558 } else {
559 if chunk_size != usize::MAX {
560 vec![0; safe_chunk_size]
561 } else {
562 Vec::new()
563 }
564 };
565
566 let stereo_result = if stereo_buf.len() >= safe_chunk_size {
567 Self::extract_samples(&mut stereo_buf, safe_chunk_size)
568 } else if !stereo_buf.is_empty() {
569 let available_len = stereo_buf.len();
570 let mut result = Self::extract_samples(&mut stereo_buf, available_len);
571 if chunk_size != usize::MAX {
572 result.resize(safe_chunk_size, 0);
573 }
574 result
575 } else {
576 if chunk_size != usize::MAX {
577 vec![0; safe_chunk_size]
578 } else {
579 Vec::new()
580 }
581 };
582
583 if chunk_size == usize::MAX {
584 let max_len = mono_result.len().max(stereo_result.len());
585 let mut mono_final = mono_result;
586 let mut stereo_final = stereo_result;
587 mono_final.resize(max_len, 0);
588 stereo_final.resize(max_len, 0);
589 (mono_final, stereo_final)
590 } else {
591 (mono_result, stereo_result)
592 }
593 }
594
595 pub fn stop_recording(&self) -> Result<()> {
596 self.cancel_token.cancel();
597 Ok(())
598 }
599
600 pub(crate) fn mix_buffers(mono_buf: &PcmBuf, stereo_buf: &PcmBuf) -> Vec<i16> {
601 assert_eq!(
602 mono_buf.len(),
603 stereo_buf.len(),
604 "Buffer lengths must be equal after pop()"
605 );
606
607 let len = mono_buf.len();
608 let mut mix_buff = Vec::with_capacity(len * 2);
609
610 for i in 0..len {
611 mix_buff.push(mono_buf[i]);
612 mix_buff.push(stereo_buf[i]);
613 }
614
615 mix_buff
616 }
617
618 async fn write_audio_data(
619 &self,
620 file: &mut File,
621 mono_buf: &PcmBuf,
622 stereo_buf: &PcmBuf,
623 ) -> Result<usize> {
624 let max_len = mono_buf.len().max(stereo_buf.len());
625 if max_len == 0 {
626 return Ok(0);
627 }
628
629 let mix_buff = Self::mix_buffers(mono_buf, stereo_buf);
630
631 file.seek(std::io::SeekFrom::End(0)).await?;
632 file.write_all(&samples_to_bytes(&mix_buff)).await?;
633
634 Ok(max_len)
635 }
636
637 async fn process_buffers(
638 &self,
639 file: &mut File,
640 mono_buf: PcmBuf,
641 stereo_buf: PcmBuf,
642 ) -> Result<()> {
643 if mono_buf.is_empty() && stereo_buf.is_empty() {
644 return Ok(());
645 }
646 let samples_written = self.write_audio_data(file, &mono_buf, &stereo_buf).await?;
647 if samples_written > 0 {
648 self.samples_written
649 .fetch_add(samples_written * 4, Ordering::SeqCst);
650 }
651 Ok(())
652 }
653
654 async fn flush_buffers(&self, file: &mut File) -> Result<()> {
655 loop {
656 let (mono_buf, stereo_buf) = self.pop(usize::MAX).await;
657
658 if mono_buf.is_empty() && stereo_buf.is_empty() {
659 break;
660 }
661
662 let samples_written = self.write_audio_data(file, &mono_buf, &stereo_buf).await?;
663 if samples_written > 0 {
664 self.samples_written
665 .fetch_add(samples_written * 4, Ordering::SeqCst);
666 }
667 }
668
669 Ok(())
670 }
671}