Skip to content

Commit 1848fd2

Browse files
committed
Accept AVIF lsel property with layer_id 0xffff
Currently any lsel (LayerSelectorProperty) is treated as an unsupported essential property, so its item is forbidden and consumers reject the whole image. A layer_id of 0xffff, however, does not select a specific spatial layer. It only enables (without requiring) progressive rendering: a client may render progressively or just show the final image, which is also what ignoring the property does, so no client can get it wrong. Such a property therefore should not make the item unsupported. Parse the lsel layer_id (previously the box was skipped) and, when it is 0xffff, accept the item and don't report lsel as an unsupported feature. Other layer_id values remain unsupported as before, and the requirement that lsel be marked essential is still enforced for all layer_id values. Adds a test and fixture (tests/lsel-layer-id-ffff.avif, a green image carrying an essential lsel with layer_id 0xffff). See https://aomediacodec.github.io/av1-avif/#layer-selector-property
1 parent b693c7e commit 1848fd2

3 files changed

Lines changed: 74 additions & 20 deletions

File tree

mp4parse/src/lib.rs

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ impl TryFrom<&ItemProperty> for Feature {
324324
ItemProperty::Colour(_) => Self::Colr,
325325
ItemProperty::ImageSpatialExtents(_) => Self::Ispe,
326326
ItemProperty::LayeredImageIndexing => Self::A1lx,
327-
ItemProperty::LayerSelection => Self::Lsel,
327+
ItemProperty::LayerSelection(_) => Self::Lsel,
328328
ItemProperty::Mirroring(_) => Self::Imir,
329329
ItemProperty::OperatingPointSelector => Self::A1op,
330330
ItemProperty::PixelAspectRatio(_) => Self::Pasp,
@@ -3024,6 +3024,14 @@ fn read_iprp<T: Read>(
30243024
let mut association_entries = TryVec::<ItemPropertyAssociationEntry>::new();
30253025
let mut forbidden_items = TryVec::new();
30263026

3027+
// A LayerSelectorProperty with this layer_id enables, but does not require,
3028+
// progressive rendering: a client may render progressively or just show the
3029+
// final image (which is also what ignoring the property does), so no client
3030+
// can get this wrong. It therefore does not need to be treated as an
3031+
// unsupported essential property, and its item is accepted and processed.
3032+
// See <https://aomediacodec.github.io/av1-avif/#layer-selector-property>
3033+
const LSEL_LAYER_ID_NO_SELECTION: u16 = 0xffff;
3034+
30273035
while let Some(mut b) = iter.next_box()? {
30283036
if b.head.name != BoxType::ItemPropertyAssociationBox {
30293037
return Status::IprpBadChild.into();
@@ -3089,16 +3097,25 @@ fn read_iprp<T: Read>(
30893097
assert!(brand == MIF1_BRAND);
30903098

30913099
let feature = Feature::try_from(property);
3092-
let property_supported = match feature {
3093-
Ok(feature) => {
3094-
if feature.supported() {
3095-
true
3096-
} else {
3097-
unsupported_features.insert(feature);
3098-
false
3100+
let property_supported = if matches!(
3101+
property,
3102+
ItemProperty::LayerSelection(layer_id)
3103+
if *layer_id == LSEL_LAYER_ID_NO_SELECTION
3104+
) {
3105+
// Not an unsupported feature; see LSEL_LAYER_ID_NO_SELECTION.
3106+
true
3107+
} else {
3108+
match feature {
3109+
Ok(feature) => {
3110+
if feature.supported() {
3111+
true
3112+
} else {
3113+
unsupported_features.insert(feature);
3114+
false
3115+
}
30993116
}
3117+
Err(_) => false,
31003118
}
3101-
Err(_) => false,
31023119
};
31033120

31043121
if !property_supported {
@@ -3169,18 +3186,22 @@ fn read_iprp<T: Read>(
31693186
}
31703187
}
31713188

3172-
ItemProperty::LayerSelection => {
3173-
assert!(feature.is_ok() && unsupported_features.contains(feature?));
3174-
if a.essential {
3175-
assert!(
3176-
forbidden_items.contains(&association_entry.item_id)
3177-
|| strictness == ParseStrictness::Permissive
3178-
);
3179-
} else {
3189+
ItemProperty::LayerSelection(layer_id) => {
3190+
if !a.essential {
3191+
// lsel shall be marked as essential regardless of its
3192+
// layer_id.
31803193
fail_with_status_if(
31813194
strictness != ParseStrictness::Permissive,
31823195
Status::LselNoEssential,
31833196
)?;
3197+
} else if *layer_id != LSEL_LAYER_ID_NO_SELECTION {
3198+
// A specific layer was requested; selecting a layer is
3199+
// unsupported, so the item shall not be processed.
3200+
assert!(feature.is_ok() && unsupported_features.contains(feature?));
3201+
assert!(
3202+
forbidden_items.contains(&association_entry.item_id)
3203+
|| strictness == ParseStrictness::Permissive
3204+
);
31843205
}
31853206
}
31863207

@@ -3266,7 +3287,7 @@ pub enum ItemProperty {
32663287
Colour(ColourInformation),
32673288
ImageSpatialExtents(ImageSpatialExtentsProperty),
32683289
LayeredImageIndexing,
3269-
LayerSelection,
3290+
LayerSelection(u16),
32703291
Mirroring(ImageMirror),
32713292
OperatingPointSelector,
32723293
PixelAspectRatio(PixelAspectRatio),
@@ -3283,7 +3304,7 @@ impl From<&ItemProperty> for BoxType {
32833304
ItemProperty::CleanAperture => BoxType::CleanApertureBox,
32843305
ItemProperty::Colour(_) => BoxType::ColourInformationBox,
32853306
ItemProperty::LayeredImageIndexing => BoxType::AV1LayeredImageIndexingProperty,
3286-
ItemProperty::LayerSelection => BoxType::LayerSelectorProperty,
3307+
ItemProperty::LayerSelection(_) => BoxType::LayerSelectorProperty,
32873308
ItemProperty::Mirroring(_) => BoxType::ImageMirror,
32883309
ItemProperty::OperatingPointSelector => BoxType::OperatingPointSelectorProperty,
32893310
ItemProperty::PixelAspectRatio(_) => BoxType::PixelAspectRatioBox,
@@ -3653,6 +3674,7 @@ fn read_ipco<T: Read>(
36533674
}
36543675
BoxType::PixelAspectRatioBox => ItemProperty::PixelAspectRatio(read_pasp(&mut b)?),
36553676
BoxType::PixelInformationBox => ItemProperty::Channels(read_pixi(&mut b)?),
3677+
BoxType::LayerSelectorProperty => ItemProperty::LayerSelection(read_lsel(&mut b)?),
36563678

36573679
other_box_type => {
36583680
// Even if we didn't do anything with other property types, we still store
@@ -3661,7 +3683,6 @@ fn read_ipco<T: Read>(
36613683
let item_property = match other_box_type {
36623684
BoxType::AV1LayeredImageIndexingProperty => ItemProperty::LayeredImageIndexing,
36633685
BoxType::CleanApertureBox => ItemProperty::CleanAperture,
3664-
BoxType::LayerSelectorProperty => ItemProperty::LayerSelection,
36653686
BoxType::OperatingPointSelectorProperty => ItemProperty::OperatingPointSelector,
36663687
_ => {
36673688
warn!("No ItemProperty variant for {other_box_type:?}");
@@ -3687,6 +3708,14 @@ fn read_ipco<T: Read>(
36873708
Ok(properties)
36883709
}
36893710

3711+
/// Parse a LayerSelectorProperty, returning its layer_id.
3712+
///
3713+
/// See <https://aomediacodec.github.io/av1-avif/#layer-selector-property>
3714+
fn read_lsel<T: Read>(src: &mut BMFFBox<T>) -> Result<u16> {
3715+
let layer_id = be_u16(src)?;
3716+
Ok(layer_id)
3717+
}
3718+
36903719
#[repr(C)]
36913720
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36923721
pub struct ImageSpatialExtentsProperty {
305 Bytes
Binary file not shown.

mp4parse/tests/public.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ static AVIF_TEST_DIRS: &[&str] = &["tests", "av1-avif/testFiles", "link-u-avif-s
8282
static AVIF_A1OP: &str = "tests/a1op.avif";
8383
static AVIF_A1LX: &str = "tests/a1lx.avif";
8484
static AVIF_LSEL: &str = "tests/lsel.avif";
85+
// This is tests/valid.avif with an essential lsel property whose layer_id is
86+
// 0xffff added to the primary item. That layer_id selects no particular layer,
87+
// so the item is still accepted rather than treated as having an unsupported
88+
// essential property.
89+
static AVIF_LSEL_LAYER_ID_FFFF: &str = "tests/lsel-layer-id-ffff.avif";
8590

8691
static AVIF_CLAP: &str = "tests/clap-basic-1_3x3-to-1x1.avif";
8792
static AVIF_GRID: &str = "av1-avif/testFiles/Microsoft/Summer_in_Tomsk_720p_5x4_grid.avif";
@@ -1285,6 +1290,26 @@ fn public_avif_lsel_missing_essential() {
12851290
assert_avif_shall(IMAGE_AVIF_LSEL_MISSING_ESSENTIAL, Status::LselNoEssential);
12861291
}
12871292

1293+
// An lsel property with layer_id 0xffff does not select a specific layer, so the
1294+
// item is still accepted (decodable, and lsel not reported as an unsupported
1295+
// feature) regardless of strictness.
1296+
#[test]
1297+
fn public_avif_lsel_no_layer_selection() {
1298+
for_strictness_result(AVIF_LSEL_LAYER_ID_FFFF, |_strictness, result| match result {
1299+
Ok(context) => {
1300+
assert!(
1301+
!context.unsupported_features.contains(mp4::Feature::Lsel),
1302+
"lsel with layer_id 0xffff should not be an unsupported feature"
1303+
);
1304+
assert!(
1305+
context.primary_item_coded_data().is_some(),
1306+
"primary item associated with an lsel of layer_id 0xffff should be decodable"
1307+
);
1308+
}
1309+
r => panic!("Expected Ok, found {:?}", r),
1310+
});
1311+
}
1312+
12881313
#[test]
12891314
fn public_avif_clap() {
12901315
assert_unsupported_essential(AVIF_CLAP, mp4::Feature::Clap);

0 commit comments

Comments
 (0)