Browse Source

ObuWriter improvements

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
ab2ae29dc9
  1. 21
      src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs
  2. 19
      src/ImageSharp/Formats/Heif/Av1/IAv1TileWriter.cs
  3. 308
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
  4. 8
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs
  5. 11
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs
  6. 92
      tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs

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

@ -113,4 +113,25 @@ internal ref struct Av1BitStreamWriter(Stream stream)
this.bitOffset = 0; this.bitOffset = 0;
} }
} }
public void WriteLittleEndian(uint value, int n)
{
// See section 4.10.4 of the AV1-Specification
DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Writing of Little Endian value only allowed on byte alignment");
uint t = value;
for (int i = 0; i < n; i++)
{
this.WriteLiteral(t & 0xff, 8);
t >>= 8;
}
}
internal void WriteBlob(Span<byte> tileData)
{
DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Writing of Tile Data only allowed on byte alignment");
this.stream.Write(tileData);
this.bitOffset += tileData.Length << 3;
}
} }

19
src/ImageSharp/Formats/Heif/Av1/IAv1TileWriter.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Interface for writing of image tiles.
/// </summary>
internal interface IAv1TileWriter
{
/// <summary>
/// Write the information for a single tile.
/// </summary>
/// <param name="tileNum">The index of the tile that is to be read.</param>
/// <returns>
/// The bytes of encoded data in the bitstream dedicated to this tile.
/// </returns>
Span<byte> WriteTile(int tileNum);
}

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

@ -1,16 +1,20 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuWriter internal class ObuWriter
{ {
private int[] previousQIndex = [];
private int[] previousDeltaLoopFilter = [];
/// <summary> /// <summary>
/// Encode a single frame into OBU's. /// Encode a single frame into OBU's.
/// </summary> /// </summary>
public static void WriteAll(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) public void WriteAll(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, IAv1TileWriter tileWriter)
{ {
MemoryStream bufferStream = new(100); MemoryStream bufferStream = new(100);
Av1BitStreamWriter writer = new(bufferStream); Av1BitStreamWriter writer = new(bufferStream);
@ -27,19 +31,15 @@ internal class ObuWriter
if (frameInfo != null && sequenceHeader != null) if (frameInfo != null && sequenceHeader != null)
{ {
bufferStream.Position = 0; bufferStream.Position = 0;
WriteFrameHeader(ref writer, sequenceHeader, frameInfo, true); this.WriteFrameHeader(ref writer, sequenceHeader, frameInfo, true);
int bytesWritten = (writer.BitPosition + 7) >> 3; if (frameInfo.TilesInfo != null)
writer.Flush(); {
WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), bytesWritten); WriteTileGroup(ref writer, frameInfo.TilesInfo, tileWriter);
} }
if (frameInfo?.TilesInfo != null) int bytesWritten = 5; // (writer.BitPosition + 7) >> 3;
{
bufferStream.Position = 0;
WriteTileGroup(ref writer, frameInfo.TilesInfo);
int bytesWritten = (writer.BitPosition + 7) >> 3;
writer.Flush(); writer.Flush();
WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), bytesWritten); WriteObuHeaderAndSize(stream, ObuType.Frame, bufferStream.GetBuffer(), bytesWritten);
} }
} }
@ -234,53 +234,53 @@ internal class ObuWriter
WriteSuperResolutionParameters(ref writer, sequenceHeader, frameInfo); WriteSuperResolutionParameters(ref writer, sequenceHeader, frameInfo);
} }
private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileGroupHeader tileInfo) private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo)
{ {
int superBlockColumnCount; ObuTileGroupHeader tileInfo = frameInfo.TilesInfo;
int superBlockRowCount; int superblockColumnCount;
int superBlockShift; int superblockRowCount;
if (sequenceHeader.Use128x128Superblock) int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2;
{ int superblockShift = superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2;
superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 31) >> 5; superblockColumnCount = (frameInfo.ModeInfoColumnCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift;
superBlockRowCount = (frameInfo.ModeInfoRowCount + 31) >> 5; superblockRowCount = (frameInfo.ModeInfoRowCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift;
superBlockShift = 5; int superBlockSize = superblockShift + 2;
}
else
{
superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 15) >> 4;
superBlockRowCount = (frameInfo.ModeInfoRowCount + 15) >> 4;
superBlockShift = 4;
}
int superBlockSize = superBlockShift + 2;
int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (2 * superBlockSize); int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (2 * superBlockSize);
tileInfo.MaxTileWidthSuperblock = Av1Constants.MaxTileWidth >> superBlockSize; tileInfo.MaxTileWidthSuperblock = Av1Constants.MaxTileWidth >> superBlockSize;
tileInfo.MaxTileHeightSuperblock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superBlockSize; tileInfo.MaxTileHeightSuperblock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superBlockSize;
tileInfo.MinLog2TileColumnCount = ObuReader.TileLog2(tileInfo.MaxTileWidthSuperblock, superBlockColumnCount); tileInfo.MinLog2TileColumnCount = ObuReader.TileLog2(tileInfo.MaxTileWidthSuperblock, superblockColumnCount);
tileInfo.MaxLog2TileColumnCount = ObuReader.TileLog2(1, Math.Min(superBlockColumnCount, Av1Constants.MaxTileColumnCount)); tileInfo.MaxLog2TileColumnCount = ObuReader.TileLog2(1, Math.Min(superblockColumnCount, Av1Constants.MaxTileColumnCount));
tileInfo.MaxLog2TileRowCount = ObuReader.TileLog2(1, Math.Min(superBlockRowCount, Av1Constants.MaxTileRowCount)); tileInfo.MaxLog2TileRowCount = ObuReader.TileLog2(1, Math.Min(superblockRowCount, Av1Constants.MaxTileRowCount));
tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, ObuReader.TileLog2(maxTileAreaOfSuperBlock, superBlockColumnCount * superBlockRowCount)); tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, ObuReader.TileLog2(maxTileAreaOfSuperBlock, superblockColumnCount * superblockRowCount));
int log2TileColumnCount = Av1Math.Log2(tileInfo.TileColumnCount);
int log2TileRowCount = Av1Math.Log2(tileInfo.TileRowCount);
writer.WriteBoolean(tileInfo.HasUniformTileSpacing); writer.WriteBoolean(tileInfo.HasUniformTileSpacing);
if (tileInfo.HasUniformTileSpacing) if (tileInfo.HasUniformTileSpacing)
{ {
for (int i = 0; i < tileInfo.TileColumnCountLog2; i++) // Uniform spaced tiles with power-of-two number of rows and columns
// tile columns
int ones = log2TileColumnCount - tileInfo.MinLog2TileColumnCount;
while (ones-- > 0)
{ {
writer.WriteBoolean(true); writer.WriteBoolean(true);
} }
if (tileInfo.TileColumnCountLog2 < tileInfo.MaxLog2TileColumnCount) if (log2TileColumnCount < tileInfo.MaxLog2TileColumnCount)
{ {
writer.WriteBoolean(false); writer.WriteBoolean(false);
} }
for (int i = 0; i < tileInfo.TileRowCountLog2; i++) // rows
tileInfo.MinLog2TileRowCount = Math.Min(tileInfo.MinLog2TileCount - log2TileColumnCount, 0);
ones = log2TileRowCount - tileInfo.MinLog2TileRowCount;
while (ones-- > 0)
{ {
writer.WriteBoolean(true); writer.WriteBoolean(true);
} }
if (tileInfo.TileRowCountLog2 < tileInfo.MaxLog2TileRowCount) if (log2TileRowCount < tileInfo.MaxLog2TileRowCount)
{ {
writer.WriteBoolean(false); writer.WriteBoolean(false);
} }
@ -289,29 +289,29 @@ internal class ObuWriter
{ {
int startSuperBlock = 0; int startSuperBlock = 0;
int i = 0; int i = 0;
for (; startSuperBlock < superBlockColumnCount; i++) for (; startSuperBlock < superblockColumnCount; i++)
{ {
uint widthInSuperBlocks = (uint)((tileInfo.TileColumnStartModeInfo[i] >> superBlockShift) - startSuperBlock); uint widthInSuperBlocks = (uint)((tileInfo.TileColumnStartModeInfo[i] >> superblockShift) - startSuperBlock);
uint maxWidth = (uint)Math.Min(superBlockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperblock); uint maxWidth = (uint)Math.Min(superblockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperblock);
writer.WriteNonSymmetric(widthInSuperBlocks - 1, maxWidth); writer.WriteNonSymmetric(widthInSuperBlocks - 1, maxWidth);
startSuperBlock += (int)widthInSuperBlocks; startSuperBlock += (int)widthInSuperBlocks;
} }
if (startSuperBlock != superBlockColumnCount) if (startSuperBlock != superblockColumnCount)
{ {
throw new ImageFormatException("Super block tiles width does not add up to total width."); throw new ImageFormatException("Super block tiles width does not add up to total width.");
} }
startSuperBlock = 0; startSuperBlock = 0;
for (i = 0; startSuperBlock < superBlockRowCount; i++) for (i = 0; startSuperBlock < superblockRowCount; i++)
{ {
uint heightInSuperBlocks = (uint)((tileInfo.TileRowStartModeInfo[i] >> superBlockShift) - startSuperBlock); uint heightInSuperBlocks = (uint)((tileInfo.TileRowStartModeInfo[i] >> superblockShift) - startSuperBlock);
uint maxHeight = (uint)Math.Min(superBlockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperblock); uint maxHeight = (uint)Math.Min(superblockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperblock);
writer.WriteNonSymmetric(heightInSuperBlocks - 1, maxHeight); writer.WriteNonSymmetric(heightInSuperBlocks - 1, maxHeight);
startSuperBlock += (int)heightInSuperBlocks; startSuperBlock += (int)heightInSuperBlocks;
} }
if (startSuperBlock != superBlockRowCount) if (startSuperBlock != superblockRowCount)
{ {
throw new ImageFormatException("Super block tiles height does not add up to total height."); throw new ImageFormatException("Super block tiles height does not add up to total height.");
} }
@ -322,122 +322,163 @@ internal class ObuWriter
writer.WriteLiteral(tileInfo.ContextUpdateTileId, tileInfo.TileRowCountLog2 + tileInfo.TileColumnCountLog2); writer.WriteLiteral(tileInfo.ContextUpdateTileId, tileInfo.TileRowCountLog2 + tileInfo.TileColumnCountLog2);
writer.WriteLiteral((uint)tileInfo.TileSizeBytes - 1, 2); writer.WriteLiteral((uint)tileInfo.TileSizeBytes - 1, 2);
} }
frameInfo.TilesInfo = tileInfo;
} }
private static void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
uint previousFrameId = 0; // TODO: Make tile count configurable.
bool isIntraFrame = true; int tileCount = 1;
int idLength = sequenceHeader.FrameIdLength - 1 + sequenceHeader.DeltaFrameIdLength - 2 + 3; int planesCount = sequenceHeader.ColorConfig.PlaneCount;
writer.WriteBoolean(frameInfo.DisableCdfUpdate); writer.WriteBoolean(frameHeader.DisableCdfUpdate);
if (frameInfo.AllowScreenContentTools) if (sequenceHeader.ForceScreenContentTools == 2)
{ {
writer.WriteBoolean(frameInfo.AllowScreenContentTools); writer.WriteBoolean(frameHeader.AllowScreenContentTools);
}
else
{
// Guard.IsTrue(frameInfo.AllowScreenContentTools == sequenceHeader.ForceScreenContentTools);
} }
if (frameInfo.AllowScreenContentTools) if (frameHeader.AllowScreenContentTools)
{ {
if (sequenceHeader.ForceIntegerMotionVector == 1) if (sequenceHeader.ForceIntegerMotionVector == 2)
{ {
writer.WriteBoolean(frameInfo.ForceIntegerMotionVector); writer.WriteBoolean(frameHeader.ForceIntegerMotionVector);
}
else
{
// Guard.IsTrue(frameInfo.ForceIntegerMotionVector == sequenceHeader.ForceIntegerMotionVector, nameof(frameInfo.ForceIntegerMotionVector), "Frame and sequence must be in sync");
} }
} }
bool havePreviousFrameId = !(frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame); if (frameHeader.FrameType == ObuFrameType.KeyFrame)
if (havePreviousFrameId) {
if (!frameHeader.ShowFrame)
{
throw new NotImplementedException("No support for hidden frames.");
}
}
else if (frameHeader.FrameType == ObuFrameType.IntraOnlyFrame)
{ {
previousFrameId = frameInfo.CurrentFrameId; throw new NotImplementedException("No IntraOnly frames supported.");
} }
if (sequenceHeader.IsFrameIdNumbersPresent) if (frameHeader.FrameType == ObuFrameType.KeyFrame)
{ {
writer.WriteLiteral(frameInfo.CurrentFrameId, idLength); WriteFrameSize(ref writer, sequenceHeader, frameHeader, false);
if (havePreviousFrameId) if (frameHeader.AllowScreenContentTools)
{ {
uint diffFrameId = (frameInfo.CurrentFrameId > previousFrameId) ? writer.WriteBoolean(frameHeader.AllowIntraBlockCopy);
frameInfo.CurrentFrameId - previousFrameId : }
(uint)((1 << idLength) + (int)frameInfo.CurrentFrameId - previousFrameId); }
if (frameInfo.CurrentFrameId == previousFrameId || diffFrameId >= 1 << (idLength - 1)) else if (frameHeader.FrameType == ObuFrameType.IntraOnlyFrame)
{ {
throw new ImageFormatException("Current frame ID cannot be same as previous Frame ID"); WriteFrameSize(ref writer, sequenceHeader, frameHeader, false);
} if (frameHeader.AllowScreenContentTools)
{
writer.WriteBoolean(frameHeader.AllowIntraBlockCopy);
} }
}
else
{
throw new NotImplementedException("Inter frames not applicable for AVIF.");
}
int diffLength = sequenceHeader.DeltaFrameIdLength; WriteTileInfo(ref writer, sequenceHeader, frameHeader);
for (int i = 0; i < Av1Constants.ReferenceFrameCount; i++) WriteQuantizationParameters(ref writer, frameHeader.QuantizationParameters, sequenceHeader.ColorConfig, planesCount);
WriteSegmentationParameters(ref writer, sequenceHeader, frameHeader, planesCount);
if (frameHeader.QuantizationParameters.BaseQIndex > 0)
{
writer.WriteBoolean(frameHeader.DeltaQParameters.IsPresent);
if (frameHeader.DeltaQParameters.IsPresent)
{ {
if (frameInfo.CurrentFrameId > (1U << diffLength)) writer.WriteLiteral((uint)frameHeader.DeltaQParameters.Resolution - 1, 2);
for (int tileIndex = 0; tileIndex < tileCount; tileIndex++)
{ {
if ((frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId) || this.previousQIndex[tileIndex] = frameHeader.QuantizationParameters.BaseQIndex;
frameInfo.ReferenceFrameIndex[i] > (frameInfo.CurrentFrameId - (1 - diffLength))) }
{
frameInfo.ReferenceValid[i] = false; if (frameHeader.AllowIntraBlockCopy)
} {
Guard.IsFalse(
frameHeader.DeltaLoopFilterParameters.IsPresent,
nameof(frameHeader.DeltaLoopFilterParameters.IsPresent),
"Allow INTRA block copy required Loop Filter.");
} }
else if (frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId && else
frameInfo.ReferenceFrameIndex[i] < ((1 << idLength) + (frameInfo.CurrentFrameId - (1 << diffLength))))
{ {
frameInfo.ReferenceValid[i] = false; writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsPresent);
} }
}
}
writer.WriteLiteral(frameInfo.OrderHint, sequenceHeader.OrderHintInfo.OrderHintBits);
if (!isIntraFrame && !frameInfo.ErrorResilientMode) if (frameHeader.DeltaLoopFilterParameters.IsPresent)
{ {
writer.WriteLiteral(frameInfo.PrimaryReferenceFrame, Av1Constants.PimaryReferenceBits); writer.WriteLiteral((uint)(1 + Av1Math.MostSignificantBit((uint)frameHeader.DeltaLoopFilterParameters.Resolution) - 1), 2);
writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti);
int frameLoopFilterCount = sequenceHeader.ColorConfig.IsMonochrome ? Av1Constants.FrameLoopFilterCount - 2 : Av1Constants.FrameLoopFilterCount;
for (int loopFilterId = 0; loopFilterId < frameLoopFilterCount; loopFilterId++)
{
this.previousDeltaLoopFilter[loopFilterId] = 0;
}
}
}
} }
// Skipping, as no decoder info model present if (frameHeader.AllLossless)
frameInfo.AllowHighPrecisionMotionVector = false;
frameInfo.UseReferenceFrameMotionVectors = false;
frameInfo.AllowIntraBlockCopy = false;
if (frameInfo.FrameType != ObuFrameType.SwitchFrame && !(frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame))
{ {
writer.WriteLiteral(frameInfo.RefreshFrameFlags, 8); throw new NotImplementedException("No entire lossless supported.");
} }
else
if (isIntraFrame)
{ {
WriteFrameSize(ref writer, sequenceHeader, frameInfo, false); if (!frameHeader.CodedLossless)
WriteRenderSize(ref writer, frameInfo);
if (frameInfo.AllowScreenContentTools && frameInfo.FrameSize.RenderWidth != 0)
{ {
if (frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth) WriteLoopFilterParameters(ref writer, sequenceHeader, frameHeader, planesCount);
if (sequenceHeader.CdefLevel > 0)
{ {
writer.WriteBoolean(frameInfo.AllowIntraBlockCopy); WriteCdefParameters(ref writer, sequenceHeader, frameHeader, planesCount);
} }
} }
if (sequenceHeader.EnableRestoration)
{
WriteLoopRestorationParameters(ref writer, sequenceHeader, frameHeader, planesCount);
}
} }
if (frameInfo.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) writer.WriteBoolean(frameHeader.TransformMode == Av1TransformMode.Select);
// No compound INTER-INTER for AVIF.
if (frameHeader.SkipModeParameters.SkipModeAllowed)
{ {
SetupPastIndependence(frameInfo); writer.WriteBoolean(frameHeader.SkipModeParameters.SkipModeFlag);
} }
// GenerateNextReferenceFrameMap(sequenceHeader, frameInfo); if (FrameMightAllowWarpedMotion(sequenceHeader, frameHeader))
WriteTileInfo(ref writer, sequenceHeader, frameInfo, frameInfo.TilesInfo); {
WriteQuantizationParameters(ref writer, frameInfo.QuantizationParameters, sequenceHeader.ColorConfig, planesCount); writer.WriteBoolean(frameHeader.AllowWarpedMotion);
WriteSegmentationParameters(ref writer, sequenceHeader, frameInfo, planesCount); }
WriteFrameDeltaQParameters(ref writer, frameInfo); else
WriteFrameDeltaLoopFilterParameters(ref writer, frameInfo); {
Guard.IsFalse(frameHeader.AllowWarpedMotion, nameof(frameHeader.AllowWarpedMotion), "No warped motion allowed.");
WriteLoopFilterParameters(ref writer, sequenceHeader, frameInfo, planesCount); }
WriteCdefParameters(ref writer, sequenceHeader, frameInfo, planesCount);
WriteLoopRestorationParameters(ref writer, sequenceHeader, frameInfo, planesCount);
WriteTransformMode(ref writer, frameInfo);
// Not applicable for INTRA frames. writer.WriteBoolean(frameHeader.UseReducedTransformSet);
// WriteFrameReferenceMode(ref writer, frameInfo.ReferenceMode, isIntraFrame);
// WriteSkipModeParameters(ref writer, sequenceHeader, frameInfo, isIntraFrame, frameInfo.ReferenceMode);
writer.WriteBoolean(frameInfo.UseReducedTransformSet);
// Not applicable for INTRA frames. // No global motion for AVIF.
// WriteGlobalMotionParameters(ref writer, sequenceHeader, frameInfo, isIntraFrame); if (sequenceHeader.AreFilmGrainingParametersPresent && (frameHeader.ShowFrame || frameHeader.ShowableFrame))
WriteFilmGrainFilterParameters(ref writer, frameInfo.FilmGrainParameters); {
WriteFilmGrainFilterParameters(ref writer, frameHeader.FilmGrainParameters);
}
} }
private static bool IsSuperResolutionUnscaled(ObuFrameSize frameSize)
=> frameSize.FrameWidth == frameSize.SuperResolutionUpscaledWidth;
private static bool FrameMightAllowWarpedMotion(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
=> false; // !frameHeader.ErrorResilientMode && !FrameIsIntraOnly(sequenceHeader) && scs->enable_warped_motion;
private static void SetupPastIndependence(ObuFrameHeader frameInfo) private static void SetupPastIndependence(ObuFrameHeader frameInfo)
{ {
// TODO: Initialize the loop filter parameters. // TODO: Initialize the loop filter parameters.
@ -460,24 +501,21 @@ internal class ObuWriter
} }
} }
private static int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool writeTrailingBits) private int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool writeTrailingBits)
{ {
int planeCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : 3;
int startBitPosition = writer.BitPosition; int startBitPosition = writer.BitPosition;
WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameInfo, planeCount); this.WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameInfo);
if (writeTrailingBits) if (writeTrailingBits)
{ {
WriteTrailingBits(ref writer); WriteTrailingBits(ref writer);
} }
AlignToByteBoundary(ref writer);
int endPosition = writer.BitPosition; int endPosition = writer.BitPosition;
int headerBytes = (endPosition - startBitPosition) / 8; int headerBytes = (endPosition - startBitPosition) / 8;
return headerBytes; return headerBytes;
} }
private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo) private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter)
{ {
int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
int startBitPosition = writer.BitPosition; int startBitPosition = writer.BitPosition;
@ -494,11 +532,29 @@ internal class ObuWriter
} }
AlignToByteBoundary(ref writer); AlignToByteBoundary(ref writer);
WriteTileData(ref writer, tileInfo, tileWriter);
int endBitPosition = writer.BitPosition; int endBitPosition = writer.BitPosition;
int headerBytes = (endBitPosition - startBitPosition) / 8; int headerBytes = (endBitPosition - startBitPosition) / 8;
return headerBytes; return headerBytes;
} }
private static void WriteTileData(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter)
{
int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
for (int tileNum = 0; tileNum < tileCount; tileNum++)
{
Span<byte> tileData = tileWriter.WriteTile(tileNum);
if (tileNum != tileCount - 1 && tileCount > 1)
{
writer.WriteLittleEndian((uint)tileData.Length - 1U, tileInfo.TileSizeBytes);
}
writer.WriteBlob(tileData);
}
}
private static int WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ) private static int WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ)
{ {
bool isCoded = deltaQ == 0; bool isCoded = deltaQ == 0;

8
src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs

@ -8,9 +8,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
internal class Av1FrameDecoder internal class Av1FrameDecoder
{ {
private ObuSequenceHeader sequenceHeader; private readonly ObuSequenceHeader sequenceHeader;
private ObuFrameHeader frameHeader; private readonly ObuFrameHeader frameHeader;
private Av1FrameBuffer frameBuffer; private readonly Av1FrameBuffer frameBuffer;
public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameBuffer frameBuffer) public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameBuffer frameBuffer)
{ {
@ -22,6 +22,8 @@ internal class Av1FrameDecoder
public void DecodeFrame() public void DecodeFrame()
{ {
Guard.NotNull(this.sequenceHeader); Guard.NotNull(this.sequenceHeader);
Guard.NotNull(this.frameHeader);
Guard.NotNull(this.frameBuffer);
// TODO: Implement. // TODO: Implement.
} }

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

@ -5,10 +5,13 @@ using SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
internal class Av1TileDecoderStub : IAv1TileReader internal class Av1TileDecoderStub : IAv1TileReader, IAv1TileWriter
{ {
private readonly Dictionary<int, byte[]> tileDatas = [];
public void ReadTile(Span<byte> tileData, int tileNum) public void ReadTile(Span<byte> tileData, int tileNum)
{ => this.tileDatas.Add(tileNum, tileData.ToArray());
// Intentionally left blank.
} public Span<byte> WriteTile(int tileNum)
=> this.tileDatas[tileNum];
} }

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

@ -12,6 +12,11 @@ public class ObuFrameHeaderTests
private static readonly byte[] DefaultSequenceHeaderBitStream = private static readonly byte[] DefaultSequenceHeaderBitStream =
[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]; [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];
// TODO: Check with libgav1 test code.
private static readonly byte[] KeyFrameHeaderBitStream =
// libgav1 expects this: [0x32, 0x05, 0x10, 0x00];
[0x32, 0x05, 0x20, 0x04];
// Bits Syntax element Value // Bits Syntax element Value
// 1 obu_forbidden_bit 0 // 1 obu_forbidden_bit 0
// 4 obu_type 2 (OBU_TEMPORAL_DELIMITER) // 4 obu_type 2 (OBU_TEMPORAL_DELIMITER)
@ -22,10 +27,10 @@ public class ObuFrameHeaderTests
private static readonly byte[] DefaultTemporalDelimiterBitStream = [0x12, 0x00]; private static readonly byte[] DefaultTemporalDelimiterBitStream = [0x12, 0x00];
[Theory] [Theory]
// [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000D)] // [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000d)]
// [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6BD1)] // [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6bd1)]
[InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)] [InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc)]
[InlineData(TestImages.Heif.Orange4x4, 0x010E, 0x001d)] [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d)]
public void ReadFrameHeader(string filename, int fileOffset, int blockSize) public void ReadFrameHeader(string filename, int fileOffset, int blockSize)
{ {
// Assign // Assign
@ -49,26 +54,27 @@ public class ObuFrameHeaderTests
/* /*
[Theory] [Theory]
[InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)] [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d, 0x0128)]
public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, int blockSize) [InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc, 0x0114)]
public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, int blockSize, int tileOffset)
{ {
// Assign // Assign
string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename);
byte[] content = File.ReadAllBytes(filePath); byte[] content = File.ReadAllBytes(filePath);
Span<byte> span = content.AsSpan(fileOffset, blockSize); Span<byte> span = content.AsSpan(fileOffset, blockSize);
Av1TileDecoderStub tileDecoder = new(); Av1TileDecoderStub tileStub = new();
Av1BitStreamReader reader = new(span); Av1BitStreamReader reader = new(span);
ObuReader obuReader = new(); ObuReader obuReader = new();
// Act 1 // Act 1
obuReader.ReadAll(ref reader, blockSize, tileDecoder); obuReader.ReadAll(ref reader, blockSize, tileStub);
// Assign 2 // Assign 2
MemoryStream encoded = new(); MemoryStream encoded = new();
// Act 2 // Act 2
ObuWriter obuWriter = new(); ObuWriter obuWriter = new();
ObuWriter.Write(encoded, obuReader.SequenceHeader, obuReader.FrameHeader); obuWriter.WriteAll(encoded, obuReader.SequenceHeader, obuReader.FrameHeader, tileStub);
// Assert // Assert
Assert.Equal(span, encoded.ToArray()); Assert.Equal(span, encoded.ToArray());
@ -76,25 +82,27 @@ public class ObuFrameHeaderTests
*/ */
[Theory] [Theory]
[InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)] [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d)]
[InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc)]
public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int blockSize) public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int blockSize)
{ {
// Assign // Assign
string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename);
byte[] content = File.ReadAllBytes(filePath); byte[] content = File.ReadAllBytes(filePath);
Span<byte> span = content.AsSpan(fileOffset, blockSize); Span<byte> span = content.AsSpan(fileOffset, blockSize);
IAv1TileReader tileDecoder = new Av1TileDecoderStub(); Av1TileDecoderStub tileStub = new();
Av1BitStreamReader reader = new(span); Av1BitStreamReader reader = new(span);
ObuReader obuReader1 = new(); ObuReader obuReader1 = new();
// Act 1 // Act 1
obuReader1.ReadAll(ref reader, blockSize, tileDecoder); obuReader1.ReadAll(ref reader, blockSize, tileStub);
// Assign 2 // Assign 2
MemoryStream encoded = new(); MemoryStream encoded = new();
// Act 2 // Act 2
ObuWriter.WriteAll(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader); ObuWriter obuWriter = new();
obuWriter.WriteAll(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader, tileStub);
// Assign 2 // Assign 2
Span<byte> encodedBuffer = encoded.ToArray(); Span<byte> encodedBuffer = encoded.ToArray();
@ -169,9 +177,10 @@ public class ObuFrameHeaderTests
{ {
// Arrange // Arrange
using MemoryStream stream = new(2); using MemoryStream stream = new(2);
ObuWriter obuWriter = new();
// Act // Act
ObuWriter.WriteAll(stream, null, null); obuWriter.WriteAll(stream, null, null, null);
byte[] actual = stream.GetBuffer(); byte[] actual = stream.GetBuffer();
// Assert // Assert
@ -184,9 +193,10 @@ public class ObuFrameHeaderTests
// Arrange // Arrange
using MemoryStream stream = new(10); using MemoryStream stream = new(10);
ObuSequenceHeader input = GetDefaultSequenceHeader(); ObuSequenceHeader input = GetDefaultSequenceHeader();
ObuWriter obuWriter = new();
// Act // Act
ObuWriter.WriteAll(stream, input, null); obuWriter.WriteAll(stream, input, null, null);
byte[] buffer = stream.GetBuffer(); byte[] buffer = stream.GetBuffer();
// Assert // Assert
@ -195,6 +205,28 @@ public class ObuFrameHeaderTests
Assert.Equal(DefaultSequenceHeaderBitStream, actual); Assert.Equal(DefaultSequenceHeaderBitStream, actual);
} }
[Fact]
public void WriteFrameHeader()
{
// Arrange
using MemoryStream stream = new(10);
ObuSequenceHeader sequenceInput = GetDefaultSequenceHeader();
ObuFrameHeader frameInput = GetKeyFrameHeader();
Av1TileDecoderStub tileStub = new();
byte[] empty = [];
tileStub.ReadTile(empty, 0);
ObuWriter obuWriter = new();
// Act
obuWriter.WriteAll(stream, sequenceInput, frameInput, tileStub);
byte[] buffer = stream.GetBuffer();
// Assert
// Skip over Temporal Delimiter and Sequence header.
byte[] actual = buffer.AsSpan().Slice(DefaultTemporalDelimiterBitStream.Length + DefaultSequenceHeaderBitStream.Length, KeyFrameHeaderBitStream.Length).ToArray();
Assert.Equal(KeyFrameHeaderBitStream, actual);
}
private static ObuSequenceHeader GetDefaultSequenceHeader() private static ObuSequenceHeader GetDefaultSequenceHeader()
// Offset Bits Syntax element Value // Offset Bits Syntax element Value
@ -261,4 +293,34 @@ public class ObuFrameHeaderTests
}, },
AreFilmGrainingParametersPresent = true, AreFilmGrainingParametersPresent = true,
}; };
private static ObuFrameHeader GetKeyFrameHeader()
=> new()
{
FrameType = ObuFrameType.KeyFrame,
ShowFrame = true,
ShowableFrame = false,
DisableFrameEndUpdateCdf = false,
FrameSize = new()
{
FrameWidth = 426,
FrameHeight = 240,
RenderWidth = 426,
RenderHeight = 240,
SuperResolutionUpscaledWidth = 426,
},
PrimaryReferenceFrame = 7,
ModeInfoRowCount = 60,
ModeInfoColumnCount = 108,
RefreshFrameFlags = 0xff,
ErrorResilientMode = true,
ForceIntegerMotionVector = true,
TilesInfo = new ObuTileGroupHeader()
{
HasUniformTileSpacing = true,
TileColumnCount = 1,
TileRowCount = 1,
}
};
} }

Loading…
Cancel
Save