From 0313bdf549dbd5f1b242fa1ca81a0dde6a2129c4 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 20 Jul 2024 17:48:36 +0200 Subject: [PATCH] Replace Av1BitStreamReader implementation --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 93 +++------- .../Formats/Heif/Av1/Av1BitStreamReader2.cs | 173 ------------------ src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 8 + .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 4 +- 4 files changed, 37 insertions(+), 241 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index c5bd837fd..f4a1af2b7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -7,70 +7,40 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal ref struct Av1BitStreamReader { - public const int WordSize = 1 << WordSizeLog2; - private const int WordSizeLog2 = 5; - private const int WordSizeInBytesLog2 = WordSizeLog2 - Log2Of8; - private const int Log2Of8 = 3; - - private readonly Span data; - private uint currentWord; - private uint nextWord; - private int wordPosition = 0; - private int bitOffset = 0; - - public Av1BitStreamReader(Span data) - { - this.data = MemoryMarshal.Cast(data); - this.wordPosition = -1; - this.AdvanceToNextWord(); - this.AdvanceToNextWord(); - } + private readonly Span data; + + public Av1BitStreamReader(Span data) => this.data = data; - public readonly int BitPosition => ((this.wordPosition - 1) * WordSize) + this.bitOffset; + public int BitPosition { get; private set; } = 0; /// /// Gets the number of bytes in the readers buffer. /// public readonly int Length => this.data.Length; - public void Reset() - { - this.wordPosition = 0; - this.bitOffset = 0; - } + public void Reset() => this.BitPosition = 0; - public void Skip(int bitCount) - { - this.bitOffset += bitCount; - while (this.bitOffset >= WordSize) - { - this.bitOffset -= WordSize; - this.wordPosition++; - } - } + public void Skip(int bitCount) => this.BitPosition += bitCount; public uint ReadLiteral(int bitCount) { - DebugGuard.MustBeBetweenOrEqualTo(bitCount, 1, 32, nameof(bitCount)); + DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount)); - uint bits = (this.currentWord << this.bitOffset) >> (WordSize - bitCount); - this.bitOffset += bitCount; - if (this.bitOffset > WordSize) + uint literal = 0; + for (int bit = bitCount - 1; bit >= 0; bit--) { - int overshoot = WordSize + WordSize - this.bitOffset; - if (overshoot < 32) - { - bits |= this.nextWord >> overshoot; - } + literal |= this.ReadBit() << bit; } - if (this.bitOffset >= WordSize) - { - this.AdvanceToNextWord(); - this.bitOffset -= WordSize; - } + return literal; + } - return bits; + internal uint ReadBit() + { + int byteOffset = Av1Math.DivideBy8Floor(this.BitPosition); + byte shift = (byte)(7 - Av1Math.Modulus8(this.BitPosition)); + this.BitPosition++; + return (uint)((this.data[byteOffset] >> shift) & 0x01); } internal bool ReadBoolean() => this.ReadLiteral(1) > 0; @@ -78,7 +48,7 @@ internal ref struct Av1BitStreamReader public ulong ReadLittleEndianBytes128(out int length) { // See section 4.10.5 of the AV1-Specification - DebugGuard.IsTrue((this.bitOffset & 0x07) == 0, $"Reading of Little Endian 128 value only allowed on byte alignment (offset {this.BitPosition})."); + DebugGuard.IsTrue((this.BitPosition & 0x07) == 0, $"Reading of Little Endian 128 value only allowed on byte alignment (offset {this.BitPosition})."); ulong value = 0; length = 0; @@ -100,7 +70,7 @@ internal ref struct Av1BitStreamReader { // See section 4.10.3 of the AV1-Specification int leadingZerosCount = 0; - while (leadingZerosCount < 32 && this.ReadLiteral(1) == 0U) + while (leadingZerosCount < 32 && !this.ReadBoolean()) { leadingZerosCount++; } @@ -156,7 +126,7 @@ internal ref struct Av1BitStreamReader 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"); + DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Reading of Little Endian value only allowed on byte alignment"); uint t = 0; for (int i = 0; i < 8 * n; i += 8) @@ -167,22 +137,13 @@ internal ref struct Av1BitStreamReader return t; } - public void AdvanceToNextWord() - { - this.currentWord = this.nextWord; - this.wordPosition++; - uint temp = this.data[this.wordPosition]; - this.nextWord = (temp << 24) | ((temp & 0x0000ff00U) << 8) | ((temp & 0x00ff0000U) >> 8) | (temp >> 24); - } - public Span GetSymbolReader(int tileDataSize) { - DebugGuard.IsTrue((this.bitOffset & 0x7) == 0, "Symbol reading needs to start on byte boundary."); - - // TODO: Pass exact byte iso Word start. - int spanLength = tileDataSize >> WordSizeInBytesLog2; - Span span = this.data.Slice(this.bitOffset >> WordSizeLog2, spanLength); - this.Skip(tileDataSize << Log2Of8); - return MemoryMarshal.Cast(span); + DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Symbol reading needs to start on byte boundary."); + int bytesRead = Av1Math.DivideBy8Floor(this.BitPosition); + int spanLength = tileDataSize - bytesRead; + Span span = this.data.Slice(bytesRead, spanLength); + this.Skip(tileDataSize << 3); + return span; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs deleted file mode 100644 index 2c0c36af4..000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1; - -internal ref struct Av1BitStreamReader2 -{ - public const int WordSize = 1 << WordSizeLog2; - private const int WordSizeLog2 = 5; - - private readonly Span data; - private int wordPosition = 0; - private int bitOffset = 0; - - public Av1BitStreamReader2(Span data) => this.data = data; - - public readonly int BitPosition => ((this.wordPosition - 1) * WordSize) + this.bitOffset; - - /// - /// Gets the number of bytes in the readers buffer. - /// - 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) - { - DebugGuard.MustBeBetweenOrEqualTo(bitCount, 1, 32, nameof(bitCount)); - - uint literal = 0; - for (int bit = bitCount - 1; bit >= 0; bit--) - { - literal |= this.ReadBit() << bit; - } - - return literal; - } - - internal uint ReadBit() - { - int byteOffset = DivideBy8(this.bitOffset, false); - byte shift = (byte)(7 - Mod8(this.bitOffset)); - this.bitOffset++; - return (uint)((this.data[byteOffset] >> shift) & 0x01); - } - - internal bool ReadBoolean() => this.ReadLiteral(1) > 0; - - public ulong ReadLittleEndianBytes128(out int length) - { - // See section 4.10.5 of the AV1-Specification - DebugGuard.IsTrue((this.bitOffset & 0x07) == 0, $"Reading of Little Endian 128 value only allowed on byte alignment (offset {this.BitPosition})."); - - 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) == 0) - { - 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.FloorLog2(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; - } - - public Span GetSymbolReader(int tileDataSize) - { - DebugGuard.IsTrue((this.bitOffset & 0x7) == 0, "Symbol reading needs to start on byte boundary."); - - throw new NotImplementedException("GetSymbolReader is not implemented yet / needs to be reviewd again"); - - // TODO: Pass exact byte iso Word start. - // TODO: This needs to be reviewed again, due to the change in how ReadLiteral() works now! - /*int spanLength = tileDataSize >> WordSizeInBytesLog2; - Span span = this.data.Slice(this.bitOffset >> WordSizeLog2, spanLength); - this.Skip(tileDataSize << Log2Of8); - return MemoryMarshal.Cast(span);*/ - } - - // Last 3 bits are the value of mod 8. - internal static int Mod8(int n) => n & 0x07; - - internal static int DivideBy8(int n, bool ceil) => (n + (ceil ? 7 : 0)) >> 3; -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index 43e571703..936186a3e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -142,6 +142,14 @@ internal static class Av1Math internal static int Clamp(int value, int low, int high) => value < low ? low : (value > high ? high : value); + internal static int DivideLog2Floor(int value, int n) + => value >> n; + internal static int DivideLog2Ceiling(int value, int n) => (value + (1 << n) - 1) >> n; + + // Last 3 bits are the value of mod 8. + internal static int Modulus8(int value) => value & 0x07; + + internal static int DivideBy8Floor(int value) => value >> 3; } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index a965857d1..51533ba6f 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -33,8 +33,8 @@ public class ObuFrameHeaderTests Assert.NotNull(obuReader.SequenceHeader); Assert.NotNull(obuReader.FrameHeader); Assert.NotNull(obuReader.FrameHeader.TilesInfo); - Assert.Equal(reader.Length * Av1BitStreamReader.WordSize, reader.BitPosition); - Assert.Equal(reader.Length * 4, blockSize); + Assert.Equal(reader.Length * 8, reader.BitPosition); + Assert.Equal(reader.Length, blockSize); } /*