Skip to main content

mpeg_ps/
scr.rs

1//! System Clock Reference — 42-bit timestamp at 27 MHz
2//! (ISO/IEC 13818-1 §2.5.3.3, Table 2-39).
3//!
4//! Encoded across 6 bytes: 33-bit base + 9-bit extension, with four interleaved
5//! `marker_bit`s and a `'01'` prefix.
6
7use crate::error::{Error, Result};
8
9/// 33-bit base mask.
10const BASE_MASK: u64 = (1 << 33) - 1;
11/// 9-bit extension mask.
12const EXT_MASK: u64 = (1 << 9) - 1;
13
14/// Number of bytes in the SCR field (including the `01` prefix).
15pub(crate) const SCR_FIELD_LEN: usize = 6;
16
17/// System Clock Reference (42-bit total: 33-bit base + 9-bit extension,
18/// in 27 MHz units).
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize))]
21pub struct Scr {
22    /// 33-bit SCR base (27 MHz ticks, integer part).
23    pub base: u64,
24    /// 9-bit SCR extension (27 MHz ticks, fractional part / 300).
25    pub extension: u16,
26}
27
28impl Scr {
29    /// The total SCR value in 27 MHz ticks.
30    ///
31    /// `base * 300 + extension`.
32    #[must_use]
33    pub const fn ticks(self) -> u64 {
34        self.base * 300 + self.extension as u64
35    }
36
37    /// Value in seconds.
38    #[must_use]
39    pub fn seconds(self) -> f64 {
40        self.ticks() as f64 / 27_000_000.0
41    }
42}
43
44/// Bit-packed SCR field layout (48 bits total, 6 bytes):
45///
46/// `01`                                    | 2  [47:46]
47/// `system_clock_reference_base[32:30]`    | 3  [45:43]
48/// `marker_bit`                            | 1  [42]
49/// `system_clock_reference_base[29:28]`    | 2  [41:40]
50/// `system_clock_reference_base[27:20]`    | 8  [39:32]
51/// `system_clock_reference_base[19:15]`    | 5  [31:27]
52/// `marker_bit`                            | 1  [26]
53/// `system_clock_reference_base[14:13]`    | 2  [25:24]
54/// `system_clock_reference_base[12:5]`     | 8  [23:16]
55/// `system_clock_reference_base[4:0]`      | 5  [15:11]
56/// `marker_bit`                            | 1  [10]
57/// `system_clock_reference_extension[8:7]` | 2  [9:8]
58/// `system_clock_reference_extension[6:0]` | 7  [7:1]
59/// `marker_bit`                            | 1  [0]
60pub(crate) fn read_scr_field(b: &[u8], what: &'static str) -> Result<Scr> {
61    if b.len() < SCR_FIELD_LEN {
62        return Err(Error::BufferTooShort {
63            need: SCR_FIELD_LEN,
64            have: b.len(),
65            what,
66        });
67    }
68
69    let b0 = b[0];
70    let b1 = b[1];
71    let b2 = b[2];
72    let b3 = b[3];
73    let b4 = b[4];
74    let b5 = b[5];
75
76    // Check '01' prefix
77    if (b0 >> 6) != 0b01 {
78        return Err(Error::BadScrPrefix(b0 >> 6));
79    }
80
81    // marker bits
82    if (b0 & 0x04) == 0 {
83        return Err(Error::BadMarker(what));
84    }
85    if (b2 & 0x04) == 0 {
86        return Err(Error::BadMarker(what));
87    }
88    if (b4 & 0x04) == 0 {
89        return Err(Error::BadMarker(what));
90    }
91    if (b5 & 0x01) == 0 {
92        return Err(Error::BadMarker(what));
93    }
94
95    // base[32:30] from b0[5:3]
96    let hi = u64::from((b0 >> 3) & 0x07);
97    // base[29:28] from b0[1:0]
98    let mid_hi = u64::from(b0 & 0x03);
99    // base[27:20] from b1[7:0]
100    let mid_mid = u64::from(b1);
101    // base[19:15] from b2[7:3]
102    let mid_lo = u64::from((b2 >> 3) & 0x1F);
103    // base[14:13] from b2[1:0]
104    let lo_hi = u64::from(b2 & 0x03);
105    // base[12:5] from b3[7:0]
106    let lo_mid = u64::from(b3);
107    // base[4:0] from b4[7:3]
108    let lo_lo = u64::from((b4 >> 3) & 0x1F);
109
110    let base = (hi << 30)
111        | (mid_hi << 28)
112        | (mid_mid << 20)
113        | (mid_lo << 15)
114        | (lo_hi << 13)
115        | (lo_mid << 5)
116        | lo_lo;
117
118    // extension[8:7] from b4[1:0]
119    let ext_hi = u16::from(b4 & 0x03);
120    // extension[6:0] from b5[7:1]
121    let ext_lo = u16::from(b5 >> 1);
122
123    let extension = (ext_hi << 7) | ext_lo;
124
125    Ok(Scr { base, extension })
126}
127
128/// Encode a 42-bit SCR into a 6-byte SCR field (without pack_start_code).
129pub(crate) fn write_scr_field(scr: Scr) -> [u8; SCR_FIELD_LEN] {
130    let base = scr.base & BASE_MASK;
131    let extension = (scr.extension as u64) & EXT_MASK;
132
133    let b0 = 0x40 // '01' in bits [7:6]
134        | (((base >> 30) & 0x07) as u8) << 3   // base[32:30] in bits [5:3]
135        | 0x04 // marker_bit at bit2
136        | ((base >> 28) & 0x03) as u8; // base[29:28] in bits [1:0]
137
138    let b1 = ((base >> 20) & 0xFF) as u8; // base[27:20]
139
140    let b2 = (((base >> 15) & 0x1F) as u8) << 3 // base[19:15] in bits [7:3]
141        | 0x04 // marker_bit at bit2
142        | ((base >> 13) & 0x03) as u8; // base[14:13] in bits [1:0]
143
144    let b3 = ((base >> 5) & 0xFF) as u8; // base[12:5]
145
146    let b4 = ((base & 0x1F) as u8) << 3 // base[4:0] in bits [7:3]
147        | 0x04 // marker_bit at bit2
148        | ((extension >> 7) & 0x03) as u8; // extension[8:7] in bits [1:0]
149
150    let b5 = (((extension & 0x7F) as u8) << 1) // extension[6:0] in bits [7:1]
151        | 0x01; // marker_bit at bit0
152
153    [b0, b1, b2, b3, b4, b5]
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn scr_round_trip_boundary_values() {
162        let cases: &[(u64, u16)] = &[
163            (0, 0),
164            (1, 0),
165            (0, 1),
166            (BASE_MASK, EXT_MASK as u16),
167            (0x0001_9999, 0x0123),
168            (0x0123_4567, 0x1AB),
169        ];
170        for &(base_val, ext) in cases {
171            let scr = Scr {
172                base: base_val,
173                extension: ext,
174            };
175            let enc = write_scr_field(scr);
176            let dec = read_scr_field(&enc, "SCR").unwrap();
177            assert_eq!(dec, scr, "base={base_val:#x}, ext={ext:#x}");
178        }
179    }
180
181    #[test]
182    fn scr_zero_matches_fixture_pattern() {
183        // The ffmpeg-muxed fixture uses SCR base=0, extension=0
184        let scr = Scr {
185            base: 0,
186            extension: 0,
187        };
188        let enc = write_scr_field(scr);
189        // Matches fixture bytes for the constant part of SCR: 44 00 04 00 04 01
190        assert_eq!(enc, [0x44, 0x00, 0x04, 0x00, 0x04, 0x01]);
191        let dec = read_scr_field(&enc, "SCR").unwrap();
192        assert_eq!(dec, scr);
193    }
194
195    #[test]
196    fn scr_rejects_bad_prefix() {
197        let mut enc = write_scr_field(Scr {
198            base: 0,
199            extension: 0,
200        });
201        enc[0] |= 0x80; // change '01' to '11'
202        assert!(matches!(
203            read_scr_field(&enc, "SCR"),
204            Err(Error::BadScrPrefix(_))
205        ));
206    }
207
208    #[test]
209    fn scr_rejects_bad_marker() {
210        for byte_idx in [0usize, 2, 4] {
211            let mut enc = write_scr_field(Scr {
212                base: 0,
213                extension: 0,
214            });
215            enc[byte_idx] &= !0x04; // clear marker at bit2
216            assert!(matches!(
217                read_scr_field(&enc, "SCR"),
218                Err(Error::BadMarker(_))
219            ));
220        }
221        // marker in bit0 of byte5
222        let mut enc = write_scr_field(Scr {
223            base: 0,
224            extension: 0,
225        });
226        enc[5] &= 0xFE;
227        assert!(matches!(
228            read_scr_field(&enc, "SCR"),
229            Err(Error::BadMarker(_))
230        ));
231    }
232
233    #[test]
234    fn scr_ticks() {
235        let scr = Scr {
236            base: 100,
237            extension: 50,
238        };
239        assert_eq!(scr.ticks(), 100 * 300 + 50);
240    }
241
242    #[test]
243    fn scr_seconds() {
244        let scr = Scr {
245            base: 27_000_000 / 300,
246            extension: 0,
247        };
248        assert!((scr.seconds() - 1.0).abs() < 1e-9);
249    }
250}