Skip to main content

dvb_si/tables/
pmt.rs

1//! Program Map Table — MPEG-2 ISO/IEC 13818-1 §2.4.4.8.
2//!
3//! PMT describes the elementary streams that make up one programme.
4//! Carried on a per-programme PID signalled by the PAT, with table_id 0x02.
5//! Descriptor parsing is out of scope for this commit — raw bytes only.
6
7use crate::descriptors::DescriptorLoop;
8use crate::error::{Error, Result};
9use alloc::vec::Vec;
10use broadcast_common::{Parse, Serialize};
11
12/// PMT table_id (ISO/IEC 13818-1 Table 2-30).
13pub const TABLE_ID: u8 = 0x02;
14/// PMT PIDs are programme-specific and signalled via PAT; 0x0000 is a
15/// placeholder meaning "no well-known PID".
16pub const PID: u16 = 0x0000;
17
18const MIN_HEADER_LEN: usize = 3;
19const EXTENSION_HEADER_LEN: usize = 5;
20const PCR_PID_LEN: usize = 2;
21const PROG_INFO_LEN_BYTES: usize = 2;
22const CRC_LEN: usize = 4;
23const MIN_SECTION_LEN: usize =
24    MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES + CRC_LEN;
25const STREAM_HEADER_LEN: usize = 5;
26
27/// Stream type coding — Rec. ITU-T H.222.0 (06/2021) Table 2-34.
28///
29/// Identifies the elementary-stream type carried in the associated PID.
30/// Values `0x80`–`0xFF` are user private; only well-established entries
31/// cited to their own specs are named — the rest fall through to
32/// [`UserPrivate`](Self::UserPrivate).
33///
34/// # Examples
35/// ```
36/// use dvb_si::tables::pmt::StreamType;
37///
38/// assert_eq!(StreamType::from_u8(0x02).name(), "MPEG-2 Video");
39/// assert_eq!(StreamType::from_u8(0x1B).to_u8(), 0x1B); // H.264, lossless round-trip
40/// ```
41/// A parsed `PmtStream.stream_type` is already a `StreamType` — match on it directly.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize))]
44#[non_exhaustive]
45pub enum StreamType {
46    /// 0x00 — ITU-T | ISO/IEC Reserved.
47    Reserved,
48    /// 0x01 — ISO/IEC 11172-2 Video (MPEG-1 video).
49    Mpeg1Video,
50    /// 0x02 — Rec. ITU-T H.262 | ISO/IEC 13818-2 Video (MPEG-2 video).
51    Mpeg2Video,
52    /// 0x03 — ISO/IEC 11172-3 Audio (MPEG-1 audio).
53    Mpeg1Audio,
54    /// 0x04 — ISO/IEC 13818-3 Audio (MPEG-2 audio).
55    Mpeg2Audio,
56    /// 0x05 — Rec. ITU-T H.222.0 | ISO/IEC 13818-1 private_sections.
57    PrivateSections,
58    /// 0x06 — Rec. ITU-T H.222.0 | ISO/IEC 13818-1 PES packets containing private data.
59    PesPrivateData,
60    /// 0x07 — ISO/IEC 13522 MHEG.
61    Mheg,
62    /// 0x08 — Rec. ITU-T H.222.0 | ISO/IEC 13818-1 Annex A DSM-CC.
63    DsmCc,
64    /// 0x09 — Rec. ITU-T H.222.1.
65    H222_1,
66    /// 0x0A — ISO/IEC 13818-6 type A.
67    Iso13818_6TypeA,
68    /// 0x0B — ISO/IEC 13818-6 type B.
69    Iso13818_6TypeB,
70    /// 0x0C — ISO/IEC 13818-6 type C.
71    Iso13818_6TypeC,
72    /// 0x0D — ISO/IEC 13818-6 type D.
73    Iso13818_6TypeD,
74    /// 0x0E — Rec. ITU-T H.222.0 | ISO/IEC 13818-1 auxiliary.
75    Auxiliary,
76    /// 0x0F — ISO/IEC 13818-7 Audio with ADTS transport syntax (AAC).
77    AacAdts,
78    /// 0x10 — ISO/IEC 14496-2 Visual (MPEG-4 video).
79    Mpeg4Video,
80    /// 0x11 — ISO/IEC 14496-3 Audio with LATM transport syntax (AAC LATM).
81    AacLatm,
82    /// 0x12 — ISO/IEC 14496-1 SL-packetized / FlexMux stream in PES.
83    SlFlexMuxPes,
84    /// 0x13 — ISO/IEC 14496-1 SL-packetized / FlexMux stream in sections.
85    SlFlexMuxSections,
86    /// 0x14 — ISO/IEC 13818-6 Synchronized Download Protocol.
87    SyncDownload,
88    /// 0x15 — Metadata carried in PES packets.
89    MetadataPes,
90    /// 0x16 — Metadata carried in metadata_sections.
91    MetadataSections,
92    /// 0x17 — Metadata carried in ISO/IEC 13818-6 Data Carousel.
93    MetadataDataCarousel,
94    /// 0x18 — Metadata carried in ISO/IEC 13818-6 Object Carousel.
95    MetadataObjectCarousel,
96    /// 0x19 — Metadata carried in ISO/IEC 13818-6 Synchronized Download Protocol.
97    MetadataSyncDownload,
98    /// 0x1A — IPMP stream (ISO/IEC 13818-11, MPEG-2 IPMP).
99    Ipmp,
100    /// 0x1B — AVC video stream (Rec. ITU-T H.264 | ISO/IEC 14496-10).
101    H264,
102    /// 0x1C — ISO/IEC 14496-3 Audio without additional transport syntax (DST, ALS, SLS).
103    Iso14496_3Audio,
104    /// 0x1D — ISO/IEC 14496-17 Text.
105    Iso14496_17Text,
106    /// 0x1E — Auxiliary video stream (ISO/IEC 23002-3).
107    AuxiliaryVideo,
108    /// 0x1F — SVC video sub-bitstream of an AVC video stream (H.264 Annex G).
109    Svc,
110    /// 0x20 — MVC video sub-bitstream of an AVC video stream (H.264 Annex H).
111    Mvc,
112    /// 0x21 — JPEG 2000 video (Rec. ITU-T T.800 | ISO/IEC 15444-1).
113    Jpeg2000,
114    /// 0x22 — Additional view H.262 | ISO/IEC 13818-2 video for service-compatible stereoscopic 3D.
115    AdditionalViewH262,
116    /// 0x23 — Additional view H.264 | ISO/IEC 14496-10 video for service-compatible stereoscopic 3D.
117    AdditionalViewH264,
118    /// 0x24 — Rec. ITU-T H.265 | ISO/IEC 23008-2 video (HEVC) or HEVC temporal sub-bitstream.
119    Hevc,
120    /// 0x25 — HEVC temporal video subset (H.265 Annex A profiles).
121    HevcTemporalSubset,
122    /// 0x26 — MVCD video sub-bitstream of an AVC video stream (H.264 Annex I).
123    Mvcd,
124    /// 0x27 — Timeline and External Media Information (TEMI, H.222.0 Annex U).
125    Temi,
126    /// 0x28 — HEVC enhancement sub-partition incl. TemporalId 0 (H.265 Annex G).
127    HevcAnnexG,
128    /// 0x29 — HEVC temporal enhancement sub-partition (H.265 Annex G).
129    HevcAnnexGTemporal,
130    /// 0x2A — HEVC enhancement sub-partition incl. TemporalId 0 (H.265 Annex H).
131    HevcAnnexH,
132    /// 0x2B — HEVC temporal enhancement sub-partition (H.265 Annex H).
133    HevcAnnexHTemporal,
134    /// 0x2C — Green access units carried in MPEG-2 sections.
135    GreenAccessUnits,
136    /// 0x2D — ISO/IEC 23008-3 Audio with MHAS transport syntax — main stream.
137    MhasAudioMain,
138    /// 0x2E — ISO/IEC 23008-3 Audio with MHAS transport syntax — auxiliary stream.
139    MhasAudioAux,
140    /// 0x2F — Quality access units carried in sections.
141    QualityAccessUnits,
142    /// 0x30 — Media Orchestration Access Units carried in sections.
143    MediaOrchestration,
144    /// 0x31 — MCTS substream of an H.265 | ISO/IEC 23008-2 video stream.
145    MctsHevc,
146    /// 0x32 — JPEG XS video stream (ISO/IEC 21122-2 profiles).
147    JpegXs,
148    /// 0x33 — VVC video stream (Rec. ITU-T H.266 | ISO/IEC 23090-3) or VVC temporal sub-bitstream.
149    Vvc,
150    /// 0x34 — VVC temporal video subset (H.266 Annex A profiles).
151    VvcTemporalSubset,
152    /// 0x35 — EVC video stream or EVC temporal sub-bitstream (ISO/IEC 23094-1).
153    Evc,
154    /// 0x81 — ATSC AC-3 audio (A/52).
155    Ac3,
156    /// 0x86 — SCTE-35 splice_info_section (ANSI/SCTE 35).
157    Scte35,
158    /// 0x87 — ATSC E-AC-3 / Dolby Digital Plus audio (A/52B).
159    EAc3,
160    /// 0x7F — IPMP stream (H.222.0 Table 2-34).
161    IpmpHigh,
162    /// Rec. ITU-T H.222.0 reserved range `0x36`..=`0x7E`.
163    ReservedRange(u8),
164    /// User private range `0x80`..=`0xFF` (except named entries).
165    UserPrivate(u8),
166}
167
168impl StreamType {
169    /// Decode from the wire byte. Every byte maps to a variant (lossless).
170    #[must_use]
171    pub fn from_u8(v: u8) -> Self {
172        match v {
173            0x00 => Self::Reserved,
174            0x01 => Self::Mpeg1Video,
175            0x02 => Self::Mpeg2Video,
176            0x03 => Self::Mpeg1Audio,
177            0x04 => Self::Mpeg2Audio,
178            0x05 => Self::PrivateSections,
179            0x06 => Self::PesPrivateData,
180            0x07 => Self::Mheg,
181            0x08 => Self::DsmCc,
182            0x09 => Self::H222_1,
183            0x0A => Self::Iso13818_6TypeA,
184            0x0B => Self::Iso13818_6TypeB,
185            0x0C => Self::Iso13818_6TypeC,
186            0x0D => Self::Iso13818_6TypeD,
187            0x0E => Self::Auxiliary,
188            0x0F => Self::AacAdts,
189            0x10 => Self::Mpeg4Video,
190            0x11 => Self::AacLatm,
191            0x12 => Self::SlFlexMuxPes,
192            0x13 => Self::SlFlexMuxSections,
193            0x14 => Self::SyncDownload,
194            0x15 => Self::MetadataPes,
195            0x16 => Self::MetadataSections,
196            0x17 => Self::MetadataDataCarousel,
197            0x18 => Self::MetadataObjectCarousel,
198            0x19 => Self::MetadataSyncDownload,
199            0x1A => Self::Ipmp,
200            0x1B => Self::H264,
201            0x1C => Self::Iso14496_3Audio,
202            0x1D => Self::Iso14496_17Text,
203            0x1E => Self::AuxiliaryVideo,
204            0x1F => Self::Svc,
205            0x20 => Self::Mvc,
206            0x21 => Self::Jpeg2000,
207            0x22 => Self::AdditionalViewH262,
208            0x23 => Self::AdditionalViewH264,
209            0x24 => Self::Hevc,
210            0x25 => Self::HevcTemporalSubset,
211            0x26 => Self::Mvcd,
212            0x27 => Self::Temi,
213            0x28 => Self::HevcAnnexG,
214            0x29 => Self::HevcAnnexGTemporal,
215            0x2A => Self::HevcAnnexH,
216            0x2B => Self::HevcAnnexHTemporal,
217            0x2C => Self::GreenAccessUnits,
218            0x2D => Self::MhasAudioMain,
219            0x2E => Self::MhasAudioAux,
220            0x2F => Self::QualityAccessUnits,
221            0x30 => Self::MediaOrchestration,
222            0x31 => Self::MctsHevc,
223            0x32 => Self::JpegXs,
224            0x33 => Self::Vvc,
225            0x34 => Self::VvcTemporalSubset,
226            0x35 => Self::Evc,
227            0x36..=0x7E => Self::ReservedRange(v),
228            0x7F => Self::IpmpHigh,
229            0x81 => Self::Ac3,
230            0x86 => Self::Scte35,
231            0x87 => Self::EAc3,
232            _ => Self::UserPrivate(v),
233        }
234    }
235
236    /// Encode to the wire byte. Inverse of `from_u8`.
237    #[must_use]
238    pub const fn to_u8(self) -> u8 {
239        match self {
240            Self::Reserved => 0x00,
241            Self::Mpeg1Video => 0x01,
242            Self::Mpeg2Video => 0x02,
243            Self::Mpeg1Audio => 0x03,
244            Self::Mpeg2Audio => 0x04,
245            Self::PrivateSections => 0x05,
246            Self::PesPrivateData => 0x06,
247            Self::Mheg => 0x07,
248            Self::DsmCc => 0x08,
249            Self::H222_1 => 0x09,
250            Self::Iso13818_6TypeA => 0x0A,
251            Self::Iso13818_6TypeB => 0x0B,
252            Self::Iso13818_6TypeC => 0x0C,
253            Self::Iso13818_6TypeD => 0x0D,
254            Self::Auxiliary => 0x0E,
255            Self::AacAdts => 0x0F,
256            Self::Mpeg4Video => 0x10,
257            Self::AacLatm => 0x11,
258            Self::SlFlexMuxPes => 0x12,
259            Self::SlFlexMuxSections => 0x13,
260            Self::SyncDownload => 0x14,
261            Self::MetadataPes => 0x15,
262            Self::MetadataSections => 0x16,
263            Self::MetadataDataCarousel => 0x17,
264            Self::MetadataObjectCarousel => 0x18,
265            Self::MetadataSyncDownload => 0x19,
266            Self::Ipmp => 0x1A,
267            Self::H264 => 0x1B,
268            Self::Iso14496_3Audio => 0x1C,
269            Self::Iso14496_17Text => 0x1D,
270            Self::AuxiliaryVideo => 0x1E,
271            Self::Svc => 0x1F,
272            Self::Mvc => 0x20,
273            Self::Jpeg2000 => 0x21,
274            Self::AdditionalViewH262 => 0x22,
275            Self::AdditionalViewH264 => 0x23,
276            Self::Hevc => 0x24,
277            Self::HevcTemporalSubset => 0x25,
278            Self::Mvcd => 0x26,
279            Self::Temi => 0x27,
280            Self::HevcAnnexG => 0x28,
281            Self::HevcAnnexGTemporal => 0x29,
282            Self::HevcAnnexH => 0x2A,
283            Self::HevcAnnexHTemporal => 0x2B,
284            Self::GreenAccessUnits => 0x2C,
285            Self::MhasAudioMain => 0x2D,
286            Self::MhasAudioAux => 0x2E,
287            Self::QualityAccessUnits => 0x2F,
288            Self::MediaOrchestration => 0x30,
289            Self::MctsHevc => 0x31,
290            Self::JpegXs => 0x32,
291            Self::Vvc => 0x33,
292            Self::VvcTemporalSubset => 0x34,
293            Self::Evc => 0x35,
294            Self::IpmpHigh => 0x7F,
295            Self::Ac3 => 0x81,
296            Self::Scte35 => 0x86,
297            Self::EAc3 => 0x87,
298            Self::ReservedRange(v) | Self::UserPrivate(v) => v,
299        }
300    }
301
302    /// Returns `true` if this stream_type carries a video elementary stream.
303    ///
304    /// Covers the video-coded stream types defined in Rec. ITU-T H.222.0 |
305    /// ISO/IEC 13818-1 Table 2-34 (2021 edition):
306    ///
307    /// | Variant | Value | Spec |
308    /// |---------|-------|------|
309    /// | `Mpeg1Video` | 0x01 | ISO/IEC 11172-2 |
310    /// | `Mpeg2Video` | 0x02 | ITU-T H.262 / ISO/IEC 13818-2 |
311    /// | `Mpeg4Video` | 0x10 | ISO/IEC 14496-2 |
312    /// | `H264` | 0x1B | ITU-T H.264 / ISO/IEC 14496-10 |
313    /// | `AuxiliaryVideo` | 0x1E | ISO/IEC 23002-3 |
314    /// | `Svc` | 0x1F | H.264 Annex G |
315    /// | `Mvc` | 0x20 | H.264 Annex H |
316    /// | `Jpeg2000` | 0x21 | ITU-T T.800 / ISO/IEC 15444-1 |
317    /// | `AdditionalViewH262` | 0x22 | H.262 stereoscopic 3D |
318    /// | `AdditionalViewH264` | 0x23 | H.264 stereoscopic 3D |
319    /// | `Hevc` | 0x24 | ITU-T H.265 / ISO/IEC 23008-2 |
320    /// | `HevcTemporalSubset` | 0x25 | H.265 Annex A |
321    /// | `Mvcd` | 0x26 | H.264 Annex I |
322    /// | `HevcAnnexG` | 0x28 | H.265 Annex G |
323    /// | `HevcAnnexGTemporal` | 0x29 | H.265 Annex G |
324    /// | `HevcAnnexH` | 0x2A | H.265 Annex H |
325    /// | `HevcAnnexHTemporal` | 0x2B | H.265 Annex H |
326    /// | `MctsHevc` | 0x31 | H.265 / ISO/IEC 23008-2 |
327    /// | `JpegXs` | 0x32 | ISO/IEC 21122-2 |
328    /// | `Vvc` | 0x33 | ITU-T H.266 / ISO/IEC 23090-3 |
329    /// | `VvcTemporalSubset` | 0x34 | H.266 Annex A |
330    /// | `Evc` | 0x35 | ISO/IEC 23094-1 |
331    ///
332    /// **Note on `PesPrivateData` (0x06):** this stream_type alone does *not*
333    /// identify the payload — AC-3, H.264, DVB subtitle, teletext, and others
334    /// all share 0x06 and are disambiguated by component/registration descriptors
335    /// per ETSI TS 101 154. `is_video` returns `false` for `PesPrivateData`;
336    /// callers must inspect the ES_info descriptor loop to resolve the actual
337    /// content type.
338    #[must_use]
339    pub fn is_video(self) -> bool {
340        matches!(
341            self,
342            Self::Mpeg1Video
343                | Self::Mpeg2Video
344                | Self::Mpeg4Video
345                | Self::H264
346                | Self::AuxiliaryVideo
347                | Self::Svc
348                | Self::Mvc
349                | Self::Jpeg2000
350                | Self::AdditionalViewH262
351                | Self::AdditionalViewH264
352                | Self::Hevc
353                | Self::HevcTemporalSubset
354                | Self::Mvcd
355                | Self::HevcAnnexG
356                | Self::HevcAnnexGTemporal
357                | Self::HevcAnnexH
358                | Self::HevcAnnexHTemporal
359                | Self::MctsHevc
360                | Self::JpegXs
361                | Self::Vvc
362                | Self::VvcTemporalSubset
363                | Self::Evc
364        )
365    }
366
367    /// Returns `true` if this stream_type carries an audio elementary stream.
368    ///
369    /// Covers the audio-coded stream types defined in Rec. ITU-T H.222.0 |
370    /// ISO/IEC 13818-1 Table 2-34 (2021 edition):
371    ///
372    /// | Variant | Value | Spec |
373    /// |---------|-------|------|
374    /// | `Mpeg1Audio` | 0x03 | ISO/IEC 11172-3 |
375    /// | `Mpeg2Audio` | 0x04 | ISO/IEC 13818-3 |
376    /// | `AacAdts` | 0x0F | ISO/IEC 13818-7 (AAC in ADTS) |
377    /// | `AacLatm` | 0x11 | ISO/IEC 14496-3 (AAC in LATM) |
378    /// | `Iso14496_3Audio` | 0x1C | ISO/IEC 14496-3 (raw, no transport syntax) |
379    /// | `MhasAudioMain` | 0x2D | ISO/IEC 23008-3 MHAS main |
380    /// | `MhasAudioAux` | 0x2E | ISO/IEC 23008-3 MHAS auxiliary |
381    /// | `Ac3` | 0x81 | ATSC A/52 (AC-3) |
382    /// | `EAc3` | 0x87 | ATSC A/52B (E-AC-3 / Dolby Digital Plus) |
383    ///
384    /// **Note on `PesPrivateData` (0x06):** this stream_type alone does *not*
385    /// identify the payload — AC-3 carried over DVB uses 0x06 with an
386    /// AC-3/enhanced AC-3 descriptor per ETSI TS 101 154. `is_audio` returns
387    /// `false` for `PesPrivateData`; callers must inspect the ES_info descriptor
388    /// loop to resolve the actual content type.
389    #[must_use]
390    pub fn is_audio(self) -> bool {
391        matches!(
392            self,
393            Self::Mpeg1Audio
394                | Self::Mpeg2Audio
395                | Self::AacAdts
396                | Self::AacLatm
397                | Self::Iso14496_3Audio
398                | Self::MhasAudioMain
399                | Self::MhasAudioAux
400                | Self::Ac3
401                | Self::EAc3
402        )
403    }
404
405    /// Returns `true` if this stream_type unambiguously carries subtitle/text
406    /// content.
407    ///
408    /// Only `Iso14496_17Text` (0x1D, ISO/IEC 14496-17 timed text) is
409    /// unambiguously identified as subtitle/text by the stream_type byte alone.
410    ///
411    /// **Note on `PesPrivateData` (0x06):** DVB bitmap subtitles (ETSI EN 300 743)
412    /// and DVB teletext (ETSI EN 300 472) both use stream_type 0x06 and are
413    /// identified by subtitling / teletext descriptors in the ES_info loop per
414    /// ETSI TS 101 154. `is_subtitle` returns `false` for `PesPrivateData`;
415    /// callers must inspect the ES_info descriptor loop to resolve the actual
416    /// content type.
417    #[must_use]
418    pub fn is_subtitle(self) -> bool {
419        matches!(self, Self::Iso14496_17Text)
420    }
421
422    /// Returns `true` if this stream_type carries data, private sections,
423    /// signalling, or metadata — i.e. neither video, audio, nor subtitle.
424    ///
425    /// This includes:
426    ///
427    /// | Variant | Value | Notes |
428    /// |---------|-------|-------|
429    /// | `Reserved` | 0x00 | ITU-T / ISO reserved |
430    /// | `PrivateSections` | 0x05 | private_sections (H.222.0 §2.4.4.10) |
431    /// | `PesPrivateData` | 0x06 | descriptor-dependent; see note below |
432    /// | `Mheg` | 0x07 | MHEG interactive |
433    /// | `DsmCc` | 0x08 | DSM-CC (H.222.0 Annex A) |
434    /// | `H222_1` | 0x09 | H.222.1 |
435    /// | `Iso13818_6TypeA`–`TypeD` | 0x0A–0x0D | ISO/IEC 13818-6 carousel/download |
436    /// | `Auxiliary` | 0x0E | H.222.0 auxiliary |
437    /// | `SlFlexMuxPes` | 0x12 | SL/FlexMux in PES |
438    /// | `SlFlexMuxSections` | 0x13 | SL/FlexMux in sections |
439    /// | `SyncDownload` | 0x14 | ISO/IEC 13818-6 Synchronized Download Protocol |
440    /// | `MetadataPes`–`MetadataSyncDownload` | 0x15–0x19 | metadata streams |
441    /// | `Ipmp` | 0x1A | IPMP (ISO/IEC 13818-11) |
442    /// | `Temi` | 0x27 | Timeline and External Media Information |
443    /// | `GreenAccessUnits` | 0x2C | green access units |
444    /// | `QualityAccessUnits` | 0x2F | quality access units |
445    /// | `MediaOrchestration` | 0x30 | Media Orchestration |
446    /// | `IpmpHigh` | 0x7F | IPMP (H.222.0 Table 2-34) |
447    /// | `Scte35` | 0x86 | SCTE-35 splice_info_section |
448    /// | `ReservedRange(_)` | 0x36–0x7E | reserved, treated conservatively as data |
449    /// | `UserPrivate(_)` | 0x80–0xFF | user private, treated conservatively as data |
450    ///
451    /// **Note on `PesPrivateData` (0x06):** this stream_type is shared by many
452    /// DVB content types — AC-3 audio, E-AC-3, DVB subtitles, teletext, and
453    /// others — all identified via descriptors per ETSI TS 101 154. This
454    /// predicate conservatively classifies 0x06 as data/unknown. Callers that
455    /// need to classify 0x06 streams must inspect the ES_info descriptor loop.
456    #[must_use]
457    pub fn is_data(self) -> bool {
458        !self.is_video() && !self.is_audio() && !self.is_subtitle()
459    }
460
461    /// Human-readable spec display name.
462    #[must_use]
463    pub fn name(self) -> &'static str {
464        match self {
465            Self::Reserved => "Reserved",
466            Self::Mpeg1Video => "MPEG-1 Video",
467            Self::Mpeg2Video => "MPEG-2 Video",
468            Self::Mpeg1Audio => "MPEG-1 Audio",
469            Self::Mpeg2Audio => "MPEG-2 Audio",
470            Self::PrivateSections => "Private Sections",
471            Self::PesPrivateData => "PES Private Data",
472            Self::Mheg => "MHEG",
473            Self::DsmCc => "DSM-CC",
474            Self::H222_1 => "H.222.1",
475            Self::Iso13818_6TypeA => "ISO/IEC 13818-6 Type A",
476            Self::Iso13818_6TypeB => "ISO/IEC 13818-6 Type B",
477            Self::Iso13818_6TypeC => "ISO/IEC 13818-6 Type C",
478            Self::Iso13818_6TypeD => "ISO/IEC 13818-6 Type D",
479            Self::Auxiliary => "Auxiliary",
480            Self::AacAdts => "AAC ADTS",
481            Self::Mpeg4Video => "MPEG-4 Video",
482            Self::AacLatm => "AAC LATM",
483            Self::SlFlexMuxPes => "SL/FlexMux in PES",
484            Self::SlFlexMuxSections => "SL/FlexMux in Sections",
485            Self::SyncDownload => "Sync Download Protocol",
486            Self::MetadataPes => "Metadata in PES",
487            Self::MetadataSections => "Metadata in Sections",
488            Self::MetadataDataCarousel => "Metadata Data Carousel",
489            Self::MetadataObjectCarousel => "Metadata Object Carousel",
490            Self::MetadataSyncDownload => "Metadata Sync Download",
491            Self::Ipmp => "IPMP",
492            Self::H264 => "H.264/AVC",
493            Self::Iso14496_3Audio => "ISO/IEC 14496-3 Audio",
494            Self::Iso14496_17Text => "ISO/IEC 14496-17 Text",
495            Self::AuxiliaryVideo => "Auxiliary Video",
496            Self::Svc => "SVC",
497            Self::Mvc => "MVC",
498            Self::Jpeg2000 => "JPEG 2000",
499            Self::AdditionalViewH262 => "Additional View H.262 (3D)",
500            Self::AdditionalViewH264 => "Additional View H.264 (3D)",
501            Self::Hevc => "HEVC/H.265",
502            Self::HevcTemporalSubset => "HEVC Temporal Subset",
503            Self::Mvcd => "MVCD (H.264 Annex I)",
504            Self::Temi => "TEMI",
505            Self::HevcAnnexG => "HEVC Annex G",
506            Self::HevcAnnexGTemporal => "HEVC Annex G Temporal",
507            Self::HevcAnnexH => "HEVC Annex H",
508            Self::HevcAnnexHTemporal => "HEVC Annex H Temporal",
509            Self::GreenAccessUnits => "Green Access Units",
510            Self::MhasAudioMain => "MHAS Audio Main",
511            Self::MhasAudioAux => "MHAS Audio Aux",
512            Self::QualityAccessUnits => "Quality Access Units",
513            Self::MediaOrchestration => "Media Orchestration",
514            Self::MctsHevc => "MCTS HEVC",
515            Self::JpegXs => "JPEG XS",
516            Self::Vvc => "VVC/H.266",
517            Self::VvcTemporalSubset => "VVC Temporal Subset",
518            Self::Evc => "EVC",
519            Self::IpmpHigh => "IPMP (0x7F)",
520            Self::Ac3 => "AC-3",
521            Self::Scte35 => "SCTE-35",
522            Self::EAc3 => "E-AC-3",
523            Self::ReservedRange(_) => "Reserved",
524            Self::UserPrivate(_) => "User Private",
525        }
526    }
527}
528broadcast_common::impl_spec_display!(StreamType, ReservedRange, UserPrivate);
529
530/// One elementary stream entry in the PMT's ES loop.
531#[derive(Debug, Clone, PartialEq, Eq)]
532#[cfg_attr(feature = "serde", derive(serde::Serialize))]
533#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
534pub struct PmtStream<'a> {
535    /// MPEG-2 stream_type byte (ISO/IEC 13818-1 Table 2-34).
536    pub stream_type: StreamType,
537    /// 13-bit elementary stream PID.
538    pub elementary_pid: u16,
539    /// Raw ES_info descriptor bytes; parsing lives in crate::descriptors.
540    /// Elementary-stream descriptor loop. Serializes as the typed descriptor
541    /// sequence; `.raw()` yields the wire bytes.
542    pub es_info: DescriptorLoop<'a>,
543}
544
545/// Program Map Table.
546#[non_exhaustive]
547#[derive(Debug, Clone, PartialEq, Eq)]
548#[cfg_attr(feature = "serde", derive(serde::Serialize))]
549#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
550pub struct PmtSection<'a> {
551    /// Programme number from the table_id_extension field.
552    pub program_number: u16,
553    /// 5-bit version_number.
554    pub version_number: u8,
555    /// current_next_indicator bit.
556    pub current_next_indicator: bool,
557    /// section_number in the sub-table sequence (ISO/IEC 13818-1 §2.4.4.8;
558    /// shall be 0x00 for conformant PMTs but preserved for round-trip fidelity).
559    pub section_number: u8,
560    /// last_section_number in the sub-table sequence (ISO/IEC 13818-1 §2.4.4.8;
561    /// shall be 0x00 for conformant PMTs but preserved for round-trip fidelity).
562    pub last_section_number: u8,
563    /// 13-bit PCR PID.
564    pub pcr_pid: u16,
565    /// Raw program_info descriptor bytes.
566    /// Program-info descriptor loop. Serializes as the typed descriptor
567    /// sequence; `.raw()` yields the wire bytes.
568    pub program_info: DescriptorLoop<'a>,
569    /// Elementary streams in wire order.
570    pub streams: Vec<PmtStream<'a>>,
571}
572
573impl<'a> PmtSection<'a> {
574    /// Construct a `PmtSection` from its fields.
575    ///
576    /// This is the canonical constructor for external code. `PmtSection` is
577    /// `#[non_exhaustive]` so struct literal syntax is not available outside
578    /// the crate; use this function instead.
579    #[must_use]
580    #[allow(clippy::too_many_arguments)]
581    pub fn new(
582        program_number: u16,
583        version_number: u8,
584        current_next_indicator: bool,
585        section_number: u8,
586        last_section_number: u8,
587        pcr_pid: u16,
588        program_info: DescriptorLoop<'a>,
589        streams: Vec<PmtStream<'a>>,
590    ) -> Self {
591        Self {
592            program_number,
593            version_number,
594            current_next_indicator,
595            section_number,
596            last_section_number,
597            pcr_pid,
598            program_info,
599            streams,
600        }
601    }
602}
603
604impl<'a> Parse<'a> for PmtSection<'a> {
605    type Error = crate::error::Error;
606    fn parse(bytes: &'a [u8]) -> Result<Self> {
607        let min_len =
608            MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES + CRC_LEN;
609        if bytes.len() < min_len {
610            return Err(Error::BufferTooShort {
611                need: min_len,
612                have: bytes.len(),
613                what: "PmtSection",
614            });
615        }
616        if bytes[0] != TABLE_ID {
617            return Err(Error::UnexpectedTableId {
618                table_id: bytes[0],
619                what: "PmtSection",
620                expected: &[TABLE_ID],
621            });
622        }
623
624        let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
625        let total = super::check_section_length(
626            bytes.len(),
627            MIN_HEADER_LEN,
628            section_length as usize,
629            MIN_SECTION_LEN,
630        )?;
631
632        let program_number = u16::from_be_bytes(*bytes[3..].first_chunk::<2>().unwrap());
633        let version_number = (bytes[5] >> 1) & 0x1F;
634        let current_next_indicator = (bytes[5] & 0x01) != 0;
635        let section_number = bytes[6];
636        let last_section_number = bytes[7];
637
638        let pcr_pid = (((bytes[8] & 0x1F) as u16) << 8) | bytes[9] as u16;
639        let program_info_length = (((bytes[10] & 0x0F) as usize) << 8) | bytes[11] as usize;
640
641        let prog_info_start =
642            MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES;
643        let prog_info_end = prog_info_start + program_info_length;
644        let stream_loop_end = total - CRC_LEN;
645        if prog_info_end > stream_loop_end {
646            return Err(Error::SectionLengthOverflow {
647                declared: program_info_length,
648                available: stream_loop_end.saturating_sub(prog_info_start),
649            });
650        }
651        let program_info = DescriptorLoop::new(&bytes[prog_info_start..prog_info_end]);
652
653        let mut streams = Vec::new();
654        let mut pos = prog_info_end;
655        while pos + STREAM_HEADER_LEN <= stream_loop_end {
656            let stream_type = StreamType::from_u8(bytes[pos]);
657            let elementary_pid = (((bytes[pos + 1] & 0x1F) as u16) << 8) | bytes[pos + 2] as u16;
658            let es_info_length =
659                (((bytes[pos + 3] & 0x0F) as usize) << 8) | bytes[pos + 4] as usize;
660            let es_start = pos + STREAM_HEADER_LEN;
661            let es_end = es_start + es_info_length;
662            if es_end > stream_loop_end {
663                return Err(Error::SectionLengthOverflow {
664                    declared: es_info_length,
665                    available: stream_loop_end.saturating_sub(es_start),
666                });
667            }
668            streams.push(PmtStream {
669                stream_type,
670                elementary_pid,
671                es_info: DescriptorLoop::new(&bytes[es_start..es_end]),
672            });
673            pos = es_end;
674        }
675
676        Ok(PmtSection {
677            program_number,
678            version_number,
679            current_next_indicator,
680            section_number,
681            last_section_number,
682            pcr_pid,
683            program_info,
684            streams,
685        })
686    }
687}
688
689impl Serialize for PmtSection<'_> {
690    type Error = crate::error::Error;
691    fn serialized_len(&self) -> usize {
692        let streams_bytes: usize = self
693            .streams
694            .iter()
695            .map(|s| STREAM_HEADER_LEN + s.es_info.len())
696            .sum();
697        MIN_HEADER_LEN
698            + EXTENSION_HEADER_LEN
699            + PCR_PID_LEN
700            + PROG_INFO_LEN_BYTES
701            + self.program_info.len()
702            + streams_bytes
703            + CRC_LEN
704    }
705
706    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
707        let len = self.serialized_len();
708        if buf.len() < len {
709            return Err(Error::OutputBufferTooSmall {
710                need: len,
711                have: buf.len(),
712            });
713        }
714
715        let section_length: u16 = (len - MIN_HEADER_LEN) as u16;
716        buf[0] = TABLE_ID;
717        buf[1] = super::SECTION_B1_FLAGS_PSI | ((section_length >> 8) as u8 & 0x0F);
718        buf[2] = (section_length & 0xFF) as u8;
719        buf[3..5].copy_from_slice(&self.program_number.to_be_bytes());
720        buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
721        buf[6] = self.section_number;
722        buf[7] = self.last_section_number;
723        buf[8] = 0xE0 | ((self.pcr_pid >> 8) as u8 & 0x1F);
724        buf[9] = (self.pcr_pid & 0xFF) as u8;
725        let pil = self.program_info.len() as u16;
726        buf[10] = 0xF0 | ((pil >> 8) as u8 & 0x0F);
727        buf[11] = (pil & 0xFF) as u8;
728
729        let prog_info_start =
730            MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES;
731        buf[prog_info_start..prog_info_start + self.program_info.len()]
732            .copy_from_slice(self.program_info.raw());
733
734        let mut pos = prog_info_start + self.program_info.len();
735        for stream in &self.streams {
736            buf[pos] = stream.stream_type.to_u8();
737            buf[pos + 1] = 0xE0 | ((stream.elementary_pid >> 8) as u8 & 0x1F);
738            buf[pos + 2] = (stream.elementary_pid & 0xFF) as u8;
739            let esl = stream.es_info.len() as u16;
740            buf[pos + 3] = 0xF0 | ((esl >> 8) as u8 & 0x0F);
741            buf[pos + 4] = (esl & 0xFF) as u8;
742            let es_start = pos + STREAM_HEADER_LEN;
743            buf[es_start..es_start + stream.es_info.len()].copy_from_slice(stream.es_info.raw());
744            pos = es_start + stream.es_info.len();
745        }
746
747        let crc_pos = len - CRC_LEN;
748        let crc = broadcast_common::crc32_mpeg2::compute(&buf[..crc_pos]);
749        buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
750        Ok(len)
751    }
752}
753impl<'a> crate::traits::TableDef<'a> for PmtSection<'a> {
754    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
755    const NAME: &'static str = "PROGRAM_MAP";
756}
757
758#[cfg(test)]
759mod tests {
760    use super::*;
761
762    /// Build a PMT section with given fields. Placeholder CRC.
763    fn build_pmt(
764        program_number: u16,
765        version: u8,
766        pcr_pid: u16,
767        program_info: &[u8],
768        streams: &[(u8, u16, Vec<u8>)],
769    ) -> Vec<u8> {
770        let streams_bytes: usize = streams
771            .iter()
772            .map(|(_, _, es)| STREAM_HEADER_LEN + es.len())
773            .sum();
774        let section_length: u16 = (EXTENSION_HEADER_LEN
775            + PCR_PID_LEN
776            + PROG_INFO_LEN_BYTES
777            + program_info.len()
778            + streams_bytes
779            + CRC_LEN) as u16;
780        let mut v = Vec::new();
781        v.push(TABLE_ID);
782        v.push(super::super::SECTION_B1_FLAGS_PSI | ((section_length >> 8) as u8 & 0x0F));
783        v.push((section_length & 0xFF) as u8);
784        v.extend_from_slice(&program_number.to_be_bytes());
785        v.push(0xC0 | ((version & 0x1F) << 1) | 0x01);
786        v.push(0);
787        v.push(0);
788        v.push(0xE0 | ((pcr_pid >> 8) as u8 & 0x1F));
789        v.push((pcr_pid & 0xFF) as u8);
790        v.push(0xF0 | ((program_info.len() >> 8) as u8 & 0x0F));
791        v.push((program_info.len() & 0xFF) as u8);
792        v.extend_from_slice(program_info);
793        for (stype, pid, es) in streams {
794            v.push(*stype);
795            v.push(0xE0 | ((pid >> 8) as u8 & 0x1F));
796            v.push((pid & 0xFF) as u8);
797            v.push(0xF0 | ((es.len() >> 8) as u8 & 0x0F));
798            v.push((es.len() & 0xFF) as u8);
799            v.extend_from_slice(es);
800        }
801        v.extend_from_slice(&[0, 0, 0, 0]);
802        v
803    }
804
805    #[test]
806    fn parse_extracts_pcr_pid_and_program_info() {
807        let bytes = build_pmt(42, 5, 0x0100, &[0xAA, 0xBB], &[]);
808        let pmt = PmtSection::parse(&bytes).unwrap();
809        assert_eq!(pmt.program_number, 42);
810        assert_eq!(pmt.version_number, 5);
811        assert!(pmt.current_next_indicator);
812        assert_eq!(pmt.pcr_pid, 0x0100);
813        assert_eq!(pmt.program_info.raw(), &[0xAA, 0xBB]);
814        assert_eq!(pmt.streams.len(), 0);
815    }
816
817    #[test]
818    fn parse_elementary_streams_and_es_info_slices() {
819        let bytes = build_pmt(
820            1,
821            0,
822            0x101,
823            &[],
824            &[(0x02, 0x102, vec![0x11, 0x22]), (0x1B, 0x103, vec![0x33])],
825        );
826        let pmt = PmtSection::parse(&bytes).unwrap();
827        assert_eq!(pmt.streams.len(), 2);
828        assert_eq!(pmt.streams[0].stream_type, StreamType::Mpeg2Video);
829        assert_eq!(pmt.streams[0].elementary_pid, 0x102);
830        assert_eq!(pmt.streams[0].es_info.raw(), &[0x11, 0x22]);
831        assert_eq!(pmt.streams[1].stream_type, StreamType::H264);
832        assert_eq!(pmt.streams[1].elementary_pid, 0x103);
833        assert_eq!(pmt.streams[1].es_info.raw(), &[0x33]);
834    }
835
836    #[test]
837    fn parse_rejects_wrong_table_id() {
838        let mut bytes = build_pmt(1, 0, 0x100, &[], &[]);
839        bytes[0] = 0x00;
840        let err = PmtSection::parse(&bytes).unwrap_err();
841        assert!(matches!(
842            err,
843            Error::UnexpectedTableId { table_id: 0x00, .. }
844        ));
845    }
846
847    #[test]
848    fn parse_rejects_short_buffer() {
849        let err = PmtSection::parse(&[0x02, 0x00]).unwrap_err();
850        assert!(matches!(err, Error::BufferTooShort { .. }));
851    }
852
853    #[test]
854    fn serialize_round_trip_empty_program() {
855        let pmt = PmtSection {
856            program_number: 1,
857            version_number: 0,
858            current_next_indicator: true,
859            section_number: 0,
860            last_section_number: 0,
861            pcr_pid: 0x100,
862            program_info: DescriptorLoop::new(&[]),
863            streams: vec![],
864        };
865        let mut buf = vec![0u8; pmt.serialized_len()];
866        pmt.serialize_into(&mut buf).unwrap();
867        let re = PmtSection::parse(&buf).unwrap();
868        assert_eq!(pmt, re);
869    }
870
871    #[test]
872    fn serialize_round_trip_with_streams_and_descriptors() {
873        let prog_info: [u8; 3] = [0x09, 0x01, 0xFF];
874        let es1: [u8; 4] = [0x52, 0x02, 0xAA, 0xBB];
875        let es2: [u8; 2] = [0x0A, 0x00];
876        let pmt = PmtSection {
877            program_number: 0xABCD,
878            version_number: 7,
879            current_next_indicator: true,
880            section_number: 0,
881            last_section_number: 0,
882            pcr_pid: 0x1F0,
883            program_info: DescriptorLoop::new(&prog_info),
884            streams: vec![
885                PmtStream {
886                    stream_type: StreamType::Mpeg2Video,
887                    elementary_pid: 0x100,
888                    es_info: DescriptorLoop::new(&es1),
889                },
890                PmtStream {
891                    stream_type: StreamType::Mpeg1Audio,
892                    elementary_pid: 0x101,
893                    es_info: DescriptorLoop::new(&es2),
894                },
895                PmtStream {
896                    stream_type: StreamType::H264,
897                    elementary_pid: 0x102,
898                    es_info: DescriptorLoop::new(&[]),
899                },
900            ],
901        };
902        let mut buf = vec![0u8; pmt.serialized_len()];
903        pmt.serialize_into(&mut buf).unwrap();
904        let re = PmtSection::parse(&buf).unwrap();
905        assert_eq!(pmt, re);
906    }
907
908    #[test]
909    fn zero_elementary_streams_is_valid() {
910        let bytes = build_pmt(99, 0, 0x0100, &[], &[]);
911        let pmt = PmtSection::parse(&bytes).unwrap();
912        assert_eq!(pmt.streams.len(), 0);
913    }
914
915    #[test]
916    fn parse_preserves_raw_program_info_bytes() {
917        let pi = vec![0x09, 0x04, 0x01, 0x02, 0x03, 0x04];
918        let bytes = build_pmt(1, 0, 0x100, &pi, &[]);
919        let pmt = PmtSection::parse(&bytes).unwrap();
920        assert_eq!(pmt.program_info.raw(), &pi[..]);
921    }
922
923    #[test]
924    fn parse_rejects_zero_section_length() {
925        let mut buf = vec![0u8; 64];
926        buf[0] = TABLE_ID;
927        buf[1] = 0xF0;
928        buf[2] = 0x00;
929        for b in &mut buf[3..] {
930            *b = 0xFF;
931        }
932        assert!(matches!(
933            PmtSection::parse(&buf).unwrap_err(),
934            Error::SectionLengthOverflow { .. }
935        ));
936    }
937
938    #[test]
939    fn stream_type_full_range_round_trip() {
940        for byte in 0u8..=0xFF {
941            let st = StreamType::from_u8(byte);
942            assert_eq!(
943                st.to_u8(),
944                byte,
945                "StreamType round-trip failed for {byte:#04x}"
946            );
947        }
948    }
949
950    #[test]
951    fn stream_type_named_values() {
952        assert_eq!(StreamType::Mpeg2Video.to_u8(), 0x02);
953        assert_eq!(StreamType::H264.to_u8(), 0x1B);
954        assert_eq!(StreamType::Hevc.to_u8(), 0x24);
955        assert_eq!(StreamType::Vvc.to_u8(), 0x33);
956        assert_eq!(StreamType::MediaOrchestration.to_u8(), 0x30);
957        assert_eq!(StreamType::Mvcd.to_u8(), 0x26);
958        assert_eq!(StreamType::Temi.to_u8(), 0x27);
959        assert_eq!(StreamType::Ac3.to_u8(), 0x81);
960        assert_eq!(StreamType::Scte35.to_u8(), 0x86);
961        assert_eq!(StreamType::EAc3.to_u8(), 0x87);
962        assert_eq!(StreamType::AacAdts.to_u8(), 0x0F);
963        assert_eq!(StreamType::IpmpHigh.to_u8(), 0x7F);
964    }
965
966    #[test]
967    fn stream_type_names() {
968        assert_eq!(StreamType::Mpeg2Video.name(), "MPEG-2 Video");
969        assert_eq!(StreamType::H264.name(), "H.264/AVC");
970        assert_eq!(StreamType::Hevc.name(), "HEVC/H.265");
971        assert_eq!(StreamType::Vvc.name(), "VVC/H.266");
972        assert_eq!(StreamType::MediaOrchestration.name(), "Media Orchestration");
973        assert_eq!(StreamType::Mvcd.name(), "MVCD (H.264 Annex I)");
974        assert_eq!(StreamType::Temi.name(), "TEMI");
975        assert_eq!(StreamType::DsmCc.name(), "DSM-CC");
976        assert_eq!(StreamType::Ac3.name(), "AC-3");
977        assert_eq!(StreamType::Scte35.name(), "SCTE-35");
978    }
979
980    #[test]
981    fn stream_type_wire_to_name() {
982        assert_eq!(StreamType::from_u8(0x02).name(), "MPEG-2 Video");
983        assert_eq!(StreamType::from_u8(0x1B).name(), "H.264/AVC");
984        assert_eq!(StreamType::from_u8(0x24).name(), "HEVC/H.265");
985        assert_eq!(StreamType::from_u8(0x00).name(), "Reserved");
986        assert_eq!(StreamType::from_u8(0x81).name(), "AC-3");
987    }
988
989    // ── StreamType classifier predicates (#522) ────────────────────────────
990
991    #[test]
992    fn stream_type_is_video_known_types() {
993        // Each video stream_type must be recognised.
994        for (byte, label) in [
995            (0x01_u8, "Mpeg1Video"),
996            (0x02, "Mpeg2Video"),
997            (0x10, "Mpeg4Video"),
998            (0x1B, "H264"),
999            (0x1E, "AuxiliaryVideo"),
1000            (0x1F, "Svc"),
1001            (0x20, "Mvc"),
1002            (0x21, "Jpeg2000"),
1003            (0x22, "AdditionalViewH262"),
1004            (0x23, "AdditionalViewH264"),
1005            (0x24, "Hevc"),
1006            (0x25, "HevcTemporalSubset"),
1007            (0x26, "Mvcd"),
1008            (0x28, "HevcAnnexG"),
1009            (0x29, "HevcAnnexGTemporal"),
1010            (0x2A, "HevcAnnexH"),
1011            (0x2B, "HevcAnnexHTemporal"),
1012            (0x31, "MctsHevc"),
1013            (0x32, "JpegXs"),
1014            (0x33, "Vvc"),
1015            (0x34, "VvcTemporalSubset"),
1016            (0x35, "Evc"),
1017        ] {
1018            let st = StreamType::from_u8(byte);
1019            assert!(st.is_video(), "{label} (0x{byte:02X}) should be is_video");
1020            assert!(
1021                !st.is_audio(),
1022                "{label} (0x{byte:02X}) must not be is_audio"
1023            );
1024            assert!(
1025                !st.is_subtitle(),
1026                "{label} (0x{byte:02X}) must not be is_subtitle"
1027            );
1028            assert!(!st.is_data(), "{label} (0x{byte:02X}) must not be is_data");
1029        }
1030    }
1031
1032    #[test]
1033    fn stream_type_is_audio_known_types() {
1034        for (byte, label) in [
1035            (0x03_u8, "Mpeg1Audio"),
1036            (0x04, "Mpeg2Audio"),
1037            (0x0F, "AacAdts"),
1038            (0x11, "AacLatm"),
1039            (0x1C, "Iso14496_3Audio"),
1040            (0x2D, "MhasAudioMain"),
1041            (0x2E, "MhasAudioAux"),
1042            (0x81, "Ac3"),
1043            (0x87, "EAc3"),
1044        ] {
1045            let st = StreamType::from_u8(byte);
1046            assert!(st.is_audio(), "{label} (0x{byte:02X}) should be is_audio");
1047            assert!(
1048                !st.is_video(),
1049                "{label} (0x{byte:02X}) must not be is_video"
1050            );
1051            assert!(
1052                !st.is_subtitle(),
1053                "{label} (0x{byte:02X}) must not be is_subtitle"
1054            );
1055            assert!(!st.is_data(), "{label} (0x{byte:02X}) must not be is_data");
1056        }
1057    }
1058
1059    #[test]
1060    fn stream_type_is_subtitle_known_types() {
1061        let st = StreamType::from_u8(0x1D); // Iso14496_17Text
1062        assert!(st.is_subtitle(), "Iso14496_17Text should be is_subtitle");
1063        assert!(!st.is_video(), "Iso14496_17Text must not be is_video");
1064        assert!(!st.is_audio(), "Iso14496_17Text must not be is_audio");
1065        assert!(!st.is_data(), "Iso14496_17Text must not be is_data");
1066    }
1067
1068    #[test]
1069    fn stream_type_pes_private_data_is_data() {
1070        // 0x06 is descriptor-dependent; the conservative classification is is_data.
1071        let st = StreamType::from_u8(0x06);
1072        assert!(
1073            st.is_data(),
1074            "PesPrivateData (0x06) should classify as is_data"
1075        );
1076        assert!(!st.is_video(), "PesPrivateData must not be is_video");
1077        assert!(!st.is_audio(), "PesPrivateData must not be is_audio");
1078        assert!(!st.is_subtitle(), "PesPrivateData must not be is_subtitle");
1079    }
1080
1081    #[test]
1082    fn stream_type_data_known_types() {
1083        for (byte, label) in [
1084            (0x00_u8, "Reserved"),
1085            (0x05, "PrivateSections"),
1086            (0x06, "PesPrivateData"),
1087            (0x07, "Mheg"),
1088            (0x08, "DsmCc"),
1089            (0x09, "H222_1"),
1090            (0x0A, "Iso13818_6TypeA"),
1091            (0x0B, "Iso13818_6TypeB"),
1092            (0x0C, "Iso13818_6TypeC"),
1093            (0x0D, "Iso13818_6TypeD"),
1094            (0x0E, "Auxiliary"),
1095            (0x12, "SlFlexMuxPes"),
1096            (0x13, "SlFlexMuxSections"),
1097            (0x14, "SyncDownload"),
1098            (0x15, "MetadataPes"),
1099            (0x16, "MetadataSections"),
1100            (0x17, "MetadataDataCarousel"),
1101            (0x18, "MetadataObjectCarousel"),
1102            (0x19, "MetadataSyncDownload"),
1103            (0x1A, "Ipmp"),
1104            (0x27, "Temi"),
1105            (0x2C, "GreenAccessUnits"),
1106            (0x2F, "QualityAccessUnits"),
1107            (0x30, "MediaOrchestration"),
1108            (0x7F, "IpmpHigh"),
1109            (0x86, "Scte35"),
1110        ] {
1111            let st = StreamType::from_u8(byte);
1112            assert!(st.is_data(), "{label} (0x{byte:02X}) should be is_data");
1113            assert!(
1114                !st.is_video(),
1115                "{label} (0x{byte:02X}) must not be is_video"
1116            );
1117            assert!(
1118                !st.is_audio(),
1119                "{label} (0x{byte:02X}) must not be is_audio"
1120            );
1121            assert!(
1122                !st.is_subtitle(),
1123                "{label} (0x{byte:02X}) must not be is_subtitle"
1124            );
1125        }
1126    }
1127
1128    #[test]
1129    fn stream_type_classifier_partition_all_bytes() {
1130        // Every byte value is covered by exactly one predicate.
1131        for byte in 0u8..=0xFF {
1132            let st = StreamType::from_u8(byte);
1133            let n = u8::from(st.is_video())
1134                + u8::from(st.is_audio())
1135                + u8::from(st.is_subtitle())
1136                + u8::from(st.is_data());
1137            assert_eq!(
1138                n, 1,
1139                "stream_type 0x{byte:02X} must be in exactly one category, got {n}"
1140            );
1141        }
1142    }
1143
1144    /// Regression test for #181: non-zero section_number/last_section_number must
1145    /// survive a serialize → parse round-trip. Before the fix, serialize_into
1146    /// hardcoded buf[6]=0 and buf[7]=0, so this test would fail.
1147    #[test]
1148    fn section_number_round_trip_nonzero() {
1149        let pmt = PmtSection {
1150            program_number: 42,
1151            version_number: 1,
1152            current_next_indicator: true,
1153            section_number: 3,
1154            last_section_number: 7,
1155            pcr_pid: 0x0200,
1156            program_info: DescriptorLoop::new(&[]),
1157            streams: vec![],
1158        };
1159        let mut buf = vec![0u8; pmt.serialized_len()];
1160        pmt.serialize_into(&mut buf).unwrap();
1161        // Wire bytes[6] and [7] must carry the encoded values.
1162        assert_eq!(buf[6], 3, "wire byte[6] must be section_number=3");
1163        assert_eq!(buf[7], 7, "wire byte[7] must be last_section_number=7");
1164        // Re-parse must recover the same values.
1165        let re = PmtSection::parse(&buf).unwrap();
1166        assert_eq!(re.section_number, 3);
1167        assert_eq!(re.last_section_number, 7);
1168        assert_eq!(pmt, re);
1169    }
1170}