use crate::media::{
AudioFrame, PcmBuf, Sample, Samples,
recorder::{Recorder, RecorderOption},
};
use anyhow::Result;
use std::{path::Path, sync::Arc};
use tempfile::tempdir;
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
#[tokio::test]
async fn test_recorder() -> Result<()> {
let temp_dir = tempdir()?;
let file_path = temp_dir.path().join("test_recording.wav");
let file_path_clone = file_path.clone(); let cancel_token = CancellationToken::new();
let config = RecorderOption::default();
let recorder = Arc::new(Recorder::new(
cancel_token.clone(),
"test".to_string(),
config,
));
let (tx, rx) = mpsc::unbounded_channel();
let recorder_clone = recorder.clone();
let recorder_hanadle = tokio::spawn(async move {
let r = recorder_clone.process_recording(&file_path_clone, rx).await;
println!("recorder: {:?}", r);
});
let left_channel_id = "left".to_string();
let right_channel_id = "right".to_string();
let sample_count = 1600;
for i in 0..5 {
let left_samples: PcmBuf = (0..sample_count)
.map(|j| {
let t = (i * sample_count + j) as f32 / 16000.0;
((t * 440.0 * 2.0 * std::f32::consts::PI).sin() * 16384.0) as Sample
})
.collect();
let right_samples: PcmBuf = (0..sample_count)
.map(|j| {
let t = (i * sample_count + j) as f32 / 16000.0;
((t * 880.0 * 2.0 * std::f32::consts::PI).sin() * 16384.0) as Sample
})
.collect();
let left_frame = AudioFrame {
track_id: left_channel_id.clone(),
samples: Samples::PCM {
samples: left_samples,
},
timestamp: (i * 100), sample_rate: 16000,
channels: 1,
..Default::default()
};
let right_frame = AudioFrame {
track_id: right_channel_id.clone(),
samples: Samples::PCM {
samples: right_samples,
},
timestamp: (i * 100), sample_rate: 16000,
channels: 1,
..Default::default()
};
tx.send(left_frame)?;
tx.send(right_frame)?;
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
recorder.stop_recording()?;
recorder_hanadle.await?;
assert!(file_path.exists());
println!("file_path: {:?}", file_path.to_str());
verify_wav_file(&file_path)?;
Ok(())
}
fn verify_wav_file(path: &Path) -> Result<()> {
let reader = hound::WavReader::open(path)?;
let spec = reader.spec();
assert_eq!(spec.channels, 2); assert_eq!(spec.sample_rate, 16000);
assert_eq!(spec.bits_per_sample, 16);
assert_eq!(spec.sample_format, hound::SampleFormat::Int);
let samples_count = reader.len();
assert!(samples_count > 0, "WAV file has no samples");
Ok(())
}
#[tokio::test]
async fn test_recorder_intermittent_data() -> Result<()> {
let temp_dir = tempdir()?;
let file_path = temp_dir.path().join("test_intermittent.wav");
let file_path_clone = file_path.clone();
let cancel_token = CancellationToken::new();
let config = RecorderOption::default();
let recorder = Arc::new(Recorder::new(
cancel_token.clone(),
"test".to_string(),
config,
));
let (tx, rx) = mpsc::unbounded_channel();
let recorder_clone = recorder.clone();
let recorder_handle =
tokio::spawn(async move { recorder_clone.process_recording(&file_path_clone, rx).await });
let track_id = "test_track".to_string();
for i in 0..10 {
let small_samples: PcmBuf = (0..50) .map(|j| {
let t = (i * 50 + j) as f32 / 16000.0;
((t * 440.0 * 2.0 * std::f32::consts::PI).sin() * 16384.0) as Sample
})
.collect();
let frame = AudioFrame {
track_id: track_id.clone(),
samples: Samples::PCM {
samples: small_samples,
},
timestamp: (i * 50) as u64,
sample_rate: 16000,
channels: 1,
..Default::default()
};
tx.send(frame)?;
if i % 3 == 0 {
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}
}
let clipped_samples: PcmBuf = vec![32767, -32768, 32767, -32768, 0, 0, 0, 0]; let clipped_frame = AudioFrame {
track_id: track_id.clone(),
samples: Samples::PCM {
samples: clipped_samples,
},
timestamp: 1000,
sample_rate: 16000,
channels: 1,
..Default::default()
};
tx.send(clipped_frame)?;
let constant_samples: PcmBuf = vec![1000; 20]; let constant_frame = AudioFrame {
track_id: track_id.clone(),
samples: Samples::PCM {
samples: constant_samples,
},
timestamp: 1100,
sample_rate: 16000,
channels: 1,
..Default::default()
};
tx.send(constant_frame)?;
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
recorder.stop_recording()?;
recorder_handle.await??;
assert!(file_path.exists());
verify_wav_file(&file_path)?;
println!("Intermittent data test completed successfully");
Ok(())
}
#[tokio::test]
async fn test_constant_value_detection() -> Result<()> {
let temp_dir = tempdir()?;
let file_path = temp_dir.path().join("test_constant_detection.wav");
let file_path_clone = file_path.clone();
let cancel_token = CancellationToken::new();
let config = RecorderOption::default();
let recorder = Arc::new(Recorder::new(
cancel_token.clone(),
"test".to_string(),
config,
));
let (tx, rx) = mpsc::unbounded_channel();
let recorder_clone = recorder.clone();
let recorder_handle =
tokio::spawn(async move { recorder_clone.process_recording(&file_path_clone, rx).await });
let track_id = "test_track".to_string();
let short_silence: PcmBuf = vec![0; 15]; let frame1 = AudioFrame {
track_id: track_id.clone(),
samples: Samples::PCM {
samples: short_silence,
},
timestamp: 0,
sample_rate: 16000,
channels: 1,
..Default::default()
};
tx.send(frame1)?;
let medium_silence: PcmBuf = vec![0; 40]; let frame2 = AudioFrame {
track_id: track_id.clone(),
samples: Samples::PCM {
samples: medium_silence,
},
timestamp: 100,
sample_rate: 16000,
channels: 1,
..Default::default()
};
tx.send(frame2)?;
let large_silence: PcmBuf = vec![0; 100]; let frame3 = AudioFrame {
track_id: track_id.clone(),
samples: Samples::PCM {
samples: large_silence,
},
timestamp: 200,
sample_rate: 16000,
channels: 1,
..Default::default()
};
tx.send(frame3)?;
let constant_non_zero: PcmBuf = vec![1000; 50]; let frame4 = AudioFrame {
track_id: track_id.clone(),
samples: Samples::PCM {
samples: constant_non_zero,
},
timestamp: 300,
sample_rate: 16000,
channels: 1,
..Default::default()
};
tx.send(frame4)?;
let normal_audio: PcmBuf = (0..50)
.map(|i| ((i as f32 * 0.1).sin() * 1000.0) as Sample)
.collect();
let frame5 = AudioFrame {
track_id: track_id.clone(),
samples: Samples::PCM {
samples: normal_audio,
},
timestamp: 400,
sample_rate: 16000,
channels: 1,
..Default::default()
};
tx.send(frame5)?;
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
recorder.stop_recording()?;
recorder_handle.await??;
assert!(file_path.exists());
verify_wav_file(&file_path)?;
println!("Constant value detection test completed successfully");
Ok(())
}
#[tokio::test]
async fn test_recorder_200ms_timing() -> Result<()> {
let temp_dir = tempdir()?;
let file_path = temp_dir.path().join("test_200ms_recording.wav");
let file_path_clone = file_path.clone();
let cancel_token = CancellationToken::new();
let config = RecorderOption::default();
let recorder = Arc::new(Recorder::new(
cancel_token.clone(),
"test".to_string(),
config,
));
let (tx, rx) = mpsc::unbounded_channel();
let recorder_clone = recorder.clone();
let recorder_handle = tokio::spawn(async move {
let r = recorder_clone.process_recording(&file_path_clone, rx).await;
println!("recorder result: {:?}", r);
});
let track_id_1 = "track_1".to_string();
let track_id_2 = "track_2".to_string();
for i in 0..10 {
let frame_size = 100 + (i * 50);
let samples_1: PcmBuf = (0..frame_size)
.map(|j| {
let t = (i * frame_size + j) as f32 / 16000.0;
((t * 440.0 * 2.0 * std::f32::consts::PI).sin() * 8000.0) as Sample
})
.collect();
let samples_2: PcmBuf = (0..frame_size)
.map(|j| {
let t = (i * frame_size + j) as f32 / 16000.0;
((t * 880.0 * 2.0 * std::f32::consts::PI).sin() * 8000.0) as Sample
})
.collect();
let frame_1 = AudioFrame {
track_id: track_id_1.clone(),
samples: Samples::PCM { samples: samples_1 },
timestamp: (i * 200) as u64, sample_rate: 16000,
channels: 1,
..Default::default()
};
let frame_2 = AudioFrame {
track_id: track_id_2.clone(),
samples: Samples::PCM { samples: samples_2 },
timestamp: (i * 200) as u64,
sample_rate: 16000,
channels: 1,
..Default::default()
};
tx.send(frame_1)?;
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
tx.send(frame_2)?;
tokio::time::sleep(tokio::time::Duration::from_millis(150)).await;
}
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
recorder.stop_recording()?;
recorder_handle.await?;
assert!(file_path.exists());
verify_wav_file(&file_path)?;
println!("200ms timing test completed successfully");
Ok(())
}