use std::time::Duration;
const PACKET_SIZE: usize = 188;
const SYNC_BYTE: u8 = 0x47;
const PCR_HZ: u64 = 90_000;
const PCR_MODULUS: u64 = 1 << 33;
#[derive(Debug, Default)]
pub(super) struct StreamClock {
pending: Vec<u8>,
first_base: Option<u64>,
last_base: Option<u64>,
}
impl StreamClock {
pub fn observe(&mut self, data: &[u8]) {
self.pending.extend_from_slice(data);
let (packets, rest) = self.pending.as_chunks::<PACKET_SIZE>();
for packet in packets {
if let Some(base) = pcr_base(packet) {
self.first_base.get_or_insert(base);
self.last_base = Some(base);
}
}
let consumed = self.pending.len() - rest.len();
self.pending.drain(..consumed);
}
pub fn elapsed(&self) -> Option<Duration> {
let ticks = (self.last_base? + PCR_MODULUS - self.first_base?) % PCR_MODULUS;
Some(Duration::from_micros(ticks * 1_000_000 / PCR_HZ))
}
}
fn pcr_base(packet: &[u8; PACKET_SIZE]) -> Option<u64> {
if packet[0] != SYNC_BYTE {
return None;
}
let adaptation_field_control = (packet[3] >> 4) & 0x03;
if adaptation_field_control & 0x02 == 0 {
return None;
}
let length = usize::from(packet[4]);
if length < 7 {
return None;
}
let flags = packet[5];
if flags & 0x10 == 0 {
return None;
}
let pcr = &packet[6..12];
let base = (u64::from(pcr[0]) << 25)
| (u64::from(pcr[1]) << 17)
| (u64::from(pcr[2]) << 9)
| (u64::from(pcr[3]) << 1)
| (u64::from(pcr[4]) >> 7);
Some(base)
}
#[cfg(test)]
mod tests {
use super::*;
fn packet_with_pcr(base: u64) -> [u8; PACKET_SIZE] {
let mut packet = [0xffu8; PACKET_SIZE];
packet[0] = SYNC_BYTE;
packet[1] = 0x40;
packet[2] = 0x00;
packet[3] = 0x30; packet[4] = 7; packet[5] = 0x10;
packet[6] = (base >> 25) as u8;
packet[7] = (base >> 17) as u8;
packet[8] = (base >> 9) as u8;
packet[9] = (base >> 1) as u8;
packet[10] = ((base & 1) << 7) as u8 | 0x7e;
packet[11] = 0x00;
packet
}
fn packet_without_pcr() -> [u8; PACKET_SIZE] {
let mut packet = [0x00u8; PACKET_SIZE];
packet[0] = SYNC_BYTE;
packet[3] = 0x10; packet
}
#[test]
fn test_pcr_base_round_trip() {
let base = (1 << 33) - 12_345;
assert_eq!(pcr_base(&packet_with_pcr(base)), Some(base));
assert_eq!(pcr_base(&packet_without_pcr()), None);
}
#[test]
fn test_elapsed_spans_first_to_last_pcr_across_chunks() {
let mut clock = StreamClock::default();
assert_eq!(clock.elapsed(), None);
let mut stream = Vec::new();
stream.extend_from_slice(&packet_with_pcr(0));
stream.extend_from_slice(&packet_without_pcr());
stream.extend_from_slice(&packet_with_pcr(90_000)); stream.extend_from_slice(&packet_with_pcr(90_000 * 3));
for chunk in stream.chunks(100) {
clock.observe(chunk);
}
assert_eq!(clock.elapsed(), Some(Duration::from_secs(3)));
}
#[test]
fn test_elapsed_handles_wrap() {
let mut clock = StreamClock::default();
clock.observe(&packet_with_pcr((1 << 33) - 90_000));
clock.observe(&packet_with_pcr(90_000));
assert_eq!(clock.elapsed(), Some(Duration::from_secs(2)));
}
}