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::BoxedResampler;
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<BoxedResampler>,
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 = BoxedResampler::new(
161 self.sample_rate as usize,
162 self.target_sample_rate as usize,
163 )
164 .expect("invalid sample rate");
165 let result = new_resampler.resample(chunk);
166 self.resampler = Some(new_resampler);
167 result
168 }
169 }
170}
171
172async fn process_audio_reader(
174 mut processor_chain: ProcessorChain,
175 mut audio_reader: Box<dyn AudioReader>,
176 track_id: &str,
177 packet_duration_ms: u32,
178 target_sample_rate: u32,
179 token: CancellationToken,
180 paused: Arc<AtomicBool>,
181 packet_sender: TrackPacketSender,
182) -> Result<()> {
183 info!(
184 "streaming audio with target_sample_rate: {}, packet_duration: {}ms",
185 target_sample_rate, packet_duration_ms
186 );
187 let stream_loop = async move {
188 let start_time = Instant::now();
189 let mut ticker = tokio::time::interval(Duration::from_millis(packet_duration_ms as u64));
190 let channels = audio_reader.channels();
191 loop {
192 if paused.load(Ordering::Relaxed) {
193 ticker.tick().await;
194 continue;
195 }
196
197 let Some((chunk, chunk_sample_rate)) = audio_reader.read_chunk(packet_duration_ms)?
198 else {
199 break;
200 };
201
202 let mut packet = AudioFrame {
203 track_id: track_id.to_string(),
204 timestamp: crate::media::get_timestamp(),
205 samples: Samples::PCM { samples: chunk },
206 sample_rate: chunk_sample_rate,
207 channels,
208 ..Default::default()
209 };
210
211 match processor_chain.process_frame(&mut packet) {
212 Ok(_) => {}
213 Err(e) => {
214 warn!("failed to process audio packet: {}", e);
215 }
216 }
217
218 if let Err(e) = packet_sender.send(packet) {
219 warn!("failed to send audio packet: {}", e);
220 break;
221 }
222
223 ticker.tick().await;
224 }
225
226 info!("stream loop finished in {:?}", start_time.elapsed());
227 Ok(()) as Result<()>
228 };
229
230 select! {
231 _ = token.cancelled() => {
232 info!("stream cancelled");
233 return Ok(());
234 }
235 result = stream_loop => {
236 info!("stream loop finished");
237 result
238 }
239 }
240}
241
242pub struct FileTrack {
243 track_id: TrackId,
244 play_id: Option<String>,
245 config: TrackConfig,
246 cancel_token: CancellationToken,
247 processor_chain: ProcessorChain,
248 path: Option<String>,
249 use_cache: bool,
250 ssrc: u32,
251 offset_ms: u32,
252 paused: Arc<AtomicBool>,
253}
254
255impl FileTrack {
256 pub fn new(id: TrackId) -> Self {
257 let config = TrackConfig::default();
258 Self {
259 track_id: id,
260 play_id: None,
261 processor_chain: ProcessorChain::new(config.samplerate),
262 config,
263 cancel_token: CancellationToken::new(),
264 path: None,
265 use_cache: true,
266 ssrc: 0,
267 offset_ms: 0,
268 paused: Arc::new(AtomicBool::new(false)),
269 }
270 }
271
272 pub fn with_play_id(mut self, play_id: Option<String>) -> Self {
273 self.play_id = play_id;
274 self
275 }
276
277 pub fn with_ssrc(mut self, ssrc: u32) -> Self {
278 self.ssrc = ssrc;
279 self
280 }
281 pub fn with_config(mut self, config: TrackConfig) -> Self {
282 self.config = config;
283 self
284 }
285
286 pub fn with_cancel_token(mut self, cancel_token: CancellationToken) -> Self {
287 self.cancel_token = cancel_token;
288 self
289 }
290
291 pub fn with_path(mut self, path: String) -> Self {
292 self.path = Some(path);
293 self
294 }
295
296 pub fn with_sample_rate(mut self, sample_rate: u32) -> Self {
297 self.config = self.config.with_sample_rate(sample_rate);
298 self
299 }
300
301 pub fn with_ptime(mut self, ptime: Duration) -> Self {
302 self.config = self.config.with_ptime(ptime);
303 self
304 }
305
306 pub fn with_cache_enabled(mut self, use_cache: bool) -> Self {
307 self.use_cache = use_cache;
308 self
309 }
310
311 pub fn with_offset_ms(mut self, offset_ms: u32) -> Self {
312 self.offset_ms = offset_ms;
313 self
314 }
315}
316
317#[async_trait]
318impl Track for FileTrack {
319 fn ssrc(&self) -> u32 {
320 self.ssrc
321 }
322 fn id(&self) -> &TrackId {
323 &self.track_id
324 }
325 fn config(&self) -> &TrackConfig {
326 &self.config
327 }
328 fn set_paused(&self, paused: bool) -> bool {
329 self.paused.store(paused, Ordering::Relaxed);
330 true
331 }
332 fn is_paused(&self) -> bool {
333 self.paused.load(Ordering::Relaxed)
334 }
335 fn processor_chain(&mut self) -> &mut ProcessorChain {
336 &mut self.processor_chain
337 }
338
339 async fn handshake(&mut self, _offer: String, _timeout: Option<Duration>) -> Result<String> {
340 Ok("".to_string())
341 }
342 async fn update_remote_description(&mut self, _answer: &String) -> Result<()> {
343 Ok(())
344 }
345
346 async fn start(
347 &mut self,
348 event_sender: EventSender,
349 packet_sender: TrackPacketSender,
350 ) -> Result<()> {
351 if self.path.is_none() {
352 return Err(anyhow::anyhow!("filetrack: No path provided for FileTrack"));
353 }
354 let path = self.path.clone().unwrap();
355 let id = self.track_id.clone();
356 let sample_rate = self.config.samplerate;
357 let use_cache = self.use_cache;
358 let packet_duration_ms = self.config.ptime.as_millis() as u32;
359 let processor_chain = self.processor_chain.clone();
360 let token = self.cancel_token.clone();
361 let start_time = crate::media::get_timestamp();
362 let ssrc = self.ssrc;
363 let offset_ms = self.offset_ms;
364 let paused = self.paused.clone();
365 let play_id = self.play_id.clone();
367 crate::spawn(async move {
368 let res = async move {
369 let load_result = crate::media::loader::load_audio_as_pcm_cached(
372 &path,
373 sample_rate,
374 use_cache,
375 offset_ms,
376 )
377 .await;
378 let samples = match load_result {
379 Ok(samples) => samples,
380 Err(e) => {
381 warn!("filetrack: Error loading audio: {} {}", path, e);
382 event_sender
383 .send(SessionEvent::Error {
384 track_id: id.clone(),
385 timestamp: crate::media::get_timestamp(),
386 sender: format!("filetrack: {}", path),
387 error: e.to_string(),
388 code: None,
389 })
390 .ok();
391 event_sender
392 .send(SessionEvent::TrackEnd {
393 track_id: id,
394 timestamp: crate::media::get_timestamp(),
395 duration: crate::media::get_timestamp() - start_time,
396 ssrc,
397 play_id: play_id.clone(),
398 })
399 .ok();
400 return Err(e);
401 }
402 };
403
404 let stream_result = stream_pcm_samples(
406 processor_chain,
407 samples,
408 sample_rate,
409 &id,
410 packet_duration_ms,
411 token,
412 paused,
413 packet_sender,
414 )
415 .await;
416
417 if let Err(e) = stream_result {
419 warn!("filetrack: Error streaming audio: {}, {}", path, e);
420 event_sender
421 .send(SessionEvent::Error {
422 track_id: id.clone(),
423 timestamp: crate::media::get_timestamp(),
424 sender: format!("filetrack: {}", path),
425 error: e.to_string(),
426 code: None,
427 })
428 .ok();
429 }
430
431 event_sender
433 .send(SessionEvent::TrackEnd {
434 track_id: id,
435 timestamp: crate::media::get_timestamp(),
436 duration: crate::media::get_timestamp() - start_time,
437 ssrc,
438 play_id,
439 })
440 .ok();
441 Ok::<(), anyhow::Error>(())
442 }
443 .await;
444 if let Err(e) = res {
445 debug!("filetrack: streaming task finished with error: {:?}", e);
446 }
447 });
448 Ok(())
449 }
450
451 async fn stop(&self) -> Result<()> {
452 self.cancel_token.cancel();
454 Ok(())
455 }
456
457 async fn send_packet(&mut self, _packet: &AudioFrame) -> Result<()> {
459 Ok(())
460 }
461}
462
463async fn stream_pcm_samples(
465 processor_chain: ProcessorChain,
466 samples: Vec<i16>,
467 target_sample_rate: u32,
468 track_id: &str,
469 packet_duration_ms: u32,
470 token: CancellationToken,
471 paused: Arc<AtomicBool>,
472 packet_sender: TrackPacketSender,
473) -> Result<()> {
474 let reader =
475 DecodedAudioReader::from_samples(samples, target_sample_rate, target_sample_rate);
476 let audio_reader = Box::new(reader) as Box<dyn AudioReader>;
477 info!(
478 "filetrack: streaming {} decoded samples at {} Hz",
479 audio_reader.buffer_size(),
480 audio_reader.sample_rate(),
481 );
482 process_audio_reader(
483 processor_chain,
484 audio_reader,
485 track_id,
486 packet_duration_ms,
487 target_sample_rate,
488 token,
489 paused,
490 packet_sender,
491 )
492 .await
493}
494
495pub fn read_wav_file(path: &str) -> Result<(PcmBuf, u32)> {
497 let reader = BufReader::new(File::open(path)?);
498 let mut wav_reader = WavReader::new(reader)?;
499 let spec = wav_reader.spec();
500 let mut all_samples = Vec::new();
501
502 match spec.sample_format {
503 hound::SampleFormat::Int => match spec.bits_per_sample {
504 16 => {
505 for sample in wav_reader.samples::<i16>() {
506 all_samples.push(sample.unwrap_or(0));
507 }
508 }
509 8 => {
510 for sample in wav_reader.samples::<i8>() {
511 all_samples.push(sample.unwrap_or(0) as i16);
512 }
513 }
514 24 | 32 => {
515 for sample in wav_reader.samples::<i32>() {
516 all_samples.push((sample.unwrap_or(0) >> 16) as i16);
517 }
518 }
519 _ => {
520 return Err(anyhow!(
521 "Unsupported bits per sample: {}",
522 spec.bits_per_sample
523 ));
524 }
525 },
526 hound::SampleFormat::Float => {
527 for sample in wav_reader.samples::<f32>() {
528 all_samples.push((sample.unwrap_or(0.0) * 32767.0) as i16);
529 }
530 }
531 }
532
533 if spec.channels == 2 {
535 let mono_samples = all_samples
536 .chunks(2)
537 .map(|chunk| ((chunk[0] as i32 + chunk[1] as i32) / 2) as i16)
538 .collect();
539 all_samples = mono_samples;
540 }
541 Ok((all_samples, spec.sample_rate))
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547 use std::io::Write;
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_mp3_decode_sample_file() -> Result<()> {
644 let file_path = "fixtures/sample.mp3".to_string();
645 match File::open(&file_path) {
646 Ok(file) => {
647 let samples = crate::media::loader::decode_audio(file, "mp3", None, 16000)?;
648 assert!(
649 !samples.is_empty(),
650 "Decoded sample list should not be empty"
651 );
652 }
653 Err(_) => {
654 println!("Skipping MP3 test: sample file not found at {}", file_path);
655 }
656 }
657 Ok(())
658 }
659
660 #[tokio::test]
661 async fn test_mp3_file_track() -> Result<()> {
662 println!("Starting MP3 file track test");
663
664 let file_path = "fixtures/sample.mp3".to_string();
666 let file = File::open(&file_path)?;
667 let sample_rate = 16000;
668 let mut reader = DecodedAudioReader::from_file(file, "mp3", None, sample_rate)?;
669 let mut total_samples = 0;
670 let mut total_duration_ms = 0.0;
671 while let Some((chunk, _chunk_sample_rate)) = reader.read_chunk(320)? {
672 total_samples += chunk.len();
673 let chunk_duration_ms = (chunk.len() as f64 / sample_rate as f64) * 1000.0;
675 total_duration_ms += chunk_duration_ms;
676 }
677 let duration_seconds = total_duration_ms / 1000.0;
678 println!("Total samples: {}", total_samples);
679 println!("Duration: {:.2} seconds", duration_seconds);
680
681 const EXPECTED_SAMPLES: usize = 226310;
682 assert!(
683 total_samples == EXPECTED_SAMPLES,
684 "Sample count {} does not match expected {}",
685 total_samples,
686 EXPECTED_SAMPLES
687 );
688 Ok(())
689 }
690
691 #[tokio::test]
692 #[ignore = "manual debug helper: dumps raw pcm for ffplay"]
693 async fn dump_mp3_as_16k_pcm_for_ffplay() -> Result<()> {
694 let input_path = "fixtures/sample.mp3";
695 let output_path = "target/tmp/sample_16k_mono_s16le.pcm";
696
697 let file = File::open(input_path)?;
698 let mut reader = DecodedAudioReader::from_file(file, "mp3", None, 16000)?;
699
700 let mut pcm_samples = Vec::<i16>::new();
701 while let Some((chunk, _chunk_sample_rate)) = reader.read_chunk(320)? {
702 pcm_samples.extend_from_slice(&chunk);
703 }
704
705 std::fs::create_dir_all("target/tmp")?;
706 let mut out = std::fs::File::create(output_path)?;
707 for sample in pcm_samples {
708 out.write_all(&sample.to_le_bytes())?;
709 }
710 out.flush()?;
711
712 println!("PCM dumped: {}", output_path);
713 println!("ffplay -f s16le -ar 16000 -ac 1 {}", output_path);
714 Ok(())
715 }
716}