o_sfu_rfc/rtp.rs
1//! RFC references covered:
2//! - RTP base protocol: <https://www.rfc-editor.org/rfc/rfc3550>
3//! - RTP A/V profile payload assignments: <https://www.rfc-editor.org/rfc/rfc3551>
4//! - RTP header extension framework: <https://www.rfc-editor.org/rfc/rfc8285>
5//! - RTCP feedback profile: <https://www.rfc-editor.org/rfc/rfc4585>
6//! - RTP retransmission payload format: <https://www.rfc-editor.org/rfc/rfc4588>
7//! - RTP stream identifier SDES items: <https://www.rfc-editor.org/rfc/rfc8852>
8//! - Video frame marking RTP header extension: <https://www.rfc-editor.org/rfc/rfc9626>
9//! - Layer Refresh Request feedback: <https://www.rfc-editor.org/rfc/rfc9627>
10//! - RTP payload format for VP8: <https://www.rfc-editor.org/rfc/rfc7741>
11//! - RTP payload format for H264: <https://www.rfc-editor.org/rfc/rfc6184>
12//!
13//! A complete RTP packet has this outer shape:
14//!
15//! ```text
16//! 0 1 2 3
17//! 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
18//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
19//! |V=2|P|X| CC |M| PT | sequence number |
20//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
21//! | timestamp |
22//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
23//! | SSRC |
24//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
25//! | CSRC list ... |
26//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
27//! | extension block if X=1 ... |
28//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
29//! | codec payload ... |
30//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
31//! ```
32//!
33//! Generic NACK is RTCP feedback rather than a field in an RTP packet. One
34//! RTPFB packet can carry multiple `PID`/`BLP` entries:
35//!
36//! ```text
37//! 0 1 2 3
38//! 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
39//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40//! |V=2|P| FMT=1 | PT=205 | length |
41//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
42//! | SSRC of feedback sender |
43//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
44//! | SSRC of primary media |
45//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
46//! | PID | BLP |
47//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
48//! | more PID/BLP entries ... |
49//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
50//! ```
51//!
52//! `PID` names one missing primary RTP sequence number. Zero-based BLP bit `i`
53//! reports `(PID + i + 1) mod 2^16` as missing. See
54//! [RFC 4585 section 6.1](https://www.rfc-editor.org/rfc/rfc4585.html#section-6.1)
55//! and
56//! [RFC 4585 section 6.2.1](https://www.rfc-editor.org/rfc/rfc4585.html#section-6.2.1).
57//!
58//! RTX is a new RTP packet in the repair stream. Its RTP header has the repair
59//! payload type, repair SSRC and an independent repair sequence number. The
60//! payload begins with the original sequence number:
61//!
62//! ```text
63//! 0 1 2 3
64//! 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
65//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
66//! |V=2|P|X| CC |M| RTX PT | repair sequence number |
67//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
68//! | original RTP timestamp |
69//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
70//! | repair SSRC |
71//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
72//! | optional CSRC list and RTP header extension ... |
73//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
74//! | OSN | |
75//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
76//! | original RTP packet payload ... |
77//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
78//! ```
79//!
80//! `apt` binds the RTX payload type to the primary payload type in SDP. It is
81//! not carried in either packet. See
82//! [RFC 4588 section 4](https://www.rfc-editor.org/rfc/rfc4588.html#section-4)
83//! and
84//! [RFC 4588 section 8.1](https://www.rfc-editor.org/rfc/rfc4588.html#section-8.1).
85
86use std::{fmt, iter, mem};
87
88use crate::webrtc::sdp;
89
90/// RTP version defined by RFC 3550 section 5.1.
91pub const RTP_VERSION: u8 = 2;
92
93/// RTP version bits in the first octet of RTP and RTCP packets.
94///
95/// References:
96/// - <https://www.rfc-editor.org/rfc/rfc3550.html#section-5.1>
97/// - <https://www.rfc-editor.org/rfc/rfc3550.html#section-6.4.1>
98const RTP_VERSION_HEADER_BITS: u8 = RTP_VERSION << RTP_VERSION_SHIFT;
99
100/// Fixed RTP header length in bytes with no CSRC and no extension.
101///
102/// Reference: RFC 3550 section 5.1.
103pub const RTP_FIXED_HEADER_BYTES: usize = 12;
104
105/// Number of values representable by the 7-bit RTP payload type field.
106///
107/// Reference: <https://www.rfc-editor.org/rfc/rfc3550.html#section-5.1>
108pub const RTP_PAYLOAD_TYPE_COUNT: usize = 1 << RTP_PAYLOAD_TYPE_BITS;
109
110/// Maximum number of CSRC identifiers in the RTP fixed header.
111///
112/// The CC field is 4 bits, so the valid range is 0..=15.
113/// Reference: RFC 3550 section 5.1.
114pub const RTP_MAX_CSRC_COUNT: u8 = 15;
115
116/// Sequence number rollover modulus for the 16-bit RTP sequence number.
117///
118/// Reference: RFC 3550 section 5.1.
119pub const RTP_SEQUENCE_NUMBER_MODULUS: u32 = 1_u32 << 16;
120
121/// Timestamp rollover modulus for the 32-bit RTP timestamp field.
122///
123/// Reference: RFC 3550 section 5.1.
124pub const RTP_TIMESTAMP_MODULUS: u64 = 1_u64 << 32;
125
126/// Maximum value representable by the 7-bit RTP payload type field.
127///
128/// Reference: RFC 3550 section 5.1.
129pub const RTP_PAYLOAD_TYPE_MAX: u8 = 127;
130
131/// Dynamic RTP payload type range in RTP/AVP.
132///
133/// Reference: RFC 3551 section 6.
134pub const RTP_DYNAMIC_PAYLOAD_TYPE_START: u8 = 96;
135pub const RTP_DYNAMIC_PAYLOAD_TYPE_END: u8 = 127;
136
137/// Payload type range disallowed when RTP and RTCP share one port.
138///
139/// RFC 5761 reserves the full 64 through 95 payload type range for muxed
140/// sessions so the second packet octet can unambiguously distinguish RTP from
141/// RTCP.
142///
143/// Reference: RFC 5761 section 4.
144pub const RTP_RTCP_MUX_FORBIDDEN_PAYLOAD_TYPE_START: u8 = 64;
145pub const RTP_RTCP_MUX_FORBIDDEN_PAYLOAD_TYPE_END: u8 = 95;
146
147const RTP_VERSION_SHIFT: u32 = 6;
148const RTP_VERSION_MASK: u8 = 0b1100_0000;
149const RTP_PAYLOAD_TYPE_BITS: u32 = 7;
150const RTP_PAYLOAD_TYPE_MASK: u8 = RTP_PAYLOAD_TYPE_MAX;
151const RTP_PADDING_MASK: u8 = 0b0010_0000;
152const RTP_EXTENSION_MASK: u8 = 0b0001_0000;
153const RTP_CSRC_COUNT_MASK: u8 = 0b0000_1111;
154const RTP_MARKER_MASK: u8 = 0b1000_0000;
155const RTP_SEQUENCE_NUMBER_OFFSET: usize = 2;
156const RTP_TIMESTAMP_OFFSET: usize = 4;
157const RTP_SSRC_OFFSET: usize = 8;
158const RTP_CSRC_BYTES: usize = 4;
159
160/// RTCP common-header length in bytes.
161///
162/// Reference: <https://www.rfc-editor.org/rfc/rfc3550.html#section-6.4.1>
163const RTCP_COMMON_HEADER_BYTES: usize = 4;
164
165/// SSRC field length in an RTCP packet.
166///
167/// Reference: <https://www.rfc-editor.org/rfc/rfc3550.html#section-6.4.1>
168const RTCP_SSRC_BYTES: usize = 4;
169
170/// Length of an RTCP Receiver Report with no report blocks.
171///
172/// Reference: <https://www.rfc-editor.org/rfc/rfc3550.html#section-6.4.2>
173pub const RTCP_RECEIVER_REPORT_WITHOUT_BLOCKS_BYTES: usize =
174 RTCP_COMMON_HEADER_BYTES + RTCP_SSRC_BYTES;
175
176const RTCP_RECEIVER_REPORT_WITHOUT_BLOCKS_LENGTH_WORDS_MINUS_ONE: u16 = 1;
177
178/// Number of octets prepended to an RTX payload for the original sequence
179/// number.
180///
181/// Reference: <https://www.rfc-editor.org/rfc/rfc4588.html#section-4>
182pub const RTX_ORIGINAL_SEQUENCE_NUMBER_BYTES: usize = 2;
183
184/// Number of sequence numbers represented by the Generic NACK BLP field after
185/// its PID.
186///
187/// Reference: <https://www.rfc-editor.org/rfc/rfc4585.html#section-6.2.1>
188const GENERIC_NACK_BITMASK_BITS: u16 = 16;
189
190/// Common static RTP/AVP payload type assignments.
191///
192/// Reference: RFC 3551 section 6, tables 4 and 5.
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194#[repr(u8)]
195pub enum AvpStaticPayloadType {
196 Pcmu = 0,
197 Gsm = 3,
198 G723 = 4,
199 Dvi4_8000 = 5,
200 Dvi4_16000 = 6,
201 Lpc = 7,
202 Pcma = 8,
203 G722 = 9,
204 L16Stereo = 10,
205 L16Mono = 11,
206 Qcelp = 12,
207 ComfortNoise = 13,
208 Mpa = 14,
209 G728 = 15,
210 Dvi4_11025 = 16,
211 Dvi4_22050 = 17,
212 G729 = 18,
213 Celb = 25,
214 Jpeg = 26,
215 Nv = 28,
216 H261 = 31,
217 Mpv = 32,
218 Mp2t = 33,
219 H263 = 34,
220}
221
222impl AvpStaticPayloadType {
223 #[must_use]
224 #[expect(
225 clippy::as_conversions,
226 reason = "repr(u8) guarantees safe identity cast"
227 )]
228 pub const fn as_u8(self) -> u8 {
229 self as u8
230 }
231}
232
233/// 7-bit RTP payload type value usable in RTP/RTCP muxed sessions
234#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
235pub struct PayloadType(u8);
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238pub struct InvalidPayloadType;
239
240impl PayloadType {
241 #[must_use]
242 pub const fn try_new(value: u8) -> Option<Self> {
243 if is_rtcp_mux_payload_type(value) {
244 Some(Self(value))
245 } else {
246 None
247 }
248 }
249
250 /// builds a payload type for the muxed RTP sessions used by `o-sfu`
251 ///
252 /// # Panics
253 ///
254 /// panics when `value` does not fit the RTP payload type field or is in the
255 /// RTP/RTCP mux forbidden range from RFC 5761 section 4
256 #[must_use]
257 pub const fn new(value: u8) -> Self {
258 assert!(is_rtcp_mux_payload_type(value));
259 Self(value)
260 }
261
262 #[must_use]
263 pub const fn value(self) -> u8 {
264 self.0
265 }
266}
267
268impl TryFrom<u8> for PayloadType {
269 type Error = InvalidPayloadType;
270
271 fn try_from(value: u8) -> Result<Self, Self::Error> {
272 Self::try_new(value).ok_or(InvalidPayloadType)
273 }
274}
275
276impl From<PayloadType> for u8 {
277 fn from(value: PayloadType) -> Self {
278 value.value()
279 }
280}
281
282/// RTP or RTCP candidate selected by the RFC 5761 second-octet split.
283///
284/// The result identifies only the mux candidate kind. It does not validate the
285/// complete packet body.
286///
287/// Reference: <https://www.rfc-editor.org/rfc/rfc5761.html#section-4>
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub enum RtpRtcpMuxPacketKind {
290 Rtp,
291 Rtcp,
292}
293
294/// Parsed fields from an RTP fixed header in an RTP/RTCP muxed session.
295///
296/// Reference: <https://www.rfc-editor.org/rfc/rfc3550.html#section-5.1>
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub struct RtpFixedHeader {
299 payload_type: u8,
300 sequence_number: u16,
301 timestamp: u32,
302 ssrc: Ssrc,
303 marker: bool,
304 has_padding: bool,
305 has_extension: bool,
306 csrc_count: u8,
307}
308
309impl RtpFixedHeader {
310 #[must_use]
311 pub const fn payload_type(self) -> u8 {
312 self.payload_type
313 }
314
315 #[must_use]
316 pub const fn sequence_number(self) -> u16 {
317 self.sequence_number
318 }
319
320 #[must_use]
321 pub const fn timestamp(self) -> u32 {
322 self.timestamp
323 }
324
325 #[must_use]
326 pub const fn ssrc(self) -> Ssrc {
327 self.ssrc
328 }
329
330 #[must_use]
331 pub const fn marker(self) -> bool {
332 self.marker
333 }
334
335 #[must_use]
336 pub const fn has_padding(self) -> bool {
337 self.has_padding
338 }
339
340 #[must_use]
341 const fn has_extension(self) -> bool {
342 self.has_extension
343 }
344
345 fn extension_offset(self) -> usize {
346 RTP_FIXED_HEADER_BYTES + usize::from(self.csrc_count) * RTP_CSRC_BYTES
347 }
348}
349
350/// Synchronization source identifier for a media stream (RFC 3550).
351///
352/// Every distinct stream of packets (e.g. one audio track, one camera layer)
353/// is assigned a random 32-bit SSRC. This allows multiple streams to be
354/// multiplexed over a single transport (e.g. one UDP port).
355#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
356pub struct Ssrc(u32);
357
358impl Ssrc {
359 #[must_use]
360 pub const fn new(value: u32) -> Self {
361 Self(value)
362 }
363
364 #[must_use]
365 pub const fn value(self) -> u32 {
366 self.0
367 }
368}
369
370impl From<u32> for Ssrc {
371 fn from(value: u32) -> Self {
372 Self::new(value)
373 }
374}
375
376impl From<Ssrc> for u32 {
377 fn from(value: Ssrc) -> Self {
378 value.value()
379 }
380}
381
382/// Restriction identifier (RFC 8851).
383///
384/// Used in "Simulcast" to label different encodings of the same source (e.g.
385/// "low" and "high" resolution). Unlike SSRC which is a random number that
386/// can change if a collision occurs, RID is a stable string label.
387#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
388pub struct Rid(String);
389
390impl Rid {
391 #[must_use]
392 pub fn try_new(value: impl Into<String>) -> Option<Self> {
393 let value = value.into();
394 sdp::rid::is_id(value.as_str()).then_some(Self(value))
395 }
396
397 /// Builds a RID using the RFC 8851 `rid-id` grammar corrected by RFC Editor errata 7132.
398 ///
399 /// # Panics
400 ///
401 /// Panics when `value` is empty, too long or contains a non-alphanumeric
402 /// byte.
403 #[must_use]
404 pub fn new(value: impl Into<String>) -> Self {
405 let value = value.into();
406 assert!(sdp::rid::is_id(value.as_str()));
407 Self(value)
408 }
409
410 #[must_use]
411 pub fn as_str(&self) -> &str {
412 self.0.as_str()
413 }
414}
415
416impl From<&str> for Rid {
417 fn from(value: &str) -> Self {
418 Self::new(value)
419 }
420}
421
422impl From<String> for Rid {
423 fn from(value: String) -> Self {
424 Self::new(value)
425 }
426}
427
428/// Media identification (RFC 9143).
429///
430/// Ties an RTP stream to a specific "m=" section in the SDP. This is
431/// critical for "BUNDLE" where multiple media sections share one transport,
432/// as it provides a stable way to route packets to the correct logical track.
433#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
434pub struct Mid(String);
435
436impl Mid {
437 #[must_use]
438 pub fn new(value: impl Into<String>) -> Self {
439 Self(value.into())
440 }
441
442 #[must_use]
443 pub fn as_str(&self) -> &str {
444 self.0.as_str()
445 }
446}
447
448impl From<&str> for Mid {
449 fn from(value: &str) -> Self {
450 Self::new(value)
451 }
452}
453
454impl From<String> for Mid {
455 fn from(value: String) -> Self {
456 Self::new(value)
457 }
458}
459
460/// Local 4-bit identifier for a header extension (RFC 8285).
461#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
462pub struct HeaderExtensionId(u8);
463
464impl HeaderExtensionId {
465 #[must_use]
466 pub const fn try_new(value: u8) -> Option<Self> {
467 if header_extension::is_one_byte_id(value) {
468 Some(Self(value))
469 } else {
470 None
471 }
472 }
473
474 /// Builds a one-byte RTP header-extension id.
475 ///
476 /// # Panics
477 ///
478 /// Panics when `value` is padding, reserved or outside the RFC 8285
479 /// one-byte element id range.
480 #[must_use]
481 pub const fn new(value: u8) -> Self {
482 assert!(header_extension::is_one_byte_id(value));
483 Self(value)
484 }
485
486 #[must_use]
487 pub const fn value(self) -> u8 {
488 self.0
489 }
490}
491
492impl From<u8> for HeaderExtensionId {
493 fn from(value: u8) -> Self {
494 Self::new(value)
495 }
496}
497
498impl From<HeaderExtensionId> for u8 {
499 fn from(value: HeaderExtensionId) -> Self {
500 value.value()
501 }
502}
503
504/// RTP clock rate used by the supported video payload formats
505///
506/// Reference: IANA RTP Payload Format Media Types registry
507pub const RTP_VIDEO_CLOCK_RATE_HZ: u32 = 90_000;
508
509/// RTP payload-format MIME subtype names commonly used by WebRTC endpoints.
510pub mod codec_name {
511 /// PCMU RTP payload-format subtype.
512 ///
513 /// Reference: RFC 3551.
514 pub const PCMU: &str = "PCMU";
515
516 /// PCMA RTP payload-format subtype.
517 ///
518 /// Reference: RFC 3551.
519 pub const PCMA: &str = "PCMA";
520
521 /// Opus RTP payload-format subtype.
522 ///
523 /// Reference: RFC 7587.
524 pub const OPUS: &str = "opus";
525
526 /// VP8 RTP payload-format subtype.
527 ///
528 /// Reference: RFC 7741.
529 pub const VP8: &str = "VP8";
530
531 /// H264 RTP payload-format subtype.
532 ///
533 /// Reference: RFC 6184.
534 pub const H264: &str = "H264";
535
536 /// H265 RTP payload-format subtype.
537 ///
538 /// Reference: RFC 7798.
539 pub const H265: &str = "H265";
540
541 /// VP9 RTP payload-format subtype.
542 ///
543 /// Reference: RFC 9628.
544 pub const VP9: &str = "VP9";
545
546 /// AV1 RTP payload-format subtype.
547 ///
548 /// Reference: IANA Media Types registry.
549 pub const AV1: &str = "AV1";
550
551 /// RTX retransmission RTP payload-format subtype.
552 ///
553 /// Reference: RFC 4588.
554 pub const RTX: &str = "rtx";
555}
556
557/// G.711 RTP payload-format constants
558pub mod g711 {
559 /// RTP clock rate for the static PCMU and PCMA RTP/AVP payload types
560 ///
561 /// Reference: RFC 3551 section 6
562 pub const RTP_CLOCK_RATE_HZ: u32 = 8_000;
563}
564
565/// Opus RTP payload-format constants.
566///
567/// Reference: RFC 7587 for the RTP payload format and RFC 6716 for the
568/// packet table-of-contents layout.
569pub mod opus {
570 /// RTP clock rate for Opus payloads.
571 ///
572 /// Reference: RFC 7587 section 4.1.
573 pub const RTP_CLOCK_RATE_HZ: u32 = 48_000;
574
575 /// Opus channel count signaled in SDP `rtpmap` attributes.
576 ///
577 /// Reference: RFC 7587 section 7.
578 pub const RTPMAP_CHANNEL_COUNT: u16 = 2;
579
580 /// Opus packet frame-count codes from the table-of-contents byte.
581 ///
582 /// Reference: RFC 6716 section 3.1.
583 pub mod frame_count_code {
584 /// One frame in the Opus packet.
585 pub const ONE_FRAME: u8 = 0;
586 }
587
588 /// Opus table-of-contents configuration numbers.
589 ///
590 /// Reference: RFC 6716 section 3.1.
591 pub mod toc_config {
592 /// SILK-only wideband packet with a 20 ms frame duration.
593 pub const SILK_WIDEBAND_20_MS: u8 = 9;
594 }
595}
596
597/// VP8 RTP payload helpers.
598///
599/// These helpers operate on the codec payload slice, not on the full RTP
600/// packet. In runtime forwarding that slice starts after the RTP fixed header,
601/// CSRC list and any RTP header-extension block.
602///
603/// RFC 7741 puts a VP8 payload descriptor in front of the VP8 payload header.
604/// The descriptor is also where simulcast and temporal-layer packet identity is
605/// carried. o-sfu keeps the RFC constants here, while the runtime uses str0m's
606/// descriptor parser and patch support for local egress.
607///
608/// ```text
609/// VP8 payload slice passed to this module
610///
611/// +------------------+----------------------+----------------------+
612/// | descriptor byte | extension bytes | VP8 payload header |
613/// +------------------+----------------------+----------------------+
614/// | X R N S PartID | I L T K fields | P bit and VP8 data |
615/// +------------------+----------------------+----------------------+
616///
617/// Extension bytes when X=1
618///
619/// +-------------+-------------------+-------------+----------------+
620/// | I L T K ... | PictureID if I=1 | TL0 if L=1 | T/K if present |
621/// +-------------+-------------------+-------------+----------------+
622/// | | 1 or 2 bytes | 1 byte | 1 byte |
623/// +-------------+-------------------+-------------+----------------+
624/// ```
625pub mod vp8 {
626 /// Extended control bits are present in the VP8 payload descriptor.
627 pub const X_BIT: u8 = 0b1000_0000;
628
629 /// Start of VP8 partition bit in the payload descriptor.
630 pub const S_BIT: u8 = 0b0001_0000;
631
632 const PARTITION_ID_MASK: u8 = 0b0000_0111;
633
634 /// `PictureID` present bit in the extended VP8 payload descriptor.
635 pub const I_BIT: u8 = 0b1000_0000;
636
637 /// TL0PICIDX present bit in the extended VP8 payload descriptor.
638 pub const L_BIT: u8 = 0b0100_0000;
639
640 /// TID/Y/KEYIDX present bit in the extended VP8 payload descriptor.
641 pub const T_BIT: u8 = 0b0010_0000;
642
643 const K_BIT: u8 = 0b0001_0000;
644
645 /// Long `PictureID` marker bit in the VP8 `PictureID` field.
646 pub const LONG_PICTURE_ID_BIT: u8 = 0b1000_0000;
647
648 /// VP8 payload-header P bit set for interframes.
649 pub const INTERFRAME_BIT: u8 = 0b0000_0001;
650
651 const VERSION_MASK: u8 = 0b0000_1110;
652 const VERSION_SHIFT: u32 = 1;
653 const MAX_DEFINED_VERSION: u8 = 3;
654 const DIMENSION_MASK: u16 = 0x3fff;
655 const KEYFRAME_SYNC_CODE: [u8; 3] = [0x9d, 0x01, 0x2a];
656
657 /// value mask for the 7-bit VP8 short `PictureID` field
658 pub const SHORT_PICTURE_ID_MASK: u16 = 0x7f;
659
660 /// Value mask for the 15-bit VP8 long `PictureID` field.
661 pub const LONG_PICTURE_ID_MASK: u16 = 0x7fff;
662
663 /// Modulus for the 15-bit VP8 long `PictureID` field.
664 pub const LONG_PICTURE_ID_MODULUS: u16 = 1 << 15;
665
666 /// Value mask for the two-bit VP8 temporal-layer identity.
667 pub const TEMPORAL_LAYER_ID_MASK: u8 = 0b0000_0011;
668
669 /// Detects a complete VP8 keyframe prefix in the first RTP packet.
670 ///
671 /// RFC 7741 section 4.2 defines the VP8 payload descriptor. A decodable
672 /// keyframe starts at partition 0 (`S=1`, `PartID=0`) and the VP8 payload
673 /// header starts with `P=0`. RFC 6386 section 9.1 defines the keyframe sync
674 /// code and dimensions. The input must be the RTP codec payload.
675 ///
676 /// Truncated descriptors, missing payload headers and non-start partitions
677 /// return `false`. The helper performs only the cheap
678 /// keyframe probe needed by packet gates and decoder-refresh detection.
679 #[must_use]
680 pub fn payload_starts_keyframe(payload: &[u8]) -> bool {
681 let Some((&descriptor, rest)) = payload.split_first() else {
682 return false;
683 };
684 if descriptor & S_BIT == 0 || descriptor & PARTITION_ID_MASK != 0 {
685 return false;
686 }
687 let frame = if descriptor & X_BIT == 0 {
688 rest
689 } else {
690 let Some(frame) = extended_frame_payload(rest) else {
691 return false;
692 };
693 frame
694 };
695 valid_keyframe_prefix(frame)
696 }
697
698 /// Locates the VP8 frame bytes after an extended descriptor.
699 ///
700 /// `None` means the extension-bit combination is malformed or an advertised
701 /// field is absent. The keyframe probe treats that as "not a keyframe".
702 fn extended_frame_payload(payload: &[u8]) -> Option<&[u8]> {
703 let (&extension, mut rest) = payload.split_first()?;
704 if extension & I_BIT != 0 {
705 let (&picture_id, remaining) = rest.split_first()?;
706 rest = if picture_id & LONG_PICTURE_ID_BIT != 0 {
707 remaining.get(1..)?
708 } else {
709 remaining
710 };
711 }
712 if extension & L_BIT != 0 {
713 if extension & T_BIT == 0 {
714 return None;
715 }
716 rest = rest.get(1..)?;
717 }
718 if extension & T_BIT != 0 || extension & K_BIT != 0 {
719 rest = rest.get(1..)?;
720 }
721 (!rest.is_empty()).then_some(rest)
722 }
723
724 fn valid_keyframe_prefix(frame: &[u8]) -> bool {
725 let &[
726 tag,
727 _,
728 _,
729 sync_0,
730 sync_1,
731 sync_2,
732 width_low,
733 width_high,
734 height_low,
735 height_high,
736 ..,
737 ] = frame
738 else {
739 return false;
740 };
741 let version = (tag & VERSION_MASK) >> VERSION_SHIFT;
742 let width = u16::from_le_bytes([width_low, width_high]) & DIMENSION_MASK;
743 let height = u16::from_le_bytes([height_low, height_high]) & DIMENSION_MASK;
744 tag & INTERFRAME_BIT == 0
745 && version <= MAX_DEFINED_VERSION
746 && [sync_0, sync_1, sync_2] == KEYFRAME_SYNC_CODE
747 && width != 0
748 && height != 0
749 }
750}
751
752/// VP9 profile identifier defined by RFC 9628 section 6
753#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
754pub struct Vp9ProfileId(u8);
755
756impl Vp9ProfileId {
757 /// VP9 Profile 0
758 pub const PROFILE_0: Self = Self(0);
759
760 /// VP9 Profile 1
761 pub const PROFILE_1: Self = Self(1);
762
763 /// VP9 Profile 2
764 pub const PROFILE_2: Self = Self(2);
765
766 /// VP9 Profile 3
767 pub const PROFILE_3: Self = Self(3);
768
769 #[must_use]
770 pub const fn try_new(value: u8) -> Option<Self> {
771 if value <= Self::PROFILE_3.value() {
772 Some(Self(value))
773 } else {
774 None
775 }
776 }
777
778 #[must_use]
779 pub const fn value(self) -> u8 {
780 self.0
781 }
782}
783
784/// RTP payload-format MIME subtype names commonly used by WebRTC endpoints.
785#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
786pub enum CodecName {
787 Pcmu,
788 Pcma,
789 Opus,
790 Vp8,
791 H264,
792 H265,
793 Vp9,
794 Av1,
795 Rtx,
796 Other(String),
797}
798
799impl CodecName {
800 #[must_use]
801 pub fn as_str(&self) -> &str {
802 match self {
803 Self::Pcmu => codec_name::PCMU,
804 Self::Pcma => codec_name::PCMA,
805 Self::Opus => codec_name::OPUS,
806 Self::Vp8 => codec_name::VP8,
807 Self::H264 => codec_name::H264,
808 Self::H265 => codec_name::H265,
809 Self::Vp9 => codec_name::VP9,
810 Self::Av1 => codec_name::AV1,
811 Self::Rtx => codec_name::RTX,
812 Self::Other(name) => name.as_str(),
813 }
814 }
815
816 #[must_use]
817 pub fn is_rtx(&self) -> bool {
818 matches!(self, Self::Rtx)
819 }
820}
821
822impl From<&str> for CodecName {
823 fn from(value: &str) -> Self {
824 match value {
825 s if s.eq_ignore_ascii_case(codec_name::PCMU) => Self::Pcmu,
826 s if s.eq_ignore_ascii_case(codec_name::PCMA) => Self::Pcma,
827 s if s.eq_ignore_ascii_case(codec_name::OPUS) => Self::Opus,
828 s if s.eq_ignore_ascii_case(codec_name::VP8) => Self::Vp8,
829 s if s.eq_ignore_ascii_case(codec_name::H264) => Self::H264,
830 s if s.eq_ignore_ascii_case(codec_name::H265) => Self::H265,
831 s if s.eq_ignore_ascii_case(codec_name::VP9) => Self::Vp9,
832 s if s.eq_ignore_ascii_case(codec_name::AV1) => Self::Av1,
833 s if s.eq_ignore_ascii_case(codec_name::RTX) => Self::Rtx,
834 _ => Self::Other(value.to_owned()),
835 }
836 }
837}
838
839impl From<String> for CodecName {
840 fn from(value: String) -> Self {
841 Self::from(value.as_str())
842 }
843}
844
845impl AsRef<str> for CodecName {
846 fn as_ref(&self) -> &str {
847 self.as_str()
848 }
849}
850
851impl fmt::Display for CodecName {
852 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
853 f.write_str(self.as_str())
854 }
855}
856
857/// Codec `fmtp` parameter names and canonical valuse
858pub mod fmtp {
859 /// Separator between format-specific parameters used by RTX `fmtp`.
860 ///
861 /// Reference: <https://www.rfc-editor.org/rfc/rfc4588.html#section-8.6>
862 pub const PARAMETER_SEPARATOR: char = ';';
863
864 /// Separator between an RTX `fmtp` parameter name and value.
865 ///
866 /// Reference: <https://www.rfc-editor.org/rfc/rfc4588.html#section-8.6>
867 pub const NAME_VALUE_SEPARATOR: char = '=';
868
869 /// RTX associated payload type parameter
870 ///
871 /// Reference: <https://www.rfc-editor.org/rfc/rfc4588.html#section-8.1>
872 pub const RTX_ASSOCIATION: &str = "apt";
873
874 /// H264 packetization mode parameter.
875 ///
876 /// Reference: RFC 6184 section 8.1.
877 pub const H264_PACKETIZATION_MODE: &str = "packetization-mode";
878
879 /// H264 profile-level-id parameter.
880 ///
881 /// Reference: RFC 6184 section 8.1
882 pub const H264_PROFILE_LEVEL_ID: &str = "profile-level-id";
883
884 /// Default H264 profile-level-id when the parameter is omitted.
885 ///
886 /// Reference: RFC 6184 section 8.1.
887 pub const H264_DEFAULT_PROFILE_LEVEL_ID: &str = "42000a";
888
889 /// Default H264 packetization mode when the parameter is omitted.
890 ///
891 /// Reference: RFC 6184 section 6.2.
892 pub const H264_DEFAULT_PACKETIZATION_MODE: u8 = 0;
893
894 /// VP9 profile-id parameter.
895 ///
896 /// Reference: RFC 9628 section 6.
897 pub const VP9_PROFILE_ID: &str = "profile-id";
898
899 /// Default VP9 profile when `profile-id` is omitted.
900 ///
901 /// Reference: RFC 9628 section 6.
902 pub const VP9_DEFAULT_PROFILE_ID: super::Vp9ProfileId = super::Vp9ProfileId::PROFILE_0;
903
904 /// Opus in-band FEC parameter
905 ///
906 /// Reference: RFC 7587 section 6.1.
907 pub const OPUS_USE_IN_BAND_FEC: &str = "useinbandfec";
908
909 /// Canonical numeric enabled flag used in WebRTC `fmtp` dictionaries.
910 pub const VALUE_ENABLED: &str = "1";
911
912 /// Canonical numeric disabled flag used in WebRTC `fmtp` dictionaries
913 pub const VALUE_DISABLED: &str = "0";
914
915 /// Textual enabled flag accepted by current ORTC/WebRTC capability payloads
916 pub const VALUE_TRUE: &str = "true";
917
918 /// Textual disabled flag accepted by current ORTC/WebRTC capability payloads
919 pub const VALUE_FALSE: &str = "false";
920}
921
922/// H264 SDP and payload-format helpers derived from RFC 6184.
923///
924/// Packet helpers in this module receive the H264 RTP payload slice. The first
925/// byte of that slice identifies the packetization mode used by the packet.
926/// Decoder-refresh detection needs to recognize IDR access units in all packet
927/// shapes that browsers commonly send.
928///
929/// ```text
930/// Single NAL unit packet
931///
932/// +-------------+----------------------+
933/// | NAL header | NAL payload |
934/// +-------------+----------------------+
935/// | F NRI Type | ... |
936/// +-------------+----------------------+
937///
938/// STAP-A aggregation packet
939///
940/// +---------------+----------+-------------+----------+-------------+
941/// | STAP-A header | NAL size | NAL bytes | NAL size | NAL bytes |
942/// +---------------+----------+-------------+----------+-------------+
943/// | Type=24 | 16 bits | starts with | 16 bits | starts with |
944/// | | | NAL header | | NAL header |
945/// +---------------+----------+-------------+----------+-------------+
946///
947/// FU-A fragmentation packet
948///
949/// +---------------+-------------+----------------------+
950/// | FU indicator | FU header | fragment payload |
951/// +---------------+-------------+----------------------+
952/// | Type=28 | S E R Type | first fragment if S=1 |
953/// +---------------+-------------+----------------------+
954/// ```
955///
956/// rfc 6184 maps `profile-level-id` to three bytes:
957///
958/// ```text
959/// profile_idc | profile-iop | level_idc
960///
961/// profile-iop bit layout:
962/// 7 6 5 4 3 2 1 0
963/// +---+---+---+---+---+---+---+---+
964/// |c0 |c1 |c2 |c3 |c4 |c5 | r | r |
965/// +---+---+---+---+---+---+---+---+
966///
967/// cN = constraint_setN_flag
968/// r = reserved_zero bit
969/// ```
970pub mod h264 {
971 /// Mask for the H264 NAL unit type field.
972 pub const NAL_UNIT_TYPE_MASK: u8 = 0x1f;
973
974 /// Highest NRI value for reference NAL units.
975 pub const NAL_REF_IDC_HIGH: u8 = 0b0110_0000;
976
977 /// H264 IDR slice NAL unit type.
978 pub const NAL_UNIT_TYPE_IDR: u8 = 5;
979
980 /// H264 STAP-A aggregation packet type.
981 pub const NAL_UNIT_TYPE_STAP_A: u8 = 24;
982
983 /// H264 FU-A fragmentation packet type.
984 pub const NAL_UNIT_TYPE_FU_A: u8 = 28;
985
986 /// FU-A start bit in the FU header.
987 pub const FU_START_BIT: u8 = 0x80;
988
989 /// FU-A end bit in the FU header.
990 pub const FU_END_BIT: u8 = 0x40;
991
992 /// H264 RTP packetization mode.
993 ///
994 /// Reference: RFC 6184 section 6.
995 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
996 pub enum PacketizationMode {
997 SingleNalUnit,
998 NonInterleaved,
999 Interleaved,
1000 }
1001
1002 impl PacketizationMode {
1003 #[must_use]
1004 pub const fn from_fmtp_value(value: u8) -> Option<Self> {
1005 match value {
1006 0 => Some(Self::SingleNalUnit),
1007 1 => Some(Self::NonInterleaved),
1008 2 => Some(Self::Interleaved),
1009 _ => None,
1010 }
1011 }
1012
1013 #[must_use]
1014 pub const fn fmtp_value(self) -> u8 {
1015 match self {
1016 Self::SingleNalUnit => 0,
1017 Self::NonInterleaved => 1,
1018 Self::Interleaved => 2,
1019 }
1020 }
1021
1022 const fn allows_aggregation_and_fragmentation(self) -> bool {
1023 matches!(self, Self::NonInterleaved)
1024 }
1025 }
1026
1027 /// parsed H264 `profile-level-id` capability
1028 ///
1029 /// `profile_idc` names the broad H264 profile family, `profile-iop`
1030 /// carries constraint bits that narrow that family to a sub-profile and
1031 /// `level_idc` carries the decoder capability level. [`ProfileLevelId`]
1032 /// normalizes equivalent RFC 6184 profile encodings before router
1033 /// negotiation compares [`Profile`] and [`LevelIdc`]
1034 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1035 pub struct ProfileLevelId {
1036 profile: Profile,
1037 level: LevelIdc,
1038 }
1039
1040 impl ProfileLevelId {
1041 #[must_use]
1042 pub const fn new(profile: Profile, level: LevelIdc) -> Self {
1043 Self { profile, level }
1044 }
1045
1046 /// Parse the RFC 6184 `profile-level-id` hex token.
1047 #[must_use]
1048 pub fn parse(value: &str) -> Option<Self> {
1049 Self::parse_ascii_bytes(value.as_bytes())
1050 }
1051
1052 /// Parse the six-byte ASCII hex form of a `profile-level-id` token.
1053 #[must_use]
1054 pub fn parse_ascii_bytes(value: &[u8]) -> Option<Self> {
1055 let [profile_idc, profile_iop, level_idc] = parse_profile_level_id_bytes(value)?;
1056 let level = normalized_level_idc(profile_idc, profile_iop, level_idc)?;
1057 let profile = profile_from_bytes(profile_idc, profile_iop)?;
1058 Some(Self { profile, level })
1059 }
1060
1061 #[must_use]
1062 pub const fn profile(self) -> Profile {
1063 self.profile
1064 }
1065
1066 #[must_use]
1067 pub const fn level(self) -> LevelIdc {
1068 self.level
1069 }
1070
1071 #[must_use]
1072 pub const fn packed_value(self) -> u32 {
1073 let (profile_idc, profile_iop, level_idc) =
1074 profile_level_id_bytes(self.profile, self.level);
1075 pack_profile_level_id(profile_idc, profile_iop, level_idc)
1076 }
1077
1078 #[must_use]
1079 pub fn fmtp_value(self) -> String {
1080 let value = self.packed_value();
1081 format!("{value:06x}")
1082 }
1083 }
1084
1085 /// H264 sub-profile after equivalent RFC 6184 encodings are normalized
1086 ///
1087 /// the same sub-profile may be advertised through different
1088 /// `profile_idc` families when `profile-iop` constraints restrict them to
1089 /// the same coding tools
1090 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1091 pub enum Profile {
1092 Baseline,
1093 ConstrainedBaseline,
1094 Main,
1095 Extended,
1096 High,
1097 High10,
1098 High422,
1099 High444Predictive,
1100 High10Intra,
1101 High422Intra,
1102 High444Intra,
1103 Cavlc444Intra,
1104 }
1105
1106 /// H264 decoder capability level
1107 ///
1108 /// all levels except [`LevelIdc::Level1B`] map directly to `level_idc`
1109 /// level 1b was inserted between level 1 and level 1.1 after those byte
1110 /// values existed, so RFC 6184 gives it profile-dependent encodings
1111 ///
1112 /// the variant order is the negotiation order
1113 /// wire `level_idc` bytes are parsed and rendered explicitly because Level
1114 /// 1b is encoded specially
1115 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1116 pub enum LevelIdc {
1117 Level1,
1118 Level1B,
1119 Level1_1,
1120 Level1_2,
1121 Level1_3,
1122 Level2,
1123 Level2_1,
1124 Level2_2,
1125 Level3,
1126 Level3_1,
1127 Level3_2,
1128 Level4,
1129 Level4_1,
1130 Level4_2,
1131 Level5,
1132 Level5_1,
1133 Level5_2,
1134 }
1135
1136 /// Detects an H264 RTP packet that starts an IDR access unit.
1137 ///
1138 /// RFC 6184 carries IDR frames either as a single NAL unit, inside a STAP-A
1139 /// aggregation packet or as the first fragment of a FU-A packet.
1140 ///
1141 /// The input must be the RTP codec payload, not a complete RTP packet.
1142 /// Truncated aggregation packets and incomplete FU-A packets return
1143 /// `false`. The helper does not parse a full access unit. It only answers
1144 /// whether this packet can refresh a decoder if forwarded.
1145 #[must_use]
1146 pub fn payload_starts_idr(payload: &[u8], packetization_mode: PacketizationMode) -> bool {
1147 let Some((&nal_header, rest)) = payload.split_first() else {
1148 return false;
1149 };
1150 match nal_header & NAL_UNIT_TYPE_MASK {
1151 NAL_UNIT_TYPE_IDR => true,
1152 NAL_UNIT_TYPE_STAP_A if packetization_mode.allows_aggregation_and_fragmentation() => {
1153 stap_a_contains_idr(rest)
1154 }
1155 NAL_UNIT_TYPE_FU_A if packetization_mode.allows_aggregation_and_fragmentation() => {
1156 fu_a_starts_idr(rest)
1157 }
1158 _ => false,
1159 }
1160 }
1161
1162 /// Scans a STAP-A payload for any contained IDR NAL unit.
1163 ///
1164 /// each aggregate entry is length-prefixed and must use a single NAL unit type
1165 /// malformed lengths fail closed because the caller cannot safely trust later
1166 /// bytes as NAL boundaries
1167 fn stap_a_contains_idr(mut payload: &[u8]) -> bool {
1168 let mut contains_idr = false;
1169 while !payload.is_empty() {
1170 let Some((len, rest)) = payload.split_first_chunk::<2>() else {
1171 return false;
1172 };
1173 let nal_len = usize::from(u16::from_be_bytes(*len));
1174 let Some((nal, remaining_payload)) = rest.split_at_checked(nal_len) else {
1175 return false;
1176 };
1177 let Some(&nal_header) = nal.first() else {
1178 return false;
1179 };
1180 let nal_type = nal_header & NAL_UNIT_TYPE_MASK;
1181 if !(1..NAL_UNIT_TYPE_STAP_A).contains(&nal_type) {
1182 return false;
1183 }
1184 contains_idr |= nal_type == NAL_UNIT_TYPE_IDR;
1185 payload = remaining_payload;
1186 }
1187 contains_idr
1188 }
1189
1190 /// Detects whether a FU-A packet is the first fragment of an IDR NAL unit.
1191 fn fu_a_starts_idr(payload: &[u8]) -> bool {
1192 let Some((&fu_header, _fragment)) = payload.split_first() else {
1193 return false;
1194 };
1195 fu_header & FU_START_BIT != 0
1196 && fu_header & FU_END_BIT == 0
1197 && fu_header & NAL_UNIT_TYPE_MASK == NAL_UNIT_TYPE_IDR
1198 }
1199
1200 impl TryFrom<u8> for LevelIdc {
1201 type Error = ();
1202
1203 fn try_from(value: u8) -> Result<Self, Self::Error> {
1204 match value {
1205 9 => Ok(Self::Level1B),
1206 10 => Ok(Self::Level1),
1207 11 => Ok(Self::Level1_1),
1208 12 => Ok(Self::Level1_2),
1209 13 => Ok(Self::Level1_3),
1210 20 => Ok(Self::Level2),
1211 21 => Ok(Self::Level2_1),
1212 22 => Ok(Self::Level2_2),
1213 30 => Ok(Self::Level3),
1214 31 => Ok(Self::Level3_1),
1215 32 => Ok(Self::Level3_2),
1216 40 => Ok(Self::Level4),
1217 41 => Ok(Self::Level4_1),
1218 42 => Ok(Self::Level4_2),
1219 50 => Ok(Self::Level5),
1220 51 => Ok(Self::Level5_1),
1221 52 => Ok(Self::Level5_2),
1222 _ => Err(()),
1223 }
1224 }
1225 }
1226
1227 const BASELINE_IDC: u8 = 0x42;
1228 const MAIN_IDC: u8 = 0x4d;
1229 const EXTENDED_IDC: u8 = 0x58;
1230 const HIGH_IDC: u8 = 0x64;
1231 const HIGH_10_IDC: u8 = 0x6e;
1232 const HIGH_422_IDC: u8 = 0x7a;
1233 const HIGH_444_IDC: u8 = 0xf4;
1234 const CAVLC_444_IDC: u8 = 0x2c;
1235
1236 // `profile-iop` is the middle byte of `profile-level-id`
1237 // constraint flags are H264 compatibility bits that refine `profile_idc`
1238 // into the sub-profile used for offer or answer matching
1239 //
1240 // bit 7 -> constraint_set0_flag -> `IOP_CONSTRAINT_SET0`
1241 // bit 6 -> constraint_set1_flag -> `IOP_CONSTRAINT_SET1`
1242 // bit 5 -> constraint_set2_flag -> `IOP_CONSTRAINT_SET2`
1243 // bit 4 -> constraint_set3_flag -> `IOP_CONSTRAINT_SET3`
1244 // bit 3 -> constraint_set4_flag -> fixed to 0 by profile-equivalence rows
1245 // bit 2 -> constraint_set5_flag -> fixed to 0 by profile-equivalence rows
1246 // bit 1..0 -> reserved_zero_2bits -> fixed to 0 by H264
1247 const IOP_NONE: u8 = 0x00;
1248 const IOP_CONSTRAINT_SET0: u8 = 0x80;
1249 const IOP_CONSTRAINT_SET1: u8 = 0x40;
1250 const IOP_CONSTRAINT_SET2: u8 = 0x20;
1251 const IOP_CONSTRAINT_SET3: u8 = 0x10;
1252 const IOP_PROFILE_PATTERN_LOW_ZERO_MASK: u8 =
1253 !(IOP_CONSTRAINT_SET0 | IOP_CONSTRAINT_SET1 | IOP_CONSTRAINT_SET2 | IOP_CONSTRAINT_SET3);
1254 const IOP_CONSTRAINED_BASELINE: u8 =
1255 IOP_CONSTRAINT_SET0 | IOP_CONSTRAINT_SET1 | IOP_CONSTRAINT_SET2;
1256
1257 const LEVEL_1B_IDCS: [u8; 3] = [BASELINE_IDC, MAIN_IDC, EXTENDED_IDC];
1258 const LEVEL_1B_OTHER_IDC: u8 = 9;
1259 const LEVEL_1_1_IDC: u8 = 11;
1260
1261 /// one accepted RFC 6184 profile-equivalence row
1262 ///
1263 /// a row maps one concrete `profile_idc` family and one wildcarded
1264 /// `profile-iop` bit pattern to the normalized [`Profile`] used by
1265 /// negotiation
1266 #[derive(Clone, Copy)]
1267 struct ProfilePattern {
1268 profile: Profile,
1269 profile_idc: u8,
1270 iop: ProfileIopPattern,
1271 }
1272
1273 impl ProfilePattern {
1274 const fn masked(profile: Profile, profile_idc: u8, iop: ProfileIopPattern) -> Self {
1275 Self {
1276 profile,
1277 profile_idc,
1278 iop,
1279 }
1280 }
1281
1282 const fn exact(profile: Profile, profile_idc: u8, value: u8) -> Self {
1283 Self {
1284 profile,
1285 profile_idc,
1286 iop: ProfileIopPattern::exact(value),
1287 }
1288 }
1289
1290 const fn matches(self, profile_idc: u8, profile_iop: u8) -> bool {
1291 self.profile_idc == profile_idc && self.iop.matches(profile_iop)
1292 }
1293 }
1294
1295 /// masked `profile-iop` matcher for profile-equivalence wildcard bits
1296 ///
1297 /// `mask` selects bits that must equal `value`. unmasked bits are RFC
1298 /// wildcards such as the `x` bits in `x1xx0000`
1299 #[derive(Clone, Copy)]
1300 struct ProfileIopPattern {
1301 mask: u8,
1302 value: u8,
1303 }
1304
1305 impl ProfileIopPattern {
1306 /// starts with the low four bits fixed to zero and high bits wildcarded
1307 const fn wildcarded() -> Self {
1308 Self {
1309 mask: IOP_PROFILE_PATTERN_LOW_ZERO_MASK,
1310 value: IOP_NONE,
1311 }
1312 }
1313
1314 const fn exact(value: u8) -> Self {
1315 Self {
1316 mask: u8::MAX,
1317 value,
1318 }
1319 }
1320
1321 const fn one(self, bit: u8) -> Self {
1322 Self {
1323 mask: self.mask | bit,
1324 value: self.value | bit,
1325 }
1326 }
1327
1328 const fn zero(self, bit: u8) -> Self {
1329 Self {
1330 mask: self.mask | bit,
1331 value: self.value & !bit,
1332 }
1333 }
1334
1335 const fn matches(self, value: u8) -> bool {
1336 value & self.mask == self.value
1337 }
1338 }
1339
1340 // rfc 6184 lists equivalent sub-profile encodings rather than one canonical byte
1341 // example: `42 x1xx0000`, `4d 1xxx0000` and `58 11xx0000` all mean
1342 // constrained baseline, so `x` bits must be ignored during matching
1343 const PROFILE_PATTERNS: &[ProfilePattern] = &[
1344 ProfilePattern::masked(
1345 Profile::ConstrainedBaseline,
1346 BASELINE_IDC,
1347 ProfileIopPattern::wildcarded().one(IOP_CONSTRAINT_SET1),
1348 ),
1349 ProfilePattern::masked(
1350 Profile::ConstrainedBaseline,
1351 MAIN_IDC,
1352 ProfileIopPattern::wildcarded().one(IOP_CONSTRAINT_SET0),
1353 ),
1354 ProfilePattern::masked(
1355 Profile::ConstrainedBaseline,
1356 EXTENDED_IDC,
1357 ProfileIopPattern::wildcarded()
1358 .one(IOP_CONSTRAINT_SET0)
1359 .one(IOP_CONSTRAINT_SET1),
1360 ),
1361 ProfilePattern::masked(
1362 Profile::Baseline,
1363 BASELINE_IDC,
1364 ProfileIopPattern::wildcarded().zero(IOP_CONSTRAINT_SET1),
1365 ),
1366 ProfilePattern::masked(
1367 Profile::Baseline,
1368 EXTENDED_IDC,
1369 ProfileIopPattern::wildcarded()
1370 .one(IOP_CONSTRAINT_SET0)
1371 .zero(IOP_CONSTRAINT_SET1),
1372 ),
1373 ProfilePattern::masked(
1374 Profile::Main,
1375 MAIN_IDC,
1376 ProfileIopPattern::wildcarded()
1377 .zero(IOP_CONSTRAINT_SET0)
1378 .zero(IOP_CONSTRAINT_SET2),
1379 ),
1380 ProfilePattern::masked(
1381 Profile::Extended,
1382 EXTENDED_IDC,
1383 ProfileIopPattern::wildcarded()
1384 .zero(IOP_CONSTRAINT_SET0)
1385 .zero(IOP_CONSTRAINT_SET1),
1386 ),
1387 ProfilePattern::exact(Profile::High, HIGH_IDC, IOP_NONE),
1388 ProfilePattern::exact(Profile::High10, HIGH_10_IDC, IOP_NONE),
1389 ProfilePattern::exact(Profile::High422, HIGH_422_IDC, IOP_NONE),
1390 ProfilePattern::exact(Profile::High444Predictive, HIGH_444_IDC, IOP_NONE),
1391 ProfilePattern::exact(Profile::High10Intra, HIGH_10_IDC, IOP_CONSTRAINT_SET3),
1392 ProfilePattern::exact(Profile::High422Intra, HIGH_422_IDC, IOP_CONSTRAINT_SET3),
1393 ProfilePattern::exact(Profile::High444Intra, HIGH_444_IDC, IOP_CONSTRAINT_SET3),
1394 ProfilePattern::exact(Profile::Cavlc444Intra, CAVLC_444_IDC, IOP_CONSTRAINT_SET3),
1395 ];
1396
1397 const fn profile_level_id_bytes(profile: Profile, level: LevelIdc) -> (u8, u8, u8) {
1398 let (profile_idc, profile_iop) = profile_bytes(profile);
1399 match level {
1400 LevelIdc::Level1B => level_1b_profile_level_id_bytes(profile_idc, profile_iop),
1401 _ => (profile_idc, profile_iop, level_idc_value(level)),
1402 }
1403 }
1404
1405 const fn profile_bytes(profile: Profile) -> (u8, u8) {
1406 match profile {
1407 Profile::Baseline => (BASELINE_IDC, IOP_NONE),
1408 Profile::ConstrainedBaseline => (BASELINE_IDC, IOP_CONSTRAINED_BASELINE),
1409 Profile::Main => (MAIN_IDC, IOP_NONE),
1410 Profile::Extended => (EXTENDED_IDC, IOP_NONE),
1411 Profile::High => (HIGH_IDC, IOP_NONE),
1412 Profile::High10 => (HIGH_10_IDC, IOP_NONE),
1413 Profile::High422 => (HIGH_422_IDC, IOP_NONE),
1414 Profile::High444Predictive => (HIGH_444_IDC, IOP_NONE),
1415 Profile::High10Intra => (HIGH_10_IDC, IOP_CONSTRAINT_SET3),
1416 Profile::High422Intra => (HIGH_422_IDC, IOP_CONSTRAINT_SET3),
1417 Profile::High444Intra => (HIGH_444_IDC, IOP_CONSTRAINT_SET3),
1418 Profile::Cavlc444Intra => (CAVLC_444_IDC, IOP_CONSTRAINT_SET3),
1419 }
1420 }
1421
1422 // level 1b has no single `level_idc` byte
1423 // baseline, main and extended borrow level 1.1's `level_idc=11` and set
1424 // constraint_set3_flag, while other profiles use `level_idc=9`
1425 const fn level_1b_profile_level_id_bytes(profile_idc: u8, profile_iop: u8) -> (u8, u8, u8) {
1426 match profile_idc {
1427 BASELINE_IDC | MAIN_IDC | EXTENDED_IDC => (
1428 profile_idc,
1429 profile_iop | IOP_CONSTRAINT_SET3,
1430 LEVEL_1_1_IDC,
1431 ),
1432 _ => (profile_idc, profile_iop, LEVEL_1B_OTHER_IDC),
1433 }
1434 }
1435
1436 const fn level_idc_value(level: LevelIdc) -> u8 {
1437 match level {
1438 LevelIdc::Level1 => 10,
1439 LevelIdc::Level1B => LEVEL_1B_OTHER_IDC,
1440 LevelIdc::Level1_1 => 11,
1441 LevelIdc::Level1_2 => 12,
1442 LevelIdc::Level1_3 => 13,
1443 LevelIdc::Level2 => 20,
1444 LevelIdc::Level2_1 => 21,
1445 LevelIdc::Level2_2 => 22,
1446 LevelIdc::Level3 => 30,
1447 LevelIdc::Level3_1 => 31,
1448 LevelIdc::Level3_2 => 32,
1449 LevelIdc::Level4 => 40,
1450 LevelIdc::Level4_1 => 41,
1451 LevelIdc::Level4_2 => 42,
1452 LevelIdc::Level5 => 50,
1453 LevelIdc::Level5_1 => 51,
1454 LevelIdc::Level5_2 => 52,
1455 }
1456 }
1457
1458 #[expect(clippy::as_conversions, reason = "u8 to u32 widening is lossless")]
1459 const fn pack_profile_level_id(profile_idc: u8, profile_iop: u8, level_idc: u8) -> u32 {
1460 ((profile_idc as u32) << 16) | ((profile_iop as u32) << 8) | level_idc as u32
1461 }
1462
1463 fn normalized_level_idc(profile_idc: u8, profile_iop: u8, level_idc: u8) -> Option<LevelIdc> {
1464 // reject the non-canonical level 1b form for baseline, main and extended
1465 // those profile families must use `level_idc=11` plus constraint_set3_flag
1466 if LEVEL_1B_IDCS.contains(&profile_idc) {
1467 if level_idc == LEVEL_1B_OTHER_IDC {
1468 return None;
1469 }
1470 if level_idc == LEVEL_1_1_IDC {
1471 return if (profile_iop & IOP_CONSTRAINT_SET3) != 0 {
1472 Some(LevelIdc::Level1B)
1473 } else {
1474 Some(LevelIdc::Level1_1)
1475 };
1476 }
1477 }
1478 LevelIdc::try_from(level_idc).ok()
1479 }
1480
1481 fn profile_from_bytes(profile_idc: u8, profile_iop: u8) -> Option<Profile> {
1482 PROFILE_PATTERNS.iter().copied().find_map(|pattern| {
1483 pattern
1484 .matches(profile_idc, profile_iop)
1485 .then_some(pattern.profile)
1486 })
1487 }
1488
1489 fn parse_profile_level_id_bytes(value: &[u8]) -> Option<[u8; 3]> {
1490 let [
1491 first_high,
1492 first_low,
1493 second_high,
1494 second_low,
1495 third_high,
1496 third_low,
1497 ] = value
1498 else {
1499 return None;
1500 };
1501 Some([
1502 decode_hex_byte(*first_high, *first_low)?,
1503 decode_hex_byte(*second_high, *second_low)?,
1504 decode_hex_byte(*third_high, *third_low)?,
1505 ])
1506 }
1507
1508 fn decode_hex_byte(high: u8, low: u8) -> Option<u8> {
1509 Some((decode_hex_nibble(high)? << 4) | decode_hex_nibble(low)?)
1510 }
1511
1512 fn decode_hex_nibble(value: u8) -> Option<u8> {
1513 match value {
1514 b'0'..=b'9' => Some(value - b'0'),
1515 b'a'..=b'f' => Some(value - b'a' + 10),
1516 b'A'..=b'F' => Some(value - b'A' + 10),
1517 _ => None,
1518 }
1519 }
1520}
1521
1522/// Returns `true` if `payload_type` is an RTP/AVP dynamic payload type.
1523///
1524/// Use this for the `PT` field from the RTP fixed header. It does not validate
1525/// whether a signaling layer actually negotiated that payload type.
1526///
1527/// Reference: RFC 3551 section 6.
1528#[must_use]
1529pub const fn is_dynamic_payload_type(payload_type: u8) -> bool {
1530 payload_type >= RTP_DYNAMIC_PAYLOAD_TYPE_START && payload_type <= RTP_DYNAMIC_PAYLOAD_TYPE_END
1531}
1532
1533/// Returns `true` if `payload_type` fits in the RTP fixed-header PT field.
1534///
1535/// Reference: RFC 3550 section 5.1.
1536#[must_use]
1537pub const fn is_payload_type(payload_type: u8) -> bool {
1538 payload_type <= RTP_PAYLOAD_TYPE_MAX
1539}
1540
1541/// Returns `true` if `payload_type` can be used with RTP/RTCP mux.
1542///
1543/// Reference: RFC 5761 section 4.
1544#[must_use]
1545pub const fn is_rtcp_mux_payload_type(payload_type: u8) -> bool {
1546 is_payload_type(payload_type)
1547 && (payload_type < RTP_RTCP_MUX_FORBIDDEN_PAYLOAD_TYPE_START
1548 || payload_type > RTP_RTCP_MUX_FORBIDDEN_PAYLOAD_TYPE_END)
1549}
1550
1551/// Returns whether `payload_type` can be assigned dynamically in an RTP/RTCP
1552/// muxed RTP/AVP session.
1553///
1554/// RFC 3551 reserves 96 through 127 for dynamic assignment and leaves several
1555/// lower ranges unassigned. RFC 5761 permits muxed RTP to use values below 64
1556/// while excluding 64 through 95 from RTP assignment.
1557///
1558/// References:
1559/// - <https://www.rfc-editor.org/rfc/rfc3551.html#section-6>
1560/// - <https://www.rfc-editor.org/rfc/rfc5761.html#section-4>
1561#[must_use]
1562pub const fn is_rtcp_mux_dynamic_payload_type(payload_type: u8) -> bool {
1563 is_dynamic_payload_type(payload_type)
1564 || matches!(payload_type, 20..=24 | 27 | 29..=30 | 35..=63)
1565}
1566
1567/// Classifies an RTP or RTCP candidate in an RTP/RTCP muxed datagram.
1568///
1569/// The packet must contain the first two header octets plus one body octet,
1570/// matching the minimum boundary used before complete RTP or RTCP parsing.
1571///
1572/// Reference: <https://www.rfc-editor.org/rfc/rfc5761.html#section-4>
1573#[must_use]
1574pub fn classify_rtp_rtcp_mux(packet: &[u8]) -> Option<RtpRtcpMuxPacketKind> {
1575 let [first, second, _, ..] = packet else {
1576 return None;
1577 };
1578 if first & RTP_VERSION_MASK != RTP_VERSION_HEADER_BITS {
1579 return None;
1580 }
1581 let payload_type = second & RTP_PAYLOAD_TYPE_MASK;
1582 if (RTP_RTCP_MUX_FORBIDDEN_PAYLOAD_TYPE_START..=RTP_RTCP_MUX_FORBIDDEN_PAYLOAD_TYPE_END)
1583 .contains(&payload_type)
1584 {
1585 Some(RtpRtcpMuxPacketKind::Rtcp)
1586 } else {
1587 Some(RtpRtcpMuxPacketKind::Rtp)
1588 }
1589}
1590
1591/// Parses the fixed RTP header of an RTP candidate in a muxed session.
1592///
1593/// The parser rejects RTCP candidates, non-RTP versions and packets truncated
1594/// within the fixed header or declared CSRC list. Codec payload and padding
1595/// validation belong to the payload parser.
1596///
1597/// References:
1598/// - <https://www.rfc-editor.org/rfc/rfc3550.html#section-5.1>
1599/// - <https://www.rfc-editor.org/rfc/rfc5761.html#section-4>
1600#[must_use]
1601pub fn parse_muxed_rtp_fixed_header(packet: &[u8]) -> Option<RtpFixedHeader> {
1602 if classify_rtp_rtcp_mux(packet) != Some(RtpRtcpMuxPacketKind::Rtp) {
1603 return None;
1604 }
1605 let first = *packet.first()?;
1606 let second = *packet.get(1)?;
1607 let header = RtpFixedHeader {
1608 payload_type: second & RTP_PAYLOAD_TYPE_MASK,
1609 sequence_number: read_u16(packet, RTP_SEQUENCE_NUMBER_OFFSET)?,
1610 timestamp: read_u32(packet, RTP_TIMESTAMP_OFFSET)?,
1611 ssrc: Ssrc::new(read_u32(packet, RTP_SSRC_OFFSET)?),
1612 marker: second & RTP_MARKER_MASK != 0,
1613 has_padding: first & RTP_PADDING_MASK != 0,
1614 has_extension: first & RTP_EXTENSION_MASK != 0,
1615 csrc_count: first & RTP_CSRC_COUNT_MASK,
1616 };
1617 packet.get(..header.extension_offset())?;
1618 Some(header)
1619}
1620
1621/// Expands one Generic NACK PID and bitmask into requested RTP sequence
1622/// numbers.
1623///
1624/// The PID is yielded first. Each set BLP bit yields PID + bit index + 1 with
1625/// 16-bit RTP sequence-number rollover.
1626///
1627/// Reference: <https://www.rfc-editor.org/rfc/rfc4585.html#section-6.2.1>
1628pub fn generic_nack_sequence_numbers(pid: u16, blp: u16) -> impl Iterator<Item = u16> {
1629 iter::once(pid).chain(
1630 (0_u16..GENERIC_NACK_BITMASK_BITS)
1631 .filter(move |bit| blp & (1_u16 << bit) != 0)
1632 .map(move |bit| pid.wrapping_add(bit + 1)),
1633 )
1634}
1635
1636/// Extracts the original RTP sequence number from an RTX payload.
1637///
1638/// Reference: <https://www.rfc-editor.org/rfc/rfc4588.html#section-4>
1639#[must_use]
1640pub fn rtx_original_sequence_number(payload: &[u8]) -> Option<u16> {
1641 read_u16(payload, 0)
1642}
1643
1644/// Builds an RTCP Receiver Report with no reception report blocks.
1645///
1646/// Reference: <https://www.rfc-editor.org/rfc/rfc3550.html#section-6.4.2>
1647#[must_use]
1648pub fn rtcp_receiver_report_without_report_blocks(
1649 sender_ssrc: Ssrc,
1650) -> [u8; RTCP_RECEIVER_REPORT_WITHOUT_BLOCKS_BYTES] {
1651 let [length_high, length_low] =
1652 RTCP_RECEIVER_REPORT_WITHOUT_BLOCKS_LENGTH_WORDS_MINUS_ONE.to_be_bytes();
1653 let [ssrc_0, ssrc_1, ssrc_2, ssrc_3] = sender_ssrc.value().to_be_bytes();
1654 [
1655 RTP_VERSION_HEADER_BITS,
1656 rtcp_packet_type::RR,
1657 length_high,
1658 length_low,
1659 ssrc_0,
1660 ssrc_1,
1661 ssrc_2,
1662 ssrc_3,
1663 ]
1664}
1665
1666fn read_u16(bytes: &[u8], offset: usize) -> Option<u16> {
1667 bytes
1668 .get(offset..offset.checked_add(mem::size_of::<u16>())?)?
1669 .try_into()
1670 .ok()
1671 .map(u16::from_be_bytes)
1672}
1673
1674fn read_u32(bytes: &[u8], offset: usize) -> Option<u32> {
1675 bytes
1676 .get(offset..offset.checked_add(mem::size_of::<u32>())?)?
1677 .try_into()
1678 .ok()
1679 .map(u32::from_be_bytes)
1680}
1681
1682/// rtcp common-header `PT` value namespace
1683///
1684/// reference: RFC 3550 section 12.1 and RFC 4585 section 6.1
1685pub mod rtcp_packet_type {
1686 /// sender report packet
1687 pub const SR: u8 = 200;
1688 /// receiver report packet
1689 pub const RR: u8 = 201;
1690 /// source description packet
1691 pub const SDES: u8 = 202;
1692 /// goodbye packet
1693 pub const BYE: u8 = 203;
1694 /// application-defined packet
1695 pub const APP: u8 = 204;
1696
1697 /// transport-layer feedback packet
1698 pub const RTPFB: u8 = 205;
1699 /// payload-specific feedback packet
1700 pub const PSFB: u8 = 206;
1701}
1702
1703/// rtcp SDES item `type` value namespace
1704///
1705/// reference: RFC 3550 section 12.2 and RFC 8852 section 4
1706pub mod rtcp_sdes_item {
1707 /// canonical end-point identifier
1708 pub const CNAME: u8 = 1;
1709 /// user name
1710 pub const NAME: u8 = 2;
1711 /// email address
1712 pub const EMAIL: u8 = 3;
1713 /// phone number
1714 pub const PHONE: u8 = 4;
1715 /// geographic location
1716 pub const LOC: u8 = 5;
1717 /// application or tool name
1718 pub const TOOL: u8 = 6;
1719 /// transient note
1720 pub const NOTE: u8 = 7;
1721 /// private extension item
1722 pub const PRIV: u8 = 8;
1723
1724 /// RTP stream identifier carried in an SDES packet
1725 pub const RTP_STREAM_ID: u8 = 12;
1726 /// RTP stream repaired by a redundancy stream
1727 pub const REPAIRED_RTP_STREAM_ID: u8 = 13;
1728}
1729
1730/// RTCP feedback FMT values.
1731pub mod rtcp_feedback_format {
1732 /// Generic NACK FMT value for RTPFB packets.
1733 ///
1734 /// Reference: RFC 4585 section 6.2.1.
1735 pub const RTPFB_GENERIC_NACK: u8 = 1;
1736
1737 /// Picture Loss Indication FMT value for PSFB packets.
1738 ///
1739 /// Reference: RFC 4585 section 6.3.1.
1740 pub const PSFB_PLI: u8 = 1;
1741
1742 /// Full Intra Request FMT value for PSFB packets.
1743 ///
1744 /// Reference: RFC 5104 section 4.3.1.
1745 pub const PSFB_FIR: u8 = 4;
1746
1747 /// Layer Refresh Request FMT value for PSFB packets.
1748 ///
1749 /// Reference: RFC 9627 section 8.
1750 pub const PSFB_LRR: u8 = 10;
1751}
1752
1753/// RTP header-extension profile IDs from RFC 8285.
1754///
1755/// RTP packets carry an extension block only when the fixed-header `X` bit is
1756/// set. The 16-bit profile ID then decides whether extension elements use the
1757/// one-byte or two-byte element header shape.
1758///
1759/// ```text
1760/// RTP extension block
1761///
1762/// +----------------+----------------+-----------------------------+
1763/// | profile ID | length in u32 | extension elements |
1764/// +----------------+----------------+-----------------------------+
1765/// | 16 bits | 16 bits | padded to 32-bit boundary |
1766/// +----------------+----------------+-----------------------------+
1767///
1768/// One-byte element, profile 0xBEDE
1769///
1770/// +---------+---------+----------------------+
1771/// | ID | len-1 | data |
1772/// +---------+---------+----------------------+
1773/// | 4 bits | 4 bits | 1 to 16 bytes |
1774/// +---------+---------+----------------------+
1775///
1776/// Two-byte element, profile 0x1000 through 0x100F
1777///
1778/// +---------+---------+----------------------+
1779/// | ID | len | data |
1780/// +---------+---------+----------------------+
1781/// | 8 bits | 8 bits | 0 to 255 bytes |
1782/// +---------+---------+----------------------+
1783/// ```
1784pub mod header_extension {
1785 const HEADER_BYTES: usize = 4;
1786 const WORD_BYTES: usize = 4;
1787 const ONE_BYTE_ID_SHIFT: u32 = 4;
1788 const ONE_BYTE_LENGTH_MASK: u8 = 0b0000_1111;
1789 const ONE_BYTE_LENGTH_OFFSET: usize = 1;
1790
1791 /// RFC 8285 one-byte header extension profile ID.
1792 pub const ONE_BYTE_PROFILE_ID: u16 = 0xBEDE;
1793
1794 /// Base value for the RFC 8285 two-byte header profile with appbits set to 0.
1795 pub const TWO_BYTE_PROFILE_ID_BASE: u16 = 0x1000;
1796
1797 /// Mask for the fixed 12-bit prefix (`0x100`) in the two-byte profile ID.
1798 pub const TWO_BYTE_PROFILE_PREFIX_MASK: u16 = 0xFFF0;
1799
1800 /// One-byte header extension identifier values.
1801 pub const ONE_BYTE_ID_PAD: u8 = 0;
1802 pub const ONE_BYTE_ID_MIN: u8 = 1;
1803 pub const ONE_BYTE_ID_MAX: u8 = 14;
1804 pub const ONE_BYTE_ID_RESERVED: u8 = 15;
1805
1806 /// One-byte header extension data size bounds from RFC 8285 section 4.2.
1807 pub const ONE_BYTE_DATA_LEN_MIN: u8 = 1;
1808 pub const ONE_BYTE_DATA_LEN_MAX: u8 = 16;
1809
1810 /// Two-byte header extension data size bounds from RFC 8285 section 4.3.
1811 pub const TWO_BYTE_DATA_LEN_MIN: u8 = 0;
1812 pub const TWO_BYTE_DATA_LEN_MAX: u8 = u8::MAX;
1813
1814 /// Returns whether `id` is usable as an RFC 8285 one-byte element ID.
1815 ///
1816 /// ID 0 is padding and ID 15 is reserved, so only 1 through 14 can identify
1817 /// negotiated extension values.
1818 #[must_use]
1819 pub const fn is_one_byte_id(id: u8) -> bool {
1820 id >= ONE_BYTE_ID_MIN && id <= ONE_BYTE_ID_MAX
1821 }
1822
1823 /// Builds an RFC 8285 two-byte profile ID from the 4-bit appbits value.
1824 ///
1825 /// Returns `None` when `appbits` cannot fit in the low nibble of the
1826 /// profile ID.
1827 #[must_use]
1828 pub fn two_byte_profile_id(appbits: u8) -> Option<u16> {
1829 if appbits > 0x0F {
1830 return None;
1831 }
1832 Some(TWO_BYTE_PROFILE_ID_BASE | u16::from(appbits))
1833 }
1834
1835 /// Finds an RFC 8285 one-byte extension element in a complete muxed RTP
1836 /// packet.
1837 ///
1838 /// Zero octets are padding. A nonzero element with ID 0 is malformed and
1839 /// ID 15 terminates processing, so neither can expose a later element.
1840 /// Packets without the one-byte extension profile return `None`.
1841 ///
1842 /// References:
1843 /// - <https://www.rfc-editor.org/rfc/rfc8285.html#section-4.1.2>
1844 /// - <https://www.rfc-editor.org/rfc/rfc8285.html#section-4.2>
1845 #[must_use]
1846 pub fn find_one_byte_element(packet: &[u8], target_id: u8) -> Option<&[u8]> {
1847 if !is_one_byte_id(target_id) {
1848 return None;
1849 }
1850 let header = super::parse_muxed_rtp_fixed_header(packet)?;
1851 if !header.has_extension() {
1852 return None;
1853 }
1854 let extension_offset = header.extension_offset();
1855 let profile = super::read_u16(packet, extension_offset)?;
1856 if profile != ONE_BYTE_PROFILE_ID {
1857 return None;
1858 }
1859 let word_count = super::read_u16(
1860 packet,
1861 extension_offset.checked_add(super::mem::size_of::<u16>())?,
1862 )?;
1863 let elements_offset = extension_offset.checked_add(HEADER_BYTES)?;
1864 let elements_bytes = usize::from(word_count).checked_mul(WORD_BYTES)?;
1865 let mut elements =
1866 packet.get(elements_offset..elements_offset.checked_add(elements_bytes)?)?;
1867
1868 while let Some((&element_header, rest)) = elements.split_first() {
1869 elements = rest;
1870 if element_header == ONE_BYTE_ID_PAD {
1871 continue;
1872 }
1873 let id = element_header >> ONE_BYTE_ID_SHIFT;
1874 if id == ONE_BYTE_ID_PAD || id == ONE_BYTE_ID_RESERVED {
1875 return None;
1876 }
1877 let length = usize::from(element_header & ONE_BYTE_LENGTH_MASK)
1878 .checked_add(ONE_BYTE_LENGTH_OFFSET)?;
1879 let value = elements.get(..length)?;
1880 elements = elements.get(length..)?;
1881 if id == target_id {
1882 return Some(value);
1883 }
1884 }
1885 None
1886 }
1887}
1888
1889/// Video Frame Marking RTP header-extension payload values.
1890///
1891/// Frame marking is carried as one negotiated RTP header extension
1892///
1893/// These helpers retain temporal-layer fields for future negotiated SVC
1894/// selection while current packet gates match simulcast RIDs
1895///
1896/// ```text
1897/// Short form, non-scalable stream
1898///
1899/// +---+---+---+---+---------------+
1900/// | S | E | I | D | reserved |
1901/// +---+---+---+---+---------------+
1902///
1903/// Long form, scalable stream
1904///
1905/// +---+---+---+---+---+-----------+---------------+---------------+
1906/// | S | E | I | D | B | TID | LID | TL0PICIDX |
1907/// +---+---+---+---+---+-----------+---------------+---------------+
1908/// | first octet | optional | optional |
1909/// +---+---+---+---+---+-----------+---------------+---------------+
1910/// ```
1911///
1912/// Reference: RFC 9626 section 3.
1913pub mod frame_marking {
1914 /// Full long-form frame-marking payload length.
1915 pub const LONG_DATA_LEN_WITH_TL0PICIDX: u8 = 3;
1916
1917 /// Long-form payload length when `TL0PICIDX` is omitted.
1918 pub const LONG_DATA_LEN_WITHOUT_TL0PICIDX: u8 = 2;
1919
1920 /// Long-form payload length when both `LID` and `TL0PICIDX` are omitted.
1921 pub const LONG_DATA_LEN_FLAGS_ONLY: u8 = 1;
1922
1923 /// Short-form non-scalable frame-marking payload length.
1924 pub const SHORT_DATA_LEN: u8 = 1;
1925
1926 /// Start-of-frame flag in the first frame-marking octet.
1927 pub const START_OF_FRAME_MASK: u8 = 0b1000_0000;
1928
1929 /// End-of-frame flag in the first frame-marking octet.
1930 pub const END_OF_FRAME_MASK: u8 = 0b0100_0000;
1931
1932 /// Independent-frame flag in the first frame-marking octet.
1933 pub const INDEPENDENT_FRAME_MASK: u8 = 0b0010_0000;
1934
1935 /// Discardable-frame flag in the first frame-marking octet.
1936 pub const DISCARDABLE_FRAME_MASK: u8 = 0b0001_0000;
1937
1938 /// Base-layer-sync flag in the long-form first octet.
1939 pub const BASE_LAYER_SYNC_MASK: u8 = 0b0000_1000;
1940
1941 /// Temporal-layer identifier bits in the long-form first octet.
1942 pub const TEMPORAL_LAYER_ID_MASK: u8 = 0b0000_0111;
1943
1944 /// Maximum temporal-layer identifier representable by the 3-bit TID field.
1945 pub const TEMPORAL_LAYER_ID_MAX: u8 = 7;
1946
1947 /// Base layer identifier used for TID and LID.
1948 pub const BASE_LAYER_ID: u8 = 0;
1949
1950 /// Extracts the temporal-layer ID from the first long-form frame-marking
1951 /// octet.
1952 ///
1953 /// Callers must only use this value as temporal metadata when signaling or
1954 /// negotiated extension state proves the packet carries frame marking.
1955 #[must_use]
1956 pub const fn temporal_layer_id(first_octet: u8) -> u8 {
1957 first_octet & TEMPORAL_LAYER_ID_MASK
1958 }
1959
1960 /// Returns whether `value` fits in the RFC 9626 three-bit temporal-layer
1961 /// field.
1962 #[must_use]
1963 pub const fn is_valid_temporal_layer_id(value: u8) -> bool {
1964 value <= TEMPORAL_LAYER_ID_MAX
1965 }
1966}
1967
1968#[cfg(test)]
1969#[path = "TESTS/rtp.rs"]
1970mod tests;