Browse Source

Replace Av1BitStreamReader implementation

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
0313bdf549
  1. 93
      src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs
  2. 173
      src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs
  3. 8
      src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
  4. 4
      tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs

93
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<uint> data;
private uint currentWord;
private uint nextWord;
private int wordPosition = 0;
private int bitOffset = 0;
public Av1BitStreamReader(Span<byte> data)
{
this.data = MemoryMarshal.Cast<byte, uint>(data);
this.wordPosition = -1;
this.AdvanceToNextWord();
this.AdvanceToNextWord();
}
private readonly Span<byte> data;
public Av1BitStreamReader(Span<byte> data) => this.data = data;
public readonly int BitPosition => ((this.wordPosition - 1) * WordSize) + this.bitOffset;
public int BitPosition { get; private set; } = 0;
/// <summary>
/// Gets the number of bytes in the readers buffer.
/// </summary>
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<byte> 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<uint> span = this.data.Slice(this.bitOffset >> WordSizeLog2, spanLength);
this.Skip(tileDataSize << Log2Of8);
return MemoryMarshal.Cast<uint, byte>(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<byte> span = this.data.Slice(bytesRead, spanLength);
this.Skip(tileDataSize << 3);
return span;
}
}

173
src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs

@ -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<byte> data;
private int wordPosition = 0;
private int bitOffset = 0;
public Av1BitStreamReader2(Span<byte> data) => this.data = data;
public readonly int BitPosition => ((this.wordPosition - 1) * WordSize) + this.bitOffset;
/// <summary>
/// Gets the number of bytes in the readers buffer.
/// </summary>
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<byte> 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<uint> span = this.data.Slice(this.bitOffset >> WordSizeLog2, spanLength);
this.Skip(tileDataSize << Log2Of8);
return MemoryMarshal.Cast<uint, byte>(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;
}

8
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;
}

4
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);
}
/*

Loading…
Cancel
Save