Browse Source

ObuHeader round trips

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
ef572ac98b
  1. 26
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs
  2. 6
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
  3. 68
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs
  4. 67
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
  5. 2
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs
  6. 30
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs
  7. 90
      tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs

26
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs

@ -5,31 +5,31 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuColorConfig
{
internal bool IsColorDescriptionPresent { get; set; }
public bool IsColorDescriptionPresent { get; set; }
internal int ChannelCount { get; set; }
public int ChannelCount { get; set; }
internal bool Monochrome { get; set; }
public bool Monochrome { get; set; }
internal ObuColorPrimaries ColorPrimaries { get; set; }
public ObuColorPrimaries ColorPrimaries { get; set; }
internal ObuTransferCharacteristics TransferCharacteristics { get; set; }
public ObuTransferCharacteristics TransferCharacteristics { get; set; }
internal ObuMatrixCoefficients MatrixCoefficients { get; set; }
public ObuMatrixCoefficients MatrixCoefficients { get; set; }
internal bool ColorRange { get; set; }
public bool ColorRange { get; set; }
internal bool SubSamplingX { get; set; }
public bool SubSamplingX { get; set; }
internal bool SubSamplingY { get; set; }
public bool SubSamplingY { get; set; }
internal bool HasSeparateUvDelta { get; set; }
public bool HasSeparateUvDelta { get; set; }
internal ObuChromoSamplePosition ChromaSamplePosition { get; set; }
public ObuChromoSamplePosition ChromaSamplePosition { get; set; }
internal int BitDepth { get; set; }
public int BitDepth { get; set; }
internal bool HasSeparateUvDeltaQ { get; set; }
public bool HasSeparateUvDeltaQ { get; set; }
public Av1ColorFormat GetColorFormat()
{

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

@ -372,6 +372,10 @@ internal class ObuReader
{
colorConfig.BitDepth = hasHighBitDepth ? 10 : 8;
}
else
{
colorConfig.BitDepth = 8;
}
}
private static void ReadSuperResolutionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo)
@ -1282,7 +1286,7 @@ internal class ObuReader
private static bool IsValidSequenceLevel(int sequenceLevelIndex)
=> sequenceLevelIndex is < 24 or 31;
private static int TileLog2(int blockSize, int target)
public static int TileLog2(int blockSize, int target)
{
int k;
for (k = 0; (blockSize << k) < target; k++)

68
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs

@ -5,71 +5,71 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuSequenceHeader
{
internal bool EnableFilterIntra { get; set; }
public bool EnableFilterIntra { get; set; }
internal bool EnableCdef { get; set; }
public bool EnableCdef { get; set; }
internal bool IsStillPicture { get; set; }
public bool IsStillPicture { get; set; }
internal bool IsReducedStillPictureHeader { get; set; }
public bool IsReducedStillPictureHeader { get; set; }
internal ObuSequenceProfile SequenceProfile { get; set; }
public ObuSequenceProfile SequenceProfile { get; set; }
internal ObuOperatingPoint[] OperatingPoint { get; set; } = new ObuOperatingPoint[1];
public ObuOperatingPoint[] OperatingPoint { get; set; } = new ObuOperatingPoint[1];
internal bool InitialDisplayDelayPresentFlag { get; set; }
public bool InitialDisplayDelayPresentFlag { get; set; }
internal bool DecoderModelInfoPresentFlag { get; set; }
public bool DecoderModelInfoPresentFlag { get; set; }
internal object? TimingInfo { get; set; }
public object? TimingInfo { get; set; }
internal bool IsFrameIdNumbersPresent { get; set; }
public bool IsFrameIdNumbersPresent { get; set; }
internal int FrameWidthBits { get; set; }
public int FrameWidthBits { get; set; }
internal int FrameHeightBits { get; set; }
public int FrameHeightBits { get; set; }
internal int MaxFrameWidth { get; set; }
public int MaxFrameWidth { get; set; }
internal int MaxFrameHeight { get; set; }
public int MaxFrameHeight { get; set; }
internal bool Use128x128SuperBlock { get; set; }
public bool Use128x128SuperBlock { get; set; }
internal Av1BlockSize SuperBlockSize { get; set; }
public Av1BlockSize SuperBlockSize { get; set; }
internal int ModeInfoSize { get; set; }
public int ModeInfoSize { get; set; }
internal int SuperBlockSizeLog2 { get; set; }
public int SuperBlockSizeLog2 { get; set; }
internal int FilterIntraLevel { get; set; }
public int FilterIntraLevel { get; set; }
internal bool EnableIntraEdgeFilter { get; set; }
public bool EnableIntraEdgeFilter { get; set; }
internal ObuOrderHintInfo OrderHintInfo { get; set; } = new ObuOrderHintInfo();
public ObuOrderHintInfo OrderHintInfo { get; set; } = new ObuOrderHintInfo();
internal bool EnableInterIntraCompound { get; set; }
public bool EnableInterIntraCompound { get; set; }
internal bool EnableMaskedCompound { get; set; }
public bool EnableMaskedCompound { get; set; }
internal bool EnableWarpedMotion { get; set; }
public bool EnableWarpedMotion { get; set; }
internal bool EnableDualFilter { get; set; }
public bool EnableDualFilter { get; set; }
internal int SequenceForceIntegerMotionVector { get; set; }
public int SequenceForceIntegerMotionVector { get; set; }
internal int SequenceForceScreenContentTools { get; set; }
public int SequenceForceScreenContentTools { get; set; }
internal bool EnableSuperResolution { get; set; }
public bool EnableSuperResolution { get; set; }
internal int CdefLevel { get; set; }
public int CdefLevel { get; set; }
internal bool EnableRestoration { get; set; }
public bool EnableRestoration { get; set; }
internal ObuColorConfig ColorConfig { get; set; } = new ObuColorConfig();
public ObuColorConfig ColorConfig { get; set; } = new ObuColorConfig();
internal bool AreFilmGrainingParametersPresent { get; set; }
public bool AreFilmGrainingParametersPresent { get; set; }
internal int FrameIdLength { get; set; }
public int FrameIdLength { get; set; }
internal int DeltaFrameIdLength { get; set; }
public int DeltaFrameIdLength { get; set; }
}

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

@ -14,15 +14,20 @@ internal class ObuWriter
{
MemoryStream bufferStream = new(100);
Av1BitStreamWriter writer = new(bufferStream);
WriteObuHeaderAndSize(stream, ObuType.TemporalDelimiter, [], 0);
WriteSequenceHeader(ref writer, decoder.SequenceHeader);
writer.Flush();
WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, bufferStream.GetBuffer(), (int)bufferStream.Position);
bufferStream.Position = 0;
WriteFrameHeader(ref writer, decoder, true);
writer.Flush();
WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), (int)bufferStream.Position);
bufferStream.Position = 0;
WriteTileGroup(ref writer, decoder.TileInfo);
writer.Flush();
WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), (int)bufferStream.Position);
}
@ -42,7 +47,7 @@ internal class ObuWriter
{
Av1BitStreamWriter writer = new(stream);
WriteObuHeader(ref writer, type);
_ = writer.WriteLittleEndianBytes128(length);
writer.WriteLittleEndianBytes128((uint)length);
stream.Write(payload, 0, length);
}
@ -96,7 +101,6 @@ 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)
@ -214,9 +218,33 @@ internal class ObuWriter
WriteSuperResolutionParameters(ref writer, sequenceHeader, frameInfo);
}
private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuTileInfo tileInfo)
private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo)
{
Guard.IsTrue(tileInfo.HasUniformTileSpacing, nameof(tileInfo.HasUniformTileSpacing), "NON uniform_tile_spacing_flag not supported yet.");
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 = ObuReader.TileLog2(tileInfo.MaxTileWidthSuperBlock, superBlockColumnCount);
tileInfo.MaxLog2TileColumnCount = ObuReader.TileLog2(1, Math.Min(superBlockColumnCount, ObuConstants.MaxTileColumnCount));
tileInfo.MaxLog2TileRowCount = ObuReader.TileLog2(1, Math.Min(superBlockRowCount, ObuConstants.MaxTileRowCount));
tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, ObuReader.TileLog2(maxTileAreaOfSuperBlock, superBlockColumnCount * superBlockRowCount));
writer.WriteBoolean(tileInfo.HasUniformTileSpacing);
if (tileInfo.HasUniformTileSpacing)
@ -243,7 +271,34 @@ internal class ObuWriter
}
else
{
throw new NotImplementedException("NON uniform_tile_spacing_flag not supported yet.");
int startSuperBlock = 0;
int i = 0;
for (; startSuperBlock < superBlockColumnCount; i++)
{
uint widthInSuperBlocks = (uint)((tileInfo.TileColumnStartModeInfo[i] >> superBlockShift) - startSuperBlock);
uint maxWidth = (uint)Math.Min(superBlockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperBlock);
writer.WriteNonSymmetric(widthInSuperBlocks - 1, maxWidth);
startSuperBlock += (int)widthInSuperBlocks;
}
if (startSuperBlock != superBlockColumnCount)
{
throw new ImageFormatException("Super block tiles width does not add up to total width.");
}
startSuperBlock = 0;
for (i = 0; startSuperBlock < superBlockRowCount; i++)
{
uint heightInSuperBlocks = (uint)((tileInfo.TileRowStartModeInfo[i] >> superBlockShift) - startSuperBlock);
uint maxHeight = (uint)Math.Min(superBlockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperBlock);
writer.WriteNonSymmetric(heightInSuperBlocks - 1, maxHeight);
startSuperBlock += (int)heightInSuperBlocks;
}
if (startSuperBlock != superBlockRowCount)
{
throw new ImageFormatException("Super block tiles height does not add up to total height.");
}
}
if (tileInfo.TileColumnCountLog2 > 0 || tileInfo.TileRowCountLog2 > 0)
@ -346,7 +401,7 @@ internal class ObuWriter
}
// GenerateNextReferenceFrameMap(sequenceHeader, frameInfo);
WriteTileInfo(ref writer, frameInfo.TilesInfo);
WriteTileInfo(ref writer, sequenceHeader, frameInfo, frameInfo.TilesInfo);
WriteQuantizationParameters(ref writer, frameInfo.QuantizationParameters, sequenceHeader.ColorConfig, planesCount);
WriteSegmentationParameters(ref writer, sequenceHeader, frameInfo, planesCount);
WriteFrameDeltaQParameters(ref writer, frameInfo);

2
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs

@ -237,7 +237,7 @@ public class Av1BitsStreamTests
}
[Theory]
[InlineData(4, 6, 4, 9, 14)]
[InlineData(4, 6, 7, 9, 14)]
[InlineData(8, 42, 8, 189, 63)]
[InlineData(8, 52, 18, 255, 241)]
[InlineData(16, 4050, 16003, 503, 8414)]

30
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs

@ -0,0 +1,30 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
internal class Av1TileDecoderStub : IAv1TileDecoder
{
public bool SequenceHeaderDone { get; set; }
public bool ShowExistingFrame { get; set; }
public bool SeenFrameHeader { get; set; }
public ObuFrameHeader FrameInfo { get; } = new ObuFrameHeader();
public ObuSequenceHeader SequenceHeader { get; } = new ObuSequenceHeader();
public ObuTileInfo TileInfo { get; } = new ObuTileInfo();
public void DecodeTile(int tileNum)
{
}
public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration)
{
}
}

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

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Reflection;
using System.Text;
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
@ -10,10 +12,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
public class ObuFrameHeaderTests
{
[Theory]
// [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000D, false)]
// [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6BD1, false)]
[InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, false)]
public void ReadFrameHeader(string filename, int fileOffset, int blockSize, bool isAnnexB)
// [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000D)]
// [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6BD1)]
[InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)]
public void ReadFrameHeader(string filename, int fileOffset, int blockSize)
{
// Assign
string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename);
@ -29,4 +31,84 @@ public class ObuFrameHeaderTests
Assert.False(decoder.SeenFrameHeader);
}
/* [Theory]
// [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)]
// public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, int blockSize)
// {
// // Assign
// string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename);
// byte[] content = File.ReadAllBytes(filePath);
// Span<byte> span = content.AsSpan(fileOffset, blockSize);
// IAv1TileDecoder tileDecoder = new Av1TileDecoderStub();
// Av1BitStreamReader reader = new(span);
// // Act 1
// ObuReader.Read(ref reader, blockSize, tileDecoder);
// // Assign 2
// MemoryStream encoded = new();
// // Act 2
// ObuWriter.Write(encoded, tileDecoder);
// // Assert
// Assert.Equal(span, encoded.ToArray());
//}
*/
[Theory]
[InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)]
public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int blockSize)
{
// Assign
string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename);
byte[] content = File.ReadAllBytes(filePath);
Span<byte> span = content.AsSpan(fileOffset, blockSize);
IAv1TileDecoder tileDecoder = new Av1TileDecoderStub();
Av1BitStreamReader reader = new(span);
// Act 1
ObuReader.Read(ref reader, blockSize, tileDecoder);
// Assign 2
MemoryStream encoded = new();
// Act 2
ObuWriter.Write(encoded, tileDecoder);
// Assign 2
Span<byte> encodedBuffer = encoded.ToArray();
IAv1TileDecoder tileDecoder2 = new Av1TileDecoderStub();
Av1BitStreamReader reader2 = new(span);
// Act 2
ObuReader.Read(ref reader2, encodedBuffer.Length, tileDecoder2);
// Assert
Assert.Equal(PrettyPrintProperties(tileDecoder.SequenceHeader.ColorConfig), PrettyPrintProperties(tileDecoder2.SequenceHeader.ColorConfig));
Assert.Equal(PrettyPrintProperties(tileDecoder.SequenceHeader), PrettyPrintProperties(tileDecoder2.SequenceHeader));
Assert.Equal(PrettyPrintProperties(tileDecoder.FrameInfo), PrettyPrintProperties(tileDecoder2.FrameInfo));
Assert.Equal(PrettyPrintProperties(tileDecoder.TileInfo), PrettyPrintProperties(tileDecoder2.TileInfo));
}
private static string PrettyPrintProperties(object obj)
{
StringBuilder builder = new();
builder.Append(obj.GetType().Name);
builder.AppendLine("{");
MemberInfo[] properties = obj.GetType().FindMembers(MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public, null, null);
foreach (MemberInfo member in properties)
{
if (member is PropertyInfo property)
{
builder.Append(property.Name);
builder.Append(" = ");
object value = property.GetValue(obj) ?? "NULL";
builder.AppendLine(value.ToString());
}
}
builder.AppendLine("}");
return builder.ToString();
}
}

Loading…
Cancel
Save