143 lines
5.1 KiB
Rust
143 lines
5.1 KiB
Rust
use bytes::Bytes;
|
|
|
|
use crate::codec::{CodecParser, VideoFrame};
|
|
|
|
pub struct H265CodecParser {
|
|
vps: Option<Vec<u8>>,
|
|
sps: Option<Vec<u8>>,
|
|
pps: Option<Vec<u8>>,
|
|
}
|
|
|
|
impl H265CodecParser {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
vps: None,
|
|
sps: None,
|
|
pps: None,
|
|
}
|
|
}
|
|
|
|
// HEVCDecoderConfigurationRecord layout (ISO 14496-15 §8.3.3):
|
|
// [0] configurationVersion (always 1)
|
|
// [1..2] general_profile_space(2b) | general_tier_flag(1b) | general_profile_idc(5b)
|
|
// [2..6] general_profile_compatibility_flags
|
|
// [6..12] general_constraint_indicator_flags
|
|
// [12] general_level_idc
|
|
// [13..15] min_spatial_segmentation_idc (lower 12 bits)
|
|
// [15] parallelismType (lower 2 bits)
|
|
// [16] chroma_format_idc (lower 2 bits)
|
|
// [17] bit_depth_luma_minus8 (lower 3 bits)
|
|
// [18] bit_depth_chroma_minus8 (lower 3 bits)
|
|
// [19..21] avgFrameRate
|
|
// [21] constantFrameRate(2b) | numTemporalLayers(3b) | temporalIdNested(1b) | lengthSizeMinusOne(2b)
|
|
// [22] numOfArrays
|
|
// [23..] arrays: [ array_completeness(1b) | reserved(1b) | NAL_unit_type(6b), numNalus(2b),
|
|
// [ naluLength(2b), nalu(naluLength) ] ]
|
|
fn parse_sequence_header(&mut self, payload: &[u8]) {
|
|
if payload.len() < 23 {
|
|
return;
|
|
}
|
|
|
|
let num_arrays = payload[22] as usize;
|
|
let mut i = 23;
|
|
|
|
for _ in 0..num_arrays {
|
|
if i + 3 > payload.len() {
|
|
return;
|
|
}
|
|
let nal_type = payload[i] & 0x3F;
|
|
let num_nalus = u16::from_be_bytes([payload[i + 1], payload[i + 2]]) as usize;
|
|
i += 3;
|
|
|
|
for _ in 0..num_nalus {
|
|
if i + 2 > payload.len() {
|
|
return;
|
|
}
|
|
let nalu_len = u16::from_be_bytes([payload[i], payload[i + 1]]) as usize;
|
|
i += 2;
|
|
if i + nalu_len > payload.len() {
|
|
return;
|
|
}
|
|
|
|
match nal_type {
|
|
32 => self.vps = Some(payload[i..i + nalu_len].to_vec()),
|
|
33 => self.sps = Some(payload[i..i + nalu_len].to_vec()),
|
|
34 => self.pps = Some(payload[i..i + nalu_len].to_vec()),
|
|
_ => {}
|
|
}
|
|
|
|
i += nalu_len;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn hvcc_to_annexb(&self, payload: &[u8], is_keyframe: bool) -> Option<Vec<u8>> {
|
|
let mut out = Vec::with_capacity(payload.len());
|
|
|
|
if is_keyframe {
|
|
if let (Some(vps), Some(sps), Some(pps)) = (&self.vps, &self.sps, &self.pps) {
|
|
out.extend_from_slice(&[0, 0, 0, 1]);
|
|
out.extend_from_slice(vps);
|
|
out.extend_from_slice(&[0, 0, 0, 1]);
|
|
out.extend_from_slice(sps);
|
|
out.extend_from_slice(&[0, 0, 0, 1]);
|
|
out.extend_from_slice(pps);
|
|
}
|
|
}
|
|
|
|
let mut i = 0;
|
|
while i + 4 <= payload.len() {
|
|
let nalu_len = u32::from_be_bytes(payload[i..i + 4].try_into().unwrap()) as usize;
|
|
i += 4;
|
|
if i + nalu_len > payload.len() {
|
|
break;
|
|
}
|
|
out.extend_from_slice(&[0, 0, 0, 1]);
|
|
out.extend_from_slice(&payload[i..i + nalu_len]);
|
|
i += nalu_len;
|
|
}
|
|
|
|
if out.is_empty() { None } else { Some(out) }
|
|
}
|
|
}
|
|
|
|
impl CodecParser for H265CodecParser {
|
|
fn parse(&mut self, data: &[u8], timestamp_ms: u32) -> Option<VideoFrame> {
|
|
// H.265 only comes via enhanced RTMP (bit 7 set, FourCC "hvc1")
|
|
if data.len() < 5 || data[0] & 0x80 == 0 {
|
|
return None;
|
|
}
|
|
|
|
let is_keyframe = (data[0] >> 4) & 0x07 == 1;
|
|
let packet_type = data[0] & 0x0F;
|
|
// bytes 1-4 are FourCC "hvc1" — already validated by rtmp.rs
|
|
|
|
match packet_type {
|
|
0 => {
|
|
// SequenceStart: bytes[5..] = HEVCDecoderConfigurationRecord
|
|
self.parse_sequence_header(data.get(5..)?);
|
|
None
|
|
}
|
|
1 => {
|
|
// CodedFrames: bytes 5-7 = SI24 composition time offset, bytes 8+ = HVCC.
|
|
// RTP timestamps must be presentation time (RFC 7798), so emit
|
|
// PTS = DTS + CTS. Encoders use this packet type exactly when CTS != 0
|
|
// (B-frames present); stamping DTS instead makes playback stutter.
|
|
if data.len() < 8 {
|
|
return None;
|
|
}
|
|
let cts = i32::from_be_bytes([0, data[5], data[6], data[7]]) << 8 >> 8;
|
|
let pts_ms = (timestamp_ms as i64 + cts as i64).max(0) as u32;
|
|
let annexb = self.hvcc_to_annexb(data.get(8..)?, is_keyframe)?;
|
|
Some(VideoFrame { data: Bytes::from(annexb), is_keyframe, timestamp_ms: pts_ms })
|
|
}
|
|
3 => {
|
|
// CodedFramesX: no CTS, bytes 5+ = HVCC
|
|
let annexb = self.hvcc_to_annexb(data.get(5..)?, is_keyframe)?;
|
|
Some(VideoFrame { data: Bytes::from(annexb), is_keyframe, timestamp_ms })
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|