use crate::frame::PixelFormat;
use super::bitreader::{BitReader, find_next_start_code, remove_h264_rbsp_stuffing,
clamp_to_i8, more_rbsp_data};
pub(super) fn detect_h264(sample: &[u8]) -> Option<PixelFormat> {
let sps = find_h264_sps(sample)?;
let rbsp = remove_h264_rbsp_stuffing(sps);
let mut br = BitReader::new(&rbsp);
let profile_idc = br.read_bits(8)? as u8;
let _constraint_flags = br.read_bits(8)?;
let _level_idc = br.read_bits(8)?;
let _seq_parameter_set_id = br.read_ue()?;
let profile_gates_chroma = matches!(
profile_idc,
100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135
);
let (chroma_format_idc, bit_depth_luma) = if profile_gates_chroma {
let chroma_format_idc = br.read_ue()? as u8;
if chroma_format_idc == 3 {
let _separate_colour_plane_flag = br.read_bits(1)?;
}
let bit_depth_luma_minus8 = br.read_ue()? as u8;
(chroma_format_idc, bit_depth_luma_minus8 + 8)
} else {
(1, 8)
};
Some(PixelFormat::from_chroma_and_depth(
chroma_format_idc,
bit_depth_luma,
))
}
fn find_h264_sps(data: &[u8]) -> Option<&[u8]> {
let mut i = 0;
while i + 4 < data.len() {
let (start_len, nal_byte) = if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
(3, i + 3)
} else if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1 {
(4, i + 4)
} else {
i += 1;
continue;
};
if nal_byte >= data.len() {
return None;
}
let nal_unit_type = data[nal_byte] & 0x1F;
if nal_unit_type == 7 {
let start = nal_byte + 1;
let end = find_next_start_code(&data[start..])
.map(|off| start + off)
.unwrap_or(data.len());
return Some(&data[start..end]);
}
i += start_len;
}
None
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct H264SpsInfo {
pub profile_idc: u8,
pub constraint_set_flags: u8,
pub level_idc: u8,
pub chroma_format_idc: u8,
pub separate_colour_plane_flag: bool,
pub bit_depth_luma: u8,
pub bit_depth_chroma: u8,
pub frame_mbs_only: bool,
pub width: Option<u32>,
pub height: Option<u32>,
pub log2_max_frame_num_minus4: Option<u8>,
pub pic_order_cnt_type: Option<u8>,
pub log2_max_pic_order_cnt_lsb_minus4: Option<u8>,
pub delta_pic_order_always_zero_flag: Option<bool>,
pub qpprime_y_zero_transform_bypass_flag: Option<bool>,
pub seq_scaling_matrix_present_flag: Option<bool>,
pub max_num_ref_frames: Option<u8>,
pub gaps_in_frame_num_value_allowed_flag: Option<bool>,
pub mb_adaptive_frame_field_flag: Option<bool>,
pub direct_8x8_inference_flag: Option<bool>,
pub frame_cropping_flag: Option<bool>,
pub frame_crop_left_offset: Option<u32>,
pub frame_crop_right_offset: Option<u32>,
pub frame_crop_top_offset: Option<u32>,
pub frame_crop_bottom_offset: Option<u32>,
pub offset_for_non_ref_pic: Option<i32>,
pub offset_for_top_to_bottom_field: Option<i32>,
pub num_ref_frames_in_pic_order_cnt_cycle: Option<u8>,
pub offset_for_ref_frame: Vec<i32>,
}
pub fn parse_h264_sps(sample: &[u8]) -> Option<H264SpsInfo> {
let sps = find_h264_sps(sample)?;
let rbsp = remove_h264_rbsp_stuffing(sps);
let mut br = BitReader::new(&rbsp);
let profile_idc = br.read_bits(8)? as u8;
let constraint_set_flags = br.read_bits(8)? as u8;
let level_idc = br.read_bits(8)? as u8;
let _seq_parameter_set_id = br.read_ue()?;
let profile_gates_chroma = matches!(
profile_idc,
100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135
);
let (
chroma_format_idc,
separate_colour_plane_flag,
bit_depth_luma,
bit_depth_chroma,
qpprime_y_zero,
scaling_matrix,
) = if profile_gates_chroma {
let chroma = br.read_ue()? as u8;
let separate = if chroma == 3 {
br.read_bits(1)? == 1
} else {
false
};
let bit_depth_luma_m8 = br.read_ue()?;
let bit_depth_chroma_m8 = br.read_ue()?;
let qpprime = br.read_bits(1)? == 1;
let scaling_matrix_present = br.read_bits(1)? == 1;
if scaling_matrix_present {
let num_lists = if chroma == 3 { 12 } else { 8 };
for i in 0..num_lists {
if br.read_bits(1)? == 1 {
let size = if i < 6 { 16 } else { 64 };
let mut last_scale: i32 = 8;
let mut next_scale: i32 = 8;
for _j in 0..size {
if next_scale != 0 {
let delta = br.read_se()?;
next_scale = (last_scale + delta + 256).rem_euclid(256);
}
if next_scale != 0 {
last_scale = next_scale;
}
}
}
}
}
(
chroma,
separate,
bit_depth_luma_m8 as u8 + 8,
bit_depth_chroma_m8 as u8 + 8,
qpprime,
scaling_matrix_present,
)
} else {
(1u8, false, 8u8, 8u8, false, false)
};
let info_prefix = H264SpsInfo {
profile_idc,
constraint_set_flags,
level_idc,
chroma_format_idc,
separate_colour_plane_flag,
bit_depth_luma,
bit_depth_chroma,
frame_mbs_only: true,
width: None,
height: None,
log2_max_frame_num_minus4: None,
pic_order_cnt_type: None,
log2_max_pic_order_cnt_lsb_minus4: None,
delta_pic_order_always_zero_flag: None,
qpprime_y_zero_transform_bypass_flag: Some(qpprime_y_zero),
seq_scaling_matrix_present_flag: Some(scaling_matrix),
max_num_ref_frames: None,
gaps_in_frame_num_value_allowed_flag: None,
mb_adaptive_frame_field_flag: None,
direct_8x8_inference_flag: None,
frame_cropping_flag: None,
frame_crop_left_offset: None,
frame_crop_right_offset: None,
frame_crop_top_offset: None,
frame_crop_bottom_offset: None,
offset_for_non_ref_pic: None,
offset_for_top_to_bottom_field: None,
num_ref_frames_in_pic_order_cnt_cycle: None,
offset_for_ref_frame: Vec::new(),
};
let Some(dims) = parse_h264_sps_dims(&mut br, chroma_format_idc, separate_colour_plane_flag)
else {
return Some(info_prefix);
};
Some(H264SpsInfo {
frame_mbs_only: dims.frame_mbs_only,
width: Some(dims.width),
height: Some(dims.height),
log2_max_frame_num_minus4: Some(dims.log2_max_frame_num_minus4),
pic_order_cnt_type: Some(dims.pic_order_cnt_type),
log2_max_pic_order_cnt_lsb_minus4: dims.log2_max_pic_order_cnt_lsb_minus4,
delta_pic_order_always_zero_flag: dims.delta_pic_order_always_zero_flag,
max_num_ref_frames: Some(dims.max_num_ref_frames),
gaps_in_frame_num_value_allowed_flag: Some(dims.gaps_in_frame_num_value_allowed_flag),
mb_adaptive_frame_field_flag: dims.mb_adaptive_frame_field_flag,
direct_8x8_inference_flag: Some(dims.direct_8x8_inference_flag),
frame_cropping_flag: Some(dims.frame_cropping_flag),
frame_crop_left_offset: Some(dims.crop_left),
frame_crop_right_offset: Some(dims.crop_right),
frame_crop_top_offset: Some(dims.crop_top),
frame_crop_bottom_offset: Some(dims.crop_bottom),
offset_for_non_ref_pic: dims.offset_for_non_ref_pic,
offset_for_top_to_bottom_field: dims.offset_for_top_to_bottom_field,
num_ref_frames_in_pic_order_cnt_cycle: dims.num_ref_frames_in_pic_order_cnt_cycle,
offset_for_ref_frame: dims.offset_for_ref_frame,
..info_prefix
})
}
struct H264Dims {
width: u32,
height: u32,
frame_mbs_only: bool,
log2_max_frame_num_minus4: u8,
pic_order_cnt_type: u8,
log2_max_pic_order_cnt_lsb_minus4: Option<u8>,
delta_pic_order_always_zero_flag: Option<bool>,
offset_for_non_ref_pic: Option<i32>,
offset_for_top_to_bottom_field: Option<i32>,
num_ref_frames_in_pic_order_cnt_cycle: Option<u8>,
offset_for_ref_frame: Vec<i32>,
max_num_ref_frames: u8,
gaps_in_frame_num_value_allowed_flag: bool,
mb_adaptive_frame_field_flag: Option<bool>,
direct_8x8_inference_flag: bool,
frame_cropping_flag: bool,
crop_left: u32,
crop_right: u32,
crop_top: u32,
crop_bottom: u32,
}
fn parse_h264_sps_dims(
br: &mut BitReader,
chroma_format_idc: u8,
separate_colour_plane_flag: bool,
) -> Option<H264Dims> {
let log2_max_frame_num_minus4 = br.read_ue()? as u8;
let pic_order_cnt_type = br.read_ue()? as u8;
let mut log2_max_pic_order_cnt_lsb_minus4 = None;
let mut delta_pic_order_always_zero_flag = None;
let mut offset_for_non_ref_pic = None;
let mut offset_for_top_to_bottom_field = None;
let mut num_ref_frames_in_pic_order_cnt_cycle: Option<u8> = None;
let mut offset_for_ref_frame: Vec<i32> = Vec::new();
match pic_order_cnt_type {
0 => {
log2_max_pic_order_cnt_lsb_minus4 = Some(br.read_ue()? as u8);
}
1 => {
delta_pic_order_always_zero_flag = Some(br.read_bits(1)? == 1);
offset_for_non_ref_pic = Some(br.read_se()?);
offset_for_top_to_bottom_field = Some(br.read_se()?);
let cycle_len = br.read_ue()?;
let capped = cycle_len.min(255) as u8;
num_ref_frames_in_pic_order_cnt_cycle = Some(capped);
offset_for_ref_frame.reserve(capped as usize);
for _ in 0..capped {
offset_for_ref_frame.push(br.read_se()?);
}
}
2 => { }
_ => return None, }
let max_num_ref_frames = br.read_ue()?.min(u8::MAX as u32) as u8;
let gaps_in_frame_num_value_allowed_flag = br.read_bits(1)? == 1;
let pic_width_in_mbs_minus1 = br.read_ue()?;
let pic_height_in_map_units_minus1 = br.read_ue()?;
let frame_mbs_only_flag = br.read_bits(1)?;
let mut mb_adaptive_frame_field_flag = None;
if frame_mbs_only_flag == 0 {
mb_adaptive_frame_field_flag = Some(br.read_bits(1)? == 1);
}
let direct_8x8_inference_flag = br.read_bits(1)? == 1;
let frame_cropping_flag = br.read_bits(1)? == 1;
let (cl, cr, ct, cb) = if frame_cropping_flag {
(br.read_ue()?, br.read_ue()?, br.read_ue()?, br.read_ue()?)
} else {
(0, 0, 0, 0)
};
let pic_width_in_mbs = pic_width_in_mbs_minus1.saturating_add(1);
let pic_height_in_map_units = pic_height_in_map_units_minus1.saturating_add(1);
let frame_mbs_only = frame_mbs_only_flag == 1;
let frame_height_in_mbs = if frame_mbs_only {
pic_height_in_map_units
} else {
pic_height_in_map_units.saturating_mul(2)
};
let chroma_array_type = if separate_colour_plane_flag {
0
} else {
chroma_format_idc
};
let (sub_w, sub_h) = match chroma_array_type {
0 => (1u32, 1u32), 1 => (2, 2), 2 => (2, 1), 3 => (1, 1), _ => (1, 1),
};
let (crop_x, crop_y) = if chroma_array_type == 0 {
(1u32, 2u32 - frame_mbs_only_flag)
} else {
(sub_w, sub_h * (2 - frame_mbs_only_flag))
};
let width = pic_width_in_mbs
.saturating_mul(16)
.saturating_sub(crop_x.saturating_mul(cl.saturating_add(cr)));
let height = frame_height_in_mbs
.saturating_mul(16)
.saturating_sub(crop_y.saturating_mul(ct.saturating_add(cb)));
Some(H264Dims {
width,
height,
frame_mbs_only,
log2_max_frame_num_minus4,
pic_order_cnt_type,
log2_max_pic_order_cnt_lsb_minus4,
delta_pic_order_always_zero_flag,
offset_for_non_ref_pic,
offset_for_top_to_bottom_field,
num_ref_frames_in_pic_order_cnt_cycle,
offset_for_ref_frame,
max_num_ref_frames,
gaps_in_frame_num_value_allowed_flag,
mb_adaptive_frame_field_flag,
direct_8x8_inference_flag,
frame_cropping_flag,
crop_left: cl,
crop_right: cr,
crop_top: ct,
crop_bottom: cb,
})
}
pub fn h264_first_slice_nal_offset(data: &[u8]) -> Option<u32> {
let mut i = 0;
while i + 4 < data.len() {
let (start_len, nal_byte) = if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
(3usize, i + 3)
} else if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1 {
(4usize, i + 4)
} else {
i += 1;
continue;
};
if nal_byte >= data.len() {
return None;
}
let t = data[nal_byte] & 0x1F;
if matches!(t, 1 | 5 | 19) {
return Some(nal_byte as u32);
}
i += start_len;
}
None
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct H264PpsInfo {
pub pic_parameter_set_id: u8,
pub seq_parameter_set_id: u8,
pub entropy_coding_mode_flag: bool,
pub bottom_field_pic_order_in_frame_present_flag: bool,
pub num_slice_groups_minus1: u8,
pub num_ref_idx_l0_default_active_minus1: u8,
pub num_ref_idx_l1_default_active_minus1: u8,
pub weighted_pred_flag: bool,
pub weighted_bipred_idc: u8,
pub pic_init_qp_minus26: i8,
pub pic_init_qs_minus26: i8,
pub chroma_qp_index_offset: i8,
pub deblocking_filter_control_present_flag: bool,
pub constrained_intra_pred_flag: bool,
pub redundant_pic_cnt_present_flag: bool,
pub transform_8x8_mode_flag: Option<bool>,
pub pic_scaling_matrix_present_flag: Option<bool>,
pub second_chroma_qp_index_offset: Option<i8>,
}
pub fn parse_h264_pps(sample: &[u8]) -> Option<H264PpsInfo> {
let pps = find_h264_nal_by_type(sample, 8)?;
let rbsp = remove_h264_rbsp_stuffing(pps);
let mut br = BitReader::new(&rbsp);
let pic_parameter_set_id = br.read_ue()? as u8;
let seq_parameter_set_id = br.read_ue()? as u8;
let entropy_coding_mode_flag = br.read_bits(1)? == 1;
let bottom_field_pic_order_in_frame_present_flag = br.read_bits(1)? == 1;
let num_slice_groups_minus1 = br.read_ue()?;
if num_slice_groups_minus1 > 0 {
let slice_group_map_type = br.read_ue()?;
match slice_group_map_type {
0 => {
for _ in 0..=num_slice_groups_minus1 {
let _run_length_minus1 = br.read_ue()?;
}
}
2 => {
for _ in 0..num_slice_groups_minus1 {
let _top_left = br.read_ue()?;
let _bottom_right = br.read_ue()?;
}
}
3..=5 => {
let _slice_group_change_direction_flag = br.read_bits(1)?;
let _slice_group_change_rate_minus1 = br.read_ue()?;
}
6 => {
let pic_size_in_map_units_minus1 = br.read_ue()?;
let bits = ((num_slice_groups_minus1 + 1) as f64).log2().ceil() as usize;
let bits = bits.max(1);
for _ in 0..=pic_size_in_map_units_minus1 {
let _slice_group_id = br.read_bits(bits)?;
}
}
_ => {}
}
}
let num_ref_idx_l0_default_active_minus1 = br.read_ue()? as u8;
let num_ref_idx_l1_default_active_minus1 = br.read_ue()? as u8;
let weighted_pred_flag = br.read_bits(1)? == 1;
let weighted_bipred_idc = br.read_bits(2)? as u8;
let pic_init_qp_minus26 = clamp_to_i8(br.read_se()?);
let pic_init_qs_minus26 = clamp_to_i8(br.read_se()?);
let chroma_qp_index_offset = clamp_to_i8(br.read_se()?);
let deblocking_filter_control_present_flag = br.read_bits(1)? == 1;
let constrained_intra_pred_flag = br.read_bits(1)? == 1;
let redundant_pic_cnt_present_flag = br.read_bits(1)? == 1;
let (transform_8x8_mode_flag, pic_scaling_matrix_present_flag, second_chroma_qp_index_offset) =
if more_rbsp_data(&br, &rbsp) {
let t8 = br.read_bits(1).map(|v| v == 1);
let psm = br.read_bits(1).map(|v| v == 1);
if let Some(true) = psm {
let count = 6 + if let Some(true) = t8 { 2 } else { 0 };
for i in 0..count {
if br.read_bits(1) == Some(1) {
let size = if i < 6 { 16 } else { 64 };
let mut last_scale: i32 = 8;
let mut next_scale: i32 = 8;
for _ in 0..size {
if next_scale != 0 {
let delta = br.read_se().unwrap_or(0);
next_scale = (last_scale + delta + 256).rem_euclid(256);
}
if next_scale != 0 {
last_scale = next_scale;
}
}
}
}
}
let s2 = br.read_se().map(clamp_to_i8);
(t8, psm, s2)
} else {
(None, None, None)
};
Some(H264PpsInfo {
pic_parameter_set_id,
seq_parameter_set_id,
entropy_coding_mode_flag,
bottom_field_pic_order_in_frame_present_flag,
num_slice_groups_minus1: num_slice_groups_minus1.min(u8::MAX as u32) as u8,
num_ref_idx_l0_default_active_minus1,
num_ref_idx_l1_default_active_minus1,
weighted_pred_flag,
weighted_bipred_idc,
pic_init_qp_minus26,
pic_init_qs_minus26,
chroma_qp_index_offset,
deblocking_filter_control_present_flag,
constrained_intra_pred_flag,
redundant_pic_cnt_present_flag,
transform_8x8_mode_flag,
pic_scaling_matrix_present_flag,
second_chroma_qp_index_offset,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum H264SliceType {
P,
B,
I,
SP,
SI,
}
impl H264SliceType {
pub(super) fn from_ue(v: u32) -> Option<Self> {
match v % 5 {
0 => Some(Self::P),
1 => Some(Self::B),
2 => Some(Self::I),
3 => Some(Self::SP),
4 => Some(Self::SI),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct H264SliceHeader {
pub first_mb_in_slice: u32,
pub slice_type: H264SliceType,
pub pic_parameter_set_id: u8,
pub is_idr: bool,
pub frame_num: u32,
pub field_pic_flag: bool,
pub bottom_field_flag: bool,
pub colour_plane_id: Option<u8>,
pub idr_pic_id: Option<u32>,
pub pic_order_cnt_lsb: Option<u32>,
pub delta_pic_order_cnt_bottom: Option<i32>,
pub delta_pic_order_cnt: [Option<i32>; 2],
}
pub fn parse_h264_slice_header(
sample: &[u8],
sps: &H264SpsInfo,
pps: &H264PpsInfo,
) -> Option<H264SliceHeader> {
let (nal_type, rbsp) = find_h264_slice_nal(sample)?;
let is_idr = nal_type == 5;
let mut br = BitReader::new(&rbsp);
let first_mb_in_slice = br.read_ue()?;
let slice_type_code = br.read_ue()?;
let slice_type = H264SliceType::from_ue(slice_type_code)?;
let pic_parameter_set_id = br.read_ue()? as u8;
let colour_plane_id = if sps.separate_colour_plane_flag {
Some(br.read_bits(2)? as u8)
} else {
None
};
let frame_num_bits = (sps.log2_max_frame_num_minus4? as usize) + 4;
let frame_num = br.read_bits(frame_num_bits)?;
let (field_pic_flag, bottom_field_flag) = if !sps.frame_mbs_only {
let f = br.read_bits(1)? == 1;
let b = if f { br.read_bits(1)? == 1 } else { false };
(f, b)
} else {
(false, false)
};
let idr_pic_id = if is_idr { Some(br.read_ue()?) } else { None };
let poc_type = sps.pic_order_cnt_type?;
let mut pic_order_cnt_lsb = None;
let mut delta_pic_order_cnt_bottom = None;
let mut delta_pic_order_cnt: [Option<i32>; 2] = [None, None];
match poc_type {
0 => {
let bits = (sps.log2_max_pic_order_cnt_lsb_minus4? as usize) + 4;
pic_order_cnt_lsb = Some(br.read_bits(bits)?);
if pps.bottom_field_pic_order_in_frame_present_flag && !field_pic_flag {
delta_pic_order_cnt_bottom = Some(br.read_se()?);
}
}
1 => {
let always_zero = sps.delta_pic_order_always_zero_flag.unwrap_or(false);
if !always_zero {
delta_pic_order_cnt[0] = Some(br.read_se()?);
if pps.bottom_field_pic_order_in_frame_present_flag && !field_pic_flag {
delta_pic_order_cnt[1] = Some(br.read_se()?);
}
}
}
2 => { }
_ => return None,
}
Some(H264SliceHeader {
first_mb_in_slice,
slice_type,
pic_parameter_set_id,
is_idr,
frame_num,
field_pic_flag,
bottom_field_flag,
colour_plane_id,
idr_pic_id,
pic_order_cnt_lsb,
delta_pic_order_cnt_bottom,
delta_pic_order_cnt,
})
}
fn find_h264_slice_nal(data: &[u8]) -> Option<(u8, Vec<u8>)> {
let mut i = 0;
while i + 4 < data.len() {
let (start_len, nal_byte) = if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
(3, i + 3)
} else if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1 {
(4, i + 4)
} else {
i += 1;
continue;
};
if nal_byte >= data.len() {
return None;
}
let nal_unit_type = data[nal_byte] & 0x1F;
if matches!(nal_unit_type, 1 | 5 | 19) {
let start = nal_byte + 1;
let end = find_next_start_code(&data[start..])
.map(|off| start + off)
.unwrap_or(data.len());
let rbsp = remove_h264_rbsp_stuffing(&data[start..end]);
return Some((nal_unit_type, rbsp));
}
i += start_len;
}
None
}
fn find_h264_nal_by_type(data: &[u8], target_type: u8) -> Option<&[u8]> {
let mut i = 0;
while i + 4 < data.len() {
let (start_len, nal_byte) = if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 {
(3, i + 3)
} else if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1 {
(4, i + 4)
} else {
i += 1;
continue;
};
if nal_byte >= data.len() {
return None;
}
let nal_unit_type = data[nal_byte] & 0x1F;
if nal_unit_type == target_type {
let start = nal_byte + 1;
let end = find_next_start_code(&data[start..])
.map(|off| start + off)
.unwrap_or(data.len());
return Some(&data[start..end]);
}
i += start_len;
}
None
}