1use crate::callrecord::CallRecordHangupReason;
2use crate::event::{EventSender, SessionEvent};
3use crate::media::processor::ProcessorChain;
4use crate::media::{AudioFrame, PcmBuf, Samples, TrackId};
5use crate::media::track::{Track, TrackConfig, TrackPacketSender};
6use anyhow::{Result, anyhow};
7use async_trait::async_trait;
8use audio_codec::BoxedResampler;
9use hound::WavReader;
10use std::cmp::min;
11use std::fs::File;
12use std::io::BufReader;
13use std::sync::{
14 Arc,
15 atomic::{AtomicBool, Ordering},
16};
17use std::time::Instant;
18use tokio::select;
19use tokio::time::Duration;
20use tokio_util::sync::CancellationToken;
21use tracing::{debug, info, warn};
22
23trait AudioReader: Send {
24 fn fill_buffer(&mut self) -> Result<usize>;
25
26 fn read_chunk(&mut self, packet_duration_ms: u32) -> Result<Option<(PcmBuf, u32)>> {
27 let max_chunk_size = self.sample_rate() as usize * packet_duration_ms as usize / 1000;
28
29 if self.buffer_size() == 0 || self.position() >= self.buffer_size() {
30 let samples_read = self.fill_buffer()?;
31 if samples_read == 0 {
32 return Ok(None);
33 }
34 self.set_position(0);
35 }
36
37 let remaining = self.buffer_size() - self.position();
38 if remaining == 0 {
39 return Ok(None);
40 }
41
42 let chunk_size = min(max_chunk_size, remaining);
43 let end_pos = self.position() + chunk_size;
44
45 assert!(
46 end_pos <= self.buffer_size(),
47 "Buffer overrun: pos={}, end={}, size={}",
48 self.position(),
49 end_pos,
50 self.buffer_size()
51 );
52
53 let chunk = self.extract_chunk(self.position(), end_pos);
54 self.set_position(end_pos);
55
56 let final_chunk =
57 if self.sample_rate() != self.target_sample_rate() && self.sample_rate() > 0 {
58 self.resample_chunk(&chunk)
59 } else {
60 chunk
61 };
62
63 Ok(Some((final_chunk, self.target_sample_rate())))
64 }
65
66 fn buffer_size(&self) -> usize;
67 fn position(&self) -> usize;
68 fn set_position(&mut self, pos: usize);
69 fn sample_rate(&self) -> u32;
70 fn target_sample_rate(&self) -> u32;
71 fn channels(&self) -> u16;
72 fn extract_chunk(&self, start: usize, end: usize) -> Vec<i16>;
73 fn resample_chunk(&mut self, chunk: &[i16]) -> Vec<i16>;
74}
75
76struct DecodedAudioReader {
77 buffer: Vec<i16>,
78 sample_rate: u32,
79 position: usize,
80 target_sample_rate: u32,
81 resampler: Option<BoxedResampler>,
82}
83
84impl DecodedAudioReader {
85 #[cfg(test)]
86 fn from_file(
87 file: File,
88 extension: &str,
89 mime_type: Option<&str>,
90 target_sample_rate: u32,
91 ) -> Result<Self> {
92 let all_samples =
93 crate::media::loader::decode_audio(file, extension, mime_type, target_sample_rate)?;
94 Ok(Self {
95 buffer: all_samples,
96 sample_rate: target_sample_rate,
97 position: 0,
98 target_sample_rate,
99 resampler: None,
100 })
101 }
102
103 fn from_samples(buffer: Vec<i16>, sample_rate: u32, target_sample_rate: u32) -> Self {
107 Self {
108 buffer,
109 sample_rate,
110 position: 0,
111 target_sample_rate,
112 resampler: None,
113 }
114 }
115}
116
117impl AudioReader for DecodedAudioReader {
118 fn fill_buffer(&mut self) -> Result<usize> {
119 if self.position >= self.buffer.len() {
120 return Ok(0);
121 }
122 Ok(self.buffer.len() - self.position)
123 }
124
125 fn buffer_size(&self) -> usize {
126 self.buffer.len()
127 }
128
129 fn position(&self) -> usize {
130 self.position
131 }
132
133 fn set_position(&mut self, pos: usize) {
134 self.position = pos;
135 }
136
137 fn sample_rate(&self) -> u32 {
138 self.sample_rate
139 }
140
141 fn target_sample_rate(&self) -> u32 {
142 self.target_sample_rate
143 }
144
145 fn channels(&self) -> u16 {
146 1
147 }
148
149 fn extract_chunk(&self, start: usize, end: usize) -> Vec<i16> {
150 self.buffer[start..end].to_vec()
151 }
152
153 fn resample_chunk(&mut self, chunk: &[i16]) -> Vec<i16> {
154 if self.sample_rate == 0 || self.sample_rate == self.target_sample_rate {
155 return chunk.to_vec();
156 }
157
158 if let Some(resampler) = &mut self.resampler {
159 resampler.resample(chunk)
160 } else {
161 let mut new_resampler = BoxedResampler::new(
162 self.sample_rate as usize,
163 self.target_sample_rate as usize,
164 )
165 .expect("invalid sample rate");
166 let result = new_resampler.resample(chunk);
167 self.resampler = Some(new_resampler);
168 result
169 }
170 }
171}
172
173async fn process_audio_reader(
175 mut processor_chain: ProcessorChain,
176 mut audio_reader: Box<dyn AudioReader>,
177 track_id: &str,
178 packet_duration_ms: u32,
179 target_sample_rate: u32,
180 token: CancellationToken,
181 paused: Arc<AtomicBool>,
182 packet_sender: TrackPacketSender,
183) -> Result<()> {
184 info!(
185 "streaming audio with target_sample_rate: {}, packet_duration: {}ms",
186 target_sample_rate, packet_duration_ms
187 );
188 let stream_loop = async move {
189 let start_time = Instant::now();
190 let mut ticker = tokio::time::interval(Duration::from_millis(packet_duration_ms as u64));
191 let channels = audio_reader.channels();
192 loop {
193 if paused.load(Ordering::Relaxed) {
194 ticker.tick().await;
195 continue;
196 }
197
198 let Some((chunk, chunk_sample_rate)) = audio_reader.read_chunk(packet_duration_ms)?
199 else {
200 break;
201 };
202
203 let mut packet = AudioFrame {
204 track_id: track_id.to_string(),
205 timestamp: crate::media::get_timestamp(),
206 samples: Samples::PCM { samples: chunk },
207 sample_rate: chunk_sample_rate,
208 channels,
209 ..Default::default()
210 };
211
212 match processor_chain.process_frame(&mut packet) {
213 Ok(_) => {}
214 Err(e) => {
215 warn!("failed to process audio packet: {}", e);
216 }
217 }
218
219 if let Err(e) = packet_sender.send(packet) {
220 warn!("failed to send audio packet: {}", e);
221 break;
222 }
223
224 ticker.tick().await;
225 }
226
227 info!("stream loop finished in {:?}", start_time.elapsed());
228 Ok(()) as Result<()>
229 };
230
231 select! {
232 _ = token.cancelled() => {
233 info!("stream cancelled");
234 return Ok(());
235 }
236 result = stream_loop => {
237 info!("stream loop finished");
238 result
239 }
240 }
241}
242
243pub struct FileTrack {
244 track_id: TrackId,
245 play_id: Option<String>,
246 config: TrackConfig,
247 cancel_token: CancellationToken,
248 processor_chain: ProcessorChain,
249 path: Option<String>,
250 use_cache: bool,
251 ssrc: u32,
252 offset_ms: u32,
253 hangup_reason: Option<CallRecordHangupReason>,
256 paused: Arc<AtomicBool>,
257}
258
259impl FileTrack {
260 pub fn new(id: TrackId) -> Self {
261 let config = TrackConfig::default();
262 Self {
263 track_id: id,
264 play_id: None,
265 processor_chain: ProcessorChain::new(config.samplerate),
266 config,
267 cancel_token: CancellationToken::new(),
268 path: None,
269 use_cache: true,
270 ssrc: 0,
271 offset_ms: 0,
272 hangup_reason: None,
273 paused: Arc::new(AtomicBool::new(false)),
274 }
275 }
276
277 pub fn with_play_id(mut self, play_id: Option<String>) -> Self {
278 self.play_id = play_id;
279 self
280 }
281
282 pub fn with_ssrc(mut self, ssrc: u32) -> Self {
283 self.ssrc = ssrc;
284 self
285 }
286 pub fn with_config(mut self, config: TrackConfig) -> Self {
287 self.config = config;
288 self
289 }
290
291 pub fn with_cancel_token(mut self, cancel_token: CancellationToken) -> Self {
292 self.cancel_token = cancel_token;
293 self
294 }
295
296 pub fn with_path(mut self, path: String) -> Self {
297 self.path = Some(path);
298 self
299 }
300
301 pub fn with_sample_rate(mut self, sample_rate: u32) -> Self {
302 self.config = self.config.with_sample_rate(sample_rate);
303 self
304 }
305
306 pub fn with_ptime(mut self, ptime: Duration) -> Self {
307 self.config = self.config.with_ptime(ptime);
308 self
309 }
310
311 pub fn with_cache_enabled(mut self, use_cache: bool) -> Self {
312 self.use_cache = use_cache;
313 self
314 }
315
316 pub fn with_offset_ms(mut self, offset_ms: u32) -> Self {
317 self.offset_ms = offset_ms;
318 self
319 }
320
321 pub fn with_auto_hangup(mut self, auto_hangup: Option<bool>) -> Self {
323 self.hangup_reason = if auto_hangup == Some(true) {
324 Some(CallRecordHangupReason::BySystem)
325 } else {
326 None
327 };
328 self
329 }
330}
331
332#[async_trait]
333impl Track for FileTrack {
334 fn ssrc(&self) -> u32 {
335 self.ssrc
336 }
337 fn id(&self) -> &TrackId {
338 &self.track_id
339 }
340 fn config(&self) -> &TrackConfig {
341 &self.config
342 }
343 fn set_paused(&self, paused: bool) -> bool {
344 self.paused.store(paused, Ordering::Relaxed);
345 true
346 }
347 fn is_paused(&self) -> bool {
348 self.paused.load(Ordering::Relaxed)
349 }
350 fn processor_chain(&mut self) -> &mut ProcessorChain {
351 &mut self.processor_chain
352 }
353
354 async fn handshake(&mut self, _offer: String, _timeout: Option<Duration>) -> Result<String> {
355 Ok("".to_string())
356 }
357 async fn update_remote_description(&mut self, _answer: &String) -> Result<()> {
358 Ok(())
359 }
360
361 async fn start(
362 &mut self,
363 event_sender: EventSender,
364 packet_sender: TrackPacketSender,
365 ) -> Result<()> {
366 if self.path.is_none() {
367 return Err(anyhow::anyhow!("filetrack: No path provided for FileTrack"));
368 }
369 let path = self.path.clone().unwrap();
370 let id = self.track_id.clone();
371 let sample_rate = self.config.samplerate;
372 let use_cache = self.use_cache;
373 let packet_duration_ms = self.config.ptime.as_millis() as u32;
374 let processor_chain = self.processor_chain.clone();
375 let token = self.cancel_token.clone();
376 let start_time = crate::media::get_timestamp();
377 let ssrc = self.ssrc;
378 let offset_ms = self.offset_ms;
379 let paused = self.paused.clone();
380 let play_id = self.play_id.clone();
382 let hangup_reason = self.hangup_reason.clone();
383 crate::spawn(async move {
384 let res = async move {
385 let load_result = crate::media::loader::load_audio_as_pcm_cached(
388 &path,
389 sample_rate,
390 use_cache,
391 offset_ms,
392 )
393 .await;
394 let samples = match load_result {
395 Ok(samples) => samples,
396 Err(e) => {
397 warn!("filetrack: Error loading audio: {} {}", path, e);
398 event_sender
399 .send(SessionEvent::Error {
400 track_id: id.clone(),
401 timestamp: crate::media::get_timestamp(),
402 sender: format!("filetrack: {}", path),
403 error: e.to_string(),
404 code: None,
405 })
406 .ok();
407 event_sender
408 .send(SessionEvent::TrackEnd {
409 track_id: id,
410 timestamp: crate::media::get_timestamp(),
411 duration: crate::media::get_timestamp() - start_time,
412 ssrc,
413 play_id: play_id.clone(),
414 auto_hangup: hangup_reason.clone(),
415 })
416 .ok();
417 return Err(e);
418 }
419 };
420
421 let stream_result = stream_pcm_samples(
423 processor_chain,
424 samples,
425 sample_rate,
426 &id,
427 packet_duration_ms,
428 token.clone(),
429 paused,
430 packet_sender,
431 )
432 .await;
433
434 if let Err(e) = stream_result {
436 warn!("filetrack: Error streaming audio: {}, {}", path, e);
437 event_sender
438 .send(SessionEvent::Error {
439 track_id: id.clone(),
440 timestamp: crate::media::get_timestamp(),
441 sender: format!("filetrack: {}", path),
442 error: e.to_string(),
443 code: None,
444 })
445 .ok();
446 }
447
448 event_sender
450 .send(SessionEvent::TrackEnd {
451 track_id: id,
452 timestamp: crate::media::get_timestamp(),
453 duration: crate::media::get_timestamp() - start_time,
454 ssrc,
455 play_id,
456 auto_hangup: if token.is_cancelled() {
457 None
458 } else {
459 hangup_reason
460 },
461 })
462 .ok();
463 Ok::<(), anyhow::Error>(())
464 }
465 .await;
466 if let Err(e) = res {
467 debug!("filetrack: streaming task finished with error: {:?}", e);
468 }
469 });
470 Ok(())
471 }
472
473 async fn stop(&self) -> Result<()> {
474 self.cancel_token.cancel();
476 Ok(())
477 }
478
479 async fn send_packet(&mut self, _packet: &AudioFrame) -> Result<()> {
481 Ok(())
482 }
483}
484
485async fn stream_pcm_samples(
487 processor_chain: ProcessorChain,
488 samples: Vec<i16>,
489 target_sample_rate: u32,
490 track_id: &str,
491 packet_duration_ms: u32,
492 token: CancellationToken,
493 paused: Arc<AtomicBool>,
494 packet_sender: TrackPacketSender,
495) -> Result<()> {
496 let reader =
497 DecodedAudioReader::from_samples(samples, target_sample_rate, target_sample_rate);
498 let audio_reader = Box::new(reader) as Box<dyn AudioReader>;
499 info!(
500 "filetrack: streaming {} decoded samples at {} Hz",
501 audio_reader.buffer_size(),
502 audio_reader.sample_rate(),
503 );
504 process_audio_reader(
505 processor_chain,
506 audio_reader,
507 track_id,
508 packet_duration_ms,
509 target_sample_rate,
510 token,
511 paused,
512 packet_sender,
513 )
514 .await
515}
516
517pub fn read_wav_file(path: &str) -> Result<(PcmBuf, u32)> {
519 let reader = BufReader::new(File::open(path)?);
520 let mut wav_reader = WavReader::new(reader)?;
521 let spec = wav_reader.spec();
522 let mut all_samples = Vec::new();
523
524 match spec.sample_format {
525 hound::SampleFormat::Int => match spec.bits_per_sample {
526 16 => {
527 for sample in wav_reader.samples::<i16>() {
528 all_samples.push(sample.unwrap_or(0));
529 }
530 }
531 8 => {
532 for sample in wav_reader.samples::<i8>() {
533 all_samples.push(sample.unwrap_or(0) as i16);
534 }
535 }
536 24 | 32 => {
537 for sample in wav_reader.samples::<i32>() {
538 all_samples.push((sample.unwrap_or(0) >> 16) as i16);
539 }
540 }
541 _ => {
542 return Err(anyhow!(
543 "Unsupported bits per sample: {}",
544 spec.bits_per_sample
545 ));
546 }
547 },
548 hound::SampleFormat::Float => {
549 for sample in wav_reader.samples::<f32>() {
550 all_samples.push((sample.unwrap_or(0.0) * 32767.0) as i16);
551 }
552 }
553 }
554
555 if spec.channels == 2 {
557 let mono_samples = all_samples
558 .chunks(2)
559 .map(|chunk| ((chunk[0] as i32 + chunk[1] as i32) / 2) as i16)
560 .collect();
561 all_samples = mono_samples;
562 }
563 Ok((all_samples, spec.sample_rate))
564}
565
566#[cfg(test)]
567mod tests {
568 use super::*;
569 use std::io::Write;
570
571 #[tokio::test]
572 async fn test_wav_reader() -> Result<()> {
573 let file_path = "fixtures/sample.wav";
574 let file = File::open(file_path)?;
575 let mut reader = DecodedAudioReader::from_file(file, "wav", None, 16000)?;
576 let mut total_samples = 0;
577 let mut total_duration_ms = 0.0;
578 let mut chunk_count = 0;
579 while let Some((chunk, chunk_sample_rate)) = reader.read_chunk(20)? {
580 total_samples += chunk.len();
581 chunk_count += 1;
582 let chunk_duration_ms = (chunk.len() as f64 / chunk_sample_rate as f64) * 1000.0;
583 total_duration_ms += chunk_duration_ms;
584 }
585
586 let duration_seconds = total_duration_ms / 1000.0;
587 println!("Total chunks: {}", chunk_count);
588 println!("Actual samples: {}", total_samples);
589 println!("Actual duration: {:.2} seconds", duration_seconds);
590 assert_eq!(format!("{:.2}", duration_seconds), "7.51");
591 Ok(())
592 }
593 #[tokio::test]
594 async fn test_wav_file_track() -> Result<()> {
595 println!("Starting WAV file track test");
596
597 let file_path = "fixtures/sample.wav";
598 let file = File::open(file_path)?;
599
600 let mut reader = hound::WavReader::new(File::open(file_path)?)?;
602 let spec = reader.spec();
603 let total_expected_samples = reader.duration() as usize;
604 let expected_duration = total_expected_samples as f64 / spec.sample_rate as f64;
605 println!("WAV file spec: {:?}", spec);
606 println!("Expected samples: {}", total_expected_samples);
607 println!("Expected duration: {:.2} seconds", expected_duration);
608
609 let mut verify_samples = Vec::new();
611 for sample in reader.samples::<i16>() {
612 verify_samples.push(sample?);
613 }
614 println!("Verified total samples: {}", verify_samples.len());
615
616 let mut reader = DecodedAudioReader::from_file(file, "wav", None, 16000)?;
617 let mut total_samples = 0;
618 let mut total_duration_ms = 0.0;
619 let mut chunk_count = 0;
620
621 while let Some((chunk, chunk_sample_rate)) = reader.read_chunk(320)? {
622 total_samples += chunk.len();
623 chunk_count += 1;
624 let chunk_duration_ms = (chunk.len() as f64 / chunk_sample_rate as f64) * 1000.0;
626 total_duration_ms += chunk_duration_ms;
627 }
628
629 let duration_seconds = total_duration_ms / 1000.0;
630 println!("Total chunks: {}", chunk_count);
631 println!("Actual samples: {}", total_samples);
632 println!("Actual duration: {:.2} seconds", duration_seconds);
633
634 const TOLERANCE: f64 = 0.01; let expected_samples = if spec.channels == 2 {
639 total_expected_samples / 2 } else {
641 total_expected_samples
642 };
643
644 assert!(
645 (duration_seconds - expected_duration).abs() < expected_duration * TOLERANCE,
646 "Duration {:.2} differs from expected {:.2} by more than {}%",
647 duration_seconds,
648 expected_duration,
649 TOLERANCE * 100.0
650 );
651
652 assert!(
653 (total_samples as f64 - expected_samples as f64).abs()
654 < expected_samples as f64 * TOLERANCE,
655 "Sample count {} differs from expected {} by more than {}%",
656 total_samples,
657 expected_samples,
658 TOLERANCE * 100.0
659 );
660
661 Ok(())
662 }
663
664 #[tokio::test]
665 async fn test_mp3_decode_sample_file() -> Result<()> {
666 let file_path = "fixtures/sample.mp3".to_string();
667 match File::open(&file_path) {
668 Ok(file) => {
669 let samples = crate::media::loader::decode_audio(file, "mp3", None, 16000)?;
670 assert!(
671 !samples.is_empty(),
672 "Decoded sample list should not be empty"
673 );
674 }
675 Err(_) => {
676 println!("Skipping MP3 test: sample file not found at {}", file_path);
677 }
678 }
679 Ok(())
680 }
681
682 #[tokio::test]
683 async fn test_mp3_file_track() -> Result<()> {
684 println!("Starting MP3 file track test");
685
686 let file_path = "fixtures/sample.mp3".to_string();
688 let file = File::open(&file_path)?;
689 let sample_rate = 16000;
690 let mut reader = DecodedAudioReader::from_file(file, "mp3", None, sample_rate)?;
691 let mut total_samples = 0;
692 let mut total_duration_ms = 0.0;
693 while let Some((chunk, _chunk_sample_rate)) = reader.read_chunk(320)? {
694 total_samples += chunk.len();
695 let chunk_duration_ms = (chunk.len() as f64 / sample_rate as f64) * 1000.0;
697 total_duration_ms += chunk_duration_ms;
698 }
699 let duration_seconds = total_duration_ms / 1000.0;
700 println!("Total samples: {}", total_samples);
701 println!("Duration: {:.2} seconds", duration_seconds);
702
703 const EXPECTED_SAMPLES: usize = 226310;
704 assert!(
705 total_samples == EXPECTED_SAMPLES,
706 "Sample count {} does not match expected {}",
707 total_samples,
708 EXPECTED_SAMPLES
709 );
710 Ok(())
711 }
712
713 #[tokio::test]
714 #[ignore = "manual debug helper: dumps raw pcm for ffplay"]
715 async fn dump_mp3_as_16k_pcm_for_ffplay() -> Result<()> {
716 let input_path = "fixtures/sample.mp3";
717 let output_path = "target/tmp/sample_16k_mono_s16le.pcm";
718
719 let file = File::open(input_path)?;
720 let mut reader = DecodedAudioReader::from_file(file, "mp3", None, 16000)?;
721
722 let mut pcm_samples = Vec::<i16>::new();
723 while let Some((chunk, _chunk_sample_rate)) = reader.read_chunk(320)? {
724 pcm_samples.extend_from_slice(&chunk);
725 }
726
727 std::fs::create_dir_all("target/tmp")?;
728 let mut out = std::fs::File::create(output_path)?;
729 for sample in pcm_samples {
730 out.write_all(&sample.to_le_bytes())?;
731 }
732 out.flush()?;
733
734 println!("PCM dumped: {}", output_path);
735 println!("ffplay -f s16le -ar 16000 -ac 1 {}", output_path);
736 Ok(())
737 }
738}