Files
effectcraft/crates/hevcenc/src/params.rs
T
JonRC 6d5e84e7a5 Export: H.264, HEVC and AV1 tag the frames' own colour (#2879)
Every MP4 export said BT.709 whatever the Output color space, in the
colr box and in the bitstream, so Rec. 2020, P3, PQ and HLG renders
were labelled as BT.709.

Move the ProRes colr mapping to output_codes and use it for every movie
path: the nclx colr box, FilmCraft's H.264 ColorConfig (VUI), and new
colour fields in effectcraft-hevcenc (VUI) and effectcraft-av1enc
(color_config), which wrote 1-1-1 as constants. The matrix stays BT.709,
the coefficients every path converts with. Default renders don't change.
2026-11-02 14:11:33 -03:00

266 lines
10 KiB
Rust

//! Parameter sets (7.3.2.1 VPS, 7.3.2.2 SPS with E.2.1 VUI, 7.3.2.3 PPS), profile_tier_level (7.3.3),
//! level selection (Annex A, Table A.8) and the `hvcC` record (ISO/IEC 14496-15 §8.3.3.1).
use crate::bitstream::{BitWriter, NAL_PPS, NAL_SPS, NAL_VPS, nal_unit};
/// Fixed coding structure of the encoder.
pub const LOG2_CTB: u32 = 6;
pub const LOG2_MIN_CB: u32 = 3;
pub const LOG2_MIN_TB: u32 = 2;
pub const LOG2_MAX_TB: u32 = 5;
pub const LOG2_MAX_POC_LSB: u32 = 8;
/// MaxNumMergeCand.
pub const MAX_MERGE_CAND: usize = 5;
/// Sequence-level settings shared by the parameter-set writers.
#[derive(Clone, Debug)]
pub struct SeqParams {
/// Coded (padded) size, multiples of the minimum CB size.
pub coded_width: u32,
pub coded_height: u32,
/// Output (cropped) size.
pub width: u32,
pub height: u32,
pub bit_depth: u32,
pub profile_idc: u8,
pub level_idc: u8,
pub full_range: bool,
pub colour_primaries: u8,
pub transfer_characteristics: u8,
pub matrix_coeffs: u8,
pub fps_num: u32,
pub fps_den: u32,
}
/// (level_idc, MaxLumaPs, MaxLumaSr, MaxBR main tier in kbit/s) of Table A.8 (and A.9 for luma rates).
const LEVELS: [(u8, u64, u64, u64); 13] = [
(30, 36_864, 552_960, 128),
(60, 122_880, 3_686_400, 1_500),
(63, 245_760, 7_372_800, 3_000),
(90, 552_960, 16_588_800, 6_000),
(93, 983_040, 33_177_600, 10_000),
(120, 2_228_224, 66_846_720, 12_000),
(123, 2_228_224, 133_693_440, 20_000),
(150, 8_912_896, 267_386_880, 25_000),
(153, 8_912_896, 534_773_760, 40_000),
(156, 8_912_896, 1_069_547_520, 60_000),
(180, 35_651_584, 1_069_547_520, 60_000),
(183, 35_651_584, 2_139_095_040, 120_000),
(186, 35_651_584, 4_278_190_080, 240_000),
];
/// Lowest level whose picture size, sample rate and (when known) bitrate limits fit.
pub fn choose_level(width: u32, height: u32, fps_num: u32, fps_den: u32, kbps: Option<u32>) -> u8 {
let ps = width as u64 * height as u64;
let sr = (ps as f64 * fps_num as f64 / fps_den.max(1) as f64).ceil() as u64;
for &(idc, max_ps, max_sr, max_br) in &LEVELS {
let max_dim = ((max_ps * 8) as f64).sqrt() as u64;
if ps <= max_ps && (width as u64) <= max_dim && (height as u64) <= max_dim && sr <= max_sr && kbps.is_none_or(|k| k as u64 <= max_br) {
return idc;
}
}
186
}
fn profile_tier_level(w: &mut BitWriter, p: &SeqParams) {
w.put(0, 2); // general_profile_space
w.put(0, 1); // general_tier_flag (Main tier)
w.put(p.profile_idc as u32, 5);
w.put(compat_flags(p.profile_idc), 32);
w.put(1, 1); // general_progressive_source_flag
w.put(0, 1); // general_interlaced_source_flag
w.put(0, 1); // general_non_packed_constraint_flag
w.put(1, 1); // general_frame_only_constraint_flag
w.put(0, 32); // general_reserved_zero_43bits ...
w.put(0, 11);
w.put(0, 1); // general_inbld_flag / reserved
w.put(p.level_idc as u32, 8);
// sps_max_sub_layers_minus1 == 0: no sub-layer flags
}
/// general_profile_compatibility_flag[j] as a 32-bit word (flag 0 is the MSB). A Main stream is also
/// Main 10 compatible.
fn compat_flags(profile_idc: u8) -> u32 {
let mut f = 1u32 << (31 - profile_idc as u32);
if profile_idc == 1 {
f |= 1 << (31 - 2);
}
f
}
pub fn vps(p: &SeqParams) -> Vec<u8> {
let mut w = BitWriter::new();
w.put(0, 4); // vps_video_parameter_set_id
w.put(1, 1); // vps_base_layer_internal_flag
w.put(1, 1); // vps_base_layer_available_flag
w.put(0, 6); // vps_max_layers_minus1
w.put(0, 3); // vps_max_sub_layers_minus1
w.put(1, 1); // vps_temporal_id_nesting_flag
w.put(0xffff, 16);
profile_tier_level(&mut w, p);
w.put(1, 1); // vps_sub_layer_ordering_info_present_flag
w.ue(1); // vps_max_dec_pic_buffering_minus1
w.ue(0); // vps_max_num_reorder_pics
w.ue(0); // vps_max_latency_increase_plus1
w.put(0, 6); // vps_max_layer_id
w.ue(0); // vps_num_layer_sets_minus1
w.put(0, 1); // vps_timing_info_present_flag
w.put(0, 1); // vps_extension_flag
w.trailing();
nal_unit(NAL_VPS, &w.into_bytes())
}
pub fn sps(p: &SeqParams) -> Vec<u8> {
let mut w = BitWriter::new();
w.put(0, 4); // sps_video_parameter_set_id
w.put(0, 3); // sps_max_sub_layers_minus1
w.put(1, 1); // sps_temporal_id_nesting_flag
profile_tier_level(&mut w, p);
w.ue(0); // sps_seq_parameter_set_id
w.ue(1); // chroma_format_idc 4:2:0
w.ue(p.coded_width);
w.ue(p.coded_height);
let crop = p.coded_width != p.width || p.coded_height != p.height;
w.flag(crop); // conformance_window_flag
if crop {
// offsets in chroma sample units (SubWidthC = SubHeightC = 2)
w.ue(0);
w.ue((p.coded_width - p.width) / 2);
w.ue(0);
w.ue((p.coded_height - p.height) / 2);
}
w.ue(p.bit_depth - 8); // bit_depth_luma_minus8
w.ue(p.bit_depth - 8); // bit_depth_chroma_minus8
w.ue(LOG2_MAX_POC_LSB - 4);
w.put(1, 1); // sps_sub_layer_ordering_info_present_flag
w.ue(1); // sps_max_dec_pic_buffering_minus1
w.ue(0); // sps_max_num_reorder_pics
w.ue(0); // sps_max_latency_increase_plus1
w.ue(LOG2_MIN_CB - 3);
w.ue(LOG2_CTB - LOG2_MIN_CB);
w.ue(LOG2_MIN_TB - 2);
w.ue(LOG2_MAX_TB - LOG2_MIN_TB);
w.ue(0); // max_transform_hierarchy_depth_inter
w.ue(0); // max_transform_hierarchy_depth_intra
w.put(0, 1); // scaling_list_enabled_flag
w.put(0, 1); // amp_enabled_flag
w.put(0, 1); // sample_adaptive_offset_enabled_flag
w.put(0, 1); // pcm_enabled_flag
w.ue(1); // num_short_term_ref_pic_sets
// st_ref_pic_set(0): one negative picture at delta POC -1, used by the current picture
w.ue(1); // num_negative_pics
w.ue(0); // num_positive_pics
w.ue(0); // delta_poc_s0_minus1
w.put(1, 1); // used_by_curr_pic_s0_flag
w.put(0, 1); // long_term_ref_pics_present_flag
w.put(0, 1); // sps_temporal_mvp_enabled_flag
w.put(1, 1); // strong_intra_smoothing_enabled_flag
w.put(1, 1); // vui_parameters_present_flag
vui(&mut w, p);
w.put(0, 1); // sps_extension_present_flag
w.trailing();
nal_unit(NAL_SPS, &w.into_bytes())
}
fn vui(w: &mut BitWriter, p: &SeqParams) {
w.put(0, 1); // aspect_ratio_info_present_flag
w.put(0, 1); // overscan_info_present_flag
w.put(1, 1); // video_signal_type_present_flag
w.put(5, 3); // video_format: unspecified
w.flag(p.full_range);
w.put(1, 1); // colour_description_present_flag
w.put(p.colour_primaries as u32, 8); // colour_primaries
w.put(p.transfer_characteristics as u32, 8); // transfer_characteristics
w.put(p.matrix_coeffs as u32, 8); // matrix_coeffs
w.put(0, 1); // chroma_loc_info_present_flag
w.put(0, 1); // neutral_chroma_indication_flag
w.put(0, 1); // field_seq_flag
w.put(0, 1); // frame_field_info_present_flag
w.put(0, 1); // default_display_window_flag
w.put(1, 1); // vui_timing_info_present_flag
w.put(p.fps_den, 32); // vui_num_units_in_tick
w.put(p.fps_num, 32); // vui_time_scale
w.put(0, 1); // vui_poc_proportional_to_timing_flag
w.put(0, 1); // vui_hrd_parameters_present_flag
w.put(0, 1); // bitstream_restriction_flag
}
pub fn pps() -> Vec<u8> {
let mut w = BitWriter::new();
w.ue(0); // pps_pic_parameter_set_id
w.ue(0); // pps_seq_parameter_set_id
w.put(0, 1); // dependent_slice_segments_enabled_flag
w.put(0, 1); // output_flag_present_flag
w.put(0, 3); // num_extra_slice_header_bits
w.put(1, 1); // sign_data_hiding_enabled_flag
w.put(0, 1); // cabac_init_present_flag
w.ue(0); // num_ref_idx_l0_default_active_minus1
w.ue(0); // num_ref_idx_l1_default_active_minus1
w.se(0); // init_qp_minus26
w.put(0, 1); // constrained_intra_pred_flag
w.put(0, 1); // transform_skip_enabled_flag
w.put(0, 1); // cu_qp_delta_enabled_flag
w.se(0); // pps_cb_qp_offset
w.se(0); // pps_cr_qp_offset
w.put(0, 1); // pps_slice_chroma_qp_offsets_present_flag
w.put(0, 1); // weighted_pred_flag
w.put(0, 1); // weighted_bipred_flag
w.put(0, 1); // transquant_bypass_enabled_flag
w.put(0, 1); // tiles_enabled_flag
w.put(0, 1); // entropy_coding_sync_enabled_flag
w.put(0, 1); // pps_loop_filter_across_slices_enabled_flag
w.put(1, 1); // deblocking_filter_control_present_flag
w.put(0, 1); // deblocking_filter_override_enabled_flag
w.put(0, 1); // pps_deblocking_filter_disabled_flag
w.se(0); // pps_beta_offset_div2
w.se(0); // pps_tc_offset_div2
w.put(0, 1); // pps_scaling_list_data_present_flag
w.put(0, 1); // lists_modification_present_flag
w.ue(0); // log2_parallel_merge_level_minus2
w.put(0, 1); // slice_segment_header_extension_present_flag
w.put(0, 1); // pps_extension_present_flag
w.trailing();
nal_unit(NAL_PPS, &w.into_bytes())
}
/// HEVCDecoderConfigurationRecord with one VPS, SPS and PPS (lengthSizeMinusOne = 3).
pub fn hvcc(p: &SeqParams, sets: &[Vec<u8>; 3]) -> Vec<u8> {
let mut o = vec![1u8]; // configurationVersion
o.push(p.profile_idc); // profile_space 0, tier 0, profile_idc
o.extend_from_slice(&compat_flags(p.profile_idc).to_be_bytes());
// general_constraint_indicator_flags (48 bits): progressive, interlaced, non_packed, frame_only
o.extend_from_slice(&[0x90, 0, 0, 0, 0, 0]);
o.push(p.level_idc);
o.extend_from_slice(&[0xf0, 0x00]); // reserved + min_spatial_segmentation_idc 0
o.push(0xfc); // reserved + parallelismType 0
o.push(0xfc | 1); // reserved + chroma_format_idc 1
o.push(0xf8 | (p.bit_depth - 8) as u8);
o.push(0xf8 | (p.bit_depth - 8) as u8);
o.extend_from_slice(&[0, 0]); // avgFrameRate unspecified
// constantFrameRate 0, numTemporalLayers 1, temporalIdNested 1, lengthSizeMinusOne 3
o.push((1 << 3) | (1 << 2) | 3);
o.push(3); // numOfArrays
for (set, ty) in sets.iter().zip([NAL_VPS, NAL_SPS, NAL_PPS]) {
o.push(0x80 | ty); // array_completeness 1
o.extend_from_slice(&1u16.to_be_bytes());
o.extend_from_slice(&(set.len() as u16).to_be_bytes());
o.extend_from_slice(set);
}
o
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn levels() {
assert_eq!(choose_level(1920, 1080, 30, 1, None), 120);
assert_eq!(choose_level(1920, 1080, 60, 1, None), 123);
assert_eq!(choose_level(3840, 2160, 30, 1, None), 150);
assert_eq!(choose_level(1280, 720, 30, 1, None), 93);
assert_eq!(choose_level(64, 64, 30, 1, None), 30);
assert_eq!(choose_level(1920, 1080, 30, 1, Some(15_000)), 123);
}
}