Browse Source

Additional unit tests for OBU bitstream

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
984d0ac0c4
  1. 7
      src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs
  2. 2
      src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
  3. 6
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
  4. 44
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
  5. 2
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs
  6. 44
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ScanOrderTests.cs
  7. 226
      tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs

7
src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs

@ -27,7 +27,12 @@ internal ref struct Av1BitStreamWriter(Stream stream)
public void Flush()
{
this.stream.WriteByte(this.buffer);
if (Av1Math.Modulus8(this.bitOffset) != 0)
{
// Flush a partial byte also.
this.stream.WriteByte(this.buffer);
}
this.bitOffset = 0;
}

2
src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs

@ -21,7 +21,7 @@ internal class Av1Decoder : IAv1TileDecoder
public void Decode(Span<byte> buffer)
{
Av1BitStreamReader reader = new(buffer);
this.obuReader.Read(ref reader, buffer.Length, this, false);
this.obuReader.ReadAll(ref reader, buffer.Length, this, false);
this.frameBuffer = this.tileDecoder?.FrameBuffer;
}

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

@ -45,7 +45,7 @@ internal class ObuReader
/// <summary>
/// Decode all OBU's in a frame.
/// </summary>
public void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB = false)
public void ReadAll(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB = false)
{
bool seenFrameHeader = false;
bool frameDecodingFinished = false;
@ -258,7 +258,7 @@ internal class ObuReader
/// <summary>
/// 5.5.1. General sequence header OBU syntax.
/// </summary>
private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
internal static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
{
sequenceHeader.SequenceProfile = (ObuSequenceProfile)reader.ReadLiteral(3);
if (sequenceHeader.SequenceProfile > Av1Constants.MaxSequenceProfile)
@ -1212,7 +1212,7 @@ internal class ObuReader
/// <summary>
/// 5.9.1. General frame header OBU syntax.
/// </summary>
private void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit)
internal void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit)
{
int planeCount = this.SequenceHeader!.ColorConfig.PlaneCount;
int startBitPosition = reader.BitPosition;

44
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs

@ -10,25 +10,37 @@ internal class ObuWriter
/// <summary>
/// Encode a single frame into OBU's.
/// </summary>
public static void Write(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo)
public static void WriteAll(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo)
{
MemoryStream bufferStream = new(100);
Av1BitStreamWriter writer = new(bufferStream);
WriteObuHeaderAndSize(stream, ObuType.TemporalDelimiter, [], 0);
WriteSequenceHeader(ref writer, sequenceHeader);
writer.Flush();
WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, bufferStream.GetBuffer(), (int)bufferStream.Position);
if (sequenceHeader != null)
{
WriteSequenceHeader(ref writer, sequenceHeader);
int bytesWritten = (writer.BitPosition + 7) >> 3;
writer.Flush();
WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, bufferStream.GetBuffer(), bytesWritten);
}
bufferStream.Position = 0;
WriteFrameHeader(ref writer, sequenceHeader, frameInfo, true);
writer.Flush();
WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), (int)bufferStream.Position);
if (frameInfo != null && sequenceHeader != null)
{
bufferStream.Position = 0;
WriteFrameHeader(ref writer, sequenceHeader, frameInfo, true);
int bytesWritten = (writer.BitPosition + 7) >> 3;
writer.Flush();
WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), bytesWritten);
}
bufferStream.Position = 0;
WriteTileGroup(ref writer, frameInfo.TilesInfo);
writer.Flush();
WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), (int)bufferStream.Position);
if (frameInfo?.TilesInfo != null)
{
bufferStream.Position = 0;
WriteTileGroup(ref writer, frameInfo.TilesInfo);
int bytesWritten = (writer.BitPosition + 7) >> 3;
writer.Flush();
WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), bytesWritten);
}
}
private static void WriteObuHeader(ref Av1BitStreamWriter writer, ObuType type)
@ -58,7 +70,10 @@ internal class ObuWriter
private static void WriteTrailingBits(ref Av1BitStreamWriter writer)
{
int bitsBeforeAlignment = 8 - (writer.BitPosition & 0x7);
writer.WriteLiteral(0, bitsBeforeAlignment);
if (bitsBeforeAlignment != 8)
{
writer.WriteLiteral(1U << (bitsBeforeAlignment - 1), bitsBeforeAlignment);
}
}
private static void AlignToByteBoundary(ref Av1BitStreamWriter writer)
@ -101,11 +116,12 @@ internal class ObuWriter
writer.WriteBoolean(sequenceHeader.EnableRestoration);
WriteColorConfig(ref writer, sequenceHeader);
writer.WriteBoolean(sequenceHeader.AreFilmGrainingParametersPresent);
WriteTrailingBits(ref writer);
}
private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader)
{
ObuColorConfig colorConfig = new();
ObuColorConfig colorConfig = sequenceHeader.ColorConfig;
WriteBitDepth(ref writer, colorConfig, sequenceHeader);
if (sequenceHeader.SequenceProfile != ObuSequenceProfile.High)
{

2
src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs

@ -40,7 +40,7 @@ internal enum Av1TransformType : byte
/// <summary>
/// Number of Transform types.
/// </summary>
TransformTypes,
AllTransformTypes,
/// <summary>
/// Invalid value.

44
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ScanOrderTests.cs

@ -0,0 +1,44 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
[Trait("Format", "Avif")]
public class Av1ScanOrderTests
{
[Theory]
[MemberData(nameof(GetCombinations))]
internal void AllIndicesScannedExactlyOnce(int s, int t)
{
// Assign
HashSet<short> visitedScans = [];
Av1TransformSize transformSize = (Av1TransformSize)s;
Av1TransformType transformType = (Av1TransformType)t;
// Act
Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType);
// Assert
foreach (short scan in scanOrder.Scan)
{
Assert.False(visitedScans.Contains(scan), $"Scan {scan} already visited before.");
visitedScans.Add(scan);
}
}
public static TheoryData<int, int> GetCombinations()
{
TheoryData<int, int> combinations = [];
for (int s = 0; s < (int)Av1TransformSize.AllSizes; s++)
{
for (int t = 0; t < (int)Av1TransformType.AllTransformTypes; t++)
{
combinations.Add(s, t);
}
}
return combinations;
}
}

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

@ -1,9 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Collections;
using System.Reflection;
using System.Text;
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
@ -13,7 +10,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
public class ObuFrameHeaderTests
{
private static readonly byte[] DefaultSequenceHeaderBitStream =
[0x0a, 0x0b, 0x00, 0x00, 0x00, 0x04, 0x3e, 0xa7, 0xbd, 0xf7, 0xf9, 0x80, 0x40];
[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];
// 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, 0x0102, 0x000D)]
@ -30,7 +36,7 @@ public class ObuFrameHeaderTests
ObuReader obuReader = new();
// Act
obuReader.Read(ref reader, blockSize, decoder);
obuReader.ReadAll(ref reader, blockSize, decoder);
// Assert
Assert.NotNull(obuReader.SequenceHeader);
@ -54,7 +60,7 @@ public class ObuFrameHeaderTests
ObuReader obuReader = new();
// Act 1
obuReader.Read(ref reader, blockSize, tileDecoder);
obuReader.ReadAll(ref reader, blockSize, tileDecoder);
// Assign 2
MemoryStream encoded = new();
@ -81,13 +87,13 @@ public class ObuFrameHeaderTests
ObuReader obuReader1 = new();
// Act 1
obuReader1.Read(ref reader, blockSize, tileDecoder);
obuReader1.ReadAll(ref reader, blockSize, tileDecoder);
// Assign 2
MemoryStream encoded = new();
// Act 2
ObuWriter.Write(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader);
ObuWriter.WriteAll(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader);
// Assign 2
Span<byte> encodedBuffer = encoded.ToArray();
@ -96,7 +102,7 @@ public class ObuFrameHeaderTests
ObuReader obuReader2 = new();
// Act 2
obuReader2.Read(ref reader2, encodedBuffer.Length, tileDecoder2);
obuReader2.ReadAll(ref reader2, encodedBuffer.Length, tileDecoder2);
// Assert
Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(obuReader1.SequenceHeader.ColorConfig), ObuPrettyPrint.PrettyPrintProperties(obuReader2.SequenceHeader.ColorConfig));
@ -106,16 +112,15 @@ public class ObuFrameHeaderTests
}
[Fact]
public void DefaultTemporalDelimiter()
public void ReadTemporalDelimiter()
{
// Arrange
byte[] bitStream = [0x12, 0x00];
Av1BitStreamReader reader = new(bitStream);
Av1BitStreamReader reader = new(DefaultTemporalDelimiterBitStream);
ObuReader obuReader = new();
IAv1TileDecoder tileDecoder = new Av1TileDecoderStub();
// Act
obuReader.Read(ref reader, bitStream.Length, tileDecoder);
obuReader.ReadAll(ref reader, DefaultTemporalDelimiterBitStream.Length, tileDecoder);
// Assert
Assert.Null(obuReader.SequenceHeader);
@ -123,27 +128,16 @@ public class ObuFrameHeaderTests
}
[Fact]
public void DefaultTemporalDelimiterWithExtension()
public void ReadHeaderWithoutSizeField()
{
// Bits Syntax element Value
// 1 obu_forbidden_bit 0
// 4 obu_type 2 (OBU_TEMPORAL_DELIMITER)
// 1 obu_extension_flag 1
// 1 obu_has_size_field 1
// 1 obu_reserved_1bit 0
// 3 temporal_id 6
// 2 spatial_id 2
// 3 extension_header_reserved_3bits 0
// 8 obu_size 0
// Arrange
byte[] bitStream = [0x16, 0xd0, 0x00];
byte[] bitStream = [0x10];
Av1BitStreamReader reader = new(bitStream);
ObuReader obuReader = new();
IAv1TileDecoder tileDecoder = new Av1TileDecoderStub();
// Act
obuReader.Read(ref reader, bitStream.Length, tileDecoder);
obuReader.ReadAll(ref reader, bitStream.Length, tileDecoder);
// Assert
Assert.Null(obuReader.SequenceHeader);
@ -151,109 +145,119 @@ public class ObuFrameHeaderTests
}
[Fact]
public void DefaultHeaderWithoutSizeField()
public void ReadSequenceHeader()
{
// Arrange
byte[] bitStream = [0x10];
byte[] bitStream = DefaultSequenceHeaderBitStream;
Av1BitStreamReader reader = new(bitStream);
ObuReader obuReader = new();
IAv1TileDecoder tileDecoder = new Av1TileDecoderStub();
ObuSequenceHeader expected = GetDefaultSequenceHeader();
// Act
obuReader.Read(ref reader, bitStream.Length, tileDecoder);
obuReader.ReadAll(ref reader, bitStream.Length, tileDecoder);
// Assert
Assert.Null(obuReader.SequenceHeader);
Assert.NotNull(obuReader.SequenceHeader);
Assert.Null(obuReader.FrameHeader);
Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(expected), ObuPrettyPrint.PrettyPrintProperties(obuReader.SequenceHeader));
}
[Fact]
public void DefaultSequenceHeader()
public void WriteTemporalDelimiter()
{
// Offset Bits Syntax element Value
// 0 3 seq_profile 0
// 3 1 still_picture 0
// 4 1 reduced_still_picture_header 0
// 5 1 timing_info_present_flag 0
// 6 1 initial_display_delay_present_flag 0
// 7 5 operating_points_cnt_minus_1 0
// 12 12 operating_point_idc[ 0 ] 0
// 24 5 seq_level_idx[ 0 ] 0
// 29 4 frame_width_bits_minus_1 8
// 33 4 frame_height_bits_minus_1 7
// 37 9 max_frame_width_minus_1 425
// 46 8 max_frame_height_minus_1 239
// 54 1 frame_id_numbers_present_flag 0
// 55 1 use_128x128_superblock 1
// 56 1 enable_filter_intra 1
// 57 1 enable_intra_edge_filter 1
// 58 1 enable_interintra_compound 1
// 59 1 enable_masked_compound 1
// 60 1 enable_warped_motion 0
// 61 1 enable_dual_filter 1
// 62 1 enable_order_hint 1
// 63 1 enable_jnt_comp 1
// 64 1 enable_ref_frame_mvs 1
// 65 1 seq_choose_screen_content_tools 1
// 66 1 seq_choose_integer_mv 1
// 67 3 order_hint_bits_minus_1 6
// 70 1 enable_superres 0
// 71 1 enable_cdef 1
// 72 1 enable_restoration 1
// ...
// Arrange
using MemoryStream stream = new(2);
// Act
ObuWriter.WriteAll(stream, null, null);
byte[] actual = stream.GetBuffer();
// Assert
Assert.Equal(DefaultTemporalDelimiterBitStream, actual);
}
[Fact]
public void WriteSequenceHeader()
{
// Arrange
byte[] bitStream = DefaultSequenceHeaderBitStream;
Av1BitStreamReader reader = new(bitStream);
ObuReader obuReader = new();
IAv1TileDecoder tileDecoder = new Av1TileDecoderStub();
ObuSequenceHeader expected = new()
{
SequenceProfile = 0,
IsStillPicture = false,
IsReducedStillPictureHeader = false,
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 = true,
EnableMaskedCompound = true,
EnableWarpedMotion = false,
EnableDualFilter = true,
EnableOrderHint = true,
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 = true,
SubSamplingY = true,
BitDepth = 8,
}
};
using MemoryStream stream = new(10);
ObuSequenceHeader input = GetDefaultSequenceHeader();
// Act
obuReader.Read(ref reader, bitStream.Length, tileDecoder);
ObuWriter.WriteAll(stream, input, null);
byte[] buffer = stream.GetBuffer();
// Assert
Assert.NotNull(obuReader.SequenceHeader);
Assert.Null(obuReader.FrameHeader);
Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(expected), ObuPrettyPrint.PrettyPrintProperties(obuReader.SequenceHeader));
// Skip over Temporal Delimiter header.
byte[] actual = buffer.AsSpan()[DefaultTemporalDelimiterBitStream.Length..].ToArray();
Assert.Equal(DefaultSequenceHeaderBitStream, actual);
}
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 = 8,
HasSeparateUvDelta = true,
ColorRange = true,
},
AreFilmGrainingParametersPresent = true,
};
}

Loading…
Cancel
Save