mpeg_ts/pid.rs
1//! MPEG-TS Packet Identifier (PID) types and well-known PID constants.
2//!
3//! Specified by ITU-T H.222.0 §2.4 (= ISO/IEC 13818-1). DVB-specific
4//! well-known PID assignments are also listed in ETSI EN 300 468 §5.1.3 Table 1.
5
6/// A 13-bit MPEG-TS Packet Identifier.
7///
8/// Thin newtype over the wire `u16`. Values are masked to 13 bits on
9/// construction (`0x0000..=0x1FFF`), matching the transport header field
10/// width (ISO/IEC 13818-1 §2.4.3.2).
11#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize))]
13#[cfg_attr(feature = "serde", serde(transparent))]
14pub struct Pid(u16);
15
16impl Pid {
17 /// Mask covering the 13 valid PID bits.
18 pub const MASK: u16 = 0x1FFF;
19
20 /// Construct a `Pid`, masking to the low 13 bits.
21 #[must_use]
22 pub const fn new(value: u16) -> Self {
23 Self(value & Self::MASK)
24 }
25
26 /// Construct a `Pid` from the low 13 bits of `value`, **deliberately**
27 /// discarding the rest: the explicit name for extracting the 13-bit PID
28 /// field out of a wider wire word (e.g. `reserved(3)` + `PID(13)`), where
29 /// masking is the specified behaviour. Identical to [`Pid::new`]; prefer
30 /// this at parse sites so the intent is visible and a caller-supplied
31 /// value is never masked by accident (use [`Pid::try_new`] for those).
32 ///
33 /// ```
34 /// use mpeg_ts::pid::Pid;
35 /// // reserved(3) = 0b111 followed by PID 0x0100.
36 /// assert_eq!(Pid::from_wire_masked(0xE100).value(), 0x0100);
37 /// assert_eq!(Pid::try_new(0xE100).unwrap_err().value, 0xE100);
38 /// ```
39 #[must_use]
40 pub const fn from_wire_masked(value: u16) -> Self {
41 Self(value & Self::MASK)
42 }
43
44 /// Construct a `Pid`, rejecting any value above [`Pid::MASK`] (`0x1FFF`).
45 ///
46 /// Use this at API boundaries that take a caller-supplied value; use
47 /// [`Pid::new`] only where the bits are already masked by the wire format.
48 ///
49 /// # Errors
50 /// [`PidOutOfRange`] when `value > 0x1FFF`.
51 pub const fn try_new(value: u16) -> Result<Self, PidOutOfRange> {
52 if value > Self::MASK {
53 Err(PidOutOfRange { value })
54 } else {
55 Ok(Self(value))
56 }
57 }
58
59 /// The raw 13-bit value.
60 #[must_use]
61 pub const fn value(self) -> u16 {
62 self.0
63 }
64}
65
66/// A value that does not fit the 13-bit PID field (ISO/IEC 13818-1 §2.4.3.2).
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68#[non_exhaustive]
69pub struct PidOutOfRange {
70 /// The rejected value.
71 pub value: u16,
72}
73
74impl core::fmt::Display for PidOutOfRange {
75 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
76 write!(
77 f,
78 "PID 0x{:04X} exceeds the 13-bit range 0x0000..=0x1FFF",
79 self.value
80 )
81 }
82}
83
84impl core::error::Error for PidOutOfRange {}
85
86impl TryFrom<u16> for Pid {
87 type Error = PidOutOfRange;
88 fn try_from(value: u16) -> Result<Self, Self::Error> {
89 Self::try_new(value)
90 }
91}
92
93impl From<Pid> for u16 {
94 fn from(pid: Pid) -> Self {
95 pid.0
96 }
97}
98
99impl core::fmt::Debug for Pid {
100 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
101 write!(f, "Pid(0x{:04X})", self.0)
102 }
103}
104
105impl core::fmt::UpperHex for Pid {
106 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
107 core::fmt::UpperHex::fmt(&self.0, f)
108 }
109}
110
111impl core::fmt::LowerHex for Pid {
112 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
113 core::fmt::LowerHex::fmt(&self.0, f)
114 }
115}
116
117impl core::fmt::Display for Pid {
118 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
119 write!(f, "0x{:04X}", self.0)
120 }
121}
122
123/// Well-known PIDs. The transport stream MUST carry the corresponding tables
124/// on these PIDs.
125///
126/// **API 2.0 type change:** constants are now [`crate::pid::Pid`] instead of `u16`.
127/// Call `.value()` to obtain the raw `u16`, or use `Pid::from(u16)` /
128/// `u16::from(Pid)` for conversions.
129pub mod well_known {
130 use super::Pid;
131
132 /// Program Association Table (MPEG-2).
133 pub const PAT: Pid = Pid::new(0x0000);
134 /// Conditional Access Table (MPEG-2).
135 pub const CAT: Pid = Pid::new(0x0001);
136 /// Transport Stream Description Table (MPEG-2).
137 pub const TSDT: Pid = Pid::new(0x0002);
138 /// IPMP Control Information Table (MPEG-2).
139 pub const IPMP_CIT: Pid = Pid::new(0x0003);
140 /// Network Information Table (DVB).
141 pub const NIT: Pid = Pid::new(0x0010);
142 /// Service Description Table + Bouquet Association Table (DVB).
143 pub const SDT_BAT: Pid = Pid::new(0x0011);
144 /// Event Information Table (DVB).
145 pub const EIT: Pid = Pid::new(0x0012);
146 /// Running Status Table (DVB).
147 pub const RST: Pid = Pid::new(0x0013);
148 /// Time and Date + Time Offset + Stuffing (DVB).
149 pub const TDT_TOT: Pid = Pid::new(0x0014);
150 /// Network synchronisation.
151 pub const NETWORK_SYNC: Pid = Pid::new(0x0015);
152 /// Resolution Notification Table.
153 pub const RNT: Pid = Pid::new(0x0016);
154 /// Satellite Access Table (EN 300 468 Table 1).
155 pub const SAT: Pid = Pid::new(0x001B);
156 /// Link-local inband signalling (EN 300 468 Table 1); only for use between
157 /// devices in a controlled environment, never on a broadcast signal.
158 pub const INBAND_SIGNALLING: Pid = Pid::new(0x001C);
159 /// Measurement (reserved).
160 pub const MEASUREMENT: Pid = Pid::new(0x001D);
161 /// Discontinuity Information Table.
162 pub const DIT: Pid = Pid::new(0x001E);
163 /// Selection Information Table.
164 pub const SIT: Pid = Pid::new(0x001F);
165
166 /// ATSC PSIP base PID.
167 pub const ATSC_PSIP: Pid = Pid::new(0x1FFB);
168
169 /// Null-packet padding PID. Payload is ignored.
170 pub const NULL: Pid = Pid::new(0x1FFF);
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn inband_signalling_sits_between_sat_and_measurement() {
179 assert_eq!(well_known::INBAND_SIGNALLING.value(), 0x001C);
180 assert_eq!(
181 well_known::INBAND_SIGNALLING.value(),
182 well_known::SAT.value() + 1
183 );
184 assert_eq!(
185 well_known::MEASUREMENT.value(),
186 well_known::INBAND_SIGNALLING.value() + 1
187 );
188 }
189}