use crate::error::{Error, Result};
const TS_MASK: u64 = (1 << 33) - 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Pts(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Dts(pub u64);
impl Pts {
#[must_use]
pub const fn ticks(self) -> u64 {
self.0
}
#[must_use]
pub fn seconds(self) -> f64 {
self.0 as f64 / 90_000.0
}
#[must_use]
pub fn to_field_bytes(self) -> [u8; 5] {
write(self.0, 0b0010)
}
pub fn from_field_bytes(b: &[u8; 5]) -> crate::Result<Self> {
Ok(Pts(read(b, 0b0010, "PTS")?))
}
pub fn from_field_bytes_with_dts(b: &[u8; 5]) -> crate::Result<Self> {
Ok(Pts(read(b, 0b0011, "PTS(with DTS)")?))
}
}
impl Dts {
#[must_use]
pub const fn ticks(self) -> u64 {
self.0
}
#[must_use]
pub fn seconds(self) -> f64 {
self.0 as f64 / 90_000.0
}
#[must_use]
pub fn to_field_bytes(self) -> [u8; 5] {
write(self.0, 0b0001)
}
pub fn from_field_bytes(b: &[u8; 5]) -> crate::Result<Self> {
Ok(Dts(read(b, 0b0001, "DTS")?))
}
}
pub(crate) fn read(b: &[u8], prefix: u8, what: &'static str) -> Result<u64> {
if b.len() < 5 {
return Err(Error::BufferTooShort {
need: 5,
have: b.len(),
what,
});
}
if (b[0] >> 4) != prefix {
return Err(Error::BadTimestampPrefix(what));
}
if b[0] & 0x01 == 0 || b[2] & 0x01 == 0 || b[4] & 0x01 == 0 {
return Err(Error::BadTimestampMarker(what));
}
let hi = u64::from((b[0] >> 1) & 0x07); let mid = (u64::from(b[1]) << 7) | u64::from(b[2] >> 1); let lo = (u64::from(b[3]) << 7) | u64::from(b[4] >> 1); Ok((hi << 30) | (mid << 15) | lo)
}
pub(crate) fn write(ts: u64, prefix: u8) -> [u8; 5] {
let ts = ts & TS_MASK;
[
(prefix << 4) | ((((ts >> 30) & 0x07) as u8) << 1) | 0x01,
((ts >> 22) & 0xFF) as u8,
((((ts >> 15) & 0x7F) as u8) << 1) | 0x01,
((ts >> 7) & 0xFF) as u8,
(((ts & 0x7F) as u8) << 1) | 0x01,
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pts_round_trip_boundary_values() {
for ts in [0u64, 1, 90_000, 0x1_2345_6789, TS_MASK] {
let enc = write(ts, 0b0010);
assert_eq!(read(&enc, 0b0010, "pts").unwrap(), ts, "ts={ts:#x}");
}
}
#[test]
fn rejects_bad_prefix() {
let enc = write(0, 0b0011);
assert!(matches!(
read(&enc, 0b0010, "pts"),
Err(Error::BadTimestampPrefix(_))
));
}
#[test]
fn rejects_bad_marker() {
let mut enc = write(0, 0b0010);
enc[2] &= 0xFE; assert!(matches!(
read(&enc, 0b0010, "pts"),
Err(Error::BadTimestampMarker(_))
));
}
#[test]
fn seconds() {
assert!((Pts(90_000).seconds() - 1.0).abs() < 1e-9);
}
#[test]
fn pts_from_field_bytes_round_trip() {
for val in [0u64, 1, 90_000, 0x1_FFFF_FFFF, TS_MASK] {
let pts = Pts(val);
let bytes = pts.to_field_bytes();
let decoded = Pts::from_field_bytes(&bytes).unwrap();
assert_eq!(decoded, pts, "val={val:#x}");
}
}
#[test]
fn pts_from_field_bytes_with_dts_round_trip() {
let pts = Pts(0x1234_5678);
let bytes = crate::timestamp::write(pts.0, 0b0011);
let decoded = Pts::from_field_bytes_with_dts(&bytes).unwrap();
assert_eq!(decoded, pts);
}
#[test]
fn dts_from_field_bytes_round_trip() {
for val in [0u64, 1, 90_000, TS_MASK] {
let dts = Dts(val);
let bytes = dts.to_field_bytes();
let decoded = Dts::from_field_bytes(&bytes).unwrap();
assert_eq!(decoded, dts, "val={val:#x}");
}
}
}