From 5dedc94d9fdea109b57d9d6b2a5340c6f6845d84 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 7 Jan 2024 21:37:09 +0100 Subject: [PATCH] Initial av1 bitstream parsing --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 150 +++ .../Formats/Heif/Av1/Av1BlockSize.cs | 34 + .../Formats/Heif/Av1/Av1ColorFormat.cs | 12 + .../Formats/Heif/Av1/Av1MainParseContext.cs | 8 + src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 108 +++ src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs | 11 + .../ObuChromoSamplePosition.cs | 22 + .../Av1/OpenBitstreamUnit/ObuColorConfig.cs | 52 + .../OpenBitstreamUnit/ObuColorPrimaries.cs | 21 + .../Av1/OpenBitstreamUnit/ObuConstants.cs | 82 ++ ...tDirectionalEnhancementFilterParameters.cs | 13 + .../ObuFilmGrainParameters.cs | 8 + .../Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 81 ++ .../Av1/OpenBitstreamUnit/ObuFrameSize.cs | 19 + .../Av1/OpenBitstreamUnit/ObuFrameType.cs | 12 + .../Heif/Av1/OpenBitstreamUnit/ObuHeader.cs | 21 + .../ObuLoopFilterParameters.cs | 9 + .../ObuLoopRestorationParameters.cs | 9 + .../ObuMatrixCoefficients.cs | 22 + .../Av1/OpenBitstreamUnit/ObuMetadataType.cs | 13 + .../OpenBitstreamUnit/ObuOperatingPoint.cs | 17 + .../Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs | 13 + .../ObuQuantizationParameters.cs | 19 + .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 888 ++++++++++++++++++ .../Av1/OpenBitstreamUnit/ObuReferenceMode.cs | 10 + .../OpenBitstreamUnit/ObuRestorationType.cs | 9 + .../ObuSegmentationLevelFeature.cs | 16 + .../ObuSegmentationParameters.cs | 15 + .../OpenBitstreamUnit/ObuSequenceHeader.cs | 71 ++ .../OpenBitstreamUnit/ObuSequenceProfile.cs | 11 + .../Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs | 39 + .../ObuTransferCharacteristics.cs | 25 + .../Heif/Av1/OpenBitstreamUnit/ObuType.cs | 18 + 33 files changed, 1858 insertions(+) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1MainParseContext.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1Math.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuType.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs new file mode 100644 index 0000000000..e16553121c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -0,0 +1,150 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal ref struct Av1BitStreamReader(Span data) +{ + private const int WordSize = sizeof(byte); + private const int DoubleWordSize = 2 * WordSize; + private readonly Span data = data; + private int wordPosition = 0; + private int bitOffset = 0; + + public readonly int BitPosition => (this.wordPosition * WordSize) + this.bitOffset; + + public readonly int Length => this.data.Length; + + public void Reset() + { + this.wordPosition = 0; + this.bitOffset = 0; + } + + public void Skip(int bitCount) + { + this.bitOffset += bitCount; + while (this.bitOffset >= WordSize) + { + this.bitOffset -= WordSize; + this.wordPosition++; + } + } + + public uint ReadLiteral(int bitCount) + { + uint bits = (uint)(this.data[this.wordPosition] << this.bitOffset) >> (WordSize - bitCount); + this.bitOffset += bitCount; + while (this.bitOffset > WordSize) + { + uint nextWord = this.data[this.wordPosition + 1]; + bits |= nextWord << (DoubleWordSize - bitCount); + } + + if (this.bitOffset >= WordSize) + { + this.bitOffset -= WordSize; + } + + return bits; + } + + internal bool ReadBoolean() + { + bool bit = (this.data[this.wordPosition] & (1 << (WordSize - this.bitOffset))) > 0; + this.Skip(1); + return bit; + } + + public ulong ReadLittleEndianBytes128(out int length) + { + // See section 4.10.5 of the AV1-Specification + DebugGuard.IsTrue((this.bitOffset & (WordSize - 1)) == 0, "Reading of Little Endian 128 value only allowed on byte alignment"); + + ulong value = 0; + length = 0; + for (int i = 0; i < 56; i += 7) + { + uint leb128Byte = this.ReadLiteral(8); + value |= (leb128Byte & 0x7FUL) << i; + length++; + if ((leb128Byte & 0x80U) != 0x80U) + { + break; + } + } + + return value; + } + + public uint ReadUnsignedVariableLength() + { + // See section 4.10.3 of the AV1-Specification + int leadingZerosCount = 0; + while (leadingZerosCount < 32 && this.ReadLiteral(1) == 0U) + { + leadingZerosCount++; + } + + if (leadingZerosCount == 32) + { + return uint.MaxValue; + } + + uint basis = (1U << leadingZerosCount) - 1U; + uint value = this.ReadLiteral(leadingZerosCount); + return basis + value; + } + + public uint ReadNonSymmetric(uint n) + { + // See section 4.10.7 of the AV1-Specification + if (n <= 1) + { + return 0; + } + + int w = (int)(Av1Math.MostSignificantBit(n) + 1); + uint m = (uint)((1 << w) - n); + uint v = this.ReadLiteral(w - 1); + if (v < m) + { + return v; + } + + return (v << 1) - m + this.ReadLiteral(1); + } + + public int ReadSignedFromUnsigned(int n) + { + // See section 4.10.6 of the AV1-Specification + int signedValue; + uint value = this.ReadLiteral(n); + uint signMask = 1U << (n - 1); + if ((value & signMask) == signMask) + { + // Prevent overflow by casting to long; + signedValue = (int)((long)value - (signMask << 1)); + } + else + { + signedValue = (int)value; + } + + return signedValue; + } + + public uint ReadLittleEndian(int n) + { + // See section 4.10.4 of the AV1-Specification + DebugGuard.IsTrue((this.bitOffset & (WordSize - 1)) == 0, "Reading of Little Endian value only allowed on byte alignment"); + + uint t = 0; + for (int i = 0; i < 8 * n; i += 8) + { + t += this.ReadLiteral(8) << i; + } + + return t; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs new file mode 100644 index 0000000000..6eca5bc638 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal enum Av1BlockSize +{ + Block4x4, + Block4x8, + Block8x4, + Block8x8, + Block8x16, + Block16x8, + Block16x16, + Block16x32, + Block32x16, + Block32x32, + Block32x64, + Block64x32, + Block64x64, + Block64x128, + Block128x64, + Block128x128, + Block4x16, + Block16x4, + Block8x32, + Block32x8, + Block16x64, + Block64x16, + BlockSizeSAll, + BlockSizeS = Block4x16, + BlockInvalid = 255, + BlockLargest = BlockSizeS - 1, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs b/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs new file mode 100644 index 0000000000..07be6a0442 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal enum Av1ColorFormat +{ + Yuv400, + Yuv420, + Yuv422, + Yuv444, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1MainParseContext.cs b/src/ImageSharp/Formats/Heif/Av1/Av1MainParseContext.cs new file mode 100644 index 0000000000..4b13a86cd7 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1MainParseContext.cs @@ -0,0 +1,8 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal class Av1MainParseContext +{ +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs new file mode 100644 index 0000000000..c98652e048 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -0,0 +1,108 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal static class Av1Math +{ + public static uint MostSignificantBit(uint value) => value >> 31; + + public static uint Log2(uint n) + { + uint result = 0U; + while ((n >>= 1) > 0) + { + result++; + } + + return result; + } + + public static int Log2(int n) + { + int result = 0; + while ((n >>= 1) > 0) + { + result++; + } + + return result; + } + + public static uint FloorLog2(uint value) + { + uint s = 0; + while (value != 0U) + { + value >>= 1; + s++; + } + + return s - 1; + } + + public static uint CeilLog2(uint value) + { + if (value < 2) + { + return 0; + } + + uint i = 1; + uint p = 2; + while (p < value) + { + i++; + p <<= 1; + } + + return i; + } + + public static uint Clip1(uint value, int bitDepth) => + Clip3(0, (1U << bitDepth) - 1, value); + + public static uint Clip3(uint x, uint y, uint z) + { + if (z < x) + { + return x; + } + + if (z > y) + { + return y; + } + + return z; + } + + public static uint Round2(uint value, int n) + { + if (n == 0) + { + return value; + } + + return (uint)((value + (1 << (n - 1))) >> n); + } + + public static int Round2(int value, int n) + { + if (value < 0) + { + value = -value; + } + + return (int)Round2((uint)value, n); + } + + internal static int AlignPowerOf2(int value, int n) + { + int mask = (1 << n) - 1; + return (value + mask) & ~mask; + } + + internal static int Clamp(int value, int low, int high) + => value < low ? low : (value > high ? high : value); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs new file mode 100644 index 0000000000..9d73cda42a --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal enum Av1Plane : int +{ + Y = 0, + U = 1, + V = 2, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs new file mode 100644 index 0000000000..bd71ea4334 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuChromoSamplePosition : byte +{ + /// + /// Unknown. + /// + Unknown = 0, + + /// + /// Horizontally co-located with luma(0, 0) sample, between two vertical samples. + /// + Vertical = 1, + + /// + /// Co-located with luma(0, 0) sample + /// + Colocated = 2, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs new file mode 100644 index 0000000000..feafbaccd0 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuColorConfig +{ + internal bool IsColorDescriptionPresent { get; set; } + + internal int ChannelCount { get; set; } + + internal bool Monochrome { get; set; } + + internal ObuColorPrimaries ColorPrimaries { get; set; } + + internal ObuTransferCharacteristics TransferCharacteristics { get; set; } + + internal ObuMatrixCoefficients MatrixCoefficients { get; set; } + + internal bool ColorRange { get; set; } + + internal bool SubSamplingX { get; set; } + + internal bool SubSamplingY { get; set; } + + internal bool HasSeparateUvDelta { get; set; } + + internal ObuChromoSamplePosition ChromaSamplePosition { get; set; } + + internal int BitDepth { get; set; } + + internal bool HasSeparateUvDeltaQ { get; set; } + + public Av1ColorFormat GetColorFormat() + { + Av1ColorFormat format = Av1ColorFormat.Yuv400; + if (this.SubSamplingX && this.SubSamplingY) + { + format = Av1ColorFormat.Yuv420; + } + else if (this.SubSamplingX & !this.SubSamplingY) + { + format = Av1ColorFormat.Yuv422; + } + else if (!this.SubSamplingX && !this.SubSamplingY) + { + format = Av1ColorFormat.Yuv444; + } + + return format; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs new file mode 100644 index 0000000000..3136bba383 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuColorPrimaries +{ + None = 0, + Bt709 = 1, + Unspecified = 2, + Bt470M = 4, + Bt470BG = 5, + Bt601 = 6, + Smpte240 = 7, + GenericFilm = 8, + Bt2020 = 9, + Xyz = 10, + Smpte431 = 11, + Smpte432 = 12, + Ebu3213 = 22, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs new file mode 100644 index 0000000000..678b49d8b8 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal static class ObuConstants +{ + public const ObuSequenceProfile MaxSequenceProfile = ObuSequenceProfile.Professional; + + public const int LevelBits = -1; + + /// + /// Number of fractional bits for computing position in upscaling. + /// + public const int SuperResolutionScaleBits = 14; + + public const int ScaleNumerator = -1; + + /// + /// Number of reference frames that can be used for inter prediction. + /// + public const int ReferencesPerFrame = 7; + + /// + /// Maximum area of a tile in units of luma samples. + /// + public const int MaxTileArea = 4096 * 2304; + + /// + /// Maximum width of a tile in units of luma samples. + /// + public const int MaxTileWidth = 4096; + + /// + /// Maximum number of tile columns. + /// + public const int MaxTileColumnCount = 64; + + /// + /// Maximum number of tile rows. + /// + public const int MaxTileRowCount = 64; + + /// + /// Number of frames that can be stored for future reference. + /// + public const int ReferenceFrameCount = 8; + + /// + /// Value of 'PrimaryReferenceFrame' indicating that there is no primary reference frame. + /// + public const uint PrimaryReferenceFrameNone = 7; + + public const int PimaryReferenceBits = -1; + + /// + /// Number of segments allowed in segmentation map. + /// + public const int MaxSegmentCount = 8; + + /// + /// Smallest denominator for upscaling ratio. + /// + public const int SuperResolutionScaleDenominatorMinimum = 9; + + /// + /// Base 2 logarithm of maximum size of a superblock in luma samples. + /// + public const int MaxSuperBlockSizeLog2 = 7; + + /// + /// Base 2 logarithm of smallest size of a mode info block. + /// + public const int ModeInfoSizeLog2 = 2; + + public const int MaxQ = 255; + + /// + /// Number of segmentation features. + /// + public const int SegmentationLevelMax = 8; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs new file mode 100644 index 0000000000..57e54a6450 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuConstraintDirectionalEnhancementFilterParameters +{ + public bool BitCount { get; internal set; } + + public int[] YStrength { get; internal set; } + + public int[] UVStrength { get; internal set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs new file mode 100644 index 0000000000..6b74c979dd --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs @@ -0,0 +1,8 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuFilmGrainParameters +{ +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs new file mode 100644 index 0000000000..03870ca56c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuFrameHeader +{ + public bool ForceIntegerMotionVector { get; set; } + + public bool AllowIntraBlockCopy { get; set; } + + public bool UseReferenceFrameMotionVectors { get; set; } + + public bool AllowHighPrecisionMotionVector { get; set; } + + public ObuTileInfo TilesInfo { get; internal set; } = new ObuTileInfo(); + + public bool CodedLossless { get; internal set; } + + public bool[] LosslessArray { get; internal set; } = new bool[ObuConstants.MaxSegmentCount]; + + public ObuQuantizationParameters QuantizationParameters { get; set; } = new ObuQuantizationParameters(); + + public ObuSegmentationParameters SegmentationParameters { get; set; } = new ObuSegmentationParameters(); + + public bool AllLossless { get; internal set; } + + public bool AllowWarpedMotion { get; internal set; } + + public ObuReferenceMode ReferenceMode { get; internal set; } + + public ObuFilmGrainParameters FilmGrainParameters { get; internal set; } = new ObuFilmGrainParameters(); + + public bool ReducedTxSet { get; internal set; } + + public ObuLoopFilterParameters LoopFilterParameters { get; internal set; } = new ObuLoopFilterParameters(); + + public ObuLoopRestorationParameters[] LoopRestorationParameters { get; internal set; } = new ObuLoopRestorationParameters[3]; + + public ObuConstraintDirectionalEnhancementFilterParameters ConstraintDirectionalEnhancementFilterParameters { get; internal set; } = new ObuConstraintDirectionalEnhancementFilterParameters(); + + public int ModeInfoStride { get; internal set; } + + public bool DisableFrameEndUpdateCdf { get; internal set; } + + internal ObuFrameSize FrameSize { get; set; } = new ObuFrameSize(); + + internal int ModeInfoColumnCount { get; set; } + + internal int ModeInfoRowCount { get; set; } + + internal bool ShowExistingFrame { get; set; } + + internal ObuFrameType FrameType { get; set; } + + internal bool[] ReferenceValid { get; set; } = new bool[ObuConstants.ReferenceFrameCount]; + + internal bool[] ReferenceOrderHint { get; set; } = new bool[ObuConstants.ReferenceFrameCount]; + + internal bool ShowFrame { get; set; } + + internal bool ShowableFrame { get; set; } + + internal bool ErrorResilientMode { get; set; } + + internal bool AllowScreenContentTools { get; set; } + + internal bool DisableCdfUpdate { get; set; } + + internal bool ForeceIntegerMotionVector { get; set; } + + internal uint CurrentFrameId { get; set; } + + internal uint[] ReferenceFrameIndex { get; set; } = new uint[ObuConstants.ReferenceFrameCount]; + + internal uint OrderHint { get; set; } + + internal uint PrimaryReferenceFrame { get; set; } = ObuConstants.PrimaryReferenceFrameNone; + + internal uint RefreshFrameFlags { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs new file mode 100644 index 0000000000..7075b50b82 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuFrameSize +{ + internal int FrameWidth { get; set; } + + internal int FrameHeight { get; set; } + + internal int SuperResolutionDenominator { get; set; } + + internal int SuperResolutionUpscaledWidth { get; set; } + + internal int RenderWidth { get; set; } + + internal int RenderHeight { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs new file mode 100644 index 0000000000..eb2414edc7 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuFrameType +{ + KeyFrame = 0, + InterFrame = 1, + IntraOnlyFrame = 2, + SwitchFrame = 3, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs new file mode 100644 index 0000000000..f55d3eb501 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuHeader +{ + public int Size { get; set; } + + public ObuType Type { get; set; } + + public bool HasSize { get; set; } + + public bool HasExtension { get; set; } + + public int TemporalId { get; set; } + + public int SpatialId { get; set; } + + public int PayloadSize { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs new file mode 100644 index 0000000000..961f3b2bc6 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuLoopFilterParameters +{ + public bool[] FilterLevel { get; internal set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs new file mode 100644 index 0000000000..1c04072e3e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuLoopRestorationParameters +{ + public ObuRestorationType FrameRestorationType { get; internal set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs new file mode 100644 index 0000000000..e9658997fb --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuMatrixCoefficients +{ + Identity = 0, + Bt407 = 1, + Unspecified = 2, + Fcc = 4, + Bt470BG = 5, + Bt601 = 6, + Smpte240 = 7, + SmpteYCgCo = 8, + Bt2020NonConstantLuminance = 9, + Bt2020ConstantLuminance = 10, + Smpte2085 = 11, + ChromaticityDerivedNonConstantLuminance = 12, + ChromaticityDerivedConstandLuminance = 13, + Bt2100ICtCp = 14, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs new file mode 100644 index 0000000000..d6788f1745 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuMetadataType +{ + ItutT35, + HdrCll, + HdrMdcv, + Scalability, + Timecode +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs new file mode 100644 index 0000000000..a1401b1aa8 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuOperatingPoint +{ + internal int OperatorIndex { get; set; } + + internal int SequenceLevelIndex { get; set; } + + internal int SequenceTier { get; set; } + + internal bool IsDecoderModelPresent { get; set; } + + internal bool IsInitialDisplayDelayPresent { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs new file mode 100644 index 0000000000..4cdcb26550 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuOrderHintInfo +{ + internal bool EnableJointCompound { get; set; } + + internal bool EnableReferenceFrameMotionVectors { get; set; } + + internal int OrderHintBits { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs new file mode 100644 index 0000000000..53aa1c88bb --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuQuantizationParameters +{ + public int BaseQIndex { get; set; } + + public int[] QIndex { get; set; } = new int[ObuConstants.MaxSegmentCount]; + + public bool IsUsingQMatrix { get; internal set; } + + public int[] DeltaQDc { get; internal set; } = new int[3]; + + public int[] DeltaQAc { get; internal set; } = new int[3]; + + public int[] QMatrix { get; internal set; } = new int[3]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs new file mode 100644 index 0000000000..c1676febcb --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -0,0 +1,888 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuReader +{ + private static readonly int[] WienerTapsMid = new[] { 3, -7, 15 }; + private static readonly int[] SgrprojXqdMid = new[] { -32, 31 }; + + private static ObuHeader ReadObuHeader(Av1BitStreamReader reader) + { + ObuHeader header = new(); + if (!reader.ReadBoolean()) + { + throw new ImageFormatException("Forbidden bit in header should be unset."); + } + + header.Type = (ObuType)reader.ReadLiteral(4); + header.HasExtension = reader.ReadBoolean(); + header.HasSize = reader.ReadBoolean(); + if (!reader.ReadBoolean()) + { + throw new ImageFormatException("Reserved bit in header should be unset."); + } + + if (header.HasExtension) + { + header.Size++; + header.TemporalId = (int)reader.ReadLiteral(3); + header.SpatialId = (int)reader.ReadLiteral(3); + if (reader.ReadLiteral(3) != 0u) + { + throw new ImageFormatException("Reserved bits in header extension should be unset."); + } + } + else + { + header.SpatialId = 0; + header.TemporalId = 0; + } + + return header; + } + + private static void ReadObuSize(Av1BitStreamReader reader, out int obuSize) + { + ulong rawSize = reader.ReadLittleEndianBytes128(out _); + if (rawSize > uint.MaxValue) + { + throw new ImageFormatException("OBU block too large."); + } + + obuSize = (int)rawSize; + } + + /// + /// Read OBU header and size. + /// + private static ObuHeader ReadObuHeaderSize(Av1BitStreamReader reader) + { + ObuHeader header = ReadObuHeader(reader); + if (header.HasSize) + { + ReadObuSize(reader, out int payloadSize); + header.PayloadSize = payloadSize; + } + + return header; + } + + /// + /// Check that the trailing bits start with a 1 and end with 0s. + /// + /// Consumes a byte, if already byte aligned before the check. + private static void ReadTrailingBits(Av1BitStreamReader reader) + { + int bitsBeforeAlignment = 8 - (reader.BitPosition & 0x7); + uint trailing = reader.ReadLiteral(bitsBeforeAlignment); + if (trailing != (1 << (bitsBeforeAlignment - 1))) + { + throw new ImageFormatException("Trailing bits not properly formatted."); + } + } + + private static void AlignToByteBoundary(Av1BitStreamReader reader) + { + while ((reader.BitPosition & 0x7) > 0) + { + if (reader.ReadBoolean()) + { + throw new ImageFormatException("Incorrect byte alignment padding bits."); + } + } + } + + private static void ComputeImageSize(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + { + frameInfo.ModeInfoColumnCount = 2 * ((frameInfo.FrameSize.FrameWidth + 7) >> 3); + frameInfo.ModeInfoRowCount = 2 * ((frameInfo.FrameSize.FrameHeight + 7) >> 3); + frameInfo.ModeInfoStride = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, ObuConstants.MaxSuperBlockSizeLog2) >> ObuConstants.ModeInfoSizeLog2; + } + + private static bool IsValidObuType(ObuType type) => type switch + { + ObuType.SequenceHeader or ObuType.TemporalDelimiter or ObuType.FrameHeader or + ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrameHeader or + ObuType.TileList or ObuType.Padding => true, + _ => false, + }; + + private static ObuSequenceHeader ReadSequenceHeader(Av1BitStreamReader reader) + { + ObuSequenceHeader sequenceHeader = new(); + sequenceHeader.SequenceProfile = (ObuSequenceProfile)reader.ReadLiteral(3); + if (sequenceHeader.SequenceProfile > ObuConstants.MaxSequenceProfile) + { + throw new ImageFormatException("Unknown sequence profile."); + } + + sequenceHeader.IsStillPicture = reader.ReadBoolean(); + sequenceHeader.IsReducedStillPictureHeader = reader.ReadBoolean(); + if (!sequenceHeader.IsStillPicture || !sequenceHeader.IsReducedStillPictureHeader) + { + throw new ImageFormatException("Not a picture header, is this a movie file ??"); + } + + sequenceHeader.TimingInfo = null; + sequenceHeader.DecoderModelInfoPresentFlag = false; + sequenceHeader.InitialDisplayDelayPresentFlag = false; + sequenceHeader.OperatingPoint[0].OperatorIndex = 0; + sequenceHeader.OperatingPoint[0].SequenceLevelIndex = (int)reader.ReadLiteral(ObuConstants.LevelBits); + if (!IsValidSequenceLevel(sequenceHeader.OperatingPoint[0].SequenceLevelIndex)) + { + throw new ImageFormatException("Unknown sequnce level."); + } + + sequenceHeader.OperatingPoint[0].SequenceTier = 0; + sequenceHeader.OperatingPoint[0].IsDecoderModelPresent = false; + sequenceHeader.OperatingPoint[0].IsInitialDisplayDelayPresent = false; + + // Video related flags removed + + // SVT-TODO: int operatingPoint = this.ChooseOperatingPoint(); + // sequenceHeader.OperatingPointIndex = (int)operatingPointIndices[operatingPoint]; + sequenceHeader.FrameWidthBits = (int)reader.ReadLiteral(4) + 1; + sequenceHeader.FrameHeightBits = (int)reader.ReadLiteral(4) + 1; + sequenceHeader.MaxFrameWidth = (int)reader.ReadLiteral(sequenceHeader.FrameWidthBits) + 1; + sequenceHeader.MaxFrameHeight = (int)reader.ReadLiteral(sequenceHeader.FrameHeightBits) + 1; + sequenceHeader.IsFrameIdNumbersPresent = false; + + // Video related flags removed + sequenceHeader.Use128x128SuperBlock = reader.ReadBoolean(); + sequenceHeader.SuperBlockSize = sequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + sequenceHeader.ModeInfoSize = sequenceHeader.Use128x128SuperBlock ? 32 : 16; + sequenceHeader.SuperBlockSizeLog2 = sequenceHeader.Use128x128SuperBlock ? 7 : 6; + sequenceHeader.FilterIntraLevel = (int)reader.ReadLiteral(1); + sequenceHeader.EnableIntraEdgeFilter = reader.ReadBoolean(); + sequenceHeader.EnableInterIntraCompound = false; + sequenceHeader.EnableMaskedCompound = false; + sequenceHeader.EnableWarpedMotion = false; + sequenceHeader.EnableDualFilter = false; + sequenceHeader.OrderHintInfo.EnableJointCompound = false; + sequenceHeader.OrderHintInfo.EnableReferenceFrameMotionVectors = false; + sequenceHeader.SequenceForceScreenContentTools = 2; + sequenceHeader.SequenceForceIntegerMotionVector = 2; + sequenceHeader.OrderHintInfo.OrderHintBits = 0; + + // Video related flags removed + sequenceHeader.EnableSuperResolution = reader.ReadBoolean(); + sequenceHeader.CdefLevel = (int)reader.ReadLiteral(1); + sequenceHeader.EnableRestoration = reader.ReadBoolean(); + sequenceHeader.ColorConfig = ReadColorConfig(reader, sequenceHeader); + sequenceHeader.AreFilmGrainingParametersPresent = reader.ReadBoolean(); + ReadTrailingBits(reader); + return sequenceHeader; + } + + private static ObuColorConfig ReadColorConfig(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) + { + ObuColorConfig colorConfig = new(); + ReadBitDepth(reader, colorConfig, sequenceHeader); + colorConfig.Monochrome = false; + if (sequenceHeader.SequenceProfile == ObuSequenceProfile.High) + { + colorConfig.Monochrome = reader.ReadBoolean(); + } + + colorConfig.ChannelCount = colorConfig.Monochrome ? 1 : 3; + colorConfig.IsColorDescriptionPresent = reader.ReadBoolean(); + colorConfig.ColorPrimaries = ObuColorPrimaries.Unspecified; + colorConfig.TransferCharacteristics = ObuTransferCharacteristics.Unspecified; + colorConfig.MatrixCoefficients = ObuMatrixCoefficients.Unspecified; + if (colorConfig.IsColorDescriptionPresent) + { + colorConfig.ColorPrimaries = (ObuColorPrimaries)reader.ReadLiteral(8); + colorConfig.TransferCharacteristics = (ObuTransferCharacteristics)reader.ReadLiteral(8); + colorConfig.MatrixCoefficients = (ObuMatrixCoefficients)reader.ReadLiteral(8); + } + + colorConfig.ColorRange = false; + colorConfig.SubSamplingX = false; + colorConfig.SubSamplingY = false; + colorConfig.ChromaSamplePosition = ObuChromoSamplePosition.Unknown; + colorConfig.HasSeparateUvDelta = false; + if (colorConfig.Monochrome) + { + colorConfig.ColorRange = reader.ReadBoolean(); + colorConfig.SubSamplingX = true; + colorConfig.SubSamplingY = true; + return colorConfig; + } + else if ( + colorConfig.ColorPrimaries == ObuColorPrimaries.Bt709 && + colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb && + colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity) + { + colorConfig.ColorRange = true; + colorConfig.SubSamplingX = false; + colorConfig.SubSamplingY = false; + } + else + { + colorConfig.ColorRange = reader.ReadBoolean(); + if (sequenceHeader.SequenceProfile != ObuSequenceProfile.Main) + { + if (colorConfig.BitDepth == 12) + { + colorConfig.SubSamplingX = reader.ReadBoolean(); + if (colorConfig.SubSamplingX) + { + colorConfig.SubSamplingY = reader.ReadBoolean(); + } + } + else + { + colorConfig.SubSamplingX = true; + colorConfig.SubSamplingY = false; + } + } + + if (colorConfig.SubSamplingX && colorConfig.SubSamplingY) + { + colorConfig.ChromaSamplePosition = (ObuChromoSamplePosition)reader.ReadLiteral(2); + } + } + + colorConfig.HasSeparateUvDeltaQ = reader.ReadBoolean(); + return colorConfig; + } + + private static void ReadBitDepth(Av1BitStreamReader reader, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader) + { + bool hasHighBitDepth = reader.ReadBoolean(); + if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && hasHighBitDepth) + { + colorConfig.BitDepth = reader.ReadBoolean() ? 12 : 10; + } + else if (sequenceHeader.SequenceProfile <= ObuSequenceProfile.Professional) + { + colorConfig.BitDepth = hasHighBitDepth ? 10 : 8; + } + } + + private static void ReadSuperResolutionParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + { + bool useSuperResolution = false; + if (sequenceHeader.EnableSuperResolution) + { + useSuperResolution = reader.ReadBoolean(); + } + + if (useSuperResolution) + { + frameInfo.FrameSize.SuperResolutionDenominator = (int)reader.ReadLiteral(ObuConstants.SuperResolutionScaleBits) + ObuConstants.SuperResolutionScaleDenominatorMinimum; + } + else + { + frameInfo.FrameSize.SuperResolutionDenominator = ObuConstants.ScaleNumerator; + } + + frameInfo.FrameSize.SuperResolutionUpscaledWidth = frameInfo.FrameSize.FrameWidth; + frameInfo.FrameSize.FrameWidth = + (frameInfo.FrameSize.SuperResolutionUpscaledWidth * ObuConstants.ScaleNumerator) + + (frameInfo.FrameSize.SuperResolutionDenominator / 2); + + if (frameInfo.FrameSize.SuperResolutionDenominator != ObuConstants.ScaleNumerator) + { + int manWidth = Math.Min(16, frameInfo.FrameSize.SuperResolutionUpscaledWidth); + frameInfo.FrameSize.FrameWidth = Math.Max(manWidth, frameInfo.FrameSize.FrameWidth); + } + } + + private static void ReadRenderSize(Av1BitStreamReader reader, ObuFrameHeader frameInfo) + { + bool renderSizeAndFrameSizeDifferent = reader.ReadBoolean(); + if (renderSizeAndFrameSizeDifferent) + { + frameInfo.FrameSize.RenderWidth = (int)reader.ReadLiteral(16) + 1; + frameInfo.FrameSize.RenderHeight = (int)reader.ReadLiteral(16) + 1; + } + else + { + frameInfo.FrameSize.RenderWidth = frameInfo.FrameSize.SuperResolutionUpscaledWidth; + frameInfo.FrameSize.RenderHeight = frameInfo.FrameSize.FrameHeight; + } + } + + private static void ReadFrameSizeWithReferences(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) + { + bool foundReference = false; + for (int i = 0; i < ObuConstants.ReferencesPerFrame; i++) + { + foundReference = reader.ReadBoolean(); + if (foundReference) + { + // Take values over from reference frame + break; + } + } + + if (!foundReference) + { + ReadFrameSize(reader, sequenceHeader, frameInfo, frameSizeOverrideFlag); + ReadRenderSize(reader, frameInfo); + } + else + { + ReadSuperResolutionParameters(reader, sequenceHeader, frameInfo); + ComputeImageSize(sequenceHeader, frameInfo); + } + } + + private static void ReadFrameSize(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) + { + if (frameSizeOverrideFlag) + { + frameInfo.FrameSize.FrameWidth = (int)reader.ReadLiteral(sequenceHeader.FrameWidthBits) + 1; + frameInfo.FrameSize.FrameHeight = (int)reader.ReadLiteral(sequenceHeader.FrameHeightBits) + 1; + } + else + { + frameInfo.FrameSize.FrameWidth = sequenceHeader.MaxFrameWidth; + frameInfo.FrameSize.FrameHeight = sequenceHeader.MaxFrameHeight; + } + + ReadSuperResolutionParameters(reader, sequenceHeader, frameInfo); + ComputeImageSize(sequenceHeader, frameInfo); + } + + private static ObuTileInfo ReadTileInfo(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + { + ObuTileInfo tileInfo = new(); + int superBlockColumnCount; + int superBlockRowCount; + int superBlockShift; + if (sequenceHeader.Use128x128SuperBlock) + { + superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 31) >> 5; + superBlockRowCount = (frameInfo.ModeInfoRowCount + 31) >> 5; + superBlockShift = 5; + } + else + { + superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 15) >> 4; + superBlockRowCount = (frameInfo.ModeInfoRowCount + 15) >> 4; + superBlockShift = 4; + } + + int superBlockSize = superBlockShift + 2; + int maxTileAreaOfSuperBlock = ObuConstants.MaxTileArea >> (2 * superBlockSize); + + tileInfo.MaxTileWidthSuperBlock = ObuConstants.MaxTileWidth >> superBlockSize; + tileInfo.MaxTileHeightSuperBlock = (ObuConstants.MaxTileArea / ObuConstants.MaxTileWidth) >> superBlockSize; + tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperBlock, superBlockColumnCount); + tileInfo.MaxLog2TileColumnCount = TileLog2(1, Math.Min(superBlockColumnCount, ObuConstants.MaxTileColumnCount)); + tileInfo.MaxLog2TileRowCount = TileLog2(1, Math.Min(superBlockRowCount, ObuConstants.MaxTileRowCount)); + tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, TileLog2(maxTileAreaOfSuperBlock, superBlockColumnCount * superBlockRowCount)); + tileInfo.HasUniformTileSpacing = reader.ReadBoolean(); + if (tileInfo.HasUniformTileSpacing) + { + tileInfo.TileColumnCountLog2 = tileInfo.MinLog2TileColumnCount; + while (tileInfo.TileColumnCountLog2 < tileInfo.MaxLog2TileColumnCount) + { + if (reader.ReadBoolean()) + { + tileInfo.TileColumnCountLog2++; + } + else + { + break; + } + } + + int tileWidthSuperBlock = (superBlockColumnCount + (1 << tileInfo.TileColumnCountLog2) - 1) >> tileInfo.TileColumnCountLog2; + if (tileWidthSuperBlock > tileInfo.MaxTileWidthSuperBlock) + { + throw new ImageFormatException("Invalid tile width specified."); + } + + int i = 0; + tileInfo.TileColumnStartModeInfo = new int[superBlockColumnCount + 1]; + for (int startSuperBlock = 0; startSuperBlock < superBlockColumnCount; startSuperBlock += tileWidthSuperBlock) + { + tileInfo.TileColumnStartModeInfo[i] = startSuperBlock << superBlockShift; + i++; + } + + tileInfo.TileColumnStartModeInfo[i] = frameInfo.ModeInfoColumnCount; + tileInfo.TileColumnCount = i; + + tileInfo.MinLog2TileRowCount = Math.Max(tileInfo.MinLog2TileCount - tileInfo.TileColumnCountLog2, 0); + tileInfo.TileRowCountLog2 = tileInfo.MinLog2TileRowCount; + while (tileInfo.TileRowCountLog2 < tileInfo.MaxLog2TileRowCount) + { + if (reader.ReadBoolean()) + { + tileInfo.TileRowCountLog2++; + } + else + { + break; + } + } + + int tileHeightSuperBlock = (superBlockRowCount + (1 << tileInfo.TileRowCountLog2) - 1) >> tileInfo.TileRowCountLog2; + if (tileHeightSuperBlock > tileInfo.MaxTileHeightSuperBlock) + { + throw new ImageFormatException("Invalid tile height specified."); + } + + i = 0; + tileInfo.TileRowStartModeInfo = new int[superBlockRowCount + 1]; + for (int startSuperBlock = 0; startSuperBlock < superBlockRowCount; startSuperBlock += tileHeightSuperBlock) + { + tileInfo.TileRowStartModeInfo[i] = startSuperBlock << superBlockShift; + i++; + } + + tileInfo.TileRowStartModeInfo[i] = frameInfo.ModeInfoRowCount; + tileInfo.TileRowCount = i; + } + else + { + uint widestTileSuperBlock = 0U; + int startSuperBlock = 0; + int i = 0; + for (; startSuperBlock < superBlockColumnCount; i++) + { + tileInfo.TileColumnStartModeInfo[i] = startSuperBlock << superBlockShift; + uint maxWidth = (uint)Math.Min(superBlockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperBlock); + uint widthInSuperBlocks = reader.ReadNonSymmetric(maxWidth) + 1; + widestTileSuperBlock = Math.Max(widthInSuperBlocks, widestTileSuperBlock); + startSuperBlock += (int)widthInSuperBlocks; + } + + if (startSuperBlock != superBlockColumnCount) + { + throw new ImageFormatException("Super block tiles width does not add up to total width."); + } + + tileInfo.TileColumnStartModeInfo[i] = frameInfo.ModeInfoColumnCount; + tileInfo.TileColumnCount = i; + tileInfo.TileColumnCountLog2 = TileLog2(1, tileInfo.TileColumnCount); + if (tileInfo.MinLog2TileCount > 0) + { + maxTileAreaOfSuperBlock = (superBlockRowCount * superBlockColumnCount) >> (tileInfo.MinLog2TileCount + 1); + } + else + { + maxTileAreaOfSuperBlock = superBlockRowCount * superBlockColumnCount; + } + + DebugGuard.MustBeGreaterThan(widestTileSuperBlock, 0U, nameof(widestTileSuperBlock)); + tileInfo.MaxTileHeightSuperBlock = Math.Max(maxTileAreaOfSuperBlock / (int)widestTileSuperBlock, 1); + + startSuperBlock = 0; + for (i = 0; startSuperBlock < superBlockRowCount; i++) + { + tileInfo.TileRowStartModeInfo[i] = startSuperBlock << superBlockShift; + uint maxHeight = (uint)Math.Min(superBlockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperBlock); + uint heightInSuperBlocks = reader.ReadNonSymmetric(maxHeight) + 1; + startSuperBlock += (int)heightInSuperBlocks; + } + + if (startSuperBlock != superBlockRowCount) + { + throw new ImageFormatException("Super block tiles height does not add up to total height."); + } + + tileInfo.TileRowStartModeInfo[i] = frameInfo.ModeInfoRowCount; + tileInfo.TileRowCount = i; + tileInfo.TileRowCountLog2 = TileLog2(1, tileInfo.TileRowCount); + } + + if (tileInfo.TileColumnCount > ObuConstants.MaxTileColumnCount || tileInfo.TileRowCount > ObuConstants.MaxTileRowCount) + { + throw new ImageFormatException("Tile width or height too big."); + } + + if (tileInfo.TileColumnCountLog2 > 0 || tileInfo.TileRowCountLog2 > 0) + { + tileInfo.ContextUpdateTileId = reader.ReadLiteral(tileInfo.TileRowCountLog2 + tileInfo.TileColumnCountLog2); + tileInfo.TileSizeBytes = (int)reader.ReadLiteral(2) + 1; + } + else + { + tileInfo.ContextUpdateTileId = 0; + } + + if (tileInfo.ContextUpdateTileId >= (tileInfo.TileColumnCount * tileInfo.TileRowCount)) + { + throw new ImageFormatException("Context update Tile ID too large."); + } + + return tileInfo; + } + + private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHeader header, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + { + int idLength = 0; + uint previousFrameId = 0; + bool isIntraFrame = false; + bool frameSizeOverrideFlag = false; + if (sequenceHeader.IsFrameIdNumbersPresent) + { + idLength = sequenceHeader.FrameIdLength - 1 + sequenceHeader.DeltaFrameIdLength - 2 + 3; + DebugGuard.MustBeLessThanOrEqualTo(idLength, 16, nameof(idLength)); + } + + if (sequenceHeader.IsReducedStillPictureHeader) + { + frameInfo.ShowExistingFrame = false; + frameInfo.FrameType = ObuFrameType.KeyFrame; + isIntraFrame = true; + frameInfo.ShowFrame = true; + frameInfo.ShowableFrame = false; + frameInfo.ErrorResilientMode = true; + } + + if (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame) + { + frameInfo.ReferenceValid = new bool[ObuConstants.ReferenceFrameCount]; + frameInfo.ReferenceOrderHint = new bool[ObuConstants.ReferenceFrameCount]; + for (int i = 0; i < ObuConstants.ReferenceFrameCount; i++) + { + frameInfo.ReferenceValid[i] = false; + frameInfo.ReferenceOrderHint[i] = false; + } + } + + frameInfo.DisableCdfUpdate = reader.ReadBoolean(); + frameInfo.AllowScreenContentTools = sequenceHeader.SequenceForceScreenContentTools == 1; + if (frameInfo.AllowScreenContentTools) + { + frameInfo.AllowScreenContentTools = reader.ReadBoolean(); + } + + if (frameInfo.AllowScreenContentTools) + { + if (sequenceHeader.SequenceForceIntegerMotionVector == 1) + { + frameInfo.ForeceIntegerMotionVector = reader.ReadBoolean(); + } + else + { + frameInfo.ForceIntegerMotionVector = sequenceHeader.SequenceForceIntegerMotionVector != 0; + } + } + else + { + frameInfo.ForeceIntegerMotionVector = false; + } + + if (isIntraFrame) + { + frameInfo.ForeceIntegerMotionVector = true; + } + + bool havePreviousFrameId = !(frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame); + if (havePreviousFrameId) + { + previousFrameId = frameInfo.CurrentFrameId; + } + + if (sequenceHeader.IsFrameIdNumbersPresent) + { + frameInfo.CurrentFrameId = reader.ReadLiteral(idLength); + if (havePreviousFrameId) + { + uint diffFrameId = (frameInfo.CurrentFrameId > previousFrameId) ? + frameInfo.CurrentFrameId - previousFrameId : + (uint)((1 << idLength) + (int)frameInfo.CurrentFrameId - previousFrameId); + if (frameInfo.CurrentFrameId == previousFrameId || diffFrameId >= 1 << (idLength - 1)) + { + throw new ImageFormatException("Current frame ID cannot be same as previous Frame ID"); + } + } + + int diffLength = sequenceHeader.DeltaFrameIdLength; + for (int i = 0; i < ObuConstants.ReferenceFrameCount; i++) + { + if (frameInfo.CurrentFrameId > (1U << diffLength)) + { + if ((frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId) || + frameInfo.ReferenceFrameIndex[i] > (frameInfo.CurrentFrameId - (1 - diffLength))) + { + frameInfo.ReferenceValid[i] = false; + } + } + else if (frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId && + frameInfo.ReferenceFrameIndex[i] < ((1 << idLength) + (frameInfo.CurrentFrameId - (1 << diffLength)))) + { + frameInfo.ReferenceValid[i] = false; + } + } + } + else + { + frameInfo.CurrentFrameId = 0; + } + + if (frameInfo.FrameType == ObuFrameType.SwitchFrame) + { + frameSizeOverrideFlag = true; + } + else if (sequenceHeader.IsReducedStillPictureHeader) + { + frameSizeOverrideFlag = false; + } + else + { + frameSizeOverrideFlag = reader.ReadBoolean(); + } + + frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); + if (isIntraFrame || frameInfo.ErrorResilientMode) + { + frameInfo.PrimaryReferenceFrame = ObuConstants.PrimaryReferenceFrameNone; + } + else + { + frameInfo.PrimaryReferenceFrame = reader.ReadLiteral(ObuConstants.PimaryReferenceBits); + } + + // Skipping, as no decoder info model present + frameInfo.AllowHighPrecisionMotionVector = false; + frameInfo.UseReferenceFrameMotionVectors = false; + frameInfo.AllowIntraBlockCopy = false; + if (frameInfo.FrameType == ObuFrameType.SwitchFrame || (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame)) + { + frameInfo.RefreshFrameFlags = 0xffU; + } + else + { + frameInfo.RefreshFrameFlags = reader.ReadLiteral(8); + } + + if (frameInfo.FrameType == ObuFrameType.IntraOnlyFrame) + { + DebugGuard.IsTrue(frameInfo.RefreshFrameFlags != 0xFFU, nameof(frameInfo.RefreshFrameFlags)); + } + + if (!isIntraFrame || (frameInfo.RefreshFrameFlags != 0xFFU)) + { + if (frameInfo.ErrorResilientMode && sequenceHeader.OrderHintInfo != null) + { + for (int i = 0; i < ObuConstants.ReferenceFrameCount; i++) + { + int referenceOrderHint = (int)reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); + if (referenceOrderHint != (frameInfo.ReferenceOrderHint[i] ? 1U : 0U)) + { + frameInfo.ReferenceValid[i] = false; + } + } + } + } + + if (isIntraFrame) + { + ReadFrameSize(reader, sequenceHeader, frameInfo, frameSizeOverrideFlag); + ReadRenderSize(reader, frameInfo); + if (frameInfo.AllowScreenContentTools && frameInfo.FrameSize.RenderWidth != 0) + { + if (frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth) + { + frameInfo.AllowIntraBlockCopy = reader.ReadBoolean(); + } + } + } + else + { + // Single image is always Intra. + } + + SetupFrameBufferReferences(sequenceHeader, frameInfo); + CheckAddTemporalMotionVectorBuffer(sequenceHeader, frameInfo); + SetupFrameSignBias(sequenceHeader, frameInfo); + + if (sequenceHeader.IsReducedStillPictureHeader || frameInfo.DisableCdfUpdate) + { + frameInfo.DisableFrameEndUpdateCdf = true; + } + + if (frameInfo.PrimaryReferenceFrame == ObuConstants.PrimaryReferenceFrameNone) + { + SetupPastIndependence(); + } + + GenerateNextReferenceFrameMap(sequenceHeader, frameInfo); + + frameInfo.TilesInfo = ReadTileInfo(reader, sequenceHeader, frameInfo); + frameInfo.QuantizationParameters = ReadQuantizationParameters(reader, sequenceHeader.ColorConfig, planesCount); + ReadSegmentationParameters(reader, sequenceHeader, frameInfo); + ReadDeltaQParameters(reader, frameInfo); + ReadDeltaLoopFilterParameters(reader, frameInfo); + SetupSegmentationDequantization(); + + Av1MainParseContext mainParseContext = new(); + if (frameInfo.PrimaryReferenceFrame == ObuConstants.PrimaryReferenceFrameNone) + { + ResetParseContext(mainParseContext, frameInfo.QuantizationParameters.BaseQIndex); + } + + int tilesCount = frameInfo.TilesInfo.TileColumnCount * frameInfo.TilesInfo.TileRowCount; + frameInfo.CodedLossless = true; + for (int segmentId = 0; segmentId < ObuConstants.MaxSegmentCount; segmentId++) + { + int qIndex = GetQIndex(frameInfo.SegmentationParameters, segmentId, frameInfo.QuantizationParameters.BaseQIndex); + frameInfo.QuantizationParameters.QIndex[segmentId] = qIndex; + frameInfo.LosslessArray[segmentId] = qIndex == 0 && + frameInfo.QuantizationParameters.DeltaQDc[(int)Av1Plane.Y] == 0 && + frameInfo.QuantizationParameters.DeltaQAc[(int)Av1Plane.U] == 0 && + frameInfo.QuantizationParameters.DeltaQDc[(int)Av1Plane.U] == 0 && + frameInfo.QuantizationParameters.DeltaQAc[(int)Av1Plane.V] == 0 && + frameInfo.QuantizationParameters.DeltaQDc[(int)Av1Plane.V] == 0; + if (!frameInfo.LosslessArray[segmentId]) + { + frameInfo.CodedLossless = false; + } + + if (frameInfo.QuantizationParameters.IsUsingQMatrix) + { + if (frameInfo.LosslessArray[segmentId]) + { + frameInfo.SegmentationParameters.QMLevel[0, segmentId] = 15; + frameInfo.SegmentationParameters.QMLevel[1, segmentId] = 15; + frameInfo.SegmentationParameters.QMLevel[2, segmentId] = 15; + } + else + { + frameInfo.SegmentationParameters.QMLevel[0, segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.Y]; + frameInfo.SegmentationParameters.QMLevel[1, segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.U]; + frameInfo.SegmentationParameters.QMLevel[2, segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.V]; + } + } + } + + frameInfo.AllLossless = frameInfo.CodedLossless && frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth; + ReadLoopFilterParameters(reader, sequenceHeader, frameInfo, planesCount); + ReadConstraintDirectionalEnhancementFilterParameters(reader, sequenceHeader, frameInfo, planesCount); + ReadLoopRestorationParameter(reader, sequenceHeader, frameInfo, planesCount); + ReadTransformMode(reader, frameInfo); + + frameInfo.ReferenceMode = ReadFrameReferenceMode(reader, isIntraFrame); + ReadSkipModeParameters(reader, frameInfo, isIntraFrame, sequenceHeader, frameInfo.ReferenceMode); + if (isIntraFrame || frameInfo.ErrorResilientMode || !sequenceHeader.EnableWarpedMotion) + { + frameInfo.AllowWarpedMotion = false; + } + + frameInfo.ReducedTxSet = reader.ReadBoolean(); + ReadGlobalMotionParameters(reader, sequenceHeader, frameInfo, isIntraFrame); + frameInfo.FilmGrainParameters = ReadFilmGrainParameters(reader, sequenceHeader, frameInfo); + } + + private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) + => segmentationParameters.SegmentationEnabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; + + private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) + { + if (IsSegmentationFeatureActive(segmentationParameters, segmentId, ObuSegmentationLevelFeature.AlternativeQuantizer)) + { + int data = segmentationParameters.FeatureData[segmentId, (int)ObuSegmentationLevelFeature.AlternativeQuantizer]; + int qIndex = baseQIndex + data; + return Av1Math.Clamp(qIndex, 0, ObuConstants.MaxQ); + } + else + { + return baseQIndex; + } + } + + private static void ReadFrameHeader(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuHeader header, bool trailingBit) + { + int planeCount = sequenceHeader.ColorConfig.Monochrome ? 1 : 3; + int startBitPosition = reader.BitPosition; + ReadUncompressedFrameHeader(reader, header, sequenceHeader, frameInfo, planeCount); + if (trailingBit) + { + ReadTrailingBits(reader); + } + + AlignToByteBoundary(reader); + + int endPosition = reader.BitPosition; + int headerBytes = (endPosition - startBitPosition) / 8; + header.PayloadSize -= headerBytes; + } + + private static void ReadTileGroup(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo, ObuHeader header, out bool isLastTileGroup) + { + int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; + int startBitPosition = reader.BitPosition; + bool tileStartAndEndPresentFlag = false; + if (tileCount > 1) + { + tileStartAndEndPresentFlag = reader.ReadBoolean(); + } + + if (header.Type == ObuType.FrameHeader) + { + DebugGuard.IsFalse(tileStartAndEndPresentFlag, nameof(tileStartAndEndPresentFlag), "Frame header should not set 'tileStartAndEndPresentFlag'."); + } + + int tileGroupStart = 0; + int tileGroupEnd = tileCount - 1; + if (tileCount != 1 && tileStartAndEndPresentFlag) + { + int tileBits = Av1Math.Log2(tileInfo.TileColumnCount) + Av1Math.Log2(tileInfo.TileRowCount); + tileGroupStart = (int)reader.ReadLiteral(tileBits); + tileGroupEnd = (int)reader.ReadLiteral(tileBits); + } + + isLastTileGroup = (tileGroupEnd + 1) == tileCount; + AlignToByteBoundary(reader); + int endBitPosition = reader.BitPosition; + int headerBytes = (endBitPosition - startBitPosition) / 8; + header.PayloadSize -= headerBytes; + + bool noIbc = !frameInfo.AllowIntraBlockCopy; + bool doLoopFilter = noIbc && (frameInfo.LoopFilterParameters.FilterLevel[0] || frameInfo.LoopFilterParameters.FilterLevel[1]); + bool doCdef = noIbc && (!frameInfo.CodedLossless && + (frameInfo.ConstraintDirectionalEnhancementFilterParameters.BitCount || + frameInfo.ConstraintDirectionalEnhancementFilterParameters.YStrength[0] != 0 || + frameInfo.ConstraintDirectionalEnhancementFilterParameters.UVStrength[0] != 0)); + bool doLoopRestoration = noIbc && + (frameInfo.LoopRestorationParameters[(int)Av1Plane.Y].FrameRestorationType != ObuRestorationType.RestoreNone || + frameInfo.LoopRestorationParameters[(int)Av1Plane.U].FrameRestorationType != ObuRestorationType.RestoreNone || + frameInfo.LoopRestorationParameters[(int)Av1Plane.V].FrameRestorationType != ObuRestorationType.RestoreNone); + + for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++) + { + int tileRow = tileNum / tileInfo.TileColumnCount; + int tileColumn = tileNum % tileInfo.TileColumnCount; + bool isLastTile = tileNum == tileGroupEnd; + int tileSize = header.PayloadSize; + if (!isLastTile) + { + tileSize = (int)reader.ReadLittleEndian(tileInfo.TileSizeBytes) + 1; + header.PayloadSize -= tileSize + tileInfo.TileSizeBytes; + } + + // TODO: Pass more info to the decoder. + DecodeTile(sequenceHeader, frameInfo, tileInfo, tileNum); + } + + if (tileGroupEnd != tileCount - 1) + { + return; + } + + FinishDecodeTiles(sequenceHeader, frameInfo, doCdef, doLoopRestoration); + } + + private static bool IsValidSequenceLevel(int sequenceLevelIndex) + => sequenceLevelIndex is < 24 or 31; + + private static int TileLog2(int blockSize, int target) + { + int k; + for (k = 0; (blockSize << k) < target; k++) + { + } + + return k; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.cs new file mode 100644 index 0000000000..a76bc7601e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuReferenceMode +{ + ReferenceModeSelect, + SingleReference, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs new file mode 100644 index 0000000000..02ab7cd1e4 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuRestorationType +{ + RestoreNone +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs new file mode 100644 index 0000000000..93af8e0a1c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuSegmentationLevelFeature +{ + AlternativeQuantizer, + AlternativeLoopFilterYVertical, + AlternativeLoopFilterYHorizontal, + AlternativeLoopFilterU, + AlternativeLoopFilterV, + ReferenceFrame, + Skip, + GlobalMotionVector, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs new file mode 100644 index 0000000000..ed92abf4c4 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuSegmentationParameters +{ + public int[,] QMLevel { get; internal set; } = new int[3, ObuConstants.MaxSegmentCount]; + + public bool[,] FeatureEnabled { get; internal set; } = new bool[ObuConstants.MaxSegmentCount, ObuConstants.SegmentationLevelMax]; + + public bool SegmentationEnabled { get; internal set; } + + public int[,] FeatureData { get; internal set; } = new int[ObuConstants.MaxSegmentCount, ObuConstants.SegmentationLevelMax]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs new file mode 100644 index 0000000000..652734c01b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuSequenceHeader +{ + internal bool IsStillPicture { get; set; } + + internal bool IsReducedStillPictureHeader { get; set; } + + internal ObuSequenceProfile SequenceProfile { get; set; } + + internal ObuOperatingPoint[] OperatingPoint { get; set; } = new ObuOperatingPoint[1]; + + internal bool InitialDisplayDelayPresentFlag { get; set; } + + internal bool DecoderModelInfoPresentFlag { get; set; } + + internal object? TimingInfo { get; set; } + + internal bool IsFrameIdNumbersPresent { get; set; } + + internal int FrameWidthBits { get; set; } + + internal int FrameHeightBits { get; set; } + + internal int MaxFrameWidth { get; set; } + + internal int MaxFrameHeight { get; set; } + + internal bool Use128x128SuperBlock { get; set; } + + internal Av1BlockSize SuperBlockSize { get; set; } + + internal int ModeInfoSize { get; set; } + + internal int SuperBlockSizeLog2 { get; set; } + + internal int FilterIntraLevel { get; set; } + + internal bool EnableIntraEdgeFilter { get; set; } + + internal ObuOrderHintInfo OrderHintInfo { get; set; } = new ObuOrderHintInfo(); + + internal bool EnableInterIntraCompound { get; set; } + + internal bool EnableMaskedCompound { get; set; } + + internal bool EnableWarpedMotion { get; set; } + + internal bool EnableDualFilter { get; set; } + + internal int SequenceForceIntegerMotionVector { get; set; } + + internal int SequenceForceScreenContentTools { get; set; } + + internal bool EnableSuperResolution { get; set; } + + internal int CdefLevel { get; set; } + + internal bool EnableRestoration { get; set; } + + internal ObuColorConfig ColorConfig { get; set; } = new ObuColorConfig(); + + internal bool AreFilmGrainingParametersPresent { get; set; } + + internal int FrameIdLength { get; set; } + + internal int DeltaFrameIdLength { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs new file mode 100644 index 0000000000..f1ac85edb3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuSequenceProfile : uint +{ + Main = 0, + High = 1, + Professional = 2, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs new file mode 100644 index 0000000000..24c8f963ea --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuTileInfo +{ + internal int MaxTileWidthSuperBlock { get; set; } + + internal int MaxTileHeightSuperBlock { get; set; } + + internal int MinLog2TileColumnCount { get; set; } + + internal int MaxLog2TileColumnCount { get; set; } + + internal int MaxLog2TileRowCount { get; set; } + + internal int MinLog2TileCount { get; set; } + + public bool HasUniformTileSpacing { get; set; } + + internal int TileColumnCountLog2 { get; set; } + + internal int TileColumnCount { get; set; } + + internal int[] TileColumnStartModeInfo { get; set; } + + internal int MinLog2TileRowCount { get; set; } + + internal int TileRowCountLog2 { get; set; } + + internal int[] TileRowStartModeInfo { get; set; } + + internal int TileRowCount { get; set; } + + internal uint ContextUpdateTileId { get; set; } + + internal int TileSizeBytes { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs new file mode 100644 index 0000000000..0fa71e5922 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuTransferCharacteristics +{ + Bt709 = 1, + Unspecified = 2, + Bt470M = 4, + Bt470BG = 5, + Bt601 = 6, + Smpte240 = 7, + Linear = 8, + Log100 = 9, + Log100Sqrt10 = 10, + Iec61966 = 11, + Bt1361 = 12, + Srgb = 13, + Bt202010Bit = 14, + Bt202012Bit = 15, + Smpte2084 = 16, + Smpte248 = 17, + Hlg = 18, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuType.cs new file mode 100644 index 0000000000..34285ef2f0 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuType.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuType +{ + None = 0, + SequenceHeader = 1, + TemporalDelimiter = 2, + FrameHeader = 3, + RedundantFrameHeader = 7, + TileGroup = 4, + Metadata = 5, + Frame = 6, + TileList = 8, + Padding = 15, +}