Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mp4parse/src/boxes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ box_database!(
ItemPropertyContainerBox 0x6970_636f, // "ipco"
ItemPropertyAssociationBox 0x6970_6d61, // "ipma"
ColourInformationBox 0x636f_6c72, // "colr"
MasteringDisplayColourVolumeBox 0x6d64_6376, // "mdcv"
ContentLightLevelBox 0x636c_6c69, // "clli"
ImageSpatialExtentsProperty 0x6973_7065, // "ispe"
PixelAspectRatioBox 0x7061_7370, // "pasp"
PixelInformationBox 0x7069_7869, // "pixi"
Expand Down
83 changes: 77 additions & 6 deletions mp4parse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,30 @@ pub enum VideoCodecSpecific {
HEVCConfig(TryVec<u8>),
}

/// Mastering display colour volume from an `mdcv` box (ISO 14496-12).
/// Primary indices are R\[0\], G\[1\], B\[2\]. Divide chromaticity values by 50000
/// and luminance values by 10000 to obtain physical units.
#[derive(Debug, Clone)]
pub struct MasteringDisplayColourVolume {
pub display_primaries_x: [u16; 3],
pub display_primaries_y: [u16; 3],
pub white_point_x: u16,
pub white_point_y: u16,
/// In units of 0.0001 cd/m²
pub max_display_mastering_luminance: u32,
/// In units of 0.0001 cd/m²
pub min_display_mastering_luminance: u32,
}

/// Content light level from a `clli` box (ISO 14496-12).
#[derive(Debug, Clone)]
pub struct ContentLightLevel {
/// Maximum content light level in cd/m²
pub max_content_light_level: u16,
/// Maximum picture average light level in cd/m²
pub max_pic_average_light_level: u16,
}

#[derive(Debug)]
pub struct VideoSampleEntry {
pub codec_type: CodecType,
Expand All @@ -1183,6 +1207,10 @@ pub struct VideoSampleEntry {
/// Only `ColourInformation::Nclx` is currently surfaced through the C API;
/// `ColourInformation::Icc` is stored but not exposed to C consumers.
pub colour_info: Option<ColourInformation>,
/// Mastering display colour volume from the `mdcv` box (ISO 14496-12).
pub hdr_mastering_display: Option<MasteringDisplayColourVolume>,
/// Content light level from the `clli` box (ISO 14496-12).
pub hdr_content_light_level: Option<ContentLightLevel>,
}

/// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9). The meaning of each
Expand Down Expand Up @@ -3701,6 +3729,36 @@ fn read_pasp<T: Read>(src: &mut BMFFBox<T>) -> Result<PixelAspectRatio> {
})
}

/// Parse mastering display colour volume box (ISO 14496-12).
fn read_mdcv<T: Read>(src: &mut BMFFBox<T>) -> Result<MasteringDisplayColourVolume> {
// Wire order is G, B, R (per ISO 14496-12); remap to R[0], G[1], B[2].
let (gx, gy) = (be_u16(src)?, be_u16(src)?);
let (bx, by) = (be_u16(src)?, be_u16(src)?);
let (rx, ry) = (be_u16(src)?, be_u16(src)?);
let display_primaries_x = [rx, gx, bx];
let display_primaries_y = [ry, gy, by];
let white_point_x = be_u16(src)?;
let white_point_y = be_u16(src)?;
let max_display_mastering_luminance = be_u32(src)?;
let min_display_mastering_luminance = be_u32(src)?;
Ok(MasteringDisplayColourVolume {
display_primaries_x,
display_primaries_y,
white_point_x,
white_point_y,
max_display_mastering_luminance,
min_display_mastering_luminance,
})
}

/// Parse content light level box (ISO 14496-12).
fn read_clli<T: Read>(src: &mut BMFFBox<T>) -> Result<ContentLightLevel> {
Ok(ContentLightLevel {
max_content_light_level: be_u16(src)?,
max_pic_average_light_level: be_u16(src)?,
})
}

#[derive(Debug)]
pub struct PixelInformation {
bits_per_channel: TryVec<u8>,
Expand Down Expand Up @@ -5594,6 +5652,8 @@ fn read_video_sample_entry<T: Read>(
let mut codec_specific = None;
let mut pixel_aspect_ratio = None;
let mut colour_info = None;
let mut hdr_mastering_display = None;
let mut hdr_content_light_level = None;
let mut protection_info = TryVec::new();
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
Expand Down Expand Up @@ -5718,14 +5778,23 @@ fn read_video_sample_entry<T: Read>(
Status::ColrBadQuantityBMFF,
)?;
skip_box_content(&mut b)?;
} else {
if let ParsedColourInformation::Supported(colr) = read_colr(&mut b, strictness)?
{
debug!("Parsed colr box: {colr:?}");
colour_info = Some(colr);
}
} else if let ParsedColourInformation::Supported(colr) =
read_colr(&mut b, strictness)?
{
debug!("Parsed colr box: {colr:?}");
colour_info = Some(colr);
}
}
BoxType::MasteringDisplayColourVolumeBox => {
let mdcv = read_mdcv(&mut b)?;
debug!("Parsed mdcv box: {mdcv:?}");
hdr_mastering_display = Some(mdcv);
}
BoxType::ContentLightLevelBox => {
let clli = read_clli(&mut b)?;
debug!("Parsed clli box: {clli:?}");
hdr_content_light_level = Some(clli);
}
_ => {
debug!("Unsupported video codec, box {:?} found", b.head.name);
skip_box_content(&mut b)?;
Expand All @@ -5745,6 +5814,8 @@ fn read_video_sample_entry<T: Read>(
protection_info,
pixel_aspect_ratio,
colour_info,
hdr_mastering_display,
hdr_content_light_level,
})
}),
)
Expand Down
45 changes: 45 additions & 0 deletions mp4parse/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1389,3 +1389,48 @@ fn read_to_end_oom() {
let mut src = b"1234567890".take(isize::MAX.try_into().expect("isize < u64"));
assert!(src.read_into_try_vec().is_err());
}

#[test]
fn read_mdcv() {
// Synthetic mdcv box. Wire order is G, B, R (x then y for each primary),
// remapped by read_mdcv to struct indices R[0], G[1], B[2].
let mut stream = make_box(BoxSize::Auto, b"mdcv", |s| {
s // Gx, Gy, Bx, By, Rx, Ry (wire order)
.B16(14600) // Gx
.B16(59210) // Gy
.B16(7500) // Bx
.B16(3000) // By
.B16(35400) // Rx
.B16(14600) // Ry
// white_point_x, white_point_y
.B16(15635)
.B16(16450)
// max_display_mastering_luminance, min_display_mastering_luminance
.B32(10000000)
.B32(50)
});
let mut iter = super::BoxIter::new(&mut stream);
let mut stream = iter.next_box().unwrap().unwrap();
assert_eq!(
stream.head.name,
super::BoxType::MasteringDisplayColourVolumeBox
);
let mdcv = super::read_mdcv(&mut stream).unwrap();
assert_eq!(mdcv.display_primaries_x, [35400, 14600, 7500]);
assert_eq!(mdcv.display_primaries_y, [14600, 59210, 3000]);
assert_eq!(mdcv.white_point_x, 15635);
assert_eq!(mdcv.white_point_y, 16450);
assert_eq!(mdcv.max_display_mastering_luminance, 10000000);
assert_eq!(mdcv.min_display_mastering_luminance, 50);
}

#[test]
fn read_clli() {
let mut stream = make_box(BoxSize::Auto, b"clli", |s| s.B16(1000).B16(400));
let mut iter = super::BoxIter::new(&mut stream);
let mut stream = iter.next_box().unwrap().unwrap();
assert_eq!(stream.head.name, super::BoxType::ContentLightLevelBox);
let clli = super::read_clli(&mut stream).unwrap();
assert_eq!(clli.max_content_light_level, 1000);
assert_eq!(clli.max_pic_average_light_level, 400);
}
10 changes: 2 additions & 8 deletions mp4parse/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,8 @@ pub fn create_sample_table(

let start_decode = decode_time;

let start_composition_val: i64 = match start_composition {
Some(sc) => sc.0,
None => return None,
};
let end_composition_val: i64 = match end_composition {
Some(ec) => ec.0,
None => return None,
};
let start_composition_val: i64 = start_composition?.0;
let end_composition_val: i64 = end_composition?.0;

let track_offset: i64 = track_offset_time.0;

Expand Down
50 changes: 50 additions & 0 deletions mp4parse_capi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,32 @@ impl Default for Mp4parseTrackAudioInfo {
}
}

/// Mastering display colour volume from an `mdcv` box (ISO 14496-12).
/// Primary indices are R\[0\], G\[1\], B\[2\]. Divide chromaticity values by 50000.0
/// and luminance values by 10000.0 to obtain physical units (chromaticity, cd/m²).
#[repr(C)]
#[derive(Default, Debug)]
pub struct Mp4parseMasteringDisplayColourVolume {
pub display_primaries_x: [u16; 3],
pub display_primaries_y: [u16; 3],
pub white_point_x: u16,
pub white_point_y: u16,
/// In units of 0.0001 cd/m²
pub max_display_mastering_luminance: u32,
/// In units of 0.0001 cd/m²
pub min_display_mastering_luminance: u32,
}

/// Content light level from a `clli` box (ISO 14496-12).
#[repr(C)]
#[derive(Default, Debug)]
pub struct Mp4parseContentLightLevel {
/// Maximum content light level in cd/m²
pub max_content_light_level: u16,
/// Maximum picture average light level in cd/m²
pub max_pic_average_light_level: u16,
}

#[repr(C)]
#[derive(Default, Debug)]
pub struct Mp4parseTrackVideoSampleInfo {
Expand All @@ -276,6 +302,12 @@ pub struct Mp4parseTrackVideoSampleInfo {
pub matrix_coefficients: u8,
/// Full range flag from the colr nclx box. Valid only when `has_colour_info`.
pub full_range_flag: bool,
/// True when an `mdcv` box was present. When false, `mastering_display` must not be read.
pub has_mastering_display: bool,
pub mastering_display: Mp4parseMasteringDisplayColourVolume,
/// True when a `clli` box was present. When false, `content_light_level` must not be read.
pub has_content_light_level: bool,
pub content_light_level: Mp4parseContentLightLevel,
}

#[repr(C)]
Expand Down Expand Up @@ -1139,6 +1171,24 @@ fn mp4parse_get_track_video_info_safe(
sample_info.matrix_coefficients = nclx.matrix_coefficients;
sample_info.full_range_flag = nclx.full_range_flag;
}
if let Some(ref mdcv) = video.hdr_mastering_display {
sample_info.has_mastering_display = true;
sample_info.mastering_display = Mp4parseMasteringDisplayColourVolume {
display_primaries_x: mdcv.display_primaries_x,
display_primaries_y: mdcv.display_primaries_y,
white_point_x: mdcv.white_point_x,
white_point_y: mdcv.white_point_y,
max_display_mastering_luminance: mdcv.max_display_mastering_luminance,
min_display_mastering_luminance: mdcv.min_display_mastering_luminance,
};
}
if let Some(ref clli) = video.hdr_content_light_level {
sample_info.has_content_light_level = true;
sample_info.content_light_level = Mp4parseContentLightLevel {
max_content_light_level: clli.max_content_light_level,
max_pic_average_light_level: clli.max_pic_average_light_level,
};
}

video_sample_infos.push(sample_info)?;
}
Expand Down
Loading