File
Blob: firmware/vendor/str0m/src/packet/h264.rs
| 1 | #![allow(clippy::all)] |
| 2 | |
| 3 | use super::{CodecExtra, Depacketizer, PacketError, Packetizer}; |
| 4 | |
| 5 | /// H264 information describing the depacketized / packetized data |
| 6 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] |
| 7 | pub struct H264CodecExtra { |
| 8 | /// Flag which indicates that within [`MediaData`], there is an individual frame |
| 9 | /// containing complete and independent visual information. This frame serves |
| 10 | /// as a reference point for other frames in the video sequence. |
| 11 | /// |
| 12 | /// [`MediaData`]: crate::media::MediaData |
| 13 | pub is_keyframe: bool, |
| 14 | } |
| 15 | |
| 16 | /// Packetizes H264 RTP packets. |
| 17 | /// |
| 18 | /// ## Unversioned API surface |
| 19 | /// |
| 20 | /// This struct is not currently versioned according to semver rules. |
| 21 | /// Breaking changes may be made in minor or patch releases. |
| 22 | #[derive(Default, Debug, Clone)] |
| 23 | pub struct H264Packetizer { |
| 24 | sps_nalu: Option<Vec<u8>>, |
| 25 | pps_nalu: Option<Vec<u8>>, |
| 26 | } |
| 27 | |
| 28 | pub const STAPA_NALU_TYPE: u8 = 24; |
| 29 | pub const FUA_NALU_TYPE: u8 = 28; |
| 30 | pub const FUB_NALU_TYPE: u8 = 29; |
| 31 | pub const IDR_NALU_TYPE: u8 = 5; |
| 32 | pub const SPS_NALU_TYPE: u8 = 7; |
| 33 | pub const PPS_NALU_TYPE: u8 = 8; |
| 34 | pub const AUD_NALU_TYPE: u8 = 9; |
| 35 | pub const FILLER_NALU_TYPE: u8 = 12; |
| 36 | |
| 37 | pub const FUA_HEADER_SIZE: usize = 2; |
| 38 | pub const STAPA_HEADER_SIZE: usize = 1; |
| 39 | pub const STAPA_NALU_LENGTH_SIZE: usize = 2; |
| 40 | |
| 41 | pub const NALU_TYPE_BITMASK: u8 = 0x1F; |
| 42 | pub const NALU_REF_IDC_BITMASK: u8 = 0x60; |
| 43 | pub const FU_START_BITMASK: u8 = 0x80; |
| 44 | pub const FU_END_BITMASK: u8 = 0x40; |
| 45 | |
| 46 | pub const OUTPUT_STAP_AHEADER: u8 = 0x78; |
| 47 | |
| 48 | pub static ANNEXB_NALUSTART_CODE: &[u8] = &[0x00, 0x00, 0x00, 0x01]; |
| 49 | |
| 50 | /// Detect whether an H264 RTP payload contains a keyframe. |
| 51 | /// |
| 52 | /// Checks for IDR (Instantaneous Decoding Refresh) NAL units (type 5) |
| 53 | /// in the RTP payload. Handles single NAL units, STAP-A aggregation |
| 54 | /// packets, and FU-A fragmentation units. |
| 55 | /// |
| 56 | /// For FU-A packets, only the start fragment (S=1) is detected as a |
| 57 | /// keyframe since the NAL type is only reliably available there. |
| 58 | pub fn detect_h264_keyframe(payload: &[u8]) -> bool { |
| 59 | if payload.is_empty() { |
| 60 | return false; |
| 61 | } |
| 62 | |
| 63 | let nalu_type = payload[0] & NALU_TYPE_BITMASK; |
| 64 | |
| 65 | match nalu_type { |
| 66 | // Single NAL unit (types 1-23) |
| 67 | 1..=23 => nalu_type == IDR_NALU_TYPE, |
| 68 | |
| 69 | // STAP-A: check all aggregated NALUs |
| 70 | STAPA_NALU_TYPE => { |
| 71 | let mut offset = STAPA_HEADER_SIZE; |
| 72 | while offset + STAPA_NALU_LENGTH_SIZE <= payload.len() { |
| 73 | let nalu_size = ((payload[offset] as usize) << 8) | payload[offset + 1] as usize; |
| 74 | offset += STAPA_NALU_LENGTH_SIZE; |
| 75 | if offset + nalu_size > payload.len() { |
| 76 | break; |
| 77 | } |
| 78 | if let Some(&b0) = payload.get(offset) { |
| 79 | if b0 & NALU_TYPE_BITMASK == IDR_NALU_TYPE { |
| 80 | return true; |
| 81 | } |
| 82 | } |
| 83 | offset += nalu_size; |
| 84 | } |
| 85 | false |
| 86 | } |
| 87 | |
| 88 | // FU-A: check start fragment for IDR type |
| 89 | FUA_NALU_TYPE => { |
| 90 | if payload.len() < FUA_HEADER_SIZE { |
| 91 | return false; |
| 92 | } |
| 93 | let b1 = payload[1]; |
| 94 | // Only the start fragment (S=1) reliably carries the original NAL type |
| 95 | if b1 & FU_START_BITMASK == 0 { |
| 96 | return false; |
| 97 | } |
| 98 | b1 & NALU_TYPE_BITMASK == IDR_NALU_TYPE |
| 99 | } |
| 100 | |
| 101 | _ => false, |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | impl H264Packetizer { |
| 106 | fn next_ind(nalu: &[u8], start: usize) -> (isize, isize) { |
| 107 | let mut zero_count = 0; |
| 108 | |
| 109 | for (i, &b) in nalu[start..].iter().enumerate() { |
| 110 | if b == 0 { |
| 111 | zero_count += 1; |
| 112 | continue; |
| 113 | } else if b == 1 && zero_count >= 2 { |
| 114 | return ((start + i - zero_count) as isize, zero_count as isize + 1); |
| 115 | } |
| 116 | zero_count = 0 |
| 117 | } |
| 118 | (-1, -1) |
| 119 | } |
| 120 | |
| 121 | fn emit(&mut self, nalu: &[u8], mtu: usize, payloads: &mut Vec<Vec<u8>>) { |
| 122 | if nalu.is_empty() { |
| 123 | return; |
| 124 | } |
| 125 | |
| 126 | let nalu_type = nalu[0] & NALU_TYPE_BITMASK; |
| 127 | let nalu_ref_idc = nalu[0] & NALU_REF_IDC_BITMASK; |
| 128 | |
| 129 | if nalu_type == AUD_NALU_TYPE || nalu_type == FILLER_NALU_TYPE { |
| 130 | return; |
| 131 | } else if nalu_type == SPS_NALU_TYPE { |
| 132 | self.sps_nalu = Some(nalu.to_vec()); |
| 133 | return; |
| 134 | } else if nalu_type == PPS_NALU_TYPE { |
| 135 | self.pps_nalu = Some(nalu.to_vec()); |
| 136 | return; |
| 137 | } else if let (Some(sps_nalu), Some(pps_nalu)) = (&self.sps_nalu, &self.pps_nalu) { |
| 138 | // Pack current NALU with SPS and PPS as STAP-A |
| 139 | let sps_len = (sps_nalu.len() as u16).to_be_bytes(); |
| 140 | let pps_len = (pps_nalu.len() as u16).to_be_bytes(); |
| 141 | |
| 142 | let mut stap_a_nalu = Vec::with_capacity(1 + 2 + sps_nalu.len() + 2 + pps_nalu.len()); |
| 143 | stap_a_nalu.push(OUTPUT_STAP_AHEADER); |
| 144 | stap_a_nalu.extend(sps_len); |
| 145 | stap_a_nalu.extend_from_slice(sps_nalu); |
| 146 | stap_a_nalu.extend(pps_len); |
| 147 | stap_a_nalu.extend_from_slice(pps_nalu); |
| 148 | if stap_a_nalu.len() <= mtu { |
| 149 | payloads.push(stap_a_nalu); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | if self.sps_nalu.is_some() && self.pps_nalu.is_some() { |
| 154 | self.sps_nalu = None; |
| 155 | self.pps_nalu = None; |
| 156 | } |
| 157 | |
| 158 | // Single NALU |
| 159 | if nalu.len() <= mtu { |
| 160 | payloads.push(nalu.to_vec()); |
| 161 | return; |
| 162 | } |
| 163 | |
| 164 | // FU-A |
| 165 | let max_fragment_size = mtu as isize - FUA_HEADER_SIZE as isize; |
| 166 | |
| 167 | // The FU payload consists of fragments of the payload of the fragmented |
| 168 | // NAL unit so that if the fragmentation unit payloads of consecutive |
| 169 | // FUs are sequentially concatenated, the payload of the fragmented NAL |
| 170 | // unit can be reconstructed. The NAL unit type octet of the fragmented |
| 171 | // NAL unit is not included as such in the fragmentation unit payload, |
| 172 | // but rather the information of the NAL unit type octet of the |
| 173 | // fragmented NAL unit is conveyed in the F and NRI fields of the FU |
| 174 | // indicator octet of the fragmentation unit and in the type field of |
| 175 | // the FU header. An FU payload MAY have any number of octets and MAY |
| 176 | // be empty. |
| 177 | |
| 178 | let nalu_data = nalu; |
| 179 | // According to the RFC, the first octet is skipped due to redundant information |
| 180 | let mut nalu_data_index = 1; |
| 181 | let nalu_data_length = nalu.len() as isize - nalu_data_index; |
| 182 | let mut nalu_data_remaining = nalu_data_length; |
| 183 | |
| 184 | if std::cmp::min(max_fragment_size, nalu_data_remaining) <= 0 { |
| 185 | return; |
| 186 | } |
| 187 | |
| 188 | while nalu_data_remaining > 0 { |
| 189 | let current_fragment_size = std::cmp::min(max_fragment_size, nalu_data_remaining); |
| 190 | //out: = make([]byte, fuaHeaderSize + currentFragmentSize) |
| 191 | let mut out = Vec::with_capacity(FUA_HEADER_SIZE + current_fragment_size as usize); |
| 192 | // +---------------+ |
| 193 | // |0|1|2|3|4|5|6|7| |
| 194 | // +-+-+-+-+-+-+-+-+ |
| 195 | // |F|NRI| Type | |
| 196 | // +---------------+ |
| 197 | let b0 = FUA_NALU_TYPE | nalu_ref_idc; |
| 198 | out.push(b0); |
| 199 | |
| 200 | // +---------------+ |
| 201 | //|0|1|2|3|4|5|6|7| |
| 202 | //+-+-+-+-+-+-+-+-+ |
| 203 | //|S|E|R| Type | |
| 204 | //+---------------+ |
| 205 | |
| 206 | let mut b1 = nalu_type; |
| 207 | if nalu_data_remaining == nalu_data_length { |
| 208 | // Set start bit |
| 209 | b1 |= 1 << 7; |
| 210 | } else if nalu_data_remaining - current_fragment_size == 0 { |
| 211 | // Set end bit |
| 212 | b1 |= 1 << 6; |
| 213 | } |
| 214 | out.push(b1); |
| 215 | |
| 216 | out.extend_from_slice( |
| 217 | &nalu_data |
| 218 | [nalu_data_index as usize..(nalu_data_index + current_fragment_size) as usize], |
| 219 | ); |
| 220 | payloads.push(out); |
| 221 | |
| 222 | nalu_data_remaining -= current_fragment_size; |
| 223 | nalu_data_index += current_fragment_size; |
| 224 | } |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | impl Packetizer for H264Packetizer { |
| 229 | /// Payload fragments a H264 packet across one or more byte arrays |
| 230 | fn packetize(&mut self, mtu: usize, payload: &[u8]) -> Result<Vec<Vec<u8>>, PacketError> { |
| 231 | if payload.is_empty() || mtu == 0 { |
| 232 | return Ok(vec![]); |
| 233 | } |
| 234 | |
| 235 | let mut payloads = vec![]; |
| 236 | |
| 237 | let (mut next_ind_start, mut next_ind_len) = H264Packetizer::next_ind(payload, 0); |
| 238 | if next_ind_start == -1 { |
| 239 | self.emit(payload, mtu, &mut payloads); |
| 240 | } else { |
| 241 | while next_ind_start != -1 { |
| 242 | let prev_start = (next_ind_start + next_ind_len) as usize; |
| 243 | let (next_ind_start2, next_ind_len2) = |
| 244 | H264Packetizer::next_ind(payload, prev_start); |
| 245 | next_ind_start = next_ind_start2; |
| 246 | next_ind_len = next_ind_len2; |
| 247 | if next_ind_start != -1 { |
| 248 | self.emit( |
| 249 | &payload[prev_start..next_ind_start as usize], |
| 250 | mtu, |
| 251 | &mut payloads, |
| 252 | ); |
| 253 | } else { |
| 254 | // Emit until end of stream, no end indicator found |
| 255 | self.emit(&payload[prev_start..], mtu, &mut payloads); |
| 256 | } |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | Ok(payloads) |
| 261 | } |
| 262 | |
| 263 | fn is_marker(&mut self, _data: &[u8], _previous: Option<&[u8]>, last: bool) -> bool { |
| 264 | last |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | /// Depacketizes H264 RTP packets. |
| 269 | /// |
| 270 | /// ## Unversioned API surface |
| 271 | /// |
| 272 | /// This struct is not currently versioned according to semver rules. |
| 273 | /// Breaking changes may be made in minor or patch releases. |
| 274 | #[derive(PartialEq, Eq, Debug, Default, Clone)] |
| 275 | pub struct H264Depacketizer { |
| 276 | /// Whether to output in AVC format (length-prefixed NALUs) instead of Annex B format |
| 277 | pub is_avc: bool, |
| 278 | fua_buffer: Option<Vec<u8>>, |
| 279 | } |
| 280 | |
| 281 | impl Depacketizer for H264Depacketizer { |
| 282 | fn out_size_hint(&self, packets_size: usize) -> Option<usize> { |
| 283 | // Roughly account for Annex B start codes or AVC length prefixes. |
| 284 | let estimated_packets = (packets_size / 1200).saturating_add(1); |
| 285 | Some(packets_size.saturating_add(4usize.saturating_mul(estimated_packets))) |
| 286 | } |
| 287 | |
| 288 | /// depacketize parses the passed byte slice and stores the result in the |
| 289 | /// H264Packet this method is called upon |
| 290 | fn depacketize( |
| 291 | &mut self, |
| 292 | packet: &[u8], |
| 293 | out: &mut Vec<u8>, |
| 294 | extra: &mut CodecExtra, |
| 295 | ) -> Result<(), PacketError> { |
| 296 | if packet.len() == 0 { |
| 297 | return Err(PacketError::ErrShortPacket); |
| 298 | } |
| 299 | |
| 300 | // NALU Types |
| 301 | // https://tools.ietf.org/html/rfc6184#section-5.4 |
| 302 | let b0 = packet[0]; |
| 303 | let nalu_type = b0 & NALU_TYPE_BITMASK; |
| 304 | |
| 305 | match nalu_type { |
| 306 | t @ 1..=23 => { |
| 307 | let is_keyframe = if let CodecExtra::H264(e) = extra { |
| 308 | (t == IDR_NALU_TYPE) | e.is_keyframe |
| 309 | } else { |
| 310 | t == IDR_NALU_TYPE |
| 311 | }; |
| 312 | *extra = CodecExtra::H264(H264CodecExtra { is_keyframe }); |
| 313 | |
| 314 | if self.is_avc { |
| 315 | out.extend_from_slice(&(packet.len() as u32).to_be_bytes()); |
| 316 | } else { |
| 317 | out.extend_from_slice(ANNEXB_NALUSTART_CODE); |
| 318 | } |
| 319 | out.extend_from_slice(packet); |
| 320 | Ok(()) |
| 321 | } |
| 322 | STAPA_NALU_TYPE => { |
| 323 | let mut curr_offset = STAPA_HEADER_SIZE; |
| 324 | while curr_offset + 1 < packet.len() { |
| 325 | let nalu_size = |
| 326 | ((packet[curr_offset] as usize) << 8) | packet[curr_offset + 1] as usize; |
| 327 | curr_offset += STAPA_NALU_LENGTH_SIZE; |
| 328 | |
| 329 | if curr_offset + nalu_size > packet.len() { |
| 330 | return Err(PacketError::StapASizeLargerThanBuffer( |
| 331 | nalu_size, |
| 332 | packet.len() - curr_offset, |
| 333 | )); |
| 334 | } |
| 335 | |
| 336 | let Some(b0) = packet.get(curr_offset) else { |
| 337 | continue; |
| 338 | }; |
| 339 | let t = b0 & NALU_TYPE_BITMASK; |
| 340 | let is_keyframe = if let CodecExtra::H264(e) = extra { |
| 341 | (t == IDR_NALU_TYPE) | e.is_keyframe |
| 342 | } else { |
| 343 | t == IDR_NALU_TYPE |
| 344 | }; |
| 345 | *extra = CodecExtra::H264(H264CodecExtra { is_keyframe }); |
| 346 | |
| 347 | if self.is_avc { |
| 348 | out.extend_from_slice(&(nalu_size as u32).to_be_bytes()); |
| 349 | } else { |
| 350 | out.extend_from_slice(ANNEXB_NALUSTART_CODE); |
| 351 | } |
| 352 | out.extend_from_slice(&packet[curr_offset..curr_offset + nalu_size]); |
| 353 | curr_offset += nalu_size; |
| 354 | } |
| 355 | |
| 356 | Ok(()) |
| 357 | } |
| 358 | FUA_NALU_TYPE => { |
| 359 | if packet.len() < FUA_HEADER_SIZE as usize { |
| 360 | return Err(PacketError::ErrShortPacket); |
| 361 | } |
| 362 | |
| 363 | if self.fua_buffer.is_none() { |
| 364 | self.fua_buffer = Some(Vec::new()); |
| 365 | } |
| 366 | |
| 367 | if let Some(fua_buffer) = &mut self.fua_buffer { |
| 368 | fua_buffer.extend_from_slice(&packet[FUA_HEADER_SIZE as usize..]); |
| 369 | } |
| 370 | |
| 371 | let b1 = packet[1]; |
| 372 | if b1 & FU_END_BITMASK != 0 { |
| 373 | let nalu_ref_idc = b0 & NALU_REF_IDC_BITMASK; |
| 374 | let fragmented_nalu_type = b1 & NALU_TYPE_BITMASK; |
| 375 | |
| 376 | let is_keyframe = if let CodecExtra::H264(e) = extra { |
| 377 | (fragmented_nalu_type == IDR_NALU_TYPE) | e.is_keyframe |
| 378 | } else { |
| 379 | fragmented_nalu_type == IDR_NALU_TYPE |
| 380 | }; |
| 381 | *extra = CodecExtra::H264(H264CodecExtra { is_keyframe }); |
| 382 | |
| 383 | if let Some(fua_buffer) = self.fua_buffer.take() { |
| 384 | if self.is_avc { |
| 385 | out.extend_from_slice(&((fua_buffer.len() + 1) as u32).to_be_bytes()); |
| 386 | } else { |
| 387 | out.extend_from_slice(ANNEXB_NALUSTART_CODE); |
| 388 | } |
| 389 | out.push(nalu_ref_idc | fragmented_nalu_type); |
| 390 | out.extend_from_slice(&fua_buffer); |
| 391 | } |
| 392 | |
| 393 | Ok(()) |
| 394 | } else { |
| 395 | Ok(()) |
| 396 | } |
| 397 | } |
| 398 | _ => Err(PacketError::NaluTypeIsNotHandled(nalu_type)), |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | /// is_partition_head checks if this is the head of a packetized nalu stream. |
| 403 | fn is_partition_head(&self, packet: &[u8]) -> bool { |
| 404 | if packet.len() < 2 { |
| 405 | return false; |
| 406 | } |
| 407 | |
| 408 | if packet[0] & NALU_TYPE_BITMASK == FUA_NALU_TYPE |
| 409 | || packet[0] & NALU_TYPE_BITMASK == FUB_NALU_TYPE |
| 410 | { |
| 411 | (packet[1] & FU_START_BITMASK) != 0 |
| 412 | } else { |
| 413 | true |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | fn is_partition_tail(&self, marker: bool, _packet: &[u8]) -> bool { |
| 418 | marker |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | #[cfg(test)] |
| 423 | mod test { |
| 424 | use super::*; |
| 425 | |
| 426 | #[test] |
| 427 | fn test_h264_payload() -> Result<(), PacketError> { |
| 428 | let empty = &[]; |
| 429 | let small_payload = &[0x90, 0x90, 0x90]; |
| 430 | let multiple_payload = &[0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x01, 0x90]; |
| 431 | let large_payload = &[ |
| 432 | 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, |
| 433 | 0x11, 0x12, 0x13, 0x14, 0x15, |
| 434 | ]; |
| 435 | let large_payload_packetized = vec![ |
| 436 | &[0x1c, 0x80, 0x01, 0x02, 0x03], |
| 437 | &[0x1c, 0x00, 0x04, 0x05, 0x06], |
| 438 | &[0x1c, 0x00, 0x07, 0x08, 0x09], |
| 439 | &[0x1c, 0x00, 0x10, 0x11, 0x12], |
| 440 | &[0x1c, 0x40, 0x13, 0x14, 0x15], |
| 441 | ]; |
| 442 | |
| 443 | let mut pck = H264Packetizer::default(); |
| 444 | |
| 445 | // Positive MTU, empty payload |
| 446 | let result = pck.packetize(1, empty)?; |
| 447 | assert!(result.is_empty(), "Generated payload should be empty"); |
| 448 | |
| 449 | // 0 MTU, small payload |
| 450 | let result = pck.packetize(0, small_payload)?; |
| 451 | assert_eq!(result.len(), 0, "Generated payload should be empty"); |
| 452 | |
| 453 | // Positive MTU, small payload |
| 454 | let result = pck.packetize(1, small_payload)?; |
| 455 | assert_eq!(result.len(), 0, "Generated payload should be empty"); |
| 456 | |
| 457 | // Positive MTU, small payload |
| 458 | let result = pck.packetize(5, small_payload)?; |
| 459 | assert_eq!(result.len(), 1, "Generated payload should be the 1"); |
| 460 | assert_eq!( |
| 461 | result[0].len(), |
| 462 | small_payload.len(), |
| 463 | "Generated payload should be the same size as original payload size" |
| 464 | ); |
| 465 | |
| 466 | // Multiple NALU in a single payload |
| 467 | let result = pck.packetize(5, multiple_payload)?; |
| 468 | assert_eq!(result.len(), 2, "2 nal units should be broken out"); |
| 469 | for i in 0..2 { |
| 470 | assert_eq!( |
| 471 | result[i].len(), |
| 472 | 1, |
| 473 | "Payload {} of 2 is packed incorrectly", |
| 474 | i + 1, |
| 475 | ); |
| 476 | } |
| 477 | |
| 478 | // Large Payload split across multiple RTP Packets |
| 479 | let result = pck.packetize(5, large_payload)?; |
| 480 | assert_eq!( |
| 481 | result, large_payload_packetized, |
| 482 | "FU-A packetization failed" |
| 483 | ); |
| 484 | |
| 485 | // Nalu type 9 or 12 |
| 486 | let small_payload2 = &[0x09, 0x00, 0x00]; |
| 487 | let result = pck.packetize(5, small_payload2)?; |
| 488 | assert_eq!(result.len(), 0, "Generated payload should be empty"); |
| 489 | |
| 490 | Ok(()) |
| 491 | } |
| 492 | |
| 493 | macro_rules! test_h264 { |
| 494 | ($name:tt, $is_avc:expr, $is_ok: expr, $payload:expr, $err:tt) => { |
| 495 | #[test] |
| 496 | fn $name() -> Result<(), PacketError> { |
| 497 | let mut pkt = H264Depacketizer::default(); |
| 498 | pkt.is_avc = $is_avc; |
| 499 | let mut extra = CodecExtra::None; |
| 500 | let mut out: Vec<u8> = Vec::new(); |
| 501 | let result = pkt.depacketize($payload, &mut out, &mut extra); |
| 502 | if $is_ok { |
| 503 | assert!(result.is_ok(), $err); |
| 504 | } else { |
| 505 | assert!(result.is_err(), $err); |
| 506 | } |
| 507 | Ok(()) |
| 508 | } |
| 509 | }; |
| 510 | } |
| 511 | |
| 512 | test_h264!( |
| 513 | nil_payload, |
| 514 | false, |
| 515 | false, |
| 516 | &[], |
| 517 | "Unmarshal did not fail on nil payload" |
| 518 | ); |
| 519 | test_h264!( |
| 520 | unit_delimiter, |
| 521 | false, |
| 522 | true, |
| 523 | &[0x09, 0x30], |
| 524 | "Unmarshal should accept minimal h.264 access unit delimiter" |
| 525 | ); |
| 526 | test_h264!( |
| 527 | end_of_sequence_nalu, |
| 528 | false, |
| 529 | true, |
| 530 | &[0x0A], |
| 531 | "Unmarshal should accept end of sequence NALU" |
| 532 | ); |
| 533 | test_h264!( |
| 534 | not_handled, |
| 535 | false, |
| 536 | false, |
| 537 | &[0xFF, 0x00, 0x00], |
| 538 | "Unmarshal accepted a packet with a NALU Type we don't handle" |
| 539 | ); |
| 540 | test_h264!( |
| 541 | incomplete_single_payload_multi_nalu, |
| 542 | false, |
| 543 | false, |
| 544 | &[ |
| 545 | 0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, |
| 546 | 0x3c, 0x22, 0x11, |
| 547 | ], |
| 548 | "Unmarshal accepted a STAP-A packet with insufficient data" |
| 549 | ); |
| 550 | |
| 551 | #[test] |
| 552 | fn single_payload() -> Result<(), PacketError> { |
| 553 | let mut pkt = H264Depacketizer::default(); |
| 554 | let mut extra = CodecExtra::None; |
| 555 | let mut out: Vec<u8> = Vec::new(); |
| 556 | let single_payload = &[0x90, 0x90, 0x90]; |
| 557 | let _ = pkt.depacketize(single_payload, &mut out, &mut extra); |
| 558 | let single_payload_unmarshaled = &[0x00, 0x00, 0x00, 0x01, 0x90, 0x90, 0x90]; |
| 559 | assert_eq!( |
| 560 | out, single_payload_unmarshaled, |
| 561 | "Unmarshaling a single payload shouldn't modify the payload" |
| 562 | ); |
| 563 | Ok(()) |
| 564 | } |
| 565 | |
| 566 | #[test] |
| 567 | fn single_payload_avc() -> Result<(), PacketError> { |
| 568 | let mut pkt = H264Depacketizer::default(); |
| 569 | pkt.is_avc = true; |
| 570 | let mut extra = CodecExtra::None; |
| 571 | let mut out: Vec<u8> = Vec::new(); |
| 572 | let single_payload = &[0x90, 0x90, 0x90]; |
| 573 | let _ = pkt.depacketize(single_payload, &mut out, &mut extra); |
| 574 | let single_payload_unmarshaled_avc = &[0x00, 0x00, 0x00, 0x03, 0x90, 0x90, 0x90]; |
| 575 | assert_eq!( |
| 576 | out, single_payload_unmarshaled_avc, |
| 577 | "Unmarshaling a single payload into avc stream shouldn't modify the payload" |
| 578 | ); |
| 579 | Ok(()) |
| 580 | } |
| 581 | |
| 582 | #[test] |
| 583 | fn h264_large_out() -> Result<(), PacketError> { |
| 584 | let large_payload_packetized = vec![ |
| 585 | &[0x1c, 0x80, 0x01, 0x02, 0x03], |
| 586 | &[0x1c, 0x00, 0x04, 0x05, 0x06], |
| 587 | &[0x1c, 0x00, 0x07, 0x08, 0x09], |
| 588 | &[0x1c, 0x00, 0x10, 0x11, 0x12], |
| 589 | &[0x1c, 0x40, 0x13, 0x14, 0x15], |
| 590 | ]; |
| 591 | |
| 592 | let large_payload = &[ |
| 593 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, |
| 594 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, |
| 595 | ]; |
| 596 | |
| 597 | let mut pkt = H264Depacketizer::default(); |
| 598 | let mut extra = CodecExtra::None; |
| 599 | |
| 600 | let mut large_out = Vec::new(); |
| 601 | for p in &large_payload_packetized { |
| 602 | pkt.depacketize(*p, &mut large_out, &mut extra)?; |
| 603 | } |
| 604 | assert_eq!( |
| 605 | large_out, large_payload, |
| 606 | "Failed to unmarshal a large payload" |
| 607 | ); |
| 608 | |
| 609 | Ok(()) |
| 610 | } |
| 611 | |
| 612 | #[test] |
| 613 | fn h264_large_out_avc() -> Result<(), PacketError> { |
| 614 | let large_payload_packetized = vec![ |
| 615 | &[0x1c, 0x80, 0x01, 0x02, 0x03], |
| 616 | &[0x1c, 0x00, 0x04, 0x05, 0x06], |
| 617 | &[0x1c, 0x00, 0x07, 0x08, 0x09], |
| 618 | &[0x1c, 0x00, 0x10, 0x11, 0x12], |
| 619 | &[0x1c, 0x40, 0x13, 0x14, 0x15], |
| 620 | ]; |
| 621 | |
| 622 | let large_payload_avc = &[ |
| 623 | 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, |
| 624 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, |
| 625 | ]; |
| 626 | |
| 627 | let mut avc_pkt = H264Depacketizer { |
| 628 | is_avc: true, |
| 629 | ..Default::default() |
| 630 | }; |
| 631 | |
| 632 | let mut extra = CodecExtra::None; |
| 633 | |
| 634 | let mut large_out_avc = Vec::new(); |
| 635 | for p in &large_payload_packetized { |
| 636 | avc_pkt.depacketize(*p, &mut large_out_avc, &mut extra)?; |
| 637 | } |
| 638 | assert_eq!( |
| 639 | large_out_avc, large_payload_avc, |
| 640 | "Failed to unmarshal a large payload into avc stream" |
| 641 | ); |
| 642 | |
| 643 | Ok(()) |
| 644 | } |
| 645 | |
| 646 | #[test] |
| 647 | fn single_payload_multi_nalu() -> Result<(), PacketError> { |
| 648 | let single_payload_multi_nalu = &[ |
| 649 | 0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, |
| 650 | 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8, 0x00, |
| 651 | ]; |
| 652 | let single_payload_multi_nalu_unmarshaled = &[ |
| 653 | 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, |
| 654 | 0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x00, 0x00, 0x01, 0x68, 0x1a, 0x34, 0xe3, 0xc8, |
| 655 | ]; |
| 656 | |
| 657 | let mut pkt = H264Depacketizer::default(); |
| 658 | |
| 659 | let mut extra = CodecExtra::None; |
| 660 | |
| 661 | let mut out = Vec::new(); |
| 662 | pkt.depacketize(single_payload_multi_nalu, &mut out, &mut extra)?; |
| 663 | assert_eq!( |
| 664 | out, single_payload_multi_nalu_unmarshaled, |
| 665 | "Failed to unmarshal a single packet with multiple NALUs" |
| 666 | ); |
| 667 | |
| 668 | Ok(()) |
| 669 | } |
| 670 | |
| 671 | #[test] |
| 672 | fn single_payload_multi_nalu_avc() -> Result<(), PacketError> { |
| 673 | let single_payload_multi_nalu = &[ |
| 674 | 0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, |
| 675 | 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8, 0x00, |
| 676 | ]; |
| 677 | let single_payload_multi_nalu_unmarshaled_avc = &[ |
| 678 | 0x00, 0x00, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, |
| 679 | 0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x00, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8, |
| 680 | ]; |
| 681 | |
| 682 | let mut avc_pkt = H264Depacketizer::default(); |
| 683 | avc_pkt.is_avc = true; |
| 684 | |
| 685 | let mut extra = CodecExtra::None; |
| 686 | |
| 687 | let mut out = Vec::new(); |
| 688 | avc_pkt.depacketize(single_payload_multi_nalu, &mut out, &mut extra)?; |
| 689 | assert_eq!( |
| 690 | out, single_payload_multi_nalu_unmarshaled_avc, |
| 691 | "Failed to unmarshal a single packet with multiple NALUs into avc stream" |
| 692 | ); |
| 693 | |
| 694 | Ok(()) |
| 695 | } |
| 696 | |
| 697 | #[test] |
| 698 | fn test_h264_partition_head_checker_is_partition_head() -> Result<(), PacketError> { |
| 699 | let h264 = H264Depacketizer::default(); |
| 700 | let empty_nalu = &[]; |
| 701 | assert!( |
| 702 | !h264.is_partition_head(empty_nalu), |
| 703 | "empty nalu must not be a partition head" |
| 704 | ); |
| 705 | |
| 706 | let single_nalu = &[1, 0]; |
| 707 | assert!( |
| 708 | h264.is_partition_head(single_nalu), |
| 709 | "single nalu must be a partition head" |
| 710 | ); |
| 711 | |
| 712 | let stapa_nalu = &[STAPA_NALU_TYPE, 0]; |
| 713 | assert!( |
| 714 | h264.is_partition_head(stapa_nalu), |
| 715 | "stapa nalu must be a partition head" |
| 716 | ); |
| 717 | |
| 718 | let fua_start_nalu = &[FUA_NALU_TYPE, FU_START_BITMASK]; |
| 719 | assert!( |
| 720 | h264.is_partition_head(fua_start_nalu), |
| 721 | "fua start nalu must be a partition head" |
| 722 | ); |
| 723 | |
| 724 | let fua_end_nalu = &[FUA_NALU_TYPE, FU_END_BITMASK]; |
| 725 | assert!( |
| 726 | !h264.is_partition_head(fua_end_nalu), |
| 727 | "fua end nalu must not be a partition head" |
| 728 | ); |
| 729 | |
| 730 | let fub_start_nalu = &[FUB_NALU_TYPE, FU_START_BITMASK]; |
| 731 | assert!( |
| 732 | h264.is_partition_head(fub_start_nalu), |
| 733 | "fub start nalu must be a partition head" |
| 734 | ); |
| 735 | |
| 736 | let fub_end_nalu = &[FUB_NALU_TYPE, FU_END_BITMASK]; |
| 737 | assert!( |
| 738 | !h264.is_partition_head(fub_end_nalu), |
| 739 | "fub end nalu must not be a partition head" |
| 740 | ); |
| 741 | |
| 742 | Ok(()) |
| 743 | } |
| 744 | |
| 745 | #[test] |
| 746 | fn test_h264_packetizer_payload_sps_and_pps_handling() -> Result<(), PacketError> { |
| 747 | let mut pck = H264Packetizer::default(); |
| 748 | let expected: Vec<&[u8]> = vec![ |
| 749 | &[ |
| 750 | 0x78, 0x00, 0x03, 0x07, 0x00, 0x01, 0x00, 0x03, 0x08, 0x02, 0x03, |
| 751 | ], |
| 752 | &[0x05, 0x04, 0x05], |
| 753 | ]; |
| 754 | |
| 755 | // When packetizing SPS and PPS are emitted with following NALU |
| 756 | let res = pck.packetize(1500, &[0x07, 0x00, 0x01])?; |
| 757 | assert!(res.is_empty(), "Generated payload should be empty"); |
| 758 | |
| 759 | let res = pck.packetize(1500, &[0x08, 0x02, 0x03])?; |
| 760 | assert!(res.is_empty(), "Generated payload should be empty"); |
| 761 | |
| 762 | let actual = pck.packetize(1500, &[0x05, 0x04, 0x05])?; |
| 763 | assert_eq!(actual, expected, "SPS and PPS aren't packed together"); |
| 764 | |
| 765 | Ok(()) |
| 766 | } |
| 767 | |
| 768 | #[test] |
| 769 | fn test_h264_depacketizer_idr_handling() -> Result<(), PacketError> { |
| 770 | let mut pck = H264Depacketizer::default(); |
| 771 | let mut extra = CodecExtra::None; |
| 772 | let mut out = vec![]; |
| 773 | |
| 774 | // First byte is NALU type |
| 775 | let packet = [0x85]; |
| 776 | pck.depacketize(&packet, &mut out, &mut extra)?; |
| 777 | let CodecExtra::H264(e) = extra else { |
| 778 | panic!("Expected CodecExtra::H264"); |
| 779 | }; |
| 780 | assert!(e.is_keyframe); |
| 781 | |
| 782 | // First byte is STAPA NALU type |
| 783 | let packet = vec![ |
| 784 | vec![ |
| 785 | 120, 0, 15, 103, 66, 192, 21, 140, 141, 64, 160, 203, 207, 0, 240, 136, 70, 160, 0, |
| 786 | 4, 104, 206, 60, 128, 1, 20, 101, |
| 787 | ], |
| 788 | vec![0; 276], |
| 789 | ] |
| 790 | .into_iter() |
| 791 | .flatten() |
| 792 | .collect::<Vec<_>>(); |
| 793 | pck.depacketize(packet.as_slice(), &mut out, &mut extra)?; |
| 794 | let CodecExtra::H264(e) = extra else { |
| 795 | panic!("Expected CodecExtra::H264"); |
| 796 | }; |
| 797 | assert!(e.is_keyframe); |
| 798 | |
| 799 | // First byte is FUA NALU type |
| 800 | let packet = [124, 69]; |
| 801 | pck.depacketize(&packet, &mut out, &mut extra)?; |
| 802 | let CodecExtra::H264(e) = extra else { |
| 803 | panic!("Expected CodecExtra::H264"); |
| 804 | }; |
| 805 | assert!(e.is_keyframe); |
| 806 | Ok(()) |
| 807 | } |
| 808 | |
| 809 | #[test] |
| 810 | fn parse_first_packet() { |
| 811 | const PACKET: &[u8] = &[ |
| 812 | 120, 000, 015, 103, 066, 192, 021, 140, 141, 064, 160, 203, 207, 000, 240, 136, 070, |
| 813 | 160, 000, 004, 104, 206, 060, 128, 000, 204, 101, 184, 000, 004, 000, 000, 005, 057, |
| 814 | 049, 064, 000, 064, 222, 078, 078, 078, 078, 078, 078, 078, 078, 078, 078, 078, 078, |
| 815 | 078, 078, 078, 078, 078, 078, 078, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, |
| 816 | 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, |
| 817 | 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 173, 223, 039, 125, 247, 223, |
| 818 | 125, 245, 215, 093, 117, 215, 093, 117, 214, 239, 174, 187, 235, 174, 186, 235, 174, |
| 819 | 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, |
| 820 | 174, 186, 235, 174, 183, 093, 117, 215, 093, 117, 215, 093, 117, 215, 093, 117, 215, |
| 821 | 093, 117, 215, 093, 117, 215, 092, 189, 117, 215, 093, 117, 215, 093, 117, 215, 093, |
| 822 | 117, 215, 093, 117, 215, 093, 117, 215, 093, 117, 214, 239, 190, 251, 239, 190, 186, |
| 823 | 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, |
| 824 | 186, 235, 174, 186, 235, 175, 227, 255, 240, 247, 021, 223, 125, 247, 223, 125, 247, |
| 825 | 223, 125, 247, 223, 125, 247, 223, 125, 248, |
| 826 | ]; |
| 827 | |
| 828 | let mut pck = H264Depacketizer::default(); |
| 829 | let mut extra = CodecExtra::None; |
| 830 | let mut out = vec![]; |
| 831 | pck.depacketize(PACKET, &mut out, &mut extra).unwrap(); |
| 832 | } |
| 833 | |
| 834 | #[test] |
| 835 | fn test_out_of_bounds_access() { |
| 836 | const PACKET: &[u8] = &[STAPA_NALU_TYPE, 0x00, 0x00]; |
| 837 | |
| 838 | let mut pck = H264Depacketizer::default(); |
| 839 | let mut extra = CodecExtra::None; |
| 840 | let mut out = vec![]; |
| 841 | pck.depacketize(PACKET, &mut out, &mut extra).unwrap(); |
| 842 | } |
| 843 | |
| 844 | #[test] |
| 845 | fn test_detect_h264_keyframe() { |
| 846 | // Empty payload |
| 847 | assert!(!detect_h264_keyframe(&[])); |
| 848 | |
| 849 | // Single IDR NAL unit (type 5) |
| 850 | assert!(detect_h264_keyframe(&[0x65, 0x00, 0x00])); // 0x65 & 0x1F = 5 |
| 851 | |
| 852 | // Single non-IDR NAL unit (type 1 = coded slice) |
| 853 | assert!(!detect_h264_keyframe(&[0x41, 0x00, 0x00])); // 0x41 & 0x1F = 1 |
| 854 | |
| 855 | // SPS (type 7) - not a keyframe |
| 856 | assert!(!detect_h264_keyframe(&[0x67, 0x00, 0x00])); |
| 857 | |
| 858 | // PPS (type 8) - not a keyframe |
| 859 | assert!(!detect_h264_keyframe(&[0x68, 0x00, 0x00])); |
| 860 | |
| 861 | // STAP-A containing IDR |
| 862 | // Header: type 24 (STAP-A) |
| 863 | // NALU 1: 2 bytes, type 7 (SPS) |
| 864 | // NALU 2: 2 bytes, type 5 (IDR) |
| 865 | let stapa_with_idr = [ |
| 866 | 0x18, // STAP-A (24) |
| 867 | 0x00, 0x02, 0x67, 0xAA, // SPS: size=2, type=7 |
| 868 | 0x00, 0x02, 0x65, 0xBB, // IDR: size=2, type=5 |
| 869 | ]; |
| 870 | assert!(detect_h264_keyframe(&stapa_with_idr)); |
| 871 | |
| 872 | // STAP-A without IDR |
| 873 | let stapa_no_idr = [ |
| 874 | 0x18, // STAP-A (24) |
| 875 | 0x00, 0x02, 0x67, 0xAA, // SPS: size=2, type=7 |
| 876 | 0x00, 0x02, 0x68, 0xBB, // PPS: size=2, type=8 |
| 877 | ]; |
| 878 | assert!(!detect_h264_keyframe(&stapa_no_idr)); |
| 879 | |
| 880 | // FU-A start fragment with IDR type |
| 881 | let fua_start_idr = [ |
| 882 | 0x7C, // FU indicator: type=28 (FU-A) |
| 883 | 0x85, // FU header: S=1, type=5 (IDR) |
| 884 | 0x00, 0x00, |
| 885 | ]; |
| 886 | assert!(detect_h264_keyframe(&fua_start_idr)); |
| 887 | |
| 888 | // FU-A start fragment with non-IDR type |
| 889 | let fua_start_non_idr = [ |
| 890 | 0x7C, // FU indicator: type=28 (FU-A) |
| 891 | 0x81, // FU header: S=1, type=1 (non-IDR) |
| 892 | 0x00, 0x00, |
| 893 | ]; |
| 894 | assert!(!detect_h264_keyframe(&fua_start_non_idr)); |
| 895 | |
| 896 | // FU-A continuation fragment (S=0) - cannot detect |
| 897 | let fua_continuation = [ |
| 898 | 0x7C, // FU indicator: type=28 (FU-A) |
| 899 | 0x05, // FU header: S=0, type=5 (IDR) but S=0 |
| 900 | 0x00, 0x00, |
| 901 | ]; |
| 902 | assert!(!detect_h264_keyframe(&fua_continuation)); |
| 903 | |
| 904 | // FU-A too short |
| 905 | assert!(!detect_h264_keyframe(&[0x7C])); |
| 906 | } |
| 907 | |
| 908 | #[test] |
| 909 | fn packetize_respects_mtu() -> Result<(), PacketError> { |
| 910 | // NAL header byte (type=5, IDR slice) + payload. |
| 911 | let mut payload = vec![0x05u8]; |
| 912 | payload.extend(std::iter::repeat(0xABu8).take(2000)); |
| 913 | for &mtu in &[100usize, 300, 600, 1200] { |
| 914 | let mut pck = H264Packetizer::default(); |
| 915 | let pkts = pck.packetize(mtu, &payload)?; |
| 916 | assert!(!pkts.is_empty(), "H264 produced no packets at mtu {mtu}"); |
| 917 | for (i, pkt) in pkts.iter().enumerate() { |
| 918 | assert!( |
| 919 | pkt.len() <= mtu, |
| 920 | "H264 packet {i} size {} > mtu {mtu}", |
| 921 | pkt.len() |
| 922 | ); |
| 923 | } |
| 924 | } |
| 925 | Ok(()) |
| 926 | } |
| 927 | } |