Browse Source

Document and correct AV1 OBU syntax

pull/2633/head
James Jackson-South 1 week ago
parent
commit
96d1916ecf
  1. 15
      src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
  2. 2
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs
  3. 2
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs
  4. 291
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
  5. 21
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs
  6. 425
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
  7. 52
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs
  8. 2
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs

15
src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs

@ -290,4 +290,19 @@ internal static class Av1Constants
/// The maximum number of transform units stored for one encoded block. /// The maximum number of transform units stored for one encoded block.
/// </summary> /// </summary>
public const int MaxTransformUnitCount = 16; public const int MaxTransformUnitCount = 16;
/// <summary>
/// Gets the number of payload bits used by each segmentation feature.
/// </summary>
public static ReadOnlySpan<int> SegmentationFeatureBits => [8, 6, 6, 6, 6, 3, 0, 0];
/// <summary>
/// Gets values indicating whether each segmentation feature is signed.
/// </summary>
public static ReadOnlySpan<int> SegmentationFeatureSigned => [1, 1, 1, 1, 1, 0, 0, 0];
/// <summary>
/// Gets the maximum magnitude or value permitted for each segmentation feature.
/// </summary>
public static ReadOnlySpan<int> SegmentationFeatureMax => [MaxQ, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, 7, 0, 0];
} }

2
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs

@ -16,7 +16,7 @@ internal class ObuConstraintDirectionalEnhancementFilterParameters
/// <summary> /// <summary>
/// Gets or sets the filter damping value. /// Gets or sets the filter damping value.
/// </summary> /// </summary>
public int Damping { get; internal set; } public int Damping { get; internal set; } = 3;
/// <summary> /// <summary>
/// Gets or sets the primary and secondary luma strengths for each filter entry. /// Gets or sets the primary and secondary luma strengths for each filter entry.

2
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs

@ -14,7 +14,7 @@ internal class ObuDeltaParameters
public bool IsPresent { get; internal set; } public bool IsPresent { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets the delta-value resolution. /// Gets or sets the delta-value multiplier, which is one, two, four, or eight.
/// </summary> /// </summary>
public int Resolution { get; internal set; } public int Resolution { get; internal set; }

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

@ -7,47 +7,32 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
/// <summary> /// <summary>
/// Reader for Open Bitstream Units (OBU's). /// Parses AV1 open bitstream units and supplies decoded tile payloads to an AV1 tile reader.
/// </summary> /// </summary>
internal class ObuReader internal class ObuReader
{ {
/// <summary> /// <summary>
/// Maximum value used for loop filtering. /// The tile reader created for the current coded frame.
/// </summary> /// </summary>
private const int MaxLoopFilter = 63; private IAv1TileReader? decoder;
/// <summary>
/// Number of segments allowed in segmentation map.
/// </summary>
private const int MaxSegments = 0;
/// <summary> /// <summary>
/// Number of segment features. /// Gets or sets the most recently parsed sequence header.
/// </summary> /// </summary>
private const int SegLvlMax = 8; public ObuSequenceHeader? SequenceHeader { get; set; }
/// <summary> /// <summary>
/// Index for reference frame segment feature. /// Gets or sets the frame header associated with the current coded frame.
/// </summary> /// </summary>
private const int SegLvlRefFrame = 5;
private const int PrimaryRefNone = 7;
private static readonly int[] SegmentationFeatureBits = [8, 6, 6, 6, 6, 3, 0, 0];
private static readonly int[] SegmentationFeatureSigned = [1, 1, 1, 1, 1, 0, 0, 0];
private static readonly int[] SegmentationFeatureMax = [255, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, 7, 0, 0];
private IAv1TileReader? decoder;
public ObuSequenceHeader? SequenceHeader { get; set; }
public ObuFrameHeader? FrameHeader { get; set; } public ObuFrameHeader? FrameHeader { get; set; }
/// <summary> /// <summary>
/// Decode all OBU's in a frame. /// Parses the open bitstream units that make up one coded frame.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the first OBU.</param>
/// <param name="dataSize">The number of bytes available for the coded frame.</param>
/// <param name="creator">Creates the tile reader when the first tile payload is encountered.</param>
/// <param name="isAnnexB">A value indicating whether each OBU is prefixed by an Annex B length field.</param>
public void ReadAll(ref Av1BitStreamReader reader, int dataSize, Func<IAv1TileReader> creator, bool isAnnexB = false) public void ReadAll(ref Av1BitStreamReader reader, int dataSize, Func<IAv1TileReader> creator, bool isAnnexB = false)
{ {
bool seenFrameHeader = false; bool seenFrameHeader = false;
@ -124,6 +109,9 @@ internal class ObuReader
} }
this.decoder ??= creator(); this.decoder ??= creator();
// A combined frame OBU reaches this label after its frame-header portion has
// been consumed, leaving the same tile-group syntax as a standalone tile OBU.
this.ReadTileGroup(ref reader, this.decoder, header, out frameDecodingFinished); this.ReadTileGroup(ref reader, this.decoder, header, out frameDecodingFinished);
if (frameDecodingFinished) if (frameDecodingFinished)
{ {
@ -150,8 +138,10 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.3.2. OBU header syntax. /// Reads the fixed OBU header and optional extension fields.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at an OBU header.</param>
/// <returns>The parsed OBU header.</returns>
private static ObuHeader ReadObuHeader(ref Av1BitStreamReader reader) private static ObuHeader ReadObuHeader(ref Av1BitStreamReader reader)
{ {
ObuHeader header = new(); ObuHeader header = new();
@ -188,6 +178,12 @@ internal class ObuReader
return header; return header;
} }
/// <summary>
/// Reads an OBU size encoded as an unsigned little-endian base-128 value.
/// </summary>
/// <param name="reader">The reader positioned at the size value.</param>
/// <param name="obuSize">The decoded OBU size.</param>
/// <param name="lengthSize">The number of bytes occupied by the encoded size.</param>
private static void ReadObuSize(ref Av1BitStreamReader reader, out int obuSize, out int lengthSize) private static void ReadObuSize(ref Av1BitStreamReader reader, out int obuSize, out int lengthSize)
{ {
ulong rawSize = reader.ReadLittleEndianBytes128(out lengthSize); ulong rawSize = reader.ReadLittleEndianBytes128(out lengthSize);
@ -200,8 +196,11 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// Read OBU header and size. /// Reads an OBU header followed by its optional payload-size field.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at an OBU header.</param>
/// <param name="lengthSize">The number of bytes occupied by the payload-size field.</param>
/// <returns>The parsed OBU header and payload size.</returns>
private static ObuHeader ReadObuHeaderSize(ref Av1BitStreamReader reader, out int lengthSize) private static ObuHeader ReadObuHeaderSize(ref Av1BitStreamReader reader, out int lengthSize)
{ {
ObuHeader header = ReadObuHeader(ref reader); ObuHeader header = ReadObuHeader(ref reader);
@ -216,8 +215,9 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// Check that the trailing bits start with a 1 and end with 0s. /// Reads and validates the trailing one bit followed by zero padding.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the trailing bits.</param>
/// <remarks>Consumes a byte, if already byte aligned before the check.</remarks> /// <remarks>Consumes a byte, if already byte aligned before the check.</remarks>
private static void ReadTrailingBits(ref Av1BitStreamReader reader) private static void ReadTrailingBits(ref Av1BitStreamReader reader)
{ {
@ -230,8 +230,9 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.3.5. Byte alignment syntax. /// Consumes zero padding until the reader reaches a byte boundary.
/// </summary> /// </summary>
/// <param name="reader">The reader to align.</param>
private static void AlignToByteBoundary(ref Av1BitStreamReader reader) private static void AlignToByteBoundary(ref Av1BitStreamReader reader)
{ {
while ((reader.BitPosition & 0x7) > 0) while ((reader.BitPosition & 0x7) > 0)
@ -244,8 +245,9 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.9. Compute image size function. /// Computes the mode-information dimensions and stride for the current frame.
/// </summary> /// </summary>
/// <param name="sequenceHeader">The sequence header defining the maximum frame geometry and superblock size.</param>
/// <remarks>SVT: compute_image_size</remarks> /// <remarks>SVT: compute_image_size</remarks>
private void ComputeImageSize(ObuSequenceHeader sequenceHeader) private void ComputeImageSize(ObuSequenceHeader sequenceHeader)
{ {
@ -256,8 +258,10 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.5.1. General sequence header OBU syntax. /// Reads an AV1 sequence-header OBU payload.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the sequence-header payload.</param>
/// <param name="sequenceHeader">The sequence header to populate.</param>
internal static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) internal static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
{ {
sequenceHeader.SequenceProfile = (ObuSequenceProfile)reader.ReadLiteral(3); sequenceHeader.SequenceProfile = (ObuSequenceProfile)reader.ReadLiteral(3);
@ -368,6 +372,7 @@ internal class ObuReader
{ {
sequenceHeader.DeltaFrameIdLength = (int)reader.ReadLiteral(4) + 2; sequenceHeader.DeltaFrameIdLength = (int)reader.ReadLiteral(4) + 2;
sequenceHeader.AdditionalFrameIdLength = reader.ReadLiteral(3) + 1; sequenceHeader.AdditionalFrameIdLength = reader.ReadLiteral(3) + 1;
sequenceHeader.FrameIdLength = sequenceHeader.DeltaFrameIdLength + (int)sequenceHeader.AdditionalFrameIdLength;
} }
// Video related flags removed // Video related flags removed
@ -392,7 +397,7 @@ internal class ObuReader
sequenceHeader.EnableInterIntraCompound = reader.ReadBoolean(); sequenceHeader.EnableInterIntraCompound = reader.ReadBoolean();
sequenceHeader.EnableMaskedCompound = reader.ReadBoolean(); sequenceHeader.EnableMaskedCompound = reader.ReadBoolean();
sequenceHeader.EnableWarpedMotion = reader.ReadBoolean(); sequenceHeader.EnableWarpedMotion = reader.ReadBoolean();
sequenceHeader.EnableDualFilter |= reader.ReadBoolean(); sequenceHeader.EnableDualFilter = reader.ReadBoolean();
sequenceHeader.EnableOrderHint = reader.ReadBoolean(); sequenceHeader.EnableOrderHint = reader.ReadBoolean();
if (sequenceHeader.EnableOrderHint) if (sequenceHeader.EnableOrderHint)
{ {
@ -452,8 +457,11 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.5.2. Color config syntax. /// Reads the sequence color configuration.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the color-configuration syntax.</param>
/// <param name="sequenceHeader">The sequence header that determines the permitted color formats.</param>
/// <returns>The parsed color configuration.</returns>
private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
{ {
ObuColorConfig colorConfig = new(); ObuColorConfig colorConfig = new();
@ -492,6 +500,8 @@ internal class ObuReader
colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb && colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb &&
colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity) colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity)
{ {
// AV1 defines this RGB identity-matrix combination as full-range 4:4:4 and omits
// the range and subsampling syntax that other color combinations carry.
colorConfig.ColorRange = true; colorConfig.ColorRange = true;
colorConfig.SubSamplingX = false; colorConfig.SubSamplingX = false;
colorConfig.SubSamplingY = false; colorConfig.SubSamplingY = false;
@ -539,8 +549,10 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.5.4. Decoder model info syntax. /// Reads the decoder-model field widths and decoding-clock units.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the decoder-model syntax.</param>
/// <param name="sequenceHeader">The sequence header that receives the decoder-model information.</param>
private static void ReadDecoderModelInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) => sequenceHeader.DecoderModelInfo = new ObuDecoderModelInfo private static void ReadDecoderModelInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) => sequenceHeader.DecoderModelInfo = new ObuDecoderModelInfo
{ {
BufferDelayLength = reader.ReadLiteral(5) + 1, BufferDelayLength = reader.ReadLiteral(5) + 1,
@ -550,8 +562,10 @@ internal class ObuReader
}; };
/// <summary> /// <summary>
/// 5.5.3. Timing info syntax. /// Reads the sequence timing information.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the timing-information syntax.</param>
/// <param name="sequenceHeader">The sequence header that receives the timing information.</param>
private static void ReadTimingInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) private static void ReadTimingInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
{ {
sequenceHeader.TimingInfo = new ObuTimingInfo sequenceHeader.TimingInfo = new ObuTimingInfo
@ -567,6 +581,12 @@ internal class ObuReader
} }
} }
/// <summary>
/// Reads the bit depth permitted by the selected sequence profile.
/// </summary>
/// <param name="reader">The reader positioned at the high-bit-depth flag.</param>
/// <param name="colorConfig">The color configuration that receives the bit depth.</param>
/// <param name="sequenceHeader">The sequence header containing the selected profile.</param>
private static void ReadBitDepth(ref Av1BitStreamReader reader, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader) private static void ReadBitDepth(ref Av1BitStreamReader reader, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader)
{ {
bool hasHighBitDepth = reader.ReadBoolean(); bool hasHighBitDepth = reader.ReadBoolean();
@ -585,8 +605,9 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.8. Superres params syntax. /// Reads the super-resolution parameters and derives the coded frame width.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the super-resolution syntax.</param>
private void ReadSuperResolutionParameters(ref Av1BitStreamReader reader) private void ReadSuperResolutionParameters(ref Av1BitStreamReader reader)
{ {
ObuSequenceHeader sequenceHeader = this.SequenceHeader!; ObuSequenceHeader sequenceHeader = this.SequenceHeader!;
@ -607,6 +628,9 @@ internal class ObuReader
} }
frameHeader.FrameSize.SuperResolutionUpscaledWidth = frameHeader.FrameSize.FrameWidth; frameHeader.FrameSize.SuperResolutionUpscaledWidth = frameHeader.FrameSize.FrameWidth;
// AV1 signals the upscaled width first. Tile and block decoding use the rounded-down
// coded width obtained from the fixed scale numerator and signaled denominator.
frameHeader.FrameSize.FrameWidth = frameHeader.FrameSize.FrameWidth =
((frameHeader.FrameSize.SuperResolutionUpscaledWidth * Av1Constants.ScaleNumerator) + ((frameHeader.FrameSize.SuperResolutionUpscaledWidth * Av1Constants.ScaleNumerator) +
(frameHeader.FrameSize.SuperResolutionDenominator / 2)) / (frameHeader.FrameSize.SuperResolutionDenominator / 2)) /
@ -622,8 +646,9 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.6. Render size syntax. /// Reads the optional render dimensions for the current frame.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the render-size syntax.</param>
private void ReadRenderSize(ref Av1BitStreamReader reader) private void ReadRenderSize(ref Av1BitStreamReader reader)
{ {
ObuFrameHeader frameHeader = this.FrameHeader!; ObuFrameHeader frameHeader = this.FrameHeader!;
@ -641,8 +666,10 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.5. Frame size syntax. /// Reads or derives the current frame dimensions.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the frame-size syntax.</param>
/// <param name="frameSizeOverrideFlag">A value indicating whether dimensions are signaled instead of inherited from the sequence maximum.</param>
private void ReadFrameSize(ref Av1BitStreamReader reader, bool frameSizeOverrideFlag) private void ReadFrameSize(ref Av1BitStreamReader reader, bool frameSizeOverrideFlag)
{ {
ObuSequenceHeader sequenceHeader = this.SequenceHeader!; ObuSequenceHeader sequenceHeader = this.SequenceHeader!;
@ -663,8 +690,12 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.15. Tile info syntax. /// Reads the tile layout and derives tile boundaries in mode-information units.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the tile-information syntax.</param>
/// <param name="sequenceHeader">The sequence header defining superblock geometry.</param>
/// <param name="frameHeader">The frame header defining the current frame geometry.</param>
/// <returns>The parsed tile layout.</returns>
private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
ObuTileGroupHeader tileInfo = new(); ObuTileGroupHeader tileInfo = new();
@ -677,6 +708,8 @@ internal class ObuReader
int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (superblockSizeLog2 << 1); int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (superblockSizeLog2 << 1);
// The bitstream constrains tile dimensions in superblocks, while the decoder stores
// boundaries in mode-information units for direct use during block traversal.
tileInfo.MaxTileWidthSuperblock = Av1Constants.MaxTileWidth >> superblockSizeLog2; tileInfo.MaxTileWidthSuperblock = Av1Constants.MaxTileWidth >> superblockSizeLog2;
tileInfo.MaxTileHeightSuperblock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superblockSizeLog2; tileInfo.MaxTileHeightSuperblock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superblockSizeLog2;
tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperblock, superblockColumnCount); tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperblock, superblockColumnCount);
@ -816,18 +849,18 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.2. Uncompressed header syntax. /// Reads the uncompressed syntax for the current still-image frame.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the uncompressed frame header.</param>
private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader) private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader)
{ {
ObuSequenceHeader sequenceHeader = this.SequenceHeader!; ObuSequenceHeader sequenceHeader = this.SequenceHeader!;
ObuFrameHeader frameHeader = this.FrameHeader!; ObuFrameHeader frameHeader = this.FrameHeader!;
int idLength = 0; int idLength = sequenceHeader.FrameIdLength;
uint previousFrameId = 0; uint previousFrameId = 0;
bool frameSizeOverrideFlag = false; bool frameSizeOverrideFlag = false;
if (sequenceHeader.IsFrameIdNumbersPresent) if (sequenceHeader.IsFrameIdNumbersPresent)
{ {
idLength = sequenceHeader.FrameIdLength - 1 + sequenceHeader.DeltaFrameIdLength - 2 + 3;
DebugGuard.MustBeLessThanOrEqualTo(idLength, 16, nameof(idLength)); DebugGuard.MustBeLessThanOrEqualTo(idLength, 16, nameof(idLength));
} }
@ -898,15 +931,18 @@ internal class ObuReader
} }
frameHeader.DisableCdfUpdate = reader.ReadBoolean(); frameHeader.DisableCdfUpdate = reader.ReadBoolean();
frameHeader.AllowScreenContentTools = sequenceHeader.ForceScreenContentTools == 2; if (sequenceHeader.ForceScreenContentTools == 2)
if (frameHeader.AllowScreenContentTools)
{ {
frameHeader.AllowScreenContentTools = reader.ReadBoolean(); frameHeader.AllowScreenContentTools = reader.ReadBoolean();
} }
else
{
frameHeader.AllowScreenContentTools = sequenceHeader.ForceScreenContentTools != 0;
}
if (frameHeader.AllowScreenContentTools) if (frameHeader.AllowScreenContentTools)
{ {
if (sequenceHeader.ForceIntegerMotionVector == 1) if (sequenceHeader.ForceIntegerMotionVector == 2)
{ {
frameHeader.ForceIntegerMotionVector = reader.ReadBoolean(); frameHeader.ForceIntegerMotionVector = reader.ReadBoolean();
} }
@ -1092,49 +1128,13 @@ internal class ObuReader
throw new NotImplementedException(); throw new NotImplementedException();
} }
int tilesCount = frameHeader.TilesInfo.TileColumnCount * frameHeader.TilesInfo.TileRowCount; Av1QuantizationLookup.UpdateFrameQuantizationState(frameHeader);
frameHeader.CodedLossless = true;
frameHeader.SegmentationParameters.QMLevel[0] = new int[Av1Constants.MaxSegmentCount];
frameHeader.SegmentationParameters.QMLevel[1] = new int[Av1Constants.MaxSegmentCount];
frameHeader.SegmentationParameters.QMLevel[2] = new int[Av1Constants.MaxSegmentCount];
for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++)
{
int qIndex = Av1QuantizationLookup.GetQIndex(frameHeader.SegmentationParameters, segmentId, frameHeader.QuantizationParameters.BaseQIndex);
frameHeader.QuantizationParameters.QIndex[segmentId] = qIndex;
frameHeader.LosslessArray[segmentId] = qIndex == 0 &&
frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.Y] == 0 &&
frameHeader.QuantizationParameters.DeltaQAc[(int)Av1Plane.U] == 0 &&
frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.U] == 0 &&
frameHeader.QuantizationParameters.DeltaQAc[(int)Av1Plane.V] == 0 &&
frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.V] == 0;
if (!frameHeader.LosslessArray[segmentId])
{
frameHeader.CodedLossless = false;
}
if (frameHeader.QuantizationParameters.IsUsingQMatrix)
{
if (frameHeader.LosslessArray[segmentId])
{
frameHeader.SegmentationParameters.QMLevel[0][segmentId] = 15;
frameHeader.SegmentationParameters.QMLevel[1][segmentId] = 15;
frameHeader.SegmentationParameters.QMLevel[2][segmentId] = 15;
}
else
{
frameHeader.SegmentationParameters.QMLevel[0][segmentId] = frameHeader.QuantizationParameters.QMatrix[(int)Av1Plane.Y];
frameHeader.SegmentationParameters.QMLevel[1][segmentId] = frameHeader.QuantizationParameters.QMatrix[(int)Av1Plane.U];
frameHeader.SegmentationParameters.QMLevel[2][segmentId] = frameHeader.QuantizationParameters.QMatrix[(int)Av1Plane.V];
}
}
}
if (frameHeader.CodedLossless) if (frameHeader.CodedLossless)
{ {
DebugGuard.IsFalse(frameHeader.DeltaQParameters.IsPresent, nameof(frameHeader.DeltaQParameters.IsPresent), "No Delta Q parameters are allowed for lossless frame."); DebugGuard.IsFalse(frameHeader.DeltaQParameters.IsPresent, nameof(frameHeader.DeltaQParameters.IsPresent), "No Delta Q parameters are allowed for lossless frame.");
} }
frameHeader.AllLossless = frameHeader.CodedLossless && frameHeader.FrameSize.FrameWidth == frameHeader.FrameSize.SuperResolutionUpscaledWidth;
this.ReadLoopFilterParameters(ref reader, sequenceHeader); this.ReadLoopFilterParameters(ref reader, sequenceHeader);
ReadCdefParameters(ref reader, sequenceHeader, frameHeader); ReadCdefParameters(ref reader, sequenceHeader, frameHeader);
ReadLoopRestorationParameters(ref reader, sequenceHeader, frameHeader); ReadLoopRestorationParameters(ref reader, sequenceHeader, frameHeader);
@ -1156,12 +1156,22 @@ internal class ObuReader
frameHeader.FilmGrainParameters = ReadFilmGrainFilterParameters(ref reader, sequenceHeader, frameHeader); frameHeader.FilmGrainParameters = ReadFilmGrainFilterParameters(ref reader, sequenceHeader, frameHeader);
} }
/// <summary>
/// Determines whether segmentation and a specific per-segment feature are both enabled.
/// </summary>
/// <param name="segmentationParameters">The frame segmentation state.</param>
/// <param name="segmentId">The segment identifier.</param>
/// <param name="feature">The feature to inspect.</param>
/// <returns><see langword="true"/> when the feature is active; otherwise, <see langword="false"/>.</returns>
private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature)
=> segmentationParameters.Enabled && segmentationParameters.IsFeatureActive(segmentId, feature); => segmentationParameters.Enabled && segmentationParameters.IsFeatureActive(segmentId, feature);
/// <summary> /// <summary>
/// 5.9.1. General frame header OBU syntax. /// Reads an AV1 frame header and removes its byte length from the remaining OBU payload size.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the frame-header payload.</param>
/// <param name="header">The OBU header whose remaining payload size is updated.</param>
/// <param name="trailingBit">A value indicating whether trailing-bit syntax follows the frame header.</param>
internal void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit) internal void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit)
{ {
int startBitPosition = reader.BitPosition; int startBitPosition = reader.BitPosition;
@ -1179,8 +1189,12 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.11.1. General tile group OBU syntax. /// Reads a tile-group header and passes each contained tile payload to the tile reader.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the tile-group payload.</param>
/// <param name="decoder">The tile reader that decodes each tile payload.</param>
/// <param name="header">The OBU header containing the remaining tile-group payload size.</param>
/// <param name="isLastTileGroup">Receives whether this group contains the final tile of the frame.</param>
private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileReader decoder, ObuHeader header, out bool isLastTileGroup) private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileReader decoder, ObuHeader header, out bool isLastTileGroup)
{ {
ObuSequenceHeader sequenceHeader = this.SequenceHeader!; ObuSequenceHeader sequenceHeader = this.SequenceHeader!;
@ -1225,6 +1239,8 @@ internal class ObuReader
frameHeader.LoopRestorationParameters.Items[(int)Av1Plane.U].Type != ObuRestorationType.None || frameHeader.LoopRestorationParameters.Items[(int)Av1Plane.U].Type != ObuRestorationType.None ||
frameHeader.LoopRestorationParameters.Items[(int)Av1Plane.V].Type != ObuRestorationType.None); frameHeader.LoopRestorationParameters.Items[(int)Av1Plane.V].Type != ObuRestorationType.None);
// All tile sizes except the final size are explicitly stored as size-minus-one. The
// last tile consumes the bytes that remain in the OBU payload.
for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++) for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++)
{ {
bool isLastTile = tileNum == tileGroupEnd; bool isLastTile = tileNum == tileGroupEnd;
@ -1248,8 +1264,10 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.13. Delta quantizer syntax. /// Reads an optional signed quantizer-index delta.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at a delta-quantizer field.</param>
/// <returns>The decoded delta, or zero when the field is absent.</returns>
private static int ReadDeltaQ(ref Av1BitStreamReader reader) private static int ReadDeltaQ(ref Av1BitStreamReader reader)
{ {
int deltaQ = 0; int deltaQ = 0;
@ -1262,11 +1280,13 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.17. Quantizer index delta parameters syntax. /// Reads the frame-level delta-quantizer configuration.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the delta-quantizer parameters.</param>
/// <param name="frameHeader">The frame header that receives the parameters.</param>
private static void ReadFrameDeltaQParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader) private static void ReadFrameDeltaQParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
{ {
frameHeader.DeltaQParameters.Resolution = 0; frameHeader.DeltaQParameters.Resolution = 1;
frameHeader.DeltaQParameters.IsPresent = false; frameHeader.DeltaQParameters.IsPresent = false;
if (frameHeader.QuantizationParameters.BaseQIndex > 0) if (frameHeader.QuantizationParameters.BaseQIndex > 0)
{ {
@ -1275,17 +1295,19 @@ internal class ObuReader
if (frameHeader.DeltaQParameters.IsPresent) if (frameHeader.DeltaQParameters.IsPresent)
{ {
frameHeader.DeltaQParameters.Resolution = (int)reader.ReadLiteral(2); frameHeader.DeltaQParameters.Resolution = 1 << (int)reader.ReadLiteral(2);
} }
} }
/// <summary> /// <summary>
/// 5.9.18. Loop filter delta parameters syntax. /// Reads the frame-level delta-loop-filter configuration.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the delta-loop-filter parameters.</param>
/// <param name="frameHeader">The frame header that receives the parameters.</param>
private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader) private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
{ {
frameHeader.DeltaLoopFilterParameters.IsPresent = false; frameHeader.DeltaLoopFilterParameters.IsPresent = false;
frameHeader.DeltaLoopFilterParameters.Resolution = 0; frameHeader.DeltaLoopFilterParameters.Resolution = 1;
frameHeader.DeltaLoopFilterParameters.IsMulti = false; frameHeader.DeltaLoopFilterParameters.IsMulti = false;
if (frameHeader.DeltaQParameters.IsPresent) if (frameHeader.DeltaQParameters.IsPresent)
{ {
@ -1296,15 +1318,18 @@ internal class ObuReader
if (frameHeader.DeltaLoopFilterParameters.IsPresent) if (frameHeader.DeltaLoopFilterParameters.IsPresent)
{ {
frameHeader.DeltaLoopFilterParameters.Resolution = (int)reader.ReadLiteral(2); frameHeader.DeltaLoopFilterParameters.Resolution = 1 << (int)reader.ReadLiteral(2);
frameHeader.DeltaLoopFilterParameters.IsMulti = reader.ReadBoolean(); frameHeader.DeltaLoopFilterParameters.IsMulti = reader.ReadBoolean();
} }
} }
} }
/// <summary> /// <summary>
/// 5.9.12. Quantization params syntax. /// Reads the base index, plane deltas, and optional quantization matrices for a frame.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the quantization parameters.</param>
/// <param name="sequenceHeader">The sequence header defining the active color planes.</param>
/// <param name="frameHeader">The frame header that receives the quantization parameters.</param>
private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
ObuQuantizationParameters quantParams = frameHeader.QuantizationParameters; ObuQuantizationParameters quantParams = frameHeader.QuantizationParameters;
@ -1364,15 +1389,17 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.14. Segmentation params syntax. /// Reads the segmentation map controls and per-segment feature values.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the segmentation parameters.</param>
/// <param name="frameHeader">The frame header that receives the segmentation state.</param>
private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader) private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
{ {
frameHeader.SegmentationParameters.Enabled = reader.ReadBoolean(); frameHeader.SegmentationParameters.Enabled = reader.ReadBoolean();
if (frameHeader.SegmentationParameters.Enabled) if (frameHeader.SegmentationParameters.Enabled)
{ {
if (frameHeader.PrimaryReferenceFrame == PrimaryRefNone) if (frameHeader.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone)
{ {
frameHeader.SegmentationParameters.SegmentationUpdateMap = 1; frameHeader.SegmentationParameters.SegmentationUpdateMap = 1;
frameHeader.SegmentationParameters.SegmentationTemporalUpdate = 0; frameHeader.SegmentationParameters.SegmentationTemporalUpdate = 0;
@ -1391,9 +1418,9 @@ internal class ObuReader
if (frameHeader.SegmentationParameters.SegmentationUpdateData == 1) if (frameHeader.SegmentationParameters.SegmentationUpdateData == 1)
{ {
for (int i = 0; i < MaxSegments; i++) for (int i = 0; i < Av1Constants.MaxSegmentCount; i++)
{ {
for (int j = 0; j < SegLvlMax; j++) for (int j = 0; j < Av1Constants.SegmentationLevelMax; j++)
{ {
int featureValue = 0; int featureValue = 0;
bool featureEnabled = reader.ReadBoolean(); bool featureEnabled = reader.ReadBoolean();
@ -1401,9 +1428,9 @@ internal class ObuReader
int clippedValue = 0; int clippedValue = 0;
if (featureEnabled) if (featureEnabled)
{ {
int bitsToRead = SegmentationFeatureBits[j]; int bitsToRead = Av1Constants.SegmentationFeatureBits[j];
int limit = SegmentationFeatureMax[j]; int limit = Av1Constants.SegmentationFeatureMax[j];
if (SegmentationFeatureSigned[j] == 1) if (Av1Constants.SegmentationFeatureSigned[j] == 1)
{ {
featureValue = reader.ReadSignedFromUnsigned(1 + bitsToRead); featureValue = reader.ReadSignedFromUnsigned(1 + bitsToRead);
clippedValue = Av1Math.Clip3(-limit, limit, featureValue); clippedValue = Av1Math.Clip3(-limit, limit, featureValue);
@ -1411,6 +1438,7 @@ internal class ObuReader
else else
{ {
featureValue = (int)reader.ReadLiteral(bitsToRead); featureValue = (int)reader.ReadLiteral(bitsToRead);
clippedValue = featureValue;
} }
} }
@ -1440,7 +1468,7 @@ internal class ObuReader
if (frameHeader.SegmentationParameters.FeatureEnabled[i, j]) if (frameHeader.SegmentationParameters.FeatureEnabled[i, j])
{ {
frameHeader.SegmentationParameters.LastActiveSegmentId = i; frameHeader.SegmentationParameters.LastActiveSegmentId = i;
if (j >= SegLvlRefFrame) if (j >= (int)ObuSegmentationLevelFeature.ReferenceFrame)
{ {
frameHeader.SegmentationParameters.SegmentIdPrecedesSkip = true; frameHeader.SegmentationParameters.SegmentIdPrecedesSkip = true;
} }
@ -1450,8 +1478,10 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.11. Loop filter params syntax /// Reads the deblocking-loop-filter levels and optional reference and mode deltas.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the loop-filter parameters.</param>
/// <param name="sequenceHeader">The sequence header defining the active color planes.</param>
private void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) private void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader)
{ {
ObuFrameHeader frameHeader = this.FrameHeader!; ObuFrameHeader frameHeader = this.FrameHeader!;
@ -1499,8 +1529,10 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.21. TX mode syntax. /// Reads or derives the transform-size selection mode.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the transform-mode flag.</param>
/// <param name="frameHeader">The frame header that receives the transform mode.</param>
private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader) private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
{ {
if (frameHeader.CodedLossless) if (frameHeader.CodedLossless)
@ -1521,8 +1553,11 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// See section 5.9.20. Loop restoration params syntax. /// Reads the loop-restoration type and restoration-unit size for each plane.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the loop-restoration parameters.</param>
/// <param name="sequenceHeader">The sequence header defining restoration availability and color planes.</param>
/// <param name="frameHeader">The frame header that receives the restoration parameters.</param>
private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration) if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration)
@ -1535,6 +1570,8 @@ internal class ObuReader
int planesCount = sequenceHeader.ColorConfig.PlaneCount; int planesCount = sequenceHeader.ColorConfig.PlaneCount;
for (int i = 0; i < planesCount; i++) for (int i = 0; i < planesCount; i++)
{ {
// The AV1 frame syntax orders its two restoration bits as none, switchable,
// Wiener, and self-guided projection, matching ObuRestorationType values.
frameHeader.LoopRestorationParameters.Items[i].Type = (ObuRestorationType)reader.ReadLiteral(2); frameHeader.LoopRestorationParameters.Items[i].Type = (ObuRestorationType)reader.ReadLiteral(2);
if (frameHeader.LoopRestorationParameters.Items[i].Type != ObuRestorationType.None) if (frameHeader.LoopRestorationParameters.Items[i].Type != ObuRestorationType.None)
@ -1575,13 +1612,16 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// See section 5.9.19. CDEF params syntax. /// Reads constrained directional enhancement filter strengths for the active planes.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the CDEF parameters.</param>
/// <param name="sequenceHeader">The sequence header defining CDEF availability and color planes.</param>
/// <param name="frameHeader">The frame header that receives the CDEF parameters.</param>
private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
ObuConstraintDirectionalEnhancementFilterParameters cdefInfo = frameHeader.CdefParameters; ObuConstraintDirectionalEnhancementFilterParameters cdefInfo = frameHeader.CdefParameters;
bool multiPlane = sequenceHeader.ColorConfig.PlaneCount > 1; bool multiPlane = sequenceHeader.ColorConfig.PlaneCount > 1;
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || sequenceHeader.CdefLevel == 0) if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableCdef)
{ {
cdefInfo.BitCount = 0; cdefInfo.BitCount = 0;
cdefInfo.YStrength[0] = 0; cdefInfo.YStrength[0] = 0;
@ -1606,8 +1646,11 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.24. Global motion params syntax. /// Reads global-motion parameters when permitted by the frame type.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the global-motion parameters.</param>
/// <param name="sequenceHeader">The sequence header controlling global-motion tools.</param>
/// <param name="frameHeader">The current frame header.</param>
private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
_ = reader; _ = reader;
@ -1623,8 +1666,11 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.23. Frame reference mode syntax /// Reads or derives the reference prediction mode.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the reference-mode flag.</param>
/// <param name="frameHeader">The current frame header.</param>
/// <returns>The frame reference mode.</returns>
private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader) private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader)
{ {
if (frameHeader.IsIntra) if (frameHeader.IsIntra)
@ -1636,11 +1682,14 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.11.10. Skip mode syntax. /// Reads skip-mode enablement when the frame is eligible to use it.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the skip-mode syntax.</param>
/// <param name="sequenceHeader">The sequence header controlling order hints.</param>
/// <param name="frameHeader">The frame header that receives the skip-mode state.</param>
private static void ReadSkipModeParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static void ReadSkipModeParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
if (frameHeader.IsIntra || frameHeader.ReferenceMode == ObuReferenceMode.ReferenceModeSelect || !sequenceHeader.OrderHintInfo.EnableOrderHint) if (frameHeader.IsIntra || frameHeader.ReferenceMode == ObuReferenceMode.SingleReference || !sequenceHeader.OrderHintInfo.EnableOrderHint)
{ {
frameHeader.SkipModeParameters.SkipModeAllowed = false; frameHeader.SkipModeParameters.SkipModeAllowed = false;
} }
@ -1660,8 +1709,12 @@ internal class ObuReader
} }
/// <summary> /// <summary>
/// 5.9.30. Film grain params syntax. /// Reads film-grain synthesis parameters for the current displayed frame.
/// </summary> /// </summary>
/// <param name="reader">The reader positioned at the film-grain parameters.</param>
/// <param name="sequenceHeader">The sequence header defining film-grain availability and color sampling.</param>
/// <param name="frameHeader">The current frame header.</param>
/// <returns>The parsed film-grain parameters.</returns>
private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
ObuFilmGrainParameters grainParams = new(); ObuFilmGrainParameters grainParams = new();
@ -1684,7 +1737,9 @@ internal class ObuReader
} }
else else
{ {
grainParams.UpdateGrain = false; // Only inter frames can inherit parameters from a reference frame. Still-image
// intra frames always carry a complete parameter set when grain is enabled.
grainParams.UpdateGrain = true;
} }
if (!grainParams.UpdateGrain) if (!grainParams.UpdateGrain)
@ -1737,7 +1792,7 @@ internal class ObuReader
grainParams.NumCrPoints = reader.ReadLiteral(4); grainParams.NumCrPoints = reader.ReadLiteral(4);
grainParams.PointCrValue = new uint[grainParams.NumCrPoints]; grainParams.PointCrValue = new uint[grainParams.NumCrPoints];
grainParams.PointCrScaling = new uint[grainParams.NumCrPoints]; grainParams.PointCrScaling = new uint[grainParams.NumCrPoints];
for (int i = 0; i < grainParams.NumCbPoints; i++) for (int i = 0; i < grainParams.NumCrPoints; i++)
{ {
grainParams.PointCrValue[i] = reader.ReadLiteral(8); grainParams.PointCrValue[i] = reader.ReadLiteral(8);
grainParams.PointCrScaling[i] = reader.ReadLiteral(8); grainParams.PointCrScaling[i] = reader.ReadLiteral(8);
@ -1803,12 +1858,20 @@ internal class ObuReader
return grainParams; return grainParams;
} }
/// <summary>
/// Determines whether a sequence-level index is assigned by the AV1 specification.
/// </summary>
/// <param name="sequenceLevelIndex">The sequence-level index.</param>
/// <returns><see langword="true"/> for assigned indices; otherwise, <see langword="false"/>.</returns>
private static bool IsValidSequenceLevel(int sequenceLevelIndex) private static bool IsValidSequenceLevel(int sequenceLevelIndex)
=> sequenceLevelIndex is < 24 or 31; => sequenceLevelIndex is < 24 or 31;
/// <summary> /// <summary>
/// Returns the smallest value for k such that blockSize &lt;&lt; k is greater than or equal to target. /// Returns the smallest shift for which <paramref name="blockSize"/> shifted left reaches <paramref name="target"/>.
/// </summary> /// </summary>
/// <param name="blockSize">The initial block count.</param>
/// <param name="target">The minimum shifted value.</param>
/// <returns>The required base-2 shift.</returns>
public static int TileLog2(int blockSize, int target) public static int TileLog2(int blockSize, int target)
{ {
int k; int k;

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

@ -125,11 +125,6 @@ internal class ObuSequenceHeader
/// </summary> /// </summary>
public int SuperblockSizeLog2 { get; private set; } public int SuperblockSizeLog2 { get; private set; }
/// <summary>
/// Gets or sets the filter-intra capability level.
/// </summary>
public int FilterIntraLevel { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether intra edge filtering is enabled. /// Gets or sets a value indicating whether intra edge filtering is enabled.
/// </summary> /// </summary>
@ -143,7 +138,16 @@ internal class ObuSequenceHeader
/// <summary> /// <summary>
/// Gets or sets a value indicating whether order hints are enabled. /// Gets or sets a value indicating whether order hints are enabled.
/// </summary> /// </summary>
public bool EnableOrderHint { get; set; } public bool EnableOrderHint
{
get => this.OrderHintInfo.EnableOrderHint;
set
{
// Order-hint availability is consumed through OrderHintInfo by frame parsing, so
// keep the sequence-facing flag and dependent tool state synchronized.
this.OrderHintInfo.EnableOrderHint = value;
}
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether inter-intra compound prediction is enabled. /// Gets or sets a value indicating whether inter-intra compound prediction is enabled.
@ -180,11 +184,6 @@ internal class ObuSequenceHeader
/// </summary> /// </summary>
public bool EnableSuperResolution { get; set; } public bool EnableSuperResolution { get; set; }
/// <summary>
/// Gets or sets the constrained directional enhancement filter capability level.
/// </summary>
public int CdefLevel { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether loop restoration is enabled. /// Gets or sets a value indicating whether loop restoration is enabled.
/// </summary> /// </summary>

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

@ -1,22 +1,33 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
/// <summary>
/// Writes the AV1 open bitstream units required for a single still-image frame.
/// </summary>
internal class ObuWriter internal class ObuWriter
{ {
private int[] previousQIndex = [];
private int[] previousDeltaLoopFilter = [];
/// <summary> /// <summary>
/// Encode a single frame into OBU's. /// Writes a temporal delimiter and the supplied sequence and frame OBUs.
/// </summary> /// </summary>
/// <param name="configuration">The configuration used to allocate temporary encoding memory.</param>
/// <param name="stream">The destination stream.</param>
/// <param name="sequenceHeader">The optional still-picture sequence header.</param>
/// <param name="frameHeader">The optional intra-frame header.</param>
/// <param name="tileWriter">The tile writer used when a frame header is supplied.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Performance",
"CA1822:Mark members as static",
Justification = "Preserves the existing writer instance contract.")]
public void WriteAll(Configuration configuration, Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, IAv1TileWriter tileWriter) public void WriteAll(Configuration configuration, Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, IAv1TileWriter tileWriter)
{ {
// TODO: Determine inital size dynamically // The allocation expands when necessary; this initial size avoids repeated growth for
// the small headers and tiles produced by the current still-image encoder.
int initialBufferSize = 2000; int initialBufferSize = 2000;
AutoExpandingMemory<byte> buffer = new(configuration, initialBufferSize); AutoExpandingMemory<byte> buffer = new(configuration, initialBufferSize);
Av1BitStreamWriter writer = new(buffer); Av1BitStreamWriter writer = new(buffer);
@ -32,7 +43,7 @@ internal class ObuWriter
if (frameHeader != null && sequenceHeader != null) if (frameHeader != null && sequenceHeader != null)
{ {
this.WriteFrameHeader(ref writer, sequenceHeader, frameHeader, false); WriteFrameHeader(ref writer, sequenceHeader, frameHeader);
if (frameHeader.TilesInfo != null) if (frameHeader.TilesInfo != null)
{ {
WriteTileGroup(ref writer, frameHeader.TilesInfo, tileWriter); WriteTileGroup(ref writer, frameHeader.TilesInfo, tileWriter);
@ -44,27 +55,24 @@ internal class ObuWriter
} }
} }
private static void WriteObuHeader(ref Av1BitStreamWriter writer, ObuType type) /// <summary>
/// Creates a byte-aligned OBU header with an explicit payload-size field and no extension.
/// </summary>
/// <param name="type">The OBU payload type.</param>
/// <returns>The encoded OBU header byte.</returns>
private static byte WriteObuHeader(ObuType type)
{ {
writer.WriteBoolean(false); // Forbidden bit // The only set fields are the four-bit type and the has-size flag; forbidden,
writer.WriteLiteral((uint)type, 4); // extension, and reserved bits remain zero.
writer.WriteBoolean(false); // Extension return (byte)(((byte)type << 3) | 0x02);
writer.WriteBoolean(true); // HasSize
writer.WriteBoolean(false); // Reserved
} }
private static byte WriteObuHeader(ObuType type) =>
// 0: Forbidden bit
// 1: Type, 4
// 5: Extension (false)
// 6: HasSize (true)
// 7: Reserved (false)
(byte)(((byte)type << 3) | 0x02);
/// <summary> /// <summary>
/// Read OBU header and size. /// Writes a complete byte-aligned OBU with a little-endian base-128 payload size.
/// </summary> /// </summary>
/// <param name="stream">The destination stream.</param>
/// <param name="type">The OBU payload type.</param>
/// <param name="payload">The complete OBU payload.</param>
private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span<byte> payload) private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span<byte> payload)
{ {
stream.WriteByte(WriteObuHeader(type)); stream.WriteByte(WriteObuHeader(type));
@ -77,18 +85,20 @@ internal class ObuWriter
} }
/// <summary> /// <summary>
/// Write trsainling bits to end on a byte boundary, these trailing bits start with a 1 and end with 0s. /// Writes a trailing one bit followed by enough zero bits to reach a byte boundary.
/// </summary> /// </summary>
/// <remarks>Write an additional byte, if already byte aligned before.</remarks> /// <param name="writer">The bit writer receiving the trailing bits.</param>
/// <remarks>Writes an additional byte when the writer is already byte aligned.</remarks>
private static void WriteTrailingBits(ref Av1BitStreamWriter writer) private static void WriteTrailingBits(ref Av1BitStreamWriter writer)
{ {
int bitsBeforeAlignment = 8 - (writer.BitPosition & 0x7); int bitsBeforeAlignment = 8 - (writer.BitPosition & 0x7);
if (bitsBeforeAlignment != 8) writer.WriteLiteral(1U << (bitsBeforeAlignment - 1), bitsBeforeAlignment);
{
writer.WriteLiteral(1U << (bitsBeforeAlignment - 1), bitsBeforeAlignment);
}
} }
/// <summary>
/// Writes zero padding until the output reaches a byte boundary.
/// </summary>
/// <param name="writer">The bit writer to align.</param>
private static void AlignToByteBoundary(ref Av1BitStreamWriter writer) private static void AlignToByteBoundary(ref Av1BitStreamWriter writer)
{ {
while ((writer.BitPosition & 0x7) > 0) while ((writer.BitPosition & 0x7) > 0)
@ -97,14 +107,11 @@ internal class ObuWriter
} }
} }
private static bool IsValidObuType(ObuType type) => type switch /// <summary>
{ /// Writes a reduced still-picture sequence header.
ObuType.SequenceHeader or ObuType.TemporalDelimiter or ObuType.FrameHeader or /// </summary>
ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrameHeader or /// <param name="writer">The bit writer receiving the sequence header.</param>
ObuType.TileList or ObuType.Padding => true, /// <param name="sequenceHeader">The sequence header to encode.</param>
_ => false,
};
private static void WriteSequenceHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader) private static void WriteSequenceHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader)
{ {
writer.WriteLiteral((uint)sequenceHeader.SequenceProfile, 3); writer.WriteLiteral((uint)sequenceHeader.SequenceProfile, 3);
@ -132,6 +139,11 @@ internal class ObuWriter
WriteTrailingBits(ref writer); WriteTrailingBits(ref writer);
} }
/// <summary>
/// Writes the sequence color configuration.
/// </summary>
/// <param name="writer">The bit writer receiving the color configuration.</param>
/// <param name="sequenceHeader">The sequence header containing the color configuration.</param>
private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader) private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader)
{ {
ObuColorConfig colorConfig = sequenceHeader.ColorConfig; ObuColorConfig colorConfig = sequenceHeader.ColorConfig;
@ -159,6 +171,8 @@ internal class ObuWriter
colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb && colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb &&
colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity) colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity)
{ {
// AV1 fixes this RGB identity-matrix combination to full-range 4:4:4 and omits
// the range and subsampling fields used by YUV configurations.
colorConfig.ColorRange = true; colorConfig.ColorRange = true;
colorConfig.SubSamplingX = false; colorConfig.SubSamplingX = false;
colorConfig.SubSamplingY = false; colorConfig.SubSamplingY = false;
@ -184,6 +198,12 @@ internal class ObuWriter
writer.WriteBoolean(colorConfig.HasSeparateUvDelta); writer.WriteBoolean(colorConfig.HasSeparateUvDelta);
} }
/// <summary>
/// Writes the profile-dependent bit-depth flags.
/// </summary>
/// <param name="writer">The bit writer receiving the flags.</param>
/// <param name="colorConfig">The color configuration containing the bit depth.</param>
/// <param name="sequenceHeader">The sequence header containing the selected profile.</param>
private static void WriteBitDepth(ref Av1BitStreamWriter writer, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader) private static void WriteBitDepth(ref Av1BitStreamWriter writer, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader)
{ {
bool hasHighBitDepth = colorConfig.BitDepth > Av1BitDepth.EightBit; bool hasHighBitDepth = colorConfig.BitDepth > Av1BitDepth.EightBit;
@ -194,9 +214,17 @@ internal class ObuWriter
} }
} }
/// <summary>
/// Writes the super-resolution enablement and scale denominator.
/// </summary>
/// <param name="writer">The bit writer receiving the super-resolution syntax.</param>
/// <param name="sequenceHeader">The sequence header controlling super-resolution availability.</param>
/// <param name="frameHeader">The frame header containing the scale denominator.</param>
private static void WriteSuperResolutionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static void WriteSuperResolutionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
bool useSuperResolution = false; bool useSuperResolution = sequenceHeader.EnableSuperResolution &&
frameHeader.FrameSize.SuperResolutionDenominator != Av1Constants.ScaleNumerator;
if (sequenceHeader.EnableSuperResolution) if (sequenceHeader.EnableSuperResolution)
{ {
writer.WriteBoolean(useSuperResolution); writer.WriteBoolean(useSuperResolution);
@ -208,10 +236,18 @@ internal class ObuWriter
} }
} }
/// <summary>
/// Writes the render-size override when display dimensions differ from the upscaled frame.
/// </summary>
/// <param name="writer">The bit writer receiving the render-size syntax.</param>
/// <param name="frameHeader">The frame header containing coded and render dimensions.</param>
private static void WriteRenderSize(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader) private static void WriteRenderSize(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{ {
bool renderSizeAndFrameSizeDifferent = false; bool renderSizeAndFrameSizeDifferent =
writer.WriteBoolean(false); frameHeader.FrameSize.RenderWidth != frameHeader.FrameSize.SuperResolutionUpscaledWidth ||
frameHeader.FrameSize.RenderHeight != frameHeader.FrameSize.FrameHeight;
writer.WriteBoolean(renderSizeAndFrameSizeDifferent);
if (renderSizeAndFrameSizeDifferent) if (renderSizeAndFrameSizeDifferent)
{ {
writer.WriteLiteral((uint)frameHeader.FrameSize.RenderWidth - 1, 16); writer.WriteLiteral((uint)frameHeader.FrameSize.RenderWidth - 1, 16);
@ -219,41 +255,30 @@ internal class ObuWriter
} }
} }
private static void WriteFrameSizeWithReferences(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool frameSizeOverrideFlag) /// <summary>
{ /// Writes an optional frame-size override followed by super-resolution syntax.
bool foundReference = false; /// </summary>
for (int i = 0; i < Av1Constants.ReferencesPerFrame; i++) /// <param name="writer">The bit writer receiving the frame size.</param>
{ /// <param name="sequenceHeader">The sequence header defining dimension field widths.</param>
writer.WriteBoolean(foundReference); /// <param name="frameHeader">The frame header containing the dimensions.</param>
if (foundReference) /// <param name="frameSizeOverrideFlag">A value indicating whether explicit dimensions are written.</param>
{
// Take values over from reference frame
break;
}
}
if (!foundReference)
{
WriteFrameSize(ref writer, sequenceHeader, frameHeader, frameSizeOverrideFlag);
WriteRenderSize(ref writer, frameHeader);
}
else
{
WriteSuperResolutionParameters(ref writer, sequenceHeader, frameHeader);
}
}
private static void WriteFrameSize(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool frameSizeOverrideFlag) private static void WriteFrameSize(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool frameSizeOverrideFlag)
{ {
if (frameSizeOverrideFlag) if (frameSizeOverrideFlag)
{ {
writer.WriteLiteral((uint)frameHeader.FrameSize.FrameWidth - 1, sequenceHeader.FrameWidthBits + 1); writer.WriteLiteral((uint)frameHeader.FrameSize.FrameWidth - 1, sequenceHeader.FrameWidthBits);
writer.WriteLiteral((uint)frameHeader.FrameSize.FrameHeight - 1, sequenceHeader.FrameHeightBits + 1); writer.WriteLiteral((uint)frameHeader.FrameSize.FrameHeight - 1, sequenceHeader.FrameHeightBits);
} }
WriteSuperResolutionParameters(ref writer, sequenceHeader, frameHeader); WriteSuperResolutionParameters(ref writer, sequenceHeader, frameHeader);
} }
/// <summary>
/// Writes the frame tile layout.
/// </summary>
/// <param name="writer">The bit writer receiving the tile information.</param>
/// <param name="sequenceHeader">The sequence header defining superblock geometry.</param>
/// <param name="frameHeader">The frame header containing tile boundaries.</param>
private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
ObuTileGroupHeader tileInfo = frameHeader.TilesInfo; ObuTileGroupHeader tileInfo = frameHeader.TilesInfo;
@ -273,8 +298,10 @@ internal class ObuWriter
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 log2TileColumnCount = ObuReader.TileLog2(1, tileInfo.TileColumnCount);
int log2TileRowCount = Av1Math.Log2(tileInfo.TileRowCount); int log2TileRowCount = ObuReader.TileLog2(1, tileInfo.TileRowCount);
tileInfo.TileColumnCountLog2 = log2TileColumnCount;
tileInfo.TileRowCountLog2 = log2TileRowCount;
writer.WriteBoolean(tileInfo.HasUniformTileSpacing); writer.WriteBoolean(tileInfo.HasUniformTileSpacing);
if (tileInfo.HasUniformTileSpacing) if (tileInfo.HasUniformTileSpacing)
@ -293,7 +320,7 @@ internal class ObuWriter
} }
// rows // rows
tileInfo.MinLog2TileRowCount = Math.Min(tileInfo.MinLog2TileCount - log2TileColumnCount, 0); tileInfo.MinLog2TileRowCount = Math.Max(tileInfo.MinLog2TileCount - log2TileColumnCount, 0);
ones = log2TileRowCount - tileInfo.MinLog2TileRowCount; ones = log2TileRowCount - tileInfo.MinLog2TileRowCount;
while (ones-- > 0) while (ones-- > 0)
{ {
@ -346,11 +373,14 @@ internal class ObuWriter
frameHeader.TilesInfo = tileInfo; frameHeader.TilesInfo = tileInfo;
} }
private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) /// <summary>
/// Writes the reduced uncompressed header for an intra still-image frame.
/// </summary>
/// <param name="writer">The bit writer receiving the uncompressed frame header.</param>
/// <param name="sequenceHeader">The sequence header controlling available coding tools.</param>
/// <param name="frameHeader">The frame header to encode.</param>
private static void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
// TODO: Make tile count configurable.
int tileCount = 1;
int planesCount = sequenceHeader.ColorConfig.PlaneCount;
writer.WriteBoolean(frameHeader.DisableCdfUpdate); writer.WriteBoolean(frameHeader.DisableCdfUpdate);
if (sequenceHeader.ForceScreenContentTools == 2) if (sequenceHeader.ForceScreenContentTools == 2)
{ {
@ -410,19 +440,15 @@ internal class ObuWriter
WriteTileInfo(ref writer, sequenceHeader, frameHeader); WriteTileInfo(ref writer, sequenceHeader, frameHeader);
WriteQuantizationParameters(ref writer, sequenceHeader, frameHeader); WriteQuantizationParameters(ref writer, sequenceHeader, frameHeader);
WriteSegmentationParameters(ref writer, sequenceHeader, frameHeader); WriteSegmentationParameters(ref writer, frameHeader);
Av1QuantizationLookup.UpdateFrameQuantizationState(frameHeader);
if (frameHeader.QuantizationParameters.BaseQIndex > 0) if (frameHeader.QuantizationParameters.BaseQIndex > 0)
{ {
writer.WriteBoolean(frameHeader.DeltaQParameters.IsPresent); writer.WriteBoolean(frameHeader.DeltaQParameters.IsPresent);
if (frameHeader.DeltaQParameters.IsPresent) if (frameHeader.DeltaQParameters.IsPresent)
{ {
writer.WriteLiteral((uint)frameHeader.DeltaQParameters.Resolution - 1, 2); writer.WriteLiteral((uint)Av1Math.MostSignificantBit((uint)frameHeader.DeltaQParameters.Resolution), 2);
this.previousQIndex = new int[tileCount];
for (int tileIndex = 0; tileIndex < tileCount; tileIndex++)
{
this.previousQIndex[tileIndex] = frameHeader.QuantizationParameters.BaseQIndex;
}
if (frameHeader.AllowIntraBlockCopy) if (frameHeader.AllowIntraBlockCopy)
{ {
@ -438,28 +464,18 @@ internal class ObuWriter
if (frameHeader.DeltaLoopFilterParameters.IsPresent) if (frameHeader.DeltaLoopFilterParameters.IsPresent)
{ {
writer.WriteLiteral((uint)(1 + Av1Math.MostSignificantBit((uint)frameHeader.DeltaLoopFilterParameters.Resolution) - 1), 2); writer.WriteLiteral((uint)Av1Math.MostSignificantBit((uint)frameHeader.DeltaLoopFilterParameters.Resolution), 2);
writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti); writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti);
int frameLoopFilterCount = sequenceHeader.ColorConfig.IsMonochrome ? Av1Constants.FrameLoopFilterCount - 2 : Av1Constants.FrameLoopFilterCount;
this.previousDeltaLoopFilter = new int[frameLoopFilterCount];
for (int loopFilterId = 0; loopFilterId < frameLoopFilterCount; loopFilterId++)
{
this.previousDeltaLoopFilter[loopFilterId] = 0;
}
} }
} }
} }
if (frameHeader.AllLossless) if (!frameHeader.AllLossless)
{
throw new NotImplementedException("No entire lossless supported.");
}
else
{ {
if (!frameHeader.CodedLossless) if (!frameHeader.CodedLossless)
{ {
WriteLoopFilterParameters(ref writer, sequenceHeader, frameHeader); WriteLoopFilterParameters(ref writer, sequenceHeader, frameHeader);
if (sequenceHeader.CdefLevel > 0) if (sequenceHeader.EnableCdef)
{ {
WriteCdefParameters(ref writer, sequenceHeader, frameHeader); WriteCdefParameters(ref writer, sequenceHeader, frameHeader);
} }
@ -485,38 +501,34 @@ internal class ObuWriter
WriteFilmGrainFilterParameters(ref writer, sequenceHeader, frameHeader); WriteFilmGrainFilterParameters(ref writer, sequenceHeader, frameHeader);
} }
private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) /// <summary>
=> segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; /// Writes the frame-header portion of a combined frame OBU.
/// </summary>
private int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool writeTrailingBits) /// <param name="writer">The bit writer receiving the frame header.</param>
/// <param name="sequenceHeader">The sequence header controlling available coding tools.</param>
/// <param name="frameHeader">The frame header to encode.</param>
private static void WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
int startBitPosition = writer.BitPosition; WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameHeader);
this.WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameHeader);
if (writeTrailingBits)
{
WriteTrailingBits(ref writer);
}
int endPosition = writer.BitPosition;
int headerBytes = (endPosition - startBitPosition) / 8;
return headerBytes;
} }
/// <summary> /// <summary>
/// 5.11.1. General tile group OBU syntax. /// Writes a tile-group header and all tile payloads for a combined frame OBU.
/// </summary> /// </summary>
private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter) /// <param name="writer">The bit writer receiving the tile group.</param>
/// <param name="tileInfo">The frame tile layout.</param>
/// <param name="tileWriter">The writer that produces each entropy-coded tile payload.</param>
private static void WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter)
{ {
int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
int startBitPosition = writer.BitPosition; if (tileCount > 1)
bool tileStartAndEndPresentFlag = tileCount > 1; {
writer.WriteBoolean(tileStartAndEndPresentFlag); // This writer places every tile in one group, so the optional range spans the
// complete frame whenever the range syntax is present.
uint tileGroupStart = 0U; writer.WriteBoolean(true);
uint tileGroupEnd = (uint)tileCount - 1U; uint tileGroupStart = 0U;
if (tileCount != 1) uint tileGroupEnd = (uint)tileCount - 1U;
{ int tileBits = tileInfo.TileColumnCountLog2 + tileInfo.TileRowCountLog2;
int tileBits = Av1Math.Log2(tileInfo.TileColumnCount) + Av1Math.Log2(tileInfo.TileRowCount);
writer.WriteLiteral(tileGroupStart, tileBits); writer.WriteLiteral(tileGroupStart, tileBits);
writer.WriteLiteral(tileGroupEnd, tileBits); writer.WriteLiteral(tileGroupEnd, tileBits);
} }
@ -524,12 +536,14 @@ internal class ObuWriter
AlignToByteBoundary(ref writer); AlignToByteBoundary(ref writer);
WriteTileData(ref writer, tileInfo, tileWriter); WriteTileData(ref writer, tileInfo, tileWriter);
int endBitPosition = writer.BitPosition;
int headerBytes = (endBitPosition - startBitPosition) / 8;
return headerBytes;
} }
/// <summary>
/// Writes the size-prefixed tile payloads in raster order.
/// </summary>
/// <param name="writer">The byte-aligned bit writer receiving tile data.</param>
/// <param name="tileInfo">The frame tile layout and tile-size field width.</param>
/// <param name="tileWriter">The writer that produces each tile payload.</param>
private static void WriteTileData(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter) private static void WriteTileData(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter)
{ {
int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
@ -545,7 +559,12 @@ internal class ObuWriter
} }
} }
private static int WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ) /// <summary>
/// Writes an optional signed quantizer-index delta.
/// </summary>
/// <param name="writer">The bit writer receiving the delta.</param>
/// <param name="deltaQ">The quantizer-index delta.</param>
private static void WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ)
{ {
bool isCoded = deltaQ != 0; bool isCoded = deltaQ != 0;
writer.WriteBoolean(isCoded); writer.WriteBoolean(isCoded);
@ -553,43 +572,14 @@ internal class ObuWriter
{ {
writer.WriteSignedFromUnsigned(deltaQ, 7); writer.WriteSignedFromUnsigned(deltaQ, 7);
} }
return deltaQ;
}
private static void WriteFrameDeltaQParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
if (frameHeader.QuantizationParameters.BaseQIndex > 0)
{
writer.WriteBoolean(frameHeader.DeltaQParameters.IsPresent);
}
if (frameHeader.DeltaQParameters.IsPresent)
{
writer.WriteLiteral((uint)frameHeader.DeltaQParameters.Resolution, 2);
}
}
private static void WriteFrameDeltaLoopFilterParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
if (frameHeader.DeltaQParameters.IsPresent)
{
if (!frameHeader.AllowIntraBlockCopy)
{
writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsPresent);
}
if (frameHeader.DeltaLoopFilterParameters.IsPresent)
{
writer.WriteLiteral((uint)frameHeader.DeltaLoopFilterParameters.Resolution, 2);
writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti);
}
}
} }
/// <summary> /// <summary>
/// See section 5.9.12. /// Writes the base index, plane deltas, and optional quantization matrices for a frame.
/// </summary> /// </summary>
/// <param name="writer">The bit writer receiving the quantization parameters.</param>
/// <param name="sequenceHeader">The sequence header defining active color planes.</param>
/// <param name="frameHeader">The frame header containing the quantization parameters.</param>
private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
ObuQuantizationParameters quantParams = frameHeader.QuantizationParameters; ObuQuantizationParameters quantParams = frameHeader.QuantizationParameters;
@ -623,16 +613,54 @@ internal class ObuWriter
} }
} }
private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) /// <summary>
/// Writes segmentation feature data for an independently decoded still-image frame.
/// </summary>
/// <param name="writer">The bit writer receiving the segmentation parameters.</param>
/// <param name="frameHeader">The frame header containing segmentation feature data.</param>
private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{ {
_ = sequenceHeader; ObuSegmentationParameters segmentation = frameHeader.SegmentationParameters;
Guard.IsFalse(frameHeader.SegmentationParameters.Enabled, nameof(frameHeader.SegmentationParameters.Enabled), "Segmentation not supported yet."); writer.WriteBoolean(segmentation.Enabled);
writer.WriteBoolean(false); if (!segmentation.Enabled)
{
return;
}
// The still-image writer emits independent intra frames with no primary reference.
// AV1 therefore infers update-map and update-data as enabled and carries feature data
// directly, without the inter-frame update flags.
for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++)
{
for (int featureId = 0; featureId < Av1Constants.SegmentationLevelMax; featureId++)
{
bool enabled = segmentation.FeatureEnabled[segmentId, featureId];
writer.WriteBoolean(enabled);
if (!enabled)
{
continue;
}
int bitCount = Av1Constants.SegmentationFeatureBits[featureId];
int value = segmentation.FeatureData[segmentId, featureId];
if (Av1Constants.SegmentationFeatureSigned[featureId] == 1)
{
writer.WriteSignedFromUnsigned(value, bitCount + 1);
}
else
{
writer.WriteLiteral((uint)value, bitCount);
}
}
}
} }
/// <summary> /// <summary>
/// 5.9.11. Loop filter params syntax /// Writes the deblocking-loop-filter levels and optional reference and mode deltas.
/// </summary> /// </summary>
/// <param name="writer">The bit writer receiving the loop-filter parameters.</param>
/// <param name="sequenceHeader">The sequence header defining active color planes.</param>
/// <param name="frameHeader">The frame header containing the loop-filter parameters.</param>
private static void WriteLoopFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static void WriteLoopFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy) if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy)
@ -658,14 +686,29 @@ internal class ObuWriter
writer.WriteBoolean(frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate); writer.WriteBoolean(frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate);
if (frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate) if (frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate)
{ {
throw new NotImplementedException("Reference update of loop filter not supported yet."); // An independent still frame can emit every current delta as an update. This is
// slightly larger than comparing against retained state but requires no video
// reference-frame state and produces the same observable filter parameters.
for (int i = 0; i < Av1Constants.TotalReferencesPerFrame; i++)
{
writer.WriteBoolean(true);
writer.WriteSignedFromUnsigned(frameHeader.LoopFilterParameters.ReferenceDeltas[i], 7);
}
for (int i = 0; i < 2; i++)
{
writer.WriteBoolean(true);
writer.WriteSignedFromUnsigned(frameHeader.LoopFilterParameters.ModeDeltas[i], 7);
}
} }
} }
} }
/// <summary> /// <summary>
/// 5.9.21. TX mode syntax. /// Writes the transform-size selection mode when the frame is not lossless.
/// </summary> /// </summary>
/// <param name="writer">The bit writer receiving the transform-mode flag.</param>
/// <param name="frameHeader">The frame header containing the transform mode.</param>
private static void WriteTransformMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader) private static void WriteTransformMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{ {
if (!frameHeader.CodedLossless) if (!frameHeader.CodedLossless)
@ -674,6 +717,12 @@ internal class ObuWriter
} }
} }
/// <summary>
/// Writes the loop-restoration type and restoration-unit size for each plane.
/// </summary>
/// <param name="writer">The bit writer receiving the loop-restoration parameters.</param>
/// <param name="sequenceHeader">The sequence header defining restoration availability and color planes.</param>
/// <param name="frameHeader">The frame header containing restoration parameters.</param>
private static void WriteLoopRestorationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static void WriteLoopRestorationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration) if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration)
@ -696,7 +745,7 @@ internal class ObuWriter
} }
else else
{ {
writer.WriteLiteral(unitShift & 0x01, 1); writer.WriteBoolean(unitShift > 0);
if (unitShift > 0) if (unitShift > 0)
{ {
writer.WriteLiteral(unitShift - 1, 1); writer.WriteLiteral(unitShift - 1, 1);
@ -710,19 +759,39 @@ internal class ObuWriter
} }
} }
/// <summary>
/// Writes constrained directional enhancement filter strengths for the active planes.
/// </summary>
/// <param name="writer">The bit writer receiving the CDEF parameters.</param>
/// <param name="sequenceHeader">The sequence header defining CDEF availability and color planes.</param>
/// <param name="frameHeader">The frame header containing CDEF strengths.</param>
private static void WriteCdefParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static void WriteCdefParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
_ = writer;
_ = sequenceHeader;
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableCdef) if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableCdef)
{ {
return; return;
} }
throw new NotImplementedException("Didn't implement writing CDEF yet."); ObuConstraintDirectionalEnhancementFilterParameters cdef = frameHeader.CdefParameters;
writer.WriteLiteral((uint)cdef.Damping - 3, 2);
writer.WriteLiteral((uint)cdef.BitCount, 2);
int strengthCount = 1 << cdef.BitCount;
bool hasChroma = sequenceHeader.ColorConfig.PlaneCount > 1;
for (int i = 0; i < strengthCount; i++)
{
writer.WriteLiteral((uint)cdef.YStrength[i], 6);
if (hasChroma)
{
writer.WriteLiteral((uint)cdef.UvStrength[i], 6);
}
}
} }
/// <summary>
/// Writes global-motion parameters when permitted by the frame type.
/// </summary>
/// <param name="writer">The bit writer positioned at the global-motion syntax.</param>
/// <param name="frameHeader">The current frame header.</param>
private static void WriteGlobalMotionParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader) private static void WriteGlobalMotionParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{ {
_ = writer; _ = writer;
@ -736,6 +805,11 @@ internal class ObuWriter
throw new InvalidImageContentException("AVIF files can only contain INTRA frames."); throw new InvalidImageContentException("AVIF files can only contain INTRA frames.");
} }
/// <summary>
/// Writes reference-mode selection when permitted by the frame type.
/// </summary>
/// <param name="writer">The bit writer positioned at the reference-mode syntax.</param>
/// <param name="frameHeader">The current frame header.</param>
private static void WriteFrameReferenceMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader) private static void WriteFrameReferenceMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{ {
_ = writer; _ = writer;
@ -749,6 +823,11 @@ internal class ObuWriter
throw new InvalidImageContentException("AVIF files can only contain INTRA frames."); throw new InvalidImageContentException("AVIF files can only contain INTRA frames.");
} }
/// <summary>
/// Writes the skip-mode flag when skip mode is available.
/// </summary>
/// <param name="writer">The bit writer receiving the skip-mode flag.</param>
/// <param name="frameHeader">The frame header containing skip-mode state.</param>
private static void WriteSkipModeParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader) private static void WriteSkipModeParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{ {
if (frameHeader.SkipModeParameters.SkipModeAllowed) if (frameHeader.SkipModeParameters.SkipModeAllowed)
@ -757,6 +836,12 @@ internal class ObuWriter
} }
} }
/// <summary>
/// Writes film-grain synthesis parameters for a displayed still-image frame.
/// </summary>
/// <param name="writer">The bit writer receiving the film-grain parameters.</param>
/// <param name="sequenceHeader">The sequence header defining film-grain availability and color sampling.</param>
/// <param name="frameHeader">The frame header containing film-grain parameters.</param>
private static void WriteFilmGrainFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) private static void WriteFilmGrainFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
ObuFilmGrainParameters grainParams = frameHeader.FilmGrainParameters; ObuFilmGrainParameters grainParams = frameHeader.FilmGrainParameters;
@ -802,7 +887,7 @@ internal class ObuWriter
writer.WriteLiteral(grainParams.NumCrPoints, 4); writer.WriteLiteral(grainParams.NumCrPoints, 4);
Guard.NotNull(grainParams.PointCrValue); Guard.NotNull(grainParams.PointCrValue);
Guard.NotNull(grainParams.PointCrScaling); Guard.NotNull(grainParams.PointCrScaling);
for (int i = 0; i < grainParams.NumCbPoints; i++) for (int i = 0; i < grainParams.NumCrPoints; i++)
{ {
writer.WriteLiteral(grainParams.PointCrValue[i], 8); writer.WriteLiteral(grainParams.PointCrValue[i], 8);
writer.WriteLiteral(grainParams.PointCrScaling[i], 8); writer.WriteLiteral(grainParams.PointCrScaling[i], 8);
@ -813,10 +898,10 @@ internal class ObuWriter
writer.WriteLiteral(grainParams.ArCoeffLag, 2); writer.WriteLiteral(grainParams.ArCoeffLag, 2);
uint numPosLuma = 2 * grainParams.ArCoeffLag * (grainParams.ArCoeffLag + 1); uint numPosLuma = 2 * grainParams.ArCoeffLag * (grainParams.ArCoeffLag + 1);
uint numPosChroma = 0; uint numPosChroma = numPosLuma;
if (grainParams.NumYPoints != 0) if (grainParams.NumYPoints != 0)
{ {
numPosChroma = numPosLuma + 1; numPosChroma++;
Guard.NotNull(grainParams.ArCoeffsYPlus128); Guard.NotNull(grainParams.ArCoeffsYPlus128);
for (int i = 0; i < numPosLuma; i++) for (int i = 0; i < numPosLuma; i++)
{ {

52
src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs

@ -232,4 +232,56 @@ internal class Av1QuantizationLookup
return baseQIndex; return baseQIndex;
} }
} }
/// <summary>
/// Derives the per-segment quantizer indices, lossless flags, and quantization-matrix levels for a frame.
/// </summary>
/// <param name="frameHeader">The frame header whose quantization state is updated.</param>
public static void UpdateFrameQuantizationState(ObuFrameHeader frameHeader)
{
ObuQuantizationParameters quantization = frameHeader.QuantizationParameters;
ObuSegmentationParameters segmentation = frameHeader.SegmentationParameters;
frameHeader.CodedLossless = true;
segmentation.QMLevel[0] = new int[Av1Constants.MaxSegmentCount];
segmentation.QMLevel[1] = new int[Av1Constants.MaxSegmentCount];
segmentation.QMLevel[2] = new int[Av1Constants.MaxSegmentCount];
for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++)
{
int qIndex = GetQIndex(segmentation, segmentId, quantization.BaseQIndex);
quantization.QIndex[segmentId] = qIndex;
frameHeader.LosslessArray[segmentId] = qIndex == 0 &&
quantization.DeltaQDc[(int)Av1Plane.Y] == 0 &&
quantization.DeltaQAc[(int)Av1Plane.U] == 0 &&
quantization.DeltaQDc[(int)Av1Plane.U] == 0 &&
quantization.DeltaQAc[(int)Av1Plane.V] == 0 &&
quantization.DeltaQDc[(int)Av1Plane.V] == 0;
if (!frameHeader.LosslessArray[segmentId])
{
frameHeader.CodedLossless = false;
}
if (quantization.IsUsingQMatrix)
{
// Lossless segments use the identity matrix level; lossy segments inherit the
// plane-specific levels signaled by the frame quantization parameters.
segmentation.QMLevel[(int)Av1Plane.Y][segmentId] = frameHeader.LosslessArray[segmentId]
? 15
: quantization.QMatrix[(int)Av1Plane.Y];
segmentation.QMLevel[(int)Av1Plane.U][segmentId] = frameHeader.LosslessArray[segmentId]
? 15
: quantization.QMatrix[(int)Av1Plane.U];
segmentation.QMLevel[(int)Av1Plane.V][segmentId] = frameHeader.LosslessArray[segmentId]
? 15
: quantization.QMatrix[(int)Av1Plane.V];
}
}
frameHeader.AllLossless = frameHeader.CodedLossless &&
frameHeader.FrameSize.FrameWidth == frameHeader.FrameSize.SuperResolutionUpscaledWidth;
// Header syntax and tile quantization now observe the same derived frame state.
}
} }

2
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs

@ -523,7 +523,7 @@ internal partial class Av1TileWriter
} }
if (!macroBlockModeInfo.Block.UseIntraBlockCopy && if (!macroBlockModeInfo.Block.UseIntraBlockCopy &&
IsFilterIntraAllowed(scs.SequenceHeader.FilterIntraLevel > 0, blockSize, blk_ptr.PaletteSize[0], intra_luma_mode)) IsFilterIntraAllowed(scs.SequenceHeader.EnableFilterIntra, blockSize, blk_ptr.PaletteSize[0], intra_luma_mode))
{ {
writer.WriteFilterIntraMode(blk_ptr.FilterIntraMode, blockSize); writer.WriteFilterIntraMode(blk_ptr.FilterIntraMode, blockSize);
} }

Loading…
Cancel
Save