use crate::event::SessionEvent;
use crate::media::AudioFrame;
use crate::media::Samples;
use crate::media::track::Track;
use crate::media::track::media_pass::MediaPassOption;
use crate::media::track::media_pass::MediaPassTrack;
use crate::media::{PcmBuf, Sample};
use anyhow::Result;
use audio_codec::bytes_to_samples;
use audio_codec::samples_to_bytes;
use bytes::Bytes;
use futures::FutureExt;
use futures::StreamExt;
use serde::Deserialize;
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};
use tokio::net::TcpListener;
use tokio_util::sync::CancellationToken;
use tracing::warn;
use warp;
use warp::Filter;
use warp::ws::Ws;
fn generate_audio_samples(sample_rate: u32) -> PcmBuf {
let num_samples = sample_rate * 1;
let mut samples = vec![0; num_samples as usize];
let frequency = 440.0;
for i in 0..num_samples {
let t = i as f32 / sample_rate as f32;
let amplitude = 16384.0; let sample = (amplitude * (2.0 * std::f32::consts::PI * frequency * t).sin()) as Sample;
samples[i as usize] = sample;
}
samples
}
#[derive(Deserialize, Debug)]
struct Params {
sample_rate: Option<u32>,
packet_size: Option<u32>,
}
async fn start_mock_server(
cancel_token: CancellationToken,
sample_rate_holder: Arc<AtomicU32>,
packet_size_holder: Arc<AtomicU32>,
received_data: Option<Arc<tokio::sync::Mutex<Vec<u8>>>>,
received_timestamps: Option<Arc<tokio::sync::Mutex<Vec<Instant>>>>,
) -> Result<SocketAddr> {
let listener = TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
let routes = warp::any()
.and(warp::ws())
.and(warp::query::<Params>())
.map(move |ws: Ws, params: Params| {
if let Some(sample_rate) = params.sample_rate {
sample_rate_holder.store(sample_rate, Ordering::Relaxed);
}
if let Some(packet_size) = params.packet_size {
packet_size_holder.store(packet_size, Ordering::Relaxed);
}
let received_data = received_data.clone();
let received_timestamps = received_timestamps.clone();
ws.on_upgrade(move |websocket| {
let (tx, rx) = websocket.split();
let received_data = received_data.clone();
let received_timestamps = received_timestamps.clone();
async move {
if received_data.is_some() && received_timestamps.is_some() {
let received_data = received_data.unwrap();
let received_timestamps = received_timestamps.unwrap();
let mut rx = rx;
while let Some(msg) = rx.next().await {
match msg {
Ok(msg) => {
if msg.is_binary() {
let bytes = msg.as_bytes();
if !bytes.is_empty() {
let mut data_guard = received_data.lock().await;
data_guard.extend_from_slice(bytes);
let mut timestamps_guard =
received_timestamps.lock().await;
timestamps_guard.push(Instant::now());
}
}
}
Err(_) => break,
}
}
} else {
rx.forward(tx)
.map(|result| {
if let Err(e) = result {
warn!("websocket error: {:?}", e);
}
})
.await;
}
}
})
});
let server = warp::serve(routes).incoming(listener);
tokio::spawn(async move {
server
.graceful(async move {
cancel_token.cancelled().await;
})
.run()
.await;
});
Ok(addr)
}
fn create_track(
track_id: String,
url: String,
sending_sample_rate: u32,
receiving_sample_rate: u32,
packet_size: u32,
cancel_token: CancellationToken,
ptime: Option<u32>,
) -> Result<MediaPassTrack> {
let option = MediaPassOption::new(
url,
sending_sample_rate,
receiving_sample_rate,
Some(packet_size),
ptime,
);
let track = MediaPassTrack::new(
"test_session_id".to_string(),
0,
track_id,
cancel_token,
option,
);
Ok(track)
}
async fn start_sending_server(
cancel_token: CancellationToken,
sample_rate_holder: Arc<AtomicU32>,
packet_size_holder: Arc<AtomicU32>,
audio_data: Vec<u8>,
) -> Result<SocketAddr> {
let listener = TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
let routes = warp::any()
.and(warp::ws())
.and(warp::query::<Params>())
.map(move |ws: Ws, params: Params| {
if let Some(sample_rate) = params.sample_rate {
sample_rate_holder.store(sample_rate, Ordering::Relaxed);
}
if let Some(packet_size) = params.packet_size {
packet_size_holder.store(packet_size, Ordering::Relaxed);
}
let audio_data = audio_data.clone();
ws.on_upgrade(move |websocket| {
let (mut tx, mut _rx) = websocket.split();
let audio_data = audio_data.clone();
async move {
use futures::SinkExt;
let chunk_size = 320; let chunks: Vec<Vec<u8>> =
audio_data.chunks(chunk_size).map(|c| c.to_vec()).collect();
for chunk in chunks {
if let Err(_) = tx.send(warp::ws::Message::binary(Bytes::from(chunk))).await
{
break;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
})
});
let server = warp::serve(routes).incoming(listener);
tokio::spawn(async move {
server
.graceful(async move {
cancel_token.cancelled().await;
})
.run()
.await;
});
Ok(addr)
}
#[tokio::test]
async fn test_media_pass() -> Result<()> {
let track_id = "media_pass_track".to_string();
let sending_sample_rate = 16000;
let receiving_sample_rate = 16000;
let packet_size = 640;
let sample = generate_audio_samples(sending_sample_rate);
let cancel_token = CancellationToken::new();
let sample_rate_holder = Arc::new(AtomicU32::new(0));
let packet_size_holder = Arc::new(AtomicU32::new(0));
let addr = start_mock_server(
cancel_token.clone(),
sample_rate_holder.clone(),
packet_size_holder.clone(),
None,
None,
)
.await?;
let url = format!("ws://127.0.0.1:{}/", addr.port());
let mut track = create_track(
track_id.clone(),
url,
sending_sample_rate,
receiving_sample_rate,
packet_size,
CancellationToken::new(),
None,
)?;
let (event_sender, mut event_receiver) = tokio::sync::broadcast::channel(10);
let (packet_sender, mut packet_receiver) = tokio::sync::mpsc::unbounded_channel();
track.start(event_sender, packet_sender).await?;
for i in 0..100 {
let slice = sample[i * 160..(i + 1) * 160].to_vec();
let audio_frame = AudioFrame {
track_id: track_id.clone(),
samples: Samples::PCM { samples: slice },
timestamp: crate::media::get_timestamp(),
sample_rate: sending_sample_rate,
channels: 1,
..Default::default()
};
track.send_packet(&audio_frame).await?;
}
tokio::time::sleep(Duration::from_millis(2000)).await;
assert_eq!(
receiving_sample_rate,
sample_rate_holder.load(Ordering::Relaxed)
);
assert_eq!(packet_size, packet_size_holder.load(Ordering::Relaxed));
track.stop().await?;
let mut buffer = Vec::with_capacity(16000);
while let Some(packet) = packet_receiver.recv().await {
if let Samples::PCM { samples } = packet.samples {
buffer.extend_from_slice(samples.as_slice());
}
}
assert_eq!(sample.len(), buffer.len());
let mut ended = false;
while let Ok(event) = event_receiver.recv().await {
if let SessionEvent::TrackEnd { .. } = event {
ended = true;
break;
}
}
assert!(ended);
Ok(())
}
#[tokio::test]
async fn test_track_end_on_cancel() -> Result<()> {
let track_id = "cancel_test_track".to_string();
let sending_sample_rate = 8000;
let receiving_sample_rate = 16000;
let packet_size = 640;
let cancel_token = CancellationToken::new();
let sample_rate_holder = Arc::new(AtomicU32::new(0));
let packet_size_holder = Arc::new(AtomicU32::new(0));
let server_cancel_token = CancellationToken::new();
let addr = start_mock_server(
server_cancel_token.clone(),
sample_rate_holder.clone(),
packet_size_holder.clone(),
None,
None,
)
.await?;
let url = format!("ws://127.0.0.1:{}/", addr.port());
let mut track = create_track(
track_id.clone(),
url,
sending_sample_rate,
receiving_sample_rate,
packet_size,
cancel_token.clone(),
None,
)?;
let (event_sender, mut event_receiver) = tokio::sync::broadcast::channel(10);
let (packet_sender, _packet_receiver) = tokio::sync::mpsc::unbounded_channel();
track.start(event_sender, packet_sender).await?;
tokio::time::sleep(Duration::from_millis(100)).await;
cancel_token.cancel();
let mut track_end_received = false;
let timeout = tokio::time::timeout(Duration::from_secs(2), async {
while let Ok(event) = event_receiver.recv().await {
if let SessionEvent::TrackEnd {
track_id: event_track_id,
..
} = event
{
if event_track_id == track_id {
track_end_received = true;
break;
}
}
}
})
.await;
assert!(
track_end_received,
"TrackEnd event should be sent when cancel_token is cancelled"
);
assert!(timeout.is_ok(), "Should receive TrackEnd within timeout");
server_cancel_token.cancel();
Ok(())
}
#[tokio::test]
async fn test_resampling_to_output_sample_rate() -> Result<()> {
let track_id = "resample_test_track".to_string();
let input_sample_rate = 8000;
let output_sample_rate = 16000; let packet_size = 640;
let cancel_token = CancellationToken::new();
let sample_rate_holder = Arc::new(AtomicU32::new(0));
let packet_size_holder = Arc::new(AtomicU32::new(0));
let received_data = Arc::new(tokio::sync::Mutex::new(Vec::new()));
let received_timestamps = Arc::new(tokio::sync::Mutex::new(Vec::new()));
let server_cancel_token = CancellationToken::new();
let addr = start_mock_server(
server_cancel_token.clone(),
sample_rate_holder.clone(),
packet_size_holder.clone(),
Some(received_data.clone()),
Some(received_timestamps.clone()),
)
.await?;
let url = format!("ws://127.0.0.1:{}/", addr.port());
let mut track = create_track(
track_id.clone(),
url,
input_sample_rate,
output_sample_rate,
packet_size,
cancel_token.clone(),
None,
)?;
let (event_sender, _event_receiver) = tokio::sync::broadcast::channel(10);
let (packet_sender, _packet_receiver) = tokio::sync::mpsc::unbounded_channel();
track.start(event_sender, packet_sender).await?;
tokio::time::sleep(Duration::from_millis(100)).await;
let sample = generate_audio_samples(input_sample_rate);
for i in 0..50 {
let slice = sample[i * 160..(i + 1) * 160].to_vec();
let audio_frame = AudioFrame {
track_id: track_id.clone(),
samples: Samples::PCM { samples: slice },
timestamp: crate::media::get_timestamp(),
sample_rate: input_sample_rate, channels: 1,
..Default::default()
};
track.send_packet(&audio_frame).await?;
}
tokio::time::sleep(Duration::from_millis(500)).await;
let data_guard = received_data.lock().await;
assert!(!data_guard.is_empty(), "Should receive data from websocket");
let received_samples = bytes_to_samples(&data_guard);
let expected_samples = (50 * 160) * output_sample_rate / input_sample_rate;
let tolerance = (expected_samples as f64 * 0.1) as u32;
let received_len = received_samples.len() as u32;
assert!(
received_len >= expected_samples.saturating_sub(tolerance),
"Received {} samples, expected at least {} (after resampling from {}Hz to {}Hz)",
received_len,
expected_samples.saturating_sub(tolerance),
input_sample_rate,
output_sample_rate
);
assert_eq!(
output_sample_rate,
sample_rate_holder.load(Ordering::Relaxed),
"WebSocket should receive correct output_sample_rate parameter"
);
cancel_token.cancel();
server_cancel_token.cancel();
Ok(())
}
#[tokio::test]
async fn test_ptime_buffering() -> Result<()> {
let track_id = "ptime_test_track".to_string();
let input_sample_rate = 16000;
let output_sample_rate = 16000;
let packet_size = 3200;
let ptime_ms = 20; let cancel_token = CancellationToken::new();
let sample_rate_holder = Arc::new(AtomicU32::new(0));
let packet_size_holder = Arc::new(AtomicU32::new(0));
let server_cancel_token = CancellationToken::new();
let sample = generate_audio_samples(input_sample_rate);
let audio_bytes = samples_to_bytes(&sample);
let addr = start_sending_server(
server_cancel_token.clone(),
sample_rate_holder.clone(),
packet_size_holder.clone(),
audio_bytes,
)
.await?;
let url = format!("ws://127.0.0.1:{}/", addr.port());
let mut track = create_track(
track_id.clone(),
url,
input_sample_rate,
output_sample_rate,
packet_size,
cancel_token.clone(),
Some(ptime_ms),
)?;
let (event_sender, mut event_receiver) = tokio::sync::broadcast::channel(10);
let (packet_sender, mut packet_receiver) = tokio::sync::mpsc::unbounded_channel();
track.start(event_sender, packet_sender).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let mut emitted_packets = Vec::new();
let start_time = Instant::now();
let collect_duration = Duration::from_millis(ptime_ms as u64 * 5);
while start_time.elapsed() < collect_duration {
if let Ok(packet) = packet_receiver.try_recv() {
emitted_packets.push((packet.timestamp, packet));
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
while let Ok(packet) = packet_receiver.try_recv() {
emitted_packets.push((packet.timestamp, packet));
}
assert!(
emitted_packets.len() > 0,
"Should emit audio frames when ptime is set and data is received from websocket"
);
let expected_samples = (input_sample_rate * ptime_ms / 1000) as usize;
for (_, packet) in &emitted_packets {
if let Samples::PCM { samples } = &packet.samples {
assert_eq!(
samples.len(),
expected_samples,
"Emitted audio should be in ptime-sized chunks ({} samples for {}ms at {}Hz)",
expected_samples,
ptime_ms,
input_sample_rate
);
}
}
let ptime_duration_ms = ptime_ms as u64;
let tolerance_ms = 10u64;
if emitted_packets.len() >= 2 {
for i in 1..emitted_packets.len() {
let interval_ms = emitted_packets[i]
.0
.saturating_sub(emitted_packets[i - 1].0);
assert!(
interval_ms >= ptime_duration_ms.saturating_sub(tolerance_ms)
&& interval_ms <= ptime_duration_ms + tolerance_ms * 2,
"Audio should be emitted at ptime intervals ({}ms), got {}ms",
ptime_ms,
interval_ms
);
}
}
cancel_token.cancel();
let mut track_end_received = false;
let timeout = tokio::time::timeout(Duration::from_secs(2), async {
while let Ok(event) = event_receiver.recv().await {
if let SessionEvent::TrackEnd { .. } = event {
track_end_received = true;
break;
}
}
})
.await;
assert!(track_end_received, "TrackEnd should be sent");
assert!(timeout.is_ok(), "Should receive TrackEnd within timeout");
server_cancel_token.cancel();
Ok(())
}