use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use tokio::time;
use tracing::{debug, warn};
use crate::error::Error;
use crate::packet::RtpPacket;
use crate::{Result, RtpSequenceNumber, RtpTimestamp};
pub struct RtpScheduler {
sequence: RtpSequenceNumber,
timestamp: RtpTimestamp,
clock_rate: u32,
timestamp_increment: RtpTimestamp,
initial_timestamp: RtpTimestamp,
start_time: Option<Instant>,
interval: Duration,
packet_queue: Arc<Mutex<Vec<(RtpPacket, Instant)>>>,
send_task: Option<JoinHandle<()>>,
sender: Option<mpsc::Sender<RtpPacket>>,
running: bool,
packets_scheduled: u64,
packets_sent: u64,
}
impl RtpScheduler {
pub fn new(clock_rate: u32, initial_seq: RtpSequenceNumber, initial_ts: RtpTimestamp) -> Self {
Self {
sequence: initial_seq,
timestamp: initial_ts,
clock_rate,
timestamp_increment: 0,
initial_timestamp: initial_ts,
start_time: None,
interval: Duration::from_millis(20), packet_queue: Arc::new(Mutex::new(Vec::new())),
send_task: None,
sender: None,
running: false,
packets_scheduled: 0,
packets_sent: 0,
}
}
pub fn set_interval(&mut self, interval_ms: u64, samples_per_packet: u32) {
self.interval = Duration::from_millis(interval_ms);
self.timestamp_increment = samples_per_packet;
debug!("Set packet interval to {}ms ({} samples per packet)",
interval_ms, samples_per_packet);
}
pub fn set_sender(&mut self, sender: mpsc::Sender<RtpPacket>) {
self.sender = Some(sender);
}
pub fn schedule_packet(&mut self, mut packet: RtpPacket) -> Result<()> {
if !self.running {
return Err(Error::SessionError("Scheduler not running".to_string()));
}
packet.header.sequence_number = self.sequence;
packet.header.timestamp = self.timestamp;
self.sequence = self.sequence.wrapping_add(1);
self.timestamp = self.timestamp.wrapping_add(self.timestamp_increment);
let now = Instant::now();
let start = self.start_time.unwrap_or(now);
let elapsed = now.duration_since(start);
let intervals = elapsed.as_millis() / self.interval.as_millis();
let next_interval = (intervals + 1) as u64 * self.interval.as_millis() as u64;
let next_send_time = start + Duration::from_millis(next_interval);
if let Ok(mut queue) = self.packet_queue.lock() {
queue.push((packet, next_send_time));
self.packets_scheduled += 1;
debug!("Scheduled packet with seq={}, ts={} for {:?}",
self.sequence.wrapping_sub(1), self.timestamp.wrapping_sub(self.timestamp_increment),
next_send_time);
Ok(())
} else {
Err(Error::SessionError("Failed to lock packet queue".to_string()))
}
}
pub fn start(&mut self) -> Result<()> {
if self.running {
return Ok(());
}
if self.sender.is_none() {
return Err(Error::SessionError("No sender channel configured".to_string()));
}
self.start_time = Some(Instant::now());
self.running = true;
let queue = self.packet_queue.clone();
let sender = self.sender.clone().unwrap();
let interval = self.interval;
let handle = tokio::spawn(async move {
let mut interval_timer = time::interval(Duration::from_millis(1));
loop {
interval_timer.tick().await;
let now = Instant::now();
let mut packets_to_send = Vec::new();
if let Ok(mut queue) = queue.lock() {
let mut i = 0;
while i < queue.len() {
if queue[i].1 <= now {
packets_to_send.push(queue.remove(i));
} else {
i += 1;
}
}
}
for (packet, _) in packets_to_send {
if let Err(e) = sender.send(packet).await {
warn!("Failed to send scheduled packet: {}", e);
}
}
if sender.is_closed() {
debug!("Sender channel closed, stopping scheduler");
break;
}
}
});
self.send_task = Some(handle);
debug!("Started RTP scheduler");
Ok(())
}
pub async fn stop(&mut self) {
if !self.running {
return;
}
self.running = false;
if let Some(handle) = self.send_task.take() {
handle.abort();
debug!("Stopped RTP scheduler");
}
}
pub fn queue_size(&self) -> usize {
if let Ok(queue) = self.packet_queue.lock() {
queue.len()
} else {
0
}
}
pub fn get_sequence(&self) -> RtpSequenceNumber {
self.sequence
}
pub fn get_timestamp(&self) -> RtpTimestamp {
self.timestamp
}
pub fn get_stats(&self) -> RtpSchedulerStats {
RtpSchedulerStats {
packets_scheduled: self.packets_scheduled,
packets_sent: self.packets_sent,
queue_size: self.queue_size(),
running: self.running,
}
}
}
#[derive(Debug, Clone)]
pub struct RtpSchedulerStats {
pub packets_scheduled: u64,
pub packets_sent: u64,
pub queue_size: usize,
pub running: bool,
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
use crate::packet::{RtpHeader, RtpPacket};
use crate::RtpSsrc;
#[tokio::test]
async fn test_scheduler_basic() {
let (tx, mut rx) = mpsc::channel(32);
let mut scheduler = RtpScheduler::new(8000, 1000, 0);
scheduler.set_interval(20, 160); scheduler.set_sender(tx);
scheduler.start().unwrap();
let header = RtpHeader::new(0, 0, 0, 0x12345678);
let payload = Bytes::from_static(b"test");
let packet = RtpPacket::new(header, payload);
scheduler.schedule_packet(packet).unwrap();
let received = tokio::time::timeout(Duration::from_millis(100), rx.recv()).await;
assert!(received.is_ok());
let packet = received.unwrap().unwrap();
assert_eq!(packet.header.sequence_number, 1000);
assert_eq!(packet.header.timestamp, 0);
scheduler.stop().await;
}
#[tokio::test]
async fn test_scheduler_timestamp_increment() {
let (tx, mut rx) = mpsc::channel(32);
let mut scheduler = RtpScheduler::new(8000, 1000, 0);
scheduler.set_interval(20, 160); scheduler.set_sender(tx);
scheduler.start().unwrap();
for _ in 0..3 {
let header = RtpHeader::new(0, 0, 0, 0x12345678);
let payload = Bytes::from_static(b"test");
let packet = RtpPacket::new(header, payload);
scheduler.schedule_packet(packet).unwrap();
}
let packet1 = tokio::time::timeout(Duration::from_millis(100), rx.recv()).await.unwrap().unwrap();
assert_eq!(packet1.header.sequence_number, 1000);
assert_eq!(packet1.header.timestamp, 0);
let packet2 = tokio::time::timeout(Duration::from_millis(100), rx.recv()).await.unwrap().unwrap();
assert_eq!(packet2.header.sequence_number, 1001);
assert_eq!(packet2.header.timestamp, 160);
let packet3 = tokio::time::timeout(Duration::from_millis(100), rx.recv()).await.unwrap().unwrap();
assert_eq!(packet3.header.sequence_number, 1002);
assert_eq!(packet3.header.timestamp, 320);
scheduler.stop().await;
}
}