Browse Source

Validate AV1 still-picture framing

pull/2633/head
James Jackson-South 2 months ago
parent
commit
07cc0291e3
  1. 21
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
  2. 53
      tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs

21
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs

@ -385,6 +385,13 @@ internal class ObuReader
sequenceHeader.IsStillPicture = reader.ReadBoolean(); sequenceHeader.IsStillPicture = reader.ReadBoolean();
sequenceHeader.IsReducedStillPictureHeader = reader.ReadBoolean(); sequenceHeader.IsReducedStillPictureHeader = reader.ReadBoolean();
if (!sequenceHeader.IsStillPicture && sequenceHeader.IsReducedStillPictureHeader)
{
// The reduced header omits state required by a multi-frame sequence, so AV1 permits it only when the
// sequence is explicitly declared to contain a single still picture.
throw new InvalidImageContentException("An AV1 reduced still-picture header requires the still-picture flag.");
}
if (sequenceHeader.IsReducedStillPictureHeader) if (sequenceHeader.IsReducedStillPictureHeader)
{ {
sequenceHeader.TimingInfo = null; sequenceHeader.TimingInfo = null;
@ -1005,6 +1012,11 @@ internal class ObuReader
frameHeader.ShowExistingFrame = reader.ReadBoolean(); frameHeader.ShowExistingFrame = reader.ReadBoolean();
if (frameHeader.ShowExistingFrame) if (frameHeader.ShowExistingFrame)
{ {
if (sequenceHeader.IsStillPicture)
{
throw new InvalidImageContentException("An AV1 still picture cannot display a previously decoded frame.");
}
frameHeader.FrameToShowMapIdx = reader.ReadLiteral(3); frameHeader.FrameToShowMapIdx = reader.ReadLiteral(3);
if (sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false) if (sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false)
@ -1018,12 +1030,17 @@ internal class ObuReader
frameHeader.DisplayFrameId = reader.ReadLiteral(idLength); frameHeader.DisplayFrameId = reader.ReadLiteral(idLength);
} }
// TODO: This is incomplete here, not sure how we can display an already decoded frame here or if this is really relevent for still pictures. // Showing an existing frame requires sequence reference storage. The image-item decoder deliberately
throw new NotImplementedException("ShowExistingFrame is not yet implemented"); // owns one independently coded picture and therefore rejects this sequence-only operation at its boundary.
throw new InvalidImageContentException("An AV1 image item cannot display a previously decoded frame.");
} }
frameHeader.FrameType = (ObuFrameType)reader.ReadLiteral(2); frameHeader.FrameType = (ObuFrameType)reader.ReadLiteral(2);
frameHeader.ShowFrame = reader.ReadBoolean(); frameHeader.ShowFrame = reader.ReadBoolean();
if (sequenceHeader.IsStillPicture && (frameHeader.FrameType != ObuFrameType.KeyFrame || !frameHeader.ShowFrame))
{
throw new InvalidImageContentException("An AV1 still picture must be encoded as a shown key frame.");
}
if (frameHeader.ShowFrame && sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false) if (frameHeader.ShowFrame && sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false)
{ {

53
tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs

@ -167,6 +167,59 @@ public class ObuFrameHeaderTests
Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(expected), ObuPrettyPrint.PrettyPrintProperties(obuReader.SequenceHeader)); Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(expected), ObuPrettyPrint.PrettyPrintProperties(obuReader.SequenceHeader));
} }
/// <summary>
/// Verifies that the reduced sequence syntax cannot be used without declaring a still picture.
/// </summary>
[Fact]
public void ReadReducedHeaderWithoutStillPictureThrowsInvalidImageContent()
{
const int sequenceHeaderPayloadOffset = 2;
byte[] bitStream = [.. DefaultSequenceHeaderBitStream];
// The first payload byte stores the three profile bits followed by still_picture and
// reduced_still_picture_header. Clear only still_picture so the remaining syntax stays reduced.
bitStream[sequenceHeaderPayloadOffset] &= 0b1110_1111;
Assert.Throws<InvalidImageContentException>(() => ReadObuStream(bitStream));
}
/// <summary>
/// Verifies that a declared still-picture sequence rejects frame prefixes which do not describe a shown key frame.
/// </summary>
/// <param name="invalidFramePrefix">The high nibble containing show-existing, frame-type, and show-frame syntax.</param>
[Theory]
[InlineData(0b1000_0000)] // show_existing_frame = 1
[InlineData(0b0101_0000)] // frame_type = INTRA_ONLY_FRAME, show_frame = 1
[InlineData(0b0000_0000)] // frame_type = KEY_FRAME, show_frame = 0
public void ReadInvalidStillPictureFramePrefixThrowsInvalidImageContent(int invalidFramePrefix)
{
ObuSequenceHeader sequenceHeader = GetDefaultSequenceHeader();
sequenceHeader.IsReducedStillPictureHeader = false;
ObuFrameHeader frameHeader = GetKeyFrameHeader();
Av1TileDecoderStub tileStub = new();
byte[] emptyTile = [];
tileStub.ReadTile(emptyTile, 0);
using MemoryStream stream = new();
ObuWriter writer = new();
writer.WriteAll(Configuration.Default, stream, sequenceHeader, frameHeader, tileStub);
byte[] bitStream = stream.ToArray();
int sequenceObuOffset = DefaultTemporalDelimiterBitStream.Length;
Av1BitStreamReader sequenceSizeReader = new(bitStream.AsSpan(sequenceObuOffset + 1));
ulong sequencePayloadLength = sequenceSizeReader.ReadLittleEndianBytes128(out int sequenceSizeLength);
int frameObuOffset = sequenceObuOffset + 1 + sequenceSizeLength + (int)sequencePayloadLength;
Av1BitStreamReader frameSizeReader = new(bitStream.AsSpan(frameObuOffset + 1));
_ = frameSizeReader.ReadLittleEndianBytes128(out int frameSizeLength);
int framePayloadOffset = frameObuOffset + 1 + frameSizeLength;
// Preserve syntax after the frame prefix so each malformed stream differs from a valid writer result only in
// show_existing_frame or the still-picture frame-type/show-frame conformance condition under test.
bitStream[framePayloadOffset] = (byte)((bitStream[framePayloadOffset] & 0x0F) | invalidFramePrefix);
Assert.Throws<InvalidImageContentException>(() => ReadObuStream(bitStream));
}
/// <summary> /// <summary>
/// Verifies that ignored OBU payloads are bounded and skipped before parsing the following sequence header. /// Verifies that ignored OBU payloads are bounded and skipped before parsing the following sequence header.
/// </summary> /// </summary>

Loading…
Cancel
Save