Skip to main content

mpeg_pes/
timestamp.rs

1//! PTS / DTS — 33-bit presentation / decoding timestamps at 90 kHz
2//! (ISO/IEC 13818-1 §2.4.3.7). Encoded across 5 bytes with a 4-bit prefix and
3//! three interleaved `marker_bit`s.
4
5use crate::error::{Error, Result};
6
7/// 33-bit value mask.
8const TS_MASK: u64 = (1 << 33) - 1;
9
10/// Presentation Time Stamp (33-bit, 90 kHz units).
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize))]
13pub struct Pts(pub u64);
14
15/// Decoding Time Stamp (33-bit, 90 kHz units).
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize))]
18pub struct Dts(pub u64);
19
20impl Pts {
21    /// Value in 90 kHz units (0..=2³³−1).
22    #[must_use]
23    pub const fn ticks(self) -> u64 {
24        self.0
25    }
26    /// Value in seconds.
27    #[must_use]
28    pub fn seconds(self) -> f64 {
29        self.0 as f64 / 90_000.0
30    }
31    /// Encode as a standalone 5-byte PTS field (prefix `0010`, for `PTS_DTS_flags = 10`).
32    #[must_use]
33    pub fn to_field_bytes(self) -> [u8; 5] {
34        write(self.0, 0b0010)
35    }
36    /// Decode from a 5-byte PTS-only field (prefix `0010`).
37    ///
38    /// Exact inverse of [`to_field_bytes`](Self::to_field_bytes).
39    /// Returns an error if the prefix or marker bits are wrong.
40    pub fn from_field_bytes(b: &[u8; 5]) -> crate::Result<Self> {
41        Ok(Pts(read(b, 0b0010, "PTS")?))
42    }
43    /// Decode from a 5-byte PTS field in a PTS+DTS pair (prefix `0011`).
44    pub fn from_field_bytes_with_dts(b: &[u8; 5]) -> crate::Result<Self> {
45        Ok(Pts(read(b, 0b0011, "PTS(with DTS)")?))
46    }
47}
48
49impl Dts {
50    /// Value in 90 kHz units (0..=2³³−1).
51    #[must_use]
52    pub const fn ticks(self) -> u64 {
53        self.0
54    }
55    /// Value in seconds.
56    #[must_use]
57    pub fn seconds(self) -> f64 {
58        self.0 as f64 / 90_000.0
59    }
60    /// Encode as a 5-byte DTS field (prefix `0001`, the DTS half of a PTS+DTS pair).
61    #[must_use]
62    pub fn to_field_bytes(self) -> [u8; 5] {
63        write(self.0, 0b0001)
64    }
65    /// Decode from a 5-byte DTS field (prefix `0001`).
66    ///
67    /// Exact inverse of [`to_field_bytes`](Self::to_field_bytes).
68    pub fn from_field_bytes(b: &[u8; 5]) -> crate::Result<Self> {
69        Ok(Dts(read(b, 0b0001, "DTS")?))
70    }
71}
72
73/// Decode a 5-byte PTS/DTS field. `prefix` is the expected leading 4-bit value
74/// (`0b0010` PTS-only, `0b0011` PTS in a PTS+DTS pair, `0b0001` DTS). The three
75/// `marker_bit`s must be `1`.
76pub(crate) fn read(b: &[u8], prefix: u8, what: &'static str) -> Result<u64> {
77    if b.len() < 5 {
78        return Err(Error::BufferTooShort {
79            need: 5,
80            have: b.len(),
81            what,
82        });
83    }
84    if (b[0] >> 4) != prefix {
85        return Err(Error::BadTimestampPrefix(what));
86    }
87    if b[0] & 0x01 == 0 || b[2] & 0x01 == 0 || b[4] & 0x01 == 0 {
88        return Err(Error::BadTimestampMarker(what));
89    }
90    let hi = u64::from((b[0] >> 1) & 0x07); // [32:30]
91    let mid = (u64::from(b[1]) << 7) | u64::from(b[2] >> 1); // [29:15]
92    let lo = (u64::from(b[3]) << 7) | u64::from(b[4] >> 1); // [14:0]
93    Ok((hi << 30) | (mid << 15) | lo)
94}
95
96/// Encode a 33-bit value into a 5-byte PTS/DTS field with the given 4-bit prefix.
97pub(crate) fn write(ts: u64, prefix: u8) -> [u8; 5] {
98    let ts = ts & TS_MASK;
99    [
100        (prefix << 4) | ((((ts >> 30) & 0x07) as u8) << 1) | 0x01,
101        ((ts >> 22) & 0xFF) as u8,
102        ((((ts >> 15) & 0x7F) as u8) << 1) | 0x01,
103        ((ts >> 7) & 0xFF) as u8,
104        (((ts & 0x7F) as u8) << 1) | 0x01,
105    ]
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn pts_round_trip_boundary_values() {
114        for ts in [0u64, 1, 90_000, 0x1_2345_6789, TS_MASK] {
115            let enc = write(ts, 0b0010);
116            assert_eq!(read(&enc, 0b0010, "pts").unwrap(), ts, "ts={ts:#x}");
117        }
118    }
119
120    #[test]
121    fn rejects_bad_prefix() {
122        let enc = write(0, 0b0011);
123        assert!(matches!(
124            read(&enc, 0b0010, "pts"),
125            Err(Error::BadTimestampPrefix(_))
126        ));
127    }
128
129    #[test]
130    fn rejects_bad_marker() {
131        let mut enc = write(0, 0b0010);
132        enc[2] &= 0xFE; // clear a marker bit
133        assert!(matches!(
134            read(&enc, 0b0010, "pts"),
135            Err(Error::BadTimestampMarker(_))
136        ));
137    }
138
139    #[test]
140    fn seconds() {
141        assert!((Pts(90_000).seconds() - 1.0).abs() < 1e-9);
142    }
143
144    // ── from_field_bytes round-trips ─────────────────────────────────────────
145
146    /// `to_field_bytes` → `from_field_bytes` → same value.
147    #[test]
148    fn pts_from_field_bytes_round_trip() {
149        for val in [0u64, 1, 90_000, 0x1_FFFF_FFFF, TS_MASK] {
150            let pts = Pts(val);
151            let bytes = pts.to_field_bytes();
152            let decoded = Pts::from_field_bytes(&bytes).unwrap();
153            assert_eq!(decoded, pts, "val={val:#x}");
154        }
155    }
156
157    /// `to_field_bytes` → `from_field_bytes_with_dts` → same value.
158    #[test]
159    fn pts_from_field_bytes_with_dts_round_trip() {
160        let pts = Pts(0x1234_5678);
161        // In a PTS+DTS pair, PTS uses prefix 0b0011.
162        let bytes = crate::timestamp::write(pts.0, 0b0011);
163        let decoded = Pts::from_field_bytes_with_dts(&bytes).unwrap();
164        assert_eq!(decoded, pts);
165    }
166
167    /// `Dts::to_field_bytes` → `Dts::from_field_bytes` → same value.
168    #[test]
169    fn dts_from_field_bytes_round_trip() {
170        for val in [0u64, 1, 90_000, TS_MASK] {
171            let dts = Dts(val);
172            let bytes = dts.to_field_bytes();
173            let decoded = Dts::from_field_bytes(&bytes).unwrap();
174            assert_eq!(decoded, dts, "val={val:#x}");
175        }
176    }
177}