// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] public class ObuFrameHeaderTests { /// /// Identifies one current-libaom sequence-header conformance condition used by the malformed-input theory. /// public enum InvalidSequenceHeaderCase { /// /// Encodes the otherwise valid baseline. /// None, /// /// Encodes an unassigned sequence-level index. /// UndefinedSequenceLevel, /// /// Encodes an initial display delay greater than ten frames. /// InitialDisplayDelayAboveTen, /// /// Encodes a frame identifier wider than sixteen bits. /// FrameIdentifierLengthAboveSixteen, /// /// Encodes a zero display-tick unit. /// ZeroDisplayTick, /// /// Encodes a zero time scale. /// ZeroTimeScale, /// /// Encodes the unsigned-variable-length overflow sentinel. /// OverflowingTicksPerPicture, /// /// Encodes the sRGB identity-matrix tuple with the main profile. /// MainProfileSrgbIdentity, /// /// Encodes an identity matrix with subsampled components. /// SubsampledIdentityMatrix } private static readonly byte[] DefaultSequenceHeaderBitStream = [0x0a, 0x06, 0b001_1_1_000, 0b00_1000_01, 0b11_110101, 0b001_11101, 0b111_1_1_1_0_1, 0b1_0_0_1_1_1_10]; // This complete temporal-delimiter and sequence-header prefix comes from the color item in libavif's // draw_points_idat_progressive.avif. Its operating points select spatial layers 0+1 and layer 0 respectively. private static ReadOnlySpan ProgressiveSequenceHeaderBitStream => [ 0x12, 0x00, 0x0A, 0x0F, 0x20, 0x13, 0x01, 0x00, 0x80, 0x81, 0x4E, 0x0A, 0x36, 0xBE, 0x48, 0x08, 0x20, 0x34, 0x80 ]; // Bits Syntax element Value // 1 obu_forbidden_bit 0 // 4 obu_type 2 (OBU_TEMPORAL_DELIMITER) // 1 obu_extension_flag 0 // 1 obu_has_size_field 1 // 1 obu_reserved_1bit 0 // 8 obu_size 0 private static readonly byte[] DefaultTemporalDelimiterBitStream = [0x12, 0x00]; [Theory] // [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6bd1)] [InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc)] [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d)] public void ReadFrameHeader(string filename, int fileOffset, int blockSize) { // Assign string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); byte[] content = File.ReadAllBytes(filePath); Span span = content.AsSpan(fileOffset, blockSize); Av1BitStreamReader reader = new(span); IAv1TileReader decoder = new Av1TileDecoderStub(); ObuReader obuReader = new(); // Act obuReader.ReadAll(ref reader, blockSize, () => decoder); // Assert Assert.NotNull(obuReader.SequenceHeader); Assert.NotNull(obuReader.FrameHeader); Assert.NotNull(obuReader.FrameHeader.TilesInfo); Assert.Equal(reader.Length * 8, reader.BitPosition); Assert.Equal(reader.Length, blockSize); } [Theory] [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d)] [InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc)] public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, int blockSize) { // Assign string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); byte[] content = File.ReadAllBytes(filePath); Span span = content.AsSpan(fileOffset, blockSize); Av1TileDecoderStub tileStub = new(); Av1BitStreamReader reader = new(span); ObuReader obuReader = new(); // Act 1 obuReader.ReadAll(ref reader, blockSize, () => tileStub); // Assign 2 MemoryStream encoded = new(); // Act 2 ObuWriter obuWriter = new(); obuWriter.WriteAll(Configuration.Default, encoded, obuReader.SequenceHeader, obuReader.FrameHeader, tileStub); // Assert byte[] encodedArray = encoded.ToArray(); Assert.Equal((ReadOnlySpan)span, encodedArray); } [Theory] [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d)] [InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc)] public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int blockSize) { // Assign string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); byte[] content = File.ReadAllBytes(filePath); Span span = content.AsSpan(fileOffset, blockSize); Av1TileDecoderStub tileStub = new(); Av1BitStreamReader reader = new(span); ObuReader obuReader1 = new(); // Act 1 obuReader1.ReadAll(ref reader, blockSize, () => tileStub); // Assign 2 MemoryStream encoded = new(); // Act 2 ObuWriter obuWriter = new(); obuWriter.WriteAll(Configuration.Default, encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader, tileStub); // Assign 2 Span encodedBuffer = encoded.ToArray(); IAv1TileReader tileDecoder2 = new Av1TileDecoderStub(); Av1BitStreamReader reader2 = new(span); ObuReader obuReader2 = new(); // Act 2 obuReader2.ReadAll(ref reader2, encodedBuffer.Length, () => tileDecoder2); // Assert Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(obuReader1.SequenceHeader.ColorConfig), ObuPrettyPrint.PrettyPrintProperties(obuReader2.SequenceHeader.ColorConfig)); Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(obuReader1.SequenceHeader), ObuPrettyPrint.PrettyPrintProperties(obuReader2.SequenceHeader)); Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(obuReader1.FrameHeader), ObuPrettyPrint.PrettyPrintProperties(obuReader2.FrameHeader)); Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(obuReader1.FrameHeader.TilesInfo), ObuPrettyPrint.PrettyPrintProperties(obuReader2.FrameHeader.TilesInfo)); } [Fact] public void ReadTemporalDelimiter() { // Arrange Av1BitStreamReader reader = new(DefaultTemporalDelimiterBitStream); ObuReader obuReader = new(); IAv1TileReader tileDecoder = new Av1TileDecoderStub(); // Act obuReader.ReadAll(ref reader, DefaultTemporalDelimiterBitStream.Length, () => tileDecoder); // Assert Assert.Null(obuReader.SequenceHeader); Assert.Null(obuReader.FrameHeader); } [Fact] public void ReadAnnexBHeaderWithoutSizeField() { // Arrange // Annex B's outer obu_length is one byte and covers the size-less temporal-delimiter header. byte[] bitStream = [0x01, 0x10]; Av1BitStreamReader reader = new(bitStream); ObuReader obuReader = new(); IAv1TileReader tileDecoder = new Av1TileDecoderStub(); // Act obuReader.ReadAll(ref reader, bitStream.Length, () => tileDecoder, isAnnexB: true); // Assert Assert.Null(obuReader.SequenceHeader); Assert.Null(obuReader.FrameHeader); } /// /// Verifies that a final low-overhead frame OBU may use the enclosing image-item boundary instead of an OBU size field. /// [Fact] public void ReadFinalLowOverheadFrameWithoutSizeField() { const int itemDataOffset = 0x010E; const int itemDataLength = 0x001D; string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Heif.Orange4x4); byte[] fileContent = File.ReadAllBytes(filePath); byte[] sizedBitStream = fileContent.AsSpan(itemDataOffset, itemDataLength).ToArray(); int frameOffset = DefaultTemporalDelimiterBitStream.Length; Av1BitStreamReader sequenceSizeReader = new(sizedBitStream.AsSpan(frameOffset + 1)); ulong sequencePayloadLength = sequenceSizeReader.ReadLittleEndianBytes128(out int sequenceSizeLength); frameOffset += 1 + sequenceSizeLength + (int)sequencePayloadLength; Av1BitStreamReader sizeReader = new(sizedBitStream.AsSpan(frameOffset + 1)); ulong framePayloadLength = sizeReader.ReadLittleEndianBytes128(out int encodedSizeLength); byte[] bitStream = new byte[sizedBitStream.Length - encodedSizeLength]; // Preserve the independently encoded frame payload while changing only the final OBU's legal boundary form. sizedBitStream.AsSpan(0, frameOffset).CopyTo(bitStream); bitStream[frameOffset] = (byte)(sizedBitStream[frameOffset] & ~0x02); sizedBitStream.AsSpan(frameOffset + 1 + encodedSizeLength).CopyTo(bitStream.AsSpan(frameOffset + 1)); Assert.Equal(framePayloadLength, (ulong)(bitStream.Length - frameOffset - 1)); ReadObuStream(bitStream); } [Fact] public void ReadSequenceHeader() { // Arrange byte[] bitStream = DefaultSequenceHeaderBitStream; Av1BitStreamReader reader = new(bitStream); ObuReader obuReader = new(); IAv1TileReader tileDecoder = new Av1TileDecoderStub(); ObuSequenceHeader expected = GetDefaultSequenceHeader(); // Act obuReader.ReadAll(ref reader, bitStream.Length, () => tileDecoder); // Assert Assert.NotNull(obuReader.SequenceHeader); Assert.Null(obuReader.FrameHeader); Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(expected), ObuPrettyPrint.PrettyPrintProperties(obuReader.SequenceHeader)); } /// /// Verifies current-libaom sequence-header conformance failures through the complete bounded OBU parser. /// /// The single invalid syntax condition encoded into an otherwise valid sequence header. [Theory] [InlineData(InvalidSequenceHeaderCase.UndefinedSequenceLevel)] [InlineData(InvalidSequenceHeaderCase.InitialDisplayDelayAboveTen)] [InlineData(InvalidSequenceHeaderCase.FrameIdentifierLengthAboveSixteen)] [InlineData(InvalidSequenceHeaderCase.ZeroDisplayTick)] [InlineData(InvalidSequenceHeaderCase.ZeroTimeScale)] [InlineData(InvalidSequenceHeaderCase.OverflowingTicksPerPicture)] [InlineData(InvalidSequenceHeaderCase.MainProfileSrgbIdentity)] [InlineData(InvalidSequenceHeaderCase.SubsampledIdentityMatrix)] public void ReadSequenceHeaderRejectsCurrentLibaomConformanceFailure(InvalidSequenceHeaderCase invalidCase) { byte[] bitStream = CreateNonReducedSequenceHeaderObu(invalidCase); Assert.Throws(() => ReadObuStream(bitStream)); } /// /// Verifies that reserved OBU header fields are ignored consistently by container validation and syntax parsing. /// /// Whether the sequence header also carries nonzero reserved extension bits. [Theory] [InlineData(false)] [InlineData(true)] public void ReadSequenceHeaderIgnoresReservedObuHeaderBits(bool hasExtension) { byte[] bitStream; if (hasExtension) { bitStream = new byte[DefaultSequenceHeaderBitStream.Length + 1]; bitStream[0] = (byte)(DefaultSequenceHeaderBitStream[0] | 0x05); bitStream[1] = 0x07; DefaultSequenceHeaderBitStream.AsSpan(1).CopyTo(bitStream.AsSpan(2)); } else { bitStream = [.. DefaultSequenceHeaderBitStream]; bitStream[0] |= 0x01; } Av1CodecConfiguration configuration = new([0x81, 0x00, 0x00, 0x00], new DecoderOptions()); configuration.ValidateItemData( bitStream, null, null, new DecoderOptions(), out _, out _); ReadObuStream(bitStream); } /// /// Verifies that metadata-type LEB128 values obey current libaom's shared unsigned 32-bit limit. /// [Fact] public void ValidateItemDataRejectsMetadataTypeAboveCurrentLibaomLimit() { byte[] bitStream = [ .. DefaultSequenceHeaderBitStream, // Metadata type 2^32 followed by valid byte-aligned trailing bits. 0x2A, 0x06, 0x80, 0x80, 0x80, 0x80, 0x10, 0x80 ]; DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; Av1CodecConfiguration configuration = new([0x81, 0x00, 0x00, 0x00], options); Assert.Throws( () => configuration.ValidateItemData( bitStream, null, null, options, out _, out _)); } /// /// Verifies that an item cannot select an operating-point index absent from its sequence header. /// [Fact] public void ReadOperatingPointRejectsIndexOutsideSequenceHeader() { byte[] bitStream = [.. ProgressiveSequenceHeaderBitStream]; Assert.Throws(() => ReadObuStream(bitStream, 2)); } /// /// Verifies that an extended OBU belongs to an operating point only when both of its layer identifiers are selected. /// /// The temporal-layer identifier carried by the test OBU. /// The spatial-layer identifier carried by the test OBU. /// Whether operating point one selects both identifiers. [Theory] [InlineData(0, 0, true)] [InlineData(0, 1, false)] [InlineData(1, 0, false)] public void ReadOperatingPointRequiresBothLayerBits(byte temporalId, byte spatialId, bool isIncluded) { byte extension = (byte)((temporalId << 5) | (spatialId << 3)); byte[] bitStream = [.. ProgressiveSequenceHeaderBitStream, 0x2E, extension, 0x01, 0x00]; // A selected metadata OBU reaches ignored-payload validation, where an all-zero payload is invalid. A filtered // OBU has nevertheless had its complete header, size and payload boundary consumed before syntax is skipped. if (isIncluded) { Assert.Throws(() => ReadObuStream(bitStream, 1)); } else { ReadObuStream(bitStream, 1); } } /// /// Verifies that an all-zero operating-point mask includes every extended OBU. /// [Fact] public void ReadZeroOperatingPointMaskIncludesExtendedObu() { byte[] bitStream = [.. DefaultSequenceHeaderBitStream, 0x2E, 0x08, 0x01, 0x00]; Assert.Throws(() => ReadObuStream(bitStream)); } /// /// Verifies that an OBU without an extension header applies to every operating point. /// [Fact] public void ReadOperatingPointIncludesUnextendedObu() { byte[] bitStream = [.. ProgressiveSequenceHeaderBitStream, 0x2A, 0x01, 0x00]; Assert.Throws(() => ReadObuStream(bitStream, 1)); } /// /// Verifies that temporal delimiters remain part of stream framing even when their extension is outside the selected mask. /// [Fact] public void ReadOperatingPointDoesNotFilterTemporalDelimiter() { byte[] bitStream = [.. ProgressiveSequenceHeaderBitStream, 0x16, 0x08, 0x01, 0x01]; Assert.Throws(() => ReadObuStream(bitStream, 1)); } /// /// Verifies that an excluded OBU cannot escape validation of its declared payload boundary. /// [Fact] public void ReadFilteredOperatingPointObuStillValidatesBoundary() { byte[] bitStream = [.. ProgressiveSequenceHeaderBitStream, 0x2E, 0x08, 0x02, 0x80]; Assert.Throws(() => ReadObuStream(bitStream, 1)); } /// /// Verifies that the reduced sequence syntax cannot be used without declaring a still picture. /// [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(() => ReadObuStream(bitStream)); } /// /// Verifies that a declared still-picture sequence rejects frame prefixes which do not describe a shown key frame. /// /// The high nibble containing show-existing, frame-type, and show-frame syntax. [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(() => ReadObuStream(bitStream)); } /// /// Verifies that ignored OBU payloads are bounded and skipped before parsing the following sequence header. /// [Fact] public void ReadIgnoredObusSkipsEachDeclaredPayload() { // 0x7A identifies padding and 0x4A identifies reserved OBU type 9. Both carry explicit sizes so their payload // bytes must never be interpreted as another OBU header. byte[] bitStream = [ 0x7A, 0x02, 0x80, 0x00, 0x4A, 0x01, 0x80, .. DefaultSequenceHeaderBitStream ]; Av1BitStreamReader reader = new(bitStream); ObuReader obuReader = new(); IAv1TileReader tileDecoder = new Av1TileDecoderStub(); obuReader.ReadAll(ref reader, bitStream.Length, () => tileDecoder); Assert.NotNull(obuReader.SequenceHeader); Assert.Equal(bitStream.Length * 8, reader.BitPosition); } /// /// Verifies that a four-byte tile size cannot wrap into an empty first tile. /// [Fact] public void ReadTileGroupRejectsFourByteTileSizeOverflow() { byte[] bitStream = CreateTwoTileFrame(GetDefaultSequenceHeader(), GetKeyFrameHeader(), 4); Span encodedTileSize = bitStream.AsSpan(bitStream.Length - 6, 4); encodedTileSize.Fill(byte.MaxValue); Assert.Throws(() => ReadObuStream(bitStream)); } /// /// Verifies current libaom's doubled minimum inner-tile width for a super-resolution-scaled frame. /// [Fact] public void ReadTileInfoRejectsNarrowSuperResolutionInnerTile() { ObuSequenceHeader sequenceHeader = GetDefaultSequenceHeader(); sequenceHeader.Use128x128Superblock = false; sequenceHeader.EnableSuperResolution = true; sequenceHeader.FrameWidthBits = 8; sequenceHeader.MaxFrameWidth = 192; ObuFrameHeader frameHeader = GetKeyFrameHeader(); frameHeader.FrameSize.FrameWidth = 96; frameHeader.FrameSize.SuperResolutionUpscaledWidth = 192; frameHeader.FrameSize.RenderWidth = 192; frameHeader.FrameSize.SuperResolutionDenominator = 16; frameHeader.ModeInfoColumnCount = 24; byte[] bitStream = CreateTwoTileFrame(sequenceHeader, frameHeader, 1); Assert.Throws(() => ReadObuStream(bitStream)); } /// /// Verifies that an intra-only frame cannot signal the all-slots refresh mask reserved for key and switch frames. /// [Fact] public void ReadFrameHeaderRejectsIntraOnlyAllSlotsRefresh() { byte[] sequenceHeader = CreateNonReducedSequenceHeaderObu(default); using AutoExpandingMemory frameMemory = new(Configuration.Default, 8); Av1BitStreamWriter frameWriter = new(frameMemory); frameWriter.WriteBoolean(false); frameWriter.WriteLiteral((uint)ObuFrameType.IntraOnlyFrame, 2); frameWriter.WriteBoolean(true); frameWriter.WriteBoolean(true); frameWriter.WriteBoolean(false); frameWriter.WriteBoolean(false); frameWriter.WriteLiteral(byte.MaxValue, 8); int framePayloadLength = (frameWriter.BitPosition + 7) >> 3; frameWriter.Flush(); byte[] bitStream = new byte[sequenceHeader.Length + 2 + framePayloadLength]; sequenceHeader.CopyTo(bitStream, 0); int frameObuOffset = sequenceHeader.Length; bitStream[frameObuOffset] = (byte)(((byte)ObuType.FrameHeader << 3) | 0x02); bitStream[frameObuOffset + 1] = (byte)framePayloadLength; frameMemory.GetSpan(framePayloadLength).CopyTo(bitStream.AsSpan(frameObuOffset + 2)); Assert.Throws(() => ReadObuStream(bitStream)); } /// /// Verifies that invalid OBU boundaries, size fields, and trailing bytes are rejected. /// /// The malformed OBU stream. [Theory] [InlineData(new byte[] { 0x7A, 0x02, 0x11 })] [InlineData(new byte[] { 0x7A, 0x01, 0x00 })] [InlineData(new byte[] { 0x12, 0x01, 0x01 })] [InlineData(new byte[] { 0x7A, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 })] public void ReadInvalidObuBoundaryThrows(byte[] bitStream) => Assert.Throws(() => ReadObuStream(bitStream)); /// /// Verifies that an empty temporal delimiter may occupy a payload containing only zero padding bytes. /// [Fact] public void ReadTemporalDelimiterAllowsZeroPayloadPadding() { byte[] bitStream = [0x12, 0x02, 0x00, 0x00]; ReadObuStream(bitStream); } [Fact] public void WriteTemporalDelimiter() { // Arrange using MemoryStream stream = new(2); ObuWriter obuWriter = new(); // Act obuWriter.WriteAll(Configuration.Default, stream, null, null, null); byte[] actual = stream.GetBuffer(); // Assert Assert.Equal(DefaultTemporalDelimiterBitStream, actual); } [Fact] public void WriteSequenceHeader() { // Arrange using MemoryStream stream = new(10); ObuSequenceHeader input = GetDefaultSequenceHeader(); ObuWriter obuWriter = new(); // Act obuWriter.WriteAll(Configuration.Default, stream, input, null, null); byte[] buffer = stream.GetBuffer(); // Assert // Skip over Temporal Delimiter header. byte[] actual = buffer.AsSpan()[DefaultTemporalDelimiterBitStream.Length..].ToArray(); Assert.Equal(DefaultSequenceHeaderBitStream, actual); } /// /// Verifies that the combined frame OBU declares exactly the payload bytes emitted by the writer. /// [Fact] public void WriteFrameHeader() { // Arrange using MemoryStream stream = new(10); ObuSequenceHeader sequenceInput = GetDefaultSequenceHeader(); ObuFrameHeader frameInput = GetKeyFrameHeader(); Av1TileDecoderStub tileStub = new(); byte[] tileData = [0x80]; tileStub.ReadTile(tileData, 0); ObuWriter obuWriter = new(); // Act obuWriter.WriteAll(Configuration.Default, stream, sequenceInput, frameInput, tileStub); byte[] bitStream = stream.ToArray(); // Assert int frameOffset = DefaultTemporalDelimiterBitStream.Length + DefaultSequenceHeaderBitStream.Length; Span frameObu = bitStream.AsSpan(frameOffset); byte expectedHeader = (byte)(((byte)ObuType.Frame << 3) | 0x02); Assert.Equal(expectedHeader, frameObu[0]); Av1BitStreamReader sizeReader = new(frameObu[1..]); ulong declaredPayloadSize = sizeReader.ReadLittleEndianBytes128(out int encodedSizeLength); Assert.Equal(frameObu.Length - 1 - encodedSizeLength, (int)declaredPayloadSize); Av1BitStreamReader reader = new(bitStream); ObuReader obuReader = new(); obuReader.ReadAll(ref reader, bitStream.Length, () => new Av1TileDecoderStub()); Assert.NotNull(obuReader.SequenceHeader); Assert.NotNull(obuReader.FrameHeader); Assert.Equal(bitStream.Length * 8, reader.BitPosition); } /// /// Encodes one non-reduced sequence header with a single selected conformance failure. /// /// The syntax condition to make invalid, or . /// The complete explicitly sized sequence-header OBU. private static byte[] CreateNonReducedSequenceHeaderObu(InvalidSequenceHeaderCase invalidCase) { bool hasTimingInfo = invalidCase is InvalidSequenceHeaderCase.ZeroDisplayTick or InvalidSequenceHeaderCase.ZeroTimeScale or InvalidSequenceHeaderCase.OverflowingTicksPerPicture; bool hasInitialDisplayDelay = invalidCase == InvalidSequenceHeaderCase.InitialDisplayDelayAboveTen; bool hasFrameIdentifiers = invalidCase == InvalidSequenceHeaderCase.FrameIdentifierLengthAboveSixteen; bool hasColorDescription = invalidCase is InvalidSequenceHeaderCase.MainProfileSrgbIdentity or InvalidSequenceHeaderCase.SubsampledIdentityMatrix; using AutoExpandingMemory payloadMemory = new(Configuration.Default, 32); Av1BitStreamWriter writer = new(payloadMemory); writer.WriteLiteral((uint)ObuSequenceProfile.Main, 3); writer.WriteBoolean(false); writer.WriteBoolean(false); writer.WriteBoolean(hasTimingInfo); if (hasTimingInfo) { writer.WriteLiteral(invalidCase == InvalidSequenceHeaderCase.ZeroDisplayTick ? 0U : 1U, 32); writer.WriteLiteral(invalidCase == InvalidSequenceHeaderCase.ZeroTimeScale ? 0U : 1U, 32); bool overflowingTicksPerPicture = invalidCase == InvalidSequenceHeaderCase.OverflowingTicksPerPicture; writer.WriteBoolean(overflowingTicksPerPicture); if (overflowingTicksPerPicture) { // Thirty-two leading zeros are the UVLC sentinel which current libaom rejects as UINT32_MAX. writer.WriteLiteral(0U, 32); } writer.WriteBoolean(false); } writer.WriteBoolean(hasInitialDisplayDelay); writer.WriteLiteral(0U, 5); writer.WriteLiteral(0U, 12); uint sequenceLevel = invalidCase == InvalidSequenceHeaderCase.UndefinedSequenceLevel ? 24U : 0U; writer.WriteLiteral(sequenceLevel, 5); if (sequenceLevel > 7) { writer.WriteBoolean(false); } if (hasInitialDisplayDelay) { writer.WriteBoolean(true); writer.WriteLiteral(10U, 4); } writer.WriteLiteral(7U, 4); writer.WriteLiteral(7U, 4); writer.WriteLiteral(63U, 8); writer.WriteLiteral(63U, 8); writer.WriteBoolean(hasFrameIdentifiers); if (hasFrameIdentifiers) { writer.WriteLiteral(15U, 4); writer.WriteLiteral(0U, 3); } // Disable superblock and intra-edge tools. writer.WriteBoolean(false); writer.WriteBoolean(false); writer.WriteBoolean(false); // Disable the inter compound, warped, dual-filter, and order-hint tools. writer.WriteBoolean(false); writer.WriteBoolean(false); writer.WriteBoolean(false); writer.WriteBoolean(false); writer.WriteBoolean(false); // Select fixed disabled screen-content and integer-motion-vector behavior. writer.WriteBoolean(false); writer.WriteBoolean(false); // Disable super resolution, CDEF, and restoration in the otherwise valid baseline. writer.WriteBoolean(false); writer.WriteBoolean(false); writer.WriteBoolean(false); // Encode an 8-bit, non-monochrome color configuration. writer.WriteBoolean(false); writer.WriteBoolean(false); writer.WriteBoolean(hasColorDescription); if (hasColorDescription) { bool isSrgbIdentity = invalidCase == InvalidSequenceHeaderCase.MainProfileSrgbIdentity; writer.WriteLiteral((uint)(isSrgbIdentity ? ObuColorPrimaries.Bt709 : ObuColorPrimaries.Unspecified), 8); writer.WriteLiteral((uint)(isSrgbIdentity ? ObuTransferCharacteristics.Srgb : ObuTransferCharacteristics.Unspecified), 8); writer.WriteLiteral((uint)ObuMatrixCoefficients.Identity, 8); } if (invalidCase != InvalidSequenceHeaderCase.MainProfileSrgbIdentity) { writer.WriteBoolean(false); writer.WriteLiteral((uint)ObuChromoSamplePosition.Unknown, 2); } writer.WriteBoolean(false); writer.WriteBoolean(false); int trailingBitCount = 8 - (writer.BitPosition & 0x07); writer.WriteLiteral(1U << (trailingBitCount - 1), trailingBitCount); int payloadLength = (writer.BitPosition + 7) >> 3; writer.Flush(); byte[] obu = new byte[payloadLength + 2]; obu[0] = (byte)(((byte)ObuType.SequenceHeader << 3) | 0x02); obu[1] = (byte)payloadLength; payloadMemory.GetSpan(payloadLength).CopyTo(obu.AsSpan(2)); return obu; } /// /// Encodes one valid reduced frame with two one-byte tile payloads. /// /// The sequence syntax to encode. /// The frame syntax to encode. /// The number of bytes used for the first tile's size field. /// The complete temporal delimiter, sequence header, and combined frame OBU stream. private static byte[] CreateTwoTileFrame( ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, int tileSizeBytes) { frameHeader.TilesInfo.HasUniformTileSpacing = true; frameHeader.TilesInfo.TileColumnCount = 2; frameHeader.TilesInfo.TileRowCount = 1; frameHeader.TilesInfo.TileSizeBytes = tileSizeBytes; Av1TileDecoderStub tileStub = new(); tileStub.ReadTile([0x80], 0); tileStub.ReadTile([0x80], 1); using MemoryStream stream = new(); ObuWriter writer = new(); writer.WriteAll(Configuration.Default, stream, sequenceHeader, frameHeader, tileStub); return stream.ToArray(); } private static ObuSequenceHeader GetDefaultSequenceHeader() // Offset Bits Syntax element Value // 0 3 seq_profile 1 // 3 1 still_picture 1 // 4 1 reduced_still_picture_header 1 // 5 5 seq_level_idx[ 0 ] 0 // 10 4 frame_width_bits_minus_1 8 // 14 4 frame_height_bits_minus_1 7 // 18 9 max_frame_width_minus_1 425 // 27 8 max_frame_height_minus_1 239 // 35 1 use_128x128_superblock 1 // 36 1 enable_filter_intra 1 // 37 1 enable_intra_edge_filter 1 // 38 1 enable_superres 0 // 39 1 enable_cdef 1 // 40 1 enable_restoration 1 // 41 1 ColorConfig.BitDepth.HasHighBit 0 // 42 1 ColorConfig.IsDescriptionPresent 0 // 43 1 ColorConfig.ColorRange 1 // 44 1 ColorConfig.HasSeparateUVDelta 1 // 45 1 film_grain_present 1 // 47 2 Trailing bits 2 => new() { SequenceProfile = ObuSequenceProfile.High, IsStillPicture = true, IsReducedStillPictureHeader = true, TimingInfoPresentFlag = false, InitialDisplayDelayPresentFlag = false, FrameWidthBits = 8 + 1, FrameHeightBits = 7 + 1, MaxFrameWidth = 425 + 1, MaxFrameHeight = 239 + 1, IsFrameIdNumbersPresent = false, Use128x128Superblock = true, EnableFilterIntra = true, EnableIntraEdgeFilter = true, EnableInterIntraCompound = false, EnableMaskedCompound = false, EnableWarpedMotion = false, EnableDualFilter = false, EnableOrderHint = false, OperatingPoint = [new()], // EnableJountCompound = true, // EnableReferenceFrameMotionVectors = true, ForceScreenContentTools = 2, ForceIntegerMotionVector = 2, EnableSuperResolution = false, EnableCdef = true, EnableRestoration = true, ColorConfig = new() { IsMonochrome = false, ColorPrimaries = ObuColorPrimaries.Unspecified, TransferCharacteristics = ObuTransferCharacteristics.Unspecified, MatrixCoefficients = ObuMatrixCoefficients.Unspecified, SubSamplingX = false, SubSamplingY = false, BitDepth = Av1BitDepth.EightBit, HasSeparateUvDelta = true, ColorRange = true, }, AreFilmGrainingParametersPresent = true, }; /// /// Reads one complete OBU stream for malformed-input assertions that cannot capture a ref-struct reader. /// /// The complete encoded OBU stream. private static void ReadObuStream(byte[] bitStream, byte operatingPointIndex = 0) { Av1BitStreamReader reader = new(bitStream); ObuReader obuReader = new(operatingPointIndex); IAv1TileReader tileDecoder = new Av1TileDecoderStub(); obuReader.ReadAll(ref reader, bitStream.Length, () => tileDecoder); } private static ObuFrameHeader GetKeyFrameHeader() => new() { FrameType = ObuFrameType.KeyFrame, ShowFrame = true, ShowableFrame = false, DisableFrameEndUpdateCdf = false, FrameSize = new() { FrameWidth = 426, FrameHeight = 240, RenderWidth = 426, RenderHeight = 240, SuperResolutionUpscaledWidth = 426, }, PrimaryReferenceFrame = 7, ModeInfoRowCount = 60, ModeInfoColumnCount = 108, RefreshFrameFlags = 0xff, ErrorResilientMode = true, ForceIntegerMotionVector = true, TilesInfo = new ObuTileGroupHeader() { HasUniformTileSpacing = true, TileColumnCount = 1, TileRowCount = 1, } }; }