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