use super::{CodecExtra, Depacketizer, PacketError, Packetizer};
use tracing::warn;
pub static ANNEXB_NALUSTART_CODE: &[u8] = &[0x00, 0x00, 0x00, 0x01];
const H266NALU_HEADER_SIZE: usize = 2;
const H266FRAGMENTATION_UNIT_HEADER_SIZE: usize = 1;
const H266NALU_IDR_W_RADL: u8 = 7;
const H266NALU_IDR_N_LP: u8 = 8;
const H266NALU_CRA: u8 = 9;
#[allow(dead_code)] const H266NALU_GDR: u8 = 10;
const H266NALU_VPS_NALU_TYPE: u8 = 14;
const H266NALU_SPS_NALU_TYPE: u8 = 15;
const H266NALU_PPS_NALU_TYPE: u8 = 16;
const H266NALU_AUD_NALU_TYPE: u8 = 20;
const H266NALU_FILLER_NALU_TYPE: u8 = 25;
const H266NALU_AGGREGATION_PACKET_TYPE: u8 = 28;
const H266NALU_FRAGMENTATION_UNIT_TYPE: u8 = 29;
const MAX_PACKET_SIZE: usize = 1200;
const MIN_FU_PAYLOAD: usize = 1;
const MIN_MTU: usize = H266NALU_HEADER_SIZE + H266FRAGMENTATION_UNIT_HEADER_SIZE + MIN_FU_PAYLOAD;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct H266CodecExtra {
pub is_keyframe: bool,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub struct H266NALUHeader(pub u16);
impl H266NALUHeader {
pub fn new(high_byte: u8, low_byte: u8) -> Self {
H266NALUHeader(((high_byte as u16) << 8) | low_byte as u16)
}
pub fn new_with_type(typ: u8, layer_id: u8, tid: u8) -> Self {
let b0 = layer_id & 0b0011_1111;
let b1 = ((typ & 0b1_1111) << 3) | (tid & 0b111);
Self::new(b0, b1)
}
pub fn f(&self) -> bool {
(self.0 >> 15) & 0b1 != 0
}
pub fn z(&self) -> bool {
(self.0 >> 14) & 0b1 != 0
}
pub fn layer_id(&self) -> u8 {
((self.0 >> 8) & 0b0011_1111) as u8
}
pub fn nalu_type(&self) -> u8 {
((self.0 >> 3) & 0b1_1111) as u8
}
pub fn tid(&self) -> u8 {
(self.0 & 0b111) as u8
}
pub fn is_irap(&self) -> bool {
matches!(
self.nalu_type(),
H266NALU_IDR_W_RADL | H266NALU_IDR_N_LP | H266NALU_CRA
)
}
pub fn is_aggregation_packet(&self) -> bool {
self.nalu_type() == H266NALU_AGGREGATION_PACKET_TYPE
}
pub fn is_fragmentation_unit(&self) -> bool {
self.nalu_type() == H266NALU_FRAGMENTATION_UNIT_TYPE
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub struct H266FragmentationUnitHeader(pub u8);
impl H266FragmentationUnitHeader {
pub fn new(start: bool, end: bool, last_of_picture: bool, fu_type: u8) -> Self {
let mut b = fu_type & 0b1_1111;
if start {
b |= 0b1000_0000;
}
if end {
b |= 0b0100_0000;
}
if last_of_picture {
b |= 0b0010_0000;
}
H266FragmentationUnitHeader(b)
}
pub fn s(&self) -> bool {
self.0 & 0b1000_0000 != 0
}
pub fn e(&self) -> bool {
self.0 & 0b0100_0000 != 0
}
#[allow(dead_code)] pub fn p(&self) -> bool {
self.0 & 0b0010_0000 != 0
}
pub fn fu_type(&self) -> u8 {
self.0 & 0b1_1111
}
}
pub fn detect_h266_keyframe(payload: &[u8]) -> bool {
if payload.len() < H266NALU_HEADER_SIZE {
return false;
}
let header = H266NALUHeader::new(payload[0], payload[1]);
match header.nalu_type() {
H266NALU_AGGREGATION_PACKET_TYPE => {
let mut offset = H266NALU_HEADER_SIZE;
while offset + 2 <= payload.len() {
let nalu_size = ((payload[offset] as usize) << 8) | payload[offset + 1] as usize;
offset += 2;
if offset + nalu_size > payload.len() || nalu_size < H266NALU_HEADER_SIZE {
break;
}
let inner = H266NALUHeader::new(payload[offset], payload[offset + 1]);
if inner.is_irap() {
return true;
}
offset += nalu_size;
}
false
}
H266NALU_FRAGMENTATION_UNIT_TYPE => {
if payload.len() < H266NALU_HEADER_SIZE + 1 {
return false;
}
let fu = H266FragmentationUnitHeader(payload[2]);
let t = fu.fu_type();
fu.s() && (H266NALU_IDR_W_RADL..=H266NALU_CRA).contains(&t)
}
_ => header.is_irap(),
}
}
#[derive(Debug)]
pub struct H266Packetizer {
vps_nalu: Option<Vec<u8>>,
sps_nalu: Option<Vec<u8>>,
pps_nalu: Option<Vec<u8>>,
donl: Option<u16>,
pkt_buf: Vec<u8>,
}
impl Default for H266Packetizer {
fn default() -> Self {
H266Packetizer {
vps_nalu: None,
sps_nalu: None,
pps_nalu: None,
donl: None,
pkt_buf: Vec::with_capacity(MAX_PACKET_SIZE),
}
}
}
impl H266Packetizer {
pub fn with_donl(&mut self, value: bool) {
self.donl = if value { Some(0) } else { None };
}
fn increment_donl(&mut self) {
if let Some(ref mut donl) = self.donl {
*donl = donl.wrapping_add(1);
}
}
fn increment_donl_by(&mut self, n: u16) {
if let Some(ref mut donl) = self.donl {
*donl = donl.wrapping_add(n);
}
}
fn build_ap_packet(
nal_units: &[&[u8]],
donl: Option<u16>,
buf: &mut Vec<u8>,
mtu: usize,
) -> bool {
let mut f_bit = false;
let mut min_layer = 0b0011_1111u8;
let mut min_tid = 0b111u8;
for n in nal_units {
let h = H266NALUHeader::new(n[0], n[1]);
f_bit |= h.f();
min_layer = min_layer.min(h.layer_id());
min_tid = min_tid.min(h.tid());
}
let tid = min_tid.max(1);
if nal_units.iter().any(|n| n.len() > u16::MAX as usize) {
return false;
}
let donl_overhead = if donl.is_some() { 2 } else { 0 };
let total: usize = H266NALU_HEADER_SIZE
+ donl_overhead
+ nal_units.iter().map(|n| 2 + n.len()).sum::<usize>();
if total > mtu {
return false;
}
buf.clear();
let ap_hdr =
H266NALUHeader::new_with_type(H266NALU_AGGREGATION_PACKET_TYPE, min_layer, tid);
let [b0, b1] = ap_hdr.0.to_be_bytes();
buf.push(b0 | ((f_bit as u8) << 7)); buf.push(b1);
if let Some(donl_value) = donl {
buf.extend_from_slice(&donl_value.to_be_bytes());
}
for n in nal_units {
buf.extend_from_slice(&(n.len() as u16).to_be_bytes());
buf.extend_from_slice(n);
}
true
}
fn next_start_code(payload: &[u8], start: usize) -> (isize, isize) {
let mut i = start;
while i + 3 <= payload.len() {
if payload[i] == 0 && payload[i + 1] == 0 {
if payload[i + 2] == 1 {
return (i as isize, 3);
}
if i + 4 <= payload.len() && payload[i + 2] == 0 && payload[i + 3] == 1 {
return (i as isize, 4);
}
}
i += 1;
}
(-1, -1)
}
fn emit_nalu(&mut self, nalu: &[u8], mtu: usize, out: &mut Vec<Vec<u8>>) {
if mtu == 0 || nalu.len() < H266NALU_HEADER_SIZE {
return;
}
let original_hdr = H266NALUHeader::new(nalu[0], nalu[1]);
let original_type = original_hdr.nalu_type();
if original_type == H266NALU_AUD_NALU_TYPE || original_type == H266NALU_FILLER_NALU_TYPE {
return;
}
if original_type == H266NALU_AGGREGATION_PACKET_TYPE
|| original_type == H266NALU_FRAGMENTATION_UNIT_TYPE
{
warn!(
"H266-PKT skipping reserved NAL type {} in input bitstream",
original_type
);
return;
}
match original_type {
H266NALU_VPS_NALU_TYPE => {
self.vps_nalu = Some(nalu.to_vec());
return;
}
H266NALU_SPS_NALU_TYPE => {
self.sps_nalu = Some(nalu.to_vec());
return;
}
H266NALU_PPS_NALU_TYPE => {
self.pps_nalu = Some(nalu.to_vec());
return;
}
_ => {}
}
if self.sps_nalu.is_some() && self.pps_nalu.is_some() {
let mut nal_units_arr: [&[u8]; 3] = [&[], &[], &[]];
let mut count = 0;
if let Some(vps) = &self.vps_nalu {
nal_units_arr[count] = vps;
count += 1;
}
if let Some(sps) = &self.sps_nalu {
nal_units_arr[count] = sps;
count += 1;
}
if let Some(pps) = &self.pps_nalu {
nal_units_arr[count] = pps;
count += 1;
}
let nal_units = &nal_units_arr[..count];
if count >= 2 && Self::build_ap_packet(nal_units, self.donl, &mut self.pkt_buf, mtu) {
out.push(self.pkt_buf.clone());
self.increment_donl_by(count as u16);
} else {
let fallback_donl_overhead = if self.donl.is_some() { 2 } else { 0 };
for nal_unit in nal_units {
if nal_unit.len() + fallback_donl_overhead <= mtu {
if let Some(ref mut donl_value) = self.donl {
self.pkt_buf.clear();
self.pkt_buf
.extend_from_slice(&nal_unit[..H266NALU_HEADER_SIZE]);
self.pkt_buf.extend_from_slice(&donl_value.to_be_bytes());
self.pkt_buf
.extend_from_slice(&nal_unit[H266NALU_HEADER_SIZE..]);
out.push(self.pkt_buf.clone());
*donl_value = donl_value.wrapping_add(1);
} else {
out.push(nal_unit.to_vec());
}
}
}
}
self.vps_nalu = None;
self.sps_nalu = None;
self.pps_nalu = None;
}
let donl_overhead = if self.donl.is_some() { 2 } else { 0 };
if nalu.len() + donl_overhead <= mtu {
if let Some(donl_value) = self.donl {
self.pkt_buf.clear();
self.pkt_buf
.extend_from_slice(&nalu[..H266NALU_HEADER_SIZE]);
self.pkt_buf.extend_from_slice(&donl_value.to_be_bytes());
self.pkt_buf
.extend_from_slice(&nalu[H266NALU_HEADER_SIZE..]);
out.push(self.pkt_buf.clone());
self.increment_donl();
} else {
out.push(nalu.to_vec());
}
return;
}
const FU_OVERHEAD: usize = H266NALU_HEADER_SIZE + H266FRAGMENTATION_UNIT_HEADER_SIZE;
if nalu.len() <= H266NALU_HEADER_SIZE {
return;
}
let fu_b0 = nalu[0];
let fu_b1 = (H266NALU_FRAGMENTATION_UNIT_TYPE << 3) | (nalu[1] & 0b111);
let payload = &nalu[H266NALU_HEADER_SIZE..];
let effective_mtu = mtu.min(MAX_PACKET_SIZE);
if effective_mtu <= FU_OVERHEAD + donl_overhead {
return;
}
let first_max = effective_mtu - FU_OVERHEAD - donl_overhead;
let max_fragment = effective_mtu - FU_OVERHEAD;
let donl_bytes = self.donl.map(u16::to_be_bytes);
let mut offset = 0;
while offset < payload.len() {
let first = offset == 0;
let remaining = payload.len() - offset;
let budget = if first { first_max } else { max_fragment };
let take = remaining.min(budget);
let end = offset + take == payload.len();
let fu_hdr = H266FragmentationUnitHeader::new(first, end, false, original_type);
self.pkt_buf.clear();
self.pkt_buf.push(fu_b0);
self.pkt_buf.push(fu_b1);
self.pkt_buf.push(fu_hdr.0);
if first {
if let Some(ref b) = donl_bytes {
self.pkt_buf.extend_from_slice(b);
}
}
self.pkt_buf
.extend_from_slice(&payload[offset..offset + take]);
out.push(self.pkt_buf.clone());
offset += take;
}
self.increment_donl();
}
}
impl Packetizer for H266Packetizer {
fn packetize(&mut self, mtu: usize, payload: &[u8]) -> Result<Vec<Vec<u8>>, PacketError> {
if payload.is_empty() {
return Ok(vec![]);
}
let mtu = match mtu {
0 => {
warn!("MTU is 0, cannot packetize H.266");
return Ok(vec![]);
}
mtu if mtu > MAX_PACKET_SIZE => {
warn!(
"MTU {} exceeds MAX_PACKET_SIZE {}, clamping",
mtu, MAX_PACKET_SIZE
);
MAX_PACKET_SIZE
}
mtu if mtu < MIN_MTU => {
warn!("MTU {} too small for H.266 fragmentation", mtu);
return Ok(vec![]);
}
mtu => mtu,
};
let estimated_packets = payload
.len()
.checked_div(mtu.saturating_sub(3))
.unwrap_or(1)
.saturating_add(4);
let mut packets = Vec::with_capacity(estimated_packets);
let (mut next_start, mut next_len) = Self::next_start_code(payload, 0);
if next_start == -1 {
self.emit_nalu(payload, mtu, &mut packets);
return Ok(packets);
}
while next_start != -1 {
let nalu_start = (next_start + next_len) as usize;
let (s2, l2) = Self::next_start_code(payload, nalu_start);
next_start = s2;
next_len = l2;
if next_start != -1 {
self.emit_nalu(&payload[nalu_start..next_start as usize], mtu, &mut packets);
} else {
self.emit_nalu(&payload[nalu_start..], mtu, &mut packets);
}
}
Ok(packets)
}
fn is_marker(&mut self, _data: &[u8], _previous: Option<&[u8]>, last: bool) -> bool {
last
}
}
#[derive(Debug, Default)]
pub struct H266Depacketizer {
fu_buffer: Option<Vec<u8>>,
might_need_donl: bool,
}
impl H266Depacketizer {
pub fn with_donl(&mut self, value: bool) {
self.might_need_donl = value;
}
}
impl Depacketizer for H266Depacketizer {
fn out_size_hint(&self, packets_size: usize) -> Option<usize> {
let estimated_packets = (packets_size / 1200).saturating_add(1);
Some(packets_size.saturating_add(4usize.saturating_mul(estimated_packets)))
}
fn depacketize(
&mut self,
packet: &[u8],
out: &mut Vec<u8>,
codec_extra: &mut CodecExtra,
) -> Result<(), PacketError> {
if packet.len() <= H266NALU_HEADER_SIZE {
return Err(PacketError::ErrShortPacket);
}
let header = H266NALUHeader::new(packet[0], packet[1]);
if header.f() {
return Err(PacketError::ErrH266CorruptedPacket);
}
if header.z() {
warn!("H266-DEPKT NAL with reserved Z bit set (ignored)");
}
let mark_keyframe = |codec_extra: &mut CodecExtra, irap: bool| {
let is_keyframe = if let CodecExtra::H266(e) = codec_extra {
irap | e.is_keyframe
} else {
irap
};
*codec_extra = CodecExtra::H266(H266CodecExtra { is_keyframe });
};
if header.is_fragmentation_unit() {
if packet.len() < H266NALU_HEADER_SIZE + 1 {
return Err(PacketError::ErrShortPacket);
}
let fu_header = H266FragmentationUnitHeader(packet[2]);
let payload_start = if fu_header.s() && self.might_need_donl {
if packet.len() < H266NALU_HEADER_SIZE + 1 + 2 {
return Err(PacketError::ErrShortPacket);
}
H266NALU_HEADER_SIZE + 1 + 2
} else {
H266NALU_HEADER_SIZE + 1
};
let fu_payload = &packet[payload_start..];
if fu_header.s() {
match &mut self.fu_buffer {
Some(buf) => buf.clear(),
None => self.fu_buffer = Some(Vec::with_capacity(128 * 1024)),
}
}
if let Some(ref mut buf) = self.fu_buffer {
buf.extend_from_slice(fu_payload);
}
if fu_header.e() {
if let Some(ref buf) = self.fu_buffer {
let orig_type = fu_header.fu_type();
let orig_b0 = packet[0];
let orig_b1 = (orig_type << 3) | (packet[1] & 0b111);
let irap = matches!(
orig_type,
H266NALU_IDR_W_RADL | H266NALU_IDR_N_LP | H266NALU_CRA
);
mark_keyframe(codec_extra, irap);
out.extend_from_slice(ANNEXB_NALUSTART_CODE);
out.push(orig_b0);
out.push(orig_b1);
out.extend_from_slice(buf);
}
}
Ok(())
} else if header.is_aggregation_packet() {
let mut offset = H266NALU_HEADER_SIZE;
let mut unit_count = 0;
if self.might_need_donl {
if packet.len() < offset + 2 {
return Err(PacketError::ErrShortPacket);
}
offset += 2;
}
let out_start = out.len();
while offset < packet.len() {
if offset + 2 > packet.len() {
out.truncate(out_start);
return Err(PacketError::ErrShortPacket);
}
let nalu_size = ((packet[offset] as usize) << 8) | (packet[offset + 1] as usize);
offset += 2;
if offset + nalu_size > packet.len() || nalu_size < H266NALU_HEADER_SIZE {
out.truncate(out_start);
return Err(PacketError::ErrShortPacket);
}
let nalu = &packet[offset..offset + nalu_size];
offset += nalu_size;
unit_count += 1;
let inner = H266NALUHeader::new(nalu[0], nalu[1]);
if inner.is_aggregation_packet() || inner.is_fragmentation_unit() {
out.truncate(out_start);
return Err(PacketError::ErrH266CorruptedPacket);
}
mark_keyframe(codec_extra, inner.is_irap());
out.extend_from_slice(ANNEXB_NALUSTART_CODE);
out.extend_from_slice(nalu);
}
if unit_count < 2 {
return Err(PacketError::ErrShortPacket);
}
Ok(())
} else {
mark_keyframe(codec_extra, header.is_irap());
out.extend_from_slice(ANNEXB_NALUSTART_CODE);
if self.might_need_donl {
if packet.len() < H266NALU_HEADER_SIZE + 2 + 1 {
return Err(PacketError::ErrShortPacket);
}
out.extend_from_slice(&packet[..H266NALU_HEADER_SIZE]);
out.extend_from_slice(&packet[H266NALU_HEADER_SIZE + 2..]);
} else {
out.extend_from_slice(packet);
}
Ok(())
}
}
fn is_partition_head(&self, payload: &[u8]) -> bool {
if payload.len() < H266NALU_HEADER_SIZE {
return false;
}
let header = H266NALUHeader::new(payload[0], payload[1]);
if header.f() {
return true;
}
if !header.is_fragmentation_unit() {
return true;
}
if payload.len() < H266NALU_HEADER_SIZE + 1 {
return false;
}
H266FragmentationUnitHeader(payload[2]).s()
}
fn is_partition_tail(&self, marker: bool, payload: &[u8]) -> bool {
if payload.len() < H266NALU_HEADER_SIZE {
return false;
}
let header = H266NALUHeader::new(payload[0], payload[1]);
if header.is_fragmentation_unit() {
if payload.len() < H266NALU_HEADER_SIZE + 1 {
return false;
}
return H266FragmentationUnitHeader(payload[2]).e();
}
marker
}
}
#[cfg(test)]
mod test {
use super::*;
type Result<T> = std::result::Result<T, PacketError>;
fn hdr(typ: u8, layer: u8, tid: u8) -> [u8; 2] {
let h = H266NALUHeader::new_with_type(typ, layer, tid);
h.0.to_be_bytes()
}
fn annexb(nals: &[&[u8]]) -> Vec<u8> {
let mut out = Vec::new();
for n in nals {
out.extend_from_slice(&[0, 0, 0, 1]);
out.extend_from_slice(n);
}
out
}
fn make_nal(typ: u8, len: usize) -> Vec<u8> {
assert!(len >= 2);
let mut n = hdr(typ, 0, 1).to_vec();
for i in 0..len - 2 {
n.push((i % 251) as u8);
}
n
}
fn make_nal_lt(typ: u8, len: usize, layer: u8, tid: u8) -> Vec<u8> {
assert!(len >= 2);
let mut n = hdr(typ, layer, tid).to_vec();
for i in 0..len - 2 {
n.push((i % 251) as u8);
}
n
}
fn reconstruct_from_fu_packets(packets: &[Vec<u8>]) -> Vec<u8> {
let mut out = Vec::new();
let mut started = false;
for pkt in packets {
assert!(pkt.len() >= 3);
let h = H266NALUHeader::new(pkt[0], pkt[1]);
assert!(h.is_fragmentation_unit());
let fu = H266FragmentationUnitHeader(pkt[2]);
if fu.s() {
out.push(pkt[0]);
out.push((fu.fu_type() << 3) | (pkt[1] & 0b111));
started = true;
}
assert!(started, "FU sequence must start with S=1");
out.extend_from_slice(&pkt[3..]);
}
out
}
fn depacketize_all(packets: &[Vec<u8>]) -> Result<(Vec<u8>, bool)> {
let mut depack = H266Depacketizer::default();
let mut out = Vec::new();
let mut keyframe = false;
for p in packets {
let mut extra = CodecExtra::None;
depack.depacketize(p, &mut out, &mut extra)?;
if let CodecExtra::H266(e) = extra {
keyframe |= e.is_keyframe;
}
}
Ok((out, keyframe))
}
mod header_tests {
use super::*;
#[test]
fn test_h266_nalu_header() -> Result<()> {
#[derive(Default)]
struct TestType {
raw_header: &'static [u8],
fbit: bool,
typ: u8,
layer_id: u8,
tid: u8,
is_ap: bool,
is_fu: bool,
}
let tests = vec![
TestType {
raw_header: &[0x80, 0x00],
typ: 0,
layer_id: 0,
tid: 0,
fbit: true,
..Default::default()
},
TestType {
raw_header: &[0x00, 0x01],
typ: 0,
layer_id: 0,
tid: 1,
..Default::default()
},
TestType {
raw_header: &[0x00, 0x71],
typ: 14,
layer_id: 0,
tid: 1,
..Default::default()
},
TestType {
raw_header: &[0x00, 0x79],
typ: 15,
layer_id: 0,
tid: 1,
..Default::default()
},
TestType {
raw_header: &[0x00, 0x81],
typ: 16,
layer_id: 0,
tid: 1,
..Default::default()
},
TestType {
raw_header: &[0x00, 0xB9],
typ: 23,
layer_id: 0,
tid: 1,
..Default::default()
},
TestType {
raw_header: &[0x00, 0xE9],
typ: H266NALU_FRAGMENTATION_UNIT_TYPE,
layer_id: 0,
tid: 1,
is_fu: true,
..Default::default()
},
TestType {
raw_header: &[0x00, 0xE1],
typ: H266NALU_AGGREGATION_PACKET_TYPE,
layer_id: 0,
tid: 1,
is_ap: true,
..Default::default()
},
TestType {
raw_header: &[0x05, 0x39],
typ: 7,
layer_id: 5,
tid: 1,
..Default::default()
},
TestType {
raw_header: &[0x00, 0x0B],
typ: 1,
layer_id: 0,
tid: 3,
..Default::default()
},
];
for (i, cur) in tests.iter().enumerate() {
let header = H266NALUHeader::new(cur.raw_header[0], cur.raw_header[1]);
assert_eq!(header.f(), cur.fbit, "tc {i}: f bit");
assert_eq!(header.nalu_type(), cur.typ, "tc {i}: type");
assert_eq!(header.layer_id(), cur.layer_id, "tc {i}: layer_id");
assert_eq!(header.tid(), cur.tid, "tc {i}: tid");
assert_eq!(header.is_aggregation_packet(), cur.is_ap, "tc {i}: is_ap");
assert_eq!(header.is_fragmentation_unit(), cur.is_fu, "tc {i}: is_fu");
}
Ok(())
}
#[test]
fn test_h266_irap_detection() -> Result<()> {
for typ in 0u8..=31 {
let header = H266NALUHeader::new_with_type(typ, 0, 1);
let expected = (7..=9).contains(&typ);
assert_eq!(
header.is_irap(),
expected,
"type {typ} irap should be {expected}"
);
}
Ok(())
}
#[test]
fn test_h266_fu_header() -> Result<()> {
struct TestType {
value: u8,
s: bool,
e: bool,
p: bool,
typ: u8,
}
let tests = vec![
TestType {
value: 0x87,
s: true,
e: false,
p: false,
typ: 7,
},
TestType {
value: 0x47,
s: false,
e: true,
p: false,
typ: 7,
},
TestType {
value: 0x27,
s: false,
e: false,
p: true,
typ: 7,
},
TestType {
value: 0x60,
s: false,
e: true,
p: true,
typ: 0,
},
TestType {
value: 0x9F,
s: true,
e: false,
p: false,
typ: 31,
},
TestType {
value: 0x09,
s: false,
e: false,
p: false,
typ: 9,
},
];
{
let mut depack = H266Depacketizer::default();
let mut out = Vec::new();
let mut extra = CodecExtra::None;
let res = depack.depacketize(&[0x80, 0x01, 0x93, 0xAF, 0xAF], &mut out, &mut extra);
assert!(matches!(res, Err(PacketError::ErrH266CorruptedPacket)));
}
for (i, t) in tests.iter().enumerate() {
let fu = H266FragmentationUnitHeader(t.value);
assert_eq!(fu.s(), t.s, "tc {i}: s");
assert_eq!(fu.e(), t.e, "tc {i}: e");
assert_eq!(fu.p(), t.p, "tc {i}: p");
assert_eq!(fu.fu_type(), t.typ, "tc {i}: type");
let built = H266FragmentationUnitHeader::new(t.s, t.e, t.p, t.typ);
assert_eq!(built.0, t.value, "tc {i}: build");
}
Ok(())
}
}
mod depacketizer_tests {
use super::*;
#[test]
fn test_h266_single_nalunit_packet() -> Result<()> {
let nal = make_nal(0, 5);
let (out, keyframe) = depacketize_all(&[nal.clone()])?;
assert_eq!(&out[..4], &[0, 0, 0, 1]);
assert_eq!(&out[4..], &nal[..]);
assert!(!keyframe);
let idr = make_nal(H266NALU_IDR_W_RADL, 5);
let (out, keyframe) = depacketize_all(&[idr.clone()])?;
assert_eq!(&out[4..], &idr[..]);
assert!(keyframe);
let mut depack = H266Depacketizer::default();
let mut out = Vec::new();
let mut extra = CodecExtra::None;
let err = depack.depacketize(&hdr(0, 0, 1), &mut out, &mut extra);
assert_eq!(err, Err(PacketError::ErrShortPacket));
let err = depack.depacketize(&[], &mut out, &mut extra);
assert_eq!(err, Err(PacketError::ErrShortPacket));
let err = depack.depacketize(&[0x80, 0x01, 0xAA, 0xBB], &mut out, &mut extra);
assert!(matches!(err, Err(PacketError::ErrH266CorruptedPacket)));
Ok(())
}
#[test]
fn test_h266_aggregation_packet() -> Result<()> {
let sps = make_nal(H266NALU_SPS_NALU_TYPE, 6);
let pps = make_nal(H266NALU_PPS_NALU_TYPE, 4);
let mut ap = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
ap.extend_from_slice(&(sps.len() as u16).to_be_bytes());
ap.extend_from_slice(&sps);
ap.extend_from_slice(&(pps.len() as u16).to_be_bytes());
ap.extend_from_slice(&pps);
let (out, keyframe) = depacketize_all(&[ap])?;
let expected = annexb(&[&sps, &pps]);
assert_eq!(out, expected);
assert!(!keyframe);
let mut ap1 = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
ap1.extend_from_slice(&(sps.len() as u16).to_be_bytes());
ap1.extend_from_slice(&sps);
let mut depack = H266Depacketizer::default();
let mut out = Vec::new();
let mut extra = CodecExtra::None;
let err = depack.depacketize(&ap1, &mut out, &mut extra);
assert_eq!(err, Err(PacketError::ErrShortPacket));
let mut bad = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
bad.extend_from_slice(&[0x00, 0x20]); let err = depack.depacketize(&bad, &mut out, &mut extra);
assert_eq!(err, Err(PacketError::ErrShortPacket));
let idr = make_nal(H266NALU_IDR_N_LP, 6);
let mut ap_idr = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
ap_idr.extend_from_slice(&(sps.len() as u16).to_be_bytes());
ap_idr.extend_from_slice(&sps);
ap_idr.extend_from_slice(&(idr.len() as u16).to_be_bytes());
ap_idr.extend_from_slice(&idr);
let (_, keyframe) = depacketize_all(&[ap_idr])?;
assert!(keyframe);
Ok(())
}
#[test]
fn test_h266_fragmentation_unit_packet() -> Result<()> {
let orig = make_nal(H266NALU_CRA, 8);
let fu_hdr_bytes = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1);
let payload = &orig[2..];
let (a, b) = payload.split_at(payload.len() / 2);
let mut fu1 = fu_hdr_bytes.to_vec();
fu1.push(H266FragmentationUnitHeader::new(true, false, false, H266NALU_CRA).0);
fu1.extend_from_slice(a);
let mut fu2 = fu_hdr_bytes.to_vec();
fu2.push(H266FragmentationUnitHeader::new(false, true, false, H266NALU_CRA).0);
fu2.extend_from_slice(b);
let (out, keyframe) = depacketize_all(&[fu1, fu2])?;
assert_eq!(out, annexb(&[&orig]));
assert!(keyframe, "CRA via FU must flag keyframe");
let mut depack = H266Depacketizer::default();
let mut out = Vec::new();
let mut extra = CodecExtra::None;
let err = depack.depacketize(&fu_hdr_bytes, &mut out, &mut extra);
assert_eq!(err, Err(PacketError::ErrShortPacket));
let mut orphan = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1).to_vec();
orphan.push(H266FragmentationUnitHeader::new(false, true, false, 0).0);
orphan.extend_from_slice(&[0xAA, 0xBB]);
let mut depack = H266Depacketizer::default();
let mut out = Vec::new();
depack.depacketize(&orphan, &mut out, &mut extra)?;
assert!(out.is_empty(), "orphan FU must not produce output");
Ok(())
}
#[test]
fn test_h266_packet() -> Result<()> {
struct TestType {
raw: &'static [u8],
expect_err: bool, }
let tests = vec![
TestType {
raw: &[],
expect_err: true,
},
TestType {
raw: &[0x00, 0x01],
expect_err: true,
},
TestType {
raw: &[0x00, 0x01, 0xAB, 0xCD, 0xEF],
expect_err: false,
},
TestType {
raw: &[0x00, 0xE9],
expect_err: true,
},
TestType {
raw: &[0x00, 0xE9, 0x87, 0x11, 0x22],
expect_err: false,
},
TestType {
raw: &[0x00, 0xE1, 0x00, 0x03, 0x00, 0x01, 0xAA],
expect_err: true,
},
TestType {
raw: &[
0x00, 0xE1, 0x00, 0x03, 0x00, 0x01, 0xAA, 0x00, 0x03, 0x00, 0x01, 0xBB, ],
expect_err: false,
},
];
for (i, t) in tests.iter().enumerate() {
let mut depack = H266Depacketizer::default();
let mut out = Vec::new();
let mut extra = CodecExtra::None;
let res = depack.depacketize(t.raw, &mut out, &mut extra);
if t.expect_err {
assert!(
matches!(res, Err(PacketError::ErrShortPacket)),
"tc {i} should error, got {res:?}"
);
} else {
assert!(res.is_ok(), "tc {i} should succeed: {res:?}");
}
}
Ok(())
}
}
mod packetizer_tests {
use super::*;
#[test]
fn test_h266_packetizer_single_nalu() -> Result<()> {
let mut pck = H266Packetizer::default();
let nal = make_nal(0, 10);
let packets = pck.packetize(1200, &nal)?;
assert_eq!(packets.len(), 1);
assert_eq!(packets[0], nal);
Ok(())
}
#[test]
fn test_h266_packetizer_annexb_split() -> Result<()> {
let n1 = make_nal(0, 6);
let n2 = make_nal(1, 7);
let mut payload = Vec::new();
payload.extend_from_slice(&[0, 0, 0, 1]);
payload.extend_from_slice(&n1);
payload.extend_from_slice(&[0, 0, 1]);
payload.extend_from_slice(&n2);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(1200, &payload)?;
assert_eq!(packets.len(), 2);
assert_eq!(packets[0], n1);
assert_eq!(packets[1], n2);
Ok(())
}
#[test]
fn test_h266_packetizer_single_nalu_no_fragment() -> Result<()> {
let mut pck = H266Packetizer::default();
let nal = make_nal(0, 100);
let packets = pck.packetize(100, &nal)?;
assert_eq!(packets.len(), 1);
assert_eq!(packets[0], nal);
Ok(())
}
#[test]
fn test_h266_packetizer_fu_fragmentation_roundtrip_payload() -> Result<()> {
let mut pck = H266Packetizer::default();
let nal = make_nal(H266NALU_CRA, 3000);
let mtu = 200;
let packets = pck.packetize(mtu, &nal)?;
assert!(packets.len() > 1);
for p in &packets {
assert!(p.len() <= mtu);
let h = H266NALUHeader::new(p[0], p[1]);
assert!(h.is_fragmentation_unit());
}
let rebuilt = reconstruct_from_fu_packets(&packets);
assert_eq!(rebuilt, nal);
Ok(())
}
#[test]
fn test_h266_packetizer_emits_ap_for_vps_sps_pps() -> Result<()> {
let vps = make_nal(H266NALU_VPS_NALU_TYPE, 8);
let sps = make_nal(H266NALU_SPS_NALU_TYPE, 12);
let pps = make_nal(H266NALU_PPS_NALU_TYPE, 6);
let idr = make_nal(H266NALU_IDR_W_RADL, 20);
let payload = annexb(&[&vps, &sps, &pps, &idr]);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(1200, &payload)?;
assert_eq!(packets.len(), 2, "AP + IDR single expected");
let ap = &packets[0];
let h = H266NALUHeader::new(ap[0], ap[1]);
assert!(h.is_aggregation_packet());
let mut units = Vec::new();
let mut off = 2;
while off + 2 <= ap.len() {
let sz = u16::from_be_bytes([ap[off], ap[off + 1]]) as usize;
off += 2;
units.push(ap[off..off + sz].to_vec());
off += sz;
}
assert_eq!(units.len(), 3);
assert_eq!(units[0], vps);
assert_eq!(units[1], sps);
assert_eq!(units[2], pps);
assert_eq!(packets[1], idr);
Ok(())
}
#[test]
fn test_h266_packetizer_ap_exceeds_mtu_fallback() -> Result<()> {
let mtu = 700;
let sps = make_nal(H266NALU_SPS_NALU_TYPE, 600);
let pps = make_nal(H266NALU_PPS_NALU_TYPE, 600);
let idr = make_nal(H266NALU_IDR_W_RADL, 50);
let payload = annexb(&[&sps, &pps, &idr]);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(mtu, &payload)?;
assert_eq!(packets.len(), 3);
assert_eq!(packets[0], sps);
assert_eq!(packets[1], pps);
assert_eq!(packets[2], idr);
Ok(())
}
#[test]
fn test_h266_packetizer_drops_aud_and_filler() -> Result<()> {
let aud = make_nal(H266NALU_AUD_NALU_TYPE, 4);
let fd = make_nal(H266NALU_FILLER_NALU_TYPE, 10);
let trail = make_nal(0, 8);
let payload = annexb(&[&aud, &trail, &fd]);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(1200, &payload)?;
assert_eq!(packets.len(), 1);
assert_eq!(packets[0], trail);
Ok(())
}
#[test]
fn test_h266_packetizer_marker() -> Result<()> {
let mut pck = H266Packetizer::default();
assert!(pck.is_marker(&[], None, true));
assert!(!pck.is_marker(&[], None, false));
Ok(())
}
#[test]
fn test_h266_fragmentation_start_end_flags() -> Result<()> {
let mut pck = H266Packetizer::default();
let nal = make_nal(0, 1000);
let packets = pck.packetize(100, &nal)?;
assert!(packets.len() >= 3);
for (i, p) in packets.iter().enumerate() {
let fu = H266FragmentationUnitHeader(p[2]);
let is_first = i == 0;
let is_last = i == packets.len() - 1;
assert_eq!(fu.s(), is_first, "pkt {i} S");
assert_eq!(fu.e(), is_last, "pkt {i} E");
assert!(!fu.p(), "pkt {i} P never set");
assert_eq!(fu.fu_type(), 0, "pkt {i} type");
}
Ok(())
}
#[test]
fn test_h266_packetizer_exact_fu_boundary_mtu() -> Result<()> {
let mtu = 100;
let mut pck = H266Packetizer::default();
let nal = make_nal(0, mtu);
let packets = pck.packetize(mtu, &nal)?;
assert_eq!(packets.len(), 1);
let mut pck = H266Packetizer::default();
let nal = make_nal(0, mtu + 1);
let packets = pck.packetize(mtu, &nal)?;
assert_eq!(packets.len(), 2);
assert_eq!(packets[0].len(), 3 + 97);
assert_eq!(packets[1].len(), 3 + 2);
assert_eq!(reconstruct_from_fu_packets(&packets), nal);
Ok(())
}
#[test]
fn test_h266_mtu_variation() -> Result<()> {
for mtu in [50usize, 128, 512, 1200] {
let mut pck = H266Packetizer::default();
let nal = make_nal(H266NALU_IDR_N_LP, 1000);
let packets = pck.packetize(mtu, &nal)?;
for p in &packets {
assert!(p.len() <= mtu, "mtu {mtu}: packet {} too big", p.len());
}
if nal.len() <= mtu {
assert_eq!(packets.len(), 1);
} else {
assert_eq!(reconstruct_from_fu_packets(&packets), nal, "mtu {mtu}");
}
}
Ok(())
}
#[test]
fn packetize_respects_mtu() -> Result<()> {
let nal = make_nal(0, 2002);
for &mtu in &[100usize, 300, 600, 1200] {
let mut pck = H266Packetizer::default();
let pkts = pck.packetize(mtu, &nal)?;
assert!(!pkts.is_empty(), "H266 produced no packets at mtu {mtu}");
for (i, pkt) in pkts.iter().enumerate() {
assert!(
pkt.len() <= mtu,
"H266 packet {i} size {} > mtu {mtu}",
pkt.len()
);
}
}
Ok(())
}
}
mod roundtrip_tests {
use super::*;
#[test]
fn test_h266_fu_roundtrip_with_depacketizer() -> Result<()> {
let mut pck = H266Packetizer::default();
let nal = make_nal(H266NALU_IDR_W_RADL, 2500);
let packets = pck.packetize(400, &nal)?;
assert!(packets.len() > 1);
let (out, keyframe) = depacketize_all(&packets)?;
assert_eq!(out, annexb(&[&nal]));
assert!(keyframe);
Ok(())
}
#[test]
fn test_h266_ap_roundtrip_with_depacketizer() -> Result<()> {
let vps = make_nal(H266NALU_VPS_NALU_TYPE, 8);
let sps = make_nal(H266NALU_SPS_NALU_TYPE, 12);
let pps = make_nal(H266NALU_PPS_NALU_TYPE, 6);
let idr = make_nal(H266NALU_IDR_N_LP, 40);
let payload = annexb(&[&vps, &sps, &pps, &idr]);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(1200, &payload)?;
let (out, keyframe) = depacketize_all(&packets)?;
assert_eq!(out, annexb(&[&vps, &sps, &pps, &idr]));
assert!(keyframe);
Ok(())
}
#[test]
fn test_h266_single_nalu_roundtrip_with_depacketizer() -> Result<()> {
let mut pck = H266Packetizer::default();
let nal = make_nal(0, 50);
let packets = pck.packetize(1200, &nal)?;
assert_eq!(packets.len(), 1);
let (out, keyframe) = depacketize_all(&packets)?;
assert_eq!(out, annexb(&[&nal]));
assert!(!keyframe);
Ok(())
}
#[test]
fn test_h266_mixed_packet_types_roundtrip() -> Result<()> {
let sps = make_nal(H266NALU_SPS_NALU_TYPE, 10);
let pps = make_nal(H266NALU_PPS_NALU_TYPE, 6);
let small = make_nal(H266NALU_IDR_W_RADL, 100);
let large = make_nal(0, 1500);
let payload = annexb(&[&sps, &pps, &small, &large]);
let mut pck = H266Packetizer::default();
let mtu = 600;
let packets = pck.packetize(mtu, &payload)?;
assert!(packets.len() >= 5, "got {}", packets.len());
for p in &packets {
assert!(p.len() <= mtu);
}
let (out, keyframe) = depacketize_all(&packets)?;
assert_eq!(out, annexb(&[&sps, &pps, &small, &large]));
assert!(keyframe);
Ok(())
}
#[test]
fn test_h266_annexb_roundtrip_with_depacketizer() -> Result<()> {
let n1 = make_nal(1, 30);
let n2 = make_nal(2, 40);
let n3 = make_nal(0, 25);
let mut payload = Vec::new();
payload.extend_from_slice(&[0, 0, 1]); payload.extend_from_slice(&n1);
payload.extend_from_slice(&[0, 0, 0, 1]); payload.extend_from_slice(&n2);
payload.extend_from_slice(&[0, 0, 1]);
payload.extend_from_slice(&n3);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(1200, &payload)?;
assert_eq!(packets.len(), 3);
let (out, _) = depacketize_all(&packets)?;
assert_eq!(out, annexb(&[&n1, &n2, &n3]));
Ok(())
}
}
mod partition_tests {
use super::*;
#[test]
fn test_h266_is_partition_head() -> Result<()> {
let depack = H266Depacketizer::default();
assert!(!depack.is_partition_head(&[0x00]));
assert!(depack.is_partition_head(&make_nal(0, 5)));
assert!(depack.is_partition_head(&hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1)));
let mut fu_s = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1).to_vec();
fu_s.push(H266FragmentationUnitHeader::new(true, false, false, 0).0);
assert!(depack.is_partition_head(&fu_s));
let mut fu_m = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1).to_vec();
fu_m.push(H266FragmentationUnitHeader::new(false, false, false, 0).0);
assert!(!depack.is_partition_head(&fu_m));
assert!(!depack.is_partition_head(&hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1)));
assert!(depack.is_partition_head(&[0x80, 0x01, 0x00]));
Ok(())
}
#[test]
fn test_h266_is_partition_tail() -> Result<()> {
let depack = H266Depacketizer::default();
assert!(!depack.is_partition_tail(true, &[0x00]));
let single = make_nal(0, 5);
assert!(depack.is_partition_tail(true, &single));
assert!(!depack.is_partition_tail(false, &single));
let mut fu_e = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1).to_vec();
fu_e.push(H266FragmentationUnitHeader::new(false, true, false, 0).0);
assert!(depack.is_partition_tail(false, &fu_e));
let mut fu_s = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1).to_vec();
fu_s.push(H266FragmentationUnitHeader::new(true, false, false, 0).0);
assert!(!depack.is_partition_tail(true, &fu_s));
assert!(!depack.is_partition_tail(true, &hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1)));
Ok(())
}
}
mod edge_tests {
use super::*;
#[test]
fn test_h266_zero_mtu() -> Result<()> {
let mut pck = H266Packetizer::default();
let packets = pck.packetize(0, &make_nal(0, 10))?;
assert!(packets.is_empty());
Ok(())
}
#[test]
fn test_h266_empty_nalu() -> Result<()> {
let mut pck = H266Packetizer::default();
let packets = pck.packetize(1200, &[])?;
assert!(packets.is_empty());
Ok(())
}
#[test]
fn test_h266_mtu_smaller_than_fu_overhead() -> Result<()> {
let mut pck = H266Packetizer::default();
let packets = pck.packetize(3, &make_nal(0, 100))?;
assert!(packets.is_empty());
Ok(())
}
#[test]
fn test_h266_fu_minimal_mtu() -> Result<()> {
let mut pck = H266Packetizer::default();
let nal = make_nal(0, 10);
let packets = pck.packetize(MIN_MTU, &nal)?;
assert_eq!(packets.len(), 8);
for p in &packets {
assert_eq!(p.len(), 4);
}
assert_eq!(reconstruct_from_fu_packets(&packets), nal);
Ok(())
}
#[test]
fn test_h266_fu_mtu_exceeds_max_packet_size() -> Result<()> {
let mut pck = H266Packetizer::default();
let nal = make_nal(0, 3000);
let packets = pck.packetize(5000, &nal)?;
assert!(packets.len() > 1, "must fragment despite huge mtu");
for p in &packets {
assert!(p.len() <= MAX_PACKET_SIZE);
}
assert_eq!(reconstruct_from_fu_packets(&packets), nal);
Ok(())
}
#[test]
fn test_h266_nalu_smaller_than_header() -> Result<()> {
let mut pck = H266Packetizer::default();
let mut payload = Vec::new();
payload.extend_from_slice(&[0, 0, 0, 1]);
payload.push(0x00);
let packets = pck.packetize(1200, &payload)?;
assert!(packets.is_empty());
Ok(())
}
#[test]
fn test_h266_aggregated_exact_layout() -> Result<()> {
let sps = make_nal(H266NALU_SPS_NALU_TYPE, 4);
let pps = make_nal(H266NALU_PPS_NALU_TYPE, 3);
let idr = make_nal(H266NALU_IDR_W_RADL, 5);
let payload = annexb(&[&sps, &pps, &idr]);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(1200, &payload)?;
assert_eq!(packets.len(), 2);
let mut expected = vec![0x00, (H266NALU_AGGREGATION_PACKET_TYPE << 3) | 1];
expected.extend_from_slice(&(sps.len() as u16).to_be_bytes());
expected.extend_from_slice(&sps);
expected.extend_from_slice(&(pps.len() as u16).to_be_bytes());
expected.extend_from_slice(&pps);
assert_eq!(packets[0], expected);
Ok(())
}
#[test]
fn test_h266_packet_synthetic_au() -> Result<()> {
let sps = make_nal(H266NALU_SPS_NALU_TYPE, 45);
let pps = make_nal(H266NALU_PPS_NALU_TYPE, 12);
let aps = make_nal(17, 30); let idr = make_nal(H266NALU_IDR_N_LP, 800);
let payload = annexb(&[&sps, &pps, &aps, &idr]);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(1200, &payload)?;
assert_eq!(packets.len(), 3);
let (out, keyframe) = depacketize_all(&packets)?;
assert_eq!(out, annexb(&[&sps, &pps, &aps, &idr]));
assert!(keyframe);
Ok(())
}
}
#[test]
fn test_detect_h266_keyframe() {
assert!(!detect_h266_keyframe(&[]));
assert!(!detect_h266_keyframe(&[0x00]));
for typ in [H266NALU_IDR_W_RADL, H266NALU_IDR_N_LP, H266NALU_CRA] {
let h = H266NALUHeader::new_with_type(typ, 0, 1);
assert!(
detect_h266_keyframe(&h.0.to_be_bytes()),
"type {typ} must be keyframe"
);
}
let gdr = H266NALUHeader::new_with_type(H266NALU_GDR, 0, 1);
assert!(!detect_h266_keyframe(&gdr.0.to_be_bytes()));
let trail = H266NALUHeader::new_with_type(0, 0, 1);
assert!(!detect_h266_keyframe(&trail.0.to_be_bytes()));
let ap = H266NALUHeader::new_with_type(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1);
let idr = H266NALUHeader::new_with_type(H266NALU_IDR_W_RADL, 0, 1);
let mut ap_with_idr = Vec::new();
ap_with_idr.extend_from_slice(&ap.0.to_be_bytes());
ap_with_idr.extend_from_slice(&[0x00, 0x03]); ap_with_idr.extend_from_slice(&idr.0.to_be_bytes());
ap_with_idr.push(0x00);
assert!(detect_h266_keyframe(&ap_with_idr));
let non_irap = H266NALUHeader::new_with_type(1, 0, 1);
let mut ap_no_irap = Vec::new();
ap_no_irap.extend_from_slice(&ap.0.to_be_bytes());
ap_no_irap.extend_from_slice(&[0x00, 0x03]);
ap_no_irap.extend_from_slice(&non_irap.0.to_be_bytes());
ap_no_irap.push(0x00);
assert!(!detect_h266_keyframe(&ap_no_irap));
let fu = H266NALUHeader::new_with_type(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1);
let mut fu_start_idr = Vec::new();
fu_start_idr.extend_from_slice(&fu.0.to_be_bytes());
fu_start_idr.push(0x80 | H266NALU_IDR_W_RADL); fu_start_idr.extend_from_slice(&[0x00, 0x00]);
assert!(detect_h266_keyframe(&fu_start_idr));
let mut fu_cont = Vec::new();
fu_cont.extend_from_slice(&fu.0.to_be_bytes());
fu_cont.push(H266NALU_IDR_W_RADL); fu_cont.extend_from_slice(&[0x00, 0x00]);
assert!(!detect_h266_keyframe(&fu_cont));
assert!(!detect_h266_keyframe(&fu.0.to_be_bytes()));
}
mod donl_tests {
use super::*;
#[test]
fn test_h266_donl_single_nal_round_trip() -> Result<()> {
let mut packetizer = H266Packetizer::default();
packetizer.with_donl(true);
let mut depacketizer = H266Depacketizer::default();
depacketizer.with_donl(true);
let nalu = vec![0x00, 0x09, 0xDE, 0xAD, 0xBE, 0xEF];
let packets = packetizer.packetize(MAX_PACKET_SIZE, &nalu)?;
assert_eq!(packets.len(), 1, "Should produce 1 packet");
let packet = &packets[0];
assert!(packet.len() >= 6);
assert_eq!(packet[0], 0x00);
assert_eq!(packet[1], 0x09);
let donl = u16::from_be_bytes([packet[2], packet[3]]);
assert_eq!(donl, 0, "DONL should be 0 for first NAL");
assert_eq!(packet[4], 0xDE);
assert_eq!(packet[5], 0xAD);
let mut out = Vec::new();
let mut codec_extra = CodecExtra::None;
depacketizer.depacketize(packet, &mut out, &mut codec_extra)?;
assert_eq!(out.len(), 10);
assert_eq!(&out[0..4], ANNEXB_NALUSTART_CODE);
assert_eq!(&out[4..6], &[0x00, 0x09]);
assert_eq!(&out[6..10], &[0xDE, 0xAD, 0xBE, 0xEF]);
Ok(())
}
#[test]
fn test_h266_donl_fragmentation_round_trip() -> Result<()> {
let mut packetizer = H266Packetizer::default();
packetizer.with_donl(true);
let mut depacketizer = H266Depacketizer::default();
depacketizer.with_donl(true);
let mut nalu = vec![0x00, 0x09]; nalu.extend(vec![0xAA; 3000]);
let packets = packetizer.packetize(1200, &nalu)?;
assert!(packets.len() >= 3);
let first_packet = &packets[0];
let fu_header = H266NALUHeader::new(first_packet[0], first_packet[1]);
assert_eq!(fu_header.nalu_type(), H266NALU_FRAGMENTATION_UNIT_TYPE);
let fu_hdr = H266FragmentationUnitHeader(first_packet[2]);
assert!(fu_hdr.s());
assert!(!fu_hdr.e());
let donl = u16::from_be_bytes([first_packet[3], first_packet[4]]);
assert_eq!(donl, 0, "DONL should be 0 for first NAL");
if packets.len() > 2 {
let middle_fu_hdr = H266FragmentationUnitHeader(packets[1][2]);
assert!(!middle_fu_hdr.s());
assert!(!middle_fu_hdr.e());
}
let last_fu_hdr = H266FragmentationUnitHeader(packets[packets.len() - 1][2]);
assert!(!last_fu_hdr.s());
assert!(last_fu_hdr.e());
let mut out = Vec::new();
let mut codec_extra = CodecExtra::None;
for packet in &packets {
depacketizer.depacketize(packet, &mut out, &mut codec_extra)?;
}
assert_eq!(out.len(), 4 + nalu.len());
assert_eq!(&out[0..4], ANNEXB_NALUSTART_CODE);
assert_eq!(&out[4..], &nalu[..]);
Ok(())
}
#[test]
fn test_h266_donl_aggregation_round_trip() -> Result<()> {
let mut packetizer = H266Packetizer::default();
packetizer.with_donl(true);
let mut depacketizer = H266Depacketizer::default();
depacketizer.with_donl(true);
let vps = vec![0x00, 0x71, 0x0C, 0x01, 0xFF, 0xFF]; let sps = vec![0x00, 0x79, 0x01, 0x50, 0x00, 0x00]; let pps = vec![0x00, 0x81, 0xC0, 0xF3, 0xC0, 0x02]; let payload = annexb(&[&vps, &sps, &pps]);
let packets = packetizer.packetize(MAX_PACKET_SIZE, &payload)?;
assert_eq!(packets.len(), 0, "Parameter sets should be cached");
let vcl = vec![0x00, 0x09, 0x11, 0x22, 0x33];
let vcl_packets = packetizer.packetize(MAX_PACKET_SIZE, &annexb(&[&vcl]))?;
assert_eq!(vcl_packets.len(), 2, "Should produce AP + VCL");
let ap_packet = &vcl_packets[0];
let ap_header = H266NALUHeader::new(ap_packet[0], ap_packet[1]);
assert_eq!(ap_header.nalu_type(), H266NALU_AGGREGATION_PACKET_TYPE);
let donl = u16::from_be_bytes([ap_packet[2], ap_packet[3]]);
assert_eq!(donl, 0, "DONL should be 0 for first aggregated unit");
let first_size = u16::from_be_bytes([ap_packet[4], ap_packet[5]]);
assert_eq!(first_size, 6, "VPS size should be 6 bytes");
let first_nal_end = 6 + first_size as usize;
let second_size =
u16::from_be_bytes([ap_packet[first_nal_end], ap_packet[first_nal_end + 1]]);
assert_eq!(second_size, 6, "SPS size should be 6 bytes");
let mut out = Vec::new();
let mut codec_extra = CodecExtra::None;
depacketizer.depacketize(ap_packet, &mut out, &mut codec_extra)?;
assert_eq!(out, annexb(&[&vps, &sps, &pps]));
Ok(())
}
#[test]
fn test_h266_donl_increments_correctly() -> Result<()> {
let mut packetizer = H266Packetizer::default();
packetizer.with_donl(true);
let nalu1 = vec![0x00, 0x09, 0xAA];
let nalu2 = vec![0x00, 0x09, 0xBB];
let nalu3 = vec![0x00, 0x09, 0xCC];
let packets1 = packetizer.packetize(MAX_PACKET_SIZE, &nalu1)?;
let packets2 = packetizer.packetize(MAX_PACKET_SIZE, &nalu2)?;
let packets3 = packetizer.packetize(MAX_PACKET_SIZE, &nalu3)?;
let donl1 = u16::from_be_bytes([packets1[0][2], packets1[0][3]]);
let donl2 = u16::from_be_bytes([packets2[0][2], packets2[0][3]]);
let donl3 = u16::from_be_bytes([packets3[0][2], packets3[0][3]]);
assert_eq!(donl1, 0);
assert_eq!(donl2, 1);
assert_eq!(donl3, 2);
Ok(())
}
#[test]
fn test_h266_without_donl() -> Result<()> {
let mut packetizer = H266Packetizer::default();
let nalu = vec![0x00, 0x09, 0xDE, 0xAD, 0xBE, 0xEF];
let packets = packetizer.packetize(MAX_PACKET_SIZE, &nalu)?;
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].len(), nalu.len(), "No DONL field expected");
assert_eq!(packets[0], nalu);
Ok(())
}
#[test]
fn test_h266_sdp_driven_donl_all_packet_types() -> Result<()> {
use crate::format::FormatParams;
let fmtp = FormatParams::parse_line("sprop-max-don-diff=32");
assert_eq!(fmtp.sprop_max_don_diff, Some(32));
let donl_enabled = fmtp.sprop_max_don_diff.unwrap_or(0) > 0;
assert!(donl_enabled);
let mut packetizer = H266Packetizer::default();
packetizer.with_donl(donl_enabled);
let mut depacketizer = H266Depacketizer::default();
depacketizer.with_donl(donl_enabled);
let single_nalu = vec![0x00, 0x09, 0xDE, 0xAD, 0xBE, 0xEF]; let packets = packetizer.packetize(MAX_PACKET_SIZE, &single_nalu)?;
assert_eq!(packets.len(), 1);
let pkt = &packets[0];
assert_eq!(pkt.len(), single_nalu.len() + 2, "2-byte DONL on wire");
let donl = u16::from_be_bytes([pkt[2], pkt[3]]);
assert_eq!(donl, 0);
let mut out = Vec::new();
let mut extra = CodecExtra::None;
depacketizer.depacketize(pkt, &mut out, &mut extra)?;
assert_eq!(&out[0..4], ANNEXB_NALUSTART_CODE);
assert_eq!(&out[4..], &single_nalu[..]);
let vps = vec![0x00, 0x71, 0xAA, 0xBB, 0xCC];
let sps = vec![0x00, 0x79, 0xDD, 0xEE, 0xFF, 0x11];
let pps = vec![0x00, 0x81, 0x22, 0x33];
let vcl = vec![0x00, 0x39, 0x44, 0x55, 0x66];
assert!(packetizer.packetize(MAX_PACKET_SIZE, &vps)?.is_empty());
assert!(packetizer.packetize(MAX_PACKET_SIZE, &sps)?.is_empty());
assert!(packetizer.packetize(MAX_PACKET_SIZE, &pps)?.is_empty());
let ap_packets = packetizer.packetize(MAX_PACKET_SIZE, &vcl)?;
assert_eq!(ap_packets.len(), 2, "Should produce AP + VCL");
let ap = &ap_packets[0];
let ap_hdr = H266NALUHeader::new(ap[0], ap[1]);
assert_eq!(ap_hdr.nalu_type(), H266NALU_AGGREGATION_PACKET_TYPE);
let ap_donl = u16::from_be_bytes([ap[2], ap[3]]);
assert_eq!(ap_donl, 1, "AP DONL should be 1 (single consumed DON=0)");
let first_size = u16::from_be_bytes([ap[4], ap[5]]) as usize;
let second_size_off = 6 + first_size;
let second_size =
u16::from_be_bytes([ap[second_size_off], ap[second_size_off + 1]]) as usize;
assert_eq!(second_size, 6, "SPS unit size expected");
out.clear();
extra = CodecExtra::None;
depacketizer.depacketize(ap, &mut out, &mut extra)?;
let mut offset = 0;
for expected in [&vps, &sps, &pps] {
assert_eq!(&out[offset..offset + 4], ANNEXB_NALUSTART_CODE);
offset += 4;
assert_eq!(&out[offset..offset + expected.len()], &expected[..]);
offset += expected.len();
}
assert_eq!(offset, out.len());
let vcl_pkt = &ap_packets[1];
let vcl_donl = u16::from_be_bytes([vcl_pkt[2], vcl_pkt[3]]);
assert_eq!(vcl_donl, 4);
out.clear();
extra = CodecExtra::None;
depacketizer.depacketize(vcl_pkt, &mut out, &mut extra)?;
assert_eq!(&out[4..], &vcl[..]);
let mut large_nalu = vec![0x00, 0x09];
large_nalu.extend(vec![0xAA; 200]);
let fu_packets = packetizer.packetize(100, &large_nalu)?;
assert!(fu_packets.len() > 1);
let first_fu = &fu_packets[0];
let fu_hdr_byte = H266FragmentationUnitHeader(first_fu[2]);
assert!(fu_hdr_byte.s());
let fu_donl = u16::from_be_bytes([first_fu[3], first_fu[4]]);
assert_eq!(fu_donl, 5, "FU DONL after VCL consumed DON=4");
let last_fu = &fu_packets[fu_packets.len() - 1];
let last_fu_hdr = H266FragmentationUnitHeader(last_fu[2]);
assert!(last_fu_hdr.e());
assert!(!last_fu_hdr.s());
out.clear();
extra = CodecExtra::None;
for pkt in &fu_packets {
depacketizer.depacketize(pkt, &mut out, &mut extra)?;
}
assert_eq!(&out[0..4], ANNEXB_NALUSTART_CODE);
assert_eq!(&out[4..], &large_nalu[..]);
let next_nalu = vec![0x00, 0x09, 0x77];
let next_packets = packetizer.packetize(MAX_PACKET_SIZE, &next_nalu)?;
let next_donl = u16::from_be_bytes([next_packets[0][2], next_packets[0][3]]);
assert_eq!(next_donl, 6);
Ok(())
}
#[test]
fn test_h266_roundtrip_with_donl() -> Result<()> {
let mut packetizer = H266Packetizer::default();
packetizer.with_donl(true);
let mut depacketizer = H266Depacketizer::default();
depacketizer.with_donl(true);
let single_nalu = vec![0x00, 0x09, 0xFF, 0xFF, 0xFF];
let packets = packetizer.packetize(MAX_PACKET_SIZE, &single_nalu)?;
assert_eq!(packets.len(), 1);
let donl = u16::from_be_bytes([packets[0][2], packets[0][3]]);
assert_eq!(donl, 0);
let mut out = Vec::new();
let mut codec_extra = CodecExtra::None;
depacketizer.depacketize(&packets[0], &mut out, &mut codec_extra)?;
assert_eq!(&out[4..], &single_nalu[..]);
let mut large_nalu = vec![0x00, 0x09];
for i in 0..512 {
large_nalu.push((i % 256) as u8);
}
let fu_packets = packetizer.packetize(100, &large_nalu)?;
assert!(fu_packets.len() > 1);
let first_donl = u16::from_be_bytes([fu_packets[0][3], fu_packets[0][4]]);
assert_eq!(first_donl, 1, "Second NAL should have DONL=1");
out.clear();
codec_extra = CodecExtra::None;
for packet in &fu_packets {
depacketizer.depacketize(packet, &mut out, &mut codec_extra)?;
}
assert_eq!(&out[0..4], ANNEXB_NALUSTART_CODE);
assert_eq!(&out[4..], &large_nalu[..]);
Ok(())
}
#[test]
fn test_h266_aggregation_with_donl_sequences() -> Result<()> {
let mut packetizer = H266Packetizer::default();
packetizer.with_donl(true);
let vps = vec![0x00, 0x71, 0x00, 0x01, 0x02, 0x03];
let sps = vec![0x00, 0x79, 0x00, 0x01, 0x02, 0x03];
let pps = vec![0x00, 0x81, 0x00, 0x01, 0x02, 0x03];
packetizer.packetize(MAX_PACKET_SIZE, &vps)?;
packetizer.packetize(MAX_PACKET_SIZE, &sps)?;
packetizer.packetize(MAX_PACKET_SIZE, &pps)?;
let vcl = vec![0x00, 0x09, 0xAA, 0xBB];
let packets = packetizer.packetize(MAX_PACKET_SIZE, &vcl)?;
assert!(!packets.is_empty());
for packet in &packets {
assert!(packet.len() >= 2);
}
Ok(())
}
#[test]
fn test_h266_mtu_equals_fu_overhead_plus_donl() -> Result<()> {
let mut packetizer = H266Packetizer::default();
packetizer.with_donl(true);
let mut large_nalu = vec![0x00, 0x09];
large_nalu.extend(vec![0xCC; 200]);
let packets = packetizer.packetize(5, &large_nalu)?;
assert!(packets.is_empty());
Ok(())
}
#[test]
fn test_h266_single_nal_with_donl_large() -> Result<()> {
let mut packetizer = H266Packetizer::default();
packetizer.with_donl(true);
let mut large_nalu = vec![0x00, 0x09];
large_nalu.extend(vec![0xEE; 1196]); let packets = packetizer.packetize(MAX_PACKET_SIZE, &large_nalu)?;
assert_eq!(packets.len(), 1);
assert_eq!(packets[0].len(), 1200);
let mut packetizer = H266Packetizer::default();
packetizer.with_donl(true);
let mut larger_nalu = vec![0x00, 0x09];
larger_nalu.extend(vec![0xEE; 1197]); let packets = packetizer.packetize(MAX_PACKET_SIZE, &larger_nalu)?;
assert!(packets.len() > 1, "must fragment instead of exceeding MTU");
for p in &packets {
assert!(p.len() <= MAX_PACKET_SIZE);
}
Ok(())
}
}
mod svc_tests {
use super::*;
#[test]
fn test_h266_fu_preserves_layer_and_tid() -> Result<()> {
let mut nal = hdr(1, 5, 3).to_vec();
nal.extend(std::iter::repeat(0xAB).take(998));
let mut pck = H266Packetizer::default();
let packets = pck.packetize(100, &nal)?;
assert!(packets.len() > 1);
for p in &packets {
let h = H266NALUHeader::new(p[0], p[1]);
assert!(h.is_fragmentation_unit());
assert_eq!(h.layer_id(), 5, "FU header must keep LayerId");
assert_eq!(h.tid(), 3, "FU header must keep TID");
}
assert_eq!(reconstruct_from_fu_packets(&packets), nal);
let (out, _) = depacketize_all(&packets)?;
assert_eq!(out, annexb(&[&nal]));
Ok(())
}
#[test]
fn test_h266_fu_preserves_z_bit() -> Result<()> {
let mut nal = hdr(1, 2, 1).to_vec();
nal[0] |= 0b0100_0000; nal.extend(std::iter::repeat(0xCD).take(500));
let mut pck = H266Packetizer::default();
let packets = pck.packetize(100, &nal)?;
assert!(packets.len() > 1);
for p in &packets {
let h = H266NALUHeader::new(p[0], p[1]);
assert!(h.z(), "Z bit must be preserved in FU headers");
}
assert_eq!(reconstruct_from_fu_packets(&packets), nal);
Ok(())
}
#[test]
fn test_h266_ap_header_uses_lowest_layer_and_tid() -> Result<()> {
let vps = make_nal_lt(H266NALU_VPS_NALU_TYPE, 8, 2, 3);
let sps = make_nal_lt(H266NALU_SPS_NALU_TYPE, 8, 4, 1);
let pps = make_nal_lt(H266NALU_PPS_NALU_TYPE, 8, 0, 2);
let idr = make_nal_lt(H266NALU_IDR_W_RADL, 20, 9, 7);
let payload = annexb(&[&vps, &sps, &pps, &idr]);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(1200, &payload)?;
assert_eq!(packets.len(), 2);
let ap = H266NALUHeader::new(packets[0][0], packets[0][1]);
assert!(ap.is_aggregation_packet());
assert_eq!(
ap.layer_id(),
0,
"lowest LayerId of units (0), not the trigger's (9)"
);
assert_eq!(
ap.tid(),
1,
"lowest TID of units (1), not the trigger's (7)"
);
assert!(!ap.f());
Ok(())
}
#[test]
fn test_h266_ap_units_preserve_individual_headers() -> Result<()> {
let vps = make_nal_lt(H266NALU_VPS_NALU_TYPE, 8, 2, 3);
let sps = make_nal_lt(H266NALU_SPS_NALU_TYPE, 8, 4, 1);
let pps = make_nal_lt(H266NALU_PPS_NALU_TYPE, 8, 0, 2);
let idr = make_nal_lt(H266NALU_IDR_W_RADL, 20, 1, 1);
let payload = annexb(&[&vps, &sps, &pps, &idr]);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(1200, &payload)?;
let (out, keyframe) = depacketize_all(&packets)?;
assert_eq!(out, annexb(&[&vps, &sps, &pps, &idr]));
assert!(keyframe);
Ok(())
}
#[test]
fn test_h266_keyframe_detection_with_layers() -> Result<()> {
let cra = hdr(H266NALU_CRA, 3, 2);
assert!(detect_h266_keyframe(&cra));
let mut ap = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
ap.extend_from_slice(&[0x00, 0x03]);
ap.extend_from_slice(&cra);
ap.push(0x00);
assert!(detect_h266_keyframe(&ap));
let mut fu = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 3, 2).to_vec();
fu.push(0x80 | H266NALU_CRA); fu.extend_from_slice(&[0x00, 0x00]);
assert!(detect_h266_keyframe(&fu));
assert!(!detect_h266_keyframe(&hdr(1, 3, 2)));
Ok(())
}
#[test]
fn test_h266_mixed_temporal_layers_roundtrip() -> Result<()> {
let idr = make_nal_lt(H266NALU_IDR_N_LP, 600, 0, 1); let trail_t2 = make_nal_lt(1, 700, 0, 2); let trail_t3 = make_nal_lt(1, 50, 0, 3); let payload = annexb(&[&idr, &trail_t2, &trail_t3]);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(400, &payload)?;
let (out, keyframe) = depacketize_all(&packets)?;
assert_eq!(out, annexb(&[&idr, &trail_t2, &trail_t3]));
assert!(keyframe);
let mut tids = Vec::new();
let mut off = 0;
while off + 6 <= out.len() {
assert_eq!(&out[off..off + 4], &[0, 0, 0, 1]);
let h = H266NALUHeader::new(out[off + 4], out[off + 5]);
tids.push(h.tid());
let mut next = off + 4;
loop {
next += 1;
if next + 4 > out.len() {
next = out.len();
break;
}
if out[next..next + 4] == [0, 0, 0, 1] {
break;
}
}
off = next;
}
assert_eq!(tids, vec![1, 2, 3]);
Ok(())
}
}
mod hardening_tests {
use super::*;
#[test]
fn test_h266_ap_header_f_bit_or() -> Result<()> {
let vps = make_nal(H266NALU_VPS_NALU_TYPE, 6);
let mut sps = make_nal(H266NALU_SPS_NALU_TYPE, 6);
sps[0] |= 0b1000_0000; let pps = make_nal(H266NALU_PPS_NALU_TYPE, 6);
let idr = make_nal(H266NALU_IDR_W_RADL, 10);
let payload = annexb(&[&vps, &sps, &pps, &idr]);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(1200, &payload)?;
assert_eq!(packets.len(), 2);
let ap = H266NALUHeader::new(packets[0][0], packets[0][1]);
assert!(ap.is_aggregation_packet());
assert!(ap.f(), "AP header F bit must be OR of unit F bits");
Ok(())
}
#[test]
fn test_h266_ap_header_z_bit_ignored() -> Result<()> {
let mut vps = make_nal(H266NALU_VPS_NALU_TYPE, 6);
let mut sps = make_nal(H266NALU_SPS_NALU_TYPE, 6);
let mut pps = make_nal(H266NALU_PPS_NALU_TYPE, 6);
for n in [&mut vps, &mut sps, &mut pps] {
n[0] |= 0b0100_0000; }
let idr = make_nal(H266NALU_IDR_W_RADL, 10);
let payload = annexb(&[&vps, &sps, &pps, &idr]);
let mut pck = H266Packetizer::default();
let packets = pck.packetize(1200, &payload)?;
assert_eq!(packets.len(), 2);
let ap = H266NALUHeader::new(packets[0][0], packets[0][1]);
assert!(ap.is_aggregation_packet());
assert!(!ap.z(), "AP header Z bit must be 0 regardless of units");
Ok(())
}
#[test]
fn test_h266_ap_rejects_nested_ap() -> Result<()> {
let trail = make_nal(0, 4);
let nested = make_nal(H266NALU_AGGREGATION_PACKET_TYPE, 4);
let mut ap = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
for unit in [&trail, &nested] {
ap.extend_from_slice(&(unit.len() as u16).to_be_bytes());
ap.extend_from_slice(unit);
}
let mut depack = H266Depacketizer::default();
let mut out = Vec::new();
let mut extra = CodecExtra::None;
let res = depack.depacketize(&ap, &mut out, &mut extra);
assert!(matches!(res, Err(PacketError::ErrH266CorruptedPacket)));
assert!(out.is_empty(), "no partial output on corrupted AP");
Ok(())
}
#[test]
fn test_h266_ap_rejects_nested_fu() -> Result<()> {
let trail = make_nal(0, 4);
let nested = make_nal(H266NALU_FRAGMENTATION_UNIT_TYPE, 4);
let mut ap = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
for unit in [&trail, &nested] {
ap.extend_from_slice(&(unit.len() as u16).to_be_bytes());
ap.extend_from_slice(unit);
}
let mut depack = H266Depacketizer::default();
let mut out = Vec::new();
let mut extra = CodecExtra::None;
let res = depack.depacketize(&ap, &mut out, &mut extra);
assert!(matches!(res, Err(PacketError::ErrH266CorruptedPacket)));
assert!(out.is_empty());
Ok(())
}
#[test]
fn test_h266_ap_rejects_truncated_tail() -> Result<()> {
let a = make_nal(0, 4);
let b = make_nal(1, 4);
let mut ap = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
for unit in [&a, &b] {
ap.extend_from_slice(&(unit.len() as u16).to_be_bytes());
ap.extend_from_slice(unit);
}
ap.extend_from_slice(&[0x00, 0xFF, 0x42]);
let mut depack = H266Depacketizer::default();
let mut out = Vec::new();
let mut extra = CodecExtra::None;
let res = depack.depacketize(&ap, &mut out, &mut extra);
assert!(matches!(res, Err(PacketError::ErrShortPacket)));
assert!(
out.is_empty(),
"valid leading units must not leak from a malformed AP"
);
Ok(())
}
#[test]
fn test_h266_fu_preserves_all_header_flags() -> Result<()> {
let mut nal = hdr(1, 1, 1).to_vec();
nal[0] |= 0b1100_0000; nal.extend(std::iter::repeat(0x5A).take(400));
let mut pck = H266Packetizer::default();
let packets = pck.packetize(100, &nal)?;
assert!(packets.len() > 1);
for p in &packets {
let h = H266NALUHeader::new(p[0], p[1]);
assert!(h.f() && h.z(), "F/Z must be copied into FU headers");
assert_eq!(h.layer_id(), 1);
assert_eq!(h.tid(), 1);
}
assert_eq!(reconstruct_from_fu_packets(&packets), nal);
Ok(())
}
}
}