use crate::error::Result;
use crate::pipeline::{CodecConfig, Sample, TrackSpec};
use crate::rtp::{RtpMediaKind, parse_rtp_header, reassemble_audio, reassemble_video};
use alloc::vec::Vec;
#[non_exhaustive]
pub struct RtpStreamTrack {
pub track_id: u32,
pub kind: RtpMediaKind,
pub config: CodecConfig,
pub clock_rate: u32,
}
impl RtpStreamTrack {
pub fn new(track_id: u32, kind: RtpMediaKind, config: CodecConfig, clock_rate: u32) -> Self {
Self {
track_id,
kind,
config,
clock_rate,
}
}
}
struct TrackState {
kind: RtpMediaKind,
config: CodecConfig,
clock_rate: u32,
cur_ts: Option<u32>,
cur_pkts: Vec<Vec<u8>>,
last_unwrapped: Option<u64>,
pending: Option<PendingAu>,
last_duration: u32,
}
struct PendingAu {
unwrapped_ts: u64,
is_sync: bool,
data: Vec<u8>,
}
pub struct RtpStreamDepacketizer {
tracks: Vec<(u32, TrackState)>,
}
fn unwrap_ts(prev: Option<u64>, ts: u32) -> u64 {
let Some(prev) = prev else {
return u64::from(ts);
};
let prev_low = prev as u32;
let delta = ts.wrapping_sub(prev_low) as i32;
if delta >= 0 {
prev + u64::from(delta as u32)
} else {
prev.saturating_sub(u64::from(delta.unsigned_abs()))
}
}
impl RtpStreamDepacketizer {
pub fn new(tracks: Vec<RtpStreamTrack>) -> Self {
let tracks = tracks
.into_iter()
.map(|t| {
(
t.track_id,
TrackState {
kind: t.kind,
config: t.config,
clock_rate: t.clock_rate,
cur_ts: None,
cur_pkts: Vec::new(),
last_unwrapped: None,
pending: None,
last_duration: 0,
},
)
})
.collect();
Self { tracks }
}
pub fn track_specs(&self) -> Vec<TrackSpec> {
self.tracks
.iter()
.map(|(id, st)| TrackSpec::new(*id, st.clock_rate, st.config.clone()))
.collect()
}
fn state(&mut self, track_id: u32) -> Option<&mut TrackState> {
self.tracks
.iter_mut()
.find(|(id, _)| *id == track_id)
.map(|(_, st)| st)
}
pub fn push(&mut self, track_id: u32, rtp_packet: &[u8]) -> Result<Vec<Sample>> {
let Some(st) = self.state(track_id) else {
return Ok(Vec::new());
};
let hdr = parse_rtp_header(rtp_packet)?;
let ts = hdr.timestamp;
let mut out = Vec::new();
if let Some(cur) = st.cur_ts {
if cur != ts && !st.cur_pkts.is_empty() {
Self::drain_complete(st, &mut out)?;
}
}
st.cur_ts = Some(ts);
st.cur_pkts.push(rtp_packet.to_vec());
if matches!(st.kind, RtpMediaKind::H264) && hdr.marker {
Self::drain_complete(st, &mut out)?;
st.cur_ts = None;
}
Ok(out)
}
pub fn flush(&mut self, track_id: u32) -> Result<Vec<Sample>> {
let Some(st) = self.state(track_id) else {
return Ok(Vec::new());
};
let mut out = Vec::new();
if !st.cur_pkts.is_empty() {
Self::drain_complete(st, &mut out)?;
st.cur_ts = None;
}
if let Some(p) = st.pending.take() {
out.push(Sample::new(p.data, st.last_duration, p.is_sync, 0));
}
Ok(out)
}
fn drain_complete(st: &mut TrackState, out: &mut Vec<Sample>) -> Result<()> {
let pkts = core::mem::take(&mut st.cur_pkts);
let aus = match st.kind {
RtpMediaKind::H264 => reassemble_video(&pkts)?,
RtpMediaKind::Aac => reassemble_audio(&pkts)?,
};
for au in aus {
let unwrapped = unwrap_ts(st.last_unwrapped, au.timestamp);
st.last_unwrapped = Some(unwrapped);
if let Some(prev) = st.pending.take() {
let delta = unwrapped.saturating_sub(prev.unwrapped_ts);
let duration = u32::try_from(delta).unwrap_or(u32::MAX);
st.last_duration = duration;
out.push(Sample::new(prev.data, duration, prev.is_sync, 0));
}
st.pending = Some(PendingAu {
unwrapped_ts: unwrapped,
is_sync: au.is_sync,
data: au.data,
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::avc_config::{AVCConfigurationBox, AVCDecoderConfigurationRecord};
fn dummy_avc() -> CodecConfig {
CodecConfig::Avc {
config: AVCConfigurationBox::new(AVCDecoderConfigurationRecord {
configuration_version: 1,
profile_indication: 0x42,
profile_compatibility: 0,
level_indication: 0x1E,
length_size_minus_one: 3,
sps: alloc::vec![],
pps: alloc::vec![],
chroma_format: None,
bit_depth_luma_minus8: None,
bit_depth_chroma_minus8: None,
sps_ext: alloc::vec![],
}),
width: 0,
height: 0,
}
}
fn vpkt(seq: u16, ts: u32, marker: bool, nal: &[u8]) -> Vec<u8> {
let mut p = alloc::vec![0x80u8, if marker { 0x80 | 96 } else { 96 }];
p.extend_from_slice(&seq.to_be_bytes());
p.extend_from_slice(&ts.to_be_bytes());
p.extend_from_slice(&[0, 0, 0, 0]);
p.extend_from_slice(nal);
p
}
#[test]
fn video_stream_recovers_durations_and_sync() {
let mut d = RtpStreamDepacketizer::new(alloc::vec![RtpStreamTrack::new(
1,
RtpMediaKind::H264,
dummy_avc(),
90_000,
)]);
let idr = [0x65u8, 0xAA];
let non = [0x41u8, 0xBB];
assert!(d.push(1, &vpkt(1, 1000, true, &idr)).unwrap().is_empty());
let s0 = d.push(1, &vpkt(2, 4000, true, &non)).unwrap();
assert_eq!(s0.len(), 1);
assert_eq!(s0[0].duration, 3000);
assert!(s0[0].is_sync);
assert_eq!(s0[0].composition_offset, 0);
let s1 = d.push(1, &vpkt(3, 7000, true, &non)).unwrap();
assert_eq!(s1.len(), 1);
assert_eq!(s1[0].duration, 3000);
assert!(!s1[0].is_sync);
let s2 = d.flush(1).unwrap();
assert_eq!(s2.len(), 1);
assert_eq!(s2[0].duration, 3000);
}
#[test]
fn track_specs_use_clock_rate_as_timescale() {
let d = RtpStreamDepacketizer::new(alloc::vec![RtpStreamTrack::new(
7,
RtpMediaKind::H264,
dummy_avc(),
90_000,
)]);
let specs = d.track_specs();
assert_eq!(specs.len(), 1);
assert_eq!(specs[0].track_id, 7);
assert_eq!(specs[0].timescale, 90_000);
}
}