Skip to main content

oxideav_mpegts/
pes.rs

1//! PES (Packetized Elementary Stream) reassembly per
2//! ISO/IEC 13818-1 §2.4.3.6.
3//!
4//! Wire layout (Table 2-21):
5//!
6//! ```text
7//! packet_start_code_prefix (24, = 0x00_00_01)
8//! stream_id (8)
9//! PES_packet_length (16)
10//! ```
11//!
12//! For most `stream_id` values an "Optional PES header" follows:
13//!
14//! ```text
15//! '10' (2) | PES_scrambling_control (2) | PES_priority (1) |
16//! data_alignment_indicator (1) | copyright (1) | original_or_copy (1) |
17//! PTS_DTS_flags (2) | ESCR_flag (1) | ES_rate_flag (1) |
18//! DSM_trick_mode_flag (1) | additional_copy_info_flag (1) |
19//! PES_CRC_flag (1) | PES_extension_flag (1) |
20//! PES_header_data_length (8) |
21//! <optional headers per flags above> |
22//! <padding (0xFF)> |
23//! <data>
24//! ```
25//!
26//! PTS/DTS (Table 2-22) — 5 bytes each, four `'0001'`/`'0011'` marker
27//! prefixes for the 33-bit 90 kHz timestamp:
28//!
29//! ```text
30//! '0011' | PTS[32..30] | '1' | PTS[29..15] | '1' | PTS[14..0] | '1'
31//! ```
32//!
33//! ## Reassembly model
34//!
35//! Live transport streams interleave PES packets across many TS
36//! packets on the same PID. A PES packet begins on a TS packet whose
37//! `payload_unit_start_indicator` is set; subsequent TS packets with
38//! the same PID contain continuation bytes; a new PUSI=1 packet ends
39//! the previous PES packet and starts the next.
40//!
41//! Callers drive [`PesReassembler::feed`] with each TS packet for a
42//! given PID. The reassembler returns `Some(PesPacket)` exactly when
43//! the *previous* PES packet has been completed by either:
44//!
45//! - a TS packet with PUSI=1 arriving (which starts the next packet),
46//!   or
47//! - the caller invoking [`PesReassembler::flush`] at end-of-stream.
48
49use crate::{TsError, TsPacket};
50
51/// "stream_id" values whose PES packet has no Optional PES header —
52/// the body bytes follow the 6-byte fixed header directly.
53///
54/// Per ISO/IEC 13818-1 §2.4.3.7: program_stream_map, padding_stream,
55/// private_stream_2, ECM, EMM, program_stream_directory, DSMCC_stream,
56/// H.222.1 type E.
57fn has_optional_pes_header(stream_id: u8) -> bool {
58    !matches!(
59        stream_id,
60        0xBC // program_stream_map
61        | 0xBE // padding_stream
62        | 0xBF // private_stream_2
63        | 0xF0 // ECM
64        | 0xF1 // EMM
65        | 0xFF // program_stream_directory
66        | 0xF2 // DSM-CC stream
67        | 0xF8 // ITU-T Rec. H.222.1 type E
68    )
69}
70
71/// One complete PES packet — `stream_id`, optional PTS/DTS, payload,
72/// and the per-spec optional-header fields parsed from §2.4.3.7
73/// Table 2-17.
74///
75/// Fields after `payload` mirror the optional flags in the byte
76/// immediately after the `'10'` marker. They are populated only for
77/// `stream_id` values that carry the optional PES header; for the
78/// header-less stream IDs (program_stream_map, padding_stream, …) they
79/// stay at their `None` / zero defaults.
80#[derive(Debug, Clone)]
81pub struct PesPacket {
82    /// `stream_id` byte from the PES header (Table 2-18).
83    pub stream_id: u8,
84    /// 2-bit `PES_scrambling_control` (Table 2-19).
85    pub pes_scrambling_control: u8,
86    /// `PES_priority` flag.
87    pub pes_priority: bool,
88    /// `data_alignment_indicator` flag (refer to
89    /// `data_stream_alignment_descriptor`, §2.6.10).
90    pub data_alignment_indicator: bool,
91    /// `copyright` flag.
92    pub copyright: bool,
93    /// `original_or_copy` flag — `true` when the payload is an
94    /// original.
95    pub original_or_copy: bool,
96    /// 33-bit Presentation Time Stamp (90 kHz), when present.
97    pub pts_90k: Option<u64>,
98    /// 33-bit Decoding Time Stamp (90 kHz), when present.
99    pub dts_90k: Option<u64>,
100    /// 42-bit Elementary Stream Clock Reference (27 MHz), when
101    /// present. Computed as `ESCR_base * 300 + ESCR_extension` per
102    /// equation 2-13.
103    pub escr_27mhz: Option<u64>,
104    /// 22-bit `ES_rate` field, in units of 50 bytes/second, when
105    /// present. The decoded byte-rate is `value * 50`.
106    pub es_rate_50bps: Option<u32>,
107    /// Raw 8-bit DSM trick-mode byte (`trick_mode_control` in the top
108    /// 3 bits, mode-specific tail in the bottom 5), when present.
109    pub dsm_trick_mode: Option<u8>,
110    /// 7-bit `additional_copy_info`, when present.
111    pub additional_copy_info: Option<u8>,
112    /// 16-bit `previous_PES_packet_CRC` value, when present.
113    pub previous_pes_packet_crc: Option<u16>,
114    /// Decoded `PES_extension` body, present exactly when the
115    /// `PES_extension_flag` was set in the header (Table 2-17,
116    /// concluded). `pes_extension.is_some()` is the old
117    /// "extension present" signal; the sub-fields are now decoded.
118    pub pes_extension: Option<PesExtension>,
119    /// Elementary-stream payload bytes (after the optional PES header).
120    pub payload: Vec<u8>,
121}
122
123/// Decoded `PES_extension` body — the flag-gated tail of the optional
124/// PES header per ISO/IEC 13818-1 Table 2-17 (concluded) / §2.4.3.7.
125///
126/// Wire layout when `PES_extension_flag == 1`:
127///
128/// ```text
129/// PES_private_data_flag (1) | pack_header_field_flag (1) |
130/// program_packet_sequence_counter_flag (1) | P-STD_buffer_flag (1) |
131/// reserved (3) | PES_extension_flag_2 (1) |
132/// [PES_private_data (128)] |
133/// [pack_field_length (8) + pack_header()] |
134/// [marker (1) + program_packet_sequence_counter (7) +
135///  marker (1) + MPEG1_MPEG2_identifier (1) + original_stuff_length (6)] |
136/// ['01' (2) + P-STD_buffer_scale (1) + P-STD_buffer_size (13)] |
137/// [marker (1) + PES_extension_field_length (7) + reserved bytes]
138/// ```
139///
140/// Each `Option` field maps to one of the five sub-flags.
141#[derive(Debug, Clone, PartialEq, Eq, Default)]
142pub struct PesExtension {
143    /// 16-byte `PES_private_data` (§2.4.3.7: private data that,
144    /// combined with surrounding fields, must not emulate the
145    /// `packet_start_code_prefix`).
146    pub private_data: Option<[u8; 16]>,
147    /// Raw `pack_header()` bytes (an ISO/IEC 11172-1 or Program Stream
148    /// pack header carried verbatim; `pack_field_length` gives its
149    /// size). Always `None` in a conforming Program Stream; this crate
150    /// surfaces the bytes without interpreting them.
151    pub pack_header: Option<Vec<u8>>,
152    /// `program_packet_sequence_counter` group, when present.
153    pub program_packet_sequence_counter: Option<ProgramPacketSequenceCounter>,
154    /// `P-STD_buffer_scale` / `P-STD_buffer_size` pair, when present.
155    pub p_std_buffer: Option<PStdBuffer>,
156    /// Raw bytes of the `PES_extension_flag_2` field — in this edition
157    /// of the spec every one of the `PES_extension_field_length` bytes
158    /// is `reserved`, so they are surfaced verbatim.
159    pub extension_field_2: Option<Vec<u8>>,
160}
161
162/// `program_packet_sequence_counter` group (Table 2-17, concluded) — an
163/// optional 7-bit per-program PES packet counter providing continuity-
164/// counter-like functionality across a Program Stream or ISO/IEC
165/// 11172-1 stream carried in PES packets (§2.4.3.7).
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub struct ProgramPacketSequenceCounter {
168    /// 7-bit counter; wraps to 0 past its maximum. No two consecutive
169    /// PES packets in the program multiplex may carry the same value.
170    pub counter: u8,
171    /// `MPEG1_MPEG2_identifier` — `true` when this PES packet carries
172    /// information from an ISO/IEC 11172-1 stream, `false` for a
173    /// Program Stream.
174    pub mpeg1_mpeg2_identifier: bool,
175    /// 6-bit `original_stuff_length` — number of stuffing bytes used
176    /// in the original PES packet header (or original ISO/IEC 11172-1
177    /// packet header).
178    pub original_stuff_length: u8,
179}
180
181/// `P-STD_buffer_scale` + `P-STD_buffer_size` pair (Table 2-17,
182/// concluded). Semantics are only defined when the PES packet is
183/// carried in a Program Stream (§2.4.3.7): the pair sizes the P-STD
184/// input buffer BSn.
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186pub struct PStdBuffer {
187    /// `P-STD_buffer_scale` — scaling factor for `size`: `false` ⇒
188    /// units of 128 bytes (required for audio stream_ids), `true` ⇒
189    /// units of 1024 bytes (required for video stream_ids).
190    pub scale: bool,
191    /// 13-bit `P-STD_buffer_size`, in units selected by `scale`.
192    pub size: u16,
193}
194
195impl PStdBuffer {
196    /// Buffer size in bytes: `size * 128` when `scale` is clear,
197    /// `size * 1024` when set (§2.4.3.7).
198    pub fn size_bytes(&self) -> u32 {
199        u32::from(self.size) * if self.scale { 1024 } else { 128 }
200    }
201}
202
203impl PesPacket {
204    /// Parse a complete, contiguous PES packet (header + body).
205    ///
206    /// `bytes` runs from the `packet_start_code_prefix` through the
207    /// end of the PES payload.
208    pub fn parse(bytes: &[u8]) -> Result<Self, TsError> {
209        if bytes.len() < 6 {
210            return Err(TsError::Truncated {
211                what: "PES header",
212                have: bytes.len(),
213                need: 6,
214            });
215        }
216        if bytes[0] != 0x00 || bytes[1] != 0x00 || bytes[2] != 0x01 {
217            return Err(TsError::BadPesStartCode([bytes[0], bytes[1], bytes[2]]));
218        }
219        let stream_id = bytes[3];
220        let _pes_packet_length = u16::from_be_bytes([bytes[4], bytes[5]]);
221
222        if !has_optional_pes_header(stream_id) {
223            return Ok(Self {
224                stream_id,
225                pes_scrambling_control: 0,
226                pes_priority: false,
227                data_alignment_indicator: false,
228                copyright: false,
229                original_or_copy: false,
230                pts_90k: None,
231                dts_90k: None,
232                escr_27mhz: None,
233                es_rate_50bps: None,
234                dsm_trick_mode: None,
235                additional_copy_info: None,
236                previous_pes_packet_crc: None,
237                pes_extension: None,
238                payload: bytes[6..].to_vec(),
239            });
240        }
241        if bytes.len() < 9 {
242            return Err(TsError::Truncated {
243                what: "PES optional header",
244                have: bytes.len(),
245                need: 9,
246            });
247        }
248        // bytes[6]: '10' marker | scrambling(2) | priority | data_alignment |
249        //          copyright | original_or_copy.
250        // bytes[7]: PTS_DTS_flags(2) | ESCR | ES_rate | DSM_trick_mode |
251        //          additional_copy_info | PES_CRC | PES_extension.
252        // bytes[8]: PES_header_data_length.
253        let flags1 = bytes[6];
254        let pes_scrambling_control = (flags1 >> 4) & 0b11;
255        let pes_priority = (flags1 & 0b0000_1000) != 0;
256        let data_alignment_indicator = (flags1 & 0b0000_0100) != 0;
257        let copyright = (flags1 & 0b0000_0010) != 0;
258        let original_or_copy = (flags1 & 0b0000_0001) != 0;
259
260        let flags2 = bytes[7];
261        let pts_dts_flags = (flags2 >> 6) & 0b11;
262        let escr_flag = (flags2 & 0b0010_0000) != 0;
263        let es_rate_flag = (flags2 & 0b0001_0000) != 0;
264        let dsm_trick_mode_flag = (flags2 & 0b0000_1000) != 0;
265        let additional_copy_info_flag = (flags2 & 0b0000_0100) != 0;
266        let pes_crc_flag = (flags2 & 0b0000_0010) != 0;
267        let pes_extension_flag = (flags2 & 0b0000_0001) != 0;
268
269        let pes_header_data_length = bytes[8] as usize;
270        let header_end = 9 + pes_header_data_length;
271        if bytes.len() < header_end {
272            return Err(TsError::Truncated {
273                what: "PES optional header body",
274                have: bytes.len(),
275                need: header_end,
276            });
277        }
278        let mut pts_90k = None;
279        let mut dts_90k = None;
280        let mut escr_27mhz = None;
281        let mut es_rate_50bps = None;
282        let mut dsm_trick_mode = None;
283        let mut additional_copy_info = None;
284        let mut previous_pes_packet_crc = None;
285        let opt = &bytes[9..header_end];
286        let mut cursor = 0usize;
287        match pts_dts_flags {
288            0b10 => {
289                // PTS only.
290                if opt.len() < cursor + 5 {
291                    return Err(TsError::Truncated {
292                        what: "PES PTS",
293                        have: opt.len(),
294                        need: cursor + 5,
295                    });
296                }
297                pts_90k = Some(decode_timestamp(&opt[cursor..cursor + 5])?);
298                cursor += 5;
299            }
300            0b11 => {
301                if opt.len() < cursor + 10 {
302                    return Err(TsError::Truncated {
303                        what: "PES PTS+DTS",
304                        have: opt.len(),
305                        need: cursor + 10,
306                    });
307                }
308                pts_90k = Some(decode_timestamp(&opt[cursor..cursor + 5])?);
309                dts_90k = Some(decode_timestamp(&opt[cursor + 5..cursor + 10])?);
310                cursor += 10;
311            }
312            0b00 => { /* no PTS/DTS */ }
313            // 0b01 is forbidden by spec.
314            _ => return Err(TsError::Unsupported("PES PTS_DTS_flags = 0b01")),
315        }
316        if escr_flag {
317            if opt.len() < cursor + 6 {
318                return Err(TsError::Truncated {
319                    what: "PES ESCR",
320                    have: opt.len(),
321                    need: cursor + 6,
322                });
323            }
324            escr_27mhz = Some(decode_escr(&opt[cursor..cursor + 6])?);
325            cursor += 6;
326        }
327        if es_rate_flag {
328            if opt.len() < cursor + 3 {
329                return Err(TsError::Truncated {
330                    what: "PES ES_rate",
331                    have: opt.len(),
332                    need: cursor + 3,
333                });
334            }
335            // marker_bit | ES_rate(22) | marker_bit. Bits 21..15 in
336            // opt[cursor] (lower 7), bits 14..7 in opt[cursor+1],
337            // bits 6..0 in upper 7 of opt[cursor+2].
338            let b0 = opt[cursor] as u32;
339            let b1 = opt[cursor + 1] as u32;
340            let b2 = opt[cursor + 2] as u32;
341            let es_rate = ((b0 & 0x7F) << 15) | (b1 << 7) | ((b2 >> 1) & 0x7F);
342            es_rate_50bps = Some(es_rate);
343            cursor += 3;
344        }
345        if dsm_trick_mode_flag {
346            if opt.len() < cursor + 1 {
347                return Err(TsError::Truncated {
348                    what: "PES DSM_trick_mode",
349                    have: opt.len(),
350                    need: cursor + 1,
351                });
352            }
353            dsm_trick_mode = Some(opt[cursor]);
354            cursor += 1;
355        }
356        if additional_copy_info_flag {
357            if opt.len() < cursor + 1 {
358                return Err(TsError::Truncated {
359                    what: "PES additional_copy_info",
360                    have: opt.len(),
361                    need: cursor + 1,
362                });
363            }
364            // marker_bit | additional_copy_info(7).
365            additional_copy_info = Some(opt[cursor] & 0x7F);
366            cursor += 1;
367        }
368        if pes_crc_flag {
369            if opt.len() < cursor + 2 {
370                return Err(TsError::Truncated {
371                    what: "PES previous_PES_packet_CRC",
372                    have: opt.len(),
373                    need: cursor + 2,
374                });
375            }
376            previous_pes_packet_crc = Some(u16::from_be_bytes([opt[cursor], opt[cursor + 1]]));
377            cursor += 2;
378        }
379        // PES_extension body (Table 2-17, concluded). Bounded by
380        // `pes_header_data_length`; anything after it up to
381        // `header_end` is stuffing.
382        let pes_extension = if pes_extension_flag {
383            let (ext, used) = PesExtension::parse(&opt[cursor..])?;
384            cursor += used;
385            Some(ext)
386        } else {
387            None
388        };
389        let _ = cursor;
390
391        Ok(Self {
392            stream_id,
393            pes_scrambling_control,
394            pes_priority,
395            data_alignment_indicator,
396            copyright,
397            original_or_copy,
398            pts_90k,
399            dts_90k,
400            escr_27mhz,
401            es_rate_50bps,
402            dsm_trick_mode,
403            additional_copy_info,
404            previous_pes_packet_crc,
405            pes_extension,
406            payload: bytes[header_end..].to_vec(),
407        })
408    }
409}
410
411impl PesExtension {
412    /// Parse a `PES_extension` body from the head of `b` (the bytes
413    /// remaining in the optional-header area once the earlier
414    /// flag-gated fields have been consumed). Returns the decoded
415    /// extension and the number of bytes it occupied.
416    fn parse(b: &[u8]) -> Result<(Self, usize), TsError> {
417        if b.is_empty() {
418            return Err(TsError::Truncated {
419                what: "PES extension flags",
420                have: 0,
421                need: 1,
422            });
423        }
424        // PES_private_data_flag (1) | pack_header_field_flag (1) |
425        // program_packet_sequence_counter_flag (1) | P-STD_buffer_flag (1) |
426        // reserved (3) | PES_extension_flag_2 (1).
427        let flags = b[0];
428        let private_data_flag = (flags & 0b1000_0000) != 0;
429        let pack_header_field_flag = (flags & 0b0100_0000) != 0;
430        let ppsc_flag = (flags & 0b0010_0000) != 0;
431        let p_std_buffer_flag = (flags & 0b0001_0000) != 0;
432        let extension_flag_2 = (flags & 0b0000_0001) != 0;
433        let mut cursor = 1usize;
434
435        let mut ext = Self::default();
436        if private_data_flag {
437            if b.len() < cursor + 16 {
438                return Err(TsError::Truncated {
439                    what: "PES_private_data",
440                    have: b.len(),
441                    need: cursor + 16,
442                });
443            }
444            let mut pd = [0u8; 16];
445            pd.copy_from_slice(&b[cursor..cursor + 16]);
446            ext.private_data = Some(pd);
447            cursor += 16;
448        }
449        if pack_header_field_flag {
450            if b.len() < cursor + 1 {
451                return Err(TsError::Truncated {
452                    what: "pack_field_length",
453                    have: b.len(),
454                    need: cursor + 1,
455                });
456            }
457            let pack_field_length = b[cursor] as usize;
458            cursor += 1;
459            if b.len() < cursor + pack_field_length {
460                return Err(TsError::Truncated {
461                    what: "pack_header",
462                    have: b.len(),
463                    need: cursor + pack_field_length,
464                });
465            }
466            ext.pack_header = Some(b[cursor..cursor + pack_field_length].to_vec());
467            cursor += pack_field_length;
468        }
469        if ppsc_flag {
470            if b.len() < cursor + 2 {
471                return Err(TsError::Truncated {
472                    what: "program_packet_sequence_counter",
473                    have: b.len(),
474                    need: cursor + 2,
475                });
476            }
477            // marker (1) | program_packet_sequence_counter (7),
478            // marker (1) | MPEG1_MPEG2_identifier (1) |
479            // original_stuff_length (6).
480            ext.program_packet_sequence_counter = Some(ProgramPacketSequenceCounter {
481                counter: b[cursor] & 0x7F,
482                mpeg1_mpeg2_identifier: (b[cursor + 1] & 0b0100_0000) != 0,
483                original_stuff_length: b[cursor + 1] & 0x3F,
484            });
485            cursor += 2;
486        }
487        if p_std_buffer_flag {
488            if b.len() < cursor + 2 {
489                return Err(TsError::Truncated {
490                    what: "P-STD_buffer",
491                    have: b.len(),
492                    need: cursor + 2,
493                });
494            }
495            // '01' (2) | P-STD_buffer_scale (1) | P-STD_buffer_size (13).
496            ext.p_std_buffer = Some(PStdBuffer {
497                scale: (b[cursor] & 0b0010_0000) != 0,
498                size: (u16::from(b[cursor] & 0x1F) << 8) | u16::from(b[cursor + 1]),
499            });
500            cursor += 2;
501        }
502        if extension_flag_2 {
503            if b.len() < cursor + 1 {
504                return Err(TsError::Truncated {
505                    what: "PES_extension_field_length",
506                    have: b.len(),
507                    need: cursor + 1,
508                });
509            }
510            // marker (1) | PES_extension_field_length (7), then that
511            // many reserved bytes (surfaced verbatim).
512            let len = (b[cursor] & 0x7F) as usize;
513            cursor += 1;
514            if b.len() < cursor + len {
515                return Err(TsError::Truncated {
516                    what: "PES_extension_field",
517                    have: b.len(),
518                    need: cursor + len,
519                });
520            }
521            ext.extension_field_2 = Some(b[cursor..cursor + len].to_vec());
522            cursor += len;
523        }
524        Ok((ext, cursor))
525    }
526}
527
528/// Decode a 6-byte ESCR field (Table 2-17) into a 27 MHz tick count.
529///
530/// Layout (48 bits, Table 2-17 / equations 2-13..2-15):
531///
532/// ```text
533/// reserved(2) | ESCR_base[32..30](3)  | marker(1) |
534/// ESCR_base[29..15](15) | marker(1)   |
535/// ESCR_base[14..0](15)  | marker(1)   |
536/// ESCR_extension(9)     | marker(1)
537/// ```
538///
539/// Result: `ESCR_base * 300 + ESCR_extension` per equation 2-13 (a
540/// 42-bit value held in a u64 — top 22 bits always zero).
541fn decode_escr(b: &[u8]) -> Result<u64, TsError> {
542    if b.len() < 6 {
543        return Err(TsError::Truncated {
544            what: "ESCR",
545            have: b.len(),
546            need: 6,
547        });
548    }
549    // Bit packing across 6 bytes (MSB-first per spec bslbf/uimsbf):
550    //
551    //   b[0]: r r B B B M b b      where B B B = base[32..30],
552    //                              M = marker, b b = base[29..28]
553    //   b[1]: b b b b b b b b      = base[27..20]
554    //   b[2]: b b b b b M b b      base[19..15] (top 5 of the byte),
555    //                              marker, base[14..13]
556    //   b[3]: b b b b b b b b      = base[12..5]
557    //   b[4]: b b b b b M e e      base[4..0], marker, ext[8..7]
558    //   b[5]: e e e e e e e M      ext[6..0], marker
559    let base_32_30 = ((b[0] >> 3) & 0b0000_0111) as u64;
560    let base_29_15 = (((b[0] as u64) & 0b0000_0011) << 13)
561        | ((b[1] as u64) << 5)
562        | (((b[2] as u64) >> 3) & 0b0001_1111);
563    let base_14_0 = (((b[2] as u64) & 0b0000_0011) << 13)
564        | ((b[3] as u64) << 5)
565        | (((b[4] as u64) >> 3) & 0b0001_1111);
566    let escr_ext = (((b[4] as u64) & 0b0000_0011) << 7) | (((b[5] as u64) >> 1) & 0x7F);
567
568    let base = (base_32_30 << 30) | (base_29_15 << 15) | base_14_0;
569    Ok(base * 300 + escr_ext)
570}
571
572/// Decode a 5-byte PTS or DTS field (Table 2-22).
573fn decode_timestamp(b: &[u8]) -> Result<u64, TsError> {
574    if b.len() < 5 {
575        return Err(TsError::Truncated {
576            what: "PTS/DTS",
577            have: b.len(),
578            need: 5,
579        });
580    }
581    // Top nibble of b[0] is the 4-bit marker ('0010' for PTS-only,
582    // '0011' for PTS-of-PTS+DTS, '0001' for DTS). We don't validate
583    // it — just extract the timestamp.
584    let t32_30 = ((b[0] >> 1) & 0b0000_0111) as u64;
585    let t29_22 = b[1] as u64;
586    let t21_15 = ((b[2] >> 1) & 0b0111_1111) as u64;
587    let t14_7 = b[3] as u64;
588    let t6_0 = ((b[4] >> 1) & 0b0111_1111) as u64;
589    let ts = (t32_30 << 30) | (t29_22 << 22) | (t21_15 << 15) | (t14_7 << 7) | t6_0;
590    Ok(ts)
591}
592
593/// Per-PID PES reassembler.
594///
595/// Tracks one in-flight PES packet's accumulated payload bytes; emits
596/// a parsed [`PesPacket`] when the next PUSI=1 TS packet arrives or
597/// the caller flushes.
598#[derive(Debug, Default)]
599pub struct PesReassembler {
600    /// Accumulated PES packet bytes (starts at packet_start_code_prefix).
601    buf: Vec<u8>,
602    /// Set once a PUSI=1 TS packet has populated `buf`.
603    started: bool,
604}
605
606impl PesReassembler {
607    /// Create an empty reassembler.
608    pub fn new() -> Self {
609        Self::default()
610    }
611
612    /// Feed the next TS packet (must have the same PID as the prior
613    /// feeds). Returns `Some(PesPacket)` when a complete PES packet
614    /// has been finalised by this packet's PUSI=1.
615    pub fn feed(&mut self, ts: &TsPacket<'_>) -> Result<Option<PesPacket>, TsError> {
616        if ts.payload_unit_start {
617            // The arriving PES packet's PUSI=1 closes the prior PES
618            // packet (if any).
619            let finished = if self.started {
620                Some(PesPacket::parse(&self.buf)?)
621            } else {
622                None
623            };
624            self.buf.clear();
625            self.buf.extend_from_slice(ts.payload);
626            self.started = true;
627            Ok(finished)
628        } else if self.started {
629            self.buf.extend_from_slice(ts.payload);
630            Ok(None)
631        } else {
632            // Continuation bytes before we've seen the first PUSI=1 —
633            // discard, per spec we can't anchor the packet yet.
634            Ok(None)
635        }
636    }
637
638    /// Drain the buffered PES packet (call at end-of-stream).
639    pub fn flush(&mut self) -> Result<Option<PesPacket>, TsError> {
640        if !self.started {
641            return Ok(None);
642        }
643        let buf = std::mem::take(&mut self.buf);
644        self.started = false;
645        Ok(Some(PesPacket::parse(&buf)?))
646    }
647}
648
649#[cfg(test)]
650mod tests {
651    use super::*;
652    use crate::packet::{TS_PACKET_LEN, TS_SYNC_BYTE};
653
654    /// Encode a PTS/DTS into 5 bytes per Table 2-22, with the given
655    /// 4-bit prefix nibble.
656    fn encode_timestamp(prefix: u8, ts: u64) -> [u8; 5] {
657        // 33-bit timestamp.
658        let t = ts & 0x1_FFFF_FFFF;
659        let t32_30 = ((t >> 30) & 0b0111) as u8;
660        let t29_15 = ((t >> 15) & 0x7FFF) as u16;
661        let t14_0 = (t & 0x7FFF) as u16;
662        [
663            (prefix << 4) | (t32_30 << 1) | 0b1,
664            ((t29_15 >> 7) & 0xFF) as u8,
665            (((t29_15 & 0x7F) << 1) as u8) | 0b1,
666            ((t14_0 >> 7) & 0xFF) as u8,
667            (((t14_0 & 0x7F) << 1) as u8) | 0b1,
668        ]
669    }
670
671    /// Build a video PES packet (stream_id 0xE0) with a PTS and the
672    /// given payload bytes.
673    fn build_pes(stream_id: u8, pts: u64, payload: &[u8]) -> Vec<u8> {
674        let pts_bytes = encode_timestamp(0b0010, pts);
675        let mut v = Vec::new();
676        v.extend_from_slice(&[0x00, 0x00, 0x01, stream_id]);
677        // PES_packet_length covers bytes from byte 6 onward.
678        let pes_packet_length: u16 = (3 + 5 + payload.len()) as u16;
679        v.extend_from_slice(&pes_packet_length.to_be_bytes());
680        // byte 6: '10' marker | scrambling=0 | priority=0 | data_align=0 |
681        //         copyright=0 | original_or_copy=0  ⇒ 0b1000_0000.
682        v.push(0b1000_0000);
683        // byte 7: PTS_DTS_flags=0b10 (PTS only), rest 0  ⇒ 0b1000_0000.
684        v.push(0b1000_0000);
685        // byte 8: PES_header_data_length = 5 (just PTS).
686        v.push(5);
687        v.extend_from_slice(&pts_bytes);
688        v.extend_from_slice(payload);
689        v
690    }
691
692    /// Wrap PES bytes into TS packets, splitting at `chunk_len` bytes
693    /// from the PES start, with the given PID. The first packet has
694    /// PUSI=1; the rest PUSI=0. Every packet is 188 bytes. When the
695    /// PES bytes don't fill a packet's payload area (the typical case
696    /// for the final packet of a PES packet on a real broadcast), an
697    /// adaptation field is inserted before the payload to absorb the
698    /// shortfall — matching ISO/IEC 13818-1 §2.4.3.4 stuffing
699    /// behaviour.
700    fn pes_into_ts(pid: u16, pes: &[u8], chunk_len: usize) -> Vec<u8> {
701        assert!(chunk_len > 0 && chunk_len <= 184);
702        let mut out = Vec::new();
703        let mut cursor = 0;
704        let mut first = true;
705        let mut cc: u8 = 0;
706        while cursor < pes.len() {
707            let pusi = if first { 0b0100_0000 } else { 0 };
708            let pid_hi = ((pid >> 8) & 0x1F) as u8;
709            let pid_lo = (pid & 0xFF) as u8;
710            let remaining = pes.len() - cursor;
711            let take = remaining.min(chunk_len).min(184);
712            // If the PES bytes don't fill 184 bytes, insert an AF of
713            // the required size before them. AF length byte counts
714            // ONLY the bytes after it: so for a stuffing AF of N
715            // total bytes, length byte = N-1, then N-1 0xFF stuffing
716            // bytes.
717            let af_total = 184 - take;
718            let af_control: u8 = if af_total > 0 { 0b11 } else { 0b01 };
719            let b3 = (af_control << 4) | (cc & 0x0F);
720            let mut pkt = vec![TS_SYNC_BYTE, pusi | pid_hi, pid_lo, b3];
721            if af_total > 0 {
722                // length byte counts bytes after itself.
723                pkt.push((af_total - 1) as u8);
724                // Per spec, an AF with only stuffing has length>=1
725                // (length byte + 0+ stuffing) when there is no flags
726                // byte room; but the standard requires a flags byte
727                // when length>0. We allocate the flags byte and use
728                // the rest as 0xFF stuffing.
729                if af_total >= 2 {
730                    pkt.push(0); // flags = none
731                    pkt.extend(std::iter::repeat(0xFF).take(af_total - 2));
732                }
733                // af_total == 1 ⇒ only the length byte (length=0,
734                // see above), already emitted.
735            }
736            pkt.extend_from_slice(&pes[cursor..cursor + take]);
737            cursor += take;
738            assert_eq!(pkt.len(), TS_PACKET_LEN);
739            out.extend_from_slice(&pkt);
740            cc = (cc + 1) & 0x0F;
741            first = false;
742        }
743        out
744    }
745
746    #[test]
747    fn decode_timestamp_round_trip() {
748        for ts in [0u64, 1, 90_000, 0x1_FFFF_FFFF] {
749            let enc = encode_timestamp(0b0010, ts);
750            let dec = decode_timestamp(&enc).unwrap();
751            assert_eq!(dec, ts);
752        }
753    }
754
755    #[test]
756    fn parse_complete_pes_packet_pts_only() {
757        let pes = build_pes(0xE0, 90_000, b"hello world");
758        let parsed = PesPacket::parse(&pes).unwrap();
759        assert_eq!(parsed.stream_id, 0xE0);
760        assert_eq!(parsed.pts_90k, Some(90_000));
761        assert_eq!(parsed.dts_90k, None);
762        assert_eq!(parsed.payload, b"hello world");
763    }
764
765    #[test]
766    fn parse_complete_pes_packet_pts_dts() {
767        let pts_bytes = encode_timestamp(0b0011, 200_000);
768        let dts_bytes = encode_timestamp(0b0001, 180_000);
769        let payload = b"AVCdata";
770        let mut v = Vec::new();
771        v.extend_from_slice(&[0x00, 0x00, 0x01, 0xE0]);
772        let pes_packet_length: u16 = (3 + 10 + payload.len()) as u16;
773        v.extend_from_slice(&pes_packet_length.to_be_bytes());
774        v.push(0b1000_0000);
775        v.push(0b1100_0000); // PTS_DTS_flags = 0b11
776        v.push(10);
777        v.extend_from_slice(&pts_bytes);
778        v.extend_from_slice(&dts_bytes);
779        v.extend_from_slice(payload);
780        let parsed = PesPacket::parse(&v).unwrap();
781        assert_eq!(parsed.pts_90k, Some(200_000));
782        assert_eq!(parsed.dts_90k, Some(180_000));
783        assert_eq!(parsed.payload, payload);
784    }
785
786    #[test]
787    fn reassemble_pes_split_across_three_ts_packets() {
788        let payload: Vec<u8> = (0..400u32).map(|i| (i & 0xFF) as u8).collect();
789        let pes = build_pes(0xE0, 12345, &payload);
790        // chunk_len = 150 ⇒ ~3 TS packets for a ~410-byte PES.
791        let ts_buf = pes_into_ts(0x100, &pes, 150);
792        // Append a second PUSI=1 packet (with a tiny "next" PES) so
793        // feed() emits the previous packet.
794        let next_pes = build_pes(0xE0, 67890, b"X");
795        let next_ts = pes_into_ts(0x100, &next_pes, 184);
796        let mut full = ts_buf;
797        full.extend_from_slice(&next_ts);
798
799        let mut r = PesReassembler::new();
800        let mut packets = Vec::new();
801        for pkt in crate::iter_packets(&full) {
802            let pkt = pkt.unwrap();
803            if let Some(done) = r.feed(&pkt).unwrap() {
804                packets.push(done);
805            }
806        }
807        if let Some(done) = r.flush().unwrap() {
808            packets.push(done);
809        }
810
811        assert_eq!(packets.len(), 2);
812        assert_eq!(packets[0].pts_90k, Some(12345));
813        // Payload should match exactly — stuffing is part of the TS
814        // packet, not the PES packet. But because we pad TS packets
815        // with 0xFF *after* the PES bytes, the last TS packet's
816        // payload area carries some trailing stuffing that gets
817        // appended to `buf`. We assert the PES prefix matches and
818        // the parser still pulls out the right header + initial
819        // payload bytes.
820        assert_eq!(&packets[0].payload[..payload.len()], &payload[..]);
821        assert_eq!(packets[1].pts_90k, Some(67890));
822    }
823
824    #[test]
825    fn pusi_starts_new_packet_and_emits_previous() {
826        let pes1 = build_pes(0xC0, 1000, b"first");
827        let pes2 = build_pes(0xC0, 2000, b"second");
828        let ts1 = pes_into_ts(0x101, &pes1, 184);
829        let ts2 = pes_into_ts(0x101, &pes2, 184);
830
831        let mut r = PesReassembler::new();
832        let mut emitted = Vec::new();
833        for buf in [&ts1, &ts2] {
834            for pkt in crate::iter_packets(buf) {
835                let pkt = pkt.unwrap();
836                if let Some(done) = r.feed(&pkt).unwrap() {
837                    emitted.push(done);
838                }
839            }
840        }
841        // Only `pes1` should have been emitted (closed by `pes2`'s
842        // PUSI=1). `pes2` is still buffered.
843        assert_eq!(emitted.len(), 1);
844        assert_eq!(emitted[0].pts_90k, Some(1000));
845        assert_eq!(&emitted[0].payload[..5], b"first");
846        let flushed = r.flush().unwrap().expect("buffered pes2");
847        assert_eq!(flushed.pts_90k, Some(2000));
848        assert_eq!(&flushed.payload[..6], b"second");
849    }
850
851    #[test]
852    fn padding_stream_has_no_optional_header() {
853        // stream_id = 0xBE (padding_stream) — no optional PES header.
854        let mut v = Vec::new();
855        v.extend_from_slice(&[0x00, 0x00, 0x01, 0xBE]);
856        let payload = [0xFFu8; 12];
857        let len: u16 = payload.len() as u16;
858        v.extend_from_slice(&len.to_be_bytes());
859        v.extend_from_slice(&payload);
860        let p = PesPacket::parse(&v).unwrap();
861        assert_eq!(p.stream_id, 0xBE);
862        assert_eq!(p.pts_90k, None);
863        assert_eq!(p.dts_90k, None);
864        assert_eq!(p.payload, &payload);
865    }
866
867    #[test]
868    fn bad_pes_start_code_rejected() {
869        let mut v = vec![0x00, 0x00, 0x02, 0xE0, 0, 0];
870        v.extend_from_slice(&[0u8; 3]);
871        let err = PesPacket::parse(&v).unwrap_err();
872        match err {
873            TsError::BadPesStartCode(_) => {}
874            other => panic!("expected BadPesStartCode, got {other:?}"),
875        }
876    }
877
878    /// Encode a 42-bit ESCR into the 6-byte spec layout (Table 2-17).
879    fn encode_escr(escr_42: u64) -> [u8; 6] {
880        let base = (escr_42 / 300) & 0x1_FFFF_FFFF;
881        let ext = (escr_42 % 300) & 0x1FF;
882        let base_32_30 = ((base >> 30) & 0b111) as u8;
883        let base_29_15 = ((base >> 15) & 0x7FFF) as u32;
884        let base_14_0 = (base & 0x7FFF) as u32;
885        let ext = ext as u32;
886        let b0 = 0b1100_0000 // reserved bits set to 1, matches typical encoder
887            | (base_32_30 << 3)
888            | 0b0000_0100 // marker
889            | (((base_29_15 >> 13) & 0b11) as u8);
890        let b1 = ((base_29_15 >> 5) & 0xFF) as u8;
891        let b2 = (((base_29_15 & 0x1F) as u8) << 3)
892            | 0b0000_0100 // marker
893            | (((base_14_0 >> 13) & 0b11) as u8);
894        let b3 = ((base_14_0 >> 5) & 0xFF) as u8;
895        let b4 = (((base_14_0 & 0x1F) as u8) << 3)
896            | 0b0000_0100 // marker
897            | (((ext >> 7) & 0b11) as u8);
898        let b5 = (((ext & 0x7F) as u8) << 1) | 0b0000_0001; // marker
899        [b0, b1, b2, b3, b4, b5]
900    }
901
902    /// Encode a 22-bit ES_rate into the 3-byte spec layout.
903    fn encode_es_rate(rate: u32) -> [u8; 3] {
904        let r = rate & 0x3F_FFFF;
905        [
906            0b1000_0000 | ((r >> 15) as u8 & 0x7F),
907            ((r >> 7) & 0xFF) as u8,
908            (((r & 0x7F) << 1) as u8) | 0x01,
909        ]
910    }
911
912    #[test]
913    fn escr_round_trip_round_numbers() {
914        // Spec ESCR range: ESCR_base is 33-bit and ESCR_extension is
915        // in [0, 299], so the addressable 27 MHz tick range is
916        // [0, (2^33 - 1) * 300 + 299].
917        let max_escr: u64 = (((1u64 << 33) - 1) * 300) + 299;
918        for &target in &[0u64, 1, 299, 300, 27_000_000, 27_000_001, max_escr] {
919            let enc = encode_escr(target);
920            let dec = decode_escr(&enc).unwrap();
921            assert_eq!(dec, target, "target {target:#x} encoded {enc:02X?}");
922        }
923    }
924
925    #[test]
926    fn parse_pes_with_every_optional_field() {
927        // Build a PES header carrying all flag-gated optional fields
928        // (PTS+DTS, ESCR, ES_rate, DSM_trick_mode, additional_copy_info,
929        // PES_CRC, PES_extension marker).
930        let pts_bytes = encode_timestamp(0b0011, 300_000);
931        let dts_bytes = encode_timestamp(0b0001, 240_000);
932        let escr_bytes = encode_escr(27_000_123);
933        let es_rate_bytes = encode_es_rate(123_456);
934        let dsm_byte: u8 = 0b010_00000; // trick_mode_control = freeze_frame
935        let aci_byte: u8 = 0x80 | 0x42; // marker_bit=1 | aci=0x42
936        let pes_crc_bytes: [u8; 2] = [0xCA, 0xFE];
937        // PES_extension flag set with a minimal extension byte
938        // (all sub-flags zero, no body) so PES_header_data_length
939        // accounts for it.
940        let pes_ext_flags: u8 = 0b0000_0000;
941        let optional: Vec<u8> = [
942            pts_bytes.as_slice(),
943            dts_bytes.as_slice(),
944            escr_bytes.as_slice(),
945            es_rate_bytes.as_slice(),
946            std::slice::from_ref(&dsm_byte),
947            std::slice::from_ref(&aci_byte),
948            pes_crc_bytes.as_slice(),
949            std::slice::from_ref(&pes_ext_flags),
950        ]
951        .concat();
952        let payload = b"\x01\x02\x03\x04";
953        let mut v = Vec::new();
954        v.extend_from_slice(&[0x00, 0x00, 0x01, 0xE0]);
955        let pes_packet_length: u16 = (3 + optional.len() + payload.len()) as u16;
956        v.extend_from_slice(&pes_packet_length.to_be_bytes());
957        // flags1: '10' marker | scrambling=00 | priority=1 |
958        //         data_alignment=1 | copyright=1 | original_or_copy=1
959        v.push(0b1000_1111);
960        // flags2: PTS_DTS=11 | ESCR=1 | ES_rate=1 | DSM=1 | ACI=1 |
961        //         PES_CRC=1 | PES_extension=1 = 0b11_11_11_11
962        v.push(0b1111_1111);
963        v.push(optional.len() as u8);
964        v.extend_from_slice(&optional);
965        v.extend_from_slice(payload);
966        let p = PesPacket::parse(&v).unwrap();
967        assert_eq!(p.stream_id, 0xE0);
968        assert_eq!(p.pes_scrambling_control, 0);
969        assert!(p.pes_priority);
970        assert!(p.data_alignment_indicator);
971        assert!(p.copyright);
972        assert!(p.original_or_copy);
973        assert_eq!(p.pts_90k, Some(300_000));
974        assert_eq!(p.dts_90k, Some(240_000));
975        assert_eq!(p.escr_27mhz, Some(27_000_123));
976        assert_eq!(p.es_rate_50bps, Some(123_456));
977        assert_eq!(p.dsm_trick_mode, Some(0b010_00000));
978        assert_eq!(p.additional_copy_info, Some(0x42));
979        assert_eq!(p.previous_pes_packet_crc, Some(0xCAFE));
980        // Extension flag set with an all-zero sub-flag byte ⇒ present
981        // but every sub-field absent.
982        assert_eq!(p.pes_extension, Some(PesExtension::default()));
983        assert_eq!(p.payload, payload);
984    }
985
986    #[test]
987    fn parse_pes_no_optional_fields_defaults() {
988        // PTS-only PES, no ESCR / ES_rate / DSM / ACI / CRC / ext.
989        let pes = build_pes(0xE0, 90_000, b"abcd");
990        let p = PesPacket::parse(&pes).unwrap();
991        assert_eq!(p.pts_90k, Some(90_000));
992        assert_eq!(p.escr_27mhz, None);
993        assert_eq!(p.es_rate_50bps, None);
994        assert_eq!(p.dsm_trick_mode, None);
995        assert_eq!(p.additional_copy_info, None);
996        assert_eq!(p.previous_pes_packet_crc, None);
997        assert!(p.pes_extension.is_none());
998        // Flag bits in the all-zero flags1 byte we set.
999        assert!(!p.pes_priority);
1000        assert!(!p.copyright);
1001        assert_eq!(p.pes_scrambling_control, 0);
1002    }
1003
1004    /// Wrap a raw PES_extension body (sub-flag byte + gated fields)
1005    /// into a minimal video PES packet whose only optional field is
1006    /// the extension.
1007    fn build_pes_with_extension(ext_body: &[u8]) -> Vec<u8> {
1008        let payload = b"data";
1009        let mut v = Vec::new();
1010        v.extend_from_slice(&[0x00, 0x00, 0x01, 0xE0]);
1011        let pes_packet_length: u16 = (3 + ext_body.len() + payload.len()) as u16;
1012        v.extend_from_slice(&pes_packet_length.to_be_bytes());
1013        v.push(0b1000_0000); // '10' marker, all flags1 clear
1014        v.push(0b0000_0001); // only PES_extension_flag set
1015        v.push(ext_body.len() as u8);
1016        v.extend_from_slice(ext_body);
1017        v.extend_from_slice(payload);
1018        v
1019    }
1020
1021    #[test]
1022    fn parse_pes_extension_every_sub_field() {
1023        // Sub-flags: private_data | pack_header | ppsc | P-STD | ext2
1024        // (reserved bits set to 1 to prove they're ignored).
1025        let mut ext = vec![0b1111_1111u8];
1026        let private: [u8; 16] = *b"0123456789ABCDEF";
1027        ext.extend_from_slice(&private);
1028        // pack_field_length = 3, then 3 opaque pack_header bytes.
1029        ext.extend_from_slice(&[3, 0xAA, 0xBB, 0xCC]);
1030        // marker|counter=0x55, marker|MPEG1_MPEG2=1|orig_stuff_len=0x21.
1031        ext.push(0b1101_0101);
1032        ext.push(0b1110_0001);
1033        // '01' | scale=1 | size=0x1234 (13-bit).
1034        ext.push(0b0111_0010);
1035        ext.push(0x34);
1036        // marker | PES_extension_field_length=2, then 2 reserved bytes.
1037        ext.push(0b1000_0010);
1038        ext.extend_from_slice(&[0xDE, 0xAD]);
1039
1040        let pes = build_pes_with_extension(&ext);
1041        let p = PesPacket::parse(&pes).unwrap();
1042        let e = p.pes_extension.expect("extension present");
1043        assert_eq!(e.private_data, Some(private));
1044        assert_eq!(e.pack_header.as_deref(), Some(&[0xAA, 0xBB, 0xCC][..]));
1045        let ppsc = e.program_packet_sequence_counter.unwrap();
1046        assert_eq!(ppsc.counter, 0x55);
1047        assert!(ppsc.mpeg1_mpeg2_identifier);
1048        assert_eq!(ppsc.original_stuff_length, 0x21);
1049        let pstd = e.p_std_buffer.unwrap();
1050        assert!(pstd.scale);
1051        assert_eq!(pstd.size, 0x1234);
1052        assert_eq!(pstd.size_bytes(), 0x1234 * 1024);
1053        assert_eq!(e.extension_field_2.as_deref(), Some(&[0xDE, 0xAD][..]));
1054        assert_eq!(p.payload, b"data");
1055    }
1056
1057    #[test]
1058    fn parse_pes_extension_p_std_scale_clear_units_128() {
1059        // Only the P-STD_buffer pair: '01' | scale=0 | size=10.
1060        let ext = [0b0001_0000u8, 0b0100_0000, 10];
1061        let pes = build_pes_with_extension(&ext);
1062        let p = PesPacket::parse(&pes).unwrap();
1063        let pstd = p.pes_extension.unwrap().p_std_buffer.unwrap();
1064        assert!(!pstd.scale);
1065        assert_eq!(pstd.size, 10);
1066        assert_eq!(pstd.size_bytes(), 1280);
1067    }
1068
1069    #[test]
1070    fn parse_pes_extension_truncated_private_data_rejected() {
1071        // private_data flag set but only 4 of the 16 bytes present.
1072        let ext = [0b1000_0000u8, 1, 2, 3, 4];
1073        let pes = build_pes_with_extension(&ext);
1074        let err = PesPacket::parse(&pes).unwrap_err();
1075        match err {
1076            TsError::Truncated { what, .. } => assert_eq!(what, "PES_private_data"),
1077            other => panic!("expected Truncated, got {other:?}"),
1078        }
1079    }
1080
1081    #[test]
1082    fn parse_pes_extension_truncated_field_2_rejected() {
1083        // ext2 flag set, PES_extension_field_length = 5 but no bytes.
1084        let ext = [0b0000_0001u8, 0b1000_0101];
1085        let pes = build_pes_with_extension(&ext);
1086        let err = PesPacket::parse(&pes).unwrap_err();
1087        match err {
1088            TsError::Truncated { what, .. } => assert_eq!(what, "PES_extension_field"),
1089            other => panic!("expected Truncated, got {other:?}"),
1090        }
1091    }
1092
1093    #[test]
1094    fn parse_pes_truncated_optional_body_rejected() {
1095        // PTS_DTS=11 (claims 10 bytes) but PES_header_data_length=5,
1096        // so the body can't hold both timestamps.
1097        let mut v = Vec::new();
1098        v.extend_from_slice(&[0x00, 0x00, 0x01, 0xE0]);
1099        // length covers from byte 6 onward: flags(3) + optional(5) = 8
1100        v.extend_from_slice(&8u16.to_be_bytes());
1101        v.push(0b1000_0000);
1102        v.push(0b1100_0000);
1103        v.push(5); // header_data_length too small for PTS+DTS
1104        v.extend_from_slice(&[0u8; 5]);
1105        let err = PesPacket::parse(&v).unwrap_err();
1106        match err {
1107            TsError::Truncated { what, .. } => {
1108                assert_eq!(what, "PES PTS+DTS");
1109            }
1110            other => panic!("expected Truncated, got {other:?}"),
1111        }
1112    }
1113}