Skip to content
File

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

rust1279 lines
1use super::{BitRead, CodecExtra, Depacketizer, PacketError, Packetizer, encode_leb_u63};
2 
3const OBU_EXTENSION_PRESENT_MASK: u8 = 0b0000_0100;
4const OBU_SIZE_PRESENT_MASK: u8 = 0b0000_0010;
5const OBU_TYPE_MASK: u8 = 0b0111_1000;
6const AGGREGATION_HEADER_SIZE: usize = 1;
7const MAX_NUM_OBUS_TO_OMTI_SIZE: usize = 3;
8 
9/// Detect whether an AV1 RTP payload contains a keyframe.
10///
11/// Checks the N bit (new coded video sequence) in the AV1 aggregation header.
12/// N=1 indicates the first packet of a keyframe (random access point).
13///
14/// AV1 aggregation header layout: `Z|Y|W W|N|reserved`
15/// - N (bit 3): 1 = new coded video sequence starts
16pub fn detect_av1_keyframe(payload: &[u8]) -> bool {
17 if payload.is_empty() {
18 return false;
19 }
20 // N bit is bit 3 of the aggregation header
21 payload[0] & 0x08 != 0
22}
23 
24/// AV1 information describing the depacketized / packetized data
25#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
26pub struct Av1CodecExtra {
27 /// Flag which indicates that within [`MediaData`], there is an individual frame
28 /// containing complete and independent visual information. This frame serves
29 /// as a reference point for other frames in the video sequence.
30 ///
31 /// [`MediaData`]: crate::media::MediaData
32 pub is_keyframe: bool,
33}
34 
35/// AV1 packetizer
36#[derive(Default, Debug)]
37pub struct Av1Packetizer {
38 packets: Vec<Packet>,
39 obus: Vec<Obu>,
40}
41 
42impl Av1Packetizer {
43 fn emit(&mut self, mtu: usize, payloads: &mut Vec<Vec<u8>>) {
44 if self.obus.is_empty() {
45 return;
46 }
47 
48 // Clear previous frame data
49 self.packets.clear();
50 
51 // Reserve 1 byte for aggregation header
52 let max_payload_size = mtu - AGGREGATION_HEADER_SIZE;
53 let mut remaining_packet_size = max_payload_size;
54 
55 // Push as many obus as possible into each packet
56 let mut current_packet = Packet::new(0);
57 for (obu_idx, obu) in self.obus.iter().enumerate() {
58 let mut previous_obu_extra_size = self.extra_size_for_previous_obu(&current_packet);
59 let min_required_size = if current_packet.num_obu_elements >= MAX_NUM_OBUS_TO_OMTI_SIZE
60 {
61 2
62 } else {
63 1
64 };
65 
66 // Check if new packet is needed
67 if remaining_packet_size < previous_obu_extra_size + min_required_size {
68 self.packets.push(current_packet);
69 current_packet = Packet::new(obu_idx);
70 remaining_packet_size = max_payload_size;
71 previous_obu_extra_size = 0;
72 }
73 
74 current_packet.packet_size += previous_obu_extra_size;
75 current_packet.num_obu_elements += 1;
76 remaining_packet_size -= previous_obu_extra_size;
77 
78 let must_write_obu_element_size =
79 current_packet.num_obu_elements > MAX_NUM_OBUS_TO_OMTI_SIZE;
80 let mut required_bytes = obu.size;
81 if must_write_obu_element_size {
82 required_bytes += leb_128_size(obu.size);
83 }
84 
85 if required_bytes <= remaining_packet_size {
86 current_packet.last_obu_size = obu.size;
87 current_packet.packet_size += required_bytes;
88 remaining_packet_size -= required_bytes;
89 continue;
90 }
91 
92 // Fragment the obu
93 let max_first_fragment_size = match must_write_obu_element_size {
94 true => self.max_fragment_size(remaining_packet_size),
95 false => remaining_packet_size,
96 };
97 
98 if max_first_fragment_size == 0 {
99 current_packet.num_obu_elements -= 1;
100 current_packet.packet_size -= previous_obu_extra_size;
101 } else {
102 current_packet.packet_size += max_first_fragment_size;
103 if must_write_obu_element_size {
104 current_packet.packet_size += leb_128_size(max_first_fragment_size);
105 }
106 current_packet.last_obu_size = max_first_fragment_size;
107 }
108 
109 // Middle fragments
110 let mut obu_offset = max_first_fragment_size;
111 while obu_offset + max_payload_size < obu.size {
112 self.packets.push(current_packet);
113 current_packet = Packet::new(obu_idx);
114 current_packet.num_obu_elements = 1;
115 current_packet.first_obu_offset = obu_offset;
116 current_packet.last_obu_size = max_payload_size;
117 current_packet.packet_size = max_payload_size;
118 
119 obu_offset += max_payload_size
120 }
121 
122 // Last fragment
123 let last_fragment_size = obu.size - obu_offset;
124 self.packets.push(current_packet);
125 current_packet = Packet::new(obu_idx);
126 current_packet.num_obu_elements = 1;
127 current_packet.first_obu_offset = obu_offset;
128 current_packet.last_obu_size = last_fragment_size;
129 current_packet.packet_size = last_fragment_size;
130 
131 remaining_packet_size = max_payload_size - last_fragment_size;
132 }
133 
134 self.packets.push(current_packet);
135 self.write_rtp_payloads(payloads)
136 }
137 
138 fn write_rtp_payloads(&self, payloads: &mut Vec<Vec<u8>>) {
139 for (i, packet) in self.packets.iter().enumerate() {
140 let is_first_packet = i == 0;
141 let mut rtp_payload: Vec<u8> = vec![0u8; AGGREGATION_HEADER_SIZE + packet.packet_size];
142 let mut pos = 0;
143 
144 let header = self.aggregation_header(packet, &self.obus, is_first_packet);
145 rtp_payload[pos] = header;
146 pos += 1;
147 
148 let mut obu_offset = packet.first_obu_offset;
149 // Write all obu elements except the last one
150 for obu_idx in 0..(packet.num_obu_elements - 1) {
151 let obu = &self.obus[packet.first_obu + obu_idx];
152 let obu_fragment_size = obu.size - obu_offset;
153 pos += encode_leb_u63(obu_fragment_size as u64, &mut rtp_payload[pos..]);
154 
155 if obu_offset == 0 {
156 rtp_payload[pos] = obu.header & !OBU_SIZE_PRESENT_MASK;
157 pos += 1;
158 }
159 
160 if obu_offset <= 1 && obu.has_extension() {
161 rtp_payload[pos] = obu.ext_header;
162 pos += 1;
163 }
164 
165 let payload_offset =
166 obu_offset.saturating_sub(if obu.has_extension() { 2 } else { 1 });
167 let payload_size = obu.payload.len() - payload_offset;
168 
169 if !obu.payload.is_empty() && payload_size > 0 {
170 rtp_payload[pos..pos + payload_size].copy_from_slice(
171 &obu.payload[payload_offset..payload_offset + payload_size],
172 );
173 }
174 
175 pos += payload_size;
176 obu_offset = 0;
177 }
178 
179 let last_obu = &self.obus[packet.first_obu + packet.num_obu_elements - 1];
180 let mut obu_fragment_size = packet.last_obu_size;
181 if packet.num_obu_elements > MAX_NUM_OBUS_TO_OMTI_SIZE {
182 pos += encode_leb_u63(obu_fragment_size as u64, &mut rtp_payload[pos..]);
183 }
184 
185 if obu_offset == 0 && obu_fragment_size > 0 {
186 rtp_payload[pos] = last_obu.header & !OBU_SIZE_PRESENT_MASK;
187 pos += 1;
188 obu_fragment_size -= 1;
189 }
190 
191 if obu_offset <= 1 && last_obu.has_extension() && obu_fragment_size > 0 {
192 rtp_payload[pos] = last_obu.ext_header;
193 pos += 1;
194 obu_fragment_size -= 1;
195 }
196 
197 let payload_offset =
198 obu_offset.saturating_sub(if last_obu.has_extension() { 2 } else { 1 });
199 rtp_payload[pos..pos + obu_fragment_size].copy_from_slice(
200 &last_obu.payload[payload_offset..payload_offset + obu_fragment_size],
201 );
202 
203 payloads.push(rtp_payload);
204 }
205 }
206 
207 fn aggregation_header(&self, packet: &Packet, obus: &[Obu], is_first_packet: bool) -> u8 {
208 let mut agg_header = 0;
209 
210 // set Z flag: the first obu element is the continuation of the previous one
211 if packet.first_obu_offset > 0 {
212 agg_header |= 1 << 7;
213 }
214 
215 // set Y flag: the last obu element is continued in the next packet
216 let last_obu_offset = if packet.num_obu_elements == 1 {
217 packet.first_obu_offset
218 } else {
219 0
220 };
221 let last_obu_is_fragment = (last_obu_offset + packet.last_obu_size)
222 < obus[packet.first_obu + packet.num_obu_elements - 1].size;
223 if last_obu_is_fragment {
224 agg_header |= 1 << 6;
225 }
226 
227 // set W field: small number of obu elements in the packet
228 if packet.num_obu_elements <= MAX_NUM_OBUS_TO_OMTI_SIZE {
229 agg_header |= packet.num_obu_elements << 4;
230 }
231 
232 // set N flag:
233 if let Some(ObuType::SequenceHeader) = obus[0].obu_type() {
234 if is_first_packet {
235 agg_header |= 1 << 3;
236 }
237 }
238 
239 agg_header as u8
240 }
241 
242 /// Adding new OBU to the last packet would mean that the previous one is
243 /// no longer the final OBU element in that packet. Any OBU that is not the
244 /// last element must be prefixed with its length. `extra_size_for_previous_obu`
245 /// computes the number of bytes required to encode that length.
246 fn extra_size_for_previous_obu(&self, packet: &Packet) -> usize {
247 if packet.packet_size == 0 {
248 return 0;
249 }
250 
251 if packet.num_obu_elements > MAX_NUM_OBUS_TO_OMTI_SIZE {
252 return 0;
253 }
254 
255 leb_128_size(packet.last_obu_size)
256 }
257 
258 // Given the number of free bytes remaining in a packet, the function returns the largest
259 // OBU fragment size that will fit into the packet
260 // That is, FragmentSize + Leb128Size(FragmentSize) must not exceed remaining_bytes
261 fn max_fragment_size(&self, size: usize) -> usize {
262 if size <= 1 {
263 return 0;
264 }
265 
266 let mut idx = 1;
267 loop {
268 if size < (1 << 7 * idx) + 1 {
269 return size - idx;
270 }
271 idx += 1;
272 }
273 }
274}
275 
276impl Packetizer for Av1Packetizer {
277 fn packetize(&mut self, mtu: usize, payload: &[u8]) -> Result<Vec<Vec<u8>>, PacketError> {
278 if payload.is_empty() || mtu <= AGGREGATION_HEADER_SIZE {
279 return Ok(vec![]);
280 }
281 
282 let mut payloads = vec![];
283 parse_obus(payload, &mut self.obus)?;
284 self.emit(mtu, &mut payloads);
285 
286 Ok(payloads)
287 }
288 
289 fn is_marker(&mut self, _data: &[u8], _previous: Option<&[u8]>, last: bool) -> bool {
290 last
291 }
292}
293 
294/// AV1 Depacketizer
295#[derive(Default, Debug)]
296pub struct Av1Depacketizer {
297 /// current obu payload
298 obu_buffer: Vec<u8>,
299 
300 /// reusable buffer for parsed OBUs
301 parsed_obus: Vec<Obu>,
302 
303 /// length of current obu
304 obu_length: usize,
305 
306 /// aggregation header Z bit
307 z: bool,
308 
309 /// aggregation header Y bit
310 y: bool,
311 
312 /// aggregation header N bit
313 n: bool,
314 
315 /// Number of obus in packet
316 obu_count: u8,
317}
318 
319impl Av1Depacketizer {
320 fn parse_aggregation_header(&mut self, agg_header: u8) {
321 // TODO: store these values as mask
322 self.z = agg_header & (1 << 7) != 0;
323 self.y = agg_header & (1 << 6) != 0;
324 self.obu_count = (agg_header & 0b0011_0000) >> 4;
325 self.n = agg_header & (1 << 3) != 0;
326 }
327}
328 
329impl Depacketizer for Av1Depacketizer {
330 fn out_size_hint(&self, packets_size: usize) -> Option<usize> {
331 Some(packets_size)
332 }
333 
334 fn depacketize(
335 &mut self,
336 packet: &[u8],
337 out: &mut Vec<u8>,
338 codec_extra: &mut super::CodecExtra,
339 ) -> Result<(), PacketError> {
340 if packet.is_empty() {
341 return Err(PacketError::ErrShortPacket);
342 }
343 
344 let mut reader = (packet, 0);
345 
346 self.parse_aggregation_header(packet[0]);
347 reader.skip_bytes(1);
348 
349 // if packet does not start with a continuation of an obu fragment
350 // from the previous packet new obu starts
351 if !self.z {
352 self.obu_length = 0;
353 self.obu_buffer.clear();
354 }
355 
356 // set the key frame flag if N bit is set and clear the obu buffer
357 // as it cannot start with a fragment of the previous packet
358 let mut is_keyframe = matches!(
359 *codec_extra,
360 CodecExtra::Av1(Av1CodecExtra { is_keyframe: true })
361 );
362 if self.n {
363 is_keyframe = true;
364 self.obu_length = 0;
365 self.obu_buffer.clear();
366 }
367 *codec_extra = CodecExtra::Av1(Av1CodecExtra { is_keyframe });
368 
369 let mut obu_idx: usize = 0;
370 while reader.remaining_bits() > 0 {
371 let is_first_obu = obu_idx == 0;
372 let mut is_last_obu = self.obu_count != 0 && obu_idx == (self.obu_count as usize - 1);
373 
374 // Read the length of obu
375 let fragment_obu_length = if self.obu_count == 0 || !is_last_obu {
376 let len = reader
377 .get_leb128()
378 .ok_or(PacketError::ErrAv1CorruptedPacket)?;
379 
380 if self.obu_count == 0 && len == reader.remaining_bytes() {
381 is_last_obu = true;
382 }
383 
384 len
385 } else {
386 reader.remaining_bytes()
387 };
388 
389 // Safety checks
390 if fragment_obu_length == 0 {
391 return Err(PacketError::ErrAv1CorruptedPacket);
392 }
393 if reader.byte_offset() > packet.len()
394 || fragment_obu_length > packet.len() - reader.byte_offset()
395 {
396 return Err(PacketError::ErrAv1CorruptedPacket);
397 }
398 
399 if is_first_obu && self.z {
400 // the previous fragment is lost, drop the buffer
401 if self.obu_buffer.is_empty() {
402 reader.skip_bytes(fragment_obu_length);
403 obu_idx = 1;
404 continue;
405 }
406 }
407 
408 let offset = reader.byte_offset();
409 self.obu_buffer
410 .extend_from_slice(&packet[offset..offset + fragment_obu_length]);
411 reader.skip_bytes(fragment_obu_length);
412 
413 if is_last_obu && self.y {
414 self.obu_length += fragment_obu_length;
415 break;
416 }
417 
418 self.obu_length += fragment_obu_length;
419 
420 parse_obus(&self.obu_buffer, &mut self.parsed_obus)?;
421 let Some(obu) = self.parsed_obus.first_mut() else {
422 self.obu_length = 0;
423 self.obu_buffer.clear();
424 obu_idx += 1;
425 continue;
426 };
427 
428 // Write the obu payload to output
429 // set size flag
430 let size_flag_set = obu.header & OBU_SIZE_PRESENT_MASK != 0;
431 obu.header |= OBU_SIZE_PRESENT_MASK;
432 out.push(obu.header);
433 self.obu_length = self.obu_length.saturating_sub(1);
434 
435 // add extension if any
436 if obu.has_extension() {
437 out.push(obu.ext_header);
438 self.obu_length = self.obu_length.saturating_sub(1);
439 }
440 
441 // add size if not present
442 if !size_flag_set {
443 let mut temp_space = [0u8; 9];
444 let bytes_written = encode_leb_u63(self.obu_length as u64, &mut temp_space[..]);
445 out.extend_from_slice(&temp_space[..bytes_written]);
446 }
447 
448 // finally payload
449 if self.obu_length > self.obu_buffer.len() {
450 return Err(PacketError::ErrAv1CorruptedPacket);
451 }
452 let start_idx = self.obu_buffer.len() - self.obu_length;
453 out.extend_from_slice(&self.obu_buffer[start_idx..start_idx + self.obu_length]);
454 
455 self.obu_length = 0;
456 self.obu_buffer.clear();
457 
458 if is_last_obu {
459 self.obu_length = 0;
460 self.obu_buffer.clear();
461 break;
462 }
463 
464 obu_idx += 1;
465 }
466 
467 Ok(())
468 }
469 
470 fn is_partition_head(&self, packet: &[u8]) -> bool {
471 if packet.is_empty() {
472 return false;
473 }
474 
475 // Z bit in the aggregation header is set to 0
476 packet[0] & (1 << 7) == 0
477 }
478 
479 fn is_partition_tail(&self, marker: bool, _payload: &[u8]) -> bool {
480 marker
481 }
482}
483 
484#[repr(u8)]
485enum ObuType {
486 SequenceHeader = 1,
487 TemporalDelimiter = 2,
488 FrameHeader = 3,
489 TileGroup = 4,
490 MetaData = 5,
491 Frame = 6,
492 RedundantFrameHeader = 7,
493 TileList = 8,
494 Padding = 15,
495}
496 
497impl ObuType {
498 fn include_in_packetization(self) -> bool {
499 !matches!(
500 self,
501 Self::TemporalDelimiter | Self::TileList | Self::Padding
502 )
503 }
504}
505 
506/// Represents an OBU (Open Bitstream Unit) with its header, extension, payload, and size.
507#[derive(Default, Debug)]
508struct Obu {
509 header: u8,
510 ext_header: u8,
511 payload: Vec<u8>,
512 size: usize,
513}
514 
515impl Obu {
516 fn has_extension(&self) -> bool {
517 (self.header & OBU_EXTENSION_PRESENT_MASK) != 0
518 }
519 
520 fn has_size(&self) -> bool {
521 (self.header & OBU_SIZE_PRESENT_MASK) != 0
522 }
523 
524 fn obu_type(&self) -> Option<ObuType> {
525 match (self.header & OBU_TYPE_MASK) >> 3 {
526 1 => Some(ObuType::SequenceHeader),
527 2 => Some(ObuType::TemporalDelimiter),
528 3 => Some(ObuType::FrameHeader),
529 4 => Some(ObuType::TileGroup),
530 5 => Some(ObuType::MetaData),
531 6 => Some(ObuType::Frame),
532 7 => Some(ObuType::RedundantFrameHeader),
533 8 => Some(ObuType::TileList),
534 15 => Some(ObuType::Padding),
535 _ => None,
536 }
537 }
538}
539 
540/// The `Packet` struct holds information about the OBU (Open Bitstream Unit) and
541/// related metadata necessary for constructing an RTP packet payload during packetization
542#[derive(Debug)]
543struct Packet {
544 first_obu: usize,
545 num_obu_elements: usize,
546 first_obu_offset: usize,
547 last_obu_size: usize,
548 packet_size: usize,
549}
550 
551impl Packet {
552 pub fn new(first_obu: usize) -> Self {
553 Packet {
554 first_obu,
555 num_obu_elements: 0,
556 first_obu_offset: 0,
557 last_obu_size: 0,
558 packet_size: 0,
559 }
560 }
561}
562 
563/// Returns the number of bytes required to encode a value using unsigned LEB128 encoding.
564/// LEB128 uses variable-length encoding with 7 bits per byte, and this function computes
565/// how many bytes are needed to represent the given `value`.
566pub fn leb_128_size(mut value: usize) -> usize {
567 let mut size = 0;
568 
569 while value >= 0x80 {
570 size += 1;
571 value >>= 7;
572 }
573 
574 size + 1
575}
576 
577/// OBU Header Structure (8 bits total):
578///
579/// 0 1 2 3 4 5 6 7
580/// +-+-+-+-+-+-+-+-+
581/// |F| T |S|E|R|
582/// +-+-+-+-+-+-+-+-+
583///
584/// Explanation of each bit:
585/// - F (1 bit) - OBU Forbidden Bit: must be set to 0.
586/// - T (4 bits) - OBU Type: This field specifies the type of data structure contained in the OBU payload.
587/// - S (1 bit) - OBU Has Size Field: A flag indicating whether the obu_size syntax element will be present.
588/// - E (1 bit) - OBU Extension Flag: A flag indicatin if the optional obu_extension_header is present.
589/// - R (1 bit) - OBU Reserved Bits: must be set to 0. The value is ignored by a decoder.
590fn parse_obus(payload: &[u8], parsed_obus: &mut Vec<Obu>) -> Result<(), PacketError> {
591 let mut reader = (payload, 0);
592 let mut obu_idx = 0;
593 
594 while reader.remaining_bits() > 0 {
595 // Reuse existing Obu entry if available, otherwise grow the Vec
596 if obu_idx >= parsed_obus.len() {
597 parsed_obus.push(Obu::default());
598 }
599 let obu = &mut parsed_obus[obu_idx];
600 
601 obu.header = reader.get_u8().ok_or(PacketError::ErrAv1CorruptedPacket)?;
602 obu.ext_header = 0;
603 obu.size = 1;
604 
605 if obu.has_extension() {
606 obu.ext_header = reader.get_u8().ok_or(PacketError::ErrAv1CorruptedPacket)?;
607 obu.size += 1;
608 }
609 
610 // Reuse the existing payload Vec โ€” clear preserves capacity
611 obu.payload.clear();
612 
613 if obu.has_size() {
614 let obu_size = reader
615 .get_leb128()
616 .ok_or(PacketError::ErrAv1CorruptedPacket)?;
617 if obu_size > reader.remaining_bytes() {
618 return Err(PacketError::ErrAv1CorruptedPacket);
619 }
620 let bytes = reader
621 .get_bytes(obu_size)
622 .ok_or(PacketError::ErrAv1CorruptedPacket)?;
623 obu.payload.extend_from_slice(bytes);
624 } else {
625 obu.payload.extend_from_slice(reader.get_remaining());
626 }
627 obu.size += obu.payload.len();
628 
629 if let Some(obu_type) = obu.obu_type() {
630 if obu_type.include_in_packetization() {
631 obu_idx += 1;
632 }
633 }
634 }
635 
636 // Trim any leftover entries from previous calls
637 parsed_obus.truncate(obu_idx);
638 Ok(())
639}
640 
641#[cfg(test)]
642mod test {
643 use super::*;
644 
645 /// AV1 Packetizer tets
646 #[test]
647 fn packetize_one_frame_type_obu_without_size() {
648 let payload = &[0x30, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
649 let mut packetizer = Av1Packetizer::default();
650 
651 let result = packetizer.packetize(1200, payload);
652 
653 assert!(result.is_ok());
654 assert_eq!(
655 result.unwrap(),
656 [[0x10, 0x30, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]]
657 );
658 }
659 
660 #[test]
661 fn packetize_one_frame_type_obu_without_size_with_extension() {
662 let payload = &[0x34, 0x28, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
663 let mut packetizer = Av1Packetizer::default();
664 
665 let result = packetizer.packetize(1200, payload);
666 
667 assert!(result.is_ok());
668 assert_eq!(
669 result.unwrap(),
670 [[0x10, 0x34, 0x28, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]]
671 );
672 }
673 
674 #[test]
675 fn packetize_one_frame_type_obu_remove_size_field_without_extension() {
676 let payload = &[0x32, 0x07, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11];
677 let mut packetizer = Av1Packetizer::default();
678 
679 let result = packetizer.packetize(1200, payload);
680 
681 assert!(result.is_ok());
682 assert_eq!(
683 result.unwrap(),
684 [[0x10, 0x30, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11]]
685 );
686 }
687 
688 #[test]
689 fn packetize_one_frame_type_obu_remove_size_field_with_extension() {
690 let payload = &[0x36, 0x28, 0x07, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
691 let mut packetizer = Av1Packetizer::default();
692 
693 let result = packetizer.packetize(1200, payload);
694 
695 assert!(result.is_ok());
696 assert_eq!(
697 result.unwrap(),
698 [[0x10, 0x34, 0x28, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]]
699 );
700 }
701 
702 #[test]
703 fn omit_size_for_last_obu_when_three_obus_fits_into_the_packet() {
704 let payload = &[
705 0x0a, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // sequence header
706 0x2a, 0x04, 0x0b, 0x0c, 0x0d, 0x0e, // metadata
707 0x32, 0x06, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, // frame
708 ];
709 let mut packetizer = Av1Packetizer::default();
710 
711 let result = packetizer.packetize(1200, payload);
712 
713 assert!(result.is_ok());
714 assert_eq!(
715 result.unwrap(),
716 [[
717 0x38, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x05, 0x28, 0x0b, 0x0c, 0x0d,
718 0x0e, 0x30, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a
719 ]]
720 );
721 }
722 
723 #[test]
724 fn use_size_for_all_obus_when_four_obus_fit_into_the_packet() {
725 let payload = &[
726 0x0a, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // sequence header
727 0x2a, 0x04, 0x0b, 0x0c, 0x0d, 0x0e, // metadata
728 0x1a, 0x03, 0x15, 0x16, 0x17, // frame
729 0x22, 0x06, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, // tile group
730 ];
731 let mut packetizer = Av1Packetizer::default();
732 
733 let result = packetizer.packetize(1200, payload);
734 
735 assert!(result.is_ok());
736 assert_eq!(
737 result.unwrap(),
738 [[
739 0x08, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x05, 0x28, 0x0b, 0x0c, 0x0d,
740 0x0e, 0x04, 0x18, 0x15, 0x16, 0x17, 0x07, 0x20, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24
741 ]]
742 );
743 }
744 
745 #[test]
746 fn discards_temporal_delimiter_and_tile_list_obu() {
747 let payload = &[
748 0x12, 0x00, // temporal delimeter
749 0x2a, 0x00, // metadata
750 0x42, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // tile list
751 0x1a, 0x03, 0x15, 0x16, 0x17, // frame
752 0x22, 0x06, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, // tile group
753 ];
754 let mut packetizer = Av1Packetizer::default();
755 
756 let result = packetizer.packetize(1200, payload);
757 
758 assert!(result.is_ok());
759 assert_eq!(
760 result.unwrap(),
761 [[
762 0x30, 0x01, 0x28, 0x04, 0x18, 0x15, 0x16, 0x17, 0x20, 0x1f, 0x20, 0x21, 0x22, 0x23,
763 0x24
764 ]]
765 );
766 }
767 
768 #[test]
769 fn split_two_obus_into_two_packets_force_split_obu_header() {
770 let payload = &[
771 0x1e, 0x28, 0x01, 0x15, // frame
772 0x26, 0x28, 0x04, 0x0b, 0x0c, 0x0d, 0x0e, // tile group
773 ];
774 let mut packetizer = Av1Packetizer::default();
775 
776 let result = packetizer.packetize(6, payload);
777 
778 assert!(result.is_ok());
779 assert_eq!(
780 result.unwrap(),
781 vec![
782 vec![0x60, 0x03, 0x1c, 0x28, 0x15, 0x24],
783 vec![0x90, 0x28, 0x0b, 0x0c, 0x0d, 0x0e]
784 ]
785 );
786 }
787 
788 #[test]
789 fn set_nbit_at_the_first_packet_of_coded_video_sequence() {}
790 
791 #[test]
792 fn split_single_obu_into_two_packets() {
793 let payload = &[
794 0x32, 0x09, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
795 ];
796 let mut packetizer = Av1Packetizer::default();
797 
798 let result = packetizer.packetize(8, payload);
799 
800 assert!(result.is_ok());
801 assert_eq!(
802 result.unwrap(),
803 vec![
804 vec![0x50, 0x30, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10],
805 vec![0x90, 0x11, 0x12, 0x13]
806 ]
807 );
808 }
809 
810 #[test]
811 fn split_single_obu_into_many_packets() {
812 let mut payload: Vec<u8> = vec![0x32, 0xB0, 0x09]; // obu header and leb128 encoded size
813 payload.extend(vec![27u8; 1200]); // obu payload
814 
815 let mut packetizer = Av1Packetizer::default();
816 
817 let result = packetizer.packetize(100, &payload);
818 
819 assert!(result.is_ok());
820 assert_eq!(result.unwrap().len(), 13 as usize);
821 }
822 
823 #[test]
824 fn split_two_obus_into_two_packets() {
825 let payload = &[
826 0x0a, 0x02, 0x0b, 0x0c, // sequence header
827 0x32, 0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // frame
828 ];
829 let mut packetizer = Av1Packetizer::default();
830 
831 let result = packetizer.packetize(8, payload);
832 
833 assert!(result.is_ok());
834 assert_eq!(
835 result.unwrap(),
836 vec![
837 vec![0x68, 0x03, 0x08, 0x0b, 0x0c, 0x30, 0x01, 0x02],
838 vec![0x90, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09]
839 ]
840 );
841 }
842 
843 #[test]
844 fn split_single_obu_into_two_packets_because_of_mtu_limit() {
845 let payload = &[
846 0x32, 0x09, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
847 ];
848 let mut packetizer = Av1Packetizer::default();
849 
850 let result = packetizer.packetize(10, payload);
851 
852 assert!(result.is_ok());
853 assert_eq!(
854 result.unwrap(),
855 vec![
856 vec![0x50, 0x30, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12],
857 vec![0x90, 0x13]
858 ]
859 );
860 }
861 
862 /// AV1 Depacketizer tests
863 #[test]
864 fn obu_payload_size_set_when_absent() {
865 let paylod = &[0x10, 0x30, 0x14, 0x1e, 0x28];
866 let mut out = Vec::new();
867 let mut codec_extra = CodecExtra::None;
868 let mut depacketizer = Av1Depacketizer::default();
869 
870 let result = depacketizer.depacketize(paylod, &mut out, &mut codec_extra);
871 
872 assert!(result.is_ok());
873 assert_eq!(out, vec![0x32, 0x03, 0x14, 0x1e, 0x28]);
874 }
875 
876 #[test]
877 fn obu_payload_size_set_when_present() {
878 let paylod = &[0x10, 0x32, 0x03, 0x14, 0x1e, 0x28];
879 let mut out = Vec::new();
880 let mut codec_extra = CodecExtra::None;
881 let mut depacketizer = Av1Depacketizer::default();
882 
883 let result = depacketizer.depacketize(paylod, &mut out, &mut codec_extra);
884 
885 assert!(result.is_ok());
886 assert_eq!(out, vec![0x32, 0x03, 0x14, 0x1e, 0x28]);
887 }
888 
889 #[test]
890 fn obu_payload_size_set_after_extension_when_absent() {
891 let paylod = &[0x10, 0x34, 0x48, 0x14, 0x1e, 0x28];
892 let mut out = Vec::new();
893 let mut codec_extra = CodecExtra::None;
894 let mut depacketizer = Av1Depacketizer::default();
895 
896 let result = depacketizer.depacketize(paylod, &mut out, &mut codec_extra);
897 
898 assert!(result.is_ok());
899 assert_eq!(out, vec![0x36, 0x48, 0x03, 0x14, 0x1e, 0x28]);
900 }
901 
902 #[test]
903 fn obu_payload_size_set_after_extension_when_present() {
904 let paylod = &[0x10, 0x36, 0x48, 0x03, 0x14, 0x1e, 0x28];
905 let mut out = Vec::new();
906 let mut codec_extra = CodecExtra::None;
907 let mut depacketizer = Av1Depacketizer::default();
908 
909 let result = depacketizer.depacketize(paylod, &mut out, &mut codec_extra);
910 
911 assert!(result.is_ok());
912 assert_eq!(out, vec![0x36, 0x48, 0x03, 0x14, 0x1e, 0x28]);
913 }
914 
915 #[test]
916 fn one_packet_with_two_obus() {
917 let paylod = &[0x20, 0x02, 0x08, 0x0a, 0x30, 0x14];
918 let mut out = Vec::new();
919 let mut codec_extra = CodecExtra::None;
920 let mut depacketizer = Av1Depacketizer::default();
921 
922 let result = depacketizer.depacketize(paylod, &mut out, &mut codec_extra);
923 
924 assert!(result.is_ok());
925 assert_eq!(out, vec![0x0a, 0x01, 0x0a, 0x32, 0x01, 0x14]);
926 }
927 
928 #[test]
929 fn one_obu_from_two_packets() {
930 let payload1 = &[0x50, 0x30, 0x14, 0x1e];
931 let payload2 = &[0x90, 0x28];
932 let mut out = Vec::new();
933 let mut codec_extra = CodecExtra::None;
934 let mut depacketizer = Av1Depacketizer::default();
935 
936 let result = depacketizer.depacketize(payload1, &mut out, &mut codec_extra);
937 assert!(result.is_ok());
938 
939 let result = depacketizer.depacketize(payload2, &mut out, &mut codec_extra);
940 assert!(result.is_ok());
941 assert_eq!(out, vec![0x32, 0x03, 0x14, 0x1e, 0x28]);
942 }
943 
944 #[test]
945 fn two_packets_with_three_obus() {
946 let payload1 = &[0x60, 0x02, 0x08, 0x0a, 0x30, 0x14, 0x1e];
947 let payload2 = &[0x90, 0x28];
948 let mut out = Vec::new();
949 let mut codec_extra = CodecExtra::None;
950 let mut depacketizer = Av1Depacketizer::default();
951 
952 let result = depacketizer.depacketize(payload1, &mut out, &mut codec_extra);
953 assert!(result.is_ok());
954 
955 let result = depacketizer.depacketize(payload2, &mut out, &mut codec_extra);
956 assert!(result.is_ok());
957 assert_eq!(out, vec![0x0a, 0x01, 0x0a, 0x32, 0x03, 0x14, 0x1e, 0x28]);
958 }
959 
960 #[test]
961 fn two_packets_with_many_obus_some_with_extensions() {
962 let payload1 = &[
963 0x40, 0x02, 0x08, 0x0a, 0x02, 0x28, 0x14, 0x04, 0x2c, 0x30, 0x14, 0x1e, 0x05, 0x34,
964 0x30, 0x28, 0x32, 0x3c,
965 ];
966 let payload2 = &[0x90, 0x46, 0x50, 0x5a];
967 let mut out = Vec::new();
968 let mut codec_extra = CodecExtra::None;
969 let mut depacketizer = Av1Depacketizer::default();
970 
971 let result = depacketizer.depacketize(payload1, &mut out, &mut codec_extra);
972 assert!(result.is_ok());
973 
974 let result = depacketizer.depacketize(payload2, &mut out, &mut codec_extra);
975 assert!(result.is_ok());
976 assert_eq!(
977 out,
978 vec![
979 0x0a, 0x01, 0x0a, 0x2a, 0x01, 0x14, 0x2e, 0x30, 0x02, 0x14, 0x1e, 0x36, 0x30, 0x06,
980 0x28, 0x32, 0x3c, 0x46, 0x50, 0x5a
981 ]
982 );
983 }
984 
985 #[test]
986 fn one_obu_from_many_packets() {
987 let payload1 = &[0x50, 0x30, 0x0b, 0x0c];
988 let payload2 = &[0xd0, 0x0d, 0x0e];
989 let payload3 = &[0xd0, 0x0f, 0x10, 0x11];
990 let payload4 = &[0x90, 0x12];
991 let mut out = Vec::new();
992 let mut codec_extra = CodecExtra::None;
993 let mut depacketizer = Av1Depacketizer::default();
994 
995 let result = depacketizer.depacketize(payload1, &mut out, &mut codec_extra);
996 assert!(result.is_ok());
997 let result = depacketizer.depacketize(payload2, &mut out, &mut codec_extra);
998 assert!(result.is_ok());
999 let result = depacketizer.depacketize(payload3, &mut out, &mut codec_extra);
1000 assert!(result.is_ok());
1001 let result = depacketizer.depacketize(payload4, &mut out, &mut codec_extra);
1002 assert!(result.is_ok());
1003 assert_eq!(
1004 out,
1005 vec![0x32, 0x08, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12]
1006 );
1007 }
1008 
1009 #[test]
1010 fn many_packets_with_border_aligned_obus() {
1011 let payload1 = &[0x60, 0x03, 0x18, 0x0b, 0x0c, 0x20, 0x15, 0x16, 0x17];
1012 let payload2 = &[0x90, 0x18, 0x19, 0x1a, 0x1b];
1013 let payload3 = &[0x60, 0x03, 0x38, 0x0b, 0x0c, 0x20, 0x1f, 0x20];
1014 let payload4 = &[0x90, 0x21, 0x22, 0x23, 0x24];
1015 let mut out = Vec::new();
1016 let mut codec_extra = CodecExtra::None;
1017 let mut depacketizer = Av1Depacketizer::default();
1018 
1019 let result = depacketizer.depacketize(payload1, &mut out, &mut codec_extra);
1020 assert!(result.is_ok());
1021 let result = depacketizer.depacketize(payload2, &mut out, &mut codec_extra);
1022 assert!(result.is_ok());
1023 let result = depacketizer.depacketize(payload3, &mut out, &mut codec_extra);
1024 assert!(result.is_ok());
1025 let result = depacketizer.depacketize(payload4, &mut out, &mut codec_extra);
1026 assert!(result.is_ok());
1027 assert_eq!(
1028 out,
1029 vec![
1030 0x1a, 0x02, 0x0b, 0x0c, 0x22, 0x07, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x3a,
1031 0x02, 0x0b, 0x0c, 0x22, 0x06, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24
1032 ]
1033 );
1034 }
1035 
1036 #[test]
1037 fn one_packet_one_obu_with_payload_size_127_bytes() {
1038 let mut payload = [0; 131];
1039 let mut out = Vec::new();
1040 let mut codec_extra = CodecExtra::None;
1041 let mut depacketizer = Av1Depacketizer::default();
1042 
1043 payload[0] = 0b0000_0000; // aggregation header
1044 payload[1] = 0x80; // leb128 encoded size of 128 bytes
1045 payload[2] = 0x01; // in two bytes
1046 payload[3] = 0b0011_0000; // obu_header with size and extension bits unset.
1047 payload[4 + 42] = 0x42; // random
1048 
1049 let result = depacketizer.depacketize(&payload, &mut out, &mut codec_extra);
1050 assert!(result.is_ok());
1051 assert_eq!(out[0], 0b0011_0010); // size bit set
1052 assert_eq!(out[1], 127); // obu payloa size, 1 byte is enough
1053 assert_eq!(out[44], 0x42); // check random byte
1054 }
1055 
1056 #[test]
1057 fn two_packets_one_obu_with_payload_size_128_bytes() {
1058 let mut payload1 = [0; 35];
1059 let mut payload2 = [0; 98];
1060 let mut out = Vec::new();
1061 let mut codec_extra = CodecExtra::None;
1062 let mut depacketizer = Av1Depacketizer::default();
1063 
1064 payload1[0] = 0b0100_0000; // aggregation header
1065 payload1[1] = 33; // payload size
1066 payload1[2] = 0b0011_0000; // obu_header with size and extension bits unset.
1067 payload1[3 + 10] = 0x10; // random
1068 
1069 payload2[0] = 0b1000_0000;
1070 payload2[1] = 96;
1071 payload2[2 + 20] = 0x20;
1072 
1073 let result = depacketizer.depacketize(&payload1, &mut out, &mut codec_extra);
1074 assert!(result.is_ok());
1075 let result = depacketizer.depacketize(&payload2, &mut out, &mut codec_extra);
1076 assert!(result.is_ok());
1077 assert_eq!(out[0], 0b0011_0010);
1078 assert_eq!(out[1], 0x80);
1079 assert_eq!(out[2], 0x01);
1080 assert_eq!(out[3 + 10], 0x10);
1081 assert_eq!(out[3 + 32 + 20], 0x20);
1082 }
1083 
1084 // Regression tests for leb_128_size boundary values.
1085 // The bug was `value > 0x80` instead of `value >= 0x80`.
1086 // LEB128 encodes 7 bits per byte, so values >= 128 need 2 bytes,
1087 // values >= 16384 need 3 bytes, etc.
1088 
1089 #[test]
1090 fn leb128_size_boundary_at_128() {
1091 // 127 fits in 1 byte (7 bits), 128 needs 2 bytes
1092 assert_eq!(leb_128_size(0), 1);
1093 assert_eq!(leb_128_size(1), 1);
1094 assert_eq!(leb_128_size(127), 1);
1095 assert_eq!(leb_128_size(128), 2); // was incorrectly 1 before fix
1096 assert_eq!(leb_128_size(129), 2);
1097 assert_eq!(leb_128_size(255), 2);
1098 }
1099 
1100 #[test]
1101 fn leb128_size_boundary_at_16384() {
1102 // 16383 fits in 2 bytes (14 bits), 16384 needs 3 bytes
1103 assert_eq!(leb_128_size(16383), 2);
1104 assert_eq!(leb_128_size(16384), 3); // was incorrectly 2 before fix
1105 assert_eq!(leb_128_size(16385), 3);
1106 }
1107 
1108 #[test]
1109 fn leb128_size_boundary_at_2097152() {
1110 // 2097151 fits in 3 bytes (21 bits), 2097152 needs 4 bytes
1111 assert_eq!(leb_128_size(2097151), 3);
1112 assert_eq!(leb_128_size(2097152), 4); // was incorrectly 3 before fix
1113 }
1114 
1115 // Regression test: packetize an OBU with exactly 128 bytes of payload.
1116 // This triggers the leb_128_size boundary (128 needs 2-byte LEB128).
1117 // Before the fix, leb_128_size(128) returned 1 instead of 2, causing
1118 // the output buffer to be 1 byte too small โ†’ panic on slice indexing.
1119 #[test]
1120 fn packetize_obu_with_128_byte_payload_no_panic() {
1121 // Build a raw OBU: header (0x32 = frame, size present)
1122 // + LEB128 size (0x80, 0x01 = 128) + 128 bytes payload
1123 let mut payload = Vec::with_capacity(3 + 128);
1124 payload.push(0x32); // OBU header: frame type, size present
1125 payload.push(0x80); // LEB128 size byte 1: 128
1126 payload.push(0x01); // LEB128 size byte 2
1127 payload.extend(vec![0xAB; 128]); // 128 bytes of payload
1128 
1129 let mut packetizer = Av1Packetizer::default();
1130 let result = packetizer.packetize(1200, &payload);
1131 assert!(result.is_ok());
1132 let packets = result.unwrap();
1133 assert!(!packets.is_empty());
1134 }
1135 
1136 // Regression test: packetize an OBU that requires fragmentation across
1137 // multiple RTP packets, with exactly 128 bytes of payload.
1138 // This exercises both the leb_128_size fix (buffer allocation) and
1139 // the payload_offset fix (correct data in continuation fragments).
1140 #[test]
1141 fn packetize_128_byte_obu_fragmented_correctly() {
1142 // OBU: header + LEB128(128) + 128 bytes payload
1143 let mut payload = Vec::with_capacity(3 + 128);
1144 payload.push(0x32); // frame type, size present
1145 payload.push(0x80); // LEB128: 128
1146 payload.push(0x01);
1147 for i in 0u8..128 {
1148 payload.push(i); // distinguishable payload bytes
1149 }
1150 
1151 let mut packetizer = Av1Packetizer::default();
1152 // MTU of 50 forces fragmentation into multiple packets
1153 let result = packetizer.packetize(50, &payload);
1154 assert!(result.is_ok());
1155 let packets = result.unwrap();
1156 assert!(
1157 packets.len() >= 3,
1158 "128-byte OBU with MTU 50 should produce at least 3 packets"
1159 );
1160 
1161 // Verify round-trip: depacketize all packets and check payload integrity
1162 let mut out = Vec::new();
1163 let mut codec_extra = CodecExtra::None;
1164 let mut depacketizer = Av1Depacketizer::default();
1165 for pkt in &packets {
1166 let r = depacketizer.depacketize(pkt, &mut out, &mut codec_extra);
1167 assert!(r.is_ok(), "depacketize failed: {:?}", r);
1168 }
1169 // Output should be: OBU header (with size bit) + LEB128 size + payload
1170 // Find the payload portion and verify it matches
1171 assert!(
1172 out.len() >= 128,
1173 "round-tripped output too short: {} bytes",
1174 out.len()
1175 );
1176 // The payload bytes should appear in order at the end
1177 let payload_start = out.len() - 128;
1178 for i in 0u8..128 {
1179 assert_eq!(
1180 out[payload_start + i as usize],
1181 i,
1182 "payload byte {} mismatch after round-trip",
1183 i
1184 );
1185 }
1186 }
1187 
1188 // Regression test for the payload_offset bug in continuation fragments.
1189 // When an OBU with extension header spans multiple packets, the second
1190 // fragment used `obu_offset` instead of `payload_offset` to compute the
1191 // remaining payload size, causing incorrect data or panics.
1192 #[test]
1193 fn packetize_obu_with_extension_fragmented_round_trip() {
1194 // OBU with extension: header=0x36 (frame type + extension + size present),
1195 // ext_header=0x10, LEB128 size, then payload
1196 let mut raw = Vec::with_capacity(4 + 200);
1197 raw.push(0x36); // OBU header: frame, extension present, size present
1198 raw.push(0x10); // extension header
1199 raw.push(0xC8); // LEB128 size: 200
1200 raw.push(0x01);
1201 for i in 0u8..200 {
1202 raw.push(i);
1203 }
1204 
1205 let mut packetizer = Av1Packetizer::default();
1206 let result = packetizer.packetize(40, &raw);
1207 assert!(result.is_ok());
1208 let packets = result.unwrap();
1209 assert!(
1210 packets.len() >= 5,
1211 "200-byte OBU with MTU 40 should produce multiple packets"
1212 );
1213 
1214 // Round-trip through depacketizer
1215 let mut out = Vec::new();
1216 let mut codec_extra = CodecExtra::None;
1217 let mut depacketizer = Av1Depacketizer::default();
1218 for pkt in &packets {
1219 depacketizer
1220 .depacketize(pkt, &mut out, &mut codec_extra)
1221 .unwrap();
1222 }
1223 
1224 // Verify the 200-byte payload is intact
1225 let payload_start = out.len() - 200;
1226 for i in 0u8..200 {
1227 assert_eq!(
1228 out[payload_start + i as usize],
1229 i,
1230 "extension OBU payload byte {} corrupted after round-trip",
1231 i
1232 );
1233 }
1234 }
1235 
1236 #[test]
1237 fn test_detect_av1_keyframe() {
1238 // Empty
1239 assert!(!detect_av1_keyframe(&[]));
1240 
1241 // AV1 aggregation header: Z|Y|W W|N|reserved
1242 // N bit is bit 3 (0x08)
1243 
1244 // N=1 โ†’ keyframe
1245 assert!(detect_av1_keyframe(&[0x08]));
1246 assert!(detect_av1_keyframe(&[0x18])); // Z=0,Y=0,W=01,N=1
1247 assert!(detect_av1_keyframe(&[0x78])); // Z=0,Y=1,W=11,N=1
1248 assert!(detect_av1_keyframe(&[0x88])); // Z=1,Y=0,W=00,N=1
1249 assert!(detect_av1_keyframe(&[0x0F])); // N=1, reserved bits set
1250 
1251 // N=0 โ†’ not a keyframe
1252 assert!(!detect_av1_keyframe(&[0x00]));
1253 assert!(!detect_av1_keyframe(&[0x10])); // W=01, N=0
1254 assert!(!detect_av1_keyframe(&[0x70])); // Y=1, W=11, N=0
1255 assert!(!detect_av1_keyframe(&[0xF0])); // Z=1, Y=1, W=11, N=0
1256 }
1257 
1258 #[test]
1259 fn packetize_respects_mtu() {
1260 // OBU header (frame-type, no size field) + payload bytes.
1261 let mut payload = vec![0x30u8];
1262 payload.extend(std::iter::repeat(0xABu8).take(2000));
1263 for &mtu in &[100usize, 300, 600, 1200] {
1264 let mut packetizer = Av1Packetizer::default();
1265 let pkts = packetizer
1266 .packetize(mtu, &payload)
1267 .expect("AV1 packetize ok");
1268 assert!(!pkts.is_empty(), "AV1 produced no packets at mtu {mtu}");
1269 for (i, pkt) in pkts.iter().enumerate() {
1270 assert!(
1271 pkt.len() <= mtu,
1272 "AV1 packet {i} size {} > mtu {mtu}",
1273 pkt.len()
1274 );
1275 }
1276 }
1277 }
1278}