#![allow(dead_code)]
use crate::bitstream::{BitError, BitReader};
use crate::slice_header::{DecRefPicMarking, MmcoOp};
use crate::vui::{HrdParameters, VuiError};
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum SeiError {
#[error("bitstream read failed: {0}")]
Bitstream(#[from] BitError),
#[error("hrd_parameters() parse failed: {0}")]
Hrd(#[from] VuiError),
#[error("SEI payload type {0} is not implemented — caller should keep the raw bytes")]
UnsupportedPayloadType(u32),
#[error("user_data_unregistered payload must be at least 16 bytes for the UUID (got {0})")]
UserDataTooShort(usize),
#[error("filler_payload contains a byte that is not 0xFF at position {0}")]
FillerNonFfAt(usize),
#[error("user_data_registered_itu_t_t35 payload must contain at least the country code byte")]
RegisteredUserDataMissingCountryCode,
#[error("pan_scan_rect_cnt_minus1 must be < 32 per §D.2.4 (got {0})")]
PanScanCountTooLarge(u32),
#[error(
"film_grain num_model_values_minus1 must be in 0..=5 per §D.2.21 (got {got} for component {component})"
)]
FilmGrainNumModelValuesOutOfRange { component: usize, got: u8 },
#[error(
"post_filter_hint filter_hint_size_x / filter_hint_size_y must be in 1..=15 per §D.2.24 (got x={x}, y={y})"
)]
PostFilterHintSizeOutOfRange { x: u32, y: u32 },
#[error("post_filter_hint filter_hint_type 3 is reserved per §D.2.24")]
PostFilterHintTypeReserved,
#[error(
"post_filter_hint filter_hint_type 1 (two 1D FIRs) requires filter_hint_size_y == 2 per §D.2.24 (got {0})"
)]
PostFilterHintType1WrongHeight(u32),
#[error("ambient_viewing_environment ambient_illuminance shall not be 0 per §D.2.34")]
AmbientIlluminanceZero,
#[error(
"ambient_viewing_environment ambient_light_x / ambient_light_y shall be in 0..=50000 per §D.2.34 (got x={x}, y={y})"
)]
AmbientLightOutOfRange { x: u32, y: u32 },
#[error(
"sphere_rotation yaw_rotation shall be in [-180*2^16, 180*2^16 - 1] per §D.2.35.3 (got {0})"
)]
SphereRotationYawOutOfRange(i32),
#[error(
"sphere_rotation pitch_rotation shall be in [-90*2^16, 90*2^16] per §D.2.35.3 (got {0})"
)]
SphereRotationPitchOutOfRange(i32),
#[error(
"sphere_rotation roll_rotation shall be in [-180*2^16, 180*2^16 - 1] per §D.2.35.3 (got {0})"
)]
SphereRotationRollOutOfRange(i32),
#[error(
"omni_viewport azimuth_centre shall be in [-180*2^16, 180*2^16 - 1] per §D.2.35.5 (got {0})"
)]
OmniViewportAzimuthOutOfRange(i32),
#[error(
"omni_viewport elevation_centre shall be in [-90*2^16, 90*2^16] per §D.2.35.5 (got {0})"
)]
OmniViewportElevationOutOfRange(i32),
#[error(
"omni_viewport tilt_centre shall be in [-180*2^16, 180*2^16 - 1] per §D.2.35.5 (got {0})"
)]
OmniViewportTiltOutOfRange(i32),
#[error("omni_viewport hor_range shall be in 1..=360*2^16 per §D.2.35.5 (got {0})")]
OmniViewportHorRangeOutOfRange(u32),
#[error("omni_viewport ver_range shall be in 1..=180*2^16 per §D.2.35.5 (got {0})")]
OmniViewportVerRangeOutOfRange(u32),
#[error("shutter_interval_info sii_time_scale shall not be 0 per §D.2.38")]
ShutterIntervalTimeScaleZero,
#[error("sub-sequence sub_seq_layer_num shall be less than 256 (got {0})")]
SubSeqLayerNumTooLarge(u32),
#[error("sub-sequence sub_seq_id shall be less than 65536 (got {0})")]
SubSeqIdTooLarge(u32),
#[error("sub_seq_layer_characteristics num_sub_seq_layers_minus1 shall be <= 255 (got {0})")]
SubSeqLayersTooLarge(u32),
#[error("sub_seq_characteristics num_referenced_subseqs shall be less than 256 (got {0})")]
SubSeqReferencedTooLarge(u32),
#[error("spare_pic num_spare_pics_minus1 shall be in 0..=15 per §D.2.10 (got {0})")]
SparePicCountTooLarge(u32),
#[error(
"motion_constrained_slice_group_set num_slice_groups_in_set_minus1 shall be in 0..=num_slice_groups_minus1 per §D.2.20 (got count {count}, num_slice_groups {num_slice_groups})"
)]
MotionConstrainedSliceGroupCountTooLarge { count: u32, num_slice_groups: u32 },
#[error(
"motion_constrained_slice_group_set slice_group_id[{i}] shall be in 0..=num_slice_groups_minus1 per §D.2.20 (got {id}, num_slice_groups {num_slice_groups})"
)]
MotionConstrainedSliceGroupIdOutOfRange {
i: usize,
id: u32,
num_slice_groups: u32,
},
#[error(
"spare_pic spare_area_idc shall be in 0..=2 per §D.2.10 (got {idc} for spare picture {i})"
)]
SparePicAreaIdcOutOfRange { i: usize, idc: u32 },
#[error(
"spare_pic spare_area_idc {idc} (spare picture {i}) needs PicSizeInMapUnits from the active SPS, but the SeiContext carried 0 — wire pic_size_in_map_units in"
)]
SparePicMapUnitsUnknown { i: usize, idc: u32 },
#[error(
"content_colour_volume requires at least one of the four present flags (primaries / min / max / avg) to be set per §D.2.33"
)]
ContentColourVolumeNoComponents,
#[error(
"content_colour_volume ccv_primaries_x/y[{c}] shall be in [-5000000, 5000000] per §D.2.33 (got x={x}, y={y})"
)]
ContentColourVolumePrimaryOutOfRange { c: usize, x: i32, y: i32 },
#[error(
"content_colour_volume luminance ordering violated per §D.2.33: ccv_min_luminance_value ({min}) <= ccv_avg_luminance_value ({avg}) <= ccv_max_luminance_value ({max}) must hold for whichever values are present"
)]
ContentColourVolumeLuminanceOrder { min: u64, avg: u64, max: u64 },
#[error(
"dec_ref_pic_marking_repetition memory_management_control_operation shall be in 0..=6 per §7.4.3.3 / Table 7-9 (got {0})"
)]
DecRefPicMarkingRepetitionMmcoOutOfRange(u32),
#[error("regionwise_packing num_packed_regions shall be greater than 0 per §D.2.35.4")]
RegionWisePackingNoRegions,
#[error(
"regionwise_packing proj_picture_width / proj_picture_height shall both be greater than 0 per §D.2.35.4 (got w={w}, h={h})"
)]
RegionWisePackingProjPictureZero { w: u32, h: u32 },
#[error(
"regionwise_packing packed_picture_width / packed_picture_height shall both be greater than 0 per §D.2.35.4 (got w={w}, h={h})"
)]
RegionWisePackingPackedPictureZero { w: u32, h: u32 },
#[error(
"regionwise_packing packed region {0} sets guard_band_flag but all four guard-band dimensions are 0 — at least one shall be greater than 0 per §D.2.35.4"
)]
RegionWisePackingGuardBandAllZero(usize),
#[error(
"colour_remapping_info colour_remap_repetition_period shall be in 0..=16384 per §D.2.30 (got {0})"
)]
ColourRemapRepetitionPeriodOutOfRange(u32),
#[error(
"colour_remapping_info colour_remap_{which}_bit_depth shall be in 8..=16 per §D.2.30 (got {got})"
)]
ColourRemapBitDepthOutOfRange { which: &'static str, got: u8 },
#[error(
"colour_remapping_info {which}_lut_num_val_minus1[{c}] shall be in 0..=32 per §D.2.30 (got {got})"
)]
ColourRemapLutCountOutOfRange {
which: &'static str,
c: usize,
got: u8,
},
#[error(
"colour_remapping_info colour_remap_coeffs shall be in [-32768, 32767] per §D.2.30 (got {0})"
)]
ColourRemapCoeffOutOfRange(i32),
#[error(
"sei_manifest manifest_sei_payload_type values shall be unique per §D.2.36 (got duplicate {payload_type} at indices {first} and {second})"
)]
SeiManifestDuplicatePayloadType {
payload_type: u16,
first: usize,
second: usize,
},
#[error(
"sei_prefix_indication num_bits_in_prefix_indication_minus1[{i}] plus 1 = {bits} bits exceeds the remaining payload (only {available} bits left) per §D.1.37"
)]
SeiPrefixIndicationOverflow {
i: usize,
bits: u32,
available: usize,
},
#[error(
"ATSC1 envelope requires at least 6 bytes (2 provider_code + 4 user_identifier) per ATSC A/53 Part 4 §6.2.3 (got {0})"
)]
Atsc1EnvelopeTooShort(usize),
#[error(
"ATSC1 envelope provider_code shall be 0x0031 (ATSC) per ATSC A/53 Part 4 §6.2.3 / SMPTE-RA T.35 registration (got 0x{0:04X})"
)]
Atsc1ProviderCodeMismatch(u16),
#[error(
"ATSC1 envelope user_identifier 0x{0:08X} is not a registered ATSC value per ATSC A/53 Part 4 §6.2.3 Table 6.7 (expected 'GA94' 0x47413934 or 'DTG1' 0x44544731)"
)]
Atsc1UnknownUserIdentifier(u32),
#[error(
"ATSC1 ATSC_user_data() requires at least 1 byte for user_data_type_code per A/53 Part 4 §6.2.3 Table 6.8 (got empty payload)"
)]
Atsc1AtscUserDataEmpty,
#[error(
"ATSC1 bar_data reserved field must be '1111' per A/53 Part 4 Table 6.11 (got 0b{0:04b})"
)]
Atsc1BarDataReservedMismatch(u8),
#[error(
"ATSC1 bar_data {which}_bar one_bits field must be '11' per A/53 Part 4 Table 6.11 (got 0b{got:02b})"
)]
Atsc1BarDataOneBitsMismatch { which: &'static str, got: u8 },
#[error(
"ATSC1 bar_data top_bar_flag and bottom_bar_flag must match per A/53 Part 4 §6.2.3.2 (top={top}, bottom={bottom})"
)]
Atsc1BarDataTopBottomMismatch { top: bool, bottom: bool },
#[error(
"ATSC1 bar_data left_bar_flag and right_bar_flag must match per A/53 Part 4 §6.2.3.2 (left={left}, right={right})"
)]
Atsc1BarDataLeftRightMismatch { left: bool, right: bool },
#[error(
"ATSC1 bar_data may not signal both letterbox (top/bottom) and pillarbox (left/right) at once per A/53 Part 4 §6.2.3.2"
)]
Atsc1BarDataLetterboxPillarboxBoth,
#[error("ATSC1 bar_data payload truncated — needed {needed} bytes, got {got}")]
Atsc1BarDataTruncated { needed: usize, got: usize },
#[error(
"multiview_view_position num_views_minus1 shall be in 0..=1023 per Annex G §G.13.2.10 (got {0})"
)]
MultiviewViewPositionNumViewsOutOfRange(u32),
#[error(
"multiview_view_position view_position[{i}] shall be in 0..=1023 per Annex G §G.13.2.10 (got {got})"
)]
MultiviewViewPositionViewPositionOutOfRange { i: usize, got: u32 },
#[error(
"multiview_scene_info max_disparity shall be in 0..=1023 per Annex G §G.13.2.4 (got {0})"
)]
MultiviewSceneInfoMaxDisparityOutOfRange(u32),
#[error(
"operation_point_not_present num_operation_points shall be in 0..=65536 per Annex G §G.13.2.8 (got {0})"
)]
OperationPointNotPresentCountOutOfRange(u32),
#[error(
"operation_point_not_present operation_point_not_present_id[{i}] shall be in 0..=65535 per Annex G §G.13.2.8 (got {got})"
)]
OperationPointNotPresentIdOutOfRange { i: usize, got: u32 },
#[error(
"base_view_temporal_hrd num_of_temporal_layers_in_base_view_minus1 shall be in 0..=7 per Annex G §G.13.2.9 (got {0})"
)]
BaseViewTemporalHrdLayerCountOutOfRange(u32),
#[error(
"non_required_view_component num_info_entries_minus1 shall be in 0..=1022 per Annex G §G.13.2.6 (num_views_minus1 - 1, with num_views_minus1 bounded at 1023) (got {0})"
)]
NonRequiredViewComponentNumInfoEntriesOutOfRange(u32),
#[error(
"non_required_view_component view_order_index[{i}] shall be in 1..=1023 per Annex G §G.13.2.6 (got {got})"
)]
NonRequiredViewComponentViewOrderIndexOutOfRange { i: usize, got: u32 },
#[error(
"non_required_view_component num_non_required_view_components_minus1[{i}] shall be in 0..=view_order_index[i]-1 per Annex G §G.13.2.6 (got {got}, view_order_index={view_order_index})"
)]
NonRequiredViewComponentCountOutOfRange {
i: usize,
got: u32,
view_order_index: u32,
},
#[error(
"non_required_view_component index_delta_minus1[{i}][{j}] shall be in 0..=view_order_index[i]-1 per Annex G §G.13.2.6 (got {got}, view_order_index={view_order_index})"
)]
NonRequiredViewComponentIndexDeltaOutOfRange {
i: usize,
j: usize,
got: u32,
view_order_index: u32,
},
#[error(
"multiview_acquisition_info num_views_minus1 shall be in 0..=1023 per Annex G §G.13.2.5 (got {0})"
)]
MultiviewAcquisitionInfoNumViewsOutOfRange(u32),
#[error(
"multiview_acquisition_info {field} shall be in 0..=31 per Annex G §G.13.2.5 (got {got})"
)]
MultiviewAcquisitionInfoPrecOutOfRange { field: &'static str, got: u32 },
#[error(
"three_dimensional_reference_displays_info {field} shall be in 0..=31 per Annex H §H.13.2.4 (got {got})"
)]
ThreeDimensionalReferenceDisplaysInfoPrecOutOfRange { field: &'static str, got: u32 },
#[error(
"three_dimensional_reference_displays_info num_ref_displays_minus1 shall be in 0..=31 per Annex H §H.13.2.4 (got {0})"
)]
ThreeDimensionalReferenceDisplaysInfoNumRefDisplaysOutOfRange(u32),
#[error(
"depth_representation_info num_views_minus1 shall be in 0..=1023 per Annex H §H.13.2.3 (got {0})"
)]
DepthRepresentationInfoNumViewsOutOfRange(u32),
#[error(
"depth_representation_info {field} shall be in 0..=1023 per Annex H §H.13.2.3 (got {got})"
)]
DepthRepresentationInfoViewIdOutOfRange { field: &'static str, got: u32 },
#[error(
"depth_representation_info depth_nonlinear_representation_num_minus1 shall be in 0..=62 per Annex H §H.13.2.3 (got {0})"
)]
DepthRepresentationInfoNonlinearNumOutOfRange(u32),
#[error(
"depth_representation_info depth_nonlinear_representation_model[{i}] shall be in 0..=65535 per Annex H §H.13.2.3 (got {got})"
)]
DepthRepresentationInfoNonlinearModelOutOfRange { i: usize, got: u32 },
#[error(
"constrained_depth_parameter_set_identifier max_dps_id shall be in 0..=62 per Annex I §I.13.2.1 (depth_parameter_set_id range 1..=63 caps max_dps_id + 1 ≤ 63) (got {0})"
)]
ConstrainedDepthParameterSetIdentifierMaxDpsIdOutOfRange(u32),
#[error(
"constrained_depth_parameter_set_identifier max_dps_id_diff * 2 shall be less than max_dps_id per Annex I §I.13.2.1 (got max_dps_id_diff = {max_dps_id_diff}, max_dps_id = {max_dps_id})"
)]
ConstrainedDepthParameterSetIdentifierDiffViolatesBound {
max_dps_id_diff: u32,
max_dps_id: u32,
},
#[error("depth_sampling_info dttsr_{axis}_mul = 0 is reserved per Annex H §H.13.2.7")]
DepthSamplingInfoDttsrMulReserved { axis: &'static str },
#[error(
"depth_sampling_info num_video_plus_depth_views_minus1 shall be in 0..=1023 per Annex H §H.13.2.7 (num_views_minus1 absolute upper bound from Annex G/H) (got {0})"
)]
DepthSamplingInfoNumViewsOutOfRange(u32),
#[error(
"depth_sampling_info depth_grid_view_id[{i}] shall be in 0..=1023 per Annex H §H.13.2.7 (view_id range) (got {got})"
)]
DepthSamplingInfoViewIdOutOfRange { i: usize, got: u32 },
#[error(
"depth_timing per_view_depth_timing_flag = 1 needs NumDepthViews from the active subset SPS MVCD extension (§H.7.3.2.1.5) to bound the per-view loop per Annex H §H.13.1.5, but SeiContext.num_depth_views is 0 (unknown)"
)]
DepthTimingNumDepthViewsUnknown,
#[error(
"depth_timing NumDepthViews shall be in 1..=1024 per Annex H (§H.7.3.2.1.5 num_views_minus1 ≤ 1023 caps the depth-view count) (got {0})"
)]
DepthTimingNumDepthViewsOutOfRange(u32),
#[error(
"alternative_depth_info num_constituent_views_gvd_minus1 shall be in 0..=3 per Annex H §H.13.2.6 (got {0})"
)]
AlternativeDepthInfoNumConstituentViewsOutOfRange(u32),
#[error("alternative_depth_info {field} shall be in 0..=31 per Annex H §H.13.2.6 (got {got})")]
AlternativeDepthInfoPrecOutOfRange { field: &'static str, got: u32 },
#[error(
"view_dependency_change with anchor_update_flag / non_anchor_update_flag set needs the per-view inter-view reference counts from the active MVC subset SPS (§G.7.3.2.1.4) to bound the anchor_ref / non_anchor_ref flag loops per Annex G §G.13.1.7, but SeiContext.mvc_view_ref_counts is empty (unknown)"
)]
ViewDependencyChangeRefCountsUnknown,
#[error(
"view_dependency_change seq_parameter_set_id shall be in 0..=31 per Annex G §G.13.2.7 (got {0})"
)]
ViewDependencyChangeSpsIdOutOfRange(u32),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BufferingPeriod {
pub seq_parameter_set_id: u32,
pub nal_hrd: Option<Vec<CpbRemovalDelay>>, pub vcl_hrd: Option<Vec<CpbRemovalDelay>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CpbRemovalDelay {
pub initial_cpb_removal_delay: u32,
pub initial_cpb_removal_delay_offset: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PicTiming {
pub cpb_removal_delay: u32,
pub dpb_output_delay: u32,
pub pic_struct: Option<PicStructInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PicStructInfo {
pub pic_struct: u8, pub clock_timestamps: Vec<Option<ClockTimestamp>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClockTimestamp {
pub ct_type: u8,
pub nuit_field_based_flag: bool,
pub counting_type: u8,
pub full_timestamp_flag: bool,
pub discontinuity_flag: bool,
pub cnt_dropped_flag: bool,
pub n_frames: u8,
pub seconds: u8,
pub minutes: u8,
pub hours: u8,
pub time_offset: i32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UserDataUnregistered {
pub uuid: [u8; 16],
pub user_data: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UserDataRegisteredItuTT35 {
pub country_code: u8,
pub country_code_extension: Option<u8>,
pub payload_bytes: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PanScanRect {
pub pan_scan_rect_id: u32,
pub cancel_flag: bool,
pub body: Option<PanScanRectBody>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PanScanRectBody {
pub rects: Vec<PanScanRectOffsets>,
pub repetition_period: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PanScanRectOffsets {
pub left_offset: i32,
pub right_offset: i32,
pub top_offset: i32,
pub bottom_offset: i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RecoveryPoint {
pub recovery_frame_cnt: u32,
pub exact_match_flag: bool,
pub broken_link_flag: bool,
pub changing_slice_group_idc: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FullFrameFreeze {
pub full_frame_freeze_repetition_period: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FullFrameSnapshot {
pub snapshot_id: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DeblockingFilterDisplayPreference {
pub cancel_flag: bool,
pub display_prior_to_deblocking_preferred_flag: bool,
pub dec_frame_buffering_constraint_flag: bool,
pub repetition_period: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MasteringDisplayColourVolume {
pub display_primaries: [(u16, u16); 3],
pub white_point: (u16, u16),
pub max_display_mastering_luminance: u32,
pub min_display_mastering_luminance: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ContentLightLevelInfo {
pub max_content_light_level: u16,
pub max_pic_average_light_level: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FramePackingArrangement {
pub id: u32,
pub cancel_flag: bool,
pub ty: u8, pub quincunx_sampling_flag: bool,
pub content_interpretation_type: u8,
pub spatial_flipping_flag: bool,
pub frame0_flipped_flag: bool,
pub field_views_flag: bool,
pub current_frame_is_frame0_flag: bool,
pub frame0_self_contained_flag: bool,
pub frame1_self_contained_flag: bool,
pub frame0_grid_position_x: u8,
pub frame0_grid_position_y: u8,
pub frame1_grid_position_x: u8,
pub frame1_grid_position_y: u8,
pub reserved_byte: u8,
pub repetition_period: u32,
pub extension_flag: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DisplayOrientation {
pub cancel_flag: bool,
pub hor_flip: bool,
pub ver_flip: bool,
pub anticlockwise_rotation: u16,
pub repetition_period: u32,
pub extension_flag: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PostFilterHint {
pub filter_hint_size_x: u32,
pub filter_hint_size_y: u32,
pub filter_hint_type: u8,
pub filter_hint_value: Vec<i32>,
pub additional_extension_flag: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToneMappingInfo {
pub tone_map_id: u32,
pub cancel_flag: bool,
pub body: Option<ToneMappingBody>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToneMappingBody {
pub repetition_period: u32,
pub coded_data_bit_depth: u8,
pub target_bit_depth: u8,
pub model_id: u32,
pub model: ToneMappingModel,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ToneMappingModel {
Linear { min_value: u32, max_value: u32 },
Sigmoid {
sigmoid_midpoint: u32,
sigmoid_width: u32,
},
StartOfCodedInterval { start_of_coded_interval: Vec<u32> },
PiecewisePivots {
num_pivots: u16,
coded_pivot_value: Vec<u32>,
target_pivot_value: Vec<u32>,
},
Reserved {
model_id: u32,
remaining_bytes: Vec<u8>,
},
}
impl ToneMappingBody {
#[must_use]
pub fn piecewise_default_end_points(&self) -> Option<((u32, u32), (u32, u32))> {
if self.model_id != 3 {
return None;
}
let coded_end = 1u32
.checked_shl(u32::from(self.coded_data_bit_depth))
.map_or(u32::MAX, |v| v - 1);
let target_end = 1u32
.checked_shl(u32::from(self.target_bit_depth))
.map_or(u32::MAX, |v| v - 1);
Some(((0, 0), (coded_end, target_end)))
}
#[must_use]
pub fn piecewise_total_pivot_count(&self) -> Option<u32> {
match &self.model {
ToneMappingModel::PiecewisePivots { num_pivots, .. } => {
Some(u32::from(*num_pivots) + 2)
}
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FilmGrainCharacteristics {
pub cancel_flag: bool,
pub body: Option<FilmGrainBody>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FilmGrainBody {
pub model_id: u8, pub separate_colour_description_present_flag: bool,
pub separate_colour_description: Option<FilmGrainSeparateColourDescription>,
pub blending_mode_id: u8, pub log2_scale_factor: u8, pub comp_model_present_flag: [bool; 3],
pub components: [Option<FilmGrainComponent>; 3],
pub repetition_period: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FilmGrainComponent {
pub num_model_values_minus1: u8,
pub intensity_intervals: Vec<FilmGrainIntensityInterval>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FilmGrainIntensityInterval {
pub lower_bound: u8,
pub upper_bound: u8,
pub model_values: Vec<i32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FilmGrainSeparateColourDescription {
pub bit_depth_luma_minus8: u8, pub bit_depth_chroma_minus8: u8, pub full_range_flag: bool,
pub colour_primaries: u8,
pub transfer_characteristics: u8,
pub matrix_coefficients: u8,
}
impl FilmGrainSeparateColourDescription {
#[must_use]
pub fn bit_depth_luma(&self) -> u8 {
self.bit_depth_luma_minus8 + 8
}
#[must_use]
pub fn bit_depth_chroma(&self) -> u8 {
self.bit_depth_chroma_minus8 + 8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct MvcViewRefCounts {
pub num_anchor_refs_l0: u8,
pub num_anchor_refs_l1: u8,
pub num_non_anchor_refs_l0: u8,
pub num_non_anchor_refs_l1: u8,
}
#[derive(Debug, Clone)]
pub struct SeiContext {
pub initial_cpb_removal_delay_length_minus1: u8,
pub cpb_removal_delay_length_minus1: u8,
pub dpb_output_delay_length_minus1: u8,
pub time_offset_length: u8,
pub nal_hrd_cpb_cnt_minus1: Option<u32>,
pub vcl_hrd_cpb_cnt_minus1: Option<u32>,
pub pic_struct_present_flag: bool,
pub cpb_dpb_delays_present_flag: bool,
pub num_slice_groups: u32,
pub pic_size_in_map_units: u32,
pub frame_mbs_only_flag: bool,
pub num_depth_views: u32,
pub mvc_view_ref_counts: Vec<MvcViewRefCounts>,
}
impl Default for SeiContext {
fn default() -> Self {
Self {
initial_cpb_removal_delay_length_minus1: 0,
cpb_removal_delay_length_minus1: 0,
dpb_output_delay_length_minus1: 0,
time_offset_length: 0,
nal_hrd_cpb_cnt_minus1: None,
vcl_hrd_cpb_cnt_minus1: None,
pic_struct_present_flag: false,
cpb_dpb_delays_present_flag: false,
num_slice_groups: 1,
pic_size_in_map_units: 0,
frame_mbs_only_flag: true,
num_depth_views: 0,
mvc_view_ref_counts: Vec::new(),
}
}
}
pub fn parse_buffering_period(
payload: &[u8],
ctx: &SeiContext,
) -> Result<BufferingPeriod, SeiError> {
let mut r = BitReader::new(payload);
let seq_parameter_set_id = r.ue()?;
let delay_bits = ctx.initial_cpb_removal_delay_length_minus1 as u32 + 1;
let nal_hrd = if let Some(cnt_minus1) = ctx.nal_hrd_cpb_cnt_minus1 {
let count = (cnt_minus1 as usize).saturating_add(1);
let mut out = Vec::with_capacity(count);
for _ in 0..count {
let d = r.u(delay_bits)?;
let o = r.u(delay_bits)?;
out.push(CpbRemovalDelay {
initial_cpb_removal_delay: d,
initial_cpb_removal_delay_offset: o,
});
}
Some(out)
} else {
None
};
let vcl_hrd = if let Some(cnt_minus1) = ctx.vcl_hrd_cpb_cnt_minus1 {
let count = (cnt_minus1 as usize).saturating_add(1);
let mut out = Vec::with_capacity(count);
for _ in 0..count {
let d = r.u(delay_bits)?;
let o = r.u(delay_bits)?;
out.push(CpbRemovalDelay {
initial_cpb_removal_delay: d,
initial_cpb_removal_delay_offset: o,
});
}
Some(out)
} else {
None
};
Ok(BufferingPeriod {
seq_parameter_set_id,
nal_hrd,
vcl_hrd,
})
}
pub fn parse_pic_timing(payload: &[u8], ctx: &SeiContext) -> Result<PicTiming, SeiError> {
let mut r = BitReader::new(payload);
let (cpb_removal_delay, dpb_output_delay) = if ctx.cpb_dpb_delays_present_flag {
let cpb_bits = ctx.cpb_removal_delay_length_minus1 as u32 + 1;
let dpb_bits = ctx.dpb_output_delay_length_minus1 as u32 + 1;
(r.u(cpb_bits)?, r.u(dpb_bits)?)
} else {
(0, 0)
};
let pic_struct_info = if ctx.pic_struct_present_flag {
let pic_struct_val = r.u(4)? as u8;
let num_clock_ts = num_clock_ts_from_pic_struct(pic_struct_val);
let mut ts_slots: Vec<Option<ClockTimestamp>> = Vec::with_capacity(num_clock_ts as usize);
for _ in 0..num_clock_ts {
let clock_timestamp_flag = r.u(1)? == 1;
if !clock_timestamp_flag {
ts_slots.push(None);
continue;
}
let ct_type = r.u(2)? as u8;
let nuit_field_based_flag = r.u(1)? == 1;
let counting_type = r.u(5)? as u8;
let full_timestamp_flag = r.u(1)? == 1;
let discontinuity_flag = r.u(1)? == 1;
let cnt_dropped_flag = r.u(1)? == 1;
let n_frames = r.u(8)? as u8;
let (seconds, minutes, hours) = if full_timestamp_flag {
let s = r.u(6)? as u8;
let m = r.u(6)? as u8;
let h = r.u(5)? as u8;
(s, m, h)
} else {
let seconds_flag = r.u(1)? == 1;
let (mut s, mut m, mut h) = (0u8, 0u8, 0u8);
if seconds_flag {
s = r.u(6)? as u8;
let minutes_flag = r.u(1)? == 1;
if minutes_flag {
m = r.u(6)? as u8;
let hours_flag = r.u(1)? == 1;
if hours_flag {
h = r.u(5)? as u8;
}
}
}
(s, m, h)
};
let time_offset = if ctx.time_offset_length > 0 {
r.i(ctx.time_offset_length as u32)?
} else {
0
};
ts_slots.push(Some(ClockTimestamp {
ct_type,
nuit_field_based_flag,
counting_type,
full_timestamp_flag,
discontinuity_flag,
cnt_dropped_flag,
n_frames,
seconds,
minutes,
hours,
time_offset,
}));
}
Some(PicStructInfo {
pic_struct: pic_struct_val,
clock_timestamps: ts_slots,
})
} else {
None
};
Ok(PicTiming {
cpb_removal_delay,
dpb_output_delay,
pic_struct: pic_struct_info,
})
}
fn num_clock_ts_from_pic_struct(pic_struct: u8) -> u8 {
match pic_struct {
0..=2 => 1,
3 | 4 | 7 => 2,
5 | 6 | 8 => 3,
_ => 0,
}
}
pub fn parse_filler_payload(payload: &[u8]) -> Result<(), SeiError> {
for (i, &b) in payload.iter().enumerate() {
if b != 0xFF {
return Err(SeiError::FillerNonFfAt(i));
}
}
Ok(())
}
pub fn parse_user_data_registered_itu_t_t35(
payload: &[u8],
) -> Result<UserDataRegisteredItuTT35, SeiError> {
let Some((&country_code, rest)) = payload.split_first() else {
return Err(SeiError::RegisteredUserDataMissingCountryCode);
};
let (country_code_extension, payload_bytes) = if country_code == 0xFF {
match rest.split_first() {
Some((&ext, body)) => (Some(ext), body.to_vec()),
None => return Err(SeiError::RegisteredUserDataMissingCountryCode),
}
} else {
(None, rest.to_vec())
};
Ok(UserDataRegisteredItuTT35 {
country_code,
country_code_extension,
payload_bytes,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Atsc1UserIdentifier {
Ga94,
Dtg1,
}
impl Atsc1UserIdentifier {
pub const GA94: u32 = 0x4741_3934;
pub const DTG1: u32 = 0x4454_4731;
fn from_u32(v: u32) -> Option<Self> {
match v {
Self::GA94 => Some(Self::Ga94),
Self::DTG1 => Some(Self::Dtg1),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Atsc1UserData {
CcData { cc_data_bytes: Vec<u8> },
BarData(BarData),
Reserved { type_code: u8, raw: Vec<u8> },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AfdData {
pub active_format_flag: bool,
pub active_format: Option<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BarData {
pub letterbox: Option<(u16, u16)>,
pub pillarbox: Option<(u16, u16)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Atsc1Envelope {
pub provider_code: u16,
pub user_identifier: Atsc1UserIdentifier,
pub body: Atsc1Body,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Atsc1Body {
AtscUserData(Atsc1UserData),
Afd(AfdData),
}
fn parse_afd_data(bytes: &[u8]) -> Result<AfdData, SeiError> {
let Some(&first) = bytes.first() else {
return Err(SeiError::Atsc1BarDataTruncated { needed: 1, got: 0 });
};
let active_format_flag = (first & 0b0100_0000) != 0;
if !active_format_flag {
return Ok(AfdData {
active_format_flag: false,
active_format: None,
});
}
let Some(&second) = bytes.get(1) else {
return Err(SeiError::Atsc1BarDataTruncated { needed: 2, got: 1 });
};
let active_format = second & 0x0F;
Ok(AfdData {
active_format_flag: true,
active_format: Some(active_format),
})
}
fn parse_bar_data(bytes: &[u8]) -> Result<BarData, SeiError> {
let mut r = BitReader::new(bytes);
let top = r.u(1)? == 1;
let bottom = r.u(1)? == 1;
let left = r.u(1)? == 1;
let right = r.u(1)? == 1;
let reserved = r.u(4)? as u8;
if reserved != 0b1111 {
return Err(SeiError::Atsc1BarDataReservedMismatch(reserved));
}
if top != bottom {
return Err(SeiError::Atsc1BarDataTopBottomMismatch { top, bottom });
}
if left != right {
return Err(SeiError::Atsc1BarDataLeftRightMismatch { left, right });
}
if (top || bottom) && (left || right) {
return Err(SeiError::Atsc1BarDataLetterboxPillarboxBoth);
}
let mut read_14 = |which: &'static str| -> Result<u16, SeiError> {
let one_bits = r.u(2)? as u8;
if one_bits != 0b11 {
return Err(SeiError::Atsc1BarDataOneBitsMismatch {
which,
got: one_bits,
});
}
Ok(r.u(14)? as u16)
};
let letterbox = if top {
let t = read_14("top")?;
let b = read_14("bottom")?;
Some((t, b))
} else {
None
};
let pillarbox = if left {
let l = read_14("left")?;
let rt = read_14("right")?;
Some((l, rt))
} else {
None
};
Ok(BarData {
letterbox,
pillarbox,
})
}
pub fn parse_atsc1_envelope(payload_bytes: &[u8]) -> Result<Atsc1Envelope, SeiError> {
if payload_bytes.len() < 6 {
return Err(SeiError::Atsc1EnvelopeTooShort(payload_bytes.len()));
}
let provider_code = u16::from_be_bytes([payload_bytes[0], payload_bytes[1]]);
if provider_code != 0x0031 {
return Err(SeiError::Atsc1ProviderCodeMismatch(provider_code));
}
let user_identifier_raw = u32::from_be_bytes([
payload_bytes[2],
payload_bytes[3],
payload_bytes[4],
payload_bytes[5],
]);
let user_identifier = Atsc1UserIdentifier::from_u32(user_identifier_raw)
.ok_or(SeiError::Atsc1UnknownUserIdentifier(user_identifier_raw))?;
let rest = &payload_bytes[6..];
let body = match user_identifier {
Atsc1UserIdentifier::Ga94 => {
let Some((&type_code, after)) = rest.split_first() else {
return Err(SeiError::Atsc1AtscUserDataEmpty);
};
let inner = match type_code {
0x03 => Atsc1UserData::CcData {
cc_data_bytes: after.to_vec(),
},
0x06 => Atsc1UserData::BarData(parse_bar_data(after)?),
_ => Atsc1UserData::Reserved {
type_code,
raw: after.to_vec(),
},
};
Atsc1Body::AtscUserData(inner)
}
Atsc1UserIdentifier::Dtg1 => Atsc1Body::Afd(parse_afd_data(rest)?),
};
Ok(Atsc1Envelope {
provider_code,
user_identifier,
body,
})
}
pub fn parse_pan_scan_rect(payload: &[u8]) -> Result<PanScanRect, SeiError> {
let mut r = BitReader::new(payload);
let pan_scan_rect_id = r.ue()?;
let cancel_flag = r.u(1)? == 1;
if cancel_flag {
return Ok(PanScanRect {
pan_scan_rect_id,
cancel_flag,
body: None,
});
}
let cnt_minus1 = r.ue()?;
if cnt_minus1 >= 32 {
return Err(SeiError::PanScanCountTooLarge(cnt_minus1));
}
let count = (cnt_minus1 as usize).saturating_add(1);
let mut rects = Vec::with_capacity(count);
for _ in 0..count {
let left = r.se()?;
let right = r.se()?;
let top = r.se()?;
let bottom = r.se()?;
rects.push(PanScanRectOffsets {
left_offset: left,
right_offset: right,
top_offset: top,
bottom_offset: bottom,
});
}
let repetition_period = r.ue()?;
Ok(PanScanRect {
pan_scan_rect_id,
cancel_flag,
body: Some(PanScanRectBody {
rects,
repetition_period,
}),
})
}
pub fn parse_user_data_unregistered(payload: &[u8]) -> Result<UserDataUnregistered, SeiError> {
if payload.len() < 16 {
return Err(SeiError::UserDataTooShort(payload.len()));
}
let mut uuid = [0u8; 16];
uuid.copy_from_slice(&payload[..16]);
let user_data = payload[16..].to_vec();
Ok(UserDataUnregistered { uuid, user_data })
}
pub fn parse_recovery_point(payload: &[u8]) -> Result<RecoveryPoint, SeiError> {
let mut r = BitReader::new(payload);
let recovery_frame_cnt = r.ue()?;
let exact_match_flag = r.u(1)? == 1;
let broken_link_flag = r.u(1)? == 1;
let changing_slice_group_idc = r.u(2)? as u8;
Ok(RecoveryPoint {
recovery_frame_cnt,
exact_match_flag,
broken_link_flag,
changing_slice_group_idc,
})
}
pub fn parse_full_frame_freeze(payload: &[u8]) -> Result<FullFrameFreeze, SeiError> {
let mut r = BitReader::new(payload);
let full_frame_freeze_repetition_period = r.ue()?;
Ok(FullFrameFreeze {
full_frame_freeze_repetition_period,
})
}
pub fn parse_full_frame_snapshot(payload: &[u8]) -> Result<FullFrameSnapshot, SeiError> {
let mut r = BitReader::new(payload);
let snapshot_id = r.ue()?;
Ok(FullFrameSnapshot { snapshot_id })
}
pub fn parse_deblocking_filter_display_preference(
payload: &[u8],
) -> Result<DeblockingFilterDisplayPreference, SeiError> {
let mut r = BitReader::new(payload);
let cancel_flag = r.u(1)? == 1;
if cancel_flag {
return Ok(DeblockingFilterDisplayPreference {
cancel_flag,
display_prior_to_deblocking_preferred_flag: false,
dec_frame_buffering_constraint_flag: false,
repetition_period: 0,
});
}
let display_prior_to_deblocking_preferred_flag = r.u(1)? == 1;
let dec_frame_buffering_constraint_flag = r.u(1)? == 1;
let repetition_period = r.ue()?;
Ok(DeblockingFilterDisplayPreference {
cancel_flag,
display_prior_to_deblocking_preferred_flag,
dec_frame_buffering_constraint_flag,
repetition_period,
})
}
pub fn parse_mastering_display(payload: &[u8]) -> Result<MasteringDisplayColourVolume, SeiError> {
let mut r = BitReader::new(payload);
let mut primaries = [(0u16, 0u16); 3];
for slot in primaries.iter_mut() {
let x = r.u(16)? as u16;
let y = r.u(16)? as u16;
*slot = (x, y);
}
let wp_x = r.u(16)? as u16;
let wp_y = r.u(16)? as u16;
let max_l = r.u(32)?;
let min_l = r.u(32)?;
Ok(MasteringDisplayColourVolume {
display_primaries: primaries,
white_point: (wp_x, wp_y),
max_display_mastering_luminance: max_l,
min_display_mastering_luminance: min_l,
})
}
pub fn parse_content_light_level(payload: &[u8]) -> Result<ContentLightLevelInfo, SeiError> {
let mut r = BitReader::new(payload);
let max_content_light_level = r.u(16)? as u16;
let max_pic_average_light_level = r.u(16)? as u16;
Ok(ContentLightLevelInfo {
max_content_light_level,
max_pic_average_light_level,
})
}
pub fn parse_frame_packing_arrangement(
payload: &[u8],
) -> Result<FramePackingArrangement, SeiError> {
let mut r = BitReader::new(payload);
let id = r.ue()?;
let cancel_flag = r.u(1)? == 1;
let mut out = FramePackingArrangement {
id,
cancel_flag,
ty: 0,
quincunx_sampling_flag: false,
content_interpretation_type: 0,
spatial_flipping_flag: false,
frame0_flipped_flag: false,
field_views_flag: false,
current_frame_is_frame0_flag: false,
frame0_self_contained_flag: false,
frame1_self_contained_flag: false,
frame0_grid_position_x: 0,
frame0_grid_position_y: 0,
frame1_grid_position_x: 0,
frame1_grid_position_y: 0,
reserved_byte: 0,
repetition_period: 0,
extension_flag: false,
};
if !cancel_flag {
out.ty = r.u(7)? as u8;
out.quincunx_sampling_flag = r.u(1)? == 1;
out.content_interpretation_type = r.u(6)? as u8;
out.spatial_flipping_flag = r.u(1)? == 1;
out.frame0_flipped_flag = r.u(1)? == 1;
out.field_views_flag = r.u(1)? == 1;
out.current_frame_is_frame0_flag = r.u(1)? == 1;
out.frame0_self_contained_flag = r.u(1)? == 1;
out.frame1_self_contained_flag = r.u(1)? == 1;
if !out.quincunx_sampling_flag && out.ty != 5 {
out.frame0_grid_position_x = r.u(4)? as u8;
out.frame0_grid_position_y = r.u(4)? as u8;
out.frame1_grid_position_x = r.u(4)? as u8;
out.frame1_grid_position_y = r.u(4)? as u8;
}
out.reserved_byte = r.u(8)? as u8;
out.repetition_period = r.ue()?;
}
out.extension_flag = r.u(1)? == 1;
Ok(out)
}
pub fn parse_display_orientation(payload: &[u8]) -> Result<DisplayOrientation, SeiError> {
let mut r = BitReader::new(payload);
let cancel_flag = r.u(1)? == 1;
let mut out = DisplayOrientation {
cancel_flag,
hor_flip: false,
ver_flip: false,
anticlockwise_rotation: 0,
repetition_period: 0,
extension_flag: false,
};
if !cancel_flag {
out.hor_flip = r.u(1)? == 1;
out.ver_flip = r.u(1)? == 1;
out.anticlockwise_rotation = r.u(16)? as u16;
out.repetition_period = r.ue()?;
out.extension_flag = r.u(1)? == 1;
}
Ok(out)
}
pub fn parse_alternative_transfer_characteristics(payload: &[u8]) -> Result<u8, SeiError> {
let mut r = BitReader::new(payload);
Ok(r.u(8)? as u8)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SparePicEntry {
pub delta_spare_frame_num: u32,
pub spare_bottom_field_flag: Option<bool>,
pub spare_area_idc: u32,
pub spare_units: Option<Vec<bool>>,
pub zero_run_lengths: Option<Vec<u32>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecRefPicMarkingRepetition {
pub original_idr_flag: bool,
pub original_frame_num: u32,
pub original_field_pic_flag: Option<bool>,
pub original_bottom_field_flag: Option<bool>,
pub dec_ref_pic_marking: DecRefPicMarking,
}
pub fn parse_dec_ref_pic_marking_repetition(
payload: &[u8],
ctx: &SeiContext,
) -> Result<DecRefPicMarkingRepetition, SeiError> {
let mut r = BitReader::new(payload);
let original_idr_flag = r.u(1)? == 1;
let original_frame_num = r.ue()?;
let (original_field_pic_flag, original_bottom_field_flag) = if ctx.frame_mbs_only_flag {
(None, None)
} else {
let fpf = r.u(1)? == 1;
let bff = if fpf { Some(r.u(1)? == 1) } else { None };
(Some(fpf), bff)
};
let dec_ref_pic_marking = parse_repeated_dec_ref_pic_marking(&mut r, original_idr_flag)?;
Ok(DecRefPicMarkingRepetition {
original_idr_flag,
original_frame_num,
original_field_pic_flag,
original_bottom_field_flag,
dec_ref_pic_marking,
})
}
fn parse_repeated_dec_ref_pic_marking(
r: &mut BitReader<'_>,
idr_pic_flag: bool,
) -> Result<DecRefPicMarking, SeiError> {
if idr_pic_flag {
let no_output_of_prior_pics_flag = r.u(1)? == 1;
let long_term_reference_flag = r.u(1)? == 1;
Ok(DecRefPicMarking {
no_output_of_prior_pics_flag,
long_term_reference_flag,
adaptive_marking: None,
})
} else {
let adaptive_ref_pic_marking_mode_flag = r.u(1)? == 1;
let adaptive_marking = if adaptive_ref_pic_marking_mode_flag {
let mut ops = Vec::new();
loop {
let mmco = r.ue()?;
match mmco {
0 => break,
1 => {
let diff = r.ue()?;
ops.push(MmcoOp::MarkShortTermUnused(diff));
}
2 => {
let ltpn = r.ue()?;
ops.push(MmcoOp::MarkLongTermUnused(ltpn));
}
3 => {
let diff = r.ue()?;
let ltfi = r.ue()?;
ops.push(MmcoOp::AssignLongTerm(diff, ltfi));
}
4 => {
let max = r.ue()?;
ops.push(MmcoOp::SetMaxLongTermIdx(max));
}
5 => ops.push(MmcoOp::MarkAllUnused),
6 => {
let ltfi = r.ue()?;
ops.push(MmcoOp::AssignCurrentLongTerm(ltfi));
}
other => {
return Err(SeiError::DecRefPicMarkingRepetitionMmcoOutOfRange(other));
}
}
}
Some(ops)
} else {
None
};
Ok(DecRefPicMarking {
no_output_of_prior_pics_flag: false,
long_term_reference_flag: false,
adaptive_marking,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SparePic {
pub target_frame_num: u32,
pub spare_field_flag: bool,
pub target_bottom_field_flag: Option<bool>,
pub entries: Vec<SparePicEntry>,
}
pub fn parse_spare_pic(payload: &[u8], ctx: &SeiContext) -> Result<SparePic, SeiError> {
let mut r = BitReader::new(payload);
let target_frame_num = r.ue()?;
let spare_field_flag = r.u(1)? == 1;
let target_bottom_field_flag = if spare_field_flag {
Some(r.u(1)? == 1)
} else {
None
};
let num_spare_pics_minus1 = r.ue()?;
if num_spare_pics_minus1 > 15 {
return Err(SeiError::SparePicCountTooLarge(num_spare_pics_minus1));
}
let count = (num_spare_pics_minus1 as usize) + 1;
let pic_size_in_map_units = ctx.pic_size_in_map_units;
let mut entries = Vec::with_capacity(count);
for i in 0..count {
let delta_spare_frame_num = r.ue()?;
let spare_bottom_field_flag = if spare_field_flag {
Some(r.u(1)? == 1)
} else {
None
};
let spare_area_idc = r.ue()?;
if spare_area_idc > 2 {
return Err(SeiError::SparePicAreaIdcOutOfRange {
i,
idc: spare_area_idc,
});
}
let (spare_units, zero_run_lengths) = match spare_area_idc {
0 => (None, None),
1 => {
if pic_size_in_map_units == 0 {
return Err(SeiError::SparePicMapUnitsUnknown {
i,
idc: spare_area_idc,
});
}
let mut units = Vec::with_capacity(pic_size_in_map_units as usize);
for _ in 0..pic_size_in_map_units {
units.push(r.u(1)? == 0);
}
(Some(units), None)
}
_ => {
if pic_size_in_map_units == 0 {
return Err(SeiError::SparePicMapUnitsUnknown {
i,
idc: spare_area_idc,
});
}
let mut runs = Vec::new();
let mut map_unit_cnt: u64 = 0;
let target = pic_size_in_map_units as u64;
while map_unit_cnt < target {
let zero_run_length = r.ue()?;
runs.push(zero_run_length);
map_unit_cnt += zero_run_length as u64 + 1;
}
(None, Some(runs))
}
};
entries.push(SparePicEntry {
delta_spare_frame_num,
spare_bottom_field_flag,
spare_area_idc,
spare_units,
zero_run_lengths,
});
}
Ok(SparePic {
target_frame_num,
spare_field_flag,
target_bottom_field_flag,
entries,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SceneInfo {
pub scene_info_present_flag: bool,
pub scene_id: Option<u32>,
pub scene_transition_type: Option<u32>,
pub second_scene_id: Option<u32>,
}
pub fn parse_scene_info(payload: &[u8]) -> Result<SceneInfo, SeiError> {
let mut r = BitReader::new(payload);
let scene_info_present_flag = r.u(1)? == 1;
if !scene_info_present_flag {
return Ok(SceneInfo {
scene_info_present_flag,
scene_id: None,
scene_transition_type: None,
second_scene_id: None,
});
}
let scene_id = r.ue()?;
let scene_transition_type = r.ue()?;
let second_scene_id = if scene_transition_type > 3 {
Some(r.ue()?)
} else {
None
};
Ok(SceneInfo {
scene_info_present_flag,
scene_id: Some(scene_id),
scene_transition_type: Some(scene_transition_type),
second_scene_id,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubSeqInfo {
pub sub_seq_layer_num: u32,
pub sub_seq_id: u32,
pub first_ref_pic_flag: bool,
pub leading_non_ref_pic_flag: bool,
pub last_pic_flag: bool,
pub sub_seq_frame_num: Option<u32>,
}
pub fn parse_sub_seq_info(payload: &[u8]) -> Result<SubSeqInfo, SeiError> {
let mut r = BitReader::new(payload);
let sub_seq_layer_num = r.ue()?;
if sub_seq_layer_num >= 256 {
return Err(SeiError::SubSeqLayerNumTooLarge(sub_seq_layer_num));
}
let sub_seq_id = r.ue()?;
if sub_seq_id >= 65536 {
return Err(SeiError::SubSeqIdTooLarge(sub_seq_id));
}
let first_ref_pic_flag = r.u(1)? == 1;
let leading_non_ref_pic_flag = r.u(1)? == 1;
let last_pic_flag = r.u(1)? == 1;
let sub_seq_frame_num_flag = r.u(1)? == 1;
let sub_seq_frame_num = if sub_seq_frame_num_flag {
Some(r.ue()?)
} else {
None
};
Ok(SubSeqInfo {
sub_seq_layer_num,
sub_seq_id,
first_ref_pic_flag,
leading_non_ref_pic_flag,
last_pic_flag,
sub_seq_frame_num,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SubSeqLayerCharacteristic {
pub accurate_statistics_flag: bool,
pub average_bit_rate: u16,
pub average_frame_rate: u16,
}
impl SubSeqLayerCharacteristic {
#[must_use]
pub fn average_bit_rate_bps(&self) -> Option<u32> {
if self.average_bit_rate == 0 {
None
} else {
Some((self.average_bit_rate as u32) * 1000)
}
}
#[must_use]
pub fn average_frame_rate_fps(&self) -> Option<f64> {
if self.average_frame_rate == 0 {
None
} else {
Some((self.average_frame_rate as f64) / 256.0)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubSeqLayerCharacteristics {
pub layers: Vec<SubSeqLayerCharacteristic>,
}
pub fn parse_sub_seq_layer_characteristics(
payload: &[u8],
) -> Result<SubSeqLayerCharacteristics, SeiError> {
let mut r = BitReader::new(payload);
let num_sub_seq_layers_minus1 = r.ue()?;
if num_sub_seq_layers_minus1 > 255 {
return Err(SeiError::SubSeqLayersTooLarge(num_sub_seq_layers_minus1));
}
let count = num_sub_seq_layers_minus1 as usize + 1;
let mut layers = Vec::with_capacity(count);
for _ in 0..count {
let accurate_statistics_flag = r.u(1)? == 1;
let average_bit_rate = r.u(16)? as u16;
let average_frame_rate = r.u(16)? as u16;
layers.push(SubSeqLayerCharacteristic {
accurate_statistics_flag,
average_bit_rate,
average_frame_rate,
});
}
Ok(SubSeqLayerCharacteristics { layers })
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SubSeqReference {
pub ref_sub_seq_layer_num: u32,
pub ref_sub_seq_id: u32,
pub ref_sub_seq_direction: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubSeqCharacteristics {
pub sub_seq_layer_num: u32,
pub sub_seq_id: u32,
pub sub_seq_duration: Option<u32>,
pub average_rate: Option<SubSeqLayerCharacteristic>,
pub referenced: Vec<SubSeqReference>,
}
impl SubSeqCharacteristics {
#[must_use]
pub fn sub_seq_duration_seconds(&self) -> Option<f64> {
self.sub_seq_duration
.map(|ticks| f64::from(ticks) / 90_000.0)
}
}
pub fn parse_sub_seq_characteristics(payload: &[u8]) -> Result<SubSeqCharacteristics, SeiError> {
let mut r = BitReader::new(payload);
let sub_seq_layer_num = r.ue()?;
if sub_seq_layer_num >= 256 {
return Err(SeiError::SubSeqLayerNumTooLarge(sub_seq_layer_num));
}
let sub_seq_id = r.ue()?;
if sub_seq_id >= 65536 {
return Err(SeiError::SubSeqIdTooLarge(sub_seq_id));
}
let duration_flag = r.u(1)? == 1;
let sub_seq_duration = if duration_flag { Some(r.u(32)?) } else { None };
let average_rate_flag = r.u(1)? == 1;
let average_rate = if average_rate_flag {
let accurate_statistics_flag = r.u(1)? == 1;
let average_bit_rate = r.u(16)? as u16;
let average_frame_rate = r.u(16)? as u16;
Some(SubSeqLayerCharacteristic {
accurate_statistics_flag,
average_bit_rate,
average_frame_rate,
})
} else {
None
};
let num_referenced_subseqs = r.ue()?;
if num_referenced_subseqs >= 256 {
return Err(SeiError::SubSeqReferencedTooLarge(num_referenced_subseqs));
}
let mut referenced = Vec::with_capacity(num_referenced_subseqs as usize);
for _ in 0..num_referenced_subseqs {
let ref_sub_seq_layer_num = r.ue()?;
let ref_sub_seq_id = r.ue()?;
let ref_sub_seq_direction = r.u(1)? == 1;
referenced.push(SubSeqReference {
ref_sub_seq_layer_num,
ref_sub_seq_id,
ref_sub_seq_direction,
});
}
Ok(SubSeqCharacteristics {
sub_seq_layer_num,
sub_seq_id,
sub_seq_duration,
average_rate,
referenced,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProgressiveRefinementSegmentStart {
pub progressive_refinement_id: u32,
pub num_refinement_steps_minus1: u32,
}
impl ProgressiveRefinementSegmentStart {
#[must_use]
pub fn num_refinement_steps(&self) -> u64 {
u64::from(self.num_refinement_steps_minus1) + 1
}
}
pub fn parse_progressive_refinement_segment_start(
payload: &[u8],
) -> Result<ProgressiveRefinementSegmentStart, SeiError> {
let mut r = BitReader::new(payload);
let progressive_refinement_id = r.ue()?;
let num_refinement_steps_minus1 = r.ue()?;
Ok(ProgressiveRefinementSegmentStart {
progressive_refinement_id,
num_refinement_steps_minus1,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProgressiveRefinementSegmentEnd {
pub progressive_refinement_id: u32,
}
pub fn parse_progressive_refinement_segment_end(
payload: &[u8],
) -> Result<ProgressiveRefinementSegmentEnd, SeiError> {
let mut r = BitReader::new(payload);
let progressive_refinement_id = r.ue()?;
Ok(ProgressiveRefinementSegmentEnd {
progressive_refinement_id,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StereoVideoInfo {
pub field_views_flag: bool,
pub top_field_is_left_view_flag: Option<bool>,
pub current_frame_is_left_view_flag: Option<bool>,
pub next_frame_is_second_view_flag: Option<bool>,
pub left_view_self_contained_flag: bool,
pub right_view_self_contained_flag: bool,
}
pub fn parse_stereo_video_info(payload: &[u8]) -> Result<StereoVideoInfo, SeiError> {
let mut r = BitReader::new(payload);
let field_views_flag = r.u(1)? == 1;
let (
top_field_is_left_view_flag,
current_frame_is_left_view_flag,
next_frame_is_second_view_flag,
) = if field_views_flag {
(Some(r.u(1)? == 1), None, None)
} else {
let cur = r.u(1)? == 1;
let nxt = r.u(1)? == 1;
(None, Some(cur), Some(nxt))
};
let left_view_self_contained_flag = r.u(1)? == 1;
let right_view_self_contained_flag = r.u(1)? == 1;
Ok(StereoVideoInfo {
field_views_flag,
top_field_is_left_view_flag,
current_frame_is_left_view_flag,
next_frame_is_second_view_flag,
left_view_self_contained_flag,
right_view_self_contained_flag,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MotionConstrainedSliceGroupSet {
pub slice_group_ids: Vec<u32>,
pub exact_sample_value_match_flag: bool,
pub pan_scan_rect_flag: bool,
pub pan_scan_rect_id: Option<u32>,
}
pub fn parse_motion_constrained_slice_group_set(
payload: &[u8],
ctx: &SeiContext,
) -> Result<MotionConstrainedSliceGroupSet, SeiError> {
let mut r = BitReader::new(payload);
let num_slice_groups_in_set_minus1 = r.ue()?;
let num_slice_groups = ctx.num_slice_groups.max(1);
let width = if num_slice_groups <= 1 {
0
} else {
32 - (num_slice_groups - 1).leading_zeros()
};
if num_slice_groups_in_set_minus1 >= num_slice_groups {
return Err(SeiError::MotionConstrainedSliceGroupCountTooLarge {
count: num_slice_groups_in_set_minus1.saturating_add(1),
num_slice_groups,
});
}
let count = (num_slice_groups_in_set_minus1 as usize) + 1;
let mut slice_group_ids = Vec::with_capacity(count);
for i in 0..count {
let v = if width == 0 { 0 } else { r.u(width)? };
if v >= num_slice_groups {
return Err(SeiError::MotionConstrainedSliceGroupIdOutOfRange {
i,
id: v,
num_slice_groups,
});
}
slice_group_ids.push(v);
}
let exact_sample_value_match_flag = r.u(1)? == 1;
let pan_scan_rect_flag = r.u(1)? == 1;
let pan_scan_rect_id = if pan_scan_rect_flag {
Some(r.ue()?)
} else {
None
};
Ok(MotionConstrainedSliceGroupSet {
slice_group_ids,
exact_sample_value_match_flag,
pan_scan_rect_flag,
pan_scan_rect_id,
})
}
pub fn parse_post_filter_hint(payload: &[u8]) -> Result<PostFilterHint, SeiError> {
let mut r = BitReader::new(payload);
let filter_hint_size_y = r.ue()?;
let filter_hint_size_x = r.ue()?;
if !(1..=15).contains(&filter_hint_size_y) || !(1..=15).contains(&filter_hint_size_x) {
return Err(SeiError::PostFilterHintSizeOutOfRange {
x: filter_hint_size_x,
y: filter_hint_size_y,
});
}
let filter_hint_type = r.u(2)? as u8;
if filter_hint_type == 3 {
return Err(SeiError::PostFilterHintTypeReserved);
}
if filter_hint_type == 1 && filter_hint_size_y != 2 {
return Err(SeiError::PostFilterHintType1WrongHeight(filter_hint_size_y));
}
let total = 3usize * filter_hint_size_y as usize * filter_hint_size_x as usize;
let mut filter_hint_value = Vec::with_capacity(total);
for _ in 0..total {
filter_hint_value.push(r.se()?);
}
let additional_extension_flag = r.u(1)? == 1;
Ok(PostFilterHint {
filter_hint_size_x,
filter_hint_size_y,
filter_hint_type,
filter_hint_value,
additional_extension_flag,
})
}
pub fn parse_tone_mapping_info(payload: &[u8]) -> Result<ToneMappingInfo, SeiError> {
let mut r = BitReader::new(payload);
let tone_map_id = r.ue()?;
let cancel_flag = r.u(1)? == 1;
if cancel_flag {
return Ok(ToneMappingInfo {
tone_map_id,
cancel_flag,
body: None,
});
}
let repetition_period = r.ue()?;
let coded_data_bit_depth = r.u(8)? as u8;
let target_bit_depth = r.u(8)? as u8;
let model_id = r.ue()?;
let coded_width = ((coded_data_bit_depth as u32 + 7) >> 3) << 3;
let target_width = ((target_bit_depth as u32 + 7) >> 3) << 3;
let model = match model_id {
0 => {
let min_value = r.u(32)?;
let max_value = r.u(32)?;
ToneMappingModel::Linear {
min_value,
max_value,
}
}
1 => {
let sigmoid_midpoint = r.u(32)?;
let sigmoid_width = r.u(32)?;
ToneMappingModel::Sigmoid {
sigmoid_midpoint,
sigmoid_width,
}
}
2 => {
if target_bit_depth > 16 || coded_width == 0 {
return Err(SeiError::UnsupportedPayloadType(23));
}
let count = 1usize << target_bit_depth as usize;
let mut vals = Vec::with_capacity(count);
for _ in 0..count {
vals.push(r.u(coded_width)?);
}
ToneMappingModel::StartOfCodedInterval {
start_of_coded_interval: vals,
}
}
3 => {
let num_pivots = r.u(16)? as u16;
if coded_width == 0 || target_width == 0 {
return Err(SeiError::UnsupportedPayloadType(23));
}
let mut coded_pivot_value = Vec::with_capacity(num_pivots as usize);
let mut target_pivot_value = Vec::with_capacity(num_pivots as usize);
for _ in 0..num_pivots {
coded_pivot_value.push(r.u(coded_width)?);
target_pivot_value.push(r.u(target_width)?);
}
ToneMappingModel::PiecewisePivots {
num_pivots,
coded_pivot_value,
target_pivot_value,
}
}
_ => {
let (byte_pos, bit_pos) = r.position();
let remaining_bytes = if bit_pos == 0 {
payload.get(byte_pos..).unwrap_or(&[]).to_vec()
} else {
payload.get(byte_pos + 1..).unwrap_or(&[]).to_vec()
};
ToneMappingModel::Reserved {
model_id,
remaining_bytes,
}
}
};
Ok(ToneMappingInfo {
tone_map_id,
cancel_flag,
body: Some(ToneMappingBody {
repetition_period,
coded_data_bit_depth,
target_bit_depth,
model_id,
model,
}),
})
}
pub fn parse_film_grain_characteristics(
payload: &[u8],
) -> Result<FilmGrainCharacteristics, SeiError> {
let mut r = BitReader::new(payload);
let cancel_flag = r.u(1)? == 1;
if cancel_flag {
return Ok(FilmGrainCharacteristics {
cancel_flag,
body: None,
});
}
let model_id = r.u(2)? as u8;
let separate_flag = r.u(1)? == 1;
let separate_colour_description = if separate_flag {
let bit_depth_luma_minus8 = r.u(3)? as u8;
let bit_depth_chroma_minus8 = r.u(3)? as u8;
let full_range_flag = r.u(1)? == 1;
let colour_primaries = r.u(8)? as u8;
let transfer_characteristics = r.u(8)? as u8;
let matrix_coefficients = r.u(8)? as u8;
Some(FilmGrainSeparateColourDescription {
bit_depth_luma_minus8,
bit_depth_chroma_minus8,
full_range_flag,
colour_primaries,
transfer_characteristics,
matrix_coefficients,
})
} else {
None
};
let blending_mode_id = r.u(2)? as u8;
let log2_scale_factor = r.u(4)? as u8;
let mut comp_model_present_flag = [false; 3];
for flag in comp_model_present_flag.iter_mut() {
*flag = r.u(1)? == 1;
}
let mut components: [Option<FilmGrainComponent>; 3] = [None, None, None];
for (c, present) in comp_model_present_flag.iter().enumerate() {
if !*present {
continue;
}
let num_intensity_intervals_minus1 = r.u(8)? as u8;
let num_model_values_minus1 = r.u(3)? as u8;
if num_model_values_minus1 > 5 {
return Err(SeiError::FilmGrainNumModelValuesOutOfRange {
component: c,
got: num_model_values_minus1,
});
}
let interval_count = num_intensity_intervals_minus1 as usize + 1;
let mvalue_count = num_model_values_minus1 as usize + 1;
let mut intensity_intervals = Vec::with_capacity(interval_count);
for _ in 0..interval_count {
let lower_bound = r.u(8)? as u8;
let upper_bound = r.u(8)? as u8;
let mut model_values = Vec::with_capacity(mvalue_count);
for _ in 0..mvalue_count {
model_values.push(r.se()?);
}
intensity_intervals.push(FilmGrainIntensityInterval {
lower_bound,
upper_bound,
model_values,
});
}
components[c] = Some(FilmGrainComponent {
num_model_values_minus1,
intensity_intervals,
});
}
let repetition_period = r.ue()?;
Ok(FilmGrainCharacteristics {
cancel_flag,
body: Some(FilmGrainBody {
model_id,
separate_colour_description_present_flag: separate_flag,
separate_colour_description,
blending_mode_id,
log2_scale_factor,
comp_model_present_flag,
components,
repetition_period,
}),
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ContentColourVolumePrimary {
pub x: i32,
pub y: i32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContentColourVolume {
pub cancel_flag: bool,
pub body: Option<ContentColourVolumeBody>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContentColourVolumeBody {
pub persistence_flag: bool,
pub primaries: Option<[ContentColourVolumePrimary; 3]>,
pub min_luminance_value: Option<u32>,
pub max_luminance_value: Option<u32>,
pub avg_luminance_value: Option<u32>,
}
pub fn parse_content_colour_volume(payload: &[u8]) -> Result<ContentColourVolume, SeiError> {
let mut r = BitReader::new(payload);
let cancel_flag = r.u(1)? == 1;
if cancel_flag {
return Ok(ContentColourVolume {
cancel_flag,
body: None,
});
}
let persistence_flag = r.u(1)? == 1;
let primaries_present = r.u(1)? == 1;
let min_present = r.u(1)? == 1;
let max_present = r.u(1)? == 1;
let avg_present = r.u(1)? == 1;
let _reserved = r.u(2)?;
if !primaries_present && !min_present && !max_present && !avg_present {
return Err(SeiError::ContentColourVolumeNoComponents);
}
let primaries = if primaries_present {
let mut prims = [ContentColourVolumePrimary { x: 0, y: 0 }; 3];
for (c, prim) in prims.iter_mut().enumerate() {
let x = r.i(32)?;
let y = r.i(32)?;
const PRIM_LO: i32 = -5_000_000;
const PRIM_HI: i32 = 5_000_000;
if !(PRIM_LO..=PRIM_HI).contains(&x) || !(PRIM_LO..=PRIM_HI).contains(&y) {
return Err(SeiError::ContentColourVolumePrimaryOutOfRange { c, x, y });
}
*prim = ContentColourVolumePrimary { x, y };
}
Some(prims)
} else {
None
};
let min_luminance_value = if min_present { Some(r.u(32)?) } else { None };
let max_luminance_value = if max_present { Some(r.u(32)?) } else { None };
let avg_luminance_value = if avg_present { Some(r.u(32)?) } else { None };
if let (Some(min), Some(avg)) = (min_luminance_value, avg_luminance_value) {
if min > avg {
return Err(SeiError::ContentColourVolumeLuminanceOrder {
min: min as u64,
avg: avg as u64,
max: max_luminance_value.map_or(u64::MAX, |m| m as u64),
});
}
}
if let (Some(avg), Some(max)) = (avg_luminance_value, max_luminance_value) {
if avg > max {
return Err(SeiError::ContentColourVolumeLuminanceOrder {
min: min_luminance_value.map_or(0, |m| m as u64),
avg: avg as u64,
max: max as u64,
});
}
}
if let (Some(min), Some(max)) = (min_luminance_value, max_luminance_value) {
if min > max {
return Err(SeiError::ContentColourVolumeLuminanceOrder {
min: min as u64,
avg: avg_luminance_value.map_or(min as u64, |a| a as u64),
max: max as u64,
});
}
}
Ok(ContentColourVolume {
cancel_flag,
body: Some(ContentColourVolumeBody {
persistence_flag,
primaries,
min_luminance_value,
max_luminance_value,
avg_luminance_value,
}),
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColourRemapVideoSignalInfo {
pub full_range_flag: bool,
pub primaries: u8,
pub transfer_function: u8,
pub matrix_coefficients: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColourRemapLutEntry {
pub coded_value: u32,
pub target_value: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColourRemapMatrix {
pub log2_matrix_denom: u8,
pub coeffs: [[i32; 3]; 3],
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColourRemappingInfo {
pub colour_remap_id: u32,
pub cancel_flag: bool,
pub body: Option<ColourRemappingInfoBody>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColourRemappingInfoBody {
pub repetition_period: u32,
pub video_signal_info: Option<ColourRemapVideoSignalInfo>,
pub input_bit_depth: u8,
pub output_bit_depth: u8,
pub pre_lut: [Vec<ColourRemapLutEntry>; 3],
pub matrix: Option<ColourRemapMatrix>,
pub post_lut: [Vec<ColourRemapLutEntry>; 3],
}
pub fn parse_colour_remapping_info(payload: &[u8]) -> Result<ColourRemappingInfo, SeiError> {
let mut r = BitReader::new(payload);
let colour_remap_id = r.ue()?;
let cancel_flag = r.u(1)? == 1;
if cancel_flag {
return Ok(ColourRemappingInfo {
colour_remap_id,
cancel_flag,
body: None,
});
}
let repetition_period = r.ue()?;
if repetition_period > 16_384 {
return Err(SeiError::ColourRemapRepetitionPeriodOutOfRange(
repetition_period,
));
}
let video_signal_info = if r.u(1)? == 1 {
let full_range_flag = r.u(1)? == 1;
let primaries = r.u(8)? as u8;
let transfer_function = r.u(8)? as u8;
let matrix_coefficients = r.u(8)? as u8;
Some(ColourRemapVideoSignalInfo {
full_range_flag,
primaries,
transfer_function,
matrix_coefficients,
})
} else {
None
};
let input_bit_depth = r.u(8)? as u8;
let output_bit_depth = r.u(8)? as u8;
if !(8..=16).contains(&input_bit_depth) {
return Err(SeiError::ColourRemapBitDepthOutOfRange {
which: "input",
got: input_bit_depth,
});
}
if !(8..=16).contains(&output_bit_depth) {
return Err(SeiError::ColourRemapBitDepthOutOfRange {
which: "output",
got: output_bit_depth,
});
}
let coded_bits = |bd: u8| -> u32 { ((bd as u32 + 7) >> 3) << 3 };
let pre_coded_bits = coded_bits(input_bit_depth);
let pre_target_bits = coded_bits(output_bit_depth);
let post_coded_bits = coded_bits(output_bit_depth);
let post_target_bits = coded_bits(output_bit_depth);
let read_lut = |r: &mut BitReader,
coded_bits: u32,
target_bits: u32,
which: &'static str|
-> Result<[Vec<ColourRemapLutEntry>; 3], SeiError> {
let mut lut: [Vec<ColourRemapLutEntry>; 3] = [Vec::new(), Vec::new(), Vec::new()];
for (c, comp) in lut.iter_mut().enumerate() {
let num_val_minus1 = r.u(8)? as u8;
if num_val_minus1 > 32 {
return Err(SeiError::ColourRemapLutCountOutOfRange {
which,
c,
got: num_val_minus1,
});
}
if num_val_minus1 > 0 {
for _ in 0..=num_val_minus1 {
let coded_value = r.u(coded_bits)?;
let target_value = r.u(target_bits)?;
comp.push(ColourRemapLutEntry {
coded_value,
target_value,
});
}
}
}
Ok(lut)
};
let pre_lut = read_lut(&mut r, pre_coded_bits, pre_target_bits, "pre")?;
let matrix = if r.u(1)? == 1 {
let log2_matrix_denom = r.u(4)? as u8; let mut coeffs = [[0i32; 3]; 3];
for row in coeffs.iter_mut() {
for coeff in row.iter_mut() {
let v = r.se()?;
if !(-(1 << 15)..=(1 << 15) - 1).contains(&v) {
return Err(SeiError::ColourRemapCoeffOutOfRange(v));
}
*coeff = v;
}
}
Some(ColourRemapMatrix {
log2_matrix_denom,
coeffs,
})
} else {
None
};
let post_lut = read_lut(&mut r, post_coded_bits, post_target_bits, "post")?;
Ok(ColourRemappingInfo {
colour_remap_id,
cancel_flag,
body: Some(ColourRemappingInfoBody {
repetition_period,
video_signal_info,
input_bit_depth,
output_bit_depth,
pre_lut,
matrix,
post_lut,
}),
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AmbientViewingEnvironment {
pub ambient_illuminance: u32,
pub ambient_light_x: u16,
pub ambient_light_y: u16,
}
pub fn parse_ambient_viewing_environment(
payload: &[u8],
) -> Result<AmbientViewingEnvironment, SeiError> {
let mut r = BitReader::new(payload);
let ambient_illuminance = r.u(32)?;
let ambient_light_x = r.u(16)?;
let ambient_light_y = r.u(16)?;
if ambient_illuminance == 0 {
return Err(SeiError::AmbientIlluminanceZero);
}
if ambient_light_x > 50_000 || ambient_light_y > 50_000 {
return Err(SeiError::AmbientLightOutOfRange {
x: ambient_light_x,
y: ambient_light_y,
});
}
Ok(AmbientViewingEnvironment {
ambient_illuminance,
ambient_light_x: ambient_light_x as u16,
ambient_light_y: ambient_light_y as u16,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EquirectangularProjection {
pub cancel_flag: bool,
pub persistence_flag: Option<bool>,
pub padding_flag: Option<bool>,
pub gb_erp_type: Option<u8>,
pub left_gb_erp_width: Option<u8>,
pub right_gb_erp_width: Option<u8>,
}
pub fn parse_equirectangular_projection(
payload: &[u8],
) -> Result<EquirectangularProjection, SeiError> {
let mut r = BitReader::new(payload);
let cancel_flag = r.u(1)? == 1;
if cancel_flag {
return Ok(EquirectangularProjection {
cancel_flag,
persistence_flag: None,
padding_flag: None,
gb_erp_type: None,
left_gb_erp_width: None,
right_gb_erp_width: None,
});
}
let persistence_flag = r.u(1)? == 1;
let padding_flag = r.u(1)? == 1;
let _reserved = r.u(2)?;
let (gb_erp_type, left_gb_erp_width, right_gb_erp_width) = if padding_flag {
let ty = r.u(3)? as u8;
let left = r.u(8)? as u8;
let right = r.u(8)? as u8;
(Some(ty), Some(left), Some(right))
} else {
(None, None, None)
};
Ok(EquirectangularProjection {
cancel_flag,
persistence_flag: Some(persistence_flag),
padding_flag: Some(padding_flag),
gb_erp_type,
left_gb_erp_width,
right_gb_erp_width,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CubemapProjection {
pub cancel_flag: bool,
pub persistence_flag: Option<bool>,
}
pub fn parse_cubemap_projection(payload: &[u8]) -> Result<CubemapProjection, SeiError> {
let mut r = BitReader::new(payload);
let cancel_flag = r.u(1)? == 1;
let persistence_flag = if cancel_flag {
None
} else {
Some(r.u(1)? == 1)
};
Ok(CubemapProjection {
cancel_flag,
persistence_flag,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SphereRotation {
pub cancel_flag: bool,
pub persistence_flag: Option<bool>,
pub yaw_rotation: Option<i32>,
pub pitch_rotation: Option<i32>,
pub roll_rotation: Option<i32>,
}
pub fn parse_sphere_rotation(payload: &[u8]) -> Result<SphereRotation, SeiError> {
let mut r = BitReader::new(payload);
let cancel_flag = r.u(1)? == 1;
if cancel_flag {
return Ok(SphereRotation {
cancel_flag,
persistence_flag: None,
yaw_rotation: None,
pitch_rotation: None,
roll_rotation: None,
});
}
let persistence_flag = r.u(1)? == 1;
let _reserved = r.u(6)?;
let yaw = r.i(32)?;
let pitch = r.i(32)?;
let roll = r.i(32)?;
const YAW_ROLL_LO: i32 = -180 * (1 << 16);
const YAW_ROLL_HI: i32 = 180 * (1 << 16) - 1;
const PITCH_LO: i32 = -90 * (1 << 16);
const PITCH_HI: i32 = 90 * (1 << 16);
if !(YAW_ROLL_LO..=YAW_ROLL_HI).contains(&yaw) {
return Err(SeiError::SphereRotationYawOutOfRange(yaw));
}
if !(PITCH_LO..=PITCH_HI).contains(&pitch) {
return Err(SeiError::SphereRotationPitchOutOfRange(pitch));
}
if !(YAW_ROLL_LO..=YAW_ROLL_HI).contains(&roll) {
return Err(SeiError::SphereRotationRollOutOfRange(roll));
}
Ok(SphereRotation {
cancel_flag,
persistence_flag: Some(persistence_flag),
yaw_rotation: Some(yaw),
pitch_rotation: Some(pitch),
roll_rotation: Some(roll),
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegionWisePacking {
pub cancel_flag: bool,
pub persistence_flag: Option<bool>,
pub constituent_picture_matching_flag: Option<bool>,
pub proj_picture_width: Option<u32>,
pub proj_picture_height: Option<u32>,
pub packed_picture_width: Option<u16>,
pub packed_picture_height: Option<u16>,
pub regions: Vec<PackedRegion>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PackedRegion {
pub transform_type: u8,
pub proj_region_width: u32,
pub proj_region_height: u32,
pub proj_region_top: u32,
pub proj_region_left: u32,
pub packed_region_width: u16,
pub packed_region_height: u16,
pub packed_region_top: u16,
pub packed_region_left: u16,
pub guard_band: Option<GuardBand>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GuardBand {
pub left_gb_width: u8,
pub right_gb_width: u8,
pub top_gb_height: u8,
pub bottom_gb_height: u8,
pub gb_not_used_for_pred_flag: bool,
pub gb_type: [u8; 4],
}
pub fn parse_regionwise_packing(payload: &[u8]) -> Result<RegionWisePacking, SeiError> {
let mut r = BitReader::new(payload);
let cancel_flag = r.u(1)? == 1;
if cancel_flag {
return Ok(RegionWisePacking {
cancel_flag,
persistence_flag: None,
constituent_picture_matching_flag: None,
proj_picture_width: None,
proj_picture_height: None,
packed_picture_width: None,
packed_picture_height: None,
regions: Vec::new(),
});
}
let persistence_flag = r.u(1)? == 1;
let constituent_picture_matching_flag = r.u(1)? == 1;
let _rwp_reserved_zero_5bits = r.u(5)?; let num_packed_regions = r.u(8)?;
if num_packed_regions == 0 {
return Err(SeiError::RegionWisePackingNoRegions);
}
let proj_picture_width = r.u(32)?;
let proj_picture_height = r.u(32)?;
if proj_picture_width == 0 || proj_picture_height == 0 {
return Err(SeiError::RegionWisePackingProjPictureZero {
w: proj_picture_width,
h: proj_picture_height,
});
}
let packed_picture_width = r.u(16)? as u16;
let packed_picture_height = r.u(16)? as u16;
if packed_picture_width == 0 || packed_picture_height == 0 {
return Err(SeiError::RegionWisePackingPackedPictureZero {
w: packed_picture_width as u32,
h: packed_picture_height as u32,
});
}
let mut regions = Vec::with_capacity(num_packed_regions as usize);
for i in 0..num_packed_regions as usize {
let _rwp_reserved_zero_4bits = r.u(4)?; let transform_type = r.u(3)? as u8;
let guard_band_flag = r.u(1)? == 1;
let proj_region_width = r.u(32)?;
let proj_region_height = r.u(32)?;
let proj_region_top = r.u(32)?;
let proj_region_left = r.u(32)?;
let packed_region_width = r.u(16)? as u16;
let packed_region_height = r.u(16)? as u16;
let packed_region_top = r.u(16)? as u16;
let packed_region_left = r.u(16)? as u16;
let guard_band = if guard_band_flag {
let left_gb_width = r.u(8)? as u8;
let right_gb_width = r.u(8)? as u8;
let top_gb_height = r.u(8)? as u8;
let bottom_gb_height = r.u(8)? as u8;
if left_gb_width == 0
&& right_gb_width == 0
&& top_gb_height == 0
&& bottom_gb_height == 0
{
return Err(SeiError::RegionWisePackingGuardBandAllZero(i));
}
let gb_not_used_for_pred_flag = r.u(1)? == 1;
let mut gb_type = [0u8; 4];
for entry in gb_type.iter_mut() {
*entry = r.u(3)? as u8;
}
let _rwp_gb_reserved_zero_3bits = r.u(3)?; Some(GuardBand {
left_gb_width,
right_gb_width,
top_gb_height,
bottom_gb_height,
gb_not_used_for_pred_flag,
gb_type,
})
} else {
None
};
regions.push(PackedRegion {
transform_type,
proj_region_width,
proj_region_height,
proj_region_top,
proj_region_left,
packed_region_width,
packed_region_height,
packed_region_top,
packed_region_left,
guard_band,
});
}
Ok(RegionWisePacking {
cancel_flag,
persistence_flag: Some(persistence_flag),
constituent_picture_matching_flag: Some(constituent_picture_matching_flag),
proj_picture_width: Some(proj_picture_width),
proj_picture_height: Some(proj_picture_height),
packed_picture_width: Some(packed_picture_width),
packed_picture_height: Some(packed_picture_height),
regions,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OmniViewport {
pub viewport_id: u16,
pub cancel_flag: bool,
pub persistence_flag: Option<bool>,
pub viewports: Vec<OmniViewportEntry>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OmniViewportEntry {
pub azimuth_centre: i32,
pub elevation_centre: i32,
pub tilt_centre: i32,
pub hor_range: u32,
pub ver_range: u32,
}
pub fn parse_omni_viewport(payload: &[u8]) -> Result<OmniViewport, SeiError> {
let mut r = BitReader::new(payload);
let viewport_id = r.u(10)? as u16;
let cancel_flag = r.u(1)? == 1;
if cancel_flag {
return Ok(OmniViewport {
viewport_id,
cancel_flag,
persistence_flag: None,
viewports: Vec::new(),
});
}
let persistence_flag = r.u(1)? == 1;
let cnt_minus1 = r.u(4)?;
const AZ_TILT_LO: i32 = -180 * (1 << 16);
const AZ_TILT_HI: i32 = 180 * (1 << 16) - 1;
const EL_LO: i32 = -90 * (1 << 16);
const EL_HI: i32 = 90 * (1 << 16);
const HOR_HI: u32 = 360 * (1 << 16);
const VER_HI: u32 = 180 * (1 << 16);
let count = (cnt_minus1 as usize) + 1;
let mut viewports = Vec::with_capacity(count);
for _ in 0..count {
let azimuth_centre = r.i(32)?;
let elevation_centre = r.i(32)?;
let tilt_centre = r.i(32)?;
let hor_range = r.u(32)?;
let ver_range = r.u(32)?;
if !(AZ_TILT_LO..=AZ_TILT_HI).contains(&azimuth_centre) {
return Err(SeiError::OmniViewportAzimuthOutOfRange(azimuth_centre));
}
if !(EL_LO..=EL_HI).contains(&elevation_centre) {
return Err(SeiError::OmniViewportElevationOutOfRange(elevation_centre));
}
if !(AZ_TILT_LO..=AZ_TILT_HI).contains(&tilt_centre) {
return Err(SeiError::OmniViewportTiltOutOfRange(tilt_centre));
}
if hor_range == 0 || hor_range > HOR_HI {
return Err(SeiError::OmniViewportHorRangeOutOfRange(hor_range));
}
if ver_range == 0 || ver_range > VER_HI {
return Err(SeiError::OmniViewportVerRangeOutOfRange(ver_range));
}
viewports.push(OmniViewportEntry {
azimuth_centre,
elevation_centre,
tilt_centre,
hor_range,
ver_range,
});
}
Ok(OmniViewport {
viewport_id,
cancel_flag,
persistence_flag: Some(persistence_flag),
viewports,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShutterIntervalInfo {
pub sub_layer_idx: u32,
pub body: Option<ShutterIntervalInfoBody>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShutterIntervalInfoBody {
pub info_present_flag: bool,
pub schedule: Option<ShutterIntervalSchedule>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShutterIntervalSchedule {
pub time_scale: u32,
pub kind: ShutterIntervalKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShutterIntervalKind {
Fixed { num_units: u32 },
PerSubLayer {
max_sub_layers_minus1: u8,
num_units_per_sub_layer: Vec<u32>,
},
}
pub fn parse_shutter_interval_info(payload: &[u8]) -> Result<ShutterIntervalInfo, SeiError> {
let mut r = BitReader::new(payload);
let sub_layer_idx = r.ue()?;
if sub_layer_idx != 0 {
return Ok(ShutterIntervalInfo {
sub_layer_idx,
body: None,
});
}
let info_present_flag = r.u(1)? == 1;
if !info_present_flag {
return Ok(ShutterIntervalInfo {
sub_layer_idx,
body: Some(ShutterIntervalInfoBody {
info_present_flag,
schedule: None,
}),
});
}
let time_scale = r.u(32)?;
if time_scale == 0 {
return Err(SeiError::ShutterIntervalTimeScaleZero);
}
let fixed = r.u(1)? == 1;
let kind = if fixed {
let num_units = r.u(32)?;
ShutterIntervalKind::Fixed { num_units }
} else {
let max_sub_layers_minus1 = r.u(3)? as u8;
let count = (max_sub_layers_minus1 as usize) + 1;
let mut num_units_per_sub_layer = Vec::with_capacity(count);
for _ in 0..count {
num_units_per_sub_layer.push(r.u(32)?);
}
ShutterIntervalKind::PerSubLayer {
max_sub_layers_minus1,
num_units_per_sub_layer,
}
};
Ok(ShutterIntervalInfo {
sub_layer_idx,
body: Some(ShutterIntervalInfoBody {
info_present_flag,
schedule: Some(ShutterIntervalSchedule { time_scale, kind }),
}),
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SeiManifest {
pub entries: Vec<SeiManifestEntry>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SeiManifestEntry {
pub payload_type: u16,
pub description: SeiManifestDescription,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SeiManifestDescription {
NotExpected,
ExpectedNecessary,
ExpectedUnnecessary,
ExpectedUndetermined,
Reserved(u8),
}
impl SeiManifestDescription {
pub fn from_raw(raw: u8) -> Self {
match raw {
0 => Self::NotExpected,
1 => Self::ExpectedNecessary,
2 => Self::ExpectedUnnecessary,
3 => Self::ExpectedUndetermined,
v => Self::Reserved(v),
}
}
}
pub fn parse_sei_manifest(payload: &[u8]) -> Result<SeiManifest, SeiError> {
let mut r = BitReader::new(payload);
let count = r.u(16)? as usize;
let mut entries: Vec<SeiManifestEntry> = Vec::with_capacity(count);
for i in 0..count {
let payload_type = r.u(16)? as u16;
let description_raw = r.u(8)? as u8;
if let Some(prev) = entries.iter().position(|e| e.payload_type == payload_type) {
return Err(SeiError::SeiManifestDuplicatePayloadType {
payload_type,
first: prev,
second: i,
});
}
entries.push(SeiManifestEntry {
payload_type,
description: SeiManifestDescription::from_raw(description_raw),
});
}
Ok(SeiManifest { entries })
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SeiPrefixIndication {
pub prefix_sei_payload_type: u16,
pub indications: Vec<SeiPrefixIndicationEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SeiPrefixIndicationEntry {
pub bit_count: u32,
pub data: Vec<u8>,
}
pub fn parse_sei_prefix_indication(payload: &[u8]) -> Result<SeiPrefixIndication, SeiError> {
let mut r = BitReader::new(payload);
let prefix_sei_payload_type = r.u(16)? as u16;
let count = (r.u(8)? as usize) + 1;
let mut indications: Vec<SeiPrefixIndicationEntry> = Vec::with_capacity(count);
for i in 0..count {
let bit_count = r.u(16)? + 1;
let available = r.bits_remaining();
if (bit_count as usize) > available {
return Err(SeiError::SeiPrefixIndicationOverflow {
i,
bits: bit_count,
available,
});
}
let byte_len = bit_count.div_ceil(8) as usize;
let mut data = vec![0u8; byte_len];
for j in 0..(bit_count as usize) {
let bit = r.u(1)? as u8;
if bit == 1 {
let byte = j / 8;
let pos = 7 - (j % 8);
data[byte] |= 1 << pos;
}
}
while !r.byte_aligned() {
let _ = r.u(1)?;
}
indications.push(SeiPrefixIndicationEntry { bit_count, data });
}
Ok(SeiPrefixIndication {
prefix_sei_payload_type,
indications,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MultiviewViewPosition {
pub view_positions: Vec<u16>,
pub extension_flag: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MultiviewSceneInfo {
pub max_disparity: u16,
}
pub fn parse_multiview_scene_info(payload: &[u8]) -> Result<MultiviewSceneInfo, SeiError> {
let mut r = BitReader::new(payload);
let max_disparity = r.ue()?;
if max_disparity > 1023 {
return Err(SeiError::MultiviewSceneInfoMaxDisparityOutOfRange(
max_disparity,
));
}
Ok(MultiviewSceneInfo {
max_disparity: max_disparity as u16,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OperationPointNotPresent {
pub operation_point_not_present_ids: Vec<u16>,
}
pub fn parse_operation_point_not_present(
payload: &[u8],
) -> Result<OperationPointNotPresent, SeiError> {
let mut r = BitReader::new(payload);
let num_operation_points = r.ue()?;
if num_operation_points > 65536 {
return Err(SeiError::OperationPointNotPresentCountOutOfRange(
num_operation_points,
));
}
let count = num_operation_points as usize;
let mut operation_point_not_present_ids: Vec<u16> = Vec::with_capacity(count);
for i in 0..count {
let id = r.ue()?;
if id > 65535 {
return Err(SeiError::OperationPointNotPresentIdOutOfRange { i, got: id });
}
operation_point_not_present_ids.push(id as u16);
}
Ok(OperationPointNotPresent {
operation_point_not_present_ids,
})
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ViewDependencyChangeView {
pub anchor_ref_l0_flags: Vec<bool>,
pub anchor_ref_l1_flags: Vec<bool>,
pub non_anchor_ref_l0_flags: Vec<bool>,
pub non_anchor_ref_l1_flags: Vec<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ViewDependencyChange {
pub seq_parameter_set_id: u32,
pub anchor_update_flag: bool,
pub non_anchor_update_flag: bool,
pub views: Vec<ViewDependencyChangeView>,
}
pub fn parse_view_dependency_change(
payload: &[u8],
ctx: &SeiContext,
) -> Result<ViewDependencyChange, SeiError> {
let mut r = BitReader::new(payload);
let seq_parameter_set_id = r.ue()?;
if seq_parameter_set_id > 31 {
return Err(SeiError::ViewDependencyChangeSpsIdOutOfRange(
seq_parameter_set_id,
));
}
let anchor_update_flag = r.u(1)? == 1;
let non_anchor_update_flag = r.u(1)? == 1;
let views = if anchor_update_flag || non_anchor_update_flag {
if ctx.mvc_view_ref_counts.is_empty() {
return Err(SeiError::ViewDependencyChangeRefCountsUnknown);
}
let mut views = Vec::with_capacity(ctx.mvc_view_ref_counts.len());
for counts in &ctx.mvc_view_ref_counts {
let mut view = ViewDependencyChangeView::default();
if anchor_update_flag {
for _ in 0..counts.num_anchor_refs_l0 {
view.anchor_ref_l0_flags.push(r.u(1)? == 1);
}
for _ in 0..counts.num_anchor_refs_l1 {
view.anchor_ref_l1_flags.push(r.u(1)? == 1);
}
}
views.push(view);
}
if non_anchor_update_flag {
for (idx, counts) in ctx.mvc_view_ref_counts.iter().enumerate() {
let view = &mut views[idx];
for _ in 0..counts.num_non_anchor_refs_l0 {
view.non_anchor_ref_l0_flags.push(r.u(1)? == 1);
}
for _ in 0..counts.num_non_anchor_refs_l1 {
view.non_anchor_ref_l1_flags.push(r.u(1)? == 1);
}
}
}
views
} else {
Vec::new()
};
Ok(ViewDependencyChange {
seq_parameter_set_id,
anchor_update_flag,
non_anchor_update_flag,
views,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BaseViewTemporalHrdLayer {
pub temporal_id: u8,
pub timing_info: Option<BaseViewTemporalHrdTiming>,
pub nal_hrd_parameters: Option<HrdParameters>,
pub vcl_hrd_parameters: Option<HrdParameters>,
pub low_delay_hrd_flag: Option<bool>,
pub pic_struct_present_flag: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BaseViewTemporalHrdTiming {
pub num_units_in_tick: u32,
pub time_scale: u32,
pub fixed_frame_rate_flag: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BaseViewTemporalHrd {
pub layers: Vec<BaseViewTemporalHrdLayer>,
}
pub fn parse_base_view_temporal_hrd(payload: &[u8]) -> Result<BaseViewTemporalHrd, SeiError> {
let mut r = BitReader::new(payload);
let minus1 = r.ue()?;
if minus1 > 7 {
return Err(SeiError::BaseViewTemporalHrdLayerCountOutOfRange(minus1));
}
let count = (minus1 + 1) as usize;
let mut layers: Vec<BaseViewTemporalHrdLayer> = Vec::with_capacity(count);
for _ in 0..count {
let temporal_id = r.u(3)? as u8;
let timing_info = if r.u(1)? == 1 {
let num_units_in_tick = r.u(32)?;
let time_scale = r.u(32)?;
let fixed_frame_rate_flag = r.u(1)? == 1;
Some(BaseViewTemporalHrdTiming {
num_units_in_tick,
time_scale,
fixed_frame_rate_flag,
})
} else {
None
};
let nal_present = r.u(1)? == 1;
let nal_hrd_parameters = if nal_present {
Some(HrdParameters::parse(&mut r)?)
} else {
None
};
let vcl_present = r.u(1)? == 1;
let vcl_hrd_parameters = if vcl_present {
Some(HrdParameters::parse(&mut r)?)
} else {
None
};
let low_delay_hrd_flag = if nal_present || vcl_present {
Some(r.u(1)? == 1)
} else {
None
};
let pic_struct_present_flag = r.u(1)? == 1;
layers.push(BaseViewTemporalHrdLayer {
temporal_id,
timing_info,
nal_hrd_parameters,
vcl_hrd_parameters,
low_delay_hrd_flag,
pic_struct_present_flag,
});
}
Ok(BaseViewTemporalHrd { layers })
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NonRequiredViewComponent {
pub entries: Vec<NonRequiredViewComponentEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NonRequiredViewComponentEntry {
pub view_order_index: u16,
pub index_delta_minus1: Vec<u16>,
}
pub fn parse_non_required_view_component(
payload: &[u8],
) -> Result<NonRequiredViewComponent, SeiError> {
let mut r = BitReader::new(payload);
let num_info_entries_minus1 = r.ue()?;
if num_info_entries_minus1 > 1022 {
return Err(SeiError::NonRequiredViewComponentNumInfoEntriesOutOfRange(
num_info_entries_minus1,
));
}
let entry_count = (num_info_entries_minus1 as usize) + 1;
let mut entries: Vec<NonRequiredViewComponentEntry> = Vec::with_capacity(entry_count);
for i in 0..entry_count {
let view_order_index = r.ue()?;
if !(1..=1023).contains(&view_order_index) {
return Err(SeiError::NonRequiredViewComponentViewOrderIndexOutOfRange {
i,
got: view_order_index,
});
}
let num_non_required_view_components_minus1 = r.ue()?;
let voi_minus_1 = view_order_index - 1; if num_non_required_view_components_minus1 > voi_minus_1 {
return Err(SeiError::NonRequiredViewComponentCountOutOfRange {
i,
got: num_non_required_view_components_minus1,
view_order_index,
});
}
let inner_count = (num_non_required_view_components_minus1 as usize) + 1;
let mut index_delta_minus1: Vec<u16> = Vec::with_capacity(inner_count);
for j in 0..inner_count {
let idm1 = r.ue()?;
if idm1 > voi_minus_1 {
return Err(SeiError::NonRequiredViewComponentIndexDeltaOutOfRange {
i,
j,
got: idm1,
view_order_index,
});
}
index_delta_minus1.push(idm1 as u16);
}
entries.push(NonRequiredViewComponentEntry {
view_order_index: view_order_index as u16,
index_delta_minus1,
});
}
Ok(NonRequiredViewComponent { entries })
}
pub fn parse_multiview_view_position(payload: &[u8]) -> Result<MultiviewViewPosition, SeiError> {
let mut r = BitReader::new(payload);
let num_views_minus1 = r.ue()?;
if num_views_minus1 > 1023 {
return Err(SeiError::MultiviewViewPositionNumViewsOutOfRange(
num_views_minus1,
));
}
let count = (num_views_minus1 as usize) + 1;
let mut view_positions: Vec<u16> = Vec::with_capacity(count);
for i in 0..count {
let vp = r.ue()?;
if vp > 1023 {
return Err(SeiError::MultiviewViewPositionViewPositionOutOfRange { i, got: vp });
}
view_positions.push(vp as u16);
}
let extension_flag = r.u(1)? == 1;
Ok(MultiviewViewPosition {
view_positions,
extension_flag,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MultiviewAcquisitionInfo {
pub num_views_minus1: u16,
pub intrinsic: Option<IntrinsicCameraParams>,
pub extrinsic: Option<ExtrinsicCameraParams>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IntrinsicCameraParams {
pub intrinsic_params_equal_flag: bool,
pub prec_focal_length: u8,
pub prec_principal_point: u8,
pub prec_skew_factor: u8,
pub cameras: Vec<IntrinsicCamera>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IntrinsicCamera {
pub focal_length_x: FloatComponent,
pub focal_length_y: FloatComponent,
pub principal_point_x: FloatComponent,
pub principal_point_y: FloatComponent,
pub skew_factor: FloatComponent,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtrinsicCameraParams {
pub prec_rotation_param: u8,
pub prec_translation_param: u8,
pub cameras: Vec<ExtrinsicCamera>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExtrinsicCamera {
pub r: [[FloatComponent; 3]; 3],
pub t: [FloatComponent; 3],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FloatComponent {
pub sign: bool,
pub exponent: u8,
pub mantissa: u64,
pub mantissa_width: u8,
}
impl FloatComponent {
#[must_use]
pub fn to_f64(&self) -> f64 {
if self.exponent == 63 {
return f64::NAN;
}
let sign: f64 = if self.sign { -1.0 } else { 1.0 };
let v = self.mantissa_width as i32;
let n_over_2v = if v == 0 {
0.0
} else {
(self.mantissa as f64) / (2f64.powi(v))
};
if self.exponent == 0 {
sign * 2f64.powi(-30) * n_over_2v
} else {
sign * 2f64.powi((self.exponent as i32) - 31) * (1.0 + n_over_2v)
}
}
}
fn mantissa_width_g1325(prec: u8, e: u8) -> u8 {
debug_assert!(prec <= 31);
if e == 0 {
prec.saturating_sub(30)
} else if e == 63 {
0
} else {
let sum = e as i32 + prec as i32 - 31;
if sum <= 0 {
0
} else {
sum as u8
}
}
}
fn read_float_component(r: &mut BitReader<'_>, prec: u8) -> Result<FloatComponent, SeiError> {
let sign = r.u(1)? == 1;
let exponent = r.u(6)? as u8;
let v = mantissa_width_g1325(prec, exponent);
let mantissa: u64 = if v == 0 {
0
} else if v <= 32 {
r.u(v as u32)? as u64
} else {
let hi_bits = (v - 32) as u32;
let hi = r.u(hi_bits)? as u64;
let lo = r.u(32)? as u64;
(hi << 32) | lo
};
Ok(FloatComponent {
sign,
exponent,
mantissa,
mantissa_width: v,
})
}
pub fn parse_multiview_acquisition_info(
payload: &[u8],
) -> Result<MultiviewAcquisitionInfo, SeiError> {
let mut r = BitReader::new(payload);
let num_views_minus1 = r.ue()?;
if num_views_minus1 > 1023 {
return Err(SeiError::MultiviewAcquisitionInfoNumViewsOutOfRange(
num_views_minus1,
));
}
let num_views = (num_views_minus1 as usize) + 1;
let intrinsic_param_flag = r.u(1)? == 1;
let extrinsic_param_flag = r.u(1)? == 1;
let intrinsic = if intrinsic_param_flag {
let intrinsic_params_equal_flag = r.u(1)? == 1;
let prec_focal_length = read_prec_ue(&mut r, "prec_focal_length")?;
let prec_principal_point = read_prec_ue(&mut r, "prec_principal_point")?;
let prec_skew_factor = read_prec_ue(&mut r, "prec_skew_factor")?;
let cam_count = if intrinsic_params_equal_flag {
1
} else {
num_views
};
let mut cameras: Vec<IntrinsicCamera> = Vec::with_capacity(cam_count);
for _ in 0..cam_count {
let focal_length_x = read_float_component(&mut r, prec_focal_length)?;
let focal_length_y = read_float_component(&mut r, prec_focal_length)?;
let principal_point_x = read_float_component(&mut r, prec_principal_point)?;
let principal_point_y = read_float_component(&mut r, prec_principal_point)?;
let skew_factor = read_float_component(&mut r, prec_skew_factor)?;
cameras.push(IntrinsicCamera {
focal_length_x,
focal_length_y,
principal_point_x,
principal_point_y,
skew_factor,
});
}
Some(IntrinsicCameraParams {
intrinsic_params_equal_flag,
prec_focal_length,
prec_principal_point,
prec_skew_factor,
cameras,
})
} else {
None
};
let extrinsic = if extrinsic_param_flag {
let prec_rotation_param = read_prec_ue(&mut r, "prec_rotation_param")?;
let prec_translation_param = read_prec_ue(&mut r, "prec_translation_param")?;
let mut cameras: Vec<ExtrinsicCamera> = Vec::with_capacity(num_views);
for _ in 0..num_views {
let zero = FloatComponent {
sign: false,
exponent: 0,
mantissa: 0,
mantissa_width: 0,
};
let mut r_mat: [[FloatComponent; 3]; 3] = [[zero; 3]; 3];
let mut t_vec: [FloatComponent; 3] = [zero; 3];
for (j, row) in r_mat.iter_mut().enumerate() {
for cell in row.iter_mut() {
*cell = read_float_component(&mut r, prec_rotation_param)?;
}
t_vec[j] = read_float_component(&mut r, prec_translation_param)?;
}
cameras.push(ExtrinsicCamera { r: r_mat, t: t_vec });
}
Some(ExtrinsicCameraParams {
prec_rotation_param,
prec_translation_param,
cameras,
})
} else {
None
};
Ok(MultiviewAcquisitionInfo {
num_views_minus1: num_views_minus1 as u16,
intrinsic,
extrinsic,
})
}
fn read_prec_ue(r: &mut BitReader<'_>, field: &'static str) -> Result<u8, SeiError> {
let v = r.ue()?;
if v > 31 {
return Err(SeiError::MultiviewAcquisitionInfoPrecOutOfRange { field, got: v });
}
Ok(v as u8)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnsignedFloatComponent {
pub exponent: u8,
pub mantissa: u64,
pub mantissa_width: u8,
}
impl UnsignedFloatComponent {
#[must_use]
pub fn to_f64(&self) -> f64 {
if self.exponent == 63 {
return f64::NAN;
}
let v = self.mantissa_width as i32;
let n_over_2v = if v == 0 {
0.0
} else {
(self.mantissa as f64) / (2f64.powi(v))
};
if self.exponent == 0 {
2f64.powi(-30) * n_over_2v
} else {
2f64.powi((self.exponent as i32) - 31) * (1.0 + n_over_2v)
}
}
}
fn read_unsigned_float_component(
r: &mut BitReader<'_>,
prec: u8,
) -> Result<UnsignedFloatComponent, SeiError> {
let exponent = r.u(6)? as u8;
let v = mantissa_width_g1325(prec, exponent);
let mantissa: u64 = if v == 0 {
0
} else if v <= 32 {
r.u(v as u32)? as u64
} else {
let hi_bits = (v - 32) as u32;
let hi = r.u(hi_bits)? as u64;
let lo = r.u(32)? as u64;
(hi << 32) | lo
};
Ok(UnsignedFloatComponent {
exponent,
mantissa,
mantissa_width: v,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ThreeDimensionalReferenceDisplaysInfo {
pub prec_ref_baseline: u8,
pub prec_ref_display_width: u8,
pub prec_ref_viewing_dist: Option<u8>,
pub displays: Vec<ReferenceDisplay>,
pub extension_flag: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReferenceDisplay {
pub ref_baseline: UnsignedFloatComponent,
pub ref_display_width: UnsignedFloatComponent,
pub ref_viewing_distance: Option<UnsignedFloatComponent>,
pub additional_shift_present_flag: bool,
pub num_sample_shift_plus512: Option<u16>,
}
impl ReferenceDisplay {
#[must_use]
pub fn num_sample_shift(&self) -> Option<i16> {
self.num_sample_shift_plus512
.map(|biased| (biased as i32 - 512) as i16)
}
}
pub fn parse_three_dimensional_reference_displays_info(
payload: &[u8],
) -> Result<ThreeDimensionalReferenceDisplaysInfo, SeiError> {
let mut r = BitReader::new(payload);
let prec_ref_baseline = {
let v = r.ue()?;
if v > 31 {
return Err(
SeiError::ThreeDimensionalReferenceDisplaysInfoPrecOutOfRange {
field: "prec_ref_baseline",
got: v,
},
);
}
v as u8
};
let prec_ref_display_width = {
let v = r.ue()?;
if v > 31 {
return Err(
SeiError::ThreeDimensionalReferenceDisplaysInfoPrecOutOfRange {
field: "prec_ref_display_width",
got: v,
},
);
}
v as u8
};
let ref_viewing_distance_flag = r.u(1)? == 1;
let prec_ref_viewing_dist = if ref_viewing_distance_flag {
let v = r.ue()?;
if v > 31 {
return Err(
SeiError::ThreeDimensionalReferenceDisplaysInfoPrecOutOfRange {
field: "prec_ref_viewing_dist",
got: v,
},
);
}
Some(v as u8)
} else {
None
};
let num_ref_displays_minus1 = r.ue()?;
if num_ref_displays_minus1 > 31 {
return Err(
SeiError::ThreeDimensionalReferenceDisplaysInfoNumRefDisplaysOutOfRange(
num_ref_displays_minus1,
),
);
}
let num_ref_displays = (num_ref_displays_minus1 as usize) + 1;
let mut displays = Vec::with_capacity(num_ref_displays);
for _ in 0..num_ref_displays {
let ref_baseline = read_unsigned_float_component(&mut r, prec_ref_baseline)?;
let ref_display_width = read_unsigned_float_component(&mut r, prec_ref_display_width)?;
let ref_viewing_distance = if let Some(prec) = prec_ref_viewing_dist {
Some(read_unsigned_float_component(&mut r, prec)?)
} else {
None
};
let additional_shift_present_flag = r.u(1)? == 1;
let num_sample_shift_plus512 = if additional_shift_present_flag {
Some(r.u(10)? as u16)
} else {
None
};
displays.push(ReferenceDisplay {
ref_baseline,
ref_display_width,
ref_viewing_distance,
additional_shift_present_flag,
num_sample_shift_plus512,
});
}
let extension_flag = r.u(1)? == 1;
Ok(ThreeDimensionalReferenceDisplaysInfo {
prec_ref_baseline,
prec_ref_display_width,
prec_ref_viewing_dist,
displays,
extension_flag,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DepthFloatComponent {
pub sign: bool,
pub exponent: u8,
pub mantissa: u64,
pub mantissa_width: u8,
}
impl DepthFloatComponent {
#[must_use]
pub fn to_f64(&self) -> f64 {
if self.exponent == 127 {
return f64::NAN;
}
let sign: f64 = if self.sign { -1.0 } else { 1.0 };
let v = self.mantissa_width as i32;
let n_over_2v = if v == 0 {
0.0
} else {
(self.mantissa as f64) / (2f64.powi(v))
};
if self.exponent == 0 {
sign * 2f64.powi(-30) * n_over_2v
} else {
sign * 2f64.powi((self.exponent as i32) - 31) * (1.0 + n_over_2v)
}
}
}
fn read_depth_float_component(r: &mut BitReader<'_>) -> Result<DepthFloatComponent, SeiError> {
let sign = r.u(1)? == 1;
let exponent = r.u(7)? as u8;
let mantissa_len_minus1 = r.u(5)? as u8;
let mantissa_width = mantissa_len_minus1 + 1;
let mantissa = r.u(mantissa_width as u32)? as u64;
Ok(DepthFloatComponent {
sign,
exponent,
mantissa,
mantissa_width,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DepthRepresentationInfo {
pub all_views_equal_flag: bool,
pub z_near_flag: bool,
pub z_far_flag: bool,
pub z_axis_equal_flag: Option<bool>,
pub common_z_axis_reference_view: Option<u16>,
pub d_min_flag: bool,
pub d_max_flag: bool,
pub depth_representation_type: u32,
pub views: Vec<DepthRepresentationView>,
pub depth_nonlinear_representation_num_minus1: Option<u8>,
pub depth_nonlinear_representation_model: Vec<u16>,
}
impl DepthRepresentationInfo {
#[must_use]
pub fn depth_nonlinear_representation_num_segments(&self) -> Option<u8> {
self.depth_nonlinear_representation_num_minus1
.map(|num_minus1| num_minus1 + 2)
}
#[must_use]
pub fn depth_nonlinear_representation_model_len(&self) -> Option<u8> {
self.depth_nonlinear_representation_num_minus1
.map(|num_minus1| num_minus1 + 1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DepthRepresentationView {
pub depth_info_view_id: u16,
pub z_axis_reference_view: Option<u16>,
pub disparity_reference_view: Option<u16>,
pub z_near: Option<DepthFloatComponent>,
pub z_far: Option<DepthFloatComponent>,
pub d_min: Option<DepthFloatComponent>,
pub d_max: Option<DepthFloatComponent>,
}
pub fn parse_depth_representation_info(
payload: &[u8],
) -> Result<DepthRepresentationInfo, SeiError> {
let mut r = BitReader::new(payload);
let all_views_equal_flag = r.u(1)? == 1;
let num_views = if all_views_equal_flag {
1usize
} else {
let v = r.ue()?;
if v > 1023 {
return Err(SeiError::DepthRepresentationInfoNumViewsOutOfRange(v));
}
(v as usize) + 1
};
let z_near_flag = r.u(1)? == 1;
let z_far_flag = r.u(1)? == 1;
let (z_axis_equal_flag, common_z_axis_reference_view) = if z_near_flag || z_far_flag {
let z_axis_equal_flag = r.u(1)? == 1;
let common = if z_axis_equal_flag {
let v = r.ue()?;
if v > 1023 {
return Err(SeiError::DepthRepresentationInfoViewIdOutOfRange {
field: "common_z_axis_reference_view",
got: v,
});
}
Some(v as u16)
} else {
None
};
(Some(z_axis_equal_flag), common)
} else {
(None, None)
};
let d_min_flag = r.u(1)? == 1;
let d_max_flag = r.u(1)? == 1;
let depth_representation_type = r.ue()?;
let mut views = Vec::with_capacity(num_views);
for _ in 0..num_views {
let depth_info_view_id = {
let v = r.ue()?;
if v > 1023 {
return Err(SeiError::DepthRepresentationInfoViewIdOutOfRange {
field: "depth_info_view_id",
got: v,
});
}
v as u16
};
let z_axis_reference_view =
if (z_near_flag || z_far_flag) && z_axis_equal_flag == Some(false) {
let v = r.ue()?;
if v > 1023 {
return Err(SeiError::DepthRepresentationInfoViewIdOutOfRange {
field: "z_axis_reference_view",
got: v,
});
}
Some(v as u16)
} else {
None
};
let disparity_reference_view = if d_min_flag || d_max_flag {
let v = r.ue()?;
if v > 1023 {
return Err(SeiError::DepthRepresentationInfoViewIdOutOfRange {
field: "disparity_reference_view",
got: v,
});
}
Some(v as u16)
} else {
None
};
let z_near = if z_near_flag {
Some(read_depth_float_component(&mut r)?)
} else {
None
};
let z_far = if z_far_flag {
Some(read_depth_float_component(&mut r)?)
} else {
None
};
let d_min = if d_min_flag {
Some(read_depth_float_component(&mut r)?)
} else {
None
};
let d_max = if d_max_flag {
Some(read_depth_float_component(&mut r)?)
} else {
None
};
views.push(DepthRepresentationView {
depth_info_view_id,
z_axis_reference_view,
disparity_reference_view,
z_near,
z_far,
d_min,
d_max,
});
}
let (depth_nonlinear_representation_num_minus1, depth_nonlinear_representation_model) =
if depth_representation_type == 3 {
let num_minus1 = r.ue()?;
if num_minus1 > 62 {
return Err(SeiError::DepthRepresentationInfoNonlinearNumOutOfRange(
num_minus1,
));
}
let count = (num_minus1 as usize) + 1;
let mut model = Vec::with_capacity(count);
for i in 0..count {
let v = r.ue()?;
if v > 65535 {
return Err(SeiError::DepthRepresentationInfoNonlinearModelOutOfRange {
i,
got: v,
});
}
model.push(v as u16);
}
(Some(num_minus1 as u8), model)
} else {
(None, Vec::new())
};
Ok(DepthRepresentationInfo {
all_views_equal_flag,
z_near_flag,
z_far_flag,
z_axis_equal_flag,
common_z_axis_reference_view,
d_min_flag,
d_max_flag,
depth_representation_type,
views,
depth_nonlinear_representation_num_minus1,
depth_nonlinear_representation_model,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConstrainedDepthParameterSetIdentifier {
pub max_dps_id: u8,
pub max_dps_id_diff: u8,
}
pub fn parse_constrained_depth_parameter_set_identifier(
payload: &[u8],
) -> Result<ConstrainedDepthParameterSetIdentifier, SeiError> {
let mut r = BitReader::new(payload);
let max_dps_id = r.ue()?;
if max_dps_id > 62 {
return Err(SeiError::ConstrainedDepthParameterSetIdentifierMaxDpsIdOutOfRange(max_dps_id));
}
let max_dps_id_diff = r.ue()?;
if (max_dps_id_diff as u64) * 2 >= max_dps_id as u64 {
return Err(
SeiError::ConstrainedDepthParameterSetIdentifierDiffViolatesBound {
max_dps_id_diff,
max_dps_id,
},
);
}
Ok(ConstrainedDepthParameterSetIdentifier {
max_dps_id: max_dps_id as u8,
max_dps_id_diff: max_dps_id_diff as u8,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AlternativeDepthGvd {
pub num_constituent_views_gvd_minus1: u8,
pub depth_present_gvd_flag: bool,
pub z_gvd_flag: bool,
pub intrinsic_param_gvd_flag: bool,
pub rotation_gvd_flag: bool,
pub translation_gvd_flag: bool,
pub z_values: Vec<AlternativeDepthZ>,
pub prec_gvd_focal_length: Option<u8>,
pub prec_gvd_principal_point: Option<u8>,
pub prec_gvd_rotation_param: Option<u8>,
pub prec_gvd_translation_param: Option<u8>,
pub cameras: Vec<AlternativeDepthCamera>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AlternativeDepthZ {
pub z_near: DepthFloatComponent,
pub z_far: DepthFloatComponent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AlternativeDepthCamera {
pub intrinsic: Option<AlternativeDepthIntrinsic>,
pub rotation: Option<[[FloatComponent; 3]; 3]>,
pub translation_x: Option<FloatComponent>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AlternativeDepthIntrinsic {
pub focal_length_x: FloatComponent,
pub focal_length_y: FloatComponent,
pub principal_point_x: FloatComponent,
pub principal_point_y: FloatComponent,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AlternativeDepthInfo {
pub depth_type: u32,
pub gvd: Option<AlternativeDepthGvd>,
}
fn read_alt_depth_prec(r: &mut BitReader<'_>, field: &'static str) -> Result<u8, SeiError> {
let v = r.ue()?;
if v > 31 {
return Err(SeiError::AlternativeDepthInfoPrecOutOfRange { field, got: v });
}
Ok(v as u8)
}
pub fn parse_alternative_depth_info(payload: &[u8]) -> Result<AlternativeDepthInfo, SeiError> {
let mut r = BitReader::new(payload);
let depth_type = r.ue()?;
if depth_type != 0 {
return Ok(AlternativeDepthInfo {
depth_type,
gvd: None,
});
}
let num_constituent_views_gvd_minus1 = r.ue()?;
if num_constituent_views_gvd_minus1 > 3 {
return Err(SeiError::AlternativeDepthInfoNumConstituentViewsOutOfRange(
num_constituent_views_gvd_minus1,
));
}
let cam_count = (num_constituent_views_gvd_minus1 as usize) + 2;
let depth_present_gvd_flag = r.u(1)? == 1;
let z_gvd_flag = r.u(1)? == 1;
let intrinsic_param_gvd_flag = r.u(1)? == 1;
let rotation_gvd_flag = r.u(1)? == 1;
let translation_gvd_flag = r.u(1)? == 1;
let z_values = if z_gvd_flag {
let mut v = Vec::with_capacity(cam_count);
for _ in 0..cam_count {
let z_near = read_depth_float_component(&mut r)?;
let z_far = read_depth_float_component(&mut r)?;
v.push(AlternativeDepthZ { z_near, z_far });
}
v
} else {
Vec::new()
};
let prec_gvd_focal_length = if intrinsic_param_gvd_flag {
Some(read_alt_depth_prec(&mut r, "prec_gvd_focal_length")?)
} else {
None
};
let prec_gvd_principal_point = if intrinsic_param_gvd_flag {
Some(read_alt_depth_prec(&mut r, "prec_gvd_principal_point")?)
} else {
None
};
let prec_gvd_rotation_param = if rotation_gvd_flag {
Some(read_alt_depth_prec(&mut r, "prec_gvd_rotation_param")?)
} else {
None
};
let prec_gvd_translation_param = if translation_gvd_flag {
Some(read_alt_depth_prec(&mut r, "prec_gvd_translation_param")?)
} else {
None
};
let mut cameras: Vec<AlternativeDepthCamera> = Vec::with_capacity(cam_count);
for _ in 0..cam_count {
let intrinsic = if intrinsic_param_gvd_flag {
let prec_fl = prec_gvd_focal_length.unwrap();
let prec_pp = prec_gvd_principal_point.unwrap();
let focal_length_x = read_float_component(&mut r, prec_fl)?;
let focal_length_y = read_float_component(&mut r, prec_fl)?;
let principal_point_x = read_float_component(&mut r, prec_pp)?;
let principal_point_y = read_float_component(&mut r, prec_pp)?;
Some(AlternativeDepthIntrinsic {
focal_length_x,
focal_length_y,
principal_point_x,
principal_point_y,
})
} else {
None
};
let rotation = if rotation_gvd_flag {
let prec_rot = prec_gvd_rotation_param.unwrap();
let zero = FloatComponent {
sign: false,
exponent: 0,
mantissa: 0,
mantissa_width: 0,
};
let mut mat: [[FloatComponent; 3]; 3] = [[zero; 3]; 3];
for row in mat.iter_mut() {
for cell in row.iter_mut() {
*cell = read_float_component(&mut r, prec_rot)?;
}
}
Some(mat)
} else {
None
};
let translation_x = if translation_gvd_flag {
let prec_tr = prec_gvd_translation_param.unwrap();
Some(read_float_component(&mut r, prec_tr)?)
} else {
None
};
cameras.push(AlternativeDepthCamera {
intrinsic,
rotation,
translation_x,
});
}
Ok(AlternativeDepthInfo {
depth_type,
gvd: Some(AlternativeDepthGvd {
num_constituent_views_gvd_minus1: num_constituent_views_gvd_minus1 as u8,
depth_present_gvd_flag,
z_gvd_flag,
intrinsic_param_gvd_flag,
rotation_gvd_flag,
translation_gvd_flag,
z_values,
prec_gvd_focal_length,
prec_gvd_principal_point,
prec_gvd_rotation_param,
prec_gvd_translation_param,
cameras,
}),
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DepthGridPosition {
pub pos_x_fp: u32,
pub pos_x_dp: u8,
pub pos_x_sign: bool,
pub pos_y_fp: u32,
pub pos_y_dp: u8,
pub pos_y_sign: bool,
}
impl DepthGridPosition {
pub fn x_to_f64(&self) -> f64 {
let mag = (self.pos_x_fp as f64) / (1u64 << self.pos_x_dp) as f64;
if self.pos_x_sign {
-mag
} else {
mag
}
}
pub fn y_to_f64(&self) -> f64 {
let mag = (self.pos_y_fp as f64) / (1u64 << self.pos_y_dp) as f64;
if self.pos_y_sign {
-mag
} else {
mag
}
}
}
fn read_depth_grid_position(r: &mut BitReader) -> Result<DepthGridPosition, SeiError> {
let pos_x_fp = r.u(20)?;
let pos_x_dp = r.u(4)? as u8;
let pos_x_sign = r.u(1)? == 1;
let pos_y_fp = r.u(20)?;
let pos_y_dp = r.u(4)? as u8;
let pos_y_sign = r.u(1)? == 1;
Ok(DepthGridPosition {
pos_x_fp,
pos_x_dp,
pos_x_sign,
pos_y_fp,
pos_y_dp,
pos_y_sign,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DepthSamplingViewEntry {
pub depth_grid_view_id: u16,
pub position: DepthGridPosition,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DepthSamplingInfo {
pub dttsr_x_mul: u16,
pub dttsr_x_dp: u8,
pub dttsr_y_mul: u16,
pub dttsr_y_dp: u8,
pub per_view_depth_grid_pos_flag: bool,
pub num_video_plus_depth_views_minus1: Option<u16>,
pub views: Vec<DepthSamplingViewEntry>,
}
impl DepthSamplingInfo {
pub fn dttsr_x_to_f64(&self) -> f64 {
(self.dttsr_x_mul as f64) / (1u64 << self.dttsr_x_dp) as f64
}
pub fn dttsr_y_to_f64(&self) -> f64 {
(self.dttsr_y_mul as f64) / (1u64 << self.dttsr_y_dp) as f64
}
}
pub fn parse_depth_sampling_info(payload: &[u8]) -> Result<DepthSamplingInfo, SeiError> {
let mut r = BitReader::new(payload);
let dttsr_x_mul = r.u(16)? as u16;
if dttsr_x_mul == 0 {
return Err(SeiError::DepthSamplingInfoDttsrMulReserved { axis: "x" });
}
let dttsr_x_dp = r.u(4)? as u8;
let dttsr_y_mul = r.u(16)? as u16;
if dttsr_y_mul == 0 {
return Err(SeiError::DepthSamplingInfoDttsrMulReserved { axis: "y" });
}
let dttsr_y_dp = r.u(4)? as u8;
let per_view_depth_grid_pos_flag = r.u(1)? == 1;
let (num_video_plus_depth_views_minus1, views) = if per_view_depth_grid_pos_flag {
let num_minus1 = r.ue()?;
if num_minus1 > 1023 {
return Err(SeiError::DepthSamplingInfoNumViewsOutOfRange(num_minus1));
}
let count = (num_minus1 as usize) + 1;
let mut entries = Vec::with_capacity(count);
for i in 0..count {
let view_id = r.ue()?;
if view_id > 1023 {
return Err(SeiError::DepthSamplingInfoViewIdOutOfRange { i, got: view_id });
}
let position = read_depth_grid_position(&mut r)?;
entries.push(DepthSamplingViewEntry {
depth_grid_view_id: view_id as u16,
position,
});
}
(Some(num_minus1 as u16), entries)
} else {
let position = read_depth_grid_position(&mut r)?;
(
None,
vec![DepthSamplingViewEntry {
depth_grid_view_id: 0,
position,
}],
)
};
Ok(DepthSamplingInfo {
dttsr_x_mul,
dttsr_x_dp,
dttsr_y_mul,
dttsr_y_dp,
per_view_depth_grid_pos_flag,
num_video_plus_depth_views_minus1,
views,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DepthTimingOffset {
pub offset_len_minus1: u8,
pub depth_disp_delay_offset_fp: u32,
pub depth_disp_delay_offset_dp: u8,
}
impl DepthTimingOffset {
pub fn offset_clock_ticks(&self) -> f64 {
f64::from(self.depth_disp_delay_offset_fp)
/ 2f64.powi(i32::from(self.depth_disp_delay_offset_dp))
}
}
fn read_depth_timing_offset(r: &mut BitReader) -> Result<DepthTimingOffset, SeiError> {
let offset_len_minus1 = r.u(5)? as u8;
let depth_disp_delay_offset_fp = r.u(u32::from(offset_len_minus1) + 1)?;
let depth_disp_delay_offset_dp = r.u(6)? as u8;
Ok(DepthTimingOffset {
offset_len_minus1,
depth_disp_delay_offset_fp,
depth_disp_delay_offset_dp,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DepthTiming {
pub per_view_depth_timing_flag: bool,
pub offsets: Vec<DepthTimingOffset>,
}
pub fn parse_depth_timing(payload: &[u8], ctx: &SeiContext) -> Result<DepthTiming, SeiError> {
let mut r = BitReader::new(payload);
let per_view_depth_timing_flag = r.u(1)? == 1;
let offsets = if per_view_depth_timing_flag {
if ctx.num_depth_views == 0 {
return Err(SeiError::DepthTimingNumDepthViewsUnknown);
}
if ctx.num_depth_views > 1024 {
return Err(SeiError::DepthTimingNumDepthViewsOutOfRange(
ctx.num_depth_views,
));
}
let count = ctx.num_depth_views as usize;
let mut offsets = Vec::with_capacity(count);
for _ in 0..count {
offsets.push(read_depth_timing_offset(&mut r)?);
}
offsets
} else {
vec![read_depth_timing_offset(&mut r)?]
};
Ok(DepthTiming {
per_view_depth_timing_flag,
offsets,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SeiPayload {
BufferingPeriod(BufferingPeriod),
PicTiming(PicTiming),
FillerPayload,
UserDataRegisteredItuTT35(UserDataRegisteredItuTT35),
UserDataUnregistered(UserDataUnregistered),
PanScanRect(PanScanRect),
RecoveryPoint(RecoveryPoint),
DecRefPicMarkingRepetition(DecRefPicMarkingRepetition),
SparePic(SparePic),
FullFrameFreeze(FullFrameFreeze),
FullFrameFreezeRelease,
FullFrameSnapshot(FullFrameSnapshot),
FilmGrainCharacteristics(FilmGrainCharacteristics),
DeblockingFilterDisplayPreference(DeblockingFilterDisplayPreference),
PostFilterHint(PostFilterHint),
ToneMappingInfo(ToneMappingInfo),
FramePackingArrangement(FramePackingArrangement),
DisplayOrientation(DisplayOrientation),
MasteringDisplay(MasteringDisplayColourVolume),
ContentLightLevel(ContentLightLevelInfo),
AlternativeTransferCharacteristics(u8),
SceneInfo(SceneInfo),
SubSeqInfo(SubSeqInfo),
SubSeqLayerCharacteristics(SubSeqLayerCharacteristics),
SubSeqCharacteristics(SubSeqCharacteristics),
ProgressiveRefinementSegmentStart(ProgressiveRefinementSegmentStart),
ProgressiveRefinementSegmentEnd(ProgressiveRefinementSegmentEnd),
MotionConstrainedSliceGroupSet(MotionConstrainedSliceGroupSet),
StereoVideoInfo(StereoVideoInfo),
ColourRemappingInfo(ColourRemappingInfo),
ContentColourVolume(ContentColourVolume),
AmbientViewingEnvironment(AmbientViewingEnvironment),
EquirectangularProjection(EquirectangularProjection),
CubemapProjection(CubemapProjection),
SphereRotation(SphereRotation),
RegionWisePacking(RegionWisePacking),
OmniViewport(OmniViewport),
ShutterIntervalInfo(ShutterIntervalInfo),
MultiviewSceneInfo(MultiviewSceneInfo),
MultiviewAcquisitionInfo(MultiviewAcquisitionInfo),
NonRequiredViewComponent(NonRequiredViewComponent),
ViewDependencyChange(ViewDependencyChange),
OperationPointNotPresent(OperationPointNotPresent),
BaseViewTemporalHrd(BaseViewTemporalHrd),
MultiviewViewPosition(MultiviewViewPosition),
DepthRepresentationInfo(DepthRepresentationInfo),
ThreeDimensionalReferenceDisplaysInfo(ThreeDimensionalReferenceDisplaysInfo),
DepthTiming(DepthTiming),
DepthSamplingInfo(DepthSamplingInfo),
ConstrainedDepthParameterSetIdentifier(ConstrainedDepthParameterSetIdentifier),
AlternativeDepthInfo(AlternativeDepthInfo),
SeiManifest(SeiManifest),
SeiPrefixIndication(SeiPrefixIndication),
Unknown {
payload_type: u32,
payload: Vec<u8>,
},
}
pub fn parse_payload(
payload_type: u32,
payload: &[u8],
ctx: &SeiContext,
) -> Result<SeiPayload, SeiError> {
match payload_type {
0 => Ok(SeiPayload::BufferingPeriod(parse_buffering_period(
payload, ctx,
)?)),
1 => Ok(SeiPayload::PicTiming(parse_pic_timing(payload, ctx)?)),
2 => Ok(SeiPayload::PanScanRect(parse_pan_scan_rect(payload)?)),
3 => {
parse_filler_payload(payload)?;
Ok(SeiPayload::FillerPayload)
}
4 => Ok(SeiPayload::UserDataRegisteredItuTT35(
parse_user_data_registered_itu_t_t35(payload)?,
)),
5 => Ok(SeiPayload::UserDataUnregistered(
parse_user_data_unregistered(payload)?,
)),
6 => Ok(SeiPayload::RecoveryPoint(parse_recovery_point(payload)?)),
7 => Ok(SeiPayload::DecRefPicMarkingRepetition(
parse_dec_ref_pic_marking_repetition(payload, ctx)?,
)),
8 => Ok(SeiPayload::SparePic(parse_spare_pic(payload, ctx)?)),
9 => Ok(SeiPayload::SceneInfo(parse_scene_info(payload)?)),
10 => Ok(SeiPayload::SubSeqInfo(parse_sub_seq_info(payload)?)),
11 => Ok(SeiPayload::SubSeqLayerCharacteristics(
parse_sub_seq_layer_characteristics(payload)?,
)),
12 => Ok(SeiPayload::SubSeqCharacteristics(
parse_sub_seq_characteristics(payload)?,
)),
13 => Ok(SeiPayload::FullFrameFreeze(parse_full_frame_freeze(
payload,
)?)),
14 => Ok(SeiPayload::FullFrameFreezeRelease),
15 => Ok(SeiPayload::FullFrameSnapshot(parse_full_frame_snapshot(
payload,
)?)),
16 => Ok(SeiPayload::ProgressiveRefinementSegmentStart(
parse_progressive_refinement_segment_start(payload)?,
)),
17 => Ok(SeiPayload::ProgressiveRefinementSegmentEnd(
parse_progressive_refinement_segment_end(payload)?,
)),
18 => Ok(SeiPayload::MotionConstrainedSliceGroupSet(
parse_motion_constrained_slice_group_set(payload, ctx)?,
)),
19 => Ok(SeiPayload::FilmGrainCharacteristics(
parse_film_grain_characteristics(payload)?,
)),
20 => Ok(SeiPayload::DeblockingFilterDisplayPreference(
parse_deblocking_filter_display_preference(payload)?,
)),
21 => Ok(SeiPayload::StereoVideoInfo(parse_stereo_video_info(
payload,
)?)),
22 => Ok(SeiPayload::PostFilterHint(parse_post_filter_hint(payload)?)),
23 => Ok(SeiPayload::ToneMappingInfo(parse_tone_mapping_info(
payload,
)?)),
39 => Ok(SeiPayload::MultiviewSceneInfo(parse_multiview_scene_info(
payload,
)?)),
40 => Ok(SeiPayload::MultiviewAcquisitionInfo(
parse_multiview_acquisition_info(payload)?,
)),
41 => Ok(SeiPayload::NonRequiredViewComponent(
parse_non_required_view_component(payload)?,
)),
42 => Ok(SeiPayload::ViewDependencyChange(
parse_view_dependency_change(payload, ctx)?,
)),
43 => Ok(SeiPayload::OperationPointNotPresent(
parse_operation_point_not_present(payload)?,
)),
44 => Ok(SeiPayload::BaseViewTemporalHrd(
parse_base_view_temporal_hrd(payload)?,
)),
45 => Ok(SeiPayload::FramePackingArrangement(
parse_frame_packing_arrangement(payload)?,
)),
46 => Ok(SeiPayload::MultiviewViewPosition(
parse_multiview_view_position(payload)?,
)),
47 => Ok(SeiPayload::DisplayOrientation(parse_display_orientation(
payload,
)?)),
50 => Ok(SeiPayload::DepthRepresentationInfo(
parse_depth_representation_info(payload)?,
)),
51 => Ok(SeiPayload::ThreeDimensionalReferenceDisplaysInfo(
parse_three_dimensional_reference_displays_info(payload)?,
)),
52 => Ok(SeiPayload::DepthTiming(parse_depth_timing(payload, ctx)?)),
53 => Ok(SeiPayload::DepthSamplingInfo(parse_depth_sampling_info(
payload,
)?)),
54 => Ok(SeiPayload::ConstrainedDepthParameterSetIdentifier(
parse_constrained_depth_parameter_set_identifier(payload)?,
)),
181 => Ok(SeiPayload::AlternativeDepthInfo(
parse_alternative_depth_info(payload)?,
)),
137 => Ok(SeiPayload::MasteringDisplay(parse_mastering_display(
payload,
)?)),
142 => Ok(SeiPayload::ColourRemappingInfo(
parse_colour_remapping_info(payload)?,
)),
144 => Ok(SeiPayload::ContentLightLevel(parse_content_light_level(
payload,
)?)),
147 => Ok(SeiPayload::AlternativeTransferCharacteristics(
parse_alternative_transfer_characteristics(payload)?,
)),
148 => Ok(SeiPayload::AmbientViewingEnvironment(
parse_ambient_viewing_environment(payload)?,
)),
149 => Ok(SeiPayload::ContentColourVolume(
parse_content_colour_volume(payload)?,
)),
150 => Ok(SeiPayload::EquirectangularProjection(
parse_equirectangular_projection(payload)?,
)),
151 => Ok(SeiPayload::CubemapProjection(parse_cubemap_projection(
payload,
)?)),
154 => Ok(SeiPayload::SphereRotation(parse_sphere_rotation(payload)?)),
155 => Ok(SeiPayload::RegionWisePacking(parse_regionwise_packing(
payload,
)?)),
156 => Ok(SeiPayload::OmniViewport(parse_omni_viewport(payload)?)),
200 => Ok(SeiPayload::SeiManifest(parse_sei_manifest(payload)?)),
201 => Ok(SeiPayload::SeiPrefixIndication(
parse_sei_prefix_indication(payload)?,
)),
205 => Ok(SeiPayload::ShutterIntervalInfo(
parse_shutter_interval_info(payload)?,
)),
other => Ok(SeiPayload::Unknown {
payload_type: other,
payload: payload.to_vec(),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pack_bits(fields: &[(u64, u32)]) -> Vec<u8> {
let mut bits: Vec<u8> = Vec::new();
for &(value, width) in fields {
for i in (0..width).rev() {
bits.push(((value >> i) & 1) as u8);
}
}
while bits.len() % 8 != 0 {
bits.push(0);
}
bits.chunks(8)
.map(|c| c.iter().fold(0u8, |acc, &b| (acc << 1) | b))
.collect()
}
#[test]
fn buffering_period_nal_only_single_sched() {
let fields = [(1, 1), (0x012345, 24), (0x0ABCDE, 24)];
let payload = pack_bits(&fields);
let ctx = SeiContext {
initial_cpb_removal_delay_length_minus1: 23,
nal_hrd_cpb_cnt_minus1: Some(0), ..Default::default()
};
let bp = parse_buffering_period(&payload, &ctx).unwrap();
assert_eq!(bp.seq_parameter_set_id, 0);
assert!(bp.vcl_hrd.is_none());
let nal = bp.nal_hrd.expect("nal hrd present");
assert_eq!(nal.len(), 1);
assert_eq!(nal[0].initial_cpb_removal_delay, 0x012345);
assert_eq!(nal[0].initial_cpb_removal_delay_offset, 0x0ABCDE);
}
#[test]
fn pic_timing_with_delays_and_pic_struct_zero() {
let fields = [(5, 24), (10, 24), (0, 4), (0, 1)];
let payload = pack_bits(&fields);
let ctx = SeiContext {
cpb_removal_delay_length_minus1: 23,
dpb_output_delay_length_minus1: 23,
cpb_dpb_delays_present_flag: true,
pic_struct_present_flag: true,
time_offset_length: 24,
..Default::default()
};
let pt = parse_pic_timing(&payload, &ctx).unwrap();
assert_eq!(pt.cpb_removal_delay, 5);
assert_eq!(pt.dpb_output_delay, 10);
let info = pt.pic_struct.expect("pic_struct present");
assert_eq!(info.pic_struct, 0);
assert_eq!(info.clock_timestamps.len(), 1);
assert!(info.clock_timestamps[0].is_none());
}
#[test]
fn pic_timing_without_delays_or_pic_struct() {
let ctx = SeiContext::default();
let pt = parse_pic_timing(&[], &ctx).unwrap();
assert_eq!(pt.cpb_removal_delay, 0);
assert_eq!(pt.dpb_output_delay, 0);
assert!(pt.pic_struct.is_none());
}
#[test]
fn recovery_point_basic() {
let fields = [
(0b0001001, 7), (1, 1), (0, 1), (0, 2), ];
let payload = pack_bits(&fields);
let rp = parse_recovery_point(&payload).unwrap();
assert_eq!(rp.recovery_frame_cnt, 8);
assert!(rp.exact_match_flag);
assert!(!rp.broken_link_flag);
assert_eq!(rp.changing_slice_group_idc, 0);
}
#[test]
fn user_data_unregistered_roundtrip() {
let uuid: [u8; 16] = [
0xDC, 0x45, 0xE9, 0xBD, 0xE6, 0xD9, 0x48, 0xB7, 0x96, 0x2C, 0xD8, 0x20, 0xD9, 0x23,
0xEE, 0xEF,
];
let mut payload = Vec::new();
payload.extend_from_slice(&uuid);
payload.extend_from_slice(b"hello");
let udu = parse_user_data_unregistered(&payload).unwrap();
assert_eq!(udu.uuid, uuid);
assert_eq!(udu.user_data, b"hello");
}
#[test]
fn user_data_unregistered_too_short() {
let err = parse_user_data_unregistered(&[0u8; 15]).unwrap_err();
assert_eq!(err, SeiError::UserDataTooShort(15));
}
#[test]
fn filler_payload_all_ff() {
parse_filler_payload(&[0xFF; 5]).unwrap();
}
#[test]
fn filler_payload_bad_byte_reports_position() {
let mut bytes = [0xFFu8; 5];
bytes[2] = 0xFE;
let err = parse_filler_payload(&bytes).unwrap_err();
assert_eq!(err, SeiError::FillerNonFfAt(2));
}
#[test]
fn mastering_display_decodes_fields() {
let mut p: Vec<u8> = Vec::new();
fn push_u16(v: &mut Vec<u8>, x: u16) {
v.push((x >> 8) as u8);
v.push((x & 0xFF) as u8);
}
fn push_u32(v: &mut Vec<u8>, x: u32) {
v.extend_from_slice(&x.to_be_bytes());
}
for &(x, y) in &[(10000u16, 20000u16), (30000, 40000), (45000, 5000)] {
push_u16(&mut p, x);
push_u16(&mut p, y);
}
push_u16(&mut p, 31270);
push_u16(&mut p, 32900);
push_u32(&mut p, 10_000_000);
push_u32(&mut p, 500);
let m = parse_mastering_display(&p).unwrap();
assert_eq!(m.display_primaries[0], (10000, 20000));
assert_eq!(m.display_primaries[1], (30000, 40000));
assert_eq!(m.display_primaries[2], (45000, 5000));
assert_eq!(m.white_point, (31270, 32900));
assert_eq!(m.max_display_mastering_luminance, 10_000_000);
assert_eq!(m.min_display_mastering_luminance, 500);
}
#[test]
fn content_light_level_decodes_fields() {
let payload = [0x03, 0xE8, 0x01, 0x90]; let c = parse_content_light_level(&payload).unwrap();
assert_eq!(c.max_content_light_level, 1000);
assert_eq!(c.max_pic_average_light_level, 400);
}
#[test]
fn parse_payload_dispatches_unknown() {
let ctx = SeiContext::default();
let raw = vec![0x01, 0x02, 0x03];
let got = parse_payload(999, &raw, &ctx).unwrap();
match got {
SeiPayload::Unknown {
payload_type,
payload,
} => {
assert_eq!(payload_type, 999);
assert_eq!(payload, raw);
}
other => panic!("expected Unknown, got {:?}", other),
}
}
#[test]
fn parse_payload_dispatches_known_content_light_level() {
let ctx = SeiContext::default();
let payload = [0x03, 0xE8, 0x01, 0x90];
let got = parse_payload(144, &payload, &ctx).unwrap();
match got {
SeiPayload::ContentLightLevel(c) => {
assert_eq!(c.max_content_light_level, 1000);
assert_eq!(c.max_pic_average_light_level, 400);
}
other => panic!("expected ContentLightLevel, got {:?}", other),
}
}
#[test]
fn parse_payload_dispatches_filler() {
let ctx = SeiContext::default();
let got = parse_payload(3, &[0xFF; 4], &ctx).unwrap();
assert_eq!(got, SeiPayload::FillerPayload);
}
#[test]
fn registered_user_data_us_country_no_extension() {
let payload = [0xB5, 0x00, 0x31, b'G', b'A', b'9', b'4'];
let got = parse_user_data_registered_itu_t_t35(&payload).unwrap();
assert_eq!(got.country_code, 0xB5);
assert_eq!(got.country_code_extension, None);
assert_eq!(got.payload_bytes, &[0x00, 0x31, b'G', b'A', b'9', b'4']);
}
#[test]
fn registered_user_data_escape_country_takes_extension_byte() {
let payload = [0xFF, 0x42, 0x00, 0x01];
let got = parse_user_data_registered_itu_t_t35(&payload).unwrap();
assert_eq!(got.country_code, 0xFF);
assert_eq!(got.country_code_extension, Some(0x42));
assert_eq!(got.payload_bytes, &[0x00, 0x01]);
}
#[test]
fn registered_user_data_empty_payload_after_country_code_is_ok() {
let payload = [0xB5];
let got = parse_user_data_registered_itu_t_t35(&payload).unwrap();
assert_eq!(got.country_code, 0xB5);
assert_eq!(got.country_code_extension, None);
assert!(got.payload_bytes.is_empty());
}
#[test]
fn registered_user_data_empty_payload_rejected() {
let err = parse_user_data_registered_itu_t_t35(&[]).unwrap_err();
assert_eq!(err, SeiError::RegisteredUserDataMissingCountryCode);
}
#[test]
fn registered_user_data_escape_without_extension_rejected() {
let err = parse_user_data_registered_itu_t_t35(&[0xFF]).unwrap_err();
assert_eq!(err, SeiError::RegisteredUserDataMissingCountryCode);
}
#[test]
fn parse_payload_dispatches_registered_user_data() {
let ctx = SeiContext::default();
let payload = [0xB5, 0x00, 0x31, b'G', b'A', b'9', b'4'];
let got = parse_payload(4, &payload, &ctx).unwrap();
match got {
SeiPayload::UserDataRegisteredItuTT35(u) => {
assert_eq!(u.country_code, 0xB5);
assert_eq!(u.payload_bytes, &[0x00, 0x31, b'G', b'A', b'9', b'4']);
}
other => panic!("expected UserDataRegisteredItuTT35, got {:?}", other),
}
}
#[test]
fn pan_scan_rect_cancel_flag_only() {
let payload = pack_bits(&[(1, 1), (1, 1)]);
let got = parse_pan_scan_rect(&payload).unwrap();
assert_eq!(got.pan_scan_rect_id, 0);
assert!(got.cancel_flag);
assert!(got.body.is_none());
}
#[test]
fn pan_scan_rect_single_rectangle_with_offsets() {
let fields = [
(1, 1), (0, 1), (1, 1), (0b00100, 5),
(0b00101, 5),
(0b0001000, 7),
(0b0001001, 7),
(0b010, 3),
];
let payload = pack_bits(&fields);
let got = parse_pan_scan_rect(&payload).unwrap();
assert_eq!(got.pan_scan_rect_id, 0);
assert!(!got.cancel_flag);
let body = got.body.expect("body present when cancel_flag = 0");
assert_eq!(body.rects.len(), 1);
assert_eq!(body.rects[0].left_offset, 2);
assert_eq!(body.rects[0].right_offset, -2);
assert_eq!(body.rects[0].top_offset, 4);
assert_eq!(body.rects[0].bottom_offset, -4);
assert_eq!(body.repetition_period, 1);
}
#[test]
fn pan_scan_rect_pathological_count_rejected() {
let fields = [
(1, 1),
(0, 1),
(0b00000_100001, 11), ];
let payload = pack_bits(&fields);
let err = parse_pan_scan_rect(&payload).unwrap_err();
assert_eq!(err, SeiError::PanScanCountTooLarge(32));
}
#[test]
fn parse_payload_dispatches_pan_scan_rect() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(1, 1), (1, 1)]); let got = parse_payload(2, &payload, &ctx).unwrap();
match got {
SeiPayload::PanScanRect(p) => {
assert_eq!(p.pan_scan_rect_id, 0);
assert!(p.cancel_flag);
assert!(p.body.is_none());
}
other => panic!("expected PanScanRect, got {:?}", other),
}
}
#[test]
fn num_clock_ts_table_d1() {
assert_eq!(num_clock_ts_from_pic_struct(0), 1);
assert_eq!(num_clock_ts_from_pic_struct(1), 1);
assert_eq!(num_clock_ts_from_pic_struct(2), 1);
assert_eq!(num_clock_ts_from_pic_struct(3), 2);
assert_eq!(num_clock_ts_from_pic_struct(4), 2);
assert_eq!(num_clock_ts_from_pic_struct(5), 3);
assert_eq!(num_clock_ts_from_pic_struct(6), 3);
assert_eq!(num_clock_ts_from_pic_struct(7), 2);
assert_eq!(num_clock_ts_from_pic_struct(8), 3);
assert_eq!(num_clock_ts_from_pic_struct(9), 0); }
#[test]
fn frame_packing_arrangement_side_by_side() {
let fields = [
(1, 1), (0, 1), (3, 7), (0, 1), (1, 6), (0, 1), (0, 1), (0, 1), (1, 1), (1, 1), (0, 1), (0, 4), (0, 4), (0, 4), (0, 4), (0, 8), (1, 1), (0, 1), ];
let payload = pack_bits(&fields);
let fpa = parse_frame_packing_arrangement(&payload).unwrap();
assert_eq!(fpa.id, 0);
assert!(!fpa.cancel_flag);
assert_eq!(fpa.ty, 3);
assert!(!fpa.quincunx_sampling_flag);
assert_eq!(fpa.content_interpretation_type, 1);
assert!(fpa.current_frame_is_frame0_flag);
assert!(fpa.frame0_self_contained_flag);
assert!(!fpa.frame1_self_contained_flag);
assert_eq!(fpa.frame0_grid_position_x, 0);
assert_eq!(fpa.frame1_grid_position_y, 0);
assert_eq!(fpa.repetition_period, 0);
assert!(!fpa.extension_flag);
}
#[test]
fn frame_packing_arrangement_cancel_path() {
let fields = [
(1, 1), (1, 1), (1, 1), ];
let payload = pack_bits(&fields);
let fpa = parse_frame_packing_arrangement(&payload).unwrap();
assert!(fpa.cancel_flag);
assert!(fpa.extension_flag);
assert_eq!(fpa.ty, 0);
assert_eq!(fpa.repetition_period, 0);
}
#[test]
fn frame_packing_arrangement_type5_skips_grid() {
let fields = [
(1, 1), (0, 1), (5, 7), (0, 1), (0, 6), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0xAB, 8), (1, 1), (0, 1), ];
let payload = pack_bits(&fields);
let fpa = parse_frame_packing_arrangement(&payload).unwrap();
assert_eq!(fpa.ty, 5);
assert_eq!(fpa.reserved_byte, 0xAB);
}
#[test]
fn display_orientation_full_body() {
let fields = [
(0, 1), (1, 1), (0, 1), (0x8000, 16), (1, 1), (1, 1), ];
let payload = pack_bits(&fields);
let dor = parse_display_orientation(&payload).unwrap();
assert!(!dor.cancel_flag);
assert!(dor.hor_flip);
assert!(!dor.ver_flip);
assert_eq!(dor.anticlockwise_rotation, 0x8000);
assert_eq!(dor.repetition_period, 0);
assert!(dor.extension_flag);
}
#[test]
fn display_orientation_cancel_path() {
let payload = pack_bits(&[(1, 1)]);
let dor = parse_display_orientation(&payload).unwrap();
assert!(dor.cancel_flag);
assert!(!dor.hor_flip);
assert_eq!(dor.anticlockwise_rotation, 0);
}
#[test]
fn alternative_transfer_characteristics_pq() {
let payload = [16u8];
let got = parse_alternative_transfer_characteristics(&payload).unwrap();
assert_eq!(got, 16);
}
#[test]
fn tone_mapping_info_linear() {
let fields: [(u64, u32); 8] = [
(1, 1), (0, 1), (1, 1), (10, 8), (8, 8), (1, 1), (0x0000_0100, 32), (0x0000_03FF, 32), ];
let packed = pack_bits(&fields);
let tmi = parse_tone_mapping_info(&packed).unwrap();
assert_eq!(tmi.tone_map_id, 0);
assert!(!tmi.cancel_flag);
let body = tmi.body.as_ref().expect("body present");
assert_eq!(body.repetition_period, 0);
assert_eq!(body.coded_data_bit_depth, 10);
assert_eq!(body.target_bit_depth, 8);
assert_eq!(body.model_id, 0);
match &body.model {
ToneMappingModel::Linear {
min_value,
max_value,
} => {
assert_eq!(*min_value, 0x0000_0100);
assert_eq!(*max_value, 0x0000_03FF);
}
other => panic!("expected Linear, got {:?}", other),
}
}
#[test]
fn tone_mapping_info_cancel_path() {
let fields = [
(0b0001000, 7), (1, 1), ];
let payload = pack_bits(&fields);
let tmi = parse_tone_mapping_info(&payload).unwrap();
assert_eq!(tmi.tone_map_id, 7);
assert!(tmi.cancel_flag);
assert!(tmi.body.is_none());
}
#[test]
fn tone_mapping_piecewise_accessors_none_for_linear() {
let fields: [(u64, u32); 8] = [
(1, 1), (0, 1), (1, 1), (10, 8), (8, 8), (1, 1), (0x0000_0100, 32), (0x0000_03FF, 32), ];
let packed = pack_bits(&fields);
let body = parse_tone_mapping_info(&packed).unwrap().body.unwrap();
assert_eq!(body.model_id, 0);
assert!(body.piecewise_default_end_points().is_none());
assert!(body.piecewise_total_pivot_count().is_none());
}
#[test]
fn tone_mapping_piecewise_default_end_points_and_count() {
let fields: [(u64, u32); 11] = [
(1, 1), (0, 1), (1, 1), (10, 8), (8, 8), (0b00100, 5), (2, 16), (300, 16), (100, 8), (700, 16), (200, 8), ];
let packed = pack_bits(&fields);
let body = parse_tone_mapping_info(&packed).unwrap().body.unwrap();
assert_eq!(body.model_id, 3);
match &body.model {
ToneMappingModel::PiecewisePivots {
num_pivots,
coded_pivot_value,
target_pivot_value,
} => {
assert_eq!(*num_pivots, 2);
assert_eq!(coded_pivot_value, &vec![300, 700]);
assert_eq!(target_pivot_value, &vec![100, 200]);
}
other => panic!("expected PiecewisePivots, got {:?}", other),
}
let (start, end) = body.piecewise_default_end_points().unwrap();
assert_eq!(start, (0, 0));
assert_eq!(end, (1023, 255));
assert_eq!(body.piecewise_total_pivot_count(), Some(4));
}
#[test]
fn tone_mapping_piecewise_max_bit_depths_zero_pivots() {
let fields = vec![
(1u64, 1u32), (0, 1), (1, 1), (14, 8), (16, 8), (0b00100, 5), (0, 16), ];
let packed = pack_bits(&fields);
let body = parse_tone_mapping_info(&packed).unwrap().body.unwrap();
assert_eq!(body.model_id, 3);
let (start, end) = body.piecewise_default_end_points().unwrap();
assert_eq!(start, (0, 0));
assert_eq!(end, (16_383, 65_535));
assert_eq!(body.piecewise_total_pivot_count(), Some(2));
}
#[test]
fn film_grain_characteristics_basic() {
let fields = [
(0, 1), (1, 2), (0, 1), (0, 2), (4, 4), (1, 1), (0, 1), (0, 1), (0, 8), (0, 3), (16, 8), (235, 8), (1, 1), (1, 1), ];
let payload = pack_bits(&fields);
let fg = parse_film_grain_characteristics(&payload).unwrap();
assert!(!fg.cancel_flag);
let body = fg.body.as_ref().expect("body present");
assert_eq!(body.model_id, 1);
assert!(!body.separate_colour_description_present_flag);
assert!(body.separate_colour_description.is_none());
assert_eq!(body.blending_mode_id, 0);
assert_eq!(body.log2_scale_factor, 4);
assert_eq!(body.comp_model_present_flag, [true, false, false]);
assert_eq!(body.repetition_period, 0);
let luma = body.components[0].as_ref().expect("luma component present");
assert_eq!(luma.num_model_values_minus1, 0);
assert_eq!(luma.intensity_intervals.len(), 1);
assert_eq!(luma.intensity_intervals[0].lower_bound, 16);
assert_eq!(luma.intensity_intervals[0].upper_bound, 235);
assert_eq!(luma.intensity_intervals[0].model_values, vec![0]);
assert!(body.components[1].is_none());
assert!(body.components[2].is_none());
}
#[test]
fn film_grain_characteristics_cancel_path() {
let payload = pack_bits(&[(1, 1)]);
let fg = parse_film_grain_characteristics(&payload).unwrap();
assert!(fg.cancel_flag);
assert!(fg.body.is_none());
}
#[test]
fn film_grain_characteristics_with_separate_colour() {
let fields = [
(0, 1), (2, 2), (1, 1), (2, 3), (2, 3), (1, 1), (1, 8), (13, 8), (1, 8), (0, 2), (3, 4), (1, 1), (1, 1), (0, 1), (0, 8), (0, 3), (0, 8), (255, 8), (1, 1), (0, 8), (0, 3), (0, 8), (255, 8), (1, 1), (1, 1), ];
let payload = pack_bits(&fields);
let fg = parse_film_grain_characteristics(&payload).unwrap();
let body = fg.body.expect("body present");
assert_eq!(body.model_id, 2);
let scd = body.separate_colour_description.expect("scd present");
assert_eq!(scd.bit_depth_luma_minus8, 2);
assert_eq!(scd.bit_depth_chroma_minus8, 2);
assert!(scd.full_range_flag);
assert_eq!(scd.colour_primaries, 1);
assert_eq!(scd.transfer_characteristics, 13);
assert_eq!(scd.matrix_coefficients, 1);
assert_eq!(body.blending_mode_id, 0);
assert_eq!(body.log2_scale_factor, 3);
assert_eq!(body.comp_model_present_flag, [true, true, false]);
assert!(body.components[0].is_some());
assert!(body.components[1].is_some());
assert!(body.components[2].is_none());
assert_eq!(body.repetition_period, 0);
}
#[test]
fn film_grain_bit_depth_typical_10bit() {
let scd = FilmGrainSeparateColourDescription {
bit_depth_luma_minus8: 2,
bit_depth_chroma_minus8: 2,
full_range_flag: false,
colour_primaries: 1,
transfer_characteristics: 13,
matrix_coefficients: 1,
};
assert_eq!(scd.bit_depth_luma(), 10);
assert_eq!(scd.bit_depth_chroma(), 10);
}
#[test]
fn film_grain_bit_depth_minimum_8bit() {
let scd = FilmGrainSeparateColourDescription {
bit_depth_luma_minus8: 0,
bit_depth_chroma_minus8: 0,
full_range_flag: false,
colour_primaries: 2,
transfer_characteristics: 2,
matrix_coefficients: 2,
};
assert_eq!(scd.bit_depth_luma(), 8);
assert_eq!(scd.bit_depth_chroma(), 8);
}
#[test]
fn film_grain_bit_depth_u3_ceiling_and_asymmetric() {
let scd = FilmGrainSeparateColourDescription {
bit_depth_luma_minus8: 7,
bit_depth_chroma_minus8: 4,
full_range_flag: true,
colour_primaries: 1,
transfer_characteristics: 1,
matrix_coefficients: 1,
};
assert_eq!(scd.bit_depth_luma(), 15);
assert_eq!(scd.bit_depth_chroma(), 12);
}
#[test]
fn film_grain_bit_depth_parse_round_trip() {
let fields = [
(0, 1), (2, 2), (1, 1), (2, 3), (2, 3), (1, 1), (1, 8), (13, 8), (1, 8), (0, 2), (3, 4), (1, 1), (1, 1), (0, 1), (0, 8), (0, 3), (0, 8), (255, 8), (1, 1), (0, 8), (0, 3), (0, 8), (255, 8), (1, 1), (1, 1), ];
let payload = pack_bits(&fields);
let fg = parse_film_grain_characteristics(&payload).unwrap();
let scd = fg
.body
.expect("body present")
.separate_colour_description
.expect("scd present");
assert_eq!(scd.bit_depth_luma_minus8, 2);
assert_eq!(scd.bit_depth_chroma_minus8, 2);
assert_eq!(scd.bit_depth_luma(), 10);
assert_eq!(scd.bit_depth_chroma(), 10);
}
#[test]
fn film_grain_characteristics_multi_interval_multi_value() {
let fields = [
(0, 1), (1, 2), (0, 1), (0, 2), (5, 4), (0, 1), (1, 1), (0, 1), (2, 8), (1, 3), (0, 8), (84, 8), (0b010, 3), (0b011, 3), (85, 8),
(170, 8),
(0b00100, 5), (1, 1), (171, 8),
(255, 8),
(1, 1), (0b00101, 5), (0b010, 3), ];
let payload = pack_bits(&fields);
let fg = parse_film_grain_characteristics(&payload).unwrap();
let body = fg.body.expect("body present");
assert_eq!(body.repetition_period, 1);
assert!(body.components[0].is_none());
assert!(body.components[2].is_none());
let chroma = body.components[1].as_ref().expect("Cb component present");
assert_eq!(chroma.num_model_values_minus1, 1);
assert_eq!(chroma.intensity_intervals.len(), 3);
assert_eq!(chroma.intensity_intervals[0].lower_bound, 0);
assert_eq!(chroma.intensity_intervals[0].upper_bound, 84);
assert_eq!(chroma.intensity_intervals[0].model_values, vec![1, -1]);
assert_eq!(chroma.intensity_intervals[1].lower_bound, 85);
assert_eq!(chroma.intensity_intervals[1].upper_bound, 170);
assert_eq!(chroma.intensity_intervals[1].model_values, vec![2, 0]);
assert_eq!(chroma.intensity_intervals[2].lower_bound, 171);
assert_eq!(chroma.intensity_intervals[2].upper_bound, 255);
assert_eq!(chroma.intensity_intervals[2].model_values, vec![0, -2]);
}
#[test]
fn film_grain_characteristics_rejects_num_model_values_out_of_range() {
let fields = [
(0, 1), (0, 2), (0, 1), (0, 2), (0, 4), (1, 1), (0, 1),
(0, 1),
(0, 8), (6, 3), ];
let payload = pack_bits(&fields);
let err = parse_film_grain_characteristics(&payload).unwrap_err();
assert_eq!(
err,
SeiError::FilmGrainNumModelValuesOutOfRange {
component: 0,
got: 6
}
);
}
#[test]
fn parse_payload_dispatches_display_orientation() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(1, 1)]); let got = parse_payload(47, &payload, &ctx).unwrap();
match got {
SeiPayload::DisplayOrientation(d) => assert!(d.cancel_flag),
other => panic!("expected DisplayOrientation, got {:?}", other),
}
}
#[test]
fn parse_payload_dispatches_alt_transfer() {
let ctx = SeiContext::default();
let got = parse_payload(147, &[18u8], &ctx).unwrap();
match got {
SeiPayload::AlternativeTransferCharacteristics(v) => assert_eq!(v, 18),
other => panic!(
"expected AlternativeTransferCharacteristics, got {:?}",
other
),
}
}
#[test]
fn parse_payload_dispatches_frame_packing() {
let ctx = SeiContext::default();
let payload = pack_bits(&[
(1, 1), (1, 1), (0, 1), ]);
let got = parse_payload(45, &payload, &ctx).unwrap();
match got {
SeiPayload::FramePackingArrangement(f) => assert!(f.cancel_flag),
other => panic!("expected FramePackingArrangement, got {:?}", other),
}
}
#[test]
fn multiview_view_position_two_views_left_then_right() {
let payload = pack_bits(&[
(0b010, 3), (1, 1), (0b010, 3), (0, 1), ]);
let got = parse_multiview_view_position(&payload).unwrap();
assert_eq!(got.view_positions, vec![0u16, 1u16]);
assert!(!got.extension_flag);
}
#[test]
fn multiview_view_position_single_view_zero_position() {
let payload = pack_bits(&[(1, 1), (1, 1), (0, 1)]);
let got = parse_multiview_view_position(&payload).unwrap();
assert_eq!(got.view_positions, vec![0u16]);
assert!(!got.extension_flag);
}
#[test]
fn multiview_view_position_preserves_extension_flag_when_set() {
let payload = pack_bits(&[(1, 1), (1, 1), (1, 1)]);
let got = parse_multiview_view_position(&payload).unwrap();
assert_eq!(got.view_positions, vec![0u16]);
assert!(got.extension_flag);
}
#[test]
fn multiview_view_position_rejects_num_views_above_1023() {
let mut bits = Vec::new();
for _ in 0..10 {
bits.push((0, 1));
}
bits.push((1, 1));
for _ in 0..9 {
bits.push((0, 1));
}
bits.push((1, 1));
let payload = pack_bits(&bits);
let err = parse_multiview_view_position(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::MultiviewViewPositionNumViewsOutOfRange(1024)
));
}
#[test]
fn multiview_view_position_rejects_view_position_above_1023() {
let mut bits = vec![(1u64, 1u32)];
for _ in 0..10 {
bits.push((0, 1));
}
bits.push((1, 1));
for _ in 0..9 {
bits.push((0, 1));
}
bits.push((1, 1));
let payload = pack_bits(&bits);
let err = parse_multiview_view_position(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::MultiviewViewPositionViewPositionOutOfRange { i: 0, got: 1024 }
));
}
#[test]
fn parse_payload_dispatches_multiview_view_position() {
let ctx = SeiContext::default();
let payload = pack_bits(&[
(0b010, 3), (1, 1), (0b010, 3), (0, 1), ]);
let got = parse_payload(46, &payload, &ctx).unwrap();
match got {
SeiPayload::MultiviewViewPosition(m) => {
assert_eq!(m.view_positions, vec![0u16, 1u16]);
assert!(!m.extension_flag);
}
other => panic!("expected MultiviewViewPosition, got {:?}", other),
}
}
#[test]
fn multiview_scene_info_zero_disparity() {
let payload = pack_bits(&[(1, 1)]);
let got = parse_multiview_scene_info(&payload).unwrap();
assert_eq!(got.max_disparity, 0);
}
#[test]
fn multiview_scene_info_picks_up_typical_value() {
let payload = pack_bits(&[(0b000010001, 9)]);
let got = parse_multiview_scene_info(&payload).unwrap();
assert_eq!(got.max_disparity, 16);
}
#[test]
fn multiview_scene_info_accepts_boundary_1023() {
let mut bits: Vec<(u64, u32)> = Vec::new();
for _ in 0..10 {
bits.push((0, 1));
}
bits.push((1, 1));
for _ in 0..10 {
bits.push((0, 1));
}
let payload = pack_bits(&bits);
let got = parse_multiview_scene_info(&payload).unwrap();
assert_eq!(got.max_disparity, 1023);
}
#[test]
fn multiview_scene_info_rejects_max_disparity_above_1023() {
let mut bits: Vec<(u64, u32)> = Vec::new();
for _ in 0..10 {
bits.push((0, 1));
}
bits.push((1, 1));
for _ in 0..9 {
bits.push((0, 1));
}
bits.push((1, 1));
let payload = pack_bits(&bits);
let err = parse_multiview_scene_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::MultiviewSceneInfoMaxDisparityOutOfRange(1024)
));
}
#[test]
fn parse_payload_dispatches_multiview_scene_info() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(0b0001000, 7)]);
let got = parse_payload(39, &payload, &ctx).unwrap();
match got {
SeiPayload::MultiviewSceneInfo(m) => assert_eq!(m.max_disparity, 7),
other => panic!("expected MultiviewSceneInfo, got {:?}", other),
}
}
#[test]
fn operation_point_not_present_empty_list() {
let payload = pack_bits(&[(1, 1)]);
let got = parse_operation_point_not_present(&payload).unwrap();
assert!(got.operation_point_not_present_ids.is_empty());
}
#[test]
fn operation_point_not_present_three_ids() {
let mut bits: Vec<(u64, u32)> = vec![(0b00100, 5), (1, 1), (0b010, 3)];
for _ in 0..16 {
bits.push((0, 1));
}
bits.push((1, 1));
for _ in 0..16 {
bits.push((0, 1));
}
let payload = pack_bits(&bits);
let got = parse_operation_point_not_present(&payload).unwrap();
assert_eq!(
got.operation_point_not_present_ids,
vec![0u16, 1u16, 65535u16]
);
}
#[test]
fn operation_point_not_present_rejects_id_above_65535() {
let mut bits: Vec<(u64, u32)> = vec![(0b010, 3)];
for _ in 0..16 {
bits.push((0, 1));
}
bits.push((1, 1));
for _ in 0..15 {
bits.push((0, 1));
}
bits.push((1, 1));
let payload = pack_bits(&bits);
let err = parse_operation_point_not_present(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::OperationPointNotPresentIdOutOfRange { i: 0, got: 65536 }
));
}
#[test]
fn operation_point_not_present_rejects_count_above_65536() {
let mut bits: Vec<(u64, u32)> = Vec::new();
for _ in 0..16 {
bits.push((0, 1));
}
bits.push((1, 1));
for _ in 0..14 {
bits.push((0, 1));
}
bits.push((1, 1));
bits.push((0, 1));
let payload = pack_bits(&bits);
let err = parse_operation_point_not_present(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::OperationPointNotPresentCountOutOfRange(65537)
));
}
#[test]
fn parse_payload_dispatches_operation_point_not_present() {
let ctx = SeiContext::default();
let mut bits: Vec<(u64, u32)> = vec![(0b011, 3), (0b0001011, 7)];
for _ in 0..7 {
bits.push((0, 1));
}
bits.push((1, 1));
bits.push((1, 1));
bits.push((0, 1));
bits.push((0, 1));
bits.push((1, 1));
bits.push((0, 1));
bits.push((0, 1));
bits.push((1, 1));
let payload = pack_bits(&bits);
let got = parse_payload(43, &payload, &ctx).unwrap();
match got {
SeiPayload::OperationPointNotPresent(o) => {
assert_eq!(o.operation_point_not_present_ids, vec![10u16, 200u16]);
}
other => panic!("expected OperationPointNotPresent, got {:?}", other),
}
}
fn vdc_ctx_two_views() -> SeiContext {
SeiContext {
mvc_view_ref_counts: vec![
MvcViewRefCounts {
num_anchor_refs_l0: 1,
num_anchor_refs_l1: 0,
num_non_anchor_refs_l0: 2,
num_non_anchor_refs_l1: 1,
},
MvcViewRefCounts {
num_anchor_refs_l0: 2,
num_anchor_refs_l1: 1,
num_non_anchor_refs_l0: 0,
num_non_anchor_refs_l1: 1,
},
],
..Default::default()
}
}
#[test]
fn view_dependency_change_no_updates_reads_no_flags() {
let payload = pack_bits(&[(1, 1), (0, 1), (0, 1)]);
let got = parse_view_dependency_change(&payload, &SeiContext::default()).unwrap();
assert_eq!(got.seq_parameter_set_id, 0);
assert!(!got.anchor_update_flag);
assert!(!got.non_anchor_update_flag);
assert!(got.views.is_empty());
}
#[test]
fn view_dependency_change_anchor_only() {
let payload = pack_bits(&[
(0b010, 3), (1, 1), (0, 1), (1, 1), (0, 1), (1, 1), (1, 1), ]);
let got = parse_view_dependency_change(&payload, &vdc_ctx_two_views()).unwrap();
assert_eq!(got.seq_parameter_set_id, 1);
assert!(got.anchor_update_flag);
assert!(!got.non_anchor_update_flag);
assert_eq!(got.views.len(), 2);
assert_eq!(got.views[0].anchor_ref_l0_flags, vec![true]);
assert!(got.views[0].anchor_ref_l1_flags.is_empty());
assert_eq!(got.views[1].anchor_ref_l0_flags, vec![false, true]);
assert_eq!(got.views[1].anchor_ref_l1_flags, vec![true]);
assert!(got.views[0].non_anchor_ref_l0_flags.is_empty());
assert!(got.views[1].non_anchor_ref_l1_flags.is_empty());
}
#[test]
fn view_dependency_change_both_updates_separate_loops() {
let payload = pack_bits(&[
(1, 1), (1, 1), (1, 1), (1, 1), (0, 1), (1, 1), (0, 1), (1, 1), (0, 1), (1, 1), (0, 1), ]);
let got = parse_view_dependency_change(&payload, &vdc_ctx_two_views()).unwrap();
assert!(got.anchor_update_flag);
assert!(got.non_anchor_update_flag);
assert_eq!(got.views[0].anchor_ref_l0_flags, vec![true]);
assert_eq!(got.views[1].anchor_ref_l0_flags, vec![false, true]);
assert_eq!(got.views[1].anchor_ref_l1_flags, vec![false]);
assert_eq!(got.views[0].non_anchor_ref_l0_flags, vec![true, false]);
assert_eq!(got.views[0].non_anchor_ref_l1_flags, vec![true]);
assert!(got.views[1].non_anchor_ref_l0_flags.is_empty());
assert_eq!(got.views[1].non_anchor_ref_l1_flags, vec![false]);
}
#[test]
fn view_dependency_change_update_without_context_rejected() {
let payload = pack_bits(&[(1, 1), (1, 1), (0, 1)]);
let err = parse_view_dependency_change(&payload, &SeiContext::default()).unwrap_err();
assert!(matches!(
err,
SeiError::ViewDependencyChangeRefCountsUnknown
));
}
#[test]
fn view_dependency_change_rejects_sps_id_above_31() {
let mut bits: Vec<(u64, u32)> = Vec::new();
for _ in 0..5 {
bits.push((0, 1));
}
bits.push((1, 1));
for _ in 0..4 {
bits.push((0, 1));
}
bits.push((1, 1));
let payload = pack_bits(&bits);
let err = parse_view_dependency_change(&payload, &SeiContext::default()).unwrap_err();
assert!(matches!(
err,
SeiError::ViewDependencyChangeSpsIdOutOfRange(32)
));
}
#[test]
fn parse_payload_dispatches_view_dependency_change() {
let ctx = vdc_ctx_two_views();
let payload = pack_bits(&[
(0b010, 3), (1, 1), (0, 1), (1, 1), (0, 1), (1, 1), (1, 1), ]);
let got = parse_payload(42, &payload, &ctx).unwrap();
match got {
SeiPayload::ViewDependencyChange(v) => {
assert_eq!(v.seq_parameter_set_id, 1);
assert_eq!(v.views.len(), 2);
assert_eq!(v.views[1].anchor_ref_l0_flags, vec![false, true]);
}
other => panic!("expected ViewDependencyChange, got {:?}", other),
}
}
fn minimal_hrd_bits() -> Vec<(u64, u32)> {
vec![
(1, 1), (0, 4), (0, 4), (1, 1), (1, 1), (0, 1), (0, 5), (0, 5), (0, 5), (0, 5), ]
}
#[test]
fn base_view_temporal_hrd_single_layer_no_timing_no_hrd() {
let payload = pack_bits(&[(1, 1), (5, 3), (0, 1), (0, 1), (0, 1), (1, 1)]);
let got = parse_base_view_temporal_hrd(&payload).unwrap();
assert_eq!(got.layers.len(), 1);
let l = &got.layers[0];
assert_eq!(l.temporal_id, 5);
assert!(l.timing_info.is_none());
assert!(l.nal_hrd_parameters.is_none());
assert!(l.vcl_hrd_parameters.is_none());
assert_eq!(l.low_delay_hrd_flag, None);
assert!(l.pic_struct_present_flag);
}
#[test]
fn base_view_temporal_hrd_layer_with_timing_and_nal_hrd() {
let mut bits: Vec<(u64, u32)> = vec![
(1, 1),
(2, 3),
(1, 1),
(1001, 32),
(60000, 32),
(1, 1),
(1, 1), ];
bits.extend(minimal_hrd_bits());
bits.push((0, 1)); bits.push((1, 1)); bits.push((0, 1)); let payload = pack_bits(&bits);
let got = parse_base_view_temporal_hrd(&payload).unwrap();
assert_eq!(got.layers.len(), 1);
let l = &got.layers[0];
assert_eq!(l.temporal_id, 2);
let t = l.timing_info.as_ref().expect("timing present");
assert_eq!(t.num_units_in_tick, 1001);
assert_eq!(t.time_scale, 60000);
assert!(t.fixed_frame_rate_flag);
let nal = l.nal_hrd_parameters.as_ref().expect("nal hrd present");
assert_eq!(nal.cpb_cnt_minus1, 0);
assert!(l.vcl_hrd_parameters.is_none());
assert_eq!(l.low_delay_hrd_flag, Some(true));
assert!(!l.pic_struct_present_flag);
}
#[test]
fn base_view_temporal_hrd_two_layers_via_dispatch() {
let payload = pack_bits(&[
(0b010, 3),
(0, 3),
(0, 1),
(0, 1),
(0, 1),
(0, 1), (1, 3),
(0, 1),
(0, 1),
(0, 1),
(1, 1), ]);
let ctx = SeiContext::default();
let got = parse_payload(44, &payload, &ctx).unwrap();
match got {
SeiPayload::BaseViewTemporalHrd(b) => {
assert_eq!(b.layers.len(), 2);
assert_eq!(b.layers[0].temporal_id, 0);
assert!(!b.layers[0].pic_struct_present_flag);
assert_eq!(b.layers[1].temporal_id, 1);
assert!(b.layers[1].pic_struct_present_flag);
}
other => panic!("expected BaseViewTemporalHrd, got {:?}", other),
}
}
#[test]
fn base_view_temporal_hrd_rejects_layer_count_above_8() {
let payload = pack_bits(&[(0b0001001, 7)]);
let err = parse_base_view_temporal_hrd(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::BaseViewTemporalHrdLayerCountOutOfRange(8)
));
}
#[test]
fn non_required_view_component_single_entry_single_component() {
let payload = pack_bits(&[(1, 1), (0b010, 3), (1, 1), (1, 1)]);
let got = parse_non_required_view_component(&payload).unwrap();
assert_eq!(got.entries.len(), 1);
assert_eq!(got.entries[0].view_order_index, 1);
assert_eq!(got.entries[0].index_delta_minus1, vec![0u16]);
}
#[test]
fn non_required_view_component_two_entries_with_multiple_deltas() {
let payload = pack_bits(&[
(0b010, 3),
(0b011, 3),
(0b010, 3),
(1, 1),
(0b010, 3),
(0b00100, 5),
(1, 1),
(0b011, 3),
]);
let got = parse_non_required_view_component(&payload).unwrap();
assert_eq!(got.entries.len(), 2);
assert_eq!(got.entries[0].view_order_index, 2);
assert_eq!(got.entries[0].index_delta_minus1, vec![0u16, 1u16]);
assert_eq!(got.entries[1].view_order_index, 3);
assert_eq!(got.entries[1].index_delta_minus1, vec![2u16]);
}
#[test]
fn non_required_view_component_rejects_view_order_index_zero() {
let payload = pack_bits(&[(1, 1), (1, 1)]);
let err = parse_non_required_view_component(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::NonRequiredViewComponentViewOrderIndexOutOfRange { i: 0, got: 0 }
));
}
#[test]
fn non_required_view_component_rejects_num_info_entries_above_bound() {
let mut bits: Vec<(u64, u32)> = Vec::new();
for _ in 0..10 {
bits.push((0, 1));
}
bits.push((1, 1));
for _ in 0..10 {
bits.push((0, 1));
}
let payload = pack_bits(&bits);
let err = parse_non_required_view_component(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::NonRequiredViewComponentNumInfoEntriesOutOfRange(1023)
));
}
#[test]
fn non_required_view_component_rejects_inner_count_above_voi_minus_one() {
let payload = pack_bits(&[(1, 1), (0b010, 3), (0b010, 3)]);
let err = parse_non_required_view_component(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::NonRequiredViewComponentCountOutOfRange {
i: 0,
got: 1,
view_order_index: 1
}
));
}
#[test]
fn non_required_view_component_rejects_index_delta_above_voi_minus_one() {
let payload = pack_bits(&[(1, 1), (0b011, 3), (1, 1), (0b011, 3)]);
let err = parse_non_required_view_component(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::NonRequiredViewComponentIndexDeltaOutOfRange {
i: 0,
j: 0,
got: 2,
view_order_index: 2
}
));
}
#[test]
fn parse_payload_dispatches_non_required_view_component() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(1, 1), (0b010, 3), (1, 1), (1, 1)]);
let got = parse_payload(41, &payload, &ctx).unwrap();
match got {
SeiPayload::NonRequiredViewComponent(n) => {
assert_eq!(n.entries.len(), 1);
assert_eq!(n.entries[0].view_order_index, 1);
assert_eq!(n.entries[0].index_delta_minus1, vec![0u16]);
}
other => panic!("expected NonRequiredViewComponent, got {:?}", other),
}
}
#[test]
fn multiview_acquisition_info_no_flags_set() {
let payload = pack_bits(&[(1, 1), (0, 1), (0, 1)]);
let info = parse_multiview_acquisition_info(&payload).unwrap();
assert_eq!(info.num_views_minus1, 0);
assert!(info.intrinsic.is_none());
assert!(info.extrinsic.is_none());
}
#[test]
fn multiview_acquisition_info_intrinsic_equal_flag_single_camera() {
let mut fields: Vec<(u64, u32)> = vec![
(0b010, 3), (1, 1), (0, 1), (1, 1), (0b010, 3), (0b010, 3), (0b010, 3), ];
for _ in 0..5 {
fields.push((0, 1)); fields.push((30, 6)); }
let payload = pack_bits(&fields);
let info = parse_multiview_acquisition_info(&payload).unwrap();
assert_eq!(info.num_views_minus1, 1);
let intr = info.intrinsic.expect("intrinsic block populated");
assert!(intr.intrinsic_params_equal_flag);
assert_eq!(intr.prec_focal_length, 1);
assert_eq!(intr.prec_principal_point, 1);
assert_eq!(intr.prec_skew_factor, 1);
assert_eq!(intr.cameras.len(), 1);
let cam = intr.cameras[0];
assert_eq!(cam.focal_length_x.exponent, 30);
assert_eq!(cam.focal_length_x.mantissa_width, 0);
assert_eq!(cam.focal_length_x.mantissa, 0);
assert!(!cam.focal_length_x.sign);
assert!(info.extrinsic.is_none());
}
#[test]
fn multiview_acquisition_info_extrinsic_only_single_view() {
let mut fields: Vec<(u64, u32)> = vec![
(1, 1), (0, 1), (1, 1), (1, 1), (1, 1), ];
let pattern = [
(0u64, 11u64),
(1, 12),
(0, 13),
(1, 21),
(0, 22),
(1, 23),
(0, 14),
(1, 24),
(0, 31),
];
let t_pattern = [(1u64, 1u64), (0, 2), (1, 3)];
for j in 0..3 {
for k in 0..3 {
let (s, e) = pattern[j * 3 + k];
fields.push((s, 1));
fields.push((e, 6));
}
let (s, e) = t_pattern[j];
fields.push((s, 1));
fields.push((e, 6));
}
let payload = pack_bits(&fields);
let info = parse_multiview_acquisition_info(&payload).unwrap();
assert_eq!(info.num_views_minus1, 0);
assert!(info.intrinsic.is_none());
let extr = info.extrinsic.expect("extrinsic block populated");
assert_eq!(extr.prec_rotation_param, 0);
assert_eq!(extr.prec_translation_param, 0);
assert_eq!(extr.cameras.len(), 1);
let cam = extr.cameras[0];
assert_eq!(cam.r[0][0].exponent, 11);
assert!(!cam.r[0][0].sign);
assert_eq!(cam.r[2][2].exponent, 31);
assert_eq!(cam.r[2][2].mantissa_width, 0);
assert_eq!(cam.t[0].exponent, 1);
assert!(cam.t[0].sign);
assert_eq!(cam.t[2].exponent, 3);
}
#[test]
fn multiview_acquisition_info_rejects_num_views_above_1023() {
let payload = pack_bits(&[(0, 10), (1, 1), (0b0000000001, 10)]);
let err = parse_multiview_acquisition_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::MultiviewAcquisitionInfoNumViewsOutOfRange(1024)
));
}
#[test]
fn multiview_acquisition_info_rejects_prec_focal_length_above_31() {
let payload = pack_bits(&[
(1, 1), (1, 1), (0, 1), (1, 1), (0, 5),
(0b100001, 6),
]);
let err = parse_multiview_acquisition_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::MultiviewAcquisitionInfoPrecOutOfRange {
field: "prec_focal_length",
got: 32
}
));
}
#[test]
fn multiview_acquisition_info_mantissa_width_formula() {
assert_eq!(mantissa_width_g1325(0, 0), 0); assert_eq!(mantissa_width_g1325(31, 0), 1); assert_eq!(mantissa_width_g1325(30, 0), 0); assert_eq!(mantissa_width_g1325(0, 1), 0); assert_eq!(mantissa_width_g1325(31, 1), 1); assert_eq!(mantissa_width_g1325(31, 62), 62); assert_eq!(mantissa_width_g1325(31, 63), 0); assert_eq!(mantissa_width_g1325(0, 63), 0); }
#[test]
fn float_component_to_f64_branches() {
let denormal = FloatComponent {
sign: false,
exponent: 0,
mantissa: 1,
mantissa_width: 1,
};
let expected_denormal = 2f64.powi(-31);
assert!((denormal.to_f64() - expected_denormal).abs() < 1e-300);
let unity = FloatComponent {
sign: false,
exponent: 31,
mantissa: 0,
mantissa_width: 0,
};
assert_eq!(unity.to_f64(), 1.0);
let neg_three = FloatComponent {
sign: true,
exponent: 32,
mantissa: 0x8000_0000,
mantissa_width: 32,
};
assert_eq!(neg_three.to_f64(), -3.0);
let unspec = FloatComponent {
sign: false,
exponent: 63,
mantissa: 0,
mantissa_width: 0,
};
assert!(unspec.to_f64().is_nan());
}
#[test]
fn parse_payload_dispatches_multiview_acquisition_info() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(1, 1), (0, 1), (0, 1)]);
let got = parse_payload(40, &payload, &ctx).unwrap();
match got {
SeiPayload::MultiviewAcquisitionInfo(m) => {
assert_eq!(m.num_views_minus1, 0);
assert!(m.intrinsic.is_none());
assert!(m.extrinsic.is_none());
}
other => panic!("expected MultiviewAcquisitionInfo, got {other:?}"),
}
}
#[test]
fn three_dimensional_reference_displays_info_minimum_single_display() {
let payload = pack_bits(&[
(1, 1), (1, 1), (0, 1), (1, 1), (31, 6), (31, 6), (0, 1), (0, 1), ]);
let info = parse_three_dimensional_reference_displays_info(&payload).unwrap();
assert_eq!(info.prec_ref_baseline, 0);
assert_eq!(info.prec_ref_display_width, 0);
assert!(info.prec_ref_viewing_dist.is_none());
assert_eq!(info.displays.len(), 1);
let d = info.displays[0];
assert_eq!(d.ref_baseline.exponent, 31);
assert_eq!(d.ref_baseline.mantissa_width, 0);
assert_eq!(d.ref_display_width.exponent, 31);
assert!(d.ref_viewing_distance.is_none());
assert!(!d.additional_shift_present_flag);
assert!(d.num_sample_shift_plus512.is_none());
assert!(!info.extension_flag);
}
#[test]
fn three_dimensional_reference_displays_info_two_displays_with_viewing_distance() {
let mut fields: Vec<(u64, u32)> = vec![
(1, 1), (1, 1), (1, 1), (1, 1), (0b010, 3), ];
fields.push((20, 6)); fields.push((21, 6)); fields.push((22, 6)); fields.push((1, 1)); fields.push((768, 10));
fields.push((10, 6)); fields.push((11, 6)); fields.push((12, 6)); fields.push((0, 1)); fields.push((0, 1));
let payload = pack_bits(&fields);
let info = parse_three_dimensional_reference_displays_info(&payload).unwrap();
assert_eq!(info.prec_ref_viewing_dist, Some(0));
assert_eq!(info.displays.len(), 2);
let d0 = info.displays[0];
assert_eq!(d0.ref_baseline.exponent, 20);
assert_eq!(d0.ref_display_width.exponent, 21);
assert_eq!(d0.ref_viewing_distance.expect("rvd present").exponent, 22);
assert!(d0.additional_shift_present_flag);
assert_eq!(d0.num_sample_shift_plus512, Some(768));
let d1 = info.displays[1];
assert_eq!(d1.ref_baseline.exponent, 10);
assert_eq!(d1.ref_display_width.exponent, 11);
assert_eq!(d1.ref_viewing_distance.expect("rvd present").exponent, 12);
assert!(!d1.additional_shift_present_flag);
assert!(d1.num_sample_shift_plus512.is_none());
assert!(!info.extension_flag);
}
#[test]
fn three_dimensional_reference_displays_info_extension_flag_propagates() {
let payload = pack_bits(&[
(1, 1), (1, 1), (0, 1), (1, 1), (31, 6), (31, 6), (0, 1), (1, 1), ]);
let info = parse_three_dimensional_reference_displays_info(&payload).unwrap();
assert!(info.extension_flag);
}
#[test]
fn reference_display_num_sample_shift_returns_none_when_absent() {
let d = ReferenceDisplay {
ref_baseline: UnsignedFloatComponent {
exponent: 31,
mantissa: 0,
mantissa_width: 0,
},
ref_display_width: UnsignedFloatComponent {
exponent: 31,
mantissa: 0,
mantissa_width: 0,
},
ref_viewing_distance: None,
additional_shift_present_flag: false,
num_sample_shift_plus512: None,
};
assert_eq!(d.num_sample_shift(), None);
}
#[test]
fn reference_display_num_sample_shift_zero_at_bias() {
let d = ReferenceDisplay {
ref_baseline: UnsignedFloatComponent {
exponent: 31,
mantissa: 0,
mantissa_width: 0,
},
ref_display_width: UnsignedFloatComponent {
exponent: 31,
mantissa: 0,
mantissa_width: 0,
},
ref_viewing_distance: None,
additional_shift_present_flag: true,
num_sample_shift_plus512: Some(512),
};
assert_eq!(d.num_sample_shift(), Some(0));
}
#[test]
fn reference_display_num_sample_shift_positive_branch() {
let d = ReferenceDisplay {
ref_baseline: UnsignedFloatComponent {
exponent: 31,
mantissa: 0,
mantissa_width: 0,
},
ref_display_width: UnsignedFloatComponent {
exponent: 31,
mantissa: 0,
mantissa_width: 0,
},
ref_viewing_distance: None,
additional_shift_present_flag: true,
num_sample_shift_plus512: Some(768),
};
assert_eq!(d.num_sample_shift(), Some(256));
}
#[test]
fn reference_display_num_sample_shift_negative_branch() {
let d = ReferenceDisplay {
ref_baseline: UnsignedFloatComponent {
exponent: 31,
mantissa: 0,
mantissa_width: 0,
},
ref_display_width: UnsignedFloatComponent {
exponent: 31,
mantissa: 0,
mantissa_width: 0,
},
ref_viewing_distance: None,
additional_shift_present_flag: true,
num_sample_shift_plus512: Some(256),
};
assert_eq!(d.num_sample_shift(), Some(-256));
}
#[test]
fn reference_display_num_sample_shift_extreme_endpoints() {
let template = ReferenceDisplay {
ref_baseline: UnsignedFloatComponent {
exponent: 31,
mantissa: 0,
mantissa_width: 0,
},
ref_display_width: UnsignedFloatComponent {
exponent: 31,
mantissa: 0,
mantissa_width: 0,
},
ref_viewing_distance: None,
additional_shift_present_flag: true,
num_sample_shift_plus512: Some(0),
};
assert_eq!(template.num_sample_shift(), Some(-512));
let max = ReferenceDisplay {
num_sample_shift_plus512: Some(1023),
..template
};
assert_eq!(max.num_sample_shift(), Some(511));
}
#[test]
fn reference_display_num_sample_shift_round_trip_via_parser() {
let mut fields: Vec<(u64, u32)> = vec![
(1, 1), (1, 1), (1, 1), (1, 1), (0b010, 3), ];
fields.push((20, 6)); fields.push((21, 6)); fields.push((22, 6)); fields.push((1, 1)); fields.push((768, 10));
fields.push((10, 6));
fields.push((11, 6));
fields.push((12, 6));
fields.push((0, 1));
fields.push((0, 1)); let payload = pack_bits(&fields);
let info = parse_three_dimensional_reference_displays_info(&payload).unwrap();
assert_eq!(info.displays.len(), 2);
assert_eq!(info.displays[0].num_sample_shift(), Some(256));
assert_eq!(info.displays[1].num_sample_shift(), None);
}
#[test]
fn unsigned_float_component_to_f64_branches() {
let unity = UnsignedFloatComponent {
exponent: 31,
mantissa: 0,
mantissa_width: 0,
};
assert_eq!(unity.to_f64(), 1.0);
let three = UnsignedFloatComponent {
exponent: 32,
mantissa: 0x8000_0000,
mantissa_width: 32,
};
assert_eq!(three.to_f64(), 3.0);
let denormal = UnsignedFloatComponent {
exponent: 0,
mantissa: 1,
mantissa_width: 1,
};
assert!((denormal.to_f64() - 2f64.powi(-31)).abs() < 1e-300);
let unspec = UnsignedFloatComponent {
exponent: 63,
mantissa: 0,
mantissa_width: 0,
};
assert!(unspec.to_f64().is_nan());
}
#[test]
fn three_dimensional_reference_displays_info_rejects_prec_ref_baseline_above_31() {
let payload = pack_bits(&[(0, 5), (0b100001, 6)]);
let err = parse_three_dimensional_reference_displays_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::ThreeDimensionalReferenceDisplaysInfoPrecOutOfRange {
field: "prec_ref_baseline",
got: 32
}
));
}
#[test]
fn three_dimensional_reference_displays_info_rejects_prec_ref_viewing_dist_above_31() {
let payload = pack_bits(&[
(1, 1), (1, 1), (1, 1), (0, 5), (0b100001, 6),
]);
let err = parse_three_dimensional_reference_displays_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::ThreeDimensionalReferenceDisplaysInfoPrecOutOfRange {
field: "prec_ref_viewing_dist",
got: 32
}
));
}
#[test]
fn three_dimensional_reference_displays_info_rejects_num_ref_displays_above_31() {
let payload = pack_bits(&[(1, 1), (1, 1), (0, 1), (0, 5), (0b100001, 6)]);
let err = parse_three_dimensional_reference_displays_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::ThreeDimensionalReferenceDisplaysInfoNumRefDisplaysOutOfRange(32)
));
}
#[test]
fn parse_payload_dispatches_three_dimensional_reference_displays_info() {
let ctx = SeiContext::default();
let payload = pack_bits(&[
(1, 1), (1, 1), (0, 1), (1, 1), (31, 6), (31, 6), (0, 1), (0, 1), ]);
let got = parse_payload(51, &payload, &ctx).unwrap();
match got {
SeiPayload::ThreeDimensionalReferenceDisplaysInfo(info) => {
assert_eq!(info.displays.len(), 1);
assert!(!info.extension_flag);
}
other => panic!("expected ThreeDimensionalReferenceDisplaysInfo, got {other:?}"),
}
}
#[test]
fn depth_representation_info_minimum_syntax_all_views_equal() {
let payload = pack_bits(&[
(1, 1), (0, 1), (0, 1), (0, 1), (0, 1), (1, 1), (1, 1), ]);
let info = parse_depth_representation_info(&payload).unwrap();
assert!(info.all_views_equal_flag);
assert!(!info.z_near_flag);
assert!(!info.z_far_flag);
assert!(!info.d_min_flag);
assert!(!info.d_max_flag);
assert_eq!(info.z_axis_equal_flag, None);
assert_eq!(info.common_z_axis_reference_view, None);
assert_eq!(info.depth_representation_type, 0);
assert_eq!(info.views.len(), 1);
let v0 = info.views[0];
assert_eq!(v0.depth_info_view_id, 0);
assert!(v0.z_near.is_none());
assert!(v0.z_far.is_none());
assert!(v0.d_min.is_none());
assert!(v0.d_max.is_none());
assert!(v0.z_axis_reference_view.is_none());
assert!(v0.disparity_reference_view.is_none());
assert!(info.depth_nonlinear_representation_num_minus1.is_none());
assert!(info.depth_nonlinear_representation_model.is_empty());
}
#[test]
fn depth_representation_info_two_views_per_view_z_axis() {
let view = [
(1, 1), (1, 1), (1, 1), (0, 1), (63, 7), (0, 5), (0, 1), (1, 1), (31, 7), (0, 5), (1, 1), ];
let mut fields: Vec<(u64, u32)> = vec![
(0, 1), (0b010, 3), (1, 1), (0, 1), (0, 1), (1, 1), (0, 1), (0b011, 3), ];
for _ in 0..2 {
fields.extend_from_slice(&view);
}
let payload = pack_bits(&fields);
let info = parse_depth_representation_info(&payload).unwrap();
assert!(!info.all_views_equal_flag);
assert_eq!(info.views.len(), 2);
assert_eq!(info.z_axis_equal_flag, Some(false));
assert!(info.common_z_axis_reference_view.is_none());
assert_eq!(info.depth_representation_type, 2);
for v in &info.views {
assert_eq!(v.depth_info_view_id, 0);
assert_eq!(v.z_axis_reference_view, Some(0));
assert_eq!(v.disparity_reference_view, Some(0));
let zn = v.z_near.expect("ZNear present when z_near_flag = 1");
assert!(!zn.sign);
assert_eq!(zn.exponent, 63);
assert_eq!(zn.mantissa_width, 1);
assert!(v.z_far.is_none());
let dn = v.d_min.expect("DMin present when d_min_flag = 1");
assert!(dn.sign);
assert_eq!(dn.exponent, 31);
assert_eq!(dn.mantissa_width, 1);
assert_eq!(dn.mantissa, 1);
assert!(v.d_max.is_none());
}
assert!(info.depth_nonlinear_representation_num_minus1.is_none());
}
#[test]
fn depth_representation_info_nonlinear_type_3_tail() {
let payload = pack_bits(&[
(1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (0, 1), (0, 1), (0b00100, 5), (1, 1), (0, 1),
(1, 7),
(0, 5),
(0, 1),
(0, 1),
(2, 7),
(0, 5),
(0, 1),
(1, 1),
(0b00101, 5),
]);
let info = parse_depth_representation_info(&payload).unwrap();
assert!(info.all_views_equal_flag);
assert_eq!(info.depth_representation_type, 3);
assert_eq!(info.z_axis_equal_flag, Some(true));
assert_eq!(info.common_z_axis_reference_view, Some(0));
assert_eq!(info.views.len(), 1);
assert_eq!(info.depth_nonlinear_representation_num_minus1, Some(0));
assert_eq!(info.depth_nonlinear_representation_model, vec![4]);
}
#[test]
fn depth_representation_info_num_segments_returns_none_when_absent() {
let info = DepthRepresentationInfo {
all_views_equal_flag: true,
z_near_flag: false,
z_far_flag: false,
z_axis_equal_flag: None,
common_z_axis_reference_view: None,
d_min_flag: false,
d_max_flag: false,
depth_representation_type: 2,
views: Vec::new(),
depth_nonlinear_representation_num_minus1: None,
depth_nonlinear_representation_model: Vec::new(),
};
assert_eq!(info.depth_nonlinear_representation_num_segments(), None);
assert_eq!(info.depth_nonlinear_representation_model_len(), None);
}
#[test]
fn depth_representation_info_num_segments_minimum() {
let info = DepthRepresentationInfo {
all_views_equal_flag: true,
z_near_flag: false,
z_far_flag: false,
z_axis_equal_flag: None,
common_z_axis_reference_view: None,
d_min_flag: false,
d_max_flag: false,
depth_representation_type: 3,
views: Vec::new(),
depth_nonlinear_representation_num_minus1: Some(0),
depth_nonlinear_representation_model: vec![0],
};
assert_eq!(info.depth_nonlinear_representation_num_segments(), Some(2));
assert_eq!(info.depth_nonlinear_representation_model_len(), Some(1));
}
#[test]
fn depth_representation_info_num_segments_interior() {
let info = DepthRepresentationInfo {
all_views_equal_flag: true,
z_near_flag: false,
z_far_flag: false,
z_axis_equal_flag: None,
common_z_axis_reference_view: None,
d_min_flag: false,
d_max_flag: false,
depth_representation_type: 3,
views: Vec::new(),
depth_nonlinear_representation_num_minus1: Some(3),
depth_nonlinear_representation_model: vec![10, 20, 30, 40],
};
assert_eq!(info.depth_nonlinear_representation_num_segments(), Some(5));
assert_eq!(info.depth_nonlinear_representation_model_len(), Some(4));
}
#[test]
fn depth_representation_info_num_segments_maximum() {
let info = DepthRepresentationInfo {
all_views_equal_flag: true,
z_near_flag: false,
z_far_flag: false,
z_axis_equal_flag: None,
common_z_axis_reference_view: None,
d_min_flag: false,
d_max_flag: false,
depth_representation_type: 3,
views: Vec::new(),
depth_nonlinear_representation_num_minus1: Some(62),
depth_nonlinear_representation_model: vec![0; 63],
};
assert_eq!(info.depth_nonlinear_representation_num_segments(), Some(64));
assert_eq!(info.depth_nonlinear_representation_model_len(), Some(63));
}
#[test]
fn depth_representation_info_num_segments_round_trip_via_parser() {
let payload = pack_bits(&[
(1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (0, 1), (0, 1), (0b00100, 5), (1, 1), (0, 1),
(1, 7),
(0, 5),
(0, 1),
(0, 1),
(2, 7),
(0, 5),
(0, 1),
(1, 1),
(0b00101, 5),
]);
let info = parse_depth_representation_info(&payload).unwrap();
let model_len = info
.depth_nonlinear_representation_model_len()
.expect("type-3 payload signals model_len");
let num_segments = info
.depth_nonlinear_representation_num_segments()
.expect("type-3 payload signals num_segments");
assert_eq!(
usize::from(model_len),
info.depth_nonlinear_representation_model.len()
);
assert_eq!(num_segments, model_len + 1);
assert_eq!(model_len, 1);
assert_eq!(num_segments, 2);
}
#[test]
fn depth_representation_info_reserved_type_skips_nonlinear_tail() {
let payload = pack_bits(&[
(1, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0b00101, 5), (1, 1), ]);
let info = parse_depth_representation_info(&payload).unwrap();
assert_eq!(info.depth_representation_type, 4);
assert!(info.depth_nonlinear_representation_num_minus1.is_none());
}
#[test]
fn depth_representation_info_rejects_num_views_above_1023() {
let payload = pack_bits(&[
(0, 1), (0, 10),
(0b10000000001, 11),
]);
let err = parse_depth_representation_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::DepthRepresentationInfoNumViewsOutOfRange(1024)
));
}
#[test]
fn depth_representation_info_rejects_common_z_axis_above_1023() {
let payload = pack_bits(&[
(1, 1), (1, 1), (0, 1), (1, 1), (0, 10),
(0b10000000001, 11),
]);
let err = parse_depth_representation_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::DepthRepresentationInfoViewIdOutOfRange {
field: "common_z_axis_reference_view",
got: 1024
}
));
}
#[test]
fn depth_representation_info_rejects_nonlinear_num_above_62() {
let payload = pack_bits(&[
(1, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0b00100, 5), (1, 1), (0, 6),
(0b1000000, 7),
]);
let err = parse_depth_representation_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::DepthRepresentationInfoNonlinearNumOutOfRange(63)
));
}
#[test]
fn depth_float_component_to_f64_branches() {
let one = DepthFloatComponent {
sign: false,
exponent: 31,
mantissa: 0,
mantissa_width: 1,
};
assert_eq!(one.to_f64(), 1.0);
let three = DepthFloatComponent {
sign: false,
exponent: 32,
mantissa: 1,
mantissa_width: 1,
};
assert_eq!(three.to_f64(), 3.0);
let minus_three = DepthFloatComponent {
sign: true,
exponent: 32,
mantissa: 1,
mantissa_width: 1,
};
assert_eq!(minus_three.to_f64(), -3.0);
let dn = DepthFloatComponent {
sign: false,
exponent: 0,
mantissa: 1,
mantissa_width: 1,
};
assert!((dn.to_f64() - 2f64.powi(-31)).abs() < 1e-300);
let res = DepthFloatComponent {
sign: false,
exponent: 127,
mantissa: 0,
mantissa_width: 1,
};
assert!(res.to_f64().is_nan());
}
#[test]
fn parse_payload_dispatches_depth_representation_info() {
let ctx = SeiContext::default();
let payload = pack_bits(&[
(1, 1), (0, 1), (0, 1), (0, 1), (0, 1), (1, 1), (1, 1), ]);
let got = parse_payload(50, &payload, &ctx).unwrap();
match got {
SeiPayload::DepthRepresentationInfo(info) => {
assert_eq!(info.views.len(), 1);
assert_eq!(info.depth_representation_type, 0);
}
other => panic!("expected DepthRepresentationInfo, got {other:?}"),
}
}
#[test]
fn constrained_depth_parameter_set_identifier_min_legal() {
let payload = pack_bits(&[
(0b010, 3), (1, 1), ]);
let got = parse_constrained_depth_parameter_set_identifier(&payload).unwrap();
assert_eq!(got.max_dps_id, 1);
assert_eq!(got.max_dps_id_diff, 0);
}
#[test]
fn constrained_depth_parameter_set_identifier_mid_range() {
let payload = pack_bits(&[
(0b0001000, 7), (0b00100, 5), ]);
let got = parse_constrained_depth_parameter_set_identifier(&payload).unwrap();
assert_eq!(got.max_dps_id, 7);
assert_eq!(got.max_dps_id_diff, 3);
}
#[test]
fn constrained_depth_parameter_set_identifier_max_legal() {
let payload = pack_bits(&[
(0b00000111111, 11), (0b000011111, 9), ]);
let got = parse_constrained_depth_parameter_set_identifier(&payload).unwrap();
assert_eq!(got.max_dps_id, 62);
assert_eq!(got.max_dps_id_diff, 30);
}
#[test]
fn constrained_depth_parameter_set_identifier_rejects_max_dps_id_above_62() {
let payload = pack_bits(&[(0b0000001000000, 13)]);
let err = parse_constrained_depth_parameter_set_identifier(&payload).unwrap_err();
assert_eq!(
err,
SeiError::ConstrainedDepthParameterSetIdentifierMaxDpsIdOutOfRange(63)
);
}
#[test]
fn constrained_depth_parameter_set_identifier_rejects_diff_equals_half_max() {
let payload = pack_bits(&[
(0b00101, 5), (0b011, 3), ]);
let err = parse_constrained_depth_parameter_set_identifier(&payload).unwrap_err();
assert_eq!(
err,
SeiError::ConstrainedDepthParameterSetIdentifierDiffViolatesBound {
max_dps_id_diff: 2,
max_dps_id: 4,
}
);
}
#[test]
fn constrained_depth_parameter_set_identifier_rejects_zero_pair() {
let payload = pack_bits(&[
(1, 1), (1, 1), ]);
let err = parse_constrained_depth_parameter_set_identifier(&payload).unwrap_err();
assert_eq!(
err,
SeiError::ConstrainedDepthParameterSetIdentifierDiffViolatesBound {
max_dps_id_diff: 0,
max_dps_id: 0,
}
);
}
#[test]
fn parse_payload_dispatches_constrained_depth_parameter_set_identifier() {
let ctx = SeiContext::default();
let payload = pack_bits(&[
(0b010, 3), (1, 1), ]);
let got = parse_payload(54, &payload, &ctx).unwrap();
match got {
SeiPayload::ConstrainedDepthParameterSetIdentifier(info) => {
assert_eq!(info.max_dps_id, 1);
assert_eq!(info.max_dps_id_diff, 0);
}
other => panic!("expected ConstrainedDepthParameterSetIdentifier, got {other:?}"),
}
}
#[test]
fn depth_sampling_info_min_shared_position() {
let payload = pack_bits(&[
(1, 16), (0, 4), (1, 16), (0, 4), (0, 1), (0, 20), (0, 4), (0, 1), (0, 20), (0, 4), (0, 1), ]);
let info = parse_depth_sampling_info(&payload).unwrap();
assert_eq!(info.dttsr_x_mul, 1);
assert_eq!(info.dttsr_x_dp, 0);
assert_eq!(info.dttsr_y_mul, 1);
assert_eq!(info.dttsr_y_dp, 0);
assert!(!info.per_view_depth_grid_pos_flag);
assert!(info.num_video_plus_depth_views_minus1.is_none());
assert_eq!(info.views.len(), 1);
assert_eq!(info.views[0].depth_grid_view_id, 0);
assert_eq!(info.views[0].position.pos_x_fp, 0);
assert_eq!(info.views[0].position.pos_y_fp, 0);
assert!((info.dttsr_x_to_f64() - 1.0).abs() < 1e-9);
assert!((info.dttsr_y_to_f64() - 1.0).abs() < 1e-9);
assert!((info.views[0].position.x_to_f64() - 0.0).abs() < 1e-9);
}
#[test]
fn depth_sampling_info_per_view_two_views() {
let payload = pack_bits(&[
(3, 16), (0b0001, 4), (3, 16), (0b0001, 4), (1, 1), (0b010, 3), (1, 1),
(0b0000_0000_0000_0001_0000u32 as u64, 20), (0b0011, 4), (0, 1), (0b0000_0000_0000_0000_1000u32 as u64, 20), (0b0010, 4), (1, 1), (0b00110, 5),
(0b0000_0000_0000_0010_0000u32 as u64, 20), (0b0100, 4), (1, 1), (0b0000_0000_0000_0001_0000u32 as u64, 20), (0b0011, 4), (0, 1), ]);
let info = parse_depth_sampling_info(&payload).unwrap();
assert!(info.per_view_depth_grid_pos_flag);
assert_eq!(info.num_video_plus_depth_views_minus1, Some(1));
assert_eq!(info.views.len(), 2);
assert_eq!(info.views[0].depth_grid_view_id, 0);
assert_eq!(info.views[0].position.pos_x_fp, 16);
assert_eq!(info.views[0].position.pos_x_dp, 3);
assert!(!info.views[0].position.pos_x_sign);
assert_eq!(info.views[0].position.pos_y_fp, 8);
assert!(info.views[0].position.pos_y_sign);
assert_eq!(info.views[1].depth_grid_view_id, 5);
assert_eq!(info.views[1].position.pos_x_fp, 32);
assert!(info.views[1].position.pos_x_sign);
assert_eq!(info.views[1].position.pos_y_dp, 3);
assert!((info.views[0].position.y_to_f64() + 2.0).abs() < 1e-9);
assert!((info.views[1].position.x_to_f64() + 2.0).abs() < 1e-9);
assert!((info.dttsr_x_to_f64() - 1.5).abs() < 1e-9);
}
#[test]
fn depth_sampling_info_rejects_dttsr_x_mul_zero() {
let payload = pack_bits(&[
(0, 16), (0, 4),
(1, 16),
(0, 4),
(0, 1),
]);
let err = parse_depth_sampling_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::DepthSamplingInfoDttsrMulReserved { axis: "x" }
));
}
#[test]
fn depth_sampling_info_rejects_dttsr_y_mul_zero() {
let payload = pack_bits(&[
(1, 16), (0, 4),
(0, 16), (0, 4),
(0, 1),
]);
let err = parse_depth_sampling_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::DepthSamplingInfoDttsrMulReserved { axis: "y" }
));
}
#[test]
fn depth_sampling_info_rejects_num_views_above_1023() {
let payload = pack_bits(&[
(1, 16), (0, 4),
(1, 16), (0, 4),
(1, 1), (0, 10),
(0b10000000001, 11),
]);
let err = parse_depth_sampling_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::DepthSamplingInfoNumViewsOutOfRange(1024)
));
}
#[test]
fn depth_sampling_info_rejects_view_id_above_1023() {
let payload = pack_bits(&[
(1, 16), (0, 4),
(1, 16), (0, 4),
(1, 1), (0b010, 3), (0, 10),
(0b10000000001, 11),
]);
let err = parse_depth_sampling_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::DepthSamplingInfoViewIdOutOfRange { i: 0, got: 1024 }
));
}
#[test]
fn parse_payload_dispatches_depth_sampling_info() {
let ctx = SeiContext::default();
let payload = pack_bits(&[
(2, 16), (0, 4),
(2, 16), (0, 4),
(0, 1), (0, 20), (0, 4),
(0, 1),
(0, 20), (0, 4),
(0, 1),
]);
let got = parse_payload(53, &payload, &ctx).unwrap();
match got {
SeiPayload::DepthSamplingInfo(info) => {
assert_eq!(info.dttsr_x_mul, 2);
assert_eq!(info.dttsr_y_mul, 2);
assert!(!info.per_view_depth_grid_pos_flag);
assert_eq!(info.views.len(), 1);
}
other => panic!("expected DepthSamplingInfo, got {other:?}"),
}
}
#[test]
fn alternative_depth_info_minimal_flags_clear() {
let payload = pack_bits(&[
(0b1, 1), (0b010, 3), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), ]);
let info = parse_alternative_depth_info(&payload).unwrap();
assert_eq!(info.depth_type, 0);
let gvd = info.gvd.expect("depth_type 0 carries a gvd block");
assert_eq!(gvd.num_constituent_views_gvd_minus1, 1);
assert!(!gvd.depth_present_gvd_flag);
assert!(!gvd.z_gvd_flag);
assert!(gvd.z_values.is_empty());
assert_eq!(gvd.prec_gvd_focal_length, None);
assert_eq!(gvd.prec_gvd_rotation_param, None);
assert_eq!(gvd.cameras.len(), 3);
for cam in &gvd.cameras {
assert!(cam.intrinsic.is_none());
assert!(cam.rotation.is_none());
assert!(cam.translation_x.is_none());
}
}
#[test]
fn alternative_depth_info_nonzero_type_ignored() {
let payload = pack_bits(&[(0b011, 3), (0xFF, 8)]);
let info = parse_alternative_depth_info(&payload).unwrap();
assert_eq!(info.depth_type, 2);
assert!(info.gvd.is_none());
}
#[test]
fn alternative_depth_info_num_views_out_of_range() {
let payload = pack_bits(&[(0b1, 1), (0b00101, 5)]);
let err = parse_alternative_depth_info(&payload).unwrap_err();
assert!(matches!(
err,
SeiError::AlternativeDepthInfoNumConstituentViewsOutOfRange(4)
));
}
#[test]
fn alternative_depth_info_z_block_two_cameras() {
let payload = pack_bits(&[
(0b1, 1), (0b1, 1), (0, 1), (1, 1), (0, 1), (0, 1), (0, 1), (0, 1),
(0, 7),
(3, 5),
(0b1000, 4),
(1, 1),
(64, 7),
(0, 5),
(1, 1),
(0, 1),
(127, 7),
(0, 5),
(0, 1),
(0, 1),
(1, 7),
(1, 5),
(0b10, 2),
]);
let info = parse_alternative_depth_info(&payload).unwrap();
let gvd = info.gvd.unwrap();
assert!(gvd.z_gvd_flag);
assert_eq!(gvd.z_values.len(), 2);
let z0 = gvd.z_values[0];
assert!((z0.z_near.to_f64() - 2f64.powi(-30) * 0.5).abs() < 1e-18);
assert!((z0.z_far.to_f64() - (-(2f64.powi(33)) * 1.5)).abs() < 1.0);
let z1 = gvd.z_values[1];
assert!(z1.z_near.to_f64().is_nan());
assert!((z1.z_far.to_f64() - 2f64.powi(-30) * 1.5).abs() < 1e-18);
}
#[test]
fn alternative_depth_info_intrinsic_rotation_translation() {
let mut fields: Vec<(u64, u32)> = vec![
(0b1, 1), (0b1, 1), (0, 1), (0, 1), (1, 1), (1, 1), (1, 1), (0b1, 1),
(0b1, 1),
(0b1, 1),
(0b1, 1),
];
for cam in 0..2u64 {
for f in 0..14u64 {
let exp = (cam * 14 + f) % 30 + 1; fields.push((0, 1)); fields.push((exp, 6)); }
}
let payload = pack_bits(&fields);
let info = parse_alternative_depth_info(&payload).unwrap();
let gvd = info.gvd.unwrap();
assert_eq!(gvd.prec_gvd_focal_length, Some(0));
assert_eq!(gvd.prec_gvd_rotation_param, Some(0));
assert_eq!(gvd.prec_gvd_translation_param, Some(0));
assert_eq!(gvd.cameras.len(), 2);
for cam in &gvd.cameras {
let intr = cam.intrinsic.expect("intrinsic present");
assert_eq!(intr.focal_length_x.mantissa_width, 0);
let rot = cam.rotation.expect("rotation present");
assert_eq!(rot.len(), 3);
assert_eq!(rot[0].len(), 3);
assert!(cam.translation_x.is_some());
}
}
#[test]
fn parse_payload_dispatches_alternative_depth_info() {
let ctx = SeiContext::default();
let payload = pack_bits(&[
(0b1, 1), (0b1, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), ]);
let got = parse_payload(181, &payload, &ctx).unwrap();
match got {
SeiPayload::AlternativeDepthInfo(info) => {
assert_eq!(info.depth_type, 0);
let gvd = info.gvd.unwrap();
assert_eq!(gvd.cameras.len(), 2);
}
other => panic!("expected AlternativeDepthInfo, got {other:?}"),
}
}
#[test]
fn depth_timing_shared_single_offset() {
let payload = pack_bits(&[
(0, 1), (7, 5), (64, 8), (6, 6), ]);
let got = parse_depth_timing(&payload, &SeiContext::default()).unwrap();
assert!(!got.per_view_depth_timing_flag);
assert_eq!(got.offsets.len(), 1);
let off = got.offsets[0];
assert_eq!(off.offset_len_minus1, 7);
assert_eq!(off.depth_disp_delay_offset_fp, 64);
assert_eq!(off.depth_disp_delay_offset_dp, 6);
assert!((off.offset_clock_ticks() - 1.0).abs() < 1e-12);
}
#[test]
fn depth_timing_per_view_two_views() {
let payload = pack_bits(&[
(1, 1), (0, 5),
(1, 1),
(0, 6),
(31, 5),
(u32::MAX as u64, 32),
(32, 6),
]);
let ctx = SeiContext {
num_depth_views: 2,
..SeiContext::default()
};
let got = parse_depth_timing(&payload, &ctx).unwrap();
assert!(got.per_view_depth_timing_flag);
assert_eq!(got.offsets.len(), 2);
assert_eq!(got.offsets[0].offset_len_minus1, 0);
assert_eq!(got.offsets[0].depth_disp_delay_offset_fp, 1);
assert_eq!(got.offsets[0].depth_disp_delay_offset_dp, 0);
assert!((got.offsets[0].offset_clock_ticks() - 1.0).abs() < 1e-12);
assert_eq!(got.offsets[1].offset_len_minus1, 31);
assert_eq!(got.offsets[1].depth_disp_delay_offset_fp, u32::MAX);
assert_eq!(got.offsets[1].depth_disp_delay_offset_dp, 32);
let expected = f64::from(u32::MAX) / 2f64.powi(32);
assert!((got.offsets[1].offset_clock_ticks() - expected).abs() < 1e-12);
}
#[test]
fn depth_timing_per_view_unknown_num_depth_views_rejected() {
let payload = pack_bits(&[
(1, 1), (0, 5),
(1, 1),
(0, 6),
]);
let err = parse_depth_timing(&payload, &SeiContext::default()).unwrap_err();
assert!(matches!(err, SeiError::DepthTimingNumDepthViewsUnknown));
}
#[test]
fn depth_timing_rejects_num_depth_views_above_1024() {
let payload = pack_bits(&[
(1, 1), (0, 5),
(1, 1),
(0, 6),
]);
let ctx = SeiContext {
num_depth_views: 1025,
..SeiContext::default()
};
let err = parse_depth_timing(&payload, &ctx).unwrap_err();
assert!(matches!(
err,
SeiError::DepthTimingNumDepthViewsOutOfRange(1025)
));
}
#[test]
fn depth_timing_offset_clock_ticks_fractional_and_dp_ceiling() {
let half_step = DepthTimingOffset {
offset_len_minus1: 1,
depth_disp_delay_offset_fp: 3,
depth_disp_delay_offset_dp: 1,
};
assert!((half_step.offset_clock_ticks() - 1.5).abs() < 1e-12);
let dp_ceiling = DepthTimingOffset {
offset_len_minus1: 0,
depth_disp_delay_offset_fp: 1,
depth_disp_delay_offset_dp: 63,
};
let expected = 1.0 / 2f64.powi(63);
assert!((dp_ceiling.offset_clock_ticks() - expected).abs() < 1e-300);
assert!(dp_ceiling.offset_clock_ticks() > 0.0);
}
#[test]
fn depth_timing_truncated_offset_rejected() {
let payload = pack_bits(&[
(0, 1), (31, 5), ]);
let err = parse_depth_timing(&payload, &SeiContext::default()).unwrap_err();
assert!(matches!(err, SeiError::Bitstream(_)));
}
#[test]
fn parse_payload_dispatches_depth_timing() {
let payload = pack_bits(&[
(1, 1), (2, 5),
(5, 3),
(2, 6),
]);
let ctx = SeiContext {
num_depth_views: 1,
..SeiContext::default()
};
let got = parse_payload(52, &payload, &ctx).unwrap();
match got {
SeiPayload::DepthTiming(dt) => {
assert!(dt.per_view_depth_timing_flag);
assert_eq!(dt.offsets.len(), 1);
assert_eq!(dt.offsets[0].depth_disp_delay_offset_fp, 5);
assert!((dt.offsets[0].offset_clock_ticks() - 1.25).abs() < 1e-12);
}
other => panic!("expected DepthTiming, got {other:?}"),
}
}
#[test]
fn post_filter_hint_basic_cross_correlation_1x1() {
let fields = [
(0b010, 3), (0b010, 3), (0b10, 2), (0b0001010, 7), (0b00111, 5), (1, 1), (0, 1), ];
let payload = pack_bits(&fields);
let pfh = parse_post_filter_hint(&payload).unwrap();
assert_eq!(pfh.filter_hint_size_x, 1);
assert_eq!(pfh.filter_hint_size_y, 1);
assert_eq!(pfh.filter_hint_type, 2);
assert_eq!(pfh.filter_hint_value, vec![5, -3, 0]);
assert!(!pfh.additional_extension_flag);
}
#[test]
fn post_filter_hint_2d_fir_2x3() {
let mut fields = vec![
(0b011, 3), (0b00100, 5), (0b00, 2), ];
for _ in 0..18 {
fields.push((1, 1));
}
fields.push((1, 1)); let payload = pack_bits(&fields);
let pfh = parse_post_filter_hint(&payload).unwrap();
assert_eq!(pfh.filter_hint_size_y, 2);
assert_eq!(pfh.filter_hint_size_x, 3);
assert_eq!(pfh.filter_hint_type, 0);
assert_eq!(pfh.filter_hint_value.len(), 18);
assert!(pfh.filter_hint_value.iter().all(|&v| v == 0));
assert!(pfh.additional_extension_flag);
}
#[test]
fn post_filter_hint_type1_requires_height_two() {
let mut fields = vec![
(0b00100, 5), (0b010, 3), (0b01, 2), ];
for _ in 0..9 {
fields.push((1, 1));
}
fields.push((0, 1));
let payload = pack_bits(&fields);
let err = parse_post_filter_hint(&payload).unwrap_err();
assert_eq!(err, SeiError::PostFilterHintType1WrongHeight(3));
}
#[test]
fn post_filter_hint_rejects_size_out_of_range() {
let fields = [
(0b010, 3), (0b000010001, 9), ];
let payload = pack_bits(&fields);
let err = parse_post_filter_hint(&payload).unwrap_err();
assert_eq!(err, SeiError::PostFilterHintSizeOutOfRange { x: 16, y: 1 });
}
#[test]
fn post_filter_hint_rejects_reserved_type() {
let fields = [
(0b010, 3), (0b010, 3), (0b11, 2), ];
let payload = pack_bits(&fields);
let err = parse_post_filter_hint(&payload).unwrap_err();
assert_eq!(err, SeiError::PostFilterHintTypeReserved);
}
#[test]
fn parse_payload_dispatches_post_filter_hint() {
let ctx = SeiContext::default();
let fields = [
(0b010, 3), (0b010, 3), (0b10, 2), (1, 1), (1, 1), (1, 1), (0, 1), ];
let payload = pack_bits(&fields);
let got = parse_payload(22, &payload, &ctx).unwrap();
match got {
SeiPayload::PostFilterHint(p) => {
assert_eq!(p.filter_hint_size_x, 1);
assert_eq!(p.filter_hint_size_y, 1);
assert_eq!(p.filter_hint_type, 2);
assert_eq!(p.filter_hint_value, vec![0, 0, 0]);
}
other => panic!("expected PostFilterHint, got {:?}", other),
}
}
#[test]
fn full_frame_freeze_basic() {
let payload = pack_bits(&[(0b0001000, 7)]);
let fff = parse_full_frame_freeze(&payload).unwrap();
assert_eq!(fff.full_frame_freeze_repetition_period, 7);
}
#[test]
fn parse_payload_dispatches_full_frame_freeze() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(0b010, 3)]); let got = parse_payload(13, &payload, &ctx).unwrap();
match got {
SeiPayload::FullFrameFreeze(f) => {
assert_eq!(f.full_frame_freeze_repetition_period, 1);
}
other => panic!("expected FullFrameFreeze, got {:?}", other),
}
}
#[test]
fn parse_payload_dispatches_full_frame_freeze_release() {
let ctx = SeiContext::default();
let got = parse_payload(14, &[], &ctx).unwrap();
assert_eq!(got, SeiPayload::FullFrameFreezeRelease);
}
#[test]
fn full_frame_snapshot_basic() {
let payload = pack_bits(&[(0b00000101011, 11)]);
let snap = parse_full_frame_snapshot(&payload).unwrap();
assert_eq!(snap.snapshot_id, 42);
}
#[test]
fn parse_payload_dispatches_full_frame_snapshot() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(0b010, 3)]); let got = parse_payload(15, &payload, &ctx).unwrap();
match got {
SeiPayload::FullFrameSnapshot(s) => assert_eq!(s.snapshot_id, 1),
other => panic!("expected FullFrameSnapshot, got {:?}", other),
}
}
#[test]
fn deblocking_filter_display_preference_cancel() {
let payload = pack_bits(&[(1, 1)]); let p = parse_deblocking_filter_display_preference(&payload).unwrap();
assert!(p.cancel_flag);
assert!(!p.display_prior_to_deblocking_preferred_flag);
assert!(!p.dec_frame_buffering_constraint_flag);
assert_eq!(p.repetition_period, 0);
}
#[test]
fn deblocking_filter_display_preference_full_body() {
let payload = pack_bits(&[
(0, 1), (1, 1), (0, 1), (0b011, 3), ]);
let p = parse_deblocking_filter_display_preference(&payload).unwrap();
assert!(!p.cancel_flag);
assert!(p.display_prior_to_deblocking_preferred_flag);
assert!(!p.dec_frame_buffering_constraint_flag);
assert_eq!(p.repetition_period, 2);
}
#[test]
fn parse_payload_dispatches_deblocking_filter_display_preference() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(1, 1)]); let got = parse_payload(20, &payload, &ctx).unwrap();
match got {
SeiPayload::DeblockingFilterDisplayPreference(p) => assert!(p.cancel_flag),
other => panic!(
"expected DeblockingFilterDisplayPreference, got {:?}",
other
),
}
}
#[test]
fn scene_info_present_flag_zero_yields_empty_struct() {
let payload = pack_bits(&[(0, 1)]);
let got = parse_scene_info(&payload).unwrap();
assert!(!got.scene_info_present_flag);
assert!(got.scene_id.is_none());
assert!(got.scene_transition_type.is_none());
assert!(got.second_scene_id.is_none());
}
#[test]
fn scene_info_transition_type_le_3_omits_second_scene_id() {
let payload = pack_bits(&[
(1, 1), (0b00110, 5), (0b011, 3), ]);
let got = parse_scene_info(&payload).unwrap();
assert!(got.scene_info_present_flag);
assert_eq!(got.scene_id, Some(5));
assert_eq!(got.scene_transition_type, Some(2));
assert!(got.second_scene_id.is_none());
}
#[test]
fn scene_info_transition_type_gt_3_reads_second_scene_id() {
let payload = pack_bits(&[
(1, 1),
(0b010, 3), (0b00101, 5), (0b011, 3), ]);
let got = parse_scene_info(&payload).unwrap();
assert_eq!(got.scene_id, Some(1));
assert_eq!(got.scene_transition_type, Some(4));
assert_eq!(got.second_scene_id, Some(2));
}
#[test]
fn parse_payload_dispatches_scene_info() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(0, 1)]);
let got = parse_payload(9, &payload, &ctx).unwrap();
match got {
SeiPayload::SceneInfo(s) => assert!(!s.scene_info_present_flag),
other => panic!("expected SceneInfo, got {other:?}"),
}
}
#[test]
fn spare_pic_area_idc_0_needs_no_map_units() {
let payload = pack_bits(&[
(0b00100, 5), (0, 1), (1, 1), (0b011, 3), (1, 1), ]);
let ctx = SeiContext::default(); let got = parse_spare_pic(&payload, &ctx).unwrap();
assert_eq!(got.target_frame_num, 3);
assert!(!got.spare_field_flag);
assert!(got.target_bottom_field_flag.is_none());
assert_eq!(got.entries.len(), 1);
let e = &got.entries[0];
assert_eq!(e.delta_spare_frame_num, 2);
assert_eq!(e.spare_area_idc, 0);
assert!(e.spare_units.is_none());
assert!(e.zero_run_lengths.is_none());
assert!(e.spare_bottom_field_flag.is_none());
}
#[test]
fn spare_pic_area_idc_1_reads_one_flag_per_map_unit() {
let payload = pack_bits(&[
(1, 1), (0, 1), (1, 1), (1, 1), (0b010, 3), (0, 1), (1, 1), (0, 1), (1, 1), ]);
let ctx = SeiContext {
pic_size_in_map_units: 4,
..SeiContext::default()
};
let got = parse_spare_pic(&payload, &ctx).unwrap();
let e = &got.entries[0];
assert_eq!(e.spare_area_idc, 1);
assert_eq!(e.spare_units, Some(vec![true, false, true, false]));
assert!(e.zero_run_lengths.is_none());
}
#[test]
fn spare_pic_area_idc_2_reads_zero_runs_until_map_units() {
let payload = pack_bits(&[
(1, 1), (0, 1), (1, 1), (1, 1), (0b011, 3), (0b011, 3), (0b010, 3), (1, 1), ]);
let ctx = SeiContext {
pic_size_in_map_units: 6,
..SeiContext::default()
};
let got = parse_spare_pic(&payload, &ctx).unwrap();
let e = &got.entries[0];
assert_eq!(e.spare_area_idc, 2);
assert!(e.spare_units.is_none());
assert_eq!(e.zero_run_lengths, Some(vec![2, 1, 0]));
}
#[test]
fn spare_pic_field_message_reads_bottom_field_flags() {
let payload = pack_bits(&[
(0b010, 3), (1, 1), (1, 1), (1, 1), (1, 1), (0, 1), (1, 1), ]);
let ctx = SeiContext::default();
let got = parse_spare_pic(&payload, &ctx).unwrap();
assert_eq!(got.target_frame_num, 1);
assert!(got.spare_field_flag);
assert_eq!(got.target_bottom_field_flag, Some(true));
assert_eq!(got.entries[0].spare_bottom_field_flag, Some(false));
assert_eq!(got.entries[0].spare_area_idc, 0);
}
#[test]
fn spare_pic_rejects_num_spare_pics_minus1_over_15() {
let payload = pack_bits(&[
(1, 1), (0, 1), (0b000010001, 9), ]);
let ctx = SeiContext::default();
assert_eq!(
parse_spare_pic(&payload, &ctx),
Err(SeiError::SparePicCountTooLarge(16))
);
}
#[test]
fn spare_pic_rejects_area_idc_over_2() {
let payload = pack_bits(&[
(1, 1), (0, 1), (1, 1), (1, 1), (0b00100, 5), ]);
let ctx = SeiContext::default();
assert_eq!(
parse_spare_pic(&payload, &ctx),
Err(SeiError::SparePicAreaIdcOutOfRange { i: 0, idc: 3 })
);
}
#[test]
fn spare_pic_area_idc_1_without_map_units_is_rejected() {
let payload = pack_bits(&[
(1, 1), (0, 1), (1, 1), (1, 1), (0b010, 3), ]);
let ctx = SeiContext::default(); assert_eq!(
parse_spare_pic(&payload, &ctx),
Err(SeiError::SparePicMapUnitsUnknown { i: 0, idc: 1 })
);
}
#[test]
fn spare_pic_two_entries_mix_methods() {
let payload = pack_bits(&[
(1, 1), (0, 1), (0b010, 3), (1, 1), (1, 1), (0b010, 3), (0b010, 3), (1, 1), (0, 1), ]);
let ctx = SeiContext {
pic_size_in_map_units: 2,
..SeiContext::default()
};
let got = parse_spare_pic(&payload, &ctx).unwrap();
assert_eq!(got.entries.len(), 2);
assert_eq!(got.entries[0].spare_area_idc, 0);
assert!(got.entries[0].spare_units.is_none());
assert_eq!(got.entries[1].delta_spare_frame_num, 1);
assert_eq!(got.entries[1].spare_units, Some(vec![false, true]));
}
#[test]
fn parse_payload_dispatches_spare_pic() {
let payload = pack_bits(&[
(0b00100, 5), (0, 1), (1, 1), (0b011, 3), (1, 1), ]);
let ctx = SeiContext::default();
match parse_payload(8, &payload, &ctx).unwrap() {
SeiPayload::SparePic(s) => {
assert_eq!(s.target_frame_num, 3);
assert_eq!(s.entries.len(), 1);
assert_eq!(s.entries[0].spare_area_idc, 0);
}
other => panic!("expected SparePic, got {other:?}"),
}
}
#[test]
fn dec_ref_pic_marking_repetition_idr_frame_only() {
let payload = pack_bits(&[
(1, 1), (1, 1), (1, 1), (0, 1), ]);
let ctx = SeiContext::default(); let got = parse_dec_ref_pic_marking_repetition(&payload, &ctx).unwrap();
assert!(got.original_idr_flag);
assert_eq!(got.original_frame_num, 0);
assert_eq!(got.original_field_pic_flag, None);
assert_eq!(got.original_bottom_field_flag, None);
assert!(got.dec_ref_pic_marking.no_output_of_prior_pics_flag);
assert!(!got.dec_ref_pic_marking.long_term_reference_flag);
assert_eq!(got.dec_ref_pic_marking.adaptive_marking, None);
}
#[test]
fn dec_ref_pic_marking_repetition_non_idr_adaptive_mmco() {
let payload = pack_bits(&[
(0, 1), (0b00110, 5), (1, 1), (0b010, 3), (0b00100, 5), (0b00100, 5), (1, 1), (0b011, 3), (1, 1), ]);
let ctx = SeiContext::default();
let got = parse_dec_ref_pic_marking_repetition(&payload, &ctx).unwrap();
assert!(!got.original_idr_flag);
assert_eq!(got.original_frame_num, 5);
let ops = got.dec_ref_pic_marking.adaptive_marking.unwrap();
assert_eq!(
ops,
vec![MmcoOp::MarkShortTermUnused(3), MmcoOp::AssignLongTerm(0, 2)]
);
}
#[test]
fn dec_ref_pic_marking_repetition_non_idr_sliding_window() {
let payload = pack_bits(&[
(0, 1), (0b011, 3), (0, 1), ]);
let ctx = SeiContext::default();
let got = parse_dec_ref_pic_marking_repetition(&payload, &ctx).unwrap();
assert!(!got.original_idr_flag);
assert_eq!(got.original_frame_num, 2);
assert_eq!(got.dec_ref_pic_marking.adaptive_marking, None);
}
#[test]
fn dec_ref_pic_marking_repetition_field_with_bottom_flag() {
let payload = pack_bits(&[
(1, 1), (1, 1), (1, 1), (1, 1), (0, 1), (1, 1), ]);
let ctx = SeiContext {
frame_mbs_only_flag: false,
..SeiContext::default()
};
let got = parse_dec_ref_pic_marking_repetition(&payload, &ctx).unwrap();
assert_eq!(got.original_field_pic_flag, Some(true));
assert_eq!(got.original_bottom_field_flag, Some(true));
assert!(!got.dec_ref_pic_marking.no_output_of_prior_pics_flag);
assert!(got.dec_ref_pic_marking.long_term_reference_flag);
}
#[test]
fn dec_ref_pic_marking_repetition_field_no_bottom_flag() {
let payload = pack_bits(&[
(0, 1), (0b010, 3), (0, 1), (0, 1), ]);
let ctx = SeiContext {
frame_mbs_only_flag: false,
..SeiContext::default()
};
let got = parse_dec_ref_pic_marking_repetition(&payload, &ctx).unwrap();
assert_eq!(got.original_field_pic_flag, Some(false));
assert_eq!(got.original_bottom_field_flag, None);
assert_eq!(got.dec_ref_pic_marking.adaptive_marking, None);
}
#[test]
fn dec_ref_pic_marking_repetition_all_mmco_ops() {
let payload = pack_bits(&[
(0, 1), (1, 1), (1, 1), (0b010, 3), (1, 1), (0b011, 3), (0b010, 3), (0b00100, 5), (0b010, 3), (1, 1), (0b00101, 5), (0b011, 3), (0b00110, 5), (0b00111, 5), (0b00100, 5), (1, 1), ]);
let ctx = SeiContext::default();
let got = parse_dec_ref_pic_marking_repetition(&payload, &ctx).unwrap();
let ops = got.dec_ref_pic_marking.adaptive_marking.unwrap();
assert_eq!(
ops,
vec![
MmcoOp::MarkShortTermUnused(0),
MmcoOp::MarkLongTermUnused(1),
MmcoOp::AssignLongTerm(1, 0),
MmcoOp::SetMaxLongTermIdx(2),
MmcoOp::MarkAllUnused,
MmcoOp::AssignCurrentLongTerm(3),
]
);
}
#[test]
fn dec_ref_pic_marking_repetition_mmco_out_of_range_rejected() {
let payload = pack_bits(&[
(0, 1), (1, 1), (1, 1), (0b0001000, 7), ]);
let ctx = SeiContext::default();
let err = parse_dec_ref_pic_marking_repetition(&payload, &ctx).unwrap_err();
assert_eq!(err, SeiError::DecRefPicMarkingRepetitionMmcoOutOfRange(7));
}
#[test]
fn parse_payload_dispatches_dec_ref_pic_marking_repetition() {
let payload = pack_bits(&[(1, 1), (1, 1), (1, 1), (0, 1)]);
let ctx = SeiContext::default();
match parse_payload(7, &payload, &ctx).unwrap() {
SeiPayload::DecRefPicMarkingRepetition(d) => {
assert!(d.original_idr_flag);
assert_eq!(d.original_frame_num, 0);
assert!(d.dec_ref_pic_marking.no_output_of_prior_pics_flag);
}
other => panic!("expected DecRefPicMarkingRepetition, got {other:?}"),
}
}
#[test]
fn sub_seq_info_without_frame_num() {
let payload = pack_bits(&[
(0b011, 3), (0b00110, 5), (1, 1), (0, 1), (1, 1), (0, 1), ]);
let got = parse_sub_seq_info(&payload).unwrap();
assert_eq!(got.sub_seq_layer_num, 2);
assert_eq!(got.sub_seq_id, 5);
assert!(got.first_ref_pic_flag);
assert!(!got.leading_non_ref_pic_flag);
assert!(got.last_pic_flag);
assert!(got.sub_seq_frame_num.is_none());
}
#[test]
fn sub_seq_info_with_frame_num() {
let payload = pack_bits(&[
(1, 1), (1, 1), (0, 1), (1, 1), (0, 1), (1, 1), (0b00100, 5), ]);
let got = parse_sub_seq_info(&payload).unwrap();
assert_eq!(got.sub_seq_layer_num, 0);
assert_eq!(got.sub_seq_id, 0);
assert!(!got.first_ref_pic_flag);
assert!(got.leading_non_ref_pic_flag);
assert!(!got.last_pic_flag);
assert_eq!(got.sub_seq_frame_num, Some(3));
}
#[test]
fn sub_seq_info_layer_num_out_of_range_rejected() {
let payload = pack_bits(&[(0b1_0000_0001, 17)]);
let err = parse_sub_seq_info(&payload).unwrap_err();
assert_eq!(err, SeiError::SubSeqLayerNumTooLarge(256));
}
#[test]
fn parse_payload_dispatches_sub_seq_info() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(1, 1), (1, 1), (1, 1), (0, 1), (0, 1), (0, 1)]);
match parse_payload(10, &payload, &ctx).unwrap() {
SeiPayload::SubSeqInfo(s) => {
assert_eq!(s.sub_seq_layer_num, 0);
assert!(s.first_ref_pic_flag);
}
other => panic!("expected SubSeqInfo, got {other:?}"),
}
}
#[test]
fn sub_seq_layer_characteristics_two_layers() {
let payload = pack_bits(&[
(0b010, 3), (1, 1), (0x1234, 16), (0x0100, 16), (0, 1), (0x0000, 16), (0x00FF, 16), ]);
let got = parse_sub_seq_layer_characteristics(&payload).unwrap();
assert_eq!(got.layers.len(), 2);
assert!(got.layers[0].accurate_statistics_flag);
assert_eq!(got.layers[0].average_bit_rate, 0x1234);
assert_eq!(got.layers[0].average_frame_rate, 0x0100);
assert!(!got.layers[1].accurate_statistics_flag);
assert_eq!(got.layers[1].average_bit_rate, 0);
assert_eq!(got.layers[1].average_frame_rate, 0x00FF);
}
#[test]
fn parse_payload_dispatches_sub_seq_layer_characteristics() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(1, 1), (0, 1), (0, 16), (0, 16)]);
match parse_payload(11, &payload, &ctx).unwrap() {
SeiPayload::SubSeqLayerCharacteristics(s) => {
assert_eq!(s.layers.len(), 1);
}
other => panic!("expected SubSeqLayerCharacteristics, got {other:?}"),
}
}
#[test]
fn sub_seq_layer_avg_bit_rate_bps_zero_is_none() {
let s = SubSeqLayerCharacteristic {
accurate_statistics_flag: false,
average_bit_rate: 0,
average_frame_rate: 0,
};
assert_eq!(s.average_bit_rate_bps(), None);
assert_eq!(s.average_frame_rate_fps(), None);
}
#[test]
fn sub_seq_layer_avg_bit_rate_bps_minimum_nonzero() {
let s = SubSeqLayerCharacteristic {
accurate_statistics_flag: true,
average_bit_rate: 1,
average_frame_rate: 0,
};
assert_eq!(s.average_bit_rate_bps(), Some(1_000));
}
#[test]
fn sub_seq_layer_avg_bit_rate_bps_typical() {
let s = SubSeqLayerCharacteristic {
accurate_statistics_flag: true,
average_bit_rate: 5_000,
average_frame_rate: 0,
};
assert_eq!(s.average_bit_rate_bps(), Some(5_000_000));
}
#[test]
fn sub_seq_layer_avg_bit_rate_bps_maximum() {
let s = SubSeqLayerCharacteristic {
accurate_statistics_flag: true,
average_bit_rate: u16::MAX,
average_frame_rate: 0,
};
assert_eq!(s.average_bit_rate_bps(), Some(65_535_000));
}
#[test]
fn sub_seq_layer_avg_frame_rate_fps_quarter_step() {
let s = SubSeqLayerCharacteristic {
accurate_statistics_flag: true,
average_bit_rate: 0,
average_frame_rate: 1,
};
let fps = s.average_frame_rate_fps().expect("non-zero ⇒ Some");
assert!((fps - (1.0 / 256.0)).abs() < f64::EPSILON);
}
#[test]
fn sub_seq_layer_avg_frame_rate_fps_exact_integer_rates() {
for &(carrier, expected_fps) in
&[(24u16 * 256, 24.0_f64), (25 * 256, 25.0), (30 * 256, 30.0)]
{
let s = SubSeqLayerCharacteristic {
accurate_statistics_flag: true,
average_bit_rate: 0,
average_frame_rate: carrier,
};
let fps = s.average_frame_rate_fps().expect("non-zero ⇒ Some");
assert!(
(fps - expected_fps).abs() < f64::EPSILON,
"carrier {carrier} ⇒ expected {expected_fps} got {fps}"
);
}
}
#[test]
fn sub_seq_layer_avg_frame_rate_fps_maximum() {
let s = SubSeqLayerCharacteristic {
accurate_statistics_flag: true,
average_bit_rate: 0,
average_frame_rate: u16::MAX,
};
let fps = s.average_frame_rate_fps().expect("non-zero ⇒ Some");
assert!((fps - (65535.0_f64 / 256.0)).abs() < f64::EPSILON);
}
#[test]
fn sub_seq_layer_accessors_via_parse_round_trip() {
let payload = pack_bits(&[
(1, 1), (1, 1), (2_500, 16), (24u64 * 256, 16), ]);
let got = parse_sub_seq_layer_characteristics(&payload).unwrap();
assert_eq!(got.layers.len(), 1);
assert_eq!(got.layers[0].average_bit_rate_bps(), Some(2_500_000));
let fps = got.layers[0]
.average_frame_rate_fps()
.expect("non-zero ⇒ Some");
assert!((fps - 24.0).abs() < f64::EPSILON);
}
#[test]
fn sub_seq_characteristics_avg_rate_accessors_round_trip() {
let payload = pack_bits(&[
(1, 1), (1, 1), (0, 1), (1, 1), (0, 1), (10_000, 16), (60u64 * 256, 16), (1, 1), ]);
let got = parse_sub_seq_characteristics(&payload).unwrap();
let rate = got.average_rate.expect("average_rate present");
assert_eq!(rate.average_bit_rate_bps(), Some(10_000_000));
let fps = rate.average_frame_rate_fps().expect("non-zero ⇒ Some");
assert!((fps - 60.0).abs() < f64::EPSILON);
}
#[test]
fn sub_seq_characteristics_full() {
let payload = pack_bits(&[
(0b010, 3), (0b011, 3), (1, 1), (0xDEADBEEF, 32), (1, 1), (1, 1), (0xAAAA, 16), (0x5555, 16), (0b010, 3), (1, 1), (0b00100, 5), (1, 1), ]);
let got = parse_sub_seq_characteristics(&payload).unwrap();
assert_eq!(got.sub_seq_layer_num, 1);
assert_eq!(got.sub_seq_id, 2);
assert_eq!(got.sub_seq_duration, Some(0xDEADBEEF));
let rate = got.average_rate.expect("average_rate present");
assert!(rate.accurate_statistics_flag);
assert_eq!(rate.average_bit_rate, 0xAAAA);
assert_eq!(rate.average_frame_rate, 0x5555);
assert_eq!(got.referenced.len(), 1);
assert_eq!(got.referenced[0].ref_sub_seq_layer_num, 0);
assert_eq!(got.referenced[0].ref_sub_seq_id, 3);
assert!(got.referenced[0].ref_sub_seq_direction);
}
#[test]
fn sub_seq_characteristics_minimal() {
let payload = pack_bits(&[
(1, 1), (1, 1), (0, 1), (0, 1), (1, 1), ]);
let got = parse_sub_seq_characteristics(&payload).unwrap();
assert_eq!(got.sub_seq_layer_num, 0);
assert_eq!(got.sub_seq_id, 0);
assert!(got.sub_seq_duration.is_none());
assert!(got.average_rate.is_none());
assert!(got.referenced.is_empty());
}
#[test]
fn parse_payload_dispatches_sub_seq_characteristics() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(1, 1), (1, 1), (0, 1), (0, 1), (1, 1)]);
match parse_payload(12, &payload, &ctx).unwrap() {
SeiPayload::SubSeqCharacteristics(s) => {
assert_eq!(s.sub_seq_layer_num, 0);
assert!(s.referenced.is_empty());
}
other => panic!("expected SubSeqCharacteristics, got {other:?}"),
}
}
#[test]
fn sub_seq_duration_seconds_none_when_unspecified() {
let c = SubSeqCharacteristics {
sub_seq_layer_num: 0,
sub_seq_id: 0,
sub_seq_duration: None,
average_rate: None,
referenced: Vec::new(),
};
assert!(c.sub_seq_duration_seconds().is_none());
}
#[test]
fn sub_seq_duration_seconds_minimum_nonzero() {
let c = SubSeqCharacteristics {
sub_seq_layer_num: 0,
sub_seq_id: 0,
sub_seq_duration: Some(1),
average_rate: None,
referenced: Vec::new(),
};
let s = c.sub_seq_duration_seconds().unwrap();
assert!((s * 90_000.0 - 1.0).abs() < 1e-12);
}
#[test]
fn sub_seq_duration_seconds_one_second() {
let c = SubSeqCharacteristics {
sub_seq_layer_num: 0,
sub_seq_id: 0,
sub_seq_duration: Some(90_000),
average_rate: None,
referenced: Vec::new(),
};
assert_eq!(c.sub_seq_duration_seconds(), Some(1.0));
}
#[test]
fn sub_seq_duration_seconds_one_frame_at_30fps() {
let c = SubSeqCharacteristics {
sub_seq_layer_num: 0,
sub_seq_id: 0,
sub_seq_duration: Some(3_000),
average_rate: None,
referenced: Vec::new(),
};
let s = c.sub_seq_duration_seconds().unwrap();
assert!((s - 1.0 / 30.0).abs() < 1e-12);
}
#[test]
fn sub_seq_duration_seconds_u32_ceiling() {
let c = SubSeqCharacteristics {
sub_seq_layer_num: 0,
sub_seq_id: 0,
sub_seq_duration: Some(u32::MAX),
average_rate: None,
referenced: Vec::new(),
};
let expected = f64::from(u32::MAX) / 90_000.0;
assert_eq!(c.sub_seq_duration_seconds(), Some(expected));
}
#[test]
fn sub_seq_duration_seconds_roundtrip_via_parse() {
let payload = pack_bits(&[
(1, 1), (1, 1), (1, 1), (180_000, 32), (0, 1), (1, 1), ]);
let got = parse_sub_seq_characteristics(&payload).unwrap();
assert_eq!(got.sub_seq_duration, Some(180_000));
assert_eq!(got.sub_seq_duration_seconds(), Some(2.0));
}
#[test]
fn sub_seq_duration_seconds_roundtrip_unspecified() {
let payload = pack_bits(&[
(1, 1), (1, 1), (0, 1), (0, 1), (1, 1), ]);
let got = parse_sub_seq_characteristics(&payload).unwrap();
assert!(got.sub_seq_duration.is_none());
assert!(got.sub_seq_duration_seconds().is_none());
}
#[test]
fn progressive_refinement_segment_start_basic() {
let payload = pack_bits(&[(0b011, 3), (1, 1)]);
let got = parse_progressive_refinement_segment_start(&payload).unwrap();
assert_eq!(got.progressive_refinement_id, 2);
assert_eq!(got.num_refinement_steps_minus1, 0);
}
#[test]
fn progressive_refinement_num_refinement_steps_minimum() {
let r = ProgressiveRefinementSegmentStart {
progressive_refinement_id: 0,
num_refinement_steps_minus1: 0,
};
assert_eq!(r.num_refinement_steps(), 1);
}
#[test]
fn progressive_refinement_num_refinement_steps_typical() {
let r = ProgressiveRefinementSegmentStart {
progressive_refinement_id: 7,
num_refinement_steps_minus1: 2,
};
assert_eq!(r.num_refinement_steps(), 3);
}
#[test]
fn progressive_refinement_num_refinement_steps_u32_ceiling() {
let r = ProgressiveRefinementSegmentStart {
progressive_refinement_id: 0,
num_refinement_steps_minus1: u32::MAX,
};
assert_eq!(r.num_refinement_steps(), u64::from(u32::MAX) + 1);
assert_eq!(r.num_refinement_steps(), 4_294_967_296);
}
#[test]
fn progressive_refinement_num_refinement_steps_parse_round_trip() {
let payload = pack_bits(&[(0b00110, 5), (0b0001010, 7)]);
let got = parse_progressive_refinement_segment_start(&payload).unwrap();
assert_eq!(got.progressive_refinement_id, 5);
assert_eq!(got.num_refinement_steps_minus1, 9);
assert_eq!(got.num_refinement_steps(), 10);
}
#[test]
fn progressive_refinement_segment_end_basic() {
let payload = pack_bits(&[(0b00100, 5)]);
let got = parse_progressive_refinement_segment_end(&payload).unwrap();
assert_eq!(got.progressive_refinement_id, 3);
}
#[test]
fn parse_payload_dispatches_progressive_refinement() {
let ctx = SeiContext::default();
let payload_start = pack_bits(&[(1, 1), (1, 1)]); match parse_payload(16, &payload_start, &ctx).unwrap() {
SeiPayload::ProgressiveRefinementSegmentStart(p) => {
assert_eq!(p.progressive_refinement_id, 0);
assert_eq!(p.num_refinement_steps_minus1, 0);
}
other => panic!("expected ProgressiveRefinementSegmentStart, got {other:?}"),
}
let payload_end = pack_bits(&[(1, 1)]); match parse_payload(17, &payload_end, &ctx).unwrap() {
SeiPayload::ProgressiveRefinementSegmentEnd(p) => {
assert_eq!(p.progressive_refinement_id, 0)
}
other => panic!("expected ProgressiveRefinementSegmentEnd, got {other:?}"),
}
}
#[test]
fn stereo_video_info_field_views_path() {
let payload = pack_bits(&[(1, 1), (1, 1), (1, 1), (0, 1)]);
let got = parse_stereo_video_info(&payload).unwrap();
assert!(got.field_views_flag);
assert_eq!(got.top_field_is_left_view_flag, Some(true));
assert!(got.current_frame_is_left_view_flag.is_none());
assert!(got.next_frame_is_second_view_flag.is_none());
assert!(got.left_view_self_contained_flag);
assert!(!got.right_view_self_contained_flag);
}
#[test]
fn stereo_video_info_frame_views_path() {
let payload = pack_bits(&[(0, 1), (1, 1), (0, 1), (1, 1), (1, 1)]);
let got = parse_stereo_video_info(&payload).unwrap();
assert!(!got.field_views_flag);
assert!(got.top_field_is_left_view_flag.is_none());
assert_eq!(got.current_frame_is_left_view_flag, Some(true));
assert_eq!(got.next_frame_is_second_view_flag, Some(false));
assert!(got.left_view_self_contained_flag);
assert!(got.right_view_self_contained_flag);
}
#[test]
fn parse_payload_dispatches_stereo_video_info() {
let ctx = SeiContext::default();
let payload = pack_bits(&[(1, 1), (0, 1), (1, 1), (1, 1)]);
match parse_payload(21, &payload, &ctx).unwrap() {
SeiPayload::StereoVideoInfo(s) => {
assert!(s.field_views_flag);
assert_eq!(s.top_field_is_left_view_flag, Some(false));
}
other => panic!("expected StereoVideoInfo, got {other:?}"),
}
}
#[test]
fn motion_constrained_slice_group_set_single_group_collapses_width() {
let payload = pack_bits(&[(1, 1), (1, 1), (0, 1)]);
let ctx = SeiContext {
num_slice_groups: 1,
..SeiContext::default()
};
let got = parse_motion_constrained_slice_group_set(&payload, &ctx).unwrap();
assert_eq!(got.slice_group_ids, vec![0]);
assert!(got.exact_sample_value_match_flag);
assert!(!got.pan_scan_rect_flag);
assert!(got.pan_scan_rect_id.is_none());
}
#[test]
fn motion_constrained_slice_group_set_multi_group_reads_u_v() {
let payload = pack_bits(&[
(0b010, 3), (0b01, 2), (0b11, 2), (0, 1), (1, 1), (0b00110, 5), ]);
let ctx = SeiContext {
num_slice_groups: 4,
..SeiContext::default()
};
let got = parse_motion_constrained_slice_group_set(&payload, &ctx).unwrap();
assert_eq!(got.slice_group_ids, vec![1, 3]);
assert!(!got.exact_sample_value_match_flag);
assert!(got.pan_scan_rect_flag);
assert_eq!(got.pan_scan_rect_id, Some(5));
}
#[test]
fn parse_payload_dispatches_motion_constrained_slice_group_set() {
let payload = pack_bits(&[(1, 1), (0, 1), (0, 1)]);
let ctx = SeiContext::default();
match parse_payload(18, &payload, &ctx).unwrap() {
SeiPayload::MotionConstrainedSliceGroupSet(s) => {
assert_eq!(s.slice_group_ids, vec![0]);
assert!(!s.exact_sample_value_match_flag);
}
other => panic!("expected MotionConstrainedSliceGroupSet, got {other:?}"),
}
}
fn u32_be(v: u32) -> [u8; 4] {
v.to_be_bytes()
}
fn u16_be(v: u16) -> [u8; 2] {
v.to_be_bytes()
}
fn i32_be(v: i32) -> [u8; 4] {
v.to_be_bytes()
}
#[test]
fn ambient_viewing_environment_bt2035_d65() {
let mut payload = Vec::new();
payload.extend_from_slice(&u32_be(100_000));
payload.extend_from_slice(&u16_be(15_635));
payload.extend_from_slice(&u16_be(16_450));
let got = parse_ambient_viewing_environment(&payload).unwrap();
assert_eq!(got.ambient_illuminance, 100_000);
assert_eq!(got.ambient_light_x, 15_635);
assert_eq!(got.ambient_light_y, 16_450);
}
#[test]
fn ambient_viewing_environment_zero_illuminance_rejected() {
let mut payload = Vec::new();
payload.extend_from_slice(&u32_be(0));
payload.extend_from_slice(&u16_be(0));
payload.extend_from_slice(&u16_be(0));
let err = parse_ambient_viewing_environment(&payload).unwrap_err();
assert_eq!(err, SeiError::AmbientIlluminanceZero);
}
#[test]
fn ambient_viewing_environment_chromaticity_out_of_range_rejected() {
let mut payload = Vec::new();
payload.extend_from_slice(&u32_be(1));
payload.extend_from_slice(&u16_be(50_001));
payload.extend_from_slice(&u16_be(0));
let err = parse_ambient_viewing_environment(&payload).unwrap_err();
assert_eq!(err, SeiError::AmbientLightOutOfRange { x: 50_001, y: 0 });
}
#[test]
fn parse_payload_dispatches_ambient_viewing_environment() {
let mut payload = Vec::new();
payload.extend_from_slice(&u32_be(50_000));
payload.extend_from_slice(&u16_be(14_155));
payload.extend_from_slice(&u16_be(14_855));
let ctx = SeiContext::default();
match parse_payload(148, &payload, &ctx).unwrap() {
SeiPayload::AmbientViewingEnvironment(a) => {
assert_eq!(a.ambient_illuminance, 50_000);
assert_eq!(a.ambient_light_x, 14_155);
assert_eq!(a.ambient_light_y, 14_855);
}
other => panic!("expected AmbientViewingEnvironment, got {other:?}"),
}
}
fn ccv_header(cancel: u8, persistence: u8, prim: u8, min: u8, max: u8, avg: u8) -> Vec<u8> {
pack_bits(&[
(cancel as u64, 1),
(persistence as u64, 1),
(prim as u64, 1),
(min as u64, 1),
(max as u64, 1),
(avg as u64, 1),
(0, 2), ])
}
#[test]
fn content_colour_volume_cancel() {
let payload = pack_bits(&[(1, 1)]);
let got = parse_content_colour_volume(&payload).unwrap();
assert!(got.cancel_flag);
assert!(got.body.is_none());
}
#[test]
fn content_colour_volume_all_present_bt2020() {
let mut payload = ccv_header(0, 1, 1, 1, 1, 1);
for &(x, y) in &[(8_500i32, 39_850i32), (6_550, 2_300), (35_400, 14_600)] {
payload.extend_from_slice(&i32_be(x));
payload.extend_from_slice(&i32_be(y));
}
payload.extend_from_slice(&u32_be(1)); payload.extend_from_slice(&u32_be(100_000_000)); payload.extend_from_slice(&u32_be(50_000_000)); let got = parse_content_colour_volume(&payload).unwrap();
assert!(!got.cancel_flag);
let body = got.body.expect("body present");
assert!(body.persistence_flag);
let prims = body.primaries.expect("primaries present");
assert_eq!(
prims[0],
ContentColourVolumePrimary {
x: 8_500,
y: 39_850
}
);
assert_eq!(prims[1], ContentColourVolumePrimary { x: 6_550, y: 2_300 });
assert_eq!(
prims[2],
ContentColourVolumePrimary {
x: 35_400,
y: 14_600
}
);
assert_eq!(body.min_luminance_value, Some(1));
assert_eq!(body.max_luminance_value, Some(100_000_000));
assert_eq!(body.avg_luminance_value, Some(50_000_000));
}
#[test]
fn content_colour_volume_luminance_only() {
let mut payload = ccv_header(0, 0, 0, 0, 1, 0);
payload.extend_from_slice(&u32_be(42_000_000));
let got = parse_content_colour_volume(&payload).unwrap();
let body = got.body.expect("body present");
assert!(!body.persistence_flag);
assert!(body.primaries.is_none());
assert!(body.min_luminance_value.is_none());
assert_eq!(body.max_luminance_value, Some(42_000_000));
assert!(body.avg_luminance_value.is_none());
}
#[test]
fn content_colour_volume_negative_primary_coord() {
let mut payload = ccv_header(0, 0, 1, 0, 0, 0);
for &(x, y) in &[
(-1_000_000i32, 2_000_000i32),
(0, 0),
(5_000_000, -5_000_000),
] {
payload.extend_from_slice(&i32_be(x));
payload.extend_from_slice(&i32_be(y));
}
let got = parse_content_colour_volume(&payload).unwrap();
let prims = got.body.unwrap().primaries.unwrap();
assert_eq!(
prims[0],
ContentColourVolumePrimary {
x: -1_000_000,
y: 2_000_000
}
);
assert_eq!(
prims[2],
ContentColourVolumePrimary {
x: 5_000_000,
y: -5_000_000
}
);
}
#[test]
fn content_colour_volume_no_components_rejected() {
let payload = ccv_header(0, 1, 0, 0, 0, 0);
let err = parse_content_colour_volume(&payload).unwrap_err();
assert_eq!(err, SeiError::ContentColourVolumeNoComponents);
}
#[test]
fn content_colour_volume_primary_out_of_range_rejected() {
let mut payload = ccv_header(0, 0, 1, 0, 0, 0);
payload.extend_from_slice(&i32_be(5_000_001));
payload.extend_from_slice(&i32_be(0));
for _ in 0..4 {
payload.extend_from_slice(&i32_be(0));
}
let err = parse_content_colour_volume(&payload).unwrap_err();
assert_eq!(
err,
SeiError::ContentColourVolumePrimaryOutOfRange {
c: 0,
x: 5_000_001,
y: 0
}
);
}
#[test]
fn content_colour_volume_luminance_order_min_gt_max_rejected() {
let mut payload = ccv_header(0, 0, 0, 1, 1, 0);
payload.extend_from_slice(&u32_be(100)); payload.extend_from_slice(&u32_be(50)); let err = parse_content_colour_volume(&payload).unwrap_err();
assert_eq!(
err,
SeiError::ContentColourVolumeLuminanceOrder {
min: 100,
avg: 100,
max: 50
}
);
}
#[test]
fn content_colour_volume_luminance_order_min_gt_avg_rejected() {
let mut payload = ccv_header(0, 0, 0, 1, 0, 1);
payload.extend_from_slice(&u32_be(10)); payload.extend_from_slice(&u32_be(5)); let err = parse_content_colour_volume(&payload).unwrap_err();
assert_eq!(
err,
SeiError::ContentColourVolumeLuminanceOrder {
min: 10,
avg: 5,
max: u64::MAX
}
);
}
#[test]
fn content_colour_volume_luminance_order_avg_gt_max_rejected() {
let mut payload = ccv_header(0, 0, 0, 0, 1, 1);
payload.extend_from_slice(&u32_be(60)); payload.extend_from_slice(&u32_be(80)); let err = parse_content_colour_volume(&payload).unwrap_err();
assert_eq!(
err,
SeiError::ContentColourVolumeLuminanceOrder {
min: 0,
avg: 80,
max: 60
}
);
}
#[test]
fn parse_payload_dispatches_content_colour_volume() {
let mut payload = ccv_header(0, 1, 0, 0, 1, 0);
payload.extend_from_slice(&u32_be(10_000_000));
let ctx = SeiContext::default();
match parse_payload(149, &payload, &ctx).unwrap() {
SeiPayload::ContentColourVolume(c) => {
assert!(!c.cancel_flag);
let body = c.body.unwrap();
assert!(body.persistence_flag);
assert_eq!(body.max_luminance_value, Some(10_000_000));
}
other => panic!("expected ContentColourVolume, got {other:?}"),
}
}
#[test]
fn colour_remapping_info_cancel() {
let payload = pack_bits(&[(0b011, 3), (1, 1)]);
let got = parse_colour_remapping_info(&payload).unwrap();
assert_eq!(got.colour_remap_id, 2);
assert!(got.cancel_flag);
assert!(got.body.is_none());
}
#[test]
fn colour_remapping_info_minimal_no_optionals() {
let payload = pack_bits(&[
(1, 1), (0, 1), (1, 1), (0, 1), (8, 8), (8, 8), (0, 8), (0, 8), (0, 8), (0, 1), (0, 8), (0, 8), (0, 8), ]);
let got = parse_colour_remapping_info(&payload).unwrap();
assert_eq!(got.colour_remap_id, 0);
assert!(!got.cancel_flag);
let body = got.body.unwrap();
assert_eq!(body.repetition_period, 0);
assert!(body.video_signal_info.is_none());
assert_eq!(body.input_bit_depth, 8);
assert_eq!(body.output_bit_depth, 8);
assert!(body.pre_lut.iter().all(|c| c.is_empty()));
assert!(body.matrix.is_none());
assert!(body.post_lut.iter().all(|c| c.is_empty()));
}
#[test]
fn colour_remapping_info_full_8bit() {
let payload = pack_bits(&[
(0b010, 3), (0, 1), (1, 1), (1, 1), (1, 1), (1, 8), (1, 8), (1, 8), (8, 8), (8, 8), (1, 8),
(16, 8), (32, 8), (200, 8), (220, 8), (0, 8), (0, 8), (1, 1), (5, 4), (64, 13), (1, 1), (1, 1), (1, 1), (64, 13), (1, 1), (1, 1), (1, 1), (64, 13), (0, 8),
(0, 8), (1, 8),
(5, 8), (9, 8), (250, 8), (255, 8), ]);
let got = parse_colour_remapping_info(&payload).unwrap();
assert_eq!(got.colour_remap_id, 1);
let body = got.body.unwrap();
let vsi = body.video_signal_info.unwrap();
assert!(vsi.full_range_flag);
assert_eq!(vsi.primaries, 1);
assert_eq!(vsi.transfer_function, 1);
assert_eq!(vsi.matrix_coefficients, 1);
assert_eq!(body.pre_lut[0].len(), 2);
assert_eq!(body.pre_lut[0][0].coded_value, 16);
assert_eq!(body.pre_lut[0][0].target_value, 32);
assert_eq!(body.pre_lut[0][1].coded_value, 200);
assert_eq!(body.pre_lut[0][1].target_value, 220);
assert!(body.pre_lut[1].is_empty());
assert!(body.pre_lut[2].is_empty());
let m = body.matrix.unwrap();
assert_eq!(m.log2_matrix_denom, 5);
assert_eq!(m.coeffs, [[32, 0, 0], [0, 32, 0], [0, 0, 32]]);
assert!(body.post_lut[0].is_empty());
assert!(body.post_lut[1].is_empty());
assert_eq!(body.post_lut[2].len(), 2);
assert_eq!(body.post_lut[2][1].coded_value, 250);
assert_eq!(body.post_lut[2][1].target_value, 255);
}
#[test]
fn colour_remapping_info_10bit_uses_16bit_lut_values() {
let payload = pack_bits(&[
(1, 1), (0, 1), (1, 1), (0, 1), (10, 8), (12, 8), (0, 8), (1, 8),
(500, 16), (1000, 16), (1000, 16), (3000, 16), (0, 8), (0, 1), (0, 8), (0, 8), (0, 8), ]);
let got = parse_colour_remapping_info(&payload).unwrap();
let body = got.body.unwrap();
assert_eq!(body.input_bit_depth, 10);
assert_eq!(body.output_bit_depth, 12);
assert_eq!(body.pre_lut[1].len(), 2);
assert_eq!(body.pre_lut[1][0].coded_value, 500);
assert_eq!(body.pre_lut[1][0].target_value, 1000);
assert_eq!(body.pre_lut[1][1].coded_value, 1000);
assert_eq!(body.pre_lut[1][1].target_value, 3000);
}
#[test]
fn colour_remapping_info_negative_matrix_coeff() {
let payload = pack_bits(&[
(1, 1), (0, 1), (1, 1), (0, 1), (8, 8), (8, 8), (0, 8), (0, 8), (0, 8), (1, 1), (0, 4), (0b011, 3),
(0b011, 3),
(0b011, 3),
(0b011, 3),
(0b011, 3),
(0b011, 3),
(0b011, 3),
(0b011, 3),
(0b011, 3),
(0, 8), (0, 8), (0, 8), ]);
let got = parse_colour_remapping_info(&payload).unwrap();
let m = got.body.unwrap().matrix.unwrap();
assert_eq!(m.log2_matrix_denom, 0);
assert_eq!(m.coeffs, [[-1, -1, -1], [-1, -1, -1], [-1, -1, -1]]);
}
#[test]
fn colour_remapping_info_input_bit_depth_too_low_rejected() {
let payload = pack_bits(&[
(1, 1), (0, 1), (1, 1), (0, 1), (7, 8), (8, 8), ]);
let err = parse_colour_remapping_info(&payload).unwrap_err();
assert_eq!(
err,
SeiError::ColourRemapBitDepthOutOfRange {
which: "input",
got: 7
}
);
}
#[test]
fn colour_remapping_info_repetition_period_out_of_range_rejected() {
let payload = pack_bits(&[
(1, 1), (0, 1), (0, 14), (0b100000000000010, 15), ]);
let err = parse_colour_remapping_info(&payload).unwrap_err();
assert_eq!(err, SeiError::ColourRemapRepetitionPeriodOutOfRange(16385));
}
#[test]
fn colour_remapping_info_lut_count_out_of_range_rejected() {
let payload = pack_bits(&[
(1, 1), (0, 1), (1, 1), (0, 1), (8, 8), (8, 8), (33, 8), ]);
let err = parse_colour_remapping_info(&payload).unwrap_err();
assert_eq!(
err,
SeiError::ColourRemapLutCountOutOfRange {
which: "pre",
c: 0,
got: 33
}
);
}
#[test]
fn parse_payload_dispatches_colour_remapping_info() {
let payload = pack_bits(&[(1, 1), (1, 1)]); let ctx = SeiContext::default();
match parse_payload(142, &payload, &ctx).unwrap() {
SeiPayload::ColourRemappingInfo(c) => {
assert_eq!(c.colour_remap_id, 0);
assert!(c.cancel_flag);
assert!(c.body.is_none());
}
other => panic!("expected ColourRemappingInfo, got {other:?}"),
}
}
#[test]
fn equirectangular_projection_cancel() {
let payload = pack_bits(&[(1, 1)]);
let got = parse_equirectangular_projection(&payload).unwrap();
assert!(got.cancel_flag);
assert!(got.persistence_flag.is_none());
assert!(got.padding_flag.is_none());
assert!(got.gb_erp_type.is_none());
}
#[test]
fn equirectangular_projection_no_padding() {
let payload = pack_bits(&[
(0, 1), (1, 1), (0, 1), (0, 2), ]);
let got = parse_equirectangular_projection(&payload).unwrap();
assert!(!got.cancel_flag);
assert_eq!(got.persistence_flag, Some(true));
assert_eq!(got.padding_flag, Some(false));
assert!(got.gb_erp_type.is_none());
assert!(got.left_gb_erp_width.is_none());
assert!(got.right_gb_erp_width.is_none());
}
#[test]
fn equirectangular_projection_with_padding() {
let payload = pack_bits(&[
(0, 1), (0, 1), (1, 1), (0, 2), (2, 3), (8, 8), (4, 8), ]);
let got = parse_equirectangular_projection(&payload).unwrap();
assert!(!got.cancel_flag);
assert_eq!(got.persistence_flag, Some(false));
assert_eq!(got.padding_flag, Some(true));
assert_eq!(got.gb_erp_type, Some(2));
assert_eq!(got.left_gb_erp_width, Some(8));
assert_eq!(got.right_gb_erp_width, Some(4));
}
#[test]
fn parse_payload_dispatches_equirectangular_projection() {
let payload = pack_bits(&[(1, 1)]);
let ctx = SeiContext::default();
match parse_payload(150, &payload, &ctx).unwrap() {
SeiPayload::EquirectangularProjection(e) => assert!(e.cancel_flag),
other => panic!("expected EquirectangularProjection, got {other:?}"),
}
}
#[test]
fn cubemap_projection_cancel() {
let payload = pack_bits(&[(1, 1)]);
let got = parse_cubemap_projection(&payload).unwrap();
assert!(got.cancel_flag);
assert!(got.persistence_flag.is_none());
}
#[test]
fn cubemap_projection_persist() {
let payload = pack_bits(&[(0, 1), (1, 1)]);
let got = parse_cubemap_projection(&payload).unwrap();
assert!(!got.cancel_flag);
assert_eq!(got.persistence_flag, Some(true));
}
#[test]
fn parse_payload_dispatches_cubemap_projection() {
let payload = pack_bits(&[(0, 1), (0, 1)]);
let ctx = SeiContext::default();
match parse_payload(151, &payload, &ctx).unwrap() {
SeiPayload::CubemapProjection(c) => {
assert!(!c.cancel_flag);
assert_eq!(c.persistence_flag, Some(false));
}
other => panic!("expected CubemapProjection, got {other:?}"),
}
}
#[test]
fn sphere_rotation_cancel() {
let payload = pack_bits(&[(1, 1)]);
let got = parse_sphere_rotation(&payload).unwrap();
assert!(got.cancel_flag);
assert!(got.yaw_rotation.is_none());
assert!(got.pitch_rotation.is_none());
assert!(got.roll_rotation.is_none());
}
#[test]
fn sphere_rotation_zero_angles() {
let mut payload = vec![0x40];
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(0));
let got = parse_sphere_rotation(&payload).unwrap();
assert!(!got.cancel_flag);
assert_eq!(got.persistence_flag, Some(true));
assert_eq!(got.yaw_rotation, Some(0));
assert_eq!(got.pitch_rotation, Some(0));
assert_eq!(got.roll_rotation, Some(0));
}
#[test]
fn sphere_rotation_min_max_angles() {
let mut payload = vec![0x00];
payload.extend_from_slice(&i32_be(-11_796_480));
payload.extend_from_slice(&i32_be(5_898_240));
payload.extend_from_slice(&i32_be(11_796_479));
let got = parse_sphere_rotation(&payload).unwrap();
assert_eq!(got.yaw_rotation, Some(-11_796_480));
assert_eq!(got.pitch_rotation, Some(5_898_240));
assert_eq!(got.roll_rotation, Some(11_796_479));
}
#[test]
fn sphere_rotation_yaw_out_of_range_rejected() {
let mut payload = vec![0x00];
payload.extend_from_slice(&i32_be(11_796_480));
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(0));
let err = parse_sphere_rotation(&payload).unwrap_err();
assert_eq!(err, SeiError::SphereRotationYawOutOfRange(11_796_480));
}
#[test]
fn sphere_rotation_pitch_out_of_range_rejected() {
let mut payload = vec![0x00];
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(-5_898_241));
payload.extend_from_slice(&i32_be(0));
let err = parse_sphere_rotation(&payload).unwrap_err();
assert_eq!(err, SeiError::SphereRotationPitchOutOfRange(-5_898_241));
}
#[test]
fn sphere_rotation_roll_out_of_range_rejected() {
let mut payload = vec![0x00];
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(11_796_480));
let err = parse_sphere_rotation(&payload).unwrap_err();
assert_eq!(err, SeiError::SphereRotationRollOutOfRange(11_796_480));
}
#[test]
fn parse_payload_dispatches_sphere_rotation() {
let payload = pack_bits(&[(1, 1)]);
let ctx = SeiContext::default();
match parse_payload(154, &payload, &ctx).unwrap() {
SeiPayload::SphereRotation(s) => assert!(s.cancel_flag),
other => panic!("expected SphereRotation, got {other:?}"),
}
}
#[test]
fn omni_viewport_cancel() {
let payload = pack_bits(&[(42, 10), (1, 1)]);
let got = parse_omni_viewport(&payload).unwrap();
assert_eq!(got.viewport_id, 42);
assert!(got.cancel_flag);
assert!(got.persistence_flag.is_none());
assert!(got.viewports.is_empty());
}
#[test]
fn omni_viewport_single_directors_cut() {
let mut payload = vec![0x00, 0x10];
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&u32_be(1));
payload.extend_from_slice(&u32_be(1));
let got = parse_omni_viewport(&payload).unwrap();
assert_eq!(got.viewport_id, 0);
assert!(!got.cancel_flag);
assert_eq!(got.persistence_flag, Some(true));
assert_eq!(got.viewports.len(), 1);
let v = got.viewports[0];
assert_eq!(v.azimuth_centre, 0);
assert_eq!(v.elevation_centre, 0);
assert_eq!(v.tilt_centre, 0);
assert_eq!(v.hor_range, 1);
assert_eq!(v.ver_range, 1);
}
#[test]
fn omni_viewport_max_range_boundary_accepted() {
let mut payload = vec![0x00, 0x40];
payload.extend_from_slice(&i32_be(180 * (1 << 16) - 1));
payload.extend_from_slice(&i32_be(90 * (1 << 16)));
payload.extend_from_slice(&i32_be(-180 * (1 << 16)));
payload.extend_from_slice(&u32_be(360 * (1 << 16)));
payload.extend_from_slice(&u32_be(180 * (1 << 16)));
let got = parse_omni_viewport(&payload).unwrap();
assert_eq!(got.viewport_id, 1);
assert_eq!(got.viewports.len(), 1);
let v = got.viewports[0];
assert_eq!(v.azimuth_centre, 180 * (1 << 16) - 1);
assert_eq!(v.elevation_centre, 90 * (1 << 16));
assert_eq!(v.tilt_centre, -180 * (1 << 16));
assert_eq!(v.hor_range, 360 * (1 << 16));
assert_eq!(v.ver_range, 180 * (1 << 16));
}
#[test]
fn omni_viewport_two_entries() {
let mut payload = vec![0x00, 0x91];
payload.extend_from_slice(&i32_be(100));
payload.extend_from_slice(&i32_be(200));
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&u32_be(1024));
payload.extend_from_slice(&u32_be(512));
payload.extend_from_slice(&i32_be(-300));
payload.extend_from_slice(&i32_be(-400));
payload.extend_from_slice(&i32_be(50));
payload.extend_from_slice(&u32_be(2048));
payload.extend_from_slice(&u32_be(1024));
let got = parse_omni_viewport(&payload).unwrap();
assert_eq!(got.viewport_id, 2);
assert_eq!(got.viewports.len(), 2);
assert_eq!(got.viewports[0].azimuth_centre, 100);
assert_eq!(got.viewports[1].azimuth_centre, -300);
assert_eq!(got.viewports[1].tilt_centre, 50);
}
#[test]
fn omni_viewport_hor_range_zero_rejected() {
let mut payload = vec![0x00, 0x10];
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&u32_be(0));
payload.extend_from_slice(&u32_be(1));
let err = parse_omni_viewport(&payload).unwrap_err();
assert_eq!(err, SeiError::OmniViewportHorRangeOutOfRange(0));
}
#[test]
fn omni_viewport_ver_range_overflow_rejected() {
let mut payload = vec![0x00, 0x10];
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&u32_be(1));
payload.extend_from_slice(&u32_be(180 * (1 << 16) + 1));
let err = parse_omni_viewport(&payload).unwrap_err();
assert_eq!(
err,
SeiError::OmniViewportVerRangeOutOfRange(180 * (1 << 16) + 1)
);
}
#[test]
fn omni_viewport_azimuth_overflow_rejected() {
let mut payload = vec![0x00, 0x10];
payload.extend_from_slice(&i32_be(180 * (1 << 16))); payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&u32_be(1));
payload.extend_from_slice(&u32_be(1));
let err = parse_omni_viewport(&payload).unwrap_err();
assert_eq!(
err,
SeiError::OmniViewportAzimuthOutOfRange(180 * (1 << 16))
);
}
#[test]
fn omni_viewport_elevation_underflow_rejected() {
let mut payload = vec![0x00, 0x10];
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&i32_be(-90 * (1 << 16) - 1));
payload.extend_from_slice(&i32_be(0));
payload.extend_from_slice(&u32_be(1));
payload.extend_from_slice(&u32_be(1));
let err = parse_omni_viewport(&payload).unwrap_err();
assert_eq!(
err,
SeiError::OmniViewportElevationOutOfRange(-90 * (1 << 16) - 1)
);
}
#[test]
fn parse_payload_dispatches_omni_viewport() {
let payload = pack_bits(&[(0, 10), (1, 1)]);
let ctx = SeiContext::default();
match parse_payload(156, &payload, &ctx).unwrap() {
SeiPayload::OmniViewport(v) => {
assert!(v.cancel_flag);
assert_eq!(v.viewport_id, 0);
}
other => panic!("expected OmniViewport, got {other:?}"),
}
}
#[test]
fn regionwise_packing_cancel_skips_body() {
let payload = pack_bits(&[(1, 1)]);
let got = parse_regionwise_packing(&payload).unwrap();
assert!(got.cancel_flag);
assert!(got.persistence_flag.is_none());
assert!(got.constituent_picture_matching_flag.is_none());
assert!(got.proj_picture_width.is_none());
assert!(got.regions.is_empty());
}
#[test]
fn regionwise_packing_single_region_no_guard_band() {
let fields = [
(0u64, 1),
(1, 1),
(0, 1),
(0, 5),
(1, 8),
(3840, 32),
(1920, 32),
(2560, 16),
(1280, 16),
(0, 4),
(5, 3),
(0, 1),
(1920, 32),
(1920, 32),
(0, 32),
(0, 32),
(1280, 16),
(1280, 16),
(0, 16),
(0, 16),
];
let payload = pack_bits(&fields);
let got = parse_regionwise_packing(&payload).unwrap();
assert!(!got.cancel_flag);
assert_eq!(got.persistence_flag, Some(true));
assert_eq!(got.constituent_picture_matching_flag, Some(false));
assert_eq!(got.proj_picture_width, Some(3840));
assert_eq!(got.proj_picture_height, Some(1920));
assert_eq!(got.packed_picture_width, Some(2560));
assert_eq!(got.packed_picture_height, Some(1280));
assert_eq!(got.regions.len(), 1);
let reg = got.regions[0];
assert_eq!(reg.transform_type, 5);
assert_eq!(reg.proj_region_width, 1920);
assert_eq!(reg.proj_region_height, 1920);
assert_eq!(reg.packed_region_width, 1280);
assert_eq!(reg.packed_region_height, 1280);
assert!(reg.guard_band.is_none());
}
#[test]
fn regionwise_packing_region_with_guard_band() {
let fields = [
(0u64, 1), (0, 1), (1, 1), (0, 5), (1, 8), (1000, 32),
(500, 32),
(1000, 16),
(500, 16),
(0, 4), (0, 3), (1, 1), (100, 32),
(100, 32),
(0, 32),
(0, 32),
(100, 16),
(100, 16),
(0, 16),
(0, 16),
(4, 8), (4, 8), (2, 8), (2, 8), (1, 1), (1, 3), (2, 3), (3, 3), (1, 3), (0, 3), ];
let payload = pack_bits(&fields);
let got = parse_regionwise_packing(&payload).unwrap();
assert_eq!(got.constituent_picture_matching_flag, Some(true));
assert_eq!(got.regions.len(), 1);
let gb = got.regions[0].guard_band.expect("guard band present");
assert_eq!(gb.left_gb_width, 4);
assert_eq!(gb.right_gb_width, 4);
assert_eq!(gb.top_gb_height, 2);
assert_eq!(gb.bottom_gb_height, 2);
assert!(gb.gb_not_used_for_pred_flag);
assert_eq!(gb.gb_type, [1, 2, 3, 1]);
}
#[test]
fn regionwise_packing_two_regions_mixed_guard_band() {
let fields = [
(0u64, 1), (1, 1), (0, 1), (0, 5), (2, 8), (2000, 32),
(1000, 32),
(2000, 16),
(1000, 16),
(0, 4),
(2, 3), (1, 1), (1000, 32),
(1000, 32),
(0, 32),
(0, 32),
(1000, 16),
(1000, 16),
(0, 16),
(0, 16),
(8, 8),
(0, 8),
(0, 8),
(0, 8),
(0, 1), (0, 3),
(1, 3),
(1, 3),
(1, 3),
(0, 3),
(0, 4),
(7, 3), (0, 1), (1000, 32),
(1000, 32),
(0, 32),
(1000, 32),
(1000, 16),
(1000, 16),
(0, 16),
(1000, 16),
];
let payload = pack_bits(&fields);
let got = parse_regionwise_packing(&payload).unwrap();
assert_eq!(got.regions.len(), 2);
assert_eq!(got.regions[0].transform_type, 2);
let gb = got.regions[0].guard_band.expect("region 0 guard band");
assert_eq!(gb.left_gb_width, 8);
assert!(!gb.gb_not_used_for_pred_flag);
assert_eq!(got.regions[1].transform_type, 7);
assert_eq!(got.regions[1].proj_region_left, 1000);
assert_eq!(got.regions[1].packed_region_left, 1000);
assert!(got.regions[1].guard_band.is_none());
}
#[test]
fn regionwise_packing_zero_regions_rejected() {
let fields = [(0u64, 1), (0, 1), (0, 1), (0, 5), (0, 8)];
let payload = pack_bits(&fields);
assert_eq!(
parse_regionwise_packing(&payload),
Err(SeiError::RegionWisePackingNoRegions)
);
}
#[test]
fn regionwise_packing_zero_proj_picture_rejected() {
let fields = [
(0u64, 1),
(0, 1),
(0, 1),
(0, 5),
(1, 8),
(0, 32),
(100, 32),
];
let payload = pack_bits(&fields);
assert_eq!(
parse_regionwise_packing(&payload),
Err(SeiError::RegionWisePackingProjPictureZero { w: 0, h: 100 })
);
}
#[test]
fn regionwise_packing_zero_packed_picture_rejected() {
let fields = [
(0u64, 1),
(0, 1),
(0, 1),
(0, 5),
(1, 8),
(100, 32),
(100, 32),
(100, 16),
(0, 16),
];
let payload = pack_bits(&fields);
assert_eq!(
parse_regionwise_packing(&payload),
Err(SeiError::RegionWisePackingPackedPictureZero { w: 100, h: 0 })
);
}
#[test]
fn regionwise_packing_guard_band_all_zero_rejected() {
let fields = [
(0u64, 1),
(0, 1),
(0, 1),
(0, 5),
(1, 8),
(100, 32),
(100, 32),
(100, 16),
(100, 16),
(0, 4),
(0, 3),
(1, 1), (50, 32),
(50, 32),
(0, 32),
(0, 32),
(50, 16),
(50, 16),
(0, 16),
(0, 16),
(0, 8), (0, 8),
(0, 8),
(0, 8),
];
let payload = pack_bits(&fields);
assert_eq!(
parse_regionwise_packing(&payload),
Err(SeiError::RegionWisePackingGuardBandAllZero(0))
);
}
#[test]
fn parse_payload_dispatches_regionwise_packing() {
let payload = pack_bits(&[(1, 1)]);
let ctx = SeiContext::default();
match parse_payload(155, &payload, &ctx).unwrap() {
SeiPayload::RegionWisePacking(r) => {
assert!(r.cancel_flag);
assert!(r.regions.is_empty());
}
other => panic!("expected RegionWisePacking, got {other:?}"),
}
}
#[test]
fn shutter_interval_info_sub_layer_idx_nonzero_skips_body() {
let payload = vec![0x40];
let got = parse_shutter_interval_info(&payload).unwrap();
assert_eq!(got.sub_layer_idx, 1);
assert!(got.body.is_none());
}
#[test]
fn shutter_interval_info_present_false() {
let payload = vec![0x80];
let got = parse_shutter_interval_info(&payload).unwrap();
assert_eq!(got.sub_layer_idx, 0);
let body = got.body.expect("body present when sub_layer_idx==0");
assert!(!body.info_present_flag);
assert!(body.schedule.is_none());
}
#[test]
fn shutter_interval_info_fixed_27mhz_1_080_000() {
let payload = pack_bits(&[
(1, 1), (1, 1), (27_000_000, 32), (1, 1), (1_080_000, 32), ]);
let got = parse_shutter_interval_info(&payload).unwrap();
assert_eq!(got.sub_layer_idx, 0);
let body = got.body.expect("body present");
assert!(body.info_present_flag);
let sched = body.schedule.expect("schedule present");
assert_eq!(sched.time_scale, 27_000_000);
match sched.kind {
ShutterIntervalKind::Fixed { num_units } => assert_eq!(num_units, 1_080_000),
other => panic!("expected Fixed, got {other:?}"),
}
}
#[test]
fn shutter_interval_info_per_sub_layer() {
let payload = pack_bits(&[
(1, 1), (1, 1), (90_000, 32), (0, 1), (2, 3), (1_800, 32), (900, 32), (450, 32), ]);
let got = parse_shutter_interval_info(&payload).unwrap();
let body = got.body.expect("body present");
let sched = body.schedule.expect("schedule present");
assert_eq!(sched.time_scale, 90_000);
match sched.kind {
ShutterIntervalKind::PerSubLayer {
max_sub_layers_minus1,
num_units_per_sub_layer,
} => {
assert_eq!(max_sub_layers_minus1, 2);
assert_eq!(num_units_per_sub_layer, vec![1_800, 900, 450]);
}
other => panic!("expected PerSubLayer, got {other:?}"),
}
}
#[test]
fn shutter_interval_info_time_scale_zero_rejected() {
let payload = pack_bits(&[
(1, 1), (1, 1), (0, 32), (1, 1), (1, 32), ]);
let err = parse_shutter_interval_info(&payload).unwrap_err();
assert_eq!(err, SeiError::ShutterIntervalTimeScaleZero);
}
#[test]
fn parse_payload_dispatches_shutter_interval_info() {
let payload = vec![0x80];
let ctx = SeiContext::default();
match parse_payload(205, &payload, &ctx).unwrap() {
SeiPayload::ShutterIntervalInfo(s) => {
assert_eq!(s.sub_layer_idx, 0);
assert!(s.body.is_some());
}
other => panic!("expected ShutterIntervalInfo, got {other:?}"),
}
}
#[test]
fn sei_manifest_empty() {
let payload = vec![0x00, 0x00];
let got = parse_sei_manifest(&payload).unwrap();
assert!(got.entries.is_empty());
}
#[test]
fn sei_manifest_single_entry_necessary() {
let payload = vec![
0x00, 0x01, 0x00, 0x2D, 0x01, ];
let got = parse_sei_manifest(&payload).unwrap();
assert_eq!(got.entries.len(), 1);
assert_eq!(got.entries[0].payload_type, 45);
assert_eq!(
got.entries[0].description,
SeiManifestDescription::ExpectedNecessary
);
}
#[test]
fn sei_manifest_three_entries_mixed_descriptions() {
let payload = vec![
0x00, 0x03, 0x00, 0x00, 0x01, 0x00, 0x90, 0x02, 0x00, 0x97, 0x03, ];
let got = parse_sei_manifest(&payload).unwrap();
assert_eq!(got.entries.len(), 3);
assert_eq!(got.entries[0].payload_type, 0);
assert_eq!(
got.entries[0].description,
SeiManifestDescription::ExpectedNecessary
);
assert_eq!(got.entries[1].payload_type, 144);
assert_eq!(
got.entries[1].description,
SeiManifestDescription::ExpectedUnnecessary
);
assert_eq!(got.entries[2].payload_type, 151);
assert_eq!(
got.entries[2].description,
SeiManifestDescription::ExpectedUndetermined
);
}
#[test]
fn sei_manifest_reserved_description_preserved() {
let payload = vec![
0x00, 0x01, 0x00, 0x05, 0x42, ];
let got = parse_sei_manifest(&payload).unwrap();
assert_eq!(got.entries.len(), 1);
assert_eq!(
got.entries[0].description,
SeiManifestDescription::Reserved(0x42)
);
}
#[test]
fn sei_manifest_duplicate_payload_type_rejected() {
let payload = vec![
0x00, 0x02, 0x00, 0x2D, 0x01, 0x00, 0x2D, 0x02, ];
let err = parse_sei_manifest(&payload).unwrap_err();
assert_eq!(
err,
SeiError::SeiManifestDuplicatePayloadType {
payload_type: 45,
first: 0,
second: 1
}
);
}
#[test]
fn sei_manifest_truncated_input_rejects() {
let payload = vec![0x00, 0x01, 0x00, 0x2D]; let err = parse_sei_manifest(&payload).unwrap_err();
assert!(matches!(err, SeiError::Bitstream(_)));
}
#[test]
fn parse_payload_dispatches_sei_manifest() {
let payload = vec![
0x00, 0x01, 0x00, 0x00, 0x00, ];
let ctx = SeiContext::default();
match parse_payload(200, &payload, &ctx).unwrap() {
SeiPayload::SeiManifest(m) => {
assert_eq!(m.entries.len(), 1);
assert_eq!(m.entries[0].payload_type, 0);
assert_eq!(
m.entries[0].description,
SeiManifestDescription::NotExpected
);
}
other => panic!("expected SeiManifest, got {other:?}"),
}
}
#[test]
fn sei_prefix_indication_single_byte_aligned() {
let payload = vec![
0x00, 0x2D, 0x00, 0x00, 0x07, 0xA5, ];
let got = parse_sei_prefix_indication(&payload).unwrap();
assert_eq!(got.prefix_sei_payload_type, 45);
assert_eq!(got.indications.len(), 1);
assert_eq!(got.indications[0].bit_count, 8);
assert_eq!(got.indications[0].data, vec![0xA5]);
}
#[test]
fn sei_prefix_indication_short_bitstring_with_alignment_one_bits() {
let payload = vec![
0x00, 0x2D, 0x00, 0x00, 0x02, 0xBF, ];
let got = parse_sei_prefix_indication(&payload).unwrap();
assert_eq!(got.indications.len(), 1);
assert_eq!(got.indications[0].bit_count, 3);
assert_eq!(got.indications[0].data, vec![0xA0]);
}
#[test]
fn sei_prefix_indication_two_entries() {
let payload = vec![
0x00, 0x2D, 0x01, 0x00, 0x03, 0xCF, 0x00, 0x0B, 0xAC, 0x3F, ];
let got = parse_sei_prefix_indication(&payload).unwrap();
assert_eq!(got.indications.len(), 2);
assert_eq!(got.indications[0].bit_count, 4);
assert_eq!(got.indications[0].data, vec![0xC0]);
assert_eq!(got.indications[1].bit_count, 12);
assert_eq!(got.indications[1].data, vec![0xAC, 0x30]);
}
#[test]
fn sei_prefix_indication_oversized_bit_count_rejected() {
let payload = vec![
0x00, 0x2D, 0x00, 0x00, 0x08, 0xFF, ];
let err = parse_sei_prefix_indication(&payload).unwrap_err();
match err {
SeiError::SeiPrefixIndicationOverflow { i, bits, available } => {
assert_eq!(i, 0);
assert_eq!(bits, 9);
assert_eq!(available, 8);
}
other => panic!("expected SeiPrefixIndicationOverflow, got {other:?}"),
}
}
#[test]
fn sei_prefix_indication_truncated_header_rejected() {
let payload = vec![0x00, 0x2D]; let err = parse_sei_prefix_indication(&payload).unwrap_err();
assert!(matches!(err, SeiError::Bitstream(_)));
}
#[test]
fn parse_payload_dispatches_sei_prefix_indication() {
let payload = vec![
0x00, 0x2D, 0x00, 0x00, 0x07, 0xA5,
];
let ctx = SeiContext::default();
match parse_payload(201, &payload, &ctx).unwrap() {
SeiPayload::SeiPrefixIndication(p) => {
assert_eq!(p.prefix_sei_payload_type, 45);
assert_eq!(p.indications.len(), 1);
assert_eq!(p.indications[0].bit_count, 8);
assert_eq!(p.indications[0].data, vec![0xA5]);
}
other => panic!("expected SeiPrefixIndication, got {other:?}"),
}
}
fn build_t35_atsc1(user_identifier: u32, body: &[u8]) -> Vec<u8> {
let mut v = Vec::with_capacity(7 + body.len());
v.push(0xB5);
v.extend_from_slice(&[0x00, 0x31]); v.extend_from_slice(&user_identifier.to_be_bytes());
v.extend_from_slice(body);
v
}
#[test]
fn atsc1_envelope_ga94_cc_data_round_trip() {
let cc = [0xFC, 0x94, 0x2C, 0xFE, 0xFF];
let mut body = vec![0x03u8];
body.extend_from_slice(&cc);
let raw = build_t35_atsc1(Atsc1UserIdentifier::GA94, &body);
let outer = parse_user_data_registered_itu_t_t35(&raw).unwrap();
assert_eq!(outer.country_code, 0xB5);
assert!(outer.country_code_extension.is_none());
let env = parse_atsc1_envelope(&outer.payload_bytes).unwrap();
assert_eq!(env.provider_code, 0x0031);
assert_eq!(env.user_identifier, Atsc1UserIdentifier::Ga94);
match env.body {
Atsc1Body::AtscUserData(Atsc1UserData::CcData { cc_data_bytes }) => {
assert_eq!(cc_data_bytes, cc);
}
other => panic!("expected GA94 → CcData, got {other:?}"),
}
}
#[test]
fn atsc1_envelope_ga94_bar_data_letterbox() {
let body = [0x06, 0xCF, 0xC0, 0x40, 0xC3, 0xBF];
let raw = build_t35_atsc1(Atsc1UserIdentifier::GA94, &body);
let outer = parse_user_data_registered_itu_t_t35(&raw).unwrap();
let env = parse_atsc1_envelope(&outer.payload_bytes).unwrap();
match env.body {
Atsc1Body::AtscUserData(Atsc1UserData::BarData(bar)) => {
assert_eq!(bar.letterbox, Some((64, 959)));
assert_eq!(bar.pillarbox, None);
}
other => panic!("expected BarData letterbox, got {other:?}"),
}
}
#[test]
fn atsc1_envelope_ga94_bar_data_pillarbox() {
let body = [0x06, 0x3F, 0xC0, 0x64, 0xC7, 0x1C];
let raw = build_t35_atsc1(Atsc1UserIdentifier::GA94, &body);
let outer = parse_user_data_registered_itu_t_t35(&raw).unwrap();
let env = parse_atsc1_envelope(&outer.payload_bytes).unwrap();
match env.body {
Atsc1Body::AtscUserData(Atsc1UserData::BarData(bar)) => {
assert_eq!(bar.letterbox, None);
assert_eq!(bar.pillarbox, Some((100, 1820)));
}
other => panic!("expected BarData pillarbox, got {other:?}"),
}
}
#[test]
fn atsc1_envelope_bar_data_rejects_letterbox_and_pillarbox_simultaneously() {
let body = [0x06, 0xFF];
let raw = build_t35_atsc1(Atsc1UserIdentifier::GA94, &body);
let outer = parse_user_data_registered_itu_t_t35(&raw).unwrap();
let err = parse_atsc1_envelope(&outer.payload_bytes).unwrap_err();
assert_eq!(err, SeiError::Atsc1BarDataLetterboxPillarboxBoth);
}
#[test]
fn atsc1_envelope_bar_data_rejects_top_bottom_mismatch() {
let body = [0x06, 0x8F, 0xC0, 0x10];
let raw = build_t35_atsc1(Atsc1UserIdentifier::GA94, &body);
let outer = parse_user_data_registered_itu_t_t35(&raw).unwrap();
let err = parse_atsc1_envelope(&outer.payload_bytes).unwrap_err();
assert_eq!(
err,
SeiError::Atsc1BarDataTopBottomMismatch {
top: true,
bottom: false
}
);
}
#[test]
fn atsc1_envelope_bar_data_rejects_left_right_mismatch() {
let body = [0x06, 0x2F, 0xC0, 0x10];
let raw = build_t35_atsc1(Atsc1UserIdentifier::GA94, &body);
let outer = parse_user_data_registered_itu_t_t35(&raw).unwrap();
let err = parse_atsc1_envelope(&outer.payload_bytes).unwrap_err();
assert_eq!(
err,
SeiError::Atsc1BarDataLeftRightMismatch {
left: true,
right: false,
}
);
}
#[test]
fn atsc1_envelope_bar_data_rejects_wrong_reserved_nibble() {
let body = [0x06, 0x00];
let raw = build_t35_atsc1(Atsc1UserIdentifier::GA94, &body);
let outer = parse_user_data_registered_itu_t_t35(&raw).unwrap();
let err = parse_atsc1_envelope(&outer.payload_bytes).unwrap_err();
assert_eq!(err, SeiError::Atsc1BarDataReservedMismatch(0b0000));
}
#[test]
fn atsc1_envelope_bar_data_rejects_wrong_one_bits_prefix() {
let body = [0x06, 0xCF, 0x40, 0x00, 0xC0, 0x00];
let raw = build_t35_atsc1(Atsc1UserIdentifier::GA94, &body);
let outer = parse_user_data_registered_itu_t_t35(&raw).unwrap();
let err = parse_atsc1_envelope(&outer.payload_bytes).unwrap_err();
assert_eq!(
err,
SeiError::Atsc1BarDataOneBitsMismatch {
which: "top",
got: 0b01,
}
);
}
#[test]
fn atsc1_envelope_ga94_reserved_type_code_keeps_raw_bytes() {
let body = [0x01, 0xDE, 0xAD, 0xBE, 0xEF];
let raw = build_t35_atsc1(Atsc1UserIdentifier::GA94, &body);
let outer = parse_user_data_registered_itu_t_t35(&raw).unwrap();
let env = parse_atsc1_envelope(&outer.payload_bytes).unwrap();
match env.body {
Atsc1Body::AtscUserData(Atsc1UserData::Reserved { type_code, raw }) => {
assert_eq!(type_code, 0x01);
assert_eq!(raw, vec![0xDE, 0xAD, 0xBE, 0xEF]);
}
other => panic!("expected reserved type_code, got {other:?}"),
}
}
#[test]
fn atsc1_envelope_ga94_empty_atsc_user_data_rejected() {
let body: [u8; 0] = [];
let raw = build_t35_atsc1(Atsc1UserIdentifier::GA94, &body);
let outer = parse_user_data_registered_itu_t_t35(&raw).unwrap();
let err = parse_atsc1_envelope(&outer.payload_bytes).unwrap_err();
assert_eq!(err, SeiError::Atsc1AtscUserDataEmpty);
}
#[test]
fn atsc1_envelope_dtg1_afd_flag_off() {
let body = [0x01u8];
let raw = build_t35_atsc1(Atsc1UserIdentifier::DTG1, &body);
let outer = parse_user_data_registered_itu_t_t35(&raw).unwrap();
let env = parse_atsc1_envelope(&outer.payload_bytes).unwrap();
assert_eq!(env.user_identifier, Atsc1UserIdentifier::Dtg1);
match env.body {
Atsc1Body::Afd(afd) => {
assert!(!afd.active_format_flag);
assert_eq!(afd.active_format, None);
}
other => panic!("expected DTG1 → AFD, got {other:?}"),
}
}
#[test]
fn atsc1_envelope_dtg1_afd_flag_on_carries_active_format() {
let body = [0x41u8, 0xFA];
let raw = build_t35_atsc1(Atsc1UserIdentifier::DTG1, &body);
let outer = parse_user_data_registered_itu_t_t35(&raw).unwrap();
let env = parse_atsc1_envelope(&outer.payload_bytes).unwrap();
match env.body {
Atsc1Body::Afd(afd) => {
assert!(afd.active_format_flag);
assert_eq!(afd.active_format, Some(0b1010));
}
other => panic!("expected DTG1 → AFD with active_format, got {other:?}"),
}
}
#[test]
fn atsc1_envelope_rejects_short_payload() {
let err = parse_atsc1_envelope(&[0x00, 0x31, 0x47, 0x41, 0x39]).unwrap_err();
assert_eq!(err, SeiError::Atsc1EnvelopeTooShort(5));
}
#[test]
fn atsc1_envelope_rejects_wrong_provider_code() {
let mut raw = vec![0x00, 0x3C];
raw.extend_from_slice(&Atsc1UserIdentifier::GA94.to_be_bytes());
let err = parse_atsc1_envelope(&raw).unwrap_err();
assert_eq!(err, SeiError::Atsc1ProviderCodeMismatch(0x003C));
}
#[test]
fn atsc1_envelope_rejects_unknown_user_identifier() {
let mut raw = vec![0x00, 0x31];
raw.extend_from_slice(b"ZZZZ");
let err = parse_atsc1_envelope(&raw).unwrap_err();
assert_eq!(
err,
SeiError::Atsc1UnknownUserIdentifier(u32::from_be_bytes(*b"ZZZZ"))
);
}
#[test]
fn atsc1_envelope_chain_through_parse_payload_dispatch() {
let cc = [0xFC, 0xC4, 0x91];
let mut body = vec![0x03u8];
body.extend_from_slice(&cc);
let raw = build_t35_atsc1(Atsc1UserIdentifier::GA94, &body);
let ctx = SeiContext::default();
let outer = match parse_payload(4, &raw, &ctx).unwrap() {
SeiPayload::UserDataRegisteredItuTT35(o) => o,
other => panic!("expected UserDataRegisteredItuTT35, got {other:?}"),
};
let env = parse_atsc1_envelope(&outer.payload_bytes).unwrap();
assert!(matches!(
env.body,
Atsc1Body::AtscUserData(Atsc1UserData::CcData { .. })
));
}
}