Skip to content
File

Blob: firmware/vendor/str0m/src/packet/vp9.rs

rust1587 lines
1use super::{BitRead, CodecExtra, Depacketizer, PacketError, Packetizer};
2 
3use std::fmt;
4 
5/// Max non-flexible VP9 descriptor size reserved for MTU headroom:
6/// flags(1) + PID(2) + L(1) + tl0picidx(1) + ≤1 SS byte on keyframe first-packets; 8 is a safe bound.
7const VP9_NON_FLEXIBLE_HEADER_SIZE: usize = 8;
8/// VP9 RTP payload descriptor size for flexible mode:
9/// flags(1) + 15-bit PID(2) = 3
10const VP9_FLEXIBLE_HEADER_SIZE: usize = 3;
11const MAX_SPATIAL_LAYERS: usize = 3;
12const MAX_VP9REF_PICS: usize = 3;
13 
14/// VP9 packetizer mode controlling the RTP payload descriptor format.
15#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
16pub enum Vp9PacketizerMode {
17 /// Flexible mode (F=1): minimal 3-byte header (flags + 15-bit PID).
18 /// Simpler but causes issues with Safari which drops inter-frames.
19 Flexible,
20 /// Non-flexible mode (F=0): 5-6 byte header with layer indices and
21 /// scalability structure. Compatible with all major browsers.
22 #[default]
23 NonFlexible,
24}
25 
26/// Vp9 information describing the depacketized/packetized data.
27#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
28pub struct Vp9CodecExtra {
29 /// Map of the SVC layers.
30 ///
31 /// Index of each element corresponds to `spatial layer` index.
32 ///
33 /// Each element represents the end (not including) in [`MediaData::data`]
34 /// for `spatial layer` it belongs.
35 ///
36 /// The all `spatial layer`s (up to 3) in [`MediaData::data`] are sorted by
37 /// `spatial layer` index from lower to greater. So, the beginning of some
38 /// `spatial layer` is the end (including) of the previous `spatial layer`
39 /// (for the `spatial layer` with index 0 the beginning is 0).
40 ///
41 /// ```text
42 /// [MediaData::data]:
43 /// +-----+----------------------+---------------------------+
44 /// | SL0 | SL1 | SL2 |
45 /// +-----+----------------------+---------------------------+
46 /// | | | |
47 /// V V V V
48 /// 0 layers_scheme[0] layers_scheme[1] layers_scheme[2]
49 /// ```
50 ///
51 /// **Note:** There is no syncronization between
52 /// [`Vp9CodecExtra::layers_scheme`] and [`MediaData::data`]! If you need
53 /// to keep [`Vp9CodecExtra::layers_scheme`] in actual state you have to
54 /// update the [`Vp9CodecExtra::layers_scheme`] on mutating the
55 /// [`MediaData::data`].
56 ///
57 /// [`MediaData::data`]: crate::media::MediaData::data
58 ///
59 /// ## Example
60 ///
61 /// Say you have a VP9 track working in "L3T3" scalability mode and you
62 /// want to retranslate S1T2 layer. So you need to retranslate the all
63 /// spatial and temporal layers <= target layer.
64 ///
65 /// ```no_run
66 /// # use str0m::{format::CodecExtra, media::MediaData};
67 /// #
68 /// // We want to receive S1T2 layer.
69 /// let (target_spatial, target_temporal) = (1u8, 2u8);
70 ///
71 /// let media_data: MediaData = todo!();
72 ///
73 /// if let CodecExtra::Vp9(vp9_extra) = media_data.codec_extra {
74 /// // Try to get current `temporal layer`. If `None` there is no SVC.
75 /// if let Some(tid) = vp9_extra.tid {
76 /// if let (true, Some(end_of_target_layer)) = (
77 /// // Check whether we need this `temporal layer`.
78 /// tid <= target_temporal,
79 /// // Check whether we have information about the right
80 /// // border of data for the target `spatial layer`.
81 /// vp9_extra.layers_scheme[target_spatial as usize],
82 /// ) {
83 /// // Keep only the bytes for spatial layers up to `target_spatial`.
84 /// let data = &media_data.data[..end_of_target_layer];
85 /// }
86 /// }
87 /// }
88 /// ```
89 pub layers_scheme: [Option<usize>; MAX_SPATIAL_LAYERS],
90 
91 /// Temporal layer id.
92 pub tid: Option<u8>,
93 
94 /// Map of the SVC layers widths.
95 ///
96 /// Specified for every spatial layer.
97 pub layers_widths: [Option<u16>; MAX_SPATIAL_LAYERS],
98 
99 /// Map of the SVC layers heights.
100 ///
101 /// Specified for every spatial layer.
102 pub layers_heights: [Option<u16>; MAX_SPATIAL_LAYERS],
103 
104 /// Extended picture id of layer 0 frames, if present
105 pub tl0_picture_id: Option<u8>,
106 
107 /// Picture ID.
108 pub pid: u16,
109 
110 /// Flag which indicates that within [`MediaData`], there is an individual frame
111 /// containing complete and independent visual information. This frame serves
112 /// as a reference point for other frames in the video sequence.
113 ///
114 /// [`MediaData`]: crate::media::MediaData
115 pub is_keyframe: bool,
116}
117 
118/// Detect whether a raw VP9 frame is a keyframe by inspecting the bitstream header.
119///
120/// VP9 uncompressed header (profile 0/1):
121/// `frame_marker(2) | profile_low(1) | profile_high(1) | show_existing(1) | frame_type(1)`
122///
123/// VP9 uncompressed header (profile 2/3):
124/// `frame_marker(2) | profile_low(1) | profile_high(1) | reserved(1) | show_existing(1) | frame_type(1)`
125///
126/// - `frame_marker` must be `0b10`
127/// - `frame_type`: 0 = KEY_FRAME, 1 = NON_KEY_FRAME
128/// - `show_existing_frame` = 1 references a previously decoded frame (not a real keyframe)
129///
130/// This function works on raw VP9 bitstream data (without RTP descriptor).
131pub fn detect_vp9_keyframe_bitstream(payload: &[u8]) -> bool {
132 if payload.is_empty() {
133 return false;
134 }
135 let b = payload[0];
136 // frame_marker must be 0b10 (bits 7-6)
137 if (b & 0xC0) != 0x80 {
138 return false;
139 }
140 // profile_high_bit is at bit 4
141 let profile_high = (b >> 4) & 1;
142 let (show_existing_frame, frame_type) = if profile_high == 1 {
143 // Profile 2 or 3: reserved_zero at bit 3, show_existing at bit 2, frame_type at bit 1
144 ((b >> 2) & 1, (b >> 1) & 1)
145 } else {
146 // Profile 0 or 1: show_existing at bit 3, frame_type at bit 2
147 ((b >> 3) & 1, (b >> 2) & 1)
148 };
149 // show_existing_frame references a previously decoded frame, not a real keyframe
150 if show_existing_frame != 0 {
151 return false;
152 }
153 // frame_type: 0 = KEY_FRAME
154 frame_type == 0
155}
156 
157/// Detect whether a VP9 RTP payload contains a keyframe by inspecting the P bit.
158///
159/// VP9 RTP descriptor byte 0: `I|P|L|F|B|E|V|Z`
160/// - P=0: independently decodable frame (keyframe)
161/// - P=1: inter-picture predicted frame
162///
163/// Works with both flexible (F=1) and non-flexible (F=0) mode packets.
164pub fn detect_vp9_keyframe(payload: &[u8]) -> bool {
165 if payload.is_empty() {
166 return false;
167 }
168 // P bit is bit 6 of byte 0. P=0 means keyframe.
169 (payload[0] & 0x40) == 0
170}
171 
172/// Packetizes VP9 RTP packets.
173///
174/// Supports both non-flexible mode (F=0) and flexible mode (F=1), controlled
175/// by [`Vp9PacketizerMode`]. Defaults to non-flexible mode for broad browser
176/// compatibility — Safari and older Chrome drop inter-frames with F=1.
177#[derive(Default, Clone)]
178pub struct Vp9Packetizer {
179 picture_id: u16,
180 /// Temporal layer 0 picture index — increments on every frame (since all
181 /// frames are packetized as TID=0). Used in L byte for non-flexible mode.
182 tl0picidx: u8,
183 initialized: bool,
184 mode: Vp9PacketizerMode,
185 #[cfg(test)]
186 initial_picture_id: u16,
187}
188 
189impl Vp9Packetizer {
190 /// Create a VP9 packetizer with the specified mode.
191 pub fn with_mode(mode: Vp9PacketizerMode) -> Self {
192 Self {
193 mode,
194 ..Default::default()
195 }
196 }
197}
198 
199impl fmt::Debug for Vp9Packetizer {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 f.debug_struct("Vp9Packetizer")
202 .field("picture_id", &self.picture_id)
203 .field("tl0picidx", &self.tl0picidx)
204 .field("initialized", &self.initialized)
205 .field("mode", &self.mode)
206 .finish()
207 }
208}
209 
210impl Packetizer for Vp9Packetizer {
211 /// Packetize a VP9 frame into one or more RTP packets.
212 fn packetize(&mut self, mtu: usize, payload: &[u8]) -> Result<Vec<Vec<u8>>, PacketError> {
213 if payload.is_empty() || mtu == 0 {
214 return Ok(vec![]);
215 }
216 
217 if !self.initialized {
218 #[cfg(test)]
219 {
220 self.picture_id = self.initial_picture_id;
221 }
222 #[cfg(not(test))]
223 {
224 use str0m_proto::NonCryptographicRng;
225 self.picture_id = NonCryptographicRng::u16() & 0x7FFF;
226 }
227 self.initialized = true;
228 }
229 
230 match self.mode {
231 Vp9PacketizerMode::NonFlexible => self.packetize_non_flexible(mtu, payload),
232 Vp9PacketizerMode::Flexible => self.packetize_flexible(mtu, payload),
233 }
234 }
235 
236 fn is_marker(&mut self, _data: &[u8], _previous: Option<&[u8]>, last: bool) -> bool {
237 last
238 }
239}
240 
241impl Vp9Packetizer {
242 /// Non-flexible mode (F=0) packetization.
243 ///
244 /// 5-8 byte header with layer indices and scalability structure.
245 /// Compatible with all major browsers including Safari.
246 ///
247 /// ```text
248 /// 0 1 2 3 4 5 6 7
249 /// +-+-+-+-+-+-+-+-+
250 /// |I|P|L|F|B|E|V|Z| (REQUIRED)
251 /// +-+-+-+-+-+-+-+-+
252 /// I: |M| PICTURE ID | (RECOMMENDED)
253 /// +-+-+-+-+-+-+-+-+
254 /// M: | EXTENDED PID | (RECOMMENDED)
255 /// +-+-+-+-+-+-+-+-+
256 /// L: | tid |U| SID |D| (CONDITIONALLY RECOMMENDED)
257 /// +-+-+-+-+-+-+-+-+
258 /// | tl0picidx | (CONDITIONALLY REQUIRED)
259 /// +-+-+-+-+-+-+-+-+
260 /// V: | SS |
261 /// | .. |
262 /// +-+-+-+-+-+-+-+-+
263 /// ```
264 fn packetize_non_flexible(
265 &mut self,
266 mtu: usize,
267 payload: &[u8],
268 ) -> Result<Vec<Vec<u8>>, PacketError> {
269 // Detect keyframe from VP9 bitstream to set P flag correctly.
270 let is_keyframe = detect_vp9_keyframe_bitstream(payload);
271 
272 // tl0picidx increments on every TID=0 frame. Since we always emit TID=0,
273 // this increments on every frame per draft-ietf-payload-vp9, Section 6.3.
274 self.tl0picidx = self.tl0picidx.wrapping_add(1);
275 
276 // VP9_NON_FLEXIBLE_HEADER_SIZE is the max header (8 bytes, accounting for SS
277 // on keyframes). This ensures we never exceed MTU even on keyframe first-packets.
278 let max_fragment_size = mtu as isize - VP9_NON_FLEXIBLE_HEADER_SIZE as isize;
279 let mut payloads = vec![];
280 let mut payload_data_remaining = payload.len();
281 let mut payload_data_index = 0;
282 
283 if std::cmp::min(max_fragment_size, payload_data_remaining as isize) <= 0 {
284 return Ok(vec![]);
285 }
286 
287 while payload_data_remaining > 0 {
288 let current_fragment_size =
289 std::cmp::min(max_fragment_size as usize, payload_data_remaining);
290 let is_first = payload_data_index == 0;
291 let is_last = payload_data_remaining == current_fragment_size;
292 
293 // Non-keyframe: 5 bytes (flags + 15-bit PID + L + tl0picidx)
294 // Keyframe first packet: 6 bytes (+ 1 byte SS data, G=0)
295 let ss_size = if is_keyframe && is_first { 1 } else { 0 };
296 let header_size = 5 + ss_size;
297 let mut out = Vec::with_capacity(header_size + current_fragment_size);
298 
299 // Byte 0: I|P|L|F|B|E|V|Z
300 let mut flags = 0xA0u8; // I=1, L=1 (F=0 implicit)
301 if !is_keyframe {
302 flags |= 0x40; // P=1 for inter-predicted frames
303 }
304 if is_first {
305 flags |= 0x08; // B=1 (beginning of frame)
306 }
307 if is_last {
308 flags |= 0x04; // E=1 (end of frame)
309 }
310 if is_keyframe && is_first {
311 flags |= 0x02; // V=1 (scalability structure present)
312 }
313 out.push(flags);
314 
315 // Bytes 1-2: 15-bit picture ID (M=1)
316 out.push((self.picture_id >> 8) as u8 | 0x80);
317 out.push((self.picture_id & 0xFF) as u8);
318 
319 // Bytes 3-4: Layer indices (L=1, non-flexible mode)
320 // TID(3)|U(1)|SID(3)|D(1) = TID=0, U=1, SID=0, D=0
321 out.push(0x10);
322 // tl0picidx (required when F=0 and L=1)
323 out.push(self.tl0picidx);
324 
325 // SS byte on keyframe first packet: N_S=0, Y=0, G=0 (no GOF). A single-layer stream has no
326 // GOF to advertise; a G=1/R=0 GOF mis-signals references (see commit msg). This matches what
327 // libwebrtc emits for non-scalable VP9 (g_bit = num_frames_in_gof > 0). RFC 9628 §4.2.
328 if is_keyframe && is_first {
329 out.push(0x00);
330 }
331 
332 out.extend_from_slice(
333 &payload[payload_data_index..payload_data_index + current_fragment_size],
334 );
335 
336 payloads.push(out);
337 
338 payload_data_remaining -= current_fragment_size;
339 payload_data_index += current_fragment_size;
340 }
341 
342 self.picture_id += 1;
343 self.picture_id &= 0x7FFF;
344 
345 Ok(payloads)
346 }
347 
348 /// Flexible mode (F=1) packetization.
349 ///
350 /// Minimal 3-byte header (flags + 15-bit PID). Simpler but may cause
351 /// issues with Safari which drops inter-frames.
352 ///
353 /// ```text
354 /// 0 1 2 3 4 5 6 7
355 /// +-+-+-+-+-+-+-+-+
356 /// |I|P|L|F|B|E|V|Z| (REQUIRED)
357 /// +-+-+-+-+-+-+-+-+
358 /// I: |M| PICTURE ID | (RECOMMENDED)
359 /// +-+-+-+-+-+-+-+-+
360 /// M: | EXTENDED PID | (RECOMMENDED)
361 /// +-+-+-+-+-+-+-+-+
362 /// ```
363 fn packetize_flexible(
364 &mut self,
365 mtu: usize,
366 payload: &[u8],
367 ) -> Result<Vec<Vec<u8>>, PacketError> {
368 let max_fragment_size = mtu as isize - VP9_FLEXIBLE_HEADER_SIZE as isize;
369 let mut payloads = vec![];
370 let mut payload_data_remaining = payload.len();
371 let mut payload_data_index = 0;
372 
373 if std::cmp::min(max_fragment_size, payload_data_remaining as isize) <= 0 {
374 return Ok(vec![]);
375 }
376 
377 while payload_data_remaining > 0 {
378 let current_fragment_size =
379 std::cmp::min(max_fragment_size as usize, payload_data_remaining);
380 let is_first = payload_data_index == 0;
381 let is_last = payload_data_remaining == current_fragment_size;
382 
383 let mut out = Vec::with_capacity(VP9_FLEXIBLE_HEADER_SIZE + current_fragment_size);
384 
385 // Byte 0: I|P|L|F|B|E|V|Z — I=1, F=1
386 let mut flags = 0x90u8; // I=1, F=1
387 if is_first {
388 flags |= 0x08; // B=1
389 }
390 if is_last {
391 flags |= 0x04; // E=1
392 }
393 out.push(flags);
394 
395 // Bytes 1-2: 15-bit picture ID (M=1)
396 out.push((self.picture_id >> 8) as u8 | 0x80);
397 out.push((self.picture_id & 0xFF) as u8);
398 
399 out.extend_from_slice(
400 &payload[payload_data_index..payload_data_index + current_fragment_size],
401 );
402 
403 payloads.push(out);
404 
405 payload_data_remaining -= current_fragment_size;
406 payload_data_index += current_fragment_size;
407 }
408 
409 self.picture_id += 1;
410 self.picture_id &= 0x7FFF;
411 
412 Ok(payloads)
413 }
414}
415 
416/// Depacketizes VP9 RTP packets.
417#[derive(PartialEq, Eq, Debug, Default, Clone)]
418pub struct Vp9Depacketizer {
419 /// picture ID is present
420 pub i: bool,
421 /// inter-picture predicted frame.
422 pub p: bool,
423 /// layer indices present
424 pub l: bool,
425 /// flexible mode
426 pub f: bool,
427 /// start of frame. beginning of new vp9 frame
428 pub b: bool,
429 /// end of frame
430 pub e: bool,
431 /// scalability structure (SS) present
432 pub v: bool,
433 /// Not a reference frame for upper spatial layers
434 pub z: bool,
435 
436 /// Recommended headers
437 /// 7 or 16 bits, picture ID.
438 pub picture_id: u16,
439 
440 /// Conditionally recommended headers
441 /// Temporal layer ID
442 pub tid: u8,
443 /// Switching up point
444 pub u: bool,
445 /// Spatial layer ID
446 pub sid: u8,
447 /// Inter-layer dependency used
448 pub d: bool,
449 
450 /// Conditionally required headers
451 /// Reference index (F=1)
452 pub pdiff: [u8; MAX_VP9REF_PICS],
453 /// Temporal layer zero index (F=0)
454 pub tl0picidx: u8,
455 
456 /// Scalability structure headers
457 /// N_S + 1 indicates the number of spatial layers present in the VP9 stream
458 pub ns: u8,
459 /// Each spatial layer's frame resolution present
460 pub y: bool,
461 /// PG description present flag.
462 pub g: bool,
463 /// N_G indicates the number of pictures in a Picture Group (PG)
464 pub ng: u8,
465 pub width: [Option<u16>; MAX_SPATIAL_LAYERS],
466 pub height: [Option<u16>; MAX_SPATIAL_LAYERS],
467 /// Temporal layer ID of pictures in a Picture Group
468 pub pgtid: Vec<u8>,
469 /// Switching up point of pictures in a Picture Group
470 pub pgu: Vec<bool>,
471 /// Reference indices of pictures in a Picture Group
472 pub pgpdiff: Vec<Vec<u8>>,
473}
474 
475impl Depacketizer for Vp9Depacketizer {
476 fn out_size_hint(&self, packets_size: usize) -> Option<usize> {
477 Some(packets_size)
478 }
479 
480 /// depacketize parses the passed byte slice and stores the result
481 /// in the Vp9Packet this method is called upon
482 fn depacketize(
483 &mut self,
484 packet: &[u8],
485 out: &mut Vec<u8>,
486 extra: &mut CodecExtra,
487 ) -> Result<(), PacketError> {
488 if packet.is_empty() {
489 return Err(PacketError::ErrShortPacket);
490 }
491 
492 let mut reader = (packet, 0);
493 let b = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
494 
495 self.i = (b & 0x80) != 0;
496 self.p = (b & 0x40) != 0;
497 self.l = (b & 0x20) != 0;
498 self.f = (b & 0x10) != 0;
499 self.b = (b & 0x08) != 0;
500 self.e = (b & 0x04) != 0;
501 self.v = (b & 0x02) != 0;
502 self.z = (b & 0x01) != 0;
503 
504 let mut payload_index = 1;
505 
506 if self.i {
507 payload_index = self.parse_picture_id(&mut reader, payload_index)?;
508 }
509 
510 if self.l {
511 payload_index = self.parse_layer_info(&mut reader, payload_index)?;
512 }
513 
514 if self.f && self.p {
515 payload_index = self.parse_ref_indices(&mut reader, payload_index)?;
516 }
517 
518 if self.v {
519 payload_index = self.parse_ssdata(&mut reader, payload_index)?;
520 }
521 
522 self.update_extra(extra, out.len(), packet.len(), payload_index)?;
523 
524 out.extend_from_slice(&packet[payload_index..]);
525 
526 Ok(())
527 }
528 
529 /// is_partition_head checks whether if this is a head of the VP9 partition
530 fn is_partition_head(&self, payload: &[u8]) -> bool {
531 if payload.is_empty() {
532 false
533 } else {
534 (payload[0] & 0x08) != 0
535 }
536 }
537 
538 fn is_partition_tail(&self, marker: bool, _payload: &[u8]) -> bool {
539 marker
540 }
541}
542 
543impl Vp9Depacketizer {
544 /// Updates provided [`CodecExtra`].
545 /// __MUST__ be called after the all transformations of `payload_index`.
546 fn update_extra(
547 &self,
548 extra: &mut CodecExtra,
549 out_len: usize,
550 packet_len: usize,
551 payload_index: usize,
552 ) -> Result<(), PacketError> {
553 let mut vp9_extra = match extra {
554 CodecExtra::Vp9(e) => *e,
555 _ => Vp9CodecExtra::default(),
556 };
557 
558 if self.l {
559 let new_stop = out_len + packet_len - payload_index;
560 
561 if let Some(stop) = vp9_extra.layers_scheme[self.sid as usize] {
562 if stop != out_len {
563 return Err(PacketError::ErrVP9CorruptedPacket);
564 }
565 }
566 
567 vp9_extra.layers_scheme[self.sid as usize] = Some(new_stop);
568 vp9_extra.tid = Some(self.tid);
569 
570 if !self.f {
571 vp9_extra.tl0_picture_id = Some(self.tl0picidx);
572 }
573 }
574 
575 vp9_extra.pid = self.picture_id;
576 vp9_extra
577 .layers_widths
578 .copy_from_slice(&self.width[..MAX_SPATIAL_LAYERS]);
579 vp9_extra
580 .layers_heights
581 .copy_from_slice(&self.height[..MAX_SPATIAL_LAYERS]);
582 vp9_extra.is_keyframe |= !self.p && (self.sid == 0 || !self.l) && self.b;
583 
584 *extra = CodecExtra::Vp9(vp9_extra);
585 
586 Ok(())
587 }
588 
589 // Picture ID:
590 //
591 // +-+-+-+-+-+-+-+-+
592 // I: |M| PICTURE ID | M:0 => picture id is 7 bits.
593 // +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits.
594 // M: | EXTENDED PID |
595 // +-+-+-+-+-+-+-+-+
596 //
597 fn parse_picture_id(
598 &mut self,
599 reader: &mut dyn BitRead,
600 mut payload_index: usize,
601 ) -> Result<usize, PacketError> {
602 if reader.remaining_bits() == 0 {
603 return Err(PacketError::ErrShortPacket);
604 }
605 let b = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
606 payload_index += 1;
607 // PID present?
608 if (b & 0x80) != 0 {
609 if reader.remaining_bits() == 0 {
610 return Err(PacketError::ErrShortPacket);
611 }
612 // M == 1, PID is 15bit
613 let x = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
614 self.picture_id = (((b & 0x7f) as u16) << 8) | (x as u16);
615 payload_index += 1;
616 } else {
617 self.picture_id = (b & 0x7F) as u16;
618 }
619 
620 Ok(payload_index)
621 }
622 
623 fn parse_layer_info(
624 &mut self,
625 reader: &mut dyn BitRead,
626 mut payload_index: usize,
627 ) -> Result<usize, PacketError> {
628 payload_index = self.parse_layer_info_common(reader, payload_index)?;
629 
630 if self.f {
631 Ok(payload_index)
632 } else {
633 self.parse_layer_info_non_flexible_mode(reader, payload_index)
634 }
635 }
636 
637 // Layer indices (flexible mode):
638 //
639 // +-+-+-+-+-+-+-+-+
640 // L: | T |U| S |D|
641 // +-+-+-+-+-+-+-+-+
642 //
643 fn parse_layer_info_common(
644 &mut self,
645 reader: &mut dyn BitRead,
646 mut payload_index: usize,
647 ) -> Result<usize, PacketError> {
648 if reader.remaining_bits() == 0 {
649 return Err(PacketError::ErrShortPacket);
650 }
651 let b = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
652 payload_index += 1;
653 
654 self.tid = b >> 5;
655 self.u = b & 0x10 != 0;
656 self.sid = (b >> 1) & 0x7;
657 self.d = b & 0x01 != 0;
658 
659 if self.sid as usize >= MAX_SPATIAL_LAYERS {
660 Err(PacketError::ErrTooManySpatialLayers)
661 } else {
662 Ok(payload_index)
663 }
664 }
665 
666 // Layer indices (non-flexible mode):
667 //
668 // +-+-+-+-+-+-+-+-+
669 // L: | T |U| S |D|
670 // +-+-+-+-+-+-+-+-+
671 // | tl0picidx |
672 // +-+-+-+-+-+-+-+-+
673 //
674 fn parse_layer_info_non_flexible_mode(
675 &mut self,
676 reader: &mut dyn BitRead,
677 mut payload_index: usize,
678 ) -> Result<usize, PacketError> {
679 if reader.remaining_bits() == 0 {
680 return Err(PacketError::ErrShortPacket);
681 }
682 self.tl0picidx = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
683 payload_index += 1;
684 Ok(payload_index)
685 }
686 
687 // Reference indices:
688 //
689 // +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index
690 // P,F: | P_DIFF |N| up to 3 times has to be specified.
691 // +-+-+-+-+-+-+-+-+ N=1: An additional P_DIFF follows
692 // current P_DIFF.
693 //
694 fn parse_ref_indices(
695 &mut self,
696 reader: &mut dyn BitRead,
697 mut payload_index: usize,
698 ) -> Result<usize, PacketError> {
699 let mut b = 1u8;
700 let mut num_ref_pics = 0;
701 while (b & 0x1) != 0 {
702 if num_ref_pics == MAX_VP9REF_PICS {
703 return Err(PacketError::ErrTooManyPDiff);
704 }
705 
706 if reader.remaining_bits() == 0 {
707 return Err(PacketError::ErrShortPacket);
708 }
709 b = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
710 payload_index += 1;
711 
712 self.pdiff[num_ref_pics] = b >> 1;
713 num_ref_pics += 1;
714 }
715 
716 Ok(payload_index)
717 }
718 
719 // Scalability structure (SS):
720 //
721 // +-+-+-+-+-+-+-+-+
722 // V: | N_S |Y|G|-|-|-|
723 // +-+-+-+-+-+-+-+-+ -|
724 // Y: | WIDTH | (OPTIONAL) .
725 // + + .
726 // | | (OPTIONAL) .
727 // +-+-+-+-+-+-+-+-+ . N_S + 1 times
728 // | HEIGHT | (OPTIONAL) .
729 // + + .
730 // | | (OPTIONAL) .
731 // +-+-+-+-+-+-+-+-+ -|
732 // G: | N_G | (OPTIONAL)
733 // +-+-+-+-+-+-+-+-+ -|
734 // N_G: | T |U| R |-|-| (OPTIONAL) .
735 // +-+-+-+-+-+-+-+-+ -| . N_G times
736 // | P_DIFF | (OPTIONAL) . R times .
737 // +-+-+-+-+-+-+-+-+ -| -|
738 //
739 fn parse_ssdata(
740 &mut self,
741 reader: &mut dyn BitRead,
742 mut payload_index: usize,
743 ) -> Result<usize, PacketError> {
744 if reader.remaining_bits() == 0 {
745 return Err(PacketError::ErrShortPacket);
746 }
747 
748 let b = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
749 payload_index += 1;
750 
751 self.ns = b >> 5;
752 self.y = b & 0x10 != 0;
753 self.g = (b >> 1) & 0x7 != 0;
754 
755 let ns = (self.ns + 1) as usize;
756 self.ng = 0;
757 
758 if ns > MAX_SPATIAL_LAYERS {
759 return Err(PacketError::ErrVP9CorruptedPacket);
760 }
761 
762 if self.y {
763 if reader.remaining_bits() < 4 * ns {
764 return Err(PacketError::ErrShortPacket);
765 }
766 
767 for i in 0..ns {
768 self.width[i] = Some(reader.get_u16().ok_or(PacketError::ErrShortPacket)?);
769 self.height[i] = Some(reader.get_u16().ok_or(PacketError::ErrShortPacket)?);
770 }
771 payload_index += 4 * ns;
772 }
773 
774 if self.g {
775 if reader.remaining_bits() == 0 {
776 return Err(PacketError::ErrShortPacket);
777 }
778 
779 self.ng = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
780 payload_index += 1;
781 }
782 
783 for i in 0..self.ng as usize {
784 if reader.remaining_bits() == 0 {
785 return Err(PacketError::ErrShortPacket);
786 }
787 let b = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
788 payload_index += 1;
789 
790 self.pgtid.push(b >> 5);
791 self.pgu.push(b & 0x10 != 0);
792 
793 let r = ((b >> 2) & 0x3) as usize;
794 if reader.remaining_bits() < r {
795 return Err(PacketError::ErrShortPacket);
796 }
797 
798 self.pgpdiff.push(vec![]);
799 for _ in 0..r {
800 let b = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
801 payload_index += 1;
802 
803 self.pgpdiff[i].push(b);
804 }
805 }
806 
807 Ok(payload_index)
808 }
809}
810 
811#[cfg(test)]
812mod test {
813 use super::*;
814 
815 #[test]
816 fn test_vp9_packet_unmarshal() -> Result<(), PacketError> {
817 let tests: Vec<(&str, &[u8], Vp9Depacketizer, &[u8], Option<PacketError>)> = vec![
818 (
819 "Empty",
820 &[],
821 Vp9Depacketizer::default(),
822 &[],
823 Some(PacketError::ErrShortPacket),
824 ),
825 (
826 "NonFlexible",
827 &[0x00, 0xAA],
828 Vp9Depacketizer::default(),
829 &[0xAA],
830 None,
831 ),
832 (
833 "NonFlexiblePictureID",
834 &[0x80, 0x02, 0xAA],
835 Vp9Depacketizer {
836 i: true,
837 picture_id: 0x02,
838 ..Default::default()
839 },
840 &[0xAA],
841 None,
842 ),
843 (
844 "NonFlexiblePictureIDExt",
845 &[0x80, 0x81, 0xFF, 0xAA],
846 Vp9Depacketizer {
847 i: true,
848 picture_id: 0x01FF,
849 ..Default::default()
850 },
851 &[0xAA],
852 None,
853 ),
854 (
855 "NonFlexiblePictureIDExt_ShortPacket0",
856 &[0x80, 0x81],
857 Vp9Depacketizer::default(),
858 &[],
859 Some(PacketError::ErrShortPacket),
860 ),
861 (
862 "NonFlexiblePictureIDExt_ShortPacket1",
863 &[0x80],
864 Vp9Depacketizer::default(),
865 &[],
866 Some(PacketError::ErrShortPacket),
867 ),
868 (
869 "NonFlexibleLayerIndicePictureID",
870 &[0xA0, 0x02, 0x23, 0x01, 0xAA],
871 Vp9Depacketizer {
872 i: true,
873 l: true,
874 picture_id: 0x02,
875 tid: 0x01,
876 sid: 0x01,
877 d: true,
878 tl0picidx: 0x01,
879 ..Default::default()
880 },
881 &[0xAA],
882 None,
883 ),
884 (
885 "FlexibleLayerIndicePictureID",
886 &[0xB0, 0x02, 0x23, 0x01, 0xAA],
887 Vp9Depacketizer {
888 f: true,
889 i: true,
890 l: true,
891 picture_id: 0x02,
892 tid: 0x01,
893 sid: 0x01,
894 d: true,
895 ..Default::default()
896 },
897 &[0x01, 0xAA],
898 None,
899 ),
900 (
901 "NonFlexibleLayerIndicePictureID_ShortPacket0",
902 &[0xA0, 0x02, 0x23],
903 Vp9Depacketizer::default(),
904 &[],
905 Some(PacketError::ErrShortPacket),
906 ),
907 (
908 "NonFlexibleLayerIndicePictureID_ShortPacket1",
909 &[0xA0, 0x02],
910 Vp9Depacketizer::default(),
911 &[],
912 Some(PacketError::ErrShortPacket),
913 ),
914 (
915 "FlexiblePictureIDRefIndex",
916 &[0xD0, 0x02, 0x03, 0x04, 0xAA],
917 Vp9Depacketizer {
918 i: true,
919 p: true,
920 f: true,
921 picture_id: 0x02,
922 pdiff: [0x01, 0x02, 0],
923 ..Default::default()
924 },
925 &[0xAA],
926 None,
927 ),
928 (
929 "FlexiblePictureIDRefIndex_TooManyPDiff",
930 &[0xD0, 0x02, 0x03, 0x05, 0x07, 0x09, 0x10, 0xAA],
931 Vp9Depacketizer::default(),
932 &[],
933 Some(PacketError::ErrTooManyPDiff),
934 ),
935 (
936 "FlexiblePictureIDRefIndexNoPayload",
937 &[0xD0, 0x02, 0x03, 0x04],
938 Vp9Depacketizer {
939 i: true,
940 p: true,
941 f: true,
942 picture_id: 0x02,
943 pdiff: [0x01, 0x02, 0],
944 ..Default::default()
945 },
946 &[],
947 None,
948 ),
949 (
950 "FlexiblePictureIDRefIndex_ShortPacket0",
951 &[0xD0, 0x02, 0x03],
952 Vp9Depacketizer::default(),
953 &[],
954 Some(PacketError::ErrShortPacket),
955 ),
956 (
957 "FlexiblePictureIDRefIndex_ShortPacket1",
958 &[0xD0, 0x02],
959 Vp9Depacketizer::default(),
960 &[],
961 Some(PacketError::ErrShortPacket),
962 ),
963 (
964 "FlexiblePictureIDRefIndex_ShortPacket2",
965 &[0xD0],
966 Vp9Depacketizer::default(),
967 &[],
968 Some(PacketError::ErrShortPacket),
969 ),
970 (
971 "ScalabilityStructureResolutionsNoPayload",
972 &[
973 0x0A,
974 (1 << 5) | (1 << 4), // NS:1 Y:1 G:0
975 (640 >> 8) as u8,
976 (640 & 0xff) as u8,
977 (360 >> 8) as u8,
978 (360 & 0xff) as u8,
979 (1280 >> 8) as u8,
980 (1280 & 0xff) as u8,
981 (720 >> 8) as u8,
982 (720 & 0xff) as u8,
983 ],
984 Vp9Depacketizer {
985 b: true,
986 v: true,
987 ns: 1,
988 y: true,
989 g: false,
990 ng: 0,
991 width: {
992 let mut res = [None; MAX_SPATIAL_LAYERS];
993 res[0] = Some(640);
994 res[1] = Some(1280);
995 
996 res
997 },
998 height: {
999 let mut res = [None; MAX_SPATIAL_LAYERS];
1000 res[0] = Some(360);
1001 res[1] = Some(720);
1002 
1003 res
1004 },
1005 ..Default::default()
1006 },
1007 &[],
1008 None,
1009 ),
1010 (
1011 "ScalabilityStructureNoPayload",
1012 &[
1013 0x0A,
1014 (1 << 5) | (1 << 3), // NS:1 Y:0 G:1
1015 2,
1016 (1 << 4), // T:0 U:1 R:0 -
1017 (2 << 5) | (1 << 2), // T:2 U:0 R:1 -
1018 33,
1019 ],
1020 Vp9Depacketizer {
1021 b: true,
1022 v: true,
1023 ns: 1,
1024 y: false,
1025 g: true,
1026 ng: 2,
1027 pgtid: vec![0, 2],
1028 pgu: vec![true, false],
1029 pgpdiff: vec![vec![], vec![33]],
1030 ..Default::default()
1031 },
1032 &[],
1033 None,
1034 ),
1035 (
1036 "ScalabilityStructureNoPictureGroupNoPayload",
1037 &[
1038 0x0A,
1039 1 << 3, // NS:0 Y:0 G:1
1040 0, // N_G=0, valid: single temporal layer / no fixed dependency info
1041 ],
1042 Vp9Depacketizer {
1043 b: true,
1044 v: true,
1045 ns: 0,
1046 y: false,
1047 g: true,
1048 ng: 0,
1049 ..Default::default()
1050 },
1051 &[],
1052 None,
1053 ),
1054 (
1055 "ScalabilityStructureThreeLayers",
1056 &[
1057 0x0A,
1058 (2 << 5) | (1 << 4), // NS:2 Y:1 G:0
1059 (480 >> 8) as u8,
1060 (480 & 0xff) as u8,
1061 (270 >> 8) as u8,
1062 (270 & 0xff) as u8,
1063 (960 >> 8) as u8,
1064 (960 & 0xff) as u8,
1065 (540 >> 8) as u8,
1066 (540 & 0xff) as u8,
1067 (1920 >> 8) as u8,
1068 (1920 & 0xff) as u8,
1069 (1080 >> 8) as u8,
1070 (1080 & 0xff) as u8,
1071 ],
1072 Vp9Depacketizer {
1073 b: true,
1074 v: true,
1075 ns: 2,
1076 y: true,
1077 g: false,
1078 ng: 0,
1079 width: [Some(480), Some(960), Some(1920)],
1080 height: [Some(270), Some(540), Some(1080)],
1081 ..Default::default()
1082 },
1083 &[],
1084 None,
1085 ),
1086 (
1087 "ParseRefIdx",
1088 &[
1089 0xD8, /* I:1 P:1 L:0 F:1
1090 * B:1 E:0 V:0 Z:0 */
1091 (0x80 | ((17 >> 8) & 0x7F)) as u8, // Two byte pictureID.
1092 17, // TL0PICIDX
1093 (17 << 1) | 1, // P_DIFF N:1
1094 (18 << 1) | 1, // P_DIFF N:1
1095 (127 << 1) | 0, // P_DIFF N:0
1096 ],
1097 Vp9Depacketizer {
1098 i: true,
1099 p: true,
1100 f: true,
1101 b: true,
1102 picture_id: 17,
1103 pdiff: [17, 18, 127],
1104 ..Default::default()
1105 },
1106 &[],
1107 None,
1108 ),
1109 ];
1110 
1111 for (name, b, pkt, expected, err) in tests {
1112 let mut p = Vp9Depacketizer::default();
1113 
1114 if let Some(expected) = err {
1115 let mut payload = Vec::new();
1116 let mut extra = CodecExtra::None;
1117 if let Err(actual) = p.depacketize(b, &mut payload, &mut extra) {
1118 assert_eq!(
1119 expected, actual,
1120 "{name}: expected {expected}, but got {actual}"
1121 );
1122 } else {
1123 panic!("{name}: expected error, but got passed");
1124 }
1125 } else {
1126 let mut payload = Vec::new();
1127 let mut extra = CodecExtra::None;
1128 p.depacketize(b, &mut payload, &mut extra)?;
1129 assert_eq!(pkt, p, "{name}: expected {pkt:?}, but got {p:?}");
1130 assert_eq!(payload, expected);
1131 }
1132 }
1133 
1134 Ok(())
1135 }
1136 
1137 #[test]
1138 fn test_vp9_packetizer_payload() -> Result<(), PacketError> {
1139 let mut r0 = 8692;
1140 let mut rands = vec![];
1141 for _ in 0..10 {
1142 rands.push(vec![(r0 >> 8) as u8 | 0x80, (r0 & 0xFF) as u8]);
1143 r0 += 1;
1144 }
1145 
1146 // Non-flexible mode (F=0) header layout:
1147 // Byte 0: I|P|L|F|B|E|V|Z (I=1, L=1 always set)
1148 // Bytes 1-2: 15-bit PID (M=1)
1149 // Byte 3: TID(3)|U(1)|SID(3)|D(1) = 0x10 (TID=0, U=1, SID=0, D=0)
1150 // Byte 4: tl0picidx (wrapping u8, increments every frame)
1151 // [Byte 5]: SS data on keyframe first packet only (N_S=0, Y=0, G=0)
1152 //
1153 // Flags byte (non-keyframe): I=1,P=1,L=1,F=0 = 0xE0 base
1154 // + B=0x08, E=0x04
1155 // B+E: 0xEC, B only: 0xE8, E only: 0xE4, neither: 0xE0
1156 //
1157 // Test payloads (0x01, 0x02, etc.) don't match VP9 bitstream keyframe
1158 // pattern (frame_marker != 0b10), so they are treated as inter-frames (P=1).
1159 // tl0picidx starts at 0 and increments by 1 per frame.
1160 let tests: Vec<(&str, Vec<Vec<u8>>, usize, Vec<Vec<u8>>)> = vec![
1161 ("NilPayload", vec![vec![]], 100, vec![]),
1162 ("SmallMTU", vec![vec![0x00, 0x00]], 1, vec![]),
1163 ("NegativeMTU", vec![vec![0x00, 0x00]], 0, vec![]),
1164 (
1165 // Inter-frame, single packet: B+E set, tl0picidx=1
1166 "OnePacket",
1167 vec![vec![0x01, 0x02]],
1168 10,
1169 vec![vec![0xEC, rands[0][0], rands[0][1], 0x10, 0x01, 0x01, 0x02]],
1170 ),
1171 (
1172 // Inter-frame, 2 packets: MTU=9 → max_frag=1, tl0picidx=1
1173 "TwoPackets",
1174 vec![vec![0x01, 0x02]],
1175 9,
1176 vec![
1177 vec![0xE8, rands[0][0], rands[0][1], 0x10, 0x01, 0x01],
1178 vec![0xE4, rands[0][0], rands[0][1], 0x10, 0x01, 0x02],
1179 ],
1180 ),
1181 (
1182 // Inter-frame, 3 packets: MTU=9 → max_frag=1, tl0picidx=1
1183 "ThreePackets",
1184 vec![vec![0x01, 0x02, 0x03]],
1185 9,
1186 vec![
1187 vec![0xE8, rands[0][0], rands[0][1], 0x10, 0x01, 0x01],
1188 vec![0xE0, rands[0][0], rands[0][1], 0x10, 0x01, 0x02],
1189 vec![0xE4, rands[0][0], rands[0][1], 0x10, 0x01, 0x03],
1190 ],
1191 ),
1192 (
1193 // Two inter-frames: frame1 tl0picidx=1, frame2 tl0picidx=2
1194 "TwoFramesFourPackets",
1195 vec![vec![0x01, 0x02, 0x03], vec![0x04]],
1196 10,
1197 vec![
1198 vec![0xE8, rands[0][0], rands[0][1], 0x10, 0x01, 0x01, 0x02],
1199 vec![0xE4, rands[0][0], rands[0][1], 0x10, 0x01, 0x03],
1200 vec![0xEC, rands[1][0], rands[1][1], 0x10, 0x02, 0x04],
1201 ],
1202 ),
1203 ];
1204 
1205 for (name, bs, mtu, expected) in tests {
1206 let mut pck = Vp9Packetizer {
1207 initial_picture_id: 8692,
1208 ..Default::default()
1209 };
1210 
1211 let mut actual = vec![];
1212 for b in &bs {
1213 actual.extend(pck.packetize(mtu, b)?);
1214 }
1215 assert_eq!(expected, actual, "{name}: Payloaded packet");
1216 }
1217 
1218 //"PictureIDOverflow"
1219 {
1220 let mut pck = Vp9Packetizer {
1221 initial_picture_id: 8692,
1222 ..Default::default()
1223 };
1224 let mut p_prev = Vp9Depacketizer::default();
1225 for i in 0..0x8000 {
1226 let res = pck.packetize(9, &[0x01])?;
1227 let mut p = Vp9Depacketizer::default();
1228 let mut payload = Vec::new();
1229 let mut extra = CodecExtra::None;
1230 p.depacketize(&res[0], &mut payload, &mut extra)?;
1231 
1232 if i > 0 {
1233 if p_prev.picture_id == 0x7FFF {
1234 assert_eq!(
1235 p.picture_id, 0,
1236 "Picture ID next to 0x7FFF must be 0, got {}",
1237 p.picture_id
1238 );
1239 } else if p_prev.picture_id + 1 != p.picture_id {
1240 panic!(
1241 "Picture ID next must be incremented by 1: {} -> {}",
1242 p_prev.picture_id, p.picture_id,
1243 );
1244 }
1245 }
1246 
1247 p_prev = p;
1248 }
1249 }
1250 
1251 Ok(())
1252 }
1253 
1254 #[test]
1255 fn test_vp9_packetizer_keyframe() -> Result<(), PacketError> {
1256 let mut pck = Vp9Packetizer {
1257 initial_picture_id: 100,
1258 ..Default::default()
1259 };
1260 
1261 // VP9 profile 0 keyframe bitstream: 0x82 = 0b10_00_0_0_10
1262 // frame_marker=10, profile=00, show_existing=0, frame_type=0 (KEY)
1263 let keyframe = vec![0x82, 0xAA, 0xBB];
1264 let packets = pck.packetize(20, &keyframe)?;
1265 
1266 assert_eq!(packets.len(), 1);
1267 let pkt = &packets[0];
1268 // Flags: I=1,P=0,L=1,F=0,B=1,E=1,V=1,Z=0 = 0xAE
1269 assert_eq!(pkt[0], 0xAE, "Keyframe flags should be 0xAE");
1270 // PID
1271 assert_eq!(pkt[1], (100 >> 8) as u8 | 0x80);
1272 assert_eq!(pkt[2], 100 & 0xFF);
1273 // L byte: TID=0, U=1, SID=0, D=0
1274 assert_eq!(pkt[3], 0x10);
1275 // tl0picidx = 1 (first frame)
1276 assert_eq!(pkt[4], 1);
1277 // SS: N_S=0, Y=0, G=0 (no GOF) → 0x00 (1 byte)
1278 assert_eq!(pkt[5], 0x00);
1279 // Payload starts right after the 1-byte SS
1280 assert_eq!(&pkt[6..], &keyframe[..]);
1281 
1282 // Parse-side: the emitted SS decodes as a single-layer, no-GOF structure (N_S=0, Y/G false).
1283 let mut dep = Vp9Depacketizer::default();
1284 let mut out = Vec::new();
1285 let mut extra = CodecExtra::None;
1286 dep.depacketize(pkt, &mut out, &mut extra)?;
1287 assert!(dep.v && dep.ns == 0 && !dep.y && !dep.g && dep.ng == 0);
1288 assert_eq!(&out[..], &keyframe[..]);
1289 
1290 // Now an inter-frame: 0x84 = 0b10_00_0_1_00 (frame_type=1)
1291 let inter = vec![0x84, 0xCC];
1292 let packets = pck.packetize(20, &inter)?;
1293 assert_eq!(packets.len(), 1);
1294 let pkt = &packets[0];
1295 // Flags: I=1,P=1,L=1,F=0,B=1,E=1,V=0,Z=0 = 0xEC
1296 assert_eq!(pkt[0], 0xEC, "Inter-frame flags should be 0xEC");
1297 // tl0picidx = 2 (second frame, both TID=0 so both increment)
1298 assert_eq!(pkt[4], 2);
1299 // No SS data — payload starts at byte 5
1300 assert_eq!(&pkt[5..], &inter[..]);
1301 
1302 Ok(())
1303 }
1304 
1305 #[test]
1306 fn test_vp9_packetizer_fragmented_keyframe() -> Result<(), PacketError> {
1307 let mut pck = Vp9Packetizer {
1308 initial_picture_id: 50,
1309 ..Default::default()
1310 };
1311 
1312 // VP9 profile 0 keyframe: 0x82 = frame_marker=10, profile=00, show_existing=0, frame_type=0
1313 // 10 bytes of payload, MTU=12 → max_frag = 12 - 8 = 4 (conservative reserve)
1314 // First packet: 6-byte header (with 1-byte G=0 SS) + 4 bytes payload = 10 ≤ MTU
1315 // Second packet: 5-byte header + 4 bytes payload = 9 ≤ MTU
1316 // Third packet: 5-byte header + 2 bytes payload = 7 ≤ MTU
1317 let keyframe = vec![0x82, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09];
1318 let packets = pck.packetize(12, &keyframe)?;
1319 
1320 assert_eq!(
1321 packets.len(),
1322 3,
1323 "10-byte keyframe at MTU=12 should produce 3 packets"
1324 );
1325 
1326 // Packet 1: B=1, E=0, V=1 (keyframe first)
1327 // Flags: I=1,P=0,L=1,F=0,B=1,E=0,V=1,Z=0 = 0xAA
1328 assert_eq!(packets[0][0], 0xAA);
1329 assert_eq!(packets[0].len(), 10); // 6-byte header + 4-byte payload
1330 assert_eq!(packets[0][3], 0x10); // L byte
1331 assert_eq!(packets[0][4], 1); // tl0picidx
1332 assert_eq!(packets[0][5], 0x00); // SS byte: N_S=0, Y=0, G=0 (no GOF)
1333 assert_eq!(&packets[0][6..], &keyframe[0..4]); // payload
1334 
1335 // Packet 2: B=0, E=0, V=0 (middle)
1336 // Flags: I=1,P=0,L=1,F=0,B=0,E=0,V=0,Z=0 = 0xA0
1337 assert_eq!(packets[1][0], 0xA0);
1338 assert_eq!(packets[1].len(), 9); // 5-byte header + 4-byte payload
1339 assert_eq!(packets[1][4], 1); // same tl0picidx
1340 assert_eq!(&packets[1][5..], &keyframe[4..8]);
1341 
1342 // Packet 3: B=0, E=1, V=0 (last)
1343 // Flags: I=1,P=0,L=1,F=0,B=0,E=1,V=0,Z=0 = 0xA4
1344 assert_eq!(packets[2][0], 0xA4);
1345 assert_eq!(packets[2].len(), 7); // 5-byte header + 2-byte payload
1346 assert_eq!(&packets[2][5..], &keyframe[8..10]);
1347 
1348 // Verify none exceed MTU
1349 for (i, pkt) in packets.iter().enumerate() {
1350 assert!(
1351 pkt.len() <= 12,
1352 "Packet {i} exceeds MTU: {} > 12",
1353 pkt.len()
1354 );
1355 }
1356 
1357 // Verify depacketizer round-trip: all 3 packets produce original payload
1358 let mut full_payload = Vec::new();
1359 let mut extra = CodecExtra::None;
1360 for pkt in &packets {
1361 let mut depkt = Vp9Depacketizer::default();
1362 depkt.depacketize(pkt, &mut full_payload, &mut extra)?;
1363 }
1364 assert_eq!(full_payload, keyframe);
1365 
1366 Ok(())
1367 }
1368 
1369 #[test]
1370 fn test_vp9_tl0picidx_wrapping() -> Result<(), PacketError> {
1371 let mut pck = Vp9Packetizer {
1372 initial_picture_id: 0,
1373 tl0picidx: 254, // Start near wrap point
1374 ..Default::default()
1375 };
1376 
1377 // Frame 1: tl0picidx wraps 254 → 255
1378 let packets = pck.packetize(20, &[0x01])?;
1379 assert_eq!(packets[0][4], 255);
1380 
1381 // Frame 2: tl0picidx wraps 255 → 0
1382 let packets = pck.packetize(20, &[0x01])?;
1383 assert_eq!(packets[0][4], 0);
1384 
1385 // Frame 3: 0 → 1
1386 let packets = pck.packetize(20, &[0x01])?;
1387 assert_eq!(packets[0][4], 1);
1388 
1389 Ok(())
1390 }
1391 
1392 #[test]
1393 fn test_vp9_packetizer_flexible_mode() -> Result<(), PacketError> {
1394 let mut r0 = 8692;
1395 let mut rands = vec![];
1396 for _ in 0..10 {
1397 rands.push(vec![(r0 >> 8) as u8 | 0x80, (r0 & 0xFF) as u8]);
1398 r0 += 1;
1399 }
1400 
1401 // With VP9_FLEXIBLE_HEADER_SIZE=3, min productive MTU is 4.
1402 // Flexible mode: 3 bytes header (flags + 15-bit PID)
1403 //
1404 // Flags byte: I|P|L|F|B|E|V|Z
1405 // I=1(0x80), F=1(0x10), B=first(0x08), E=last(0x04)
1406 // B+E: 0x80|0x10|0x08|0x04 = 0x9C
1407 // B: 0x80|0x10|0x08 = 0x98
1408 // E: 0x80|0x10|0x04 = 0x94
1409 // none:0x80|0x10 = 0x90
1410 let tests: Vec<(&str, Vec<Vec<u8>>, usize, Vec<Vec<u8>>)> = vec![
1411 ("NilPayload", vec![vec![]], 100, vec![]),
1412 ("SmallMTU", vec![vec![0x00, 0x00]], 1, vec![]),
1413 ("NegativeMTU", vec![vec![0x00, 0x00]], 0, vec![]),
1414 (
1415 "OnePacket",
1416 vec![vec![0x01, 0x02]],
1417 10,
1418 vec![vec![0x9C, rands[0][0], rands[0][1], 0x01, 0x02]],
1419 ),
1420 (
1421 // MTU=4 → max_frag=1 → 2 packets
1422 "TwoPackets",
1423 vec![vec![0x01, 0x02]],
1424 4,
1425 vec![
1426 vec![0x98, rands[0][0], rands[0][1], 0x01],
1427 vec![0x94, rands[0][0], rands[0][1], 0x02],
1428 ],
1429 ),
1430 (
1431 // MTU=4 → max_frag=1 → 3 packets
1432 "ThreePackets",
1433 vec![vec![0x01, 0x02, 0x03]],
1434 4,
1435 vec![
1436 vec![0x98, rands[0][0], rands[0][1], 0x01],
1437 vec![0x90, rands[0][0], rands[0][1], 0x02],
1438 vec![0x94, rands[0][0], rands[0][1], 0x03],
1439 ],
1440 ),
1441 (
1442 // MTU=5 → max_frag=2 → frame1: 2 pkts, frame2: 1 pkt
1443 "TwoFramesFourPackets",
1444 vec![vec![0x01, 0x02, 0x03], vec![0x04]],
1445 5,
1446 vec![
1447 vec![0x98, rands[0][0], rands[0][1], 0x01, 0x02],
1448 vec![0x94, rands[0][0], rands[0][1], 0x03],
1449 vec![0x9C, rands[1][0], rands[1][1], 0x04],
1450 ],
1451 ),
1452 ];
1453 
1454 for (name, bs, mtu, expected) in tests {
1455 let mut pck = Vp9Packetizer {
1456 initial_picture_id: 8692,
1457 mode: Vp9PacketizerMode::Flexible,
1458 ..Default::default()
1459 };
1460 
1461 let mut actual = vec![];
1462 for b in &bs {
1463 actual.extend(pck.packetize(mtu, b)?);
1464 }
1465 assert_eq!(expected, actual, "{name}: Payloaded packet");
1466 }
1467 
1468 Ok(())
1469 }
1470 
1471 #[test]
1472 fn test_vp9_packetizer_with_mode() {
1473 let pck = Vp9Packetizer::with_mode(Vp9PacketizerMode::Flexible);
1474 assert_eq!(pck.mode, Vp9PacketizerMode::Flexible);
1475 
1476 let pck = Vp9Packetizer::with_mode(Vp9PacketizerMode::NonFlexible);
1477 assert_eq!(pck.mode, Vp9PacketizerMode::NonFlexible);
1478 
1479 let pck = Vp9Packetizer::default();
1480 assert_eq!(pck.mode, Vp9PacketizerMode::NonFlexible);
1481 }
1482 
1483 #[test]
1484 fn test_detect_vp9_keyframe_bitstream() {
1485 // Empty payload
1486 assert!(!detect_vp9_keyframe_bitstream(&[]));
1487 
1488 // Invalid frame_marker (not 0b10)
1489 assert!(!detect_vp9_keyframe_bitstream(&[0x00])); // frame_marker=00
1490 assert!(!detect_vp9_keyframe_bitstream(&[0xC0])); // frame_marker=11
1491 assert!(!detect_vp9_keyframe_bitstream(&[0x40])); // frame_marker=01
1492 
1493 // Profile 0 keyframe: 10_00_0_0_xx = 0x80..0x83
1494 assert!(detect_vp9_keyframe_bitstream(&[0x80]));
1495 assert!(detect_vp9_keyframe_bitstream(&[0x82]));
1496 
1497 // Profile 0 inter-frame: 10_00_0_1_xx = 0x84..0x87
1498 assert!(!detect_vp9_keyframe_bitstream(&[0x84]));
1499 assert!(!detect_vp9_keyframe_bitstream(&[0x86]));
1500 
1501 // Profile 0 show_existing_frame=1: 10_00_1_x_xx = 0x88..0x8F
1502 assert!(!detect_vp9_keyframe_bitstream(&[0x88]));
1503 assert!(!detect_vp9_keyframe_bitstream(&[0x8C]));
1504 
1505 // Profile 1 keyframe: 10_10_0_0_xx = 0xA0..0xA3
1506 assert!(detect_vp9_keyframe_bitstream(&[0xA0]));
1507 
1508 // Profile 1 inter-frame: 10_10_0_1_xx = 0xA4..0xA7
1509 assert!(!detect_vp9_keyframe_bitstream(&[0xA4]));
1510 
1511 // Profile 2 keyframe: 10_01_R_0_0_x
1512 // 0x90 = 10_01_0_0_00 → profile_high=1, show_existing=0, frame_type=0
1513 assert!(detect_vp9_keyframe_bitstream(&[0x90]));
1514 
1515 // Profile 2 inter-frame: 10_01_0_0_1_0 = 0x92
1516 assert!(!detect_vp9_keyframe_bitstream(&[0x92]));
1517 }
1518 
1519 #[test]
1520 fn test_vp9_partition_head_checker_is_partition_head() -> Result<(), PacketError> {
1521 let vp9 = Vp9Depacketizer::default();
1522 
1523 //"SmallPacket"
1524 assert!(
1525 !vp9.is_partition_head(&[]),
1526 "Small packet should not be the head of a new partition"
1527 );
1528 
1529 //"NormalPacket"
1530 assert!(
1531 vp9.is_partition_head(&[0x18, 0x00, 0x00]),
1532 "VP9 RTP packet with B flag should be head of a new partition"
1533 );
1534 assert!(
1535 !vp9.is_partition_head(&[0x10, 0x00, 0x00]),
1536 "VP9 RTP packet without B flag should not be head of a new partition"
1537 );
1538 
1539 Ok(())
1540 }
1541 
1542 #[test]
1543 fn test_detect_vp9_keyframe() {
1544 // Empty payload
1545 assert!(!detect_vp9_keyframe(&[]));
1546 
1547 // VP9 RTP descriptor byte 0: I|P|L|F|B|E|V|Z
1548 // P=0 (bit 6 clear) → keyframe
1549 // P=1 (bit 6 set) → inter-frame
1550 
1551 // Keyframes (P=0)
1552 assert!(detect_vp9_keyframe(&[0x80])); // I=1
1553 assert!(detect_vp9_keyframe(&[0xA0])); // I=1, L=1
1554 assert!(detect_vp9_keyframe(&[0xAE])); // I=1, L=1, B=1, E=1, V=1
1555 assert!(detect_vp9_keyframe(&[0x00])); // all flags clear
1556 
1557 // Inter-frames (P=1)
1558 assert!(!detect_vp9_keyframe(&[0xE8])); // I=1, P=1, L=1, B=1
1559 assert!(!detect_vp9_keyframe(&[0xEC])); // I=1, P=1, L=1, B=1, E=1
1560 assert!(!detect_vp9_keyframe(&[0x40])); // P=1 only
1561 assert!(!detect_vp9_keyframe(&[0xD5])); // I=1, P=1, F=1, E=1, Z=1
1562 }
1563 
1564 #[test]
1565 fn packetize_respects_mtu() -> Result<(), PacketError> {
1566 // VP9 profile 0 keyframe header followed by payload bytes.
1567 let mut payload = vec![0x82u8];
1568 payload.extend(std::iter::repeat(0xABu8).take(2000));
1569 for &mtu in &[100usize, 300, 600, 1200] {
1570 let mut pck = Vp9Packetizer {
1571 initial_picture_id: 100,
1572 ..Default::default()
1573 };
1574 let pkts = pck.packetize(mtu, &payload)?;
1575 assert!(!pkts.is_empty(), "VP9 produced no packets at mtu {mtu}");
1576 for (i, pkt) in pkts.iter().enumerate() {
1577 assert!(
1578 pkt.len() <= mtu,
1579 "VP9 packet {i} size {} > mtu {mtu}",
1580 pkt.len()
1581 );
1582 }
1583 }
1584 Ok(())
1585 }
1586}