Skip to content
File

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

rust1265 lines
1use std::error::Error;
2use std::fmt;
3 
4use crate::rtp_::{extend_u7, extend_u8, extend_u15};
5 
6use super::{BitRead, CodecExtra, Depacketizer, PacketError, Packetizer};
7 
8pub const VP8_HEADER_SIZE: usize = 1;
9 
10const PICTURE_ID_SHORT_MAX: u16 = 0x7f; // 7-bit PictureID, excluding the M bit
11const PICTURE_ID_MAX: u16 = 0x7fff; // 15-bit PictureID, excluding the M bit
12const PICTURE_ID_SHORT_MASK: u8 = PICTURE_ID_SHORT_MAX as u8;
13const KEY_IDX_MASK: u8 = 0x1f; // 5-bit KEYIDX mask within the TID/Y/KEYIDX octet
14const VP8_PATCH_BYTES: usize = 4; // two PictureID bytes, TL0PICIDX and KEYIDX
15 
16/// Validated byte replacements for a VP8 payload descriptor.
17///
18/// Build this from [`Vp8Descriptor::patch`] when the caller already parsed the
19/// payload descriptor before fanout. The patch is fixed-size and never changes
20/// payload length.
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct Vp8Patch {
23 len: u8,
24 offsets: [u8; VP8_PATCH_BYTES],
25 bytes: [u8; VP8_PATCH_BYTES],
26}
27 
28impl Vp8Patch {
29 fn new() -> Self {
30 Self {
31 len: 0,
32 offsets: [0; VP8_PATCH_BYTES],
33 bytes: [0; VP8_PATCH_BYTES],
34 }
35 }
36 
37 fn push(&mut self, offset: usize, byte: u8) {
38 let index = usize::from(self.len);
39 debug_assert!(index < self.offsets.len());
40 debug_assert!(offset <= usize::from(u8::MAX));
41 
42 self.offsets[index] = offset as u8;
43 self.bytes[index] = byte;
44 self.len += 1;
45 }
46 
47 /// Copy `payload` into `dst` while applying VP8 descriptor byte replacements.
48 ///
49 /// # Panics
50 ///
51 /// Panics if `dst` is not the same length as `payload`.
52 pub(crate) fn copy_to(&self, payload: &[u8], dst: &mut [u8]) {
53 assert_eq!(payload.len(), dst.len());
54 
55 dst.copy_from_slice(payload);
56 
57 for i in 0..usize::from(self.len) {
58 dst[usize::from(self.offsets[i])] = self.bytes[i];
59 }
60 }
61}
62 
63/// Errors returned when a VP8 payload descriptor cannot be parsed.
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65#[non_exhaustive]
66pub enum Vp8DescriptorError {
67 /// The payload is too short to contain a valid VP8 payload descriptor.
68 ShortPayload,
69 /// The payload descriptor violates VP8 payload descriptor rules.
70 MalformedDescriptor,
71}
72 
73impl fmt::Display for Vp8DescriptorError {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 match self {
76 Self::ShortPayload => write!(f, "VP8 payload is too short"),
77 Self::MalformedDescriptor => write!(f, "VP8 payload descriptor is malformed"),
78 }
79 }
80}
81 
82impl Error for Vp8DescriptorError {}
83 
84/// Errors returned when a VP8 payload descriptor patch cannot be built.
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86#[non_exhaustive]
87pub enum Vp8PatchError {
88 /// The payload descriptor does not contain a PictureID field.
89 PictureIdMissing,
90 /// The requested PictureID does not fit the existing descriptor width.
91 PictureIdTooLarge,
92 /// The payload descriptor does not contain a TL0PICIDX field.
93 Tl0PicIdxMissing,
94 /// The payload descriptor does not contain a KEYIDX field.
95 KeyIdxMissing,
96 /// The requested KEYIDX does not fit the five-bit KEYIDX field.
97 KeyIdxTooLarge,
98}
99 
100impl fmt::Display for Vp8PatchError {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 match self {
103 Self::PictureIdMissing => write!(f, "VP8 payload descriptor has no PictureID"),
104 Self::PictureIdTooLarge => write!(f, "VP8 PictureID does not fit descriptor width"),
105 Self::Tl0PicIdxMissing => write!(f, "VP8 payload descriptor has no TL0PICIDX"),
106 Self::KeyIdxMissing => write!(f, "VP8 payload descriptor has no KEYIDX"),
107 Self::KeyIdxTooLarge => write!(f, "VP8 KEYIDX must fit in 5 bits"),
108 }
109 }
110}
111 
112impl Error for Vp8PatchError {}
113 
114/// Builder for fixed-size VP8 payload descriptor patches.
115#[derive(Clone, Copy, Debug)]
116pub struct Vp8PatchBuilder {
117 descriptor: Vp8Descriptor,
118 picture_id: Option<u16>,
119 tl0_pic_idx: Option<u8>,
120 key_idx: Option<u8>,
121}
122 
123impl Vp8PatchBuilder {
124 /// Rewrite the VP8 PictureID field.
125 ///
126 /// The parsed descriptor decides whether the existing descriptor uses the
127 /// 7-bit or 15-bit PictureID representation. Rewriting preserves that
128 /// representation.
129 ///
130 /// [`Vp8PatchBuilder::build`] returns [`Vp8PatchError::PictureIdTooLarge`]
131 /// if the value does not fit the existing representation.
132 pub fn picture_id(mut self, picture_id: u16) -> Self {
133 self.picture_id = Some(picture_id);
134 self
135 }
136 
137 /// Rewrite the VP8 TL0PICIDX field.
138 ///
139 /// The rewrite is valid only when the payload descriptor already contains an
140 /// TL0PICIDX field.
141 pub fn tl0_pic_idx(mut self, tl0_pic_idx: u8) -> Self {
142 self.tl0_pic_idx = Some(tl0_pic_idx);
143 self
144 }
145 
146 /// Rewrite the VP8 KEYIDX field.
147 ///
148 /// Only the lower five KEYIDX bits are replaced. The TID and Y bits in the
149 /// same descriptor octet are preserved.
150 ///
151 /// [`Vp8PatchBuilder::build`] returns [`Vp8PatchError::KeyIdxTooLarge`] if
152 /// the value does not fit the five-bit KEYIDX field.
153 pub fn key_idx(mut self, key_idx: u8) -> Self {
154 self.key_idx = Some(key_idx);
155 self
156 }
157 
158 /// Build a validated fixed-size VP8 descriptor patch.
159 ///
160 /// # Errors
161 ///
162 /// Returns [`Vp8PatchError::PictureIdMissing`],
163 /// [`Vp8PatchError::Tl0PicIdxMissing`] or [`Vp8PatchError::KeyIdxMissing`]
164 /// when the requested rewrite targets a descriptor field that is absent.
165 /// Returns [`Vp8PatchError::PictureIdTooLarge`] or
166 /// [`Vp8PatchError::KeyIdxTooLarge`] when a requested rewrite value does
167 /// not fit the field representation.
168 pub fn build(self) -> Result<Vp8Patch, Vp8PatchError> {
169 let mut patch = Vp8Patch::new();
170 
171 if let Some(picture_id) = self.picture_id {
172 let Some(parsed) = self.descriptor.picture_id else {
173 return Err(Vp8PatchError::PictureIdMissing);
174 };
175 if picture_id > PICTURE_ID_MAX {
176 return Err(Vp8PatchError::PictureIdTooLarge);
177 }
178 if !parsed.is_15_bit && picture_id > PICTURE_ID_SHORT_MAX {
179 return Err(Vp8PatchError::PictureIdTooLarge);
180 }
181 if parsed.is_15_bit {
182 patch.push(
183 parsed.offset,
184 0x80 | ((picture_id >> 8) as u8 & PICTURE_ID_SHORT_MASK),
185 );
186 patch.push(parsed.offset + 1, picture_id as u8);
187 } else {
188 patch.push(parsed.offset, picture_id as u8);
189 }
190 }
191 
192 if let Some(tl0_pic_idx) = self.tl0_pic_idx {
193 let Some(parsed) = self.descriptor.tl0_pic_idx else {
194 return Err(Vp8PatchError::Tl0PicIdxMissing);
195 };
196 patch.push(parsed.offset, tl0_pic_idx);
197 }
198 
199 if let Some(key_idx) = self.key_idx {
200 let Some(parsed) = self.descriptor.key_idx else {
201 return Err(Vp8PatchError::KeyIdxMissing);
202 };
203 if key_idx > KEY_IDX_MASK {
204 return Err(Vp8PatchError::KeyIdxTooLarge);
205 }
206 patch.push(parsed.offset, (parsed.value & !KEY_IDX_MASK) | key_idx);
207 }
208 
209 Ok(patch)
210 }
211}
212 
213/// Parsed VP8 RTP payload descriptor.
214///
215/// The descriptor stores VP8 fields plus private offsets needed to
216/// build a validated [`Vp8Patch`]. It is valid only for the payload bytes parsed
217/// by [`Vp8Descriptor::parse`] or a byte-identical copy of those bytes.
218#[derive(Clone, Copy, Debug)]
219pub struct Vp8Descriptor {
220 payload_offset: usize,
221 picture_id: Option<Vp8PictureId>,
222 tl0_pic_idx: Option<Vp8Field>,
223 key_idx: Option<Vp8Field>,
224}
225 
226#[derive(Clone, Copy, Debug)]
227struct Vp8Field {
228 value: u8,
229 offset: usize,
230}
231 
232#[derive(Clone, Copy, Debug)]
233struct Vp8PictureId {
234 value: u16,
235 offset: usize,
236 is_15_bit: bool,
237}
238 
239impl Vp8Descriptor {
240 /// Parse a VP8 RTP payload descriptor
241 ///
242 /// The input must be the RTP payload only. It must not include the RTP
243 /// header.
244 ///
245 /// # Errors
246 ///
247 /// Returns [`Vp8DescriptorError::ShortPayload`] when the payload is truncated.
248 /// Returns [`Vp8DescriptorError::MalformedDescriptor`] when the descriptor
249 /// has unsupported field combinations.
250 pub fn parse(payload: &[u8]) -> Result<Self, Vp8DescriptorError> {
251 let Some(required) = payload.first() else {
252 return Err(Vp8DescriptorError::ShortPayload);
253 };
254 
255 let mut descriptor = Self {
256 payload_offset: 1,
257 picture_id: None,
258 tl0_pic_idx: None,
259 key_idx: None,
260 };
261 
262 if required & 0x80 != 0 {
263 let extension = read_vp8_descriptor_byte(payload, &mut descriptor.payload_offset)?;
264 let has_picture_id = extension & 0x80 != 0;
265 let has_tl0_pic_idx = extension & 0x40 != 0;
266 let has_tid = extension & 0x20 != 0;
267 let has_key_idx = extension & 0x10 != 0;
268 
269 if has_tl0_pic_idx && !has_tid {
270 return Err(Vp8DescriptorError::MalformedDescriptor);
271 }
272 
273 if has_picture_id {
274 let picture_id_offset = descriptor.payload_offset;
275 let picture_id = read_vp8_descriptor_byte(payload, &mut descriptor.payload_offset)?;
276 let is_15_bit = picture_id & 0x80 != 0;
277 let value = if is_15_bit {
278 let picture_id_low =
279 read_vp8_descriptor_byte(payload, &mut descriptor.payload_offset)?;
280 (u16::from(picture_id & PICTURE_ID_SHORT_MASK) << 8) | u16::from(picture_id_low)
281 } else {
282 u16::from(picture_id & PICTURE_ID_SHORT_MASK)
283 };
284 descriptor.picture_id = Some(Vp8PictureId {
285 value,
286 offset: picture_id_offset,
287 is_15_bit,
288 });
289 }
290 
291 if has_tl0_pic_idx {
292 let offset = descriptor.payload_offset;
293 let value = read_vp8_descriptor_byte(payload, &mut descriptor.payload_offset)?;
294 descriptor.tl0_pic_idx = Some(Vp8Field { value, offset });
295 }
296 
297 if has_tid || has_key_idx {
298 let offset = descriptor.payload_offset;
299 let value = read_vp8_descriptor_byte(payload, &mut descriptor.payload_offset)?;
300 if has_key_idx {
301 descriptor.key_idx = Some(Vp8Field { value, offset });
302 }
303 }
304 }
305 
306 if descriptor.payload_offset >= payload.len() {
307 return Err(Vp8DescriptorError::ShortPayload);
308 }
309 
310 Ok(descriptor)
311 }
312 
313 /// Returns the VP8 PictureID if present
314 pub const fn picture_id(&self) -> Option<u16> {
315 match self.picture_id {
316 Some(picture_id) => Some(picture_id.value),
317 None => None,
318 }
319 }
320 
321 /// Returns the VP8 TL0PICIDX if present
322 pub const fn tl0_pic_idx(&self) -> Option<u8> {
323 match self.tl0_pic_idx {
324 Some(tl0_pic_idx) => Some(tl0_pic_idx.value),
325 None => None,
326 }
327 }
328 
329 /// returns the VP8 KEYIDX if present
330 pub const fn key_idx(&self) -> Option<u8> {
331 match self.key_idx {
332 Some(key_idx) => Some(key_idx.value & KEY_IDX_MASK),
333 None => None,
334 }
335 }
336 
337 /// Returns whether this descriptor starts a VP8 keyframe.
338 ///
339 /// `payload` must be the same payload that produced this descriptor or a
340 /// byte-identical copy. A mismatched or shorter payload returns `false`.
341 pub fn starts_keyframe(&self, payload: &[u8]) -> bool {
342 let Some(required) = payload.first() else {
343 return false;
344 };
345 
346 if required & 0x10 == 0 || required & 0x07 != 0 {
347 return false;
348 }
349 
350 payload
351 .get(self.payload_offset)
352 .is_some_and(|header| header & 0x01 == 0)
353 }
354 
355 /// Start building a validated fixed-size VP8 descriptor patch.
356 pub fn patch(&self) -> Vp8PatchBuilder {
357 Vp8PatchBuilder {
358 descriptor: *self,
359 picture_id: None,
360 tl0_pic_idx: None,
361 key_idx: None,
362 }
363 }
364}
365 
366fn read_vp8_descriptor_byte(payload: &[u8], offset: &mut usize) -> Result<u8, Vp8DescriptorError> {
367 let byte = payload
368 .get(*offset)
369 .ok_or(Vp8DescriptorError::ShortPayload)?;
370 *offset += 1;
371 Ok(*byte)
372}
373 
374/// Vp8 information describing the depacketized / packetized data
375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
376pub struct Vp8CodecExtra {
377 /// True if the frame can be discarded safely, without causing decoding problems
378 /// No other frames are encoded depending on this frame (non-reference frame)
379 pub discardable: bool,
380 /// True if this frame and subsequent ones on this layer depend only on tl0_pic_idx
381 pub sync: bool,
382 /// Index of the vp8 temporal layer.
383 pub layer_index: u8,
384 /// Extended picture id, if present
385 pub picture_id: Option<u64>,
386 /// Extended picture id of layer 0 frames, if present
387 pub tl0_picture_id: Option<u64>,
388 /// Flag which indicates that within [`MediaData`], there is an individual frame
389 /// containing complete and independent visual information. This frame serves
390 /// as a reference point for other frames in the video sequence.
391 ///
392 /// [`MediaData`]: crate::media::MediaData
393 pub is_keyframe: bool,
394}
395 
396/// Detect whether a VP8 RTP payload contains a keyframe.
397///
398/// Parses the VP8 RTP payload descriptor (RFC 7741) to skip past the
399/// variable-length header, then checks the P bit in the VP8 payload header.
400/// P=0 means keyframe, P=1 means interframe.
401///
402/// Returns `true` only for the first packet of a keyframe (S=1, PID=0).
403pub fn detect_vp8_keyframe(payload: &[u8]) -> bool {
404 if payload.is_empty() {
405 return false;
406 }
407 let b0 = payload[0];
408 let s = (b0 & 0x10) >> 4; // Start of VP8 partition
409 let pid = b0 & 0x07; // Partition index
410 // Only the first packet of a frame (S=1, PID=0) contains the payload header
411 if s != 1 || pid != 0 {
412 return false;
413 }
414 let x = (b0 & 0x80) >> 7; // Extension bit
415 let mut idx = 1;
416 if x == 1 {
417 if idx >= payload.len() {
418 return false;
419 }
420 let ext = payload[idx];
421 idx += 1;
422 let i = (ext & 0x80) >> 7; // PictureID present
423 let l = (ext & 0x40) >> 6; // TL0PICIDX present
424 let t = (ext & 0x20) >> 5; // TID present
425 let k = (ext & 0x10) >> 4; // KEYIDX present
426 if i == 1 {
427 if idx >= payload.len() {
428 return false;
429 }
430 if payload[idx] & 0x80 != 0 {
431 idx += 2; // 16-bit PictureID
432 } else {
433 idx += 1; // 7-bit PictureID
434 }
435 }
436 if l == 1 {
437 idx += 1; // tl0picidx
438 }
439 if t == 1 || k == 1 {
440 idx += 1; // TID/KEYIDX
441 }
442 }
443 if idx >= payload.len() {
444 return false;
445 }
446 // VP8 Payload Header: P bit is bit 0 of the first byte
447 // P=0 → keyframe, P=1 → interframe
448 payload[idx] & 0x01 == 0
449}
450 
451/// Packetizes VP8 RTP packets.
452///
453/// ## Unversioned API surface
454///
455/// This struct is not currently versioned according to semver rules.
456/// Breaking changes may be made in minor or patch releases.
457#[derive(Default, Debug, Copy, Clone)]
458pub struct Vp8Packetizer {
459 enable_picture_id: bool,
460 picture_id: u16,
461}
462 
463impl Packetizer for Vp8Packetizer {
464 /// Payload fragments a VP8 packet across one or more byte arrays
465 fn packetize(&mut self, mtu: usize, payload: &[u8]) -> Result<Vec<Vec<u8>>, PacketError> {
466 if payload.is_empty() || mtu == 0 {
467 return Ok(vec![]);
468 }
469 
470 /*
471 * https://tools.ietf.org/html/rfc7741#section-4.2
472 *
473 * 0 1 2 3 4 5 6 7
474 * +-+-+-+-+-+-+-+-+
475 * |X|R|N|S|R| PID | (REQUIRED)
476 * +-+-+-+-+-+-+-+-+
477 * X: |I|L|T|K| RSV | (OPTIONAL)
478 * +-+-+-+-+-+-+-+-+
479 * I: |M| PictureID | (OPTIONAL)
480 * +-+-+-+-+-+-+-+-+
481 * L: | tl0picidx | (OPTIONAL)
482 * +-+-+-+-+-+-+-+-+
483 * T/K: |tid|Y| KEYIDX | (OPTIONAL)
484 * +-+-+-+-+-+-+-+-+
485 * S: Start of VP8 partition. SHOULD be set to 1 when the first payload
486 * octet of the RTP packet is the beginning of a new VP8 partition,
487 * and MUST NOT be 1 otherwise. The S bit MUST be set to 1 for the
488 * first packet of each encoded frame.
489 */
490 let using_header_size = if self.enable_picture_id {
491 if self.picture_id == 0 || self.picture_id < 128 {
492 VP8_HEADER_SIZE + 2
493 } else {
494 VP8_HEADER_SIZE + 3
495 }
496 } else {
497 VP8_HEADER_SIZE
498 };
499 
500 let max_fragment_size = mtu as isize - using_header_size as isize;
501 let mut payload_data_remaining = payload.len() as isize;
502 let mut payload_data_index: usize = 0;
503 let mut payloads = vec![];
504 
505 // Make sure the fragment/payload size is correct
506 if std::cmp::min(max_fragment_size, payload_data_remaining) <= 0 {
507 return Ok(payloads);
508 }
509 
510 let mut first = true;
511 while payload_data_remaining > 0 {
512 let current_fragment_size =
513 std::cmp::min(max_fragment_size, payload_data_remaining) as usize;
514 let mut out = Vec::with_capacity(using_header_size + current_fragment_size);
515 let mut buf = [0u8; 4];
516 if first {
517 buf[0] = 0x10;
518 first = false;
519 }
520 
521 if self.enable_picture_id {
522 if using_header_size == VP8_HEADER_SIZE + 2 {
523 buf[0] |= 0x80;
524 buf[1] |= 0x80;
525 buf[2] |= (self.picture_id & u16::from(PICTURE_ID_SHORT_MASK)) as u8;
526 } else if using_header_size == VP8_HEADER_SIZE + 3 {
527 buf[0] |= 0x80;
528 buf[1] |= 0x80;
529 buf[2] |=
530 0x80 | ((self.picture_id >> 8) & u16::from(PICTURE_ID_SHORT_MASK)) as u8;
531 buf[3] |= (self.picture_id & 0xFF) as u8;
532 }
533 }
534 
535 out.extend_from_slice(&buf[..using_header_size]);
536 
537 out.extend_from_slice(
538 &payload[payload_data_index..payload_data_index + current_fragment_size],
539 );
540 payloads.push(out);
541 
542 payload_data_remaining -= current_fragment_size as isize;
543 payload_data_index += current_fragment_size;
544 }
545 
546 self.picture_id += 1;
547 self.picture_id &= PICTURE_ID_MAX;
548 
549 Ok(payloads)
550 }
551 
552 fn is_marker(&mut self, _data: &[u8], _previous: Option<&[u8]>, last: bool) -> bool {
553 last
554 }
555}
556 
557/// Depacketizes VP8 RTP packets.
558///
559/// ## Unversioned API surface
560///
561/// This struct is not currently versioned according to semver rules.
562/// Breaking changes may be made in minor or patch releases.
563#[derive(PartialEq, Eq, Debug, Default, Clone)]
564pub struct Vp8Depacketizer {
565 /// Required Header
566 /// extended controlbits present
567 pub x: u8,
568 /// when set to 1 this frame can be discarded
569 pub n: u8,
570 /// start of VP8 partition
571 pub s: u8,
572 /// partition index
573 pub pid: u8,
574 
575 /// Extended control bits
576 /// 1 if PictureID is present
577 pub i: u8,
578 /// 1 if tl0picidx is present
579 pub l: u8,
580 /// 1 if tid is present
581 pub t: u8,
582 /// 1 if KEYIDX is present
583 pub k: u8,
584 
585 /// Optional extension
586 /// 8 or 16 bits, picture ID
587 pub picture_id: u16,
588 /// extended picture id
589 pub extended_pid: Option<u64>,
590 
591 /// 8 bits temporal level zero index
592 pub tl0_pic_idx: u8,
593 /// extended version of picture_id of temporal layer 0
594 pub extended_tl0_pic_idx: Option<u64>,
595 /// 2 bits temporal layer index
596 pub tid: u8,
597 /// 1 bit layer sync bit
598 pub y: u8,
599 /// 5 bits temporal key frame index
600 pub key_idx: u8,
601 
602 /// Inverse key frame flag.
603 ///
604 /// 0 if the current frame is a key frame.
605 pub p: u8,
606}
607 
608impl Depacketizer for Vp8Depacketizer {
609 fn out_size_hint(&self, packets_size: usize) -> Option<usize> {
610 Some(packets_size)
611 }
612 
613 /// depacketize parses the passed byte slice and stores the result in the
614 /// VP8Packet this method is called upon
615 fn depacketize(
616 &mut self,
617 packet: &[u8],
618 out: &mut Vec<u8>,
619 extra: &mut CodecExtra,
620 ) -> Result<(), PacketError> {
621 let payload_len = packet.len();
622 // VP8 Payload Descriptor
623 // https://datatracker.ietf.org/doc/html/rfc7741#section-4.2
624 //
625 // 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
626 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
627 // |X|R|N|S|R| PID | (REQUIRED) |X|R|N|S|R| PID | (REQUIRED)
628 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
629 // X: |I|L|T|K| RSV | (OPTIONAL) X: |I|L|T|K| RSV | (OPTIONAL)
630 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
631 // I: |M| PictureID | (OPTIONAL) I: |M| PictureID | (OPTIONAL)
632 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
633 // L: | tl0picidx | (OPTIONAL) | PictureID |
634 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
635 //T/K:|tid|Y| KEYIDX | (OPTIONAL) L: | tl0picidx | (OPTIONAL)
636 // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
637 // T/K:|tid|Y| KEYIDX | (OPTIONAL)
638 // +-+-+-+-+-+-+-+-+
639 
640 let mut reader = (packet, 0);
641 let mut payload_index = 0;
642 
643 let mut b = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
644 payload_index += 1;
645 
646 self.x = (b & 0x80) >> 7;
647 self.n = (b & 0x20) >> 5;
648 self.s = (b & 0x10) >> 4;
649 self.pid = b & 0x07;
650 
651 let (i, l, t, k) = if self.x == 1 {
652 b = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
653 payload_index += 1;
654 let i = (b & 0x80) >> 7;
655 let l = (b & 0x40) >> 6;
656 let t = (b & 0x20) >> 5;
657 let k = (b & 0x10) >> 4;
658 
659 if l == 1 && t == 0 {
660 return Err(PacketError::ErrVP8CorruptedPacket);
661 }
662 
663 (i, l, t, k)
664 } else {
665 (0, 0, 0, 0)
666 };
667 self.i = i;
668 self.l = l;
669 self.t = t;
670 self.k = k;
671 
672 if self.i == 1 {
673 b = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
674 payload_index += 1;
675 // PID present?
676 if b & 0x80 > 0 {
677 // M == 1, PID is 16bit
678 let x = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
679 self.picture_id = (((b & PICTURE_ID_SHORT_MASK) as u16) << 8) | (x as u16);
680 self.extended_pid = Some(extend_u15(self.extended_pid, self.picture_id));
681 payload_index += 1;
682 } else {
683 self.picture_id = b as u16;
684 self.extended_pid = Some(extend_u7(self.extended_pid, b));
685 }
686 }
687 
688 if payload_index >= payload_len {
689 return Err(PacketError::ErrShortPacket);
690 }
691 
692 if self.l == 1 {
693 self.tl0_pic_idx = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
694 self.extended_tl0_pic_idx =
695 Some(extend_u8(self.extended_tl0_pic_idx, self.tl0_pic_idx));
696 payload_index += 1;
697 }
698 
699 if payload_index >= payload_len {
700 return Err(PacketError::ErrShortPacket);
701 }
702 
703 if self.t == 1 || self.k == 1 {
704 let b = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
705 if self.t == 1 {
706 self.tid = b >> 6;
707 self.y = (b >> 5) & 0x1;
708 }
709 if self.k == 1 {
710 self.key_idx = b & KEY_IDX_MASK;
711 }
712 payload_index += 1;
713 }
714 
715 if payload_index >= packet.len() {
716 return Err(PacketError::ErrShortPacket);
717 }
718 
719 out.extend_from_slice(&packet[payload_index..]);
720 
721 // VP8 Payload Header
722 // https://datatracker.ietf.org/doc/html/rfc7741#section-4.3
723 //
724 // 0 1 2 3 4 5 6 7
725 // +-+-+-+-+-+-+-+-+
726 // |Size0|H| VER |P|
727 // +-+-+-+-+-+-+-+-+
728 // | Size1 |
729 // +-+-+-+-+-+-+-+-+
730 // | Size2 |
731 // +-+-+-+-+-+-+-+-+
732 // | Octets 4..N of|
733 // | VP8 payload |
734 // : :
735 // +-+-+-+-+-+-+-+-+
736 // | OPTIONAL RTP |
737 // | padding |
738 // : :
739 // +-+-+-+-+-+-+-+-+
740 //
741 // The header is present only in packets that have the S bit equal
742 // to one and the PID equal to zero in the payload descriptor
743 self.p = if self.s == 1 && self.pid == 0 {
744 b = reader.get_u8().ok_or(PacketError::ErrShortPacket)?;
745 payload_index += 1;
746 b & 1
747 } else {
748 1
749 };
750 
751 let is_keyframe = if let CodecExtra::Vp8(e) = extra {
752 e.is_keyframe | (self.p == 0)
753 } else {
754 self.p == 0
755 };
756 
757 *extra = CodecExtra::Vp8(Vp8CodecExtra {
758 discardable: self.n == 1,
759 sync: self.y == 1,
760 layer_index: self.tid,
761 picture_id: if self.i == 1 { self.extended_pid } else { None },
762 tl0_picture_id: if self.l == 1 {
763 self.extended_tl0_pic_idx
764 } else {
765 None
766 },
767 is_keyframe,
768 });
769 
770 let _ = payload_index;
771 
772 Ok(())
773 }
774 
775 /// is_partition_head checks whether if this is a head of the VP8 partition
776 fn is_partition_head(&self, payload: &[u8]) -> bool {
777 if payload.is_empty() {
778 false
779 } else {
780 (payload[0] & 0x10) != 0
781 }
782 }
783 
784 fn is_partition_tail(&self, marker: bool, _payload: &[u8]) -> bool {
785 marker
786 }
787}
788 
789#[cfg(test)]
790mod test {
791 use super::*;
792 
793 #[test]
794 fn test_vp8_unmarshal() -> Result<(), PacketError> {
795 let mut pck = Vp8Depacketizer::default();
796 let mut extra = CodecExtra::None;
797 
798 // Empty packet
799 let empty_bytes = &[];
800 let mut payload = Vec::new();
801 let result = pck.depacketize(empty_bytes, &mut payload, &mut extra);
802 assert!(result.is_err(), "Result should be err in case of error");
803 
804 // Small Payload with single octet header
805 let small_bytes = &[0x00, 0x11, 0x22];
806 let mut payload = Vec::new();
807 pck.depacketize(small_bytes, &mut payload, &mut extra)
808 .expect("Small packet");
809 assert_eq!(payload, [0x11, 0x22]);
810 
811 // Payload is header only
812 let small_bytes = &[0x00];
813 let mut payload = Vec::new();
814 let result = pck.depacketize(small_bytes, &mut payload, &mut extra);
815 assert!(result.is_err(), "Result should be err in case of error");
816 
817 // Normal packet
818 let raw_bytes = &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x90];
819 let mut payload = Vec::new();
820 pck.depacketize(raw_bytes, &mut payload, &mut extra)
821 .expect("Normal packet");
822 assert!(!payload.is_empty(), "Payload must be not empty");
823 
824 // Header size, only X
825 let raw_bytes = &[0x80, 0x00, 0x00, 0x00];
826 let mut payload = Vec::new();
827 pck.depacketize(raw_bytes, &mut payload, &mut extra)
828 .expect("Only X");
829 assert!(!payload.is_empty(), "Payload must be not empty");
830 assert_eq!(pck.x, 1, "X must be 1");
831 assert_eq!(pck.i, 0, "I must be 0");
832 assert_eq!(pck.l, 0, "L must be 0");
833 assert_eq!(pck.t, 0, "T must be 0");
834 assert_eq!(pck.k, 0, "K must be 0");
835 assert_eq!(pck.p, 1, "P must be 1");
836 
837 // Header size, X and I, PID 16bits
838 let raw_bytes = &[0x80, 0x80, 0x81, 0x00, 0x00];
839 let mut payload = Vec::new();
840 pck.depacketize(raw_bytes, &mut payload, &mut extra)
841 .expect("X and I, PID 16bits");
842 assert!(!payload.is_empty(), "Payload must be not empty");
843 assert_eq!(pck.x, 1, "X must be 1");
844 assert_eq!(pck.i, 1, "I must be 1");
845 assert_eq!(pck.l, 0, "L must be 0");
846 assert_eq!(pck.t, 0, "T must be 0");
847 assert_eq!(pck.k, 0, "K must be 0");
848 assert_eq!(pck.p, 1, "P must be 1");
849 
850 // Header size, X and L without T
851 let raw_bytes = &[0x80, 0x40, 0x00, 0x00];
852 let mut payload = Vec::new();
853 assert_eq!(
854 pck.depacketize(raw_bytes, &mut payload, &mut extra),
855 Err(PacketError::ErrVP8CorruptedPacket)
856 );
857 
858 let raw_bytes = &[0x00, 0x11, 0x22];
859 let mut payload = Vec::new();
860 pck.depacketize(raw_bytes, &mut payload, &mut extra)
861 .expect("valid packet after malformed descriptor");
862 assert_eq!(payload, [0x11, 0x22]);
863 
864 // Header size, X and T
865 let raw_bytes = &[0x80, 0x20, 0x00, 0x00];
866 let mut payload = Vec::new();
867 pck.depacketize(raw_bytes, &mut payload, &mut extra)
868 .expect("X and T");
869 assert!(!payload.is_empty(), "Payload must be not empty");
870 assert_eq!(pck.x, 1, "X must be 1");
871 assert_eq!(pck.i, 0, "I must be 0");
872 assert_eq!(pck.l, 0, "L must be 0");
873 assert_eq!(pck.t, 1, "T must be 1");
874 assert_eq!(pck.k, 0, "K must be 0");
875 assert_eq!(pck.p, 1, "P must be 1");
876 
877 // Header size, X and K
878 let raw_bytes = &[0x80, 0x10, 0x00, 0x00];
879 let mut payload = Vec::new();
880 pck.depacketize(raw_bytes, &mut payload, &mut extra)
881 .expect("X and K");
882 assert!(!payload.is_empty(), "Payload must be not empty");
883 assert_eq!(pck.x, 1, "X must be 1");
884 assert_eq!(pck.i, 0, "I must be 0");
885 assert_eq!(pck.l, 0, "L must be 0");
886 assert_eq!(pck.t, 0, "T must be 0");
887 assert_eq!(pck.k, 1, "K must be 1");
888 assert_eq!(pck.p, 1, "P must be 1");
889 
890 // Header size, all flags and 8bit picture_id
891 let raw_bytes = &[0xff, 0xff, 0x00, 0x00, 0x00, 0x00];
892 let mut payload = Vec::new();
893 pck.depacketize(raw_bytes, &mut payload, &mut extra)
894 .expect("all flags and 8bit picture_id");
895 assert!(!payload.is_empty(), "Payload must be not empty");
896 assert_eq!(pck.x, 1, "X must be 1");
897 assert_eq!(pck.i, 1, "I must be 1");
898 assert_eq!(pck.l, 1, "L must be 1");
899 assert_eq!(pck.t, 1, "T must be 1");
900 assert_eq!(pck.k, 1, "K must be 1");
901 assert_eq!(pck.p, 1, "P must be 1");
902 
903 // Header size, all flags and 16bit picture_id
904 let raw_bytes = &[0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00];
905 let mut payload = Vec::new();
906 pck.depacketize(raw_bytes, &mut payload, &mut extra)
907 .expect("all flags and 16bit picture_id");
908 assert!(!payload.is_empty(), "Payload must be not empty");
909 assert_eq!(pck.x, 1, "X must be 1");
910 assert_eq!(pck.i, 1, "I must be 1");
911 assert_eq!(pck.l, 1, "L must be 1");
912 assert_eq!(pck.t, 1, "T must be 1");
913 assert_eq!(pck.k, 1, "K must be 1");
914 assert_eq!(pck.p, 1, "P must be 1");
915 
916 // Header size, X, I and P
917 let raw_bytes = &[0x90, 0x80, 0x11, 0x10, 0x00, 0x00, 0x00];
918 let mut payload = Vec::new();
919 pck.depacketize(raw_bytes, &mut payload, &mut extra)
920 .expect("all flags and 16bit picture_id");
921 assert!(!payload.is_empty(), "Payload must be not empty");
922 assert_eq!(pck.x, 1, "X must be 1");
923 assert_eq!(pck.i, 1, "I must be 1");
924 assert_eq!(pck.l, 0, "L must be 0");
925 assert_eq!(pck.t, 0, "T must be 0");
926 assert_eq!(pck.k, 0, "K must be 0");
927 assert_eq!(pck.p, 0, "P must be 0");
928 
929 Ok(())
930 }
931 
932 #[test]
933 fn test_vp8_payload() -> Result<(), PacketError> {
934 let tests: Vec<(&str, Vp8Packetizer, usize, Vec<&[u8]>, Vec<Vec<&[u8]>>)> = vec![
935 (
936 "WithoutPictureID",
937 Vp8Packetizer::default(),
938 2,
939 vec![&[0x90, 0x90, 0x90], &[0x91, 0x91]],
940 vec![
941 vec![&[0x10, 0x90], &[0x00, 0x90], &[0x00, 0x90]],
942 vec![&[0x10, 0x91], &[0x00, 0x91]],
943 ],
944 ),
945 (
946 "WithPictureID_1byte",
947 Vp8Packetizer {
948 enable_picture_id: true,
949 picture_id: 0x20,
950 },
951 5,
952 vec![&[0x90, 0x90, 0x90], &[0x91, 0x91]],
953 vec![
954 vec![&[0x90, 0x80, 0x20, 0x90, 0x90], &[0x80, 0x80, 0x20, 0x90]],
955 vec![&[0x90, 0x80, 0x21, 0x91, 0x91]],
956 ],
957 ),
958 (
959 "WithPictureID_2bytes",
960 Vp8Packetizer {
961 enable_picture_id: true,
962 picture_id: 0x120,
963 },
964 6,
965 vec![&[0x90, 0x90, 0x90], &[0x91, 0x91]],
966 vec![
967 vec![
968 &[0x90, 0x80, 0x81, 0x20, 0x90, 0x90],
969 &[0x80, 0x80, 0x81, 0x20, 0x90],
970 ],
971 vec![&[0x90, 0x80, 0x81, 0x21, 0x91, 0x91]],
972 ],
973 ),
974 ];
975 
976 for (name, mut pck, mtu, payloads, expected) in tests {
977 for (i, payload) in payloads.iter().enumerate() {
978 let actual = pck.packetize(mtu, payload)?;
979 assert_eq!(expected[i], actual, "{name}: Generated packet[{i}] differs");
980 }
981 }
982 
983 Ok(())
984 }
985 
986 #[test]
987 fn test_vp8_payload_eror() -> Result<(), PacketError> {
988 let mut pck = Vp8Packetizer::default();
989 let empty = &[];
990 let payload = &[0x90, 0x90, 0x90];
991 
992 // Positive MTU, empty payload
993 let result = pck.packetize(1, empty)?;
994 assert!(result.is_empty(), "Generated payload should be empty");
995 
996 // Positive MTU, small payload
997 let result = pck.packetize(1, payload)?;
998 assert_eq!(result.len(), 0, "Generated payload should be empty");
999 
1000 // Positive MTU, small payload
1001 let result = pck.packetize(2, payload)?;
1002 assert_eq!(
1003 result.len(),
1004 payload.len(),
1005 "Generated payload should be the same size as original payload size"
1006 );
1007 
1008 Ok(())
1009 }
1010 
1011 #[test]
1012 fn test_vp8_partition_head_checker_is_partition_head() -> Result<(), PacketError> {
1013 let vp8 = Vp8Depacketizer::default();
1014 
1015 //"SmallPacket"
1016 assert!(
1017 !vp8.is_partition_head(&[0x00]),
1018 "Small packet should not be the head of a new partition"
1019 );
1020 
1021 //"SFlagON",
1022 assert!(
1023 vp8.is_partition_head(&[0x10, 0x00, 0x00, 0x00]),
1024 "Packet with S flag should be the head of a new partition"
1025 );
1026 
1027 //"SFlagOFF"
1028 assert!(
1029 !vp8.is_partition_head(&[0x00, 0x00, 0x00, 0x00]),
1030 "Packet without S flag should not be the head of a new partition"
1031 );
1032 
1033 Ok(())
1034 }
1035 
1036 fn rewrite_payload(
1037 payload: &[u8],
1038 patch: impl FnOnce(Vp8PatchBuilder) -> Vp8PatchBuilder,
1039 ) -> Result<Vec<u8>, Vp8PatchError> {
1040 let descriptor = Vp8Descriptor::parse(payload).expect("valid VP8 descriptor");
1041 let patch = patch(descriptor.patch()).build()?;
1042 
1043 let mut out = vec![0; payload.len()];
1044 patch.copy_to(payload, &mut out);
1045 Ok(out)
1046 }
1047 
1048 #[test]
1049 fn test_vp8_descriptor_exposes_fields() {
1050 let short = Vp8Descriptor::parse(&[0x90, 0x80, 0x42, 0x00]).unwrap();
1051 assert_eq!(short.picture_id(), Some(0x42));
1052 assert_eq!(short.tl0_pic_idx(), None);
1053 assert_eq!(short.key_idx(), None);
1054 
1055 let full = Vp8Descriptor::parse(&[0x90, 0xf0, 0x92, 0x34, 0x55, 0xa7, 0x00]).unwrap();
1056 assert_eq!(full.picture_id(), Some(0x1234));
1057 assert_eq!(full.tl0_pic_idx(), Some(0x55));
1058 assert_eq!(full.key_idx(), Some(0x07));
1059 }
1060 
1061 #[test]
1062 fn test_vp8_patch_updates_descriptor_fields() {
1063 assert_eq!(
1064 rewrite_payload(&[0x80, 0x80, 0x01, 0x90], |patch| {
1065 patch.picture_id(u16::from(PICTURE_ID_SHORT_MASK))
1066 })
1067 .unwrap(),
1068 [0x80, 0x80, PICTURE_ID_SHORT_MASK, 0x90]
1069 );
1070 assert_eq!(
1071 rewrite_payload(&[0x80, 0x80, 0x81, 0x20, 0x90], |patch| {
1072 patch.picture_id(0x0234)
1073 })
1074 .unwrap(),
1075 [0x80, 0x80, 0x82, 0x34, 0x90]
1076 );
1077 assert_eq!(
1078 rewrite_payload(&[0x80, 0x80, 0x81, 0x20, 0x90], |patch| {
1079 patch.picture_id(PICTURE_ID_MAX)
1080 })
1081 .unwrap(),
1082 [0x80, 0x80, 0xff, 0xff, 0x90]
1083 );
1084 assert_eq!(
1085 rewrite_payload(&[0x80, 0x60, 0x12, 0x00, 0x90], |patch| {
1086 patch.tl0_pic_idx(0xab)
1087 })
1088 .unwrap(),
1089 [0x80, 0x60, 0xab, 0x00, 0x90]
1090 );
1091 assert_eq!(
1092 rewrite_payload(&[0x80, 0x30, 0xaa, 0x90], |patch| {
1093 patch.key_idx(KEY_IDX_MASK)
1094 })
1095 .unwrap(),
1096 [0x80, 0x30, 0xbf, 0x90]
1097 );
1098 }
1099 
1100 #[test]
1101 fn test_vp8_patch_rejects_unsupported_descriptor_shapes() {
1102 assert_eq!(
1103 Vp8Descriptor::parse(&[0x80, 0x80]).unwrap_err(),
1104 Vp8DescriptorError::ShortPayload
1105 );
1106 assert_eq!(
1107 rewrite_payload(&[0x00, 0x90], |patch| patch.picture_id(1)).unwrap_err(),
1108 Vp8PatchError::PictureIdMissing
1109 );
1110 assert_eq!(
1111 rewrite_payload(&[0x00, 0x90], |patch| {
1112 patch.picture_id(PICTURE_ID_MAX + 1)
1113 })
1114 .unwrap_err(),
1115 Vp8PatchError::PictureIdMissing
1116 );
1117 assert_eq!(
1118 rewrite_payload(&[0x80, 0x80, 0x81, 0x20, 0x90], |patch| {
1119 patch.tl0_pic_idx(1)
1120 })
1121 .unwrap_err(),
1122 Vp8PatchError::Tl0PicIdxMissing
1123 );
1124 assert_eq!(
1125 rewrite_payload(&[0x80, 0x80, 0x81, 0x20, 0x90], |patch| {
1126 patch.key_idx(1)
1127 })
1128 .unwrap_err(),
1129 Vp8PatchError::KeyIdxMissing
1130 );
1131 assert_eq!(
1132 rewrite_payload(&[0x80, 0x80, 0x81, 0x20, 0x90], |patch| {
1133 patch.key_idx(KEY_IDX_MASK + 1)
1134 })
1135 .unwrap_err(),
1136 Vp8PatchError::KeyIdxMissing
1137 );
1138 assert_eq!(
1139 Vp8Descriptor::parse(&[0x80, 0x40, 0x12, 0x90]).unwrap_err(),
1140 Vp8DescriptorError::MalformedDescriptor
1141 );
1142 }
1143 
1144 #[test]
1145 fn test_vp8_patch_rejects_impossible_values() {
1146 assert_eq!(
1147 rewrite_payload(&[0x80, 0x80, 0x01, 0x90], |patch| {
1148 patch.picture_id(0x80)
1149 })
1150 .unwrap_err(),
1151 Vp8PatchError::PictureIdTooLarge
1152 );
1153 assert_eq!(
1154 rewrite_payload(&[0x80, 0x80, 0x81, 0x20, 0x90], |patch| {
1155 patch.picture_id(PICTURE_ID_MAX + 1)
1156 })
1157 .unwrap_err(),
1158 Vp8PatchError::PictureIdTooLarge
1159 );
1160 assert_eq!(
1161 rewrite_payload(&[0x80, 0x10, 0x00, 0x90], |patch| {
1162 patch.key_idx(KEY_IDX_MASK + 1)
1163 })
1164 .unwrap_err(),
1165 Vp8PatchError::KeyIdxTooLarge
1166 );
1167 }
1168 
1169 #[test]
1170 fn test_detect_vp8_keyframe() {
1171 // Empty payload
1172 assert!(!detect_vp8_keyframe(&[]));
1173 
1174 // Minimal keyframe: S=1, PID=0, no extensions, P=0
1175 // Byte 0: X=0, R=0, N=0, S=1, PID=0 → 0x10
1176 // Byte 1: VP8 payload header with P=0 (keyframe) → 0x00
1177 assert!(detect_vp8_keyframe(&[0x10, 0x00]));
1178 
1179 // Minimal interframe: S=1, PID=0, no extensions, P=1
1180 // Byte 1: VP8 payload header with P=1 → 0x01
1181 assert!(!detect_vp8_keyframe(&[0x10, 0x01]));
1182 
1183 // Not the first packet (S=0) — cannot detect keyframe
1184 assert!(!detect_vp8_keyframe(&[0x00, 0x00]));
1185 
1186 // Continuation packet (PID != 0)
1187 assert!(!detect_vp8_keyframe(&[0x11, 0x00]));
1188 
1189 // With extension (X=1), 7-bit PictureID, keyframe
1190 // Byte 0: X=1, S=1, PID=0 → 0x90
1191 // Byte 1: I=1, L=0, T=0, K=0 → 0x80
1192 // Byte 2: 7-bit PictureID (M=0) → 0x42
1193 // Byte 3: VP8 payload header P=0 → 0x00
1194 assert!(detect_vp8_keyframe(&[0x90, 0x80, 0x42, 0x00]));
1195 
1196 // With extension, 7-bit PictureID, interframe
1197 assert!(!detect_vp8_keyframe(&[0x90, 0x80, 0x42, 0x01]));
1198 
1199 // With extension, 16-bit PictureID (M=1), keyframe
1200 // Byte 2: M=1 → 0x80 | PID_high
1201 // Byte 3: PID_low
1202 // Byte 4: VP8 payload header P=0
1203 assert!(detect_vp8_keyframe(&[0x90, 0x80, 0x80, 0x42, 0x00]));
1204 
1205 // With all extensions: I=1(16-bit), L=1, T=1
1206 // Byte 0: X=1, S=1 → 0x90
1207 // Byte 1: I=1, L=1, T=1 → 0xE0
1208 // Byte 2-3: 16-bit PictureID → 0x80, 0x42
1209 // Byte 4: TL0PICIDX
1210 // Byte 5: TID/KEYIDX
1211 // Byte 6: VP8 payload header P=0
1212 assert!(detect_vp8_keyframe(&[
1213 0x90, 0xE0, 0x80, 0x42, 0x01, 0x00, 0x00
1214 ]));
1215 
1216 assert!(detect_vp8_keyframe(&[0x90, 0x40, 0x01, 0x00]));
1217 
1218 // Truncated: extension says PictureID but no bytes left
1219 assert!(!detect_vp8_keyframe(&[0x90, 0x80]));
1220 
1221 // Truncated: header consumed all bytes
1222 assert!(!detect_vp8_keyframe(&[0x90, 0x80, 0x42]));
1223 }
1224 
1225 #[test]
1226 fn test_vp8_descriptor_keyframe_detection_matches_helper() {
1227 let payloads: &[&[u8]] = &[
1228 &[],
1229 &[0x10, 0x00],
1230 &[0x10, 0x01],
1231 &[0x00, 0x00],
1232 &[0x11, 0x00],
1233 &[0x90, 0x80, 0x42, 0x00],
1234 &[0x90, 0x80, 0x42, 0x01],
1235 &[0x90, 0x80],
1236 &[0x90, 0x80, 0x42],
1237 ];
1238 
1239 for payload in payloads {
1240 let descriptor_starts_keyframe = Vp8Descriptor::parse(payload)
1241 .map(|descriptor| descriptor.starts_keyframe(payload))
1242 .unwrap_or(false);
1243 assert_eq!(descriptor_starts_keyframe, detect_vp8_keyframe(payload));
1244 }
1245 }
1246 
1247 #[test]
1248 fn packetize_respects_mtu() -> Result<(), PacketError> {
1249 let payload = vec![0xABu8; 2000];
1250 for &mtu in &[100usize, 300, 600, 1200] {
1251 let mut pck = Vp8Packetizer::default();
1252 let pkts = pck.packetize(mtu, &payload)?;
1253 assert!(!pkts.is_empty(), "VP8 produced no packets at mtu {mtu}");
1254 for (i, pkt) in pkts.iter().enumerate() {
1255 assert!(
1256 pkt.len() <= mtu,
1257 "VP8 packet {i} size {} > mtu {mtu}",
1258 pkt.len()
1259 );
1260 }
1261 }
1262 Ok(())
1263 }
1264}