Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

LayerSelector support #374

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
82 changes: 57 additions & 25 deletions mp4parse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ pub enum Status {
IrefRecursion,
IspeMissing,
ItemTypeMissing,
LselBadLayerId,
LselNoEssential,
MdhdBadTimescale,
MdhdBadVersion,
Expand Down Expand Up @@ -304,14 +305,9 @@ impl Feature {
| Self::Irot
| Self::Ispe
| Self::Pasp
| Self::Pixi => true,
Self::A1lx
| Self::A1op
| Self::Clap
| Self::Grid
| Self::Ipro
| Self::Lsel
| Self::Avis => false,
| Self::Pixi
| Self::Lsel => true,
Self::A1lx | Self::A1op | Self::Clap | Self::Grid | Self::Ipro | Self::Avis => false,
}
}
}
Expand All @@ -328,7 +324,7 @@ impl TryFrom<&ItemProperty> for Feature {
ItemProperty::Colour(_) => Self::Colr,
ItemProperty::ImageSpatialExtents(_) => Self::Ispe,
ItemProperty::LayeredImageIndexing => Self::A1lx,
ItemProperty::LayerSelection => Self::Lsel,
ItemProperty::LayerSelection(_) => Self::Lsel,
ItemProperty::Mirroring(_) => Self::Imir,
ItemProperty::OperatingPointSelector => Self::A1op,
ItemProperty::PixelAspectRatio(_) => Self::Pasp,
Expand Down Expand Up @@ -693,6 +689,11 @@ impl From<Status> for &str {
"LayerSelectorProperty (lsel) shall be marked as essential \
per HEIF (ISO/IEC 23008-12:2017) § 6.5.11.1"
}
Status::LselBadLayerId => {
"LayerSelectorProperty (lsel) shall be between 0 and 3, \
or the special value 0xFFFF \
per https://aomediacodec.github.io/av1-avif/#layer-selector-property"
}
Status::MdhdBadTimescale => {
"zero timescale in mdhd"
}
Expand Down Expand Up @@ -1728,6 +1729,21 @@ impl AvifContext {
}
}

pub fn layer_selector_ptr(&self) -> Result<*const LayerSelector> {
if let Some(primary_item) = &self.primary_item {
match self
.item_properties
.get(primary_item.id, BoxType::LayerSelectorProperty)?
{
Some(ItemProperty::LayerSelection(lsel)) => Ok(lsel),
Some(other_property) => panic!("property key mismatch: {:?}", other_property),
None => Ok(std::ptr::null()),
}
} else {
Ok(std::ptr::null())
}
}

/// A helper for the various `AvifItem`s to expose a reference to the
/// underlying data while avoiding copies.
fn item_as_slice<'a>(&'a self, item: &'a AvifItem) -> &'a [u8] {
Expand Down Expand Up @@ -3193,29 +3209,31 @@ fn read_iprp<T: Read>(
}
}

// The following properties are unsupported, but we still enforce that
// they've been correctly marked as essential or not.
ItemProperty::LayeredImageIndexing => {
assert!(feature.is_ok() && unsupported_features.contains(feature?));
if a.essential {
ItemProperty::LayerSelection(lsel) => {
if !a.essential {
fail_with_status_if(
strictness != ParseStrictness::Permissive,
Status::A1lxEssential,
Status::LselNoEssential,
)?;
}

match lsel.layer_id {
0..=3 | 0xffff => (),
_ => fail_with_status_if(
strictness != ParseStrictness::Permissive,
Status::LselBadLayerId,
)?,
}
}

ItemProperty::LayerSelection => {
// The following properties are unsupported, but we still enforce that
// they've been correctly marked as essential or not.
ItemProperty::LayeredImageIndexing => {
assert!(feature.is_ok() && unsupported_features.contains(feature?));
if a.essential {
assert!(
forbidden_items.contains(&association_entry.item_id)
|| strictness == ParseStrictness::Permissive
);
} else {
fail_with_status_if(
strictness != ParseStrictness::Permissive,
Status::LselNoEssential,
Status::A1lxEssential,
)?;
}
}
Expand Down Expand Up @@ -3302,7 +3320,7 @@ pub enum ItemProperty {
Colour(ColourInformation),
ImageSpatialExtents(ImageSpatialExtentsProperty),
LayeredImageIndexing,
LayerSelection,
LayerSelection(LayerSelector),
Mirroring(ImageMirror),
OperatingPointSelector,
PixelAspectRatio(PixelAspectRatio),
Expand All @@ -3319,7 +3337,7 @@ impl From<&ItemProperty> for BoxType {
ItemProperty::CleanAperture => BoxType::CleanApertureBox,
ItemProperty::Colour(_) => BoxType::ColourInformationBox,
ItemProperty::LayeredImageIndexing => BoxType::AV1LayeredImageIndexingProperty,
ItemProperty::LayerSelection => BoxType::LayerSelectorProperty,
ItemProperty::LayerSelection(_) => BoxType::LayerSelectorProperty,
ItemProperty::Mirroring(_) => BoxType::ImageMirror,
ItemProperty::OperatingPointSelector => BoxType::OperatingPointSelectorProperty,
ItemProperty::PixelAspectRatio(_) => BoxType::PixelAspectRatioBox,
Expand Down Expand Up @@ -3681,6 +3699,7 @@ fn read_ipco<T: Read>(
}
BoxType::PixelAspectRatioBox => ItemProperty::PixelAspectRatio(read_pasp(&mut b)?),
BoxType::PixelInformationBox => ItemProperty::Channels(read_pixi(&mut b)?),
BoxType::LayerSelectorProperty => ItemProperty::LayerSelection(read_lsel(&mut b)?),

other_box_type => {
// Even if we didn't do anything with other property types, we still store
Expand All @@ -3689,7 +3708,6 @@ fn read_ipco<T: Read>(
let item_property = match other_box_type {
BoxType::AV1LayeredImageIndexingProperty => ItemProperty::LayeredImageIndexing,
BoxType::CleanApertureBox => ItemProperty::CleanAperture,
BoxType::LayerSelectorProperty => ItemProperty::LayerSelection,
BoxType::OperatingPointSelectorProperty => ItemProperty::OperatingPointSelector,
_ => {
warn!("No ItemProperty variant for {:?}", other_box_type);
Expand Down Expand Up @@ -3785,6 +3803,20 @@ fn read_pixi<T: Read>(src: &mut BMFFBox<T>) -> Result<PixelInformation> {
Ok(PixelInformation { bits_per_channel })
}

#[repr(C)]
#[derive(Debug)]
pub struct LayerSelector {
pub layer_id: u16,
}

/// Parse layer selection
/// See HEIF (ISO 23008-12:2017) § 6.5.11
fn read_lsel<T: Read>(src: &mut BMFFBox<T>) -> Result<LayerSelector> {
let layer_id = be_u16(src)?;

Ok(LayerSelector { layer_id })
}

/// Despite [Rec. ITU-T H.273] (12/2016) defining the CICP fields as having a
/// range of 0-255, and only a small fraction of those values being used,
/// ISOBMFF (ISO 14496-12:2020) § 12.1.5 defines them as 16-bit values in the
Expand Down
Binary file added mp4parse/tests/a1lx-new-lsel.avif
Binary file not shown.
Binary file added mp4parse/tests/corrupt/lsel-bad-layer-id.avif
Binary file not shown.
33 changes: 29 additions & 4 deletions mp4parse/tests/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,15 @@ static AVIF_TEST_DIRS: &[&str] = &["tests", "av1-avif/testFiles", "link-u-avif-s
// av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1op.avif
// av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1lx.avif
// av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_lsel.avif
// respectively, but with https://github.com/AOMediaCodec/av1-avif/issues/174 fixed
// respectively, before https://github.com/AOMediaCodec/av1-avif/pull/191
// and with https://github.com/AOMediaCodec/av1-avif/issues/174 fixed
static AVIF_A1OP: &str = "tests/a1op.avif";
static AVIF_A1LX: &str = "tests/a1lx.avif";
static AVIF_LSEL: &str = "tests/lsel.avif";

static AVIF_A1LX_NEW_LSEL: &str = "tests/a1lx-new-lsel.avif";
static AVIF_LSEL_BAD_LAYER_ID: &str = "tests/corrupt/lsel-bad-layer-id.avif";

static AVIF_CLAP: &str = "tests/clap-basic-1_3x3-to-1x1.avif";
static AVIF_GRID: &str = "av1-avif/testFiles/Microsoft/Summer_in_Tomsk_720p_5x4_grid.avif";
static AVIF_GRID_A1LX: &str =
Expand All @@ -89,18 +93,17 @@ static AVIF_AVIS_MAJOR_NO_MOOV: &str = "tests/corrupt/alpha_video_moov_is_moop.a
static AVIF_NO_PIXI_IMAGES: &[&str] = &[IMAGE_AVIF_NO_PIXI, IMAGE_AVIF_NO_ALPHA_PIXI];
static AVIF_UNSUPPORTED_IMAGES: &[&str] = &[
AVIF_A1LX,
AVIF_A1LX_NEW_LSEL,
AVIF_A1OP,
AVIF_CLAP,
IMAGE_AVIF_CLAP_MISSING_ESSENTIAL,
AVIF_GRID,
AVIF_GRID_A1LX,
AVIF_LSEL,
AVIF_AVIS_MAJOR_NO_PITM,
AVIF_AVIS_MAJOR_WITH_PITM_AND_ALPHA,
"av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1lx.avif",
"av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1op.avif",
"av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1op_lsel.avif",
"av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_lsel.avif",
"av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_grid_lsel.avif",
"av1-avif/testFiles/Link-U/kimono.crop.avif",
"av1-avif/testFiles/Link-U/kimono.mirror-vertical.rotate270.crop.avif",
Expand Down Expand Up @@ -1171,7 +1174,29 @@ fn public_avif_a1op_missing_essential() {

#[test]
fn public_avif_lsel() {
assert_unsupported_essential(AVIF_LSEL, mp4::Feature::Lsel);
let input = &mut File::open(AVIF_LSEL).expect("Unknown file");
let context = mp4::read_avif(input, ParseStrictness::Normal).expect("read_avif failed");
let ptr = context.layer_selector_ptr().unwrap();
assert_ne!(ptr, std::ptr::null());
unsafe {
assert_eq!(1, ptr.read().layer_id);
}
}

#[test]
fn public_avif_a1lx_new_lsel() {
let input = &mut File::open(AVIF_A1LX_NEW_LSEL).expect("Unknown file");
let context = mp4::read_avif(input, ParseStrictness::Normal).expect("read_avif failed");
let ptr = context.layer_selector_ptr().unwrap();
assert_ne!(ptr, std::ptr::null());
unsafe {
assert_eq!(0xffff, ptr.read().layer_id);
}
}

#[test]
fn public_avif_lsel_bad_layer_id() {
assert_avif_shall(AVIF_LSEL_BAD_LAYER_ID, Status::LselBadLayerId);
}

#[test]
Expand Down
2 changes: 2 additions & 0 deletions mp4parse_capi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ pub struct Mp4parseAvifImage {
pub image_rotation: mp4parse::ImageRotation,
pub image_mirror: *const mp4parse::ImageMirror,
pub pixel_aspect_ratio: *const mp4parse::PixelAspectRatio,
pub layer_selector: *const mp4parse::LayerSelector,
/// If no alpha item exists, members' `.length` will be 0 and `.data` will be null
pub alpha_image: Mp4parseAvifImageItem,
pub premultiplied_alpha: bool,
Expand Down Expand Up @@ -1079,6 +1080,7 @@ pub fn mp4parse_avif_get_image_safe(
image_rotation: context.image_rotation()?,
image_mirror: context.image_mirror_ptr()?,
pixel_aspect_ratio: context.pixel_aspect_ratio_ptr()?,
layer_selector: context.layer_selector_ptr()?,
alpha_image,
premultiplied_alpha: context.premultiplied_alpha,
major_brand: context.major_brand.value,
Expand Down