1use crate::descriptors::DescriptorLoop;
8use crate::error::{Error, Result};
9use alloc::vec::Vec;
10use broadcast_common::{Parse, Serialize};
11
12pub const TABLE_ID: u8 = 0x02;
14pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize))]
44#[non_exhaustive]
45pub enum StreamType {
46 Reserved,
48 Mpeg1Video,
50 Mpeg2Video,
52 Mpeg1Audio,
54 Mpeg2Audio,
56 PrivateSections,
58 PesPrivateData,
60 Mheg,
62 DsmCc,
64 H222_1,
66 Iso13818_6TypeA,
68 Iso13818_6TypeB,
70 Iso13818_6TypeC,
72 Iso13818_6TypeD,
74 Auxiliary,
76 AacAdts,
78 Mpeg4Video,
80 AacLatm,
82 SlFlexMuxPes,
84 SlFlexMuxSections,
86 SyncDownload,
88 MetadataPes,
90 MetadataSections,
92 MetadataDataCarousel,
94 MetadataObjectCarousel,
96 MetadataSyncDownload,
98 Ipmp,
100 H264,
102 Iso14496_3Audio,
104 Iso14496_17Text,
106 AuxiliaryVideo,
108 Svc,
110 Mvc,
112 Jpeg2000,
114 AdditionalViewH262,
116 AdditionalViewH264,
118 Hevc,
120 HevcTemporalSubset,
122 Mvcd,
124 Temi,
126 HevcAnnexG,
128 HevcAnnexGTemporal,
130 HevcAnnexH,
132 HevcAnnexHTemporal,
134 GreenAccessUnits,
136 MhasAudioMain,
138 MhasAudioAux,
140 QualityAccessUnits,
142 MediaOrchestration,
144 MctsHevc,
146 JpegXs,
148 Vvc,
150 VvcTemporalSubset,
152 Evc,
154 Ac3,
156 Scte35,
158 EAc3,
160 IpmpHigh,
162 ReservedRange(u8),
164 UserPrivate(u8),
166}
167
168impl StreamType {
169 #[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 #[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 #[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 #[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 #[must_use]
418 pub fn is_subtitle(self) -> bool {
419 matches!(self, Self::Iso14496_17Text)
420 }
421
422 #[must_use]
457 pub fn is_data(self) -> bool {
458 !self.is_video() && !self.is_audio() && !self.is_subtitle()
459 }
460
461 #[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#[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 pub stream_type: StreamType,
537 pub elementary_pid: u16,
539 pub es_info: DescriptorLoop<'a>,
543}
544
545#[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 pub program_number: u16,
553 pub version_number: u8,
555 pub current_next_indicator: bool,
557 pub section_number: u8,
560 pub last_section_number: u8,
563 pub pcr_pid: u16,
565 pub program_info: DescriptorLoop<'a>,
569 pub streams: Vec<PmtStream<'a>>,
571}
572
573impl<'a> PmtSection<'a> {
574 #[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 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 #[test]
992 fn stream_type_is_video_known_types() {
993 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); 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 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 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 #[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 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 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}