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}