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}