Skip to main content

mpeg_ts/
owned.rs

1//! Owned 188-byte TS packet with pre-parsed header fields.
2//!
3//! [`OwnedTsPacket`] complements the zero-copy [`crate::ts::TsPacket`] (which holds
4//! a borrowed `&[u8; 188]`) with an **owned** `[u8; 188]` suitable for queuing,
5//! cloning, and in-place mutation — e.g. for mux pipelines that must rewrite the
6//! continuity counter or splice in a new payload.
7//!
8//! Header parsing delegates to [`crate::ts::TsHeader::parse`]; no bit-twiddling
9//! is duplicated here.
10
11use crate::error::{Error, Result};
12use crate::ts::{
13    AdaptationFieldControl, ScramblingControl, TsHeader, TS_PACKET_SIZE, TS_SYNC_BYTE,
14};
15
16/// Owned 188-byte TS packet with pre-parsed header fields.
17///
18/// The raw bytes are stored in `raw`; the parsed flags (`pid`, `pusi`, etc.) are
19/// pre-extracted at construction time so hot paths avoid repeated byte masking.
20///
21/// # Payload access
22///
23/// Use [`payload`](Self::payload) / [`payload_mut`](Self::payload_mut) to obtain
24/// a slice that correctly skips the 4-byte header **and** any adaptation field.
25///
26/// # Building packets
27///
28/// [`serialize_with_payload`](Self::serialize_with_payload) constructs a plain
29/// payload-only packet (no adaptation field) filled with 0xFF stuffing.
30#[derive(Clone, Debug)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize))]
32pub struct OwnedTsPacket {
33    /// The raw 188 bytes (serialized as a byte sequence).
34    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_raw_bytes"))]
35    pub raw: [u8; TS_PACKET_SIZE],
36    /// 13-bit PID extracted from bytes 1–2.
37    pub pid: u16,
38    /// Payload Unit Start Indicator (byte 1 bit 6).
39    pub pusi: bool,
40    /// Adaptation field present flag (byte 3 bit 5).
41    pub has_adaptation: bool,
42    /// Payload present flag (byte 3 bit 4).
43    pub has_payload: bool,
44    /// Transport Error Indicator (byte 1 bit 7).
45    pub tei: bool,
46    /// 2-bit transport_scrambling_control (byte 3 bits 7–6).
47    pub scrambling: u8,
48    /// 4-bit continuity_counter (byte 3 bits 3–0).
49    pub continuity_counter: u8,
50    /// Discontinuity flag: `true` if the adaptation-field `discontinuity_indicator`
51    /// was set in the source packet, or if the caller marks this as a
52    /// continuity-counter discontinuity boundary. Defaults to `false` on parse.
53    pub discontinuity: bool,
54}
55
56/// Serialize a `[u8; N]` as a variable-length byte sequence so serde's
57/// blanket impls (only up to `[u8; 32]`) are not required.
58#[cfg(feature = "serde")]
59fn serialize_raw_bytes<S: serde::Serializer>(
60    bytes: &[u8; TS_PACKET_SIZE],
61    s: S,
62) -> core::result::Result<S::Ok, S::Error> {
63    use serde::ser::SerializeSeq;
64    let mut seq = s.serialize_seq(Some(bytes.len()))?;
65    for b in bytes {
66        seq.serialize_element(b)?;
67    }
68    seq.end()
69}
70
71impl OwnedTsPacket {
72    /// Parse a 188-byte owned TS packet.
73    ///
74    /// Returns [`Error::InvalidSyncByte`] if `raw[0] != 0x47`.
75    /// Header bit-parsing is delegated to [`TsHeader::parse`].
76    /// The `discontinuity` field defaults to `false`; set it manually if needed.
77    pub fn parse(raw: [u8; TS_PACKET_SIZE]) -> Result<Self> {
78        if raw[0] != TS_SYNC_BYTE {
79            return Err(Error::InvalidSyncByte { found: raw[0] });
80        }
81        let hdr = TsHeader::parse(&raw[..4])?;
82        Ok(Self {
83            raw,
84            pid: hdr.pid,
85            pusi: hdr.pusi,
86            has_adaptation: hdr.has_adaptation,
87            has_payload: hdr.has_payload,
88            tei: hdr.tei,
89            scrambling: hdr.scrambling,
90            continuity_counter: hdr.continuity_counter,
91            discontinuity: false,
92        })
93    }
94
95    /// Typed view of the 2-bit `transport_scrambling_control` field.
96    ///
97    /// See [`ScramblingControl`] for the spec citation (H.222.0 Table 2-4 +
98    /// ETSI TS 100 289 §5.1 Table 1).
99    pub fn scrambling_control(&self) -> ScramblingControl {
100        ScramblingControl::from_bits(self.scrambling)
101    }
102
103    /// Typed view of the `adaptation_field_control` 2-bit field, derived from the
104    /// stored `has_adaptation`/`has_payload` booleans.
105    ///
106    /// See [`AdaptationFieldControl`] for the spec citation (H.222.0 Table 2-5).
107    pub fn adaptation_field_control(&self) -> AdaptationFieldControl {
108        AdaptationFieldControl::from_flags(self.has_adaptation, self.has_payload)
109    }
110
111    /// Return the payload bytes (after the 4-byte header and any adaptation field).
112    ///
113    /// Returns `None` when [`has_payload`](Self::has_payload) is `false` or the
114    /// adaptation field consumed all remaining bytes.
115    pub fn payload(&self) -> Option<&[u8]> {
116        if !self.has_payload {
117            return None;
118        }
119        let offset = self.payload_offset();
120        if offset < TS_PACKET_SIZE {
121            Some(&self.raw[offset..])
122        } else {
123            None
124        }
125    }
126
127    /// Return a mutable slice of the payload bytes.
128    ///
129    /// Returns `None` when [`has_payload`](Self::has_payload) is `false` or the
130    /// adaptation field consumed all remaining bytes.
131    pub fn payload_mut(&mut self) -> Option<&mut [u8]> {
132        if !self.has_payload {
133            return None;
134        }
135        let offset = self.payload_offset();
136        if offset < TS_PACKET_SIZE {
137            Some(&mut self.raw[offset..])
138        } else {
139            None
140        }
141    }
142
143    /// Compute the byte offset of the first payload byte inside `raw`.
144    ///
145    /// The 4-byte header is always present; if `has_adaptation` is set, the next
146    /// byte is the adaptation-field length, and the payload starts after those
147    /// `1 + af_len` bytes.
148    #[inline]
149    fn payload_offset(&self) -> usize {
150        let mut offset = 4;
151        if self.has_adaptation {
152            // raw[4] is the adaptation_field_length byte
153            let af_len = self.raw[4] as usize;
154            offset += 1 + af_len;
155        }
156        offset
157    }
158
159    /// Build a 188-byte payload-only TS packet (no adaptation field).
160    ///
161    /// The packet is initialised to `0xFF` (MPEG-TS stuffing), the 4-byte header
162    /// is written via [`TsHeader::serialize_into`], then up to 184 bytes of
163    /// `payload` are copied starting at byte 4.  Any unfilled bytes remain `0xFF`.
164    ///
165    /// # Panics
166    ///
167    /// Never panics — serializing a 4-byte header into a 188-byte buffer cannot
168    /// fail.
169    pub fn serialize_with_payload(
170        pid: u16,
171        pusi: bool,
172        cc: u8,
173        payload: &[u8],
174    ) -> [u8; TS_PACKET_SIZE] {
175        let mut pkt = [0xFFu8; TS_PACKET_SIZE];
176        let hdr = TsHeader {
177            tei: false,
178            pusi,
179            pid,
180            scrambling: 0,
181            has_adaptation: false,
182            has_payload: true,
183            continuity_counter: cc & 0x0F,
184        };
185        // Cannot fail: buf is 188 bytes, need 4.
186        hdr.serialize_into(&mut pkt)
187            .expect("serialize TsHeader into 188-byte buf");
188        let copy_len = payload.len().min(184);
189        pkt[4..4 + copy_len].copy_from_slice(&payload[..copy_len]);
190        pkt
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[test]
199    fn owned_round_trip_and_payload_mut() {
200        let payload = [0xAAu8; 184];
201        let mut pkt = OwnedTsPacket::parse(OwnedTsPacket::serialize_with_payload(
202            0x0100, true, 7, &payload,
203        ))
204        .unwrap();
205        assert_eq!(pkt.pid, 0x0100);
206        assert!(pkt.pusi);
207        assert_eq!(pkt.continuity_counter, 7);
208        assert_eq!(pkt.payload().unwrap()[..184], payload[..]);
209        pkt.payload_mut().unwrap()[0] = 0x55;
210        assert_eq!(pkt.payload().unwrap()[0], 0x55);
211        // discontinuity defaults to false
212        assert!(!pkt.discontinuity);
213    }
214
215    #[test]
216    fn owned_scrambling_control_accessor() {
217        let make = |scrambling_bits: u8| -> OwnedTsPacket {
218            let mut raw = OwnedTsPacket::serialize_with_payload(0x0100, false, 0, &[]);
219            // byte 3 bits [7:6] = scrambling
220            raw[3] = (raw[3] & 0x3F) | (scrambling_bits << 6);
221            OwnedTsPacket::parse(raw).unwrap()
222        };
223        assert_eq!(
224            make(0b00).scrambling_control(),
225            ScramblingControl::NotScrambled
226        );
227        assert_eq!(make(0b01).scrambling_control(), ScramblingControl::Reserved);
228        assert_eq!(make(0b10).scrambling_control(), ScramblingControl::EvenKey);
229        assert_eq!(make(0b11).scrambling_control(), ScramblingControl::OddKey);
230    }
231
232    #[test]
233    fn owned_adaptation_field_control_accessor() {
234        let make = |afc_bits: u8| -> OwnedTsPacket {
235            let mut raw = [0xFFu8; TS_PACKET_SIZE];
236            raw[0] = TS_SYNC_BYTE;
237            raw[1] = 0x00;
238            raw[2] = 0x00;
239            raw[3] = (afc_bits << 4) & 0x30;
240            if afc_bits & 0b10 != 0 {
241                raw[4] = 0; // adaptation_field_length = 0
242            }
243            OwnedTsPacket::parse(raw).unwrap()
244        };
245        assert_eq!(
246            make(0b00).adaptation_field_control(),
247            AdaptationFieldControl::Reserved
248        );
249        assert_eq!(
250            make(0b01).adaptation_field_control(),
251            AdaptationFieldControl::PayloadOnly
252        );
253        assert_eq!(
254            make(0b10).adaptation_field_control(),
255            AdaptationFieldControl::AdaptationOnly
256        );
257        assert_eq!(
258            make(0b11).adaptation_field_control(),
259            AdaptationFieldControl::AdaptationAndPayload
260        );
261    }
262}