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    AdaptationField, AdaptationFieldControl, Pcr, ScramblingControl, TsHeader, ADAPTATION_FLAG,
14    AF_PCR_FLAG, CC_MASK, TS_PACKET_SIZE, TS_SYNC_BYTE,
15};
16
17/// The 13-bit PID value used for null packets — `0x1FFF`
18/// (ISO/IEC 13818-1 §2.4.3.3, Table 2-3).
19const NULL_PID: u16 = 0x1FFF;
20
21/// Owned 188-byte TS packet with pre-parsed header fields.
22///
23/// The raw bytes are stored in `raw`; the parsed flags (`pid`, `pusi`, etc.) are
24/// pre-extracted at construction time so hot paths avoid repeated byte masking.
25///
26/// # Payload access
27///
28/// Use [`payload`](Self::payload) / [`payload_mut`](Self::payload_mut) to obtain
29/// a slice that correctly skips the 4-byte header **and** any adaptation field.
30///
31/// # Building packets
32///
33/// [`serialize_with_payload`](Self::serialize_with_payload) constructs a plain
34/// payload-only packet (no adaptation field) filled with 0xFF stuffing.
35#[derive(Clone, Debug)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize))]
37pub struct OwnedTsPacket {
38    /// The raw 188 bytes (serialized as a byte sequence).
39    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_raw_bytes"))]
40    pub raw: [u8; TS_PACKET_SIZE],
41    /// 13-bit PID extracted from bytes 1–2.
42    pub pid: u16,
43    /// Payload Unit Start Indicator (byte 1 bit 6).
44    pub pusi: bool,
45    /// Adaptation field present flag (byte 3 bit 5).
46    pub has_adaptation: bool,
47    /// Payload present flag (byte 3 bit 4).
48    pub has_payload: bool,
49    /// Transport Error Indicator (byte 1 bit 7).
50    pub tei: bool,
51    /// 2-bit transport_scrambling_control (byte 3 bits 7–6).
52    pub scrambling: u8,
53    /// 4-bit continuity_counter (byte 3 bits 3–0).
54    pub continuity_counter: u8,
55    /// Discontinuity flag: `true` if the adaptation-field `discontinuity_indicator`
56    /// was set in the source packet, or if the caller marks this as a
57    /// continuity-counter discontinuity boundary. Defaults to `false` on parse.
58    pub discontinuity: bool,
59}
60
61/// Serialize a `[u8; N]` as a variable-length byte sequence so serde's
62/// blanket impls (only up to `[u8; 32]`) are not required.
63#[cfg(feature = "serde")]
64fn serialize_raw_bytes<S: serde::Serializer>(
65    bytes: &[u8; TS_PACKET_SIZE],
66    s: S,
67) -> core::result::Result<S::Ok, S::Error> {
68    use serde::ser::SerializeSeq;
69    let mut seq = s.serialize_seq(Some(bytes.len()))?;
70    for b in bytes {
71        seq.serialize_element(b)?;
72    }
73    seq.end()
74}
75
76impl OwnedTsPacket {
77    /// Parse a 188-byte owned TS packet.
78    ///
79    /// Returns [`Error::InvalidSyncByte`] if `raw[0] != 0x47`.
80    /// Header bit-parsing is delegated to [`TsHeader::parse`].
81    /// The `discontinuity` field defaults to `false`; set it manually if needed.
82    pub fn parse(raw: [u8; TS_PACKET_SIZE]) -> Result<Self> {
83        if raw[0] != TS_SYNC_BYTE {
84            return Err(Error::InvalidSyncByte { found: raw[0] });
85        }
86        let hdr = TsHeader::parse(&raw[..4])?;
87        Ok(Self {
88            raw,
89            pid: hdr.pid,
90            pusi: hdr.pusi,
91            has_adaptation: hdr.has_adaptation,
92            has_payload: hdr.has_payload,
93            tei: hdr.tei,
94            scrambling: hdr.scrambling,
95            continuity_counter: hdr.continuity_counter,
96            discontinuity: false,
97        })
98    }
99
100    /// Typed view of the 2-bit `transport_scrambling_control` field.
101    ///
102    /// See [`ScramblingControl`] for the spec citation (H.222.0 Table 2-4 +
103    /// ETSI TS 100 289 §5.1 Table 1).
104    pub fn scrambling_control(&self) -> ScramblingControl {
105        ScramblingControl::from_bits(self.scrambling)
106    }
107
108    /// Typed view of the `adaptation_field_control` 2-bit field, derived from the
109    /// stored `has_adaptation`/`has_payload` booleans.
110    ///
111    /// See [`AdaptationFieldControl`] for the spec citation (H.222.0 Table 2-5).
112    pub fn adaptation_field_control(&self) -> AdaptationFieldControl {
113        AdaptationFieldControl::from_flags(self.has_adaptation, self.has_payload)
114    }
115
116    /// Return the payload bytes (after the 4-byte header and any adaptation field).
117    ///
118    /// Returns `None` when [`has_payload`](Self::has_payload) is `false` or the
119    /// adaptation field consumed all remaining bytes.
120    pub fn payload(&self) -> Option<&[u8]> {
121        if !self.has_payload {
122            return None;
123        }
124        let offset = self.payload_offset();
125        if offset < TS_PACKET_SIZE {
126            Some(&self.raw[offset..])
127        } else {
128            None
129        }
130    }
131
132    /// Return a mutable slice of the payload bytes.
133    ///
134    /// Returns `None` when [`has_payload`](Self::has_payload) is `false` or the
135    /// adaptation field consumed all remaining bytes.
136    pub fn payload_mut(&mut self) -> Option<&mut [u8]> {
137        if !self.has_payload {
138            return None;
139        }
140        let offset = self.payload_offset();
141        if offset < TS_PACKET_SIZE {
142            Some(&mut self.raw[offset..])
143        } else {
144            None
145        }
146    }
147
148    /// Compute the byte offset of the first payload byte inside `raw`.
149    ///
150    /// The 4-byte header is always present; if `has_adaptation` is set, the next
151    /// byte is the adaptation-field length, and the payload starts after those
152    /// `1 + af_len` bytes.
153    #[inline]
154    fn payload_offset(&self) -> usize {
155        let mut offset = 4;
156        if self.has_adaptation {
157            // raw[4] is the adaptation_field_length byte
158            let af_len = self.raw[4] as usize;
159            offset += 1 + af_len;
160        }
161        offset
162    }
163
164    /// Build a 188-byte payload-only TS packet (no adaptation field).
165    ///
166    /// The packet is initialised to `0xFF` (MPEG-TS stuffing), the 4-byte header
167    /// is written via [`TsHeader::serialize_into`], then up to 184 bytes of
168    /// `payload` are copied starting at byte 4.  Any unfilled bytes remain `0xFF`.
169    ///
170    /// # Panics
171    ///
172    /// Never panics — serializing a 4-byte header into a 188-byte buffer cannot
173    /// fail.
174    pub fn serialize_with_payload(
175        pid: u16,
176        pusi: bool,
177        cc: u8,
178        payload: &[u8],
179    ) -> [u8; TS_PACKET_SIZE] {
180        let mut pkt = [0xFFu8; TS_PACKET_SIZE];
181        let hdr = TsHeader {
182            tei: false,
183            pusi,
184            pid,
185            scrambling: 0,
186            has_adaptation: false,
187            has_payload: true,
188            continuity_counter: cc & 0x0F,
189        };
190        // Cannot fail: buf is 188 bytes, need 4.
191        hdr.serialize_into(&mut pkt)
192            .expect("serialize TsHeader into 188-byte buf");
193        let copy_len = payload.len().min(184);
194        pkt[4..4 + copy_len].copy_from_slice(&payload[..copy_len]);
195        pkt
196    }
197
198    /// Build a 188-byte null packet (PID `0x1FFF`) with `0xFF`-stuffed payload.
199    ///
200    /// Null packets carry PID `0x1FFF` and have no meaningful payload
201    /// (ISO/IEC 13818-1 §2.4.1). The continuity counter `cc` is masked to 4
202    /// bits. Transport scrambling is `00` (not scrambled).
203    ///
204    /// # Example
205    ///
206    /// ```
207    /// use mpeg_ts::OwnedTsPacket;
208    /// use mpeg_ts::ts::{TsPacket, TS_PACKET_SIZE};
209    /// let raw = OwnedTsPacket::null_packet(3);
210    /// assert_eq!(raw.len(), TS_PACKET_SIZE);
211    /// let pkt = TsPacket::parse(&raw).unwrap();
212    /// assert_eq!(pkt.header.pid, 0x1FFF);
213    /// assert_eq!(pkt.header.continuity_counter, 3);
214    /// ```
215    #[must_use]
216    pub fn null_packet(cc: u8) -> [u8; TS_PACKET_SIZE] {
217        Self::serialize_with_payload(NULL_PID, false, cc, &[])
218    }
219
220    /// Overwrite the `continuity_counter` field in `packet` without re-parsing.
221    ///
222    /// Writes the low 4 bits of `cc` into byte 3 bits `[3:0]` in place
223    /// (ISO/IEC 13818-1 §2.4.3.3). The other bits of byte 3 are preserved.
224    ///
225    /// # Example
226    ///
227    /// ```
228    /// use mpeg_ts::OwnedTsPacket;
229    /// use mpeg_ts::ts::TsPacket;
230    /// let mut raw = OwnedTsPacket::serialize_with_payload(0x0100, false, 0, &[]);
231    /// OwnedTsPacket::set_continuity_counter(&mut raw, 7);
232    /// let pkt = TsPacket::parse(&raw).unwrap();
233    /// assert_eq!(pkt.header.continuity_counter, 7);
234    /// ```
235    pub fn set_continuity_counter(packet: &mut [u8; TS_PACKET_SIZE], cc: u8) {
236        // Byte 3 bits [3:0] = continuity_counter (ISO/IEC 13818-1 §2.4.3.3).
237        packet[3] = (packet[3] & !CC_MASK) | (cc & CC_MASK);
238    }
239
240    /// Overwrite the PCR value in an existing adaptation field in `packet`.
241    ///
242    /// The packet must already have `adaptation_field_control` with adaptation
243    /// present (`has_adaptation == true`) and the PCR flag set in the adaptation
244    /// field flags byte. This function locates the 6-byte PCR field and
245    /// overwrites it with the encoding of `pcr`.
246    ///
247    /// # Errors
248    ///
249    /// - [`Error::BufferTooShort`] — the adaptation field does not contain a
250    ///   PCR slot (no adaptation field, zero-length field, or PCR flag not set).
251    ///
252    /// # Example
253    ///
254    /// ```
255    /// use mpeg_ts::ts::{AdaptationField, AF_PCR_FLAG, Pcr, TsPacket,
256    ///                    TS_PACKET_SIZE, TS_SYNC_BYTE,
257    ///                    ADAPTATION_FLAG, PAYLOAD_FLAG};
258    /// use mpeg_ts::OwnedTsPacket;
259    ///
260    /// // Build a packet that has a PCR slot (adaptation_field_length = 7).
261    /// let mut raw = [0xAAu8; TS_PACKET_SIZE];
262    /// raw[0] = TS_SYNC_BYTE;
263    /// raw[1] = 0x00; raw[2] = 0x64; // PID = 100
264    /// raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
265    /// raw[4] = 7;  // adaptation_field_length
266    /// raw[5] = AF_PCR_FLAG;
267    /// raw[6..12].copy_from_slice(&[0u8; 6]);
268    ///
269    /// let new_pcr = Pcr { base: 10_000, extension: 0 };
270    /// OwnedTsPacket::set_pcr(&mut raw, new_pcr).unwrap();
271    ///
272    /// let pkt = TsPacket::parse(&raw).unwrap();
273    /// let af = pkt.adaptation_field().unwrap().unwrap();
274    /// assert_eq!(af.pcr, Some(new_pcr));
275    /// ```
276    pub fn set_pcr(packet: &mut [u8; TS_PACKET_SIZE], pcr: Pcr) -> Result<()> {
277        // Byte 3: check adaptation_field_control has adaptation bit set
278        // (ADAPTATION_FLAG = bit 5, ISO/IEC 13818-1 §2.4.3.3).
279        if packet[3] & ADAPTATION_FLAG == 0 {
280            return Err(Error::BufferTooShort {
281                need: 6,
282                have: 0,
283                what: "set_pcr: no adaptation field",
284            });
285        }
286        let af_len = packet[4] as usize;
287        if af_len < 1 {
288            return Err(Error::BufferTooShort {
289                need: 1,
290                have: 0,
291                what: "set_pcr: adaptation field length is 0 (no flags byte)",
292            });
293        }
294        // Byte 5: adaptation field flags byte (AF_PCR_FLAG = 0x10, §2.4.3.4).
295        if packet[5] & AF_PCR_FLAG == 0 {
296            return Err(Error::BufferTooShort {
297                need: 6,
298                have: 0,
299                what: "set_pcr: PCR flag not set in adaptation field",
300            });
301        }
302        // PCR occupies the 6 bytes starting at offset 6 (after header + af_len byte + flags byte).
303        let pcr_start = 6usize;
304        let pcr_end = pcr_start + 6;
305        if packet.len() < pcr_end {
306            return Err(Error::BufferTooShort {
307                need: pcr_end,
308                have: packet.len(),
309                what: "set_pcr: packet too short for PCR field",
310            });
311        }
312        packet[pcr_start..pcr_end].copy_from_slice(&pcr.to_field_bytes());
313        Ok(())
314    }
315
316    /// Decode the adaptation field from this packet, if present.
317    ///
318    /// Returns `None` when no adaptation field is present, and
319    /// `Some(Err(..))` when the adaptation field bytes are malformed.
320    /// The returned [`AdaptationField`] borrows from `self.raw`.
321    pub fn adaptation_field(&self) -> Option<crate::Result<AdaptationField<'_>>> {
322        if self.raw[3] & ADAPTATION_FLAG == 0 {
323            return None;
324        }
325        let af_len = self.raw[4] as usize;
326        if af_len == 0 || 5 + af_len > TS_PACKET_SIZE {
327            return None;
328        }
329        Some(AdaptationField::parse(&self.raw[5..5 + af_len]))
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336
337    #[test]
338    fn owned_round_trip_and_payload_mut() {
339        let payload = [0xAAu8; 184];
340        let mut pkt = OwnedTsPacket::parse(OwnedTsPacket::serialize_with_payload(
341            0x0100, true, 7, &payload,
342        ))
343        .unwrap();
344        assert_eq!(pkt.pid, 0x0100);
345        assert!(pkt.pusi);
346        assert_eq!(pkt.continuity_counter, 7);
347        assert_eq!(pkt.payload().unwrap()[..184], payload[..]);
348        pkt.payload_mut().unwrap()[0] = 0x55;
349        assert_eq!(pkt.payload().unwrap()[0], 0x55);
350        // discontinuity defaults to false
351        assert!(!pkt.discontinuity);
352    }
353
354    #[test]
355    fn owned_scrambling_control_accessor() {
356        let make = |scrambling_bits: u8| -> OwnedTsPacket {
357            let mut raw = OwnedTsPacket::serialize_with_payload(0x0100, false, 0, &[]);
358            // byte 3 bits [7:6] = scrambling
359            raw[3] = (raw[3] & 0x3F) | (scrambling_bits << 6);
360            OwnedTsPacket::parse(raw).unwrap()
361        };
362        assert_eq!(
363            make(0b00).scrambling_control(),
364            ScramblingControl::NotScrambled
365        );
366        assert_eq!(make(0b01).scrambling_control(), ScramblingControl::Reserved);
367        assert_eq!(make(0b10).scrambling_control(), ScramblingControl::EvenKey);
368        assert_eq!(make(0b11).scrambling_control(), ScramblingControl::OddKey);
369    }
370
371    #[test]
372    fn owned_adaptation_field_control_accessor() {
373        let make = |afc_bits: u8| -> OwnedTsPacket {
374            let mut raw = [0xFFu8; TS_PACKET_SIZE];
375            raw[0] = TS_SYNC_BYTE;
376            raw[1] = 0x00;
377            raw[2] = 0x00;
378            raw[3] = (afc_bits << 4) & 0x30;
379            if afc_bits & 0b10 != 0 {
380                raw[4] = 0; // adaptation_field_length = 0
381            }
382            OwnedTsPacket::parse(raw).unwrap()
383        };
384        assert_eq!(
385            make(0b00).adaptation_field_control(),
386            AdaptationFieldControl::Reserved
387        );
388        assert_eq!(
389            make(0b01).adaptation_field_control(),
390            AdaptationFieldControl::PayloadOnly
391        );
392        assert_eq!(
393            make(0b10).adaptation_field_control(),
394            AdaptationFieldControl::AdaptationOnly
395        );
396        assert_eq!(
397            make(0b11).adaptation_field_control(),
398            AdaptationFieldControl::AdaptationAndPayload
399        );
400    }
401}