This commit is contained in:
mofeng-git
2025-12-28 18:19:16 +08:00
commit d143d158e4
771 changed files with 220548 additions and 0 deletions

View File

@@ -0,0 +1,442 @@
//! H.265/HEVC RTP Payloader
//!
//! Implements RFC 7798: RTP Payload Format for High Efficiency Video Coding (HEVC)
//!
//! H.265 NAL unit header (2 bytes):
//! ```text
//! +---------------+---------------+
//! |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! |F| Type | LayerId | TID |
//! +---------------+---------------+
//! ```
//!
//! Fragmentation Unit (FU) header:
//! ```text
//! +---------------+---------------+---------------+
//! |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! |F| Type(49) | LayerId | TID |S|E| FuType |
//! +---------------+---------------+---------------+
//! ```
//!
//! Aggregation Packet (AP) for VPS+SPS+PPS:
//! ```text
//! +---------------+---------------+---------------+---------------+
//! | PayloadHdr (Type=48) | NALU 1 Size | NALU 1 Size |
//! +---------------+---------------+---------------+---------------+
//! | NALU 1 HDR | NALU 1 Data | NALU 2 Size | ... |
//! +---------------+---------------+---------------+---------------+
//! ```
use bytes::{BufMut, Bytes, BytesMut};
/// H.265 NAL unit types (6 bits)
const H265_NAL_VPS: u8 = 32;
const H265_NAL_SPS: u8 = 33;
const H265_NAL_PPS: u8 = 34;
const H265_NAL_AUD: u8 = 35;
const H265_NAL_FILLER: u8 = 38;
#[allow(dead_code)]
const H265_NAL_SEI_PREFIX: u8 = 39; // PREFIX_SEI_NUT
#[allow(dead_code)]
const H265_NAL_SEI_SUFFIX: u8 = 40; // SUFFIX_SEI_NUT
#[allow(dead_code)]
const H265_NAL_AP: u8 = 48; // Aggregation Packet
const H265_NAL_FU: u8 = 49; // Fragmentation Unit
/// H.265 NAL header size
const H265_NAL_HEADER_SIZE: usize = 2;
/// FU header size (1 byte after NAL header)
const H265_FU_HEADER_SIZE: usize = 1;
/// Fixed PayloadHdr for FU packets: Type=49, LayerID=0, TID=1
/// This matches the rtp crate's FRAG_PAYLOAD_HDR
#[allow(dead_code)]
const FU_PAYLOAD_HDR: [u8; 2] = [0x62, 0x01];
/// Fixed PayloadHdr for AP packets: Type=48, LayerID=0, TID=1
/// This matches the rtp crate's AGGR_PAYLOAD_HDR
const AP_PAYLOAD_HDR: [u8; 2] = [0x60, 0x01];
/// H.265 RTP Payloader
///
/// Fragments H.265 NAL units for RTP transmission according to RFC 7798.
#[derive(Default, Debug, Clone)]
pub struct H265Payloader {
/// Cached VPS NAL unit
vps_nalu: Option<Bytes>,
/// Cached SPS NAL unit
sps_nalu: Option<Bytes>,
/// Cached PPS NAL unit
pps_nalu: Option<Bytes>,
}
impl H265Payloader {
/// Create a new H265Payloader
pub fn new() -> Self {
Self::default()
}
/// Find the next Annex B start code in the NAL data
fn next_ind(nalu: &Bytes, start: usize) -> (isize, isize) {
let mut zero_count = 0;
for (i, &b) in nalu[start..].iter().enumerate() {
if b == 0 {
zero_count += 1;
continue;
} else if b == 1 && zero_count >= 2 {
return ((start + i - zero_count) as isize, zero_count as isize + 1);
}
zero_count = 0;
}
(-1, -1)
}
/// Extract NAL unit type from H.265 NAL header
fn get_nal_type(nalu: &[u8]) -> u8 {
if nalu.len() < 2 {
return 0;
}
// Type is in bits 1-6 of the first byte
(nalu[0] >> 1) & 0x3F
}
/// Emit a single NAL unit, fragmenting if necessary
fn emit(&mut self, nalu: &Bytes, mtu: usize, payloads: &mut Vec<Bytes>) {
if nalu.len() < H265_NAL_HEADER_SIZE {
return;
}
let nal_type = Self::get_nal_type(nalu);
// Skip AUD and filler data
if nal_type == H265_NAL_AUD || nal_type == H265_NAL_FILLER {
return;
}
// Cache parameter sets (VPS/SPS/PPS)
match nal_type {
H265_NAL_VPS => {
self.vps_nalu = Some(nalu.clone());
return; // Don't emit VPS separately, will be sent in AP
}
H265_NAL_SPS => {
self.sps_nalu = Some(nalu.clone());
return; // Don't emit SPS separately, will be sent in AP
}
H265_NAL_PPS => {
self.pps_nalu = Some(nalu.clone());
return; // Don't emit PPS separately, will be sent in AP
}
_ => {}
}
// Try to emit Aggregation Packet with VPS+SPS+PPS before video NAL
self.try_emit_aggregation_packet(mtu, payloads);
// Single NAL unit mode - if NAL fits in one packet
if nalu.len() <= mtu {
payloads.push(nalu.clone());
return;
}
// Fragmentation Unit (FU) mode - fragment large NAL units
self.emit_fragmented(nalu, mtu, payloads);
}
/// Try to emit an Aggregation Packet containing VPS+SPS+PPS
fn try_emit_aggregation_packet(&mut self, mtu: usize, payloads: &mut Vec<Bytes>) {
// Check if we have all three parameter sets
let (vps, sps, pps) = match (&self.vps_nalu, &self.sps_nalu, &self.pps_nalu) {
(Some(v), Some(s), Some(p)) => (v.clone(), s.clone(), p.clone()),
_ => return,
};
// Calculate AP size: PayloadHdr(2) + 3x(NALU size(2) + NALU data)
let ap_size = H265_NAL_HEADER_SIZE + 2 + vps.len() + 2 + sps.len() + 2 + pps.len();
// Only create AP if it fits in MTU
if ap_size > mtu {
// Fall back to sending separately (as single NAL unit packets)
payloads.push(vps);
payloads.push(sps);
payloads.push(pps);
self.vps_nalu = None;
self.sps_nalu = None;
self.pps_nalu = None;
return;
}
// Create Aggregation Packet
let mut ap = BytesMut::with_capacity(ap_size);
// PayloadHdr for AP (Type=48)
ap.extend_from_slice(&AP_PAYLOAD_HDR);
// VPS: size (2 bytes big-endian) + data
ap.put_u16(vps.len() as u16);
ap.extend_from_slice(&vps);
// SPS: size (2 bytes big-endian) + data
ap.put_u16(sps.len() as u16);
ap.extend_from_slice(&sps);
// PPS: size (2 bytes big-endian) + data
ap.put_u16(pps.len() as u16);
ap.extend_from_slice(&pps);
payloads.push(ap.freeze());
// Clear cached parameter sets
self.vps_nalu = None;
self.sps_nalu = None;
self.pps_nalu = None;
}
/// Fragment a large NAL unit using FU packets
fn emit_fragmented(&self, nalu: &Bytes, mtu: usize, payloads: &mut Vec<Bytes>) {
if nalu.len() < H265_NAL_HEADER_SIZE {
return;
}
// Get original NAL type for FU header
let nal_type = Self::get_nal_type(nalu);
// Maximum payload size per FU packet
// FU packet = NAL header (2) + FU header (1) + payload
let max_fragment_size = mtu - H265_NAL_HEADER_SIZE - H265_FU_HEADER_SIZE;
if max_fragment_size == 0 {
return;
}
// Skip the original NAL header, we'll create new FU headers
let nalu_payload = &nalu[H265_NAL_HEADER_SIZE..];
let full_nalu_size = nalu_payload.len();
if full_nalu_size == 0 {
return;
}
let mut offset = 0;
while offset < full_nalu_size {
let remaining = full_nalu_size - offset;
let fragment_size = remaining.min(max_fragment_size);
// Create FU packet
let mut packet = BytesMut::with_capacity(H265_NAL_HEADER_SIZE + H265_FU_HEADER_SIZE + fragment_size);
// NAL header for FU (2 bytes)
// Preserve F bit (bit 7) and LayerID MSB (bit 0) from original, set Type to 49
// This matches go2rtc approach: out[0] = (out[0] & 0b10000001) | (49 << 1)
let byte0 = (nalu[0] & 0b10000001) | (H265_NAL_FU << 1);
// Keep original byte1 (LayerID low 5 bits + TID) unchanged
let byte1 = nalu[1];
packet.put_u8(byte0);
packet.put_u8(byte1);
// FU header (1 byte)
// S (1 bit) | E (1 bit) | FuType (6 bits)
let mut fu_header = nal_type;
if offset == 0 {
fu_header |= 0x80; // S bit - start of fragmented NAL
}
if offset + fragment_size >= full_nalu_size {
fu_header |= 0x40; // E bit - end of fragmented NAL
}
packet.put_u8(fu_header);
// FU payload
packet.put_slice(&nalu_payload[offset..offset + fragment_size]);
payloads.push(packet.freeze());
offset += fragment_size;
}
}
}
impl H265Payloader {
/// Payload fragments H.265 packets across one or more RTP payloads
///
/// Takes Annex B format NAL units (with start codes) and returns RTP payloads
pub fn payload(&mut self, mtu: usize, payload: &Bytes) -> Vec<Bytes> {
if payload.is_empty() || mtu == 0 {
return vec![];
}
let mut payloads = vec![];
// Parse Annex B format NAL units
let (mut next_ind_start, mut next_ind_len) = Self::next_ind(payload, 0);
if next_ind_start == -1 {
// No start code found, treat entire payload as single NAL
self.emit(payload, mtu, &mut payloads);
} else {
while next_ind_start != -1 {
let prev_start = (next_ind_start + next_ind_len) as usize;
let (next_ind_start2, next_ind_len2) = Self::next_ind(payload, prev_start);
next_ind_start = next_ind_start2;
next_ind_len = next_ind_len2;
if next_ind_start != -1 {
self.emit(
&payload.slice(prev_start..next_ind_start as usize),
mtu,
&mut payloads,
);
} else {
// Emit until end of stream
self.emit(&payload.slice(prev_start..), mtu, &mut payloads);
}
}
}
payloads
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_nal_type() {
// VPS (type 32): 0x40 = 0100 0000, type = 32
assert_eq!(H265Payloader::get_nal_type(&[0x40, 0x01]), 32);
// SPS (type 33): 0x42 = 0100 0010, type = 33
assert_eq!(H265Payloader::get_nal_type(&[0x42, 0x01]), 33);
// PPS (type 34): 0x44 = 0100 0100, type = 34
assert_eq!(H265Payloader::get_nal_type(&[0x44, 0x01]), 34);
// IDR (type 19): 0x26 = 0010 0110, type = 19
assert_eq!(H265Payloader::get_nal_type(&[0x26, 0x01]), 19);
}
#[test]
fn test_small_nalu() {
let mut payloader = H265Payloader::new();
// Small NAL that fits in MTU (no start code, just NAL data)
let small_nal = Bytes::from(vec![0x26, 0x01, 0x00, 0x00, 0x00]); // IDR type
let result = payloader.payload(1200, &small_nal);
assert_eq!(result.len(), 1);
assert_eq!(result[0], small_nal);
}
#[test]
fn test_fragmentation() {
let mut payloader = H265Payloader::new();
// Large NAL that needs fragmentation
let mut large_nal = vec![0x26, 0x01]; // IDR type header
large_nal.extend(vec![0xAA; 2000]); // Payload
let large_nal = Bytes::from(large_nal);
let mtu = 1200;
let result = payloader.payload(mtu, &large_nal);
// Should be fragmented into multiple FU packets
assert!(result.len() > 1);
// Check first packet has S bit set
assert_eq!(result[0][2] & 0x80, 0x80);
// Check last packet has E bit set
let last = result.last().unwrap();
assert_eq!(last[2] & 0x40, 0x40);
}
#[test]
fn test_fu_packet_format() {
let mut payloader = H265Payloader::new();
// IDR NAL: type=19, header = 0x26 0x01
let mut nal = vec![0x26, 0x01]; // IDR type header (type=19, TID=1)
nal.extend(vec![0xAA; 2000]); // Payload
let nal = Bytes::from(nal);
let mtu = 100; // Small MTU to force fragmentation
let result = payloader.payload(mtu, &nal);
// Verify FU packet structure
for (i, pkt) in result.iter().enumerate() {
assert!(pkt.len() >= 3, "Packet too short");
// Check PayloadHdr (2 bytes)
let byte0 = pkt[0];
let byte1 = pkt[1];
let nal_type = (byte0 >> 1) & 0x3F;
assert_eq!(nal_type, 49, "PayloadHdr type should be 49 (FU)");
// byte0 should be: (0x26 & 0x81) | (49 << 1) = 0x00 | 0x62 = 0x62
assert_eq!(byte0, 0x62, "byte0 should be 0x62");
// byte1 should be preserved from original: 0x01
assert_eq!(byte1, 0x01, "byte1 should be 0x01");
// Check FU header (1 byte)
let fu_header = pkt[2];
let fu_s = (fu_header >> 7) & 1;
let fu_e = (fu_header >> 6) & 1;
let fu_type = fu_header & 0x3F;
assert_eq!(fu_type, 19, "FU type should be 19 (IDR)");
if i == 0 {
assert_eq!(fu_s, 1, "First packet should have S=1");
assert_eq!(fu_e, 0, "First packet should have E=0");
} else if i == result.len() - 1 {
assert_eq!(fu_s, 0, "Last packet should have S=0");
assert_eq!(fu_e, 1, "Last packet should have E=1");
} else {
assert_eq!(fu_s, 0, "Middle packet should have S=0");
assert_eq!(fu_e, 0, "Middle packet should have E=0");
}
}
}
#[test]
fn test_verify_with_rtp_depacketizer() {
use rtp::codecs::h265::{H265Packet, H265Payload, H265FragmentationUnitPacket};
use rtp::packetizer::Depacketizer;
let mut payloader = H265Payloader::new();
// Create IDR NAL with enough data to fragment
let mut nal = vec![0x26, 0x01]; // IDR type=19
nal.extend(vec![0xBB; 3000]);
let nal = Bytes::from(nal);
let result = payloader.payload(1200, &nal);
assert!(result.len() > 1, "Should produce multiple FU packets");
// Verify each packet can be depacketized by rtp crate
for (i, pkt) in result.iter().enumerate() {
let mut h265_pkt = H265Packet::default();
let depack_result = h265_pkt.depacketize(pkt);
assert!(
depack_result.is_ok(),
"Packet {} failed to depacketize: {:?}, bytes: {:02x?}",
i,
depack_result.err(),
&pkt[..3.min(pkt.len())]
);
// Verify it's recognized as FU packet
match h265_pkt.payload() {
H265Payload::H265FragmentationUnitPacket(fu) => {
assert_eq!(fu.fu_header().fu_type(), 19, "FU type should be 19");
if i == 0 {
assert!(fu.fu_header().s(), "First packet S bit");
}
if i == result.len() - 1 {
assert!(fu.fu_header().e(), "Last packet E bit");
}
}
other => panic!("Expected FU packet, got {:?}", other),
}
}
println!("All {} FU packets verified successfully!", result.len());
}
}