diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs
index a5c55530ec..28fa923be9 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs
@@ -63,4 +63,28 @@ internal ref struct Av1BitStreamWriter(Stream stream)
this.bitOffset = 0;
}
}
+
+ public void WriteSignedFromUnsigned(int signedValue, int n)
+ {
+ // See section 4.10.6 of the AV1-Specification
+ uint value = unchecked((uint)signedValue);
+ this.WriteLiteral(value, n + 1);
+ }
+
+ public void WriteLittleEndianBytes128(uint value)
+ {
+ if (value < 128)
+ {
+ this.WriteLiteral(value, 8);
+ }
+ else if (value < 0x8000U)
+ {
+ this.WriteLiteral(value >> 7, 8);
+ this.WriteLiteral(value & 0x80, 8);
+ }
+ else
+ {
+ throw new NotImplementedException("No such large values yet.");
+ }
+ }
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs
index 392a54a5c0..c89e747f3a 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs
@@ -51,7 +51,7 @@ internal static class ObuConstants
///
public const uint PrimaryReferenceFrameNone = 7;
- public const int PimaryReferenceBits = -1;
+ public const int PimaryReferenceBits = 3;
///
/// Number of segments allowed in segmentation map.
@@ -79,4 +79,9 @@ internal static class ObuConstants
/// Number of segmentation features.
///
public const int SegmentationLevelMax = 8;
+
+ ///
+ /// Maximum size of a loop restoration tile.
+ ///
+ public const int RestorationMaxTileSize = 256;
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs
index ffd561476a..f952fae216 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs
@@ -7,9 +7,9 @@ internal class ObuConstraintDirectionalEnhancementFilterParameters
{
public int BitCount { get; internal set; }
- public int[] YStrength { get; internal set; } = new int[5];
+ public int Damping { get; internal set; }
- public int[] UVStrength { get; internal set; } = new int[5];
+ public int[] YStrength { get; set; } = new int[16];
- public int Damping { get; internal set; }
+ public int[] UvStrength { get; set; } = new int[16];
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs
index 4a08041ec2..205beba370 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs
@@ -5,5 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuLoopRestorationParameters
{
- public ObuRestorationType FrameRestorationType { get; internal set; } = ObuRestorationType.RestoreNone;
+ internal int Size { get; set; }
+
+ internal ObuRestorationType Type { get; set; } = ObuRestorationType.None;
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
index e0066e7c1d..f4a134c526 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
@@ -10,7 +10,7 @@ internal class ObuReader
///
/// Decode all OBU's in a frame.
///
- public static void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB)
+ public static void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB = false)
{
bool frameDecodingFinished = false;
while (!frameDecodingFinished)
@@ -255,7 +255,7 @@ internal class ObuReader
sequenceHeader.SuperBlockSize = sequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
sequenceHeader.ModeInfoSize = sequenceHeader.Use128x128SuperBlock ? 32 : 16;
sequenceHeader.SuperBlockSizeLog2 = sequenceHeader.Use128x128SuperBlock ? 7 : 6;
- sequenceHeader.FilterIntraLevel = (int)reader.ReadLiteral(1);
+ sequenceHeader.EnableFilterIntra = reader.ReadBoolean();
sequenceHeader.EnableIntraEdgeFilter = reader.ReadBoolean();
sequenceHeader.EnableInterIntraCompound = false;
sequenceHeader.EnableMaskedCompound = false;
@@ -269,7 +269,7 @@ internal class ObuReader
// Video related flags removed
sequenceHeader.EnableSuperResolution = reader.ReadBoolean();
- sequenceHeader.CdefLevel = (int)reader.ReadLiteral(1);
+ sequenceHeader.EnableCdef = reader.ReadBoolean();
sequenceHeader.EnableRestoration = reader.ReadBoolean();
sequenceHeader.ColorConfig = ReadColorConfig(ref reader, sequenceHeader);
sequenceHeader.AreFilmGrainingParametersPresent = reader.ReadBoolean();
@@ -748,11 +748,7 @@ internal class ObuReader
frameSizeOverrideFlag = reader.ReadBoolean();
}
- frameInfo.OrderHint = 0;
- if (sequenceHeader.OrderHintInfo.OrderHintBits > 0)
- {
- frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits);
- }
+ frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits);
if (isIntraFrame || frameInfo.ErrorResilientMode)
{
@@ -972,11 +968,11 @@ internal class ObuReader
bool doCdef = noIbc && (!frameInfo.CodedLossless &&
(frameInfo.CdefParameters.BitCount != 0 ||
frameInfo.CdefParameters.YStrength[0] != 0 ||
- frameInfo.CdefParameters.UVStrength[0] != 0));
+ frameInfo.CdefParameters.UvStrength[0] != 0));
bool doLoopRestoration = noIbc &&
- (frameInfo.LoopRestorationParameters[(int)Av1Plane.Y].FrameRestorationType != ObuRestorationType.RestoreNone ||
- frameInfo.LoopRestorationParameters[(int)Av1Plane.U].FrameRestorationType != ObuRestorationType.RestoreNone ||
- frameInfo.LoopRestorationParameters[(int)Av1Plane.V].FrameRestorationType != ObuRestorationType.RestoreNone);
+ (frameInfo.LoopRestorationParameters[(int)Av1Plane.Y].Type != ObuRestorationType.None ||
+ frameInfo.LoopRestorationParameters[(int)Av1Plane.U].Type != ObuRestorationType.None ||
+ frameInfo.LoopRestorationParameters[(int)Av1Plane.V].Type != ObuRestorationType.None);
for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++)
{
@@ -1007,7 +1003,7 @@ internal class ObuReader
int deltaQ = 0;
if (reader.ReadBoolean())
{
- deltaQ = (int)reader.ReadLiteral(7);
+ deltaQ = reader.ReadSignedFromUnsigned(6);
}
return deltaQ;
@@ -1042,7 +1038,7 @@ internal class ObuReader
if (frameInfo.DeltaLoopFilterParameters.IsPresent)
{
- frameInfo.DeltaLoopFilterParameters.Resolution = (int)reader.ReadLiteral(4);
+ frameInfo.DeltaLoopFilterParameters.Resolution = (int)reader.ReadLiteral(2);
frameInfo.DeltaLoopFilterParameters.Multi = reader.ReadBoolean();
}
}
@@ -1102,11 +1098,7 @@ internal class ObuReader
private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount)
{
frameInfo.SegmentationParameters.SegmentationEnabled = reader.ReadBoolean();
- if (!frameInfo.SegmentationParameters.SegmentationEnabled)
- {
- // CopyFeatureInfo();
- return;
- }
+ Guard.IsFalse(frameInfo.SegmentationParameters.SegmentationEnabled, nameof(frameInfo.SegmentationParameters.SegmentationEnabled), "Segmentation not supported yet.");
// TODO: Parse more stuff.
}
@@ -1142,6 +1134,9 @@ internal class ObuReader
}
}
+ ///
+ /// See section 5.9.20.
+ ///
private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount)
{
_ = planesCount;
@@ -1153,37 +1148,86 @@ internal class ObuReader
return;
}
- // TODO: Parse more stuff.
+ bool usesLoopRestoration = false;
+ bool usesChromaLoopRestoration = false;
+ for (int i = 0; i < planesCount; i++)
+ {
+ frameInfo.LoopRestorationParameters[i].Type = (ObuRestorationType)reader.ReadLiteral(2);
+ if (frameInfo.LoopRestorationParameters[i].Type != ObuRestorationType.None)
+ {
+ usesLoopRestoration = true;
+ if (i > 0)
+ {
+ usesChromaLoopRestoration = true;
+ }
+ }
+ }
+
+ if (usesLoopRestoration)
+ {
+ uint loopRestorationShift = reader.ReadLiteral(1);
+ if (sequenceHeader.Use128x128SuperBlock)
+ {
+ loopRestorationShift++;
+ }
+ else
+ {
+ if (reader.ReadBoolean())
+ {
+ loopRestorationShift += reader.ReadLiteral(1);
+ }
+ }
+
+ frameInfo.LoopRestorationParameters[0].Size = ObuConstants.RestorationMaxTileSize >> (int)(2 - loopRestorationShift);
+ int uvShift = 0;
+ if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && usesChromaLoopRestoration)
+ {
+ uvShift = (int)reader.ReadLiteral(1);
+ }
+
+ frameInfo.LoopRestorationParameters[1].Size = frameInfo.LoopRestorationParameters[0].Size >> uvShift;
+ frameInfo.LoopRestorationParameters[2].Size = frameInfo.LoopRestorationParameters[0].Size >> uvShift;
+ }
}
+ ///
+ /// See section 5.9.19.
+ ///
private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount)
{
- _ = planesCount;
+ ObuConstraintDirectionalEnhancementFilterParameters cdefInfo = frameInfo.CdefParameters;
if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy || sequenceHeader.CdefLevel == 0)
{
- frameInfo.CdefParameters.BitCount = 0;
- frameInfo.CdefParameters.YStrength[0] = 0;
- frameInfo.CdefParameters.YStrength[4] = 0;
- frameInfo.CdefParameters.UVStrength[0] = 0;
- frameInfo.CdefParameters.UVStrength[4] = 0;
- frameInfo.CdefParameters.Damping = 0;
+ cdefInfo.BitCount = 0;
+ cdefInfo.YStrength[0] = 0;
+ cdefInfo.YStrength[4] = 0;
+ cdefInfo.UvStrength[0] = 0;
+ cdefInfo.UvStrength[4] = 0;
+ cdefInfo.Damping = 0;
return;
}
- // TODO: Parse more stuff.
+ cdefInfo.Damping = (int)reader.ReadLiteral(2) + 3;
+ cdefInfo.BitCount = (int)reader.ReadLiteral(2);
+ for (int i = 0; i < (1 << frameInfo.CdefParameters.BitCount); i++)
+ {
+ cdefInfo.YStrength[i] = (int)reader.ReadLiteral(6);
+
+ if (planesCount > 1)
+ {
+ cdefInfo.UvStrength[i] = (int)reader.ReadLiteral(6);
+ }
+ }
}
private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame)
{
- _ = reader;
- _ = sequenceHeader;
- _ = frameInfo;
if (isIntraFrame)
{
return;
}
- // TODO: Parse more stuff.
+ // Not applicable for INTRA frames.
}
private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader reader, bool isIntraFrame)
@@ -1204,7 +1248,7 @@ internal class ObuReader
}
else
{
- // TODO: Parse more stuff.
+ // Not applicable for INTRA frames.
}
if (frameInfo.SkipModeParameters.SkipModeAllowed)
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs
index 02ab7cd1e4..bf90f5021d 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs
@@ -3,7 +3,10 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
-internal enum ObuRestorationType
+internal enum ObuRestorationType : uint
{
- RestoreNone
+ None = 0,
+ Switchable = 1,
+ Weiner = 2,
+ SgrProj = 3,
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs
index 652734c01b..4f94e26503 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs
@@ -5,6 +5,10 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuSequenceHeader
{
+ internal bool EnableFilterIntra { get; set; }
+
+ internal bool EnableCdef { get; set; }
+
internal bool IsStillPicture { get; set; }
internal bool IsReducedStillPictureHeader { get; set; }
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
new file mode 100644
index 0000000000..0843e3967c
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
@@ -0,0 +1,583 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
+
+internal class ObuWriter
+{
+ ///
+ /// Encode a single frame into OBU's.
+ ///
+ public static void Write(Stream stream, IAv1TileDecoder decoder)
+ {
+ MemoryStream bufferStream = new(100);
+ Av1BitStreamWriter writer = new(bufferStream);
+ WriteSequenceHeader(ref writer, decoder.SequenceHeader);
+ WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, bufferStream.GetBuffer(), (int)bufferStream.Position);
+
+ bufferStream.Position = 0;
+ WriteFrameHeader(ref writer, decoder, true);
+ WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), (int)bufferStream.Position);
+
+ bufferStream.Position = 0;
+ WriteTileGroup(ref writer, decoder.TileInfo);
+ WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), (int)bufferStream.Position);
+ }
+
+ private static void WriteObuHeader(ref Av1BitStreamWriter writer, ObuType type)
+ {
+ writer.WriteBoolean(false); // Forbidden bit
+ writer.WriteLiteral((uint)type, 4);
+ writer.WriteBoolean(false); // Extension
+ writer.WriteBoolean(true); // HasSize
+ writer.WriteBoolean(false); // Reserved
+ }
+
+ ///
+ /// Read OBU header and size.
+ ///
+ private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span payload, int length)
+ {
+ Av1BitStreamWriter writer = new(stream);
+ WriteObuHeader(ref writer, type);
+ _ = writer.WriteLittleEndianBytes128(length);
+ stream.Write(payload, 0, length);
+ }
+
+ ///
+ /// Write trsainling bits to end on a byte boundary, these trailing bits start with a 1 and end with 0s.
+ ///
+ /// Write an additional byte, if already byte aligned before.
+ private static void WriteTrailingBits(ref Av1BitStreamWriter writer)
+ {
+ int bitsBeforeAlignment = 8 - (writer.BitPosition & 0x7);
+ writer.WriteLiteral(0, bitsBeforeAlignment);
+ }
+
+ private static void AlignToByteBoundary(ref Av1BitStreamWriter writer)
+ {
+ while ((writer.BitPosition & 0x7) > 0)
+ {
+ writer.WriteBoolean(false);
+ }
+ }
+
+ private static bool IsValidObuType(ObuType type) => type switch
+ {
+ ObuType.SequenceHeader or ObuType.TemporalDelimiter or ObuType.FrameHeader or
+ ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrameHeader or
+ ObuType.TileList or ObuType.Padding => true,
+ _ => false,
+ };
+
+ private static void WriteSequenceHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader)
+ {
+ writer.WriteLiteral((uint)sequenceHeader.SequenceProfile, 3);
+ writer.WriteBoolean(true); // IsStillPicture
+ writer.WriteBoolean(true); // IsReducedStillPicture
+ writer.WriteLiteral((uint)sequenceHeader.OperatingPoint[0].SequenceLevelIndex, ObuConstants.LevelBits);
+
+ // Frame width and Height
+ writer.WriteLiteral((uint)sequenceHeader.FrameWidthBits - 1, 4);
+ writer.WriteLiteral((uint)sequenceHeader.FrameHeightBits - 1, 4);
+ writer.WriteLiteral((uint)sequenceHeader.MaxFrameWidth - 1, sequenceHeader.FrameWidthBits);
+ writer.WriteLiteral((uint)sequenceHeader.MaxFrameHeight - 1, sequenceHeader.FrameHeightBits);
+
+ // Video related flags removed
+ writer.WriteBoolean(sequenceHeader.Use128x128SuperBlock);
+ writer.WriteBoolean(sequenceHeader.EnableFilterIntra);
+ writer.WriteBoolean(sequenceHeader.EnableIntraEdgeFilter);
+
+ // Video related flags removed
+ writer.WriteBoolean(sequenceHeader.EnableSuperResolution);
+ writer.WriteBoolean(sequenceHeader.EnableCdef);
+ writer.WriteBoolean(sequenceHeader.EnableRestoration);
+ WriteColorConfig(ref writer, sequenceHeader);
+ writer.WriteBoolean(sequenceHeader.AreFilmGrainingParametersPresent);
+ WriteTrailingBits(ref writer);
+ }
+
+ private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader)
+ {
+ ObuColorConfig colorConfig = new();
+ WriteBitDepth(ref writer, colorConfig, sequenceHeader);
+ if (sequenceHeader.SequenceProfile != ObuSequenceProfile.High)
+ {
+ writer.WriteBoolean(colorConfig.Monochrome);
+ }
+
+ writer.WriteBoolean(false); // colorConfig.IsColorDescriptionPresent
+ if (colorConfig.Monochrome)
+ {
+ writer.WriteBoolean(colorConfig.ColorRange);
+ return;
+ }
+ else if (
+ colorConfig.ColorPrimaries == ObuColorPrimaries.Bt709 &&
+ colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb &&
+ colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity)
+ {
+ colorConfig.ColorRange = true;
+ colorConfig.SubSamplingX = false;
+ colorConfig.SubSamplingY = false;
+ }
+ else
+ {
+ writer.WriteBoolean(colorConfig.ColorRange);
+ if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && colorConfig.BitDepth == 12)
+ {
+ writer.WriteBoolean(colorConfig.SubSamplingX);
+ if (colorConfig.SubSamplingX)
+ {
+ writer.WriteBoolean(colorConfig.SubSamplingY);
+ }
+ }
+
+ if (colorConfig.SubSamplingX && colorConfig.SubSamplingY)
+ {
+ writer.WriteLiteral((uint)colorConfig.ChromaSamplePosition, 2);
+ }
+ }
+
+ writer.WriteBoolean(colorConfig.HasSeparateUvDeltaQ);
+ }
+
+ private static void WriteBitDepth(ref Av1BitStreamWriter writer, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader)
+ {
+ bool hasHighBitDepth = colorConfig.BitDepth > 8;
+ writer.WriteBoolean(hasHighBitDepth);
+ if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && hasHighBitDepth)
+ {
+ writer.WriteBoolean(colorConfig.BitDepth == 12);
+ }
+ }
+
+ private static void WriteSuperResolutionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo)
+ {
+ bool useSuperResolution = false;
+ if (sequenceHeader.EnableSuperResolution)
+ {
+ writer.WriteBoolean(useSuperResolution);
+ }
+
+ if (useSuperResolution)
+ {
+ writer.WriteLiteral((uint)frameInfo.FrameSize.SuperResolutionDenominator - ObuConstants.SuperResolutionScaleDenominatorMinimum, ObuConstants.SuperResolutionScaleBits);
+ }
+ }
+
+ private static void WriteRenderSize(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo)
+ {
+ bool renderSizeAndFrameSizeDifferent = false;
+ writer.WriteBoolean(false);
+ if (renderSizeAndFrameSizeDifferent)
+ {
+ writer.WriteLiteral((uint)frameInfo.FrameSize.RenderWidth - 1, 16);
+ writer.WriteLiteral((uint)frameInfo.FrameSize.RenderHeight - 1, 16);
+ }
+ }
+
+ private static void WriteFrameSizeWithReferences(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag)
+ {
+ bool foundReference = false;
+ for (int i = 0; i < ObuConstants.ReferencesPerFrame; i++)
+ {
+ writer.WriteBoolean(foundReference);
+ if (foundReference)
+ {
+ // Take values over from reference frame
+ break;
+ }
+ }
+
+ if (!foundReference)
+ {
+ WriteFrameSize(ref writer, sequenceHeader, frameInfo, frameSizeOverrideFlag);
+ WriteRenderSize(ref writer, frameInfo);
+ }
+ else
+ {
+ WriteSuperResolutionParameters(ref writer, sequenceHeader, frameInfo);
+ }
+ }
+
+ private static void WriteFrameSize(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag)
+ {
+ if (frameSizeOverrideFlag)
+ {
+ writer.WriteLiteral((uint)frameInfo.FrameSize.FrameWidth - 1, sequenceHeader.FrameWidthBits + 1);
+ writer.WriteLiteral((uint)frameInfo.FrameSize.FrameHeight - 1, sequenceHeader.FrameHeightBits + 1);
+ }
+
+ WriteSuperResolutionParameters(ref writer, sequenceHeader, frameInfo);
+ }
+
+ private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuTileInfo tileInfo)
+ {
+ Guard.IsTrue(tileInfo.HasUniformTileSpacing, nameof(tileInfo.HasUniformTileSpacing), "NON uniform_tile_spacing_flag not supported yet.");
+
+ writer.WriteBoolean(tileInfo.HasUniformTileSpacing);
+ if (tileInfo.HasUniformTileSpacing)
+ {
+ for (int i = 0; i < tileInfo.TileColumnCountLog2; i++)
+ {
+ writer.WriteBoolean(true);
+ }
+
+ if (tileInfo.TileColumnCountLog2 < tileInfo.MaxLog2TileColumnCount)
+ {
+ writer.WriteBoolean(false);
+ }
+
+ for (int i = 0; i < tileInfo.TileRowCountLog2; i++)
+ {
+ writer.WriteBoolean(true);
+ }
+
+ if (tileInfo.TileRowCountLog2 < tileInfo.MaxLog2TileRowCount)
+ {
+ writer.WriteBoolean(false);
+ }
+ }
+ else
+ {
+ throw new NotImplementedException("NON uniform_tile_spacing_flag not supported yet.");
+ }
+
+ if (tileInfo.TileColumnCountLog2 > 0 || tileInfo.TileRowCountLog2 > 0)
+ {
+ writer.WriteLiteral(tileInfo.ContextUpdateTileId, tileInfo.TileRowCountLog2 + tileInfo.TileColumnCountLog2);
+ writer.WriteLiteral((uint)tileInfo.TileSizeBytes - 1, 2);
+ }
+ }
+
+ private static void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount)
+ {
+ uint previousFrameId = 0;
+ bool isIntraFrame = true;
+ int idLength = sequenceHeader.FrameIdLength - 1 + sequenceHeader.DeltaFrameIdLength - 2 + 3;
+ writer.WriteBoolean(frameInfo.DisableCdfUpdate);
+ if (frameInfo.AllowScreenContentTools)
+ {
+ writer.WriteBoolean(frameInfo.AllowScreenContentTools);
+ }
+
+ if (frameInfo.AllowScreenContentTools)
+ {
+ if (sequenceHeader.SequenceForceIntegerMotionVector == 1)
+ {
+ writer.WriteBoolean(frameInfo.ForceIntegerMotionVector);
+ }
+ }
+
+ bool havePreviousFrameId = !(frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame);
+ if (havePreviousFrameId)
+ {
+ previousFrameId = frameInfo.CurrentFrameId;
+ }
+
+ if (sequenceHeader.IsFrameIdNumbersPresent)
+ {
+ writer.WriteLiteral(frameInfo.CurrentFrameId, idLength);
+ if (havePreviousFrameId)
+ {
+ uint diffFrameId = (frameInfo.CurrentFrameId > previousFrameId) ?
+ frameInfo.CurrentFrameId - previousFrameId :
+ (uint)((1 << idLength) + (int)frameInfo.CurrentFrameId - previousFrameId);
+ if (frameInfo.CurrentFrameId == previousFrameId || diffFrameId >= 1 << (idLength - 1))
+ {
+ throw new ImageFormatException("Current frame ID cannot be same as previous Frame ID");
+ }
+ }
+
+ int diffLength = sequenceHeader.DeltaFrameIdLength;
+ for (int i = 0; i < ObuConstants.ReferenceFrameCount; i++)
+ {
+ if (frameInfo.CurrentFrameId > (1U << diffLength))
+ {
+ if ((frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId) ||
+ frameInfo.ReferenceFrameIndex[i] > (frameInfo.CurrentFrameId - (1 - diffLength)))
+ {
+ frameInfo.ReferenceValid[i] = false;
+ }
+ }
+ else if (frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId &&
+ frameInfo.ReferenceFrameIndex[i] < ((1 << idLength) + (frameInfo.CurrentFrameId - (1 << diffLength))))
+ {
+ frameInfo.ReferenceValid[i] = false;
+ }
+ }
+ }
+
+ writer.WriteLiteral(frameInfo.OrderHint, sequenceHeader.OrderHintInfo.OrderHintBits);
+
+ if (!isIntraFrame && !frameInfo.ErrorResilientMode)
+ {
+ writer.WriteLiteral(frameInfo.PrimaryReferenceFrame, ObuConstants.PimaryReferenceBits);
+ }
+
+ // Skipping, as no decoder info model present
+ 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);
+ }
+
+ if (isIntraFrame)
+ {
+ WriteFrameSize(ref writer, sequenceHeader, frameInfo, false);
+ WriteRenderSize(ref writer, frameInfo);
+ if (frameInfo.AllowScreenContentTools && frameInfo.FrameSize.RenderWidth != 0)
+ {
+ if (frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth)
+ {
+ writer.WriteBoolean(frameInfo.AllowIntraBlockCopy);
+ }
+ }
+ }
+
+ if (frameInfo.PrimaryReferenceFrame == ObuConstants.PrimaryReferenceFrameNone)
+ {
+ SetupPastIndependence(frameInfo);
+ }
+
+ // GenerateNextReferenceFrameMap(sequenceHeader, frameInfo);
+ WriteTileInfo(ref writer, frameInfo.TilesInfo);
+ WriteQuantizationParameters(ref writer, frameInfo.QuantizationParameters, sequenceHeader.ColorConfig, planesCount);
+ WriteSegmentationParameters(ref writer, sequenceHeader, frameInfo, planesCount);
+ WriteFrameDeltaQParameters(ref writer, frameInfo);
+ WriteFrameDeltaLoopFilterParameters(ref writer, frameInfo);
+
+ 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.
+ // WriteFrameReferenceMode(ref writer, frameInfo.ReferenceMode, isIntraFrame);
+ // WriteSkipModeParameters(ref writer, sequenceHeader, frameInfo, isIntraFrame, frameInfo.ReferenceMode);
+ writer.WriteBoolean(frameInfo.ReducedTransformSet);
+
+ // Not applicable for INTRA frames.
+ // WriteGlobalMotionParameters(ref writer, sequenceHeader, frameInfo, isIntraFrame);
+ WriteFilmGrainFilterParameters(ref writer, frameInfo.FilmGrainParameters);
+ }
+
+ private static void SetupPastIndependence(ObuFrameHeader frameInfo)
+ {
+ // TODO: Initialize the loop filter parameters.
+ }
+
+ private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature)
+ => segmentationParameters.SegmentationEnabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature];
+
+ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex)
+ {
+ if (IsSegmentationFeatureActive(segmentationParameters, segmentId, ObuSegmentationLevelFeature.AlternativeQuantizer))
+ {
+ int data = segmentationParameters.FeatureData[segmentId, (int)ObuSegmentationLevelFeature.AlternativeQuantizer];
+ int qIndex = baseQIndex + data;
+ return Av1Math.Clamp(qIndex, 0, ObuConstants.MaxQ);
+ }
+ else
+ {
+ return baseQIndex;
+ }
+ }
+
+ private static int WriteFrameHeader(ref Av1BitStreamWriter writer, IAv1TileDecoder decoder, bool writeTrailingBits)
+ {
+ ObuSequenceHeader sequenceHeader = decoder.SequenceHeader;
+ ObuFrameHeader frameInfo = decoder.FrameInfo;
+ int planeCount = sequenceHeader.ColorConfig.Monochrome ? 1 : 3;
+ int startBitPosition = writer.BitPosition;
+ WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameInfo, planeCount);
+ if (writeTrailingBits)
+ {
+ WriteTrailingBits(ref writer);
+ }
+
+ AlignToByteBoundary(ref writer);
+
+ int endPosition = writer.BitPosition;
+ int headerBytes = (endPosition - startBitPosition) / 8;
+ return headerBytes;
+ }
+
+ private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileInfo tileInfo)
+ {
+ int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
+ int startBitPosition = writer.BitPosition;
+ bool tileStartAndEndPresentFlag = tileCount != 0;
+ writer.WriteBoolean(tileStartAndEndPresentFlag);
+
+ uint tileGroupStart = 0U;
+ uint tileGroupEnd = (uint)tileCount - 1U;
+ if (tileCount != 1)
+ {
+ int tileBits = Av1Math.Log2(tileInfo.TileColumnCount) + Av1Math.Log2(tileInfo.TileRowCount);
+ writer.WriteLiteral(tileGroupStart, tileBits);
+ writer.WriteLiteral(tileGroupEnd, tileBits);
+ }
+
+ AlignToByteBoundary(ref writer);
+ int endBitPosition = writer.BitPosition;
+ int headerBytes = (endBitPosition - startBitPosition) / 8;
+ return headerBytes;
+ }
+
+ private static int WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ)
+ {
+ bool isCoded = deltaQ == 0;
+ writer.WriteBoolean(isCoded);
+ if (isCoded)
+ {
+ writer.WriteSignedFromUnsigned(deltaQ, 7);
+ }
+
+ return deltaQ;
+ }
+
+ private static void WriteFrameDeltaQParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo)
+ {
+ if (frameInfo.QuantizationParameters.BaseQIndex > 0)
+ {
+ writer.WriteBoolean(frameInfo.DeltaQParameters.IsPresent);
+ }
+
+ if (frameInfo.DeltaQParameters.IsPresent)
+ {
+ writer.WriteLiteral((uint)frameInfo.DeltaQParameters.Resolution, 2);
+ }
+ }
+
+ private static void WriteFrameDeltaLoopFilterParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo)
+ {
+ if (frameInfo.DeltaQParameters.IsPresent)
+ {
+ if (!frameInfo.AllowIntraBlockCopy)
+ {
+ writer.WriteBoolean(frameInfo.DeltaLoopFilterParameters.IsPresent);
+ }
+
+ if (frameInfo.DeltaLoopFilterParameters.IsPresent)
+ {
+ writer.WriteLiteral((uint)frameInfo.DeltaLoopFilterParameters.Resolution, 2);
+ writer.WriteBoolean(frameInfo.DeltaLoopFilterParameters.Multi);
+ }
+ }
+ }
+
+ ///
+ /// See section 5.9.12.
+ ///
+ private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, ObuQuantizationParameters quantParams, ObuColorConfig colorInfo, int planesCount)
+ {
+ writer.WriteLiteral((uint)quantParams.BaseQIndex, 8);
+ WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.Y]);
+ if (planesCount > 1)
+ {
+ bool areUvDeltaDifferent = false;
+ WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.U]);
+ WriteDeltaQ(ref writer, quantParams.DeltaQAc[(int)Av1Plane.U]);
+ if (areUvDeltaDifferent)
+ {
+ WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.V]);
+ WriteDeltaQ(ref writer, quantParams.DeltaQAc[(int)Av1Plane.V]);
+ }
+ }
+
+ writer.WriteBoolean(quantParams.IsUsingQMatrix);
+ if (quantParams.IsUsingQMatrix)
+ {
+ writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.Y], 4);
+ writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.U], 4);
+ if (colorInfo.HasSeparateUvDeltaQ)
+ {
+ writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.V], 4);
+ }
+ }
+ }
+
+ private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount)
+ {
+ Guard.IsFalse(frameInfo.SegmentationParameters.SegmentationEnabled, nameof(frameInfo.SegmentationParameters.SegmentationEnabled), "Segmentatino not supported yet.");
+ writer.WriteBoolean(false);
+ }
+
+ private static void WriteLoopFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount)
+ {
+ _ = writer;
+ _ = sequenceHeader;
+ _ = frameInfo;
+ _ = planesCount;
+
+ // TODO: Parse more stuff.
+ }
+
+ private static void WriteTransformMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo)
+ {
+ if (!frameInfo.CodedLossless)
+ {
+ writer.WriteBoolean(frameInfo.TransformMode == Av1TransformMode.Select);
+ }
+ }
+
+ private static void WriteLoopRestorationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount)
+ {
+ _ = writer;
+ _ = sequenceHeader;
+ _ = frameInfo;
+ _ = planesCount;
+
+ // TODO: Parse more stuff.
+ }
+
+ private static void WriteCdefParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount)
+ {
+ _ = writer;
+ _ = sequenceHeader;
+ _ = frameInfo;
+ _ = planesCount;
+
+ // TODO: Parse more stuff.
+ }
+
+ private static void WriteGlobalMotionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame)
+ {
+ _ = writer;
+ _ = sequenceHeader;
+ _ = frameInfo;
+
+ // Nothing to be written for INTRA frames.
+ Guard.IsTrue(isIntraFrame, nameof(isIntraFrame), "Still picture contains only INTRA frames.");
+ }
+
+ private static void WriteFrameReferenceMode(ref Av1BitStreamWriter writer, bool isIntraFrame)
+ {
+ _ = writer;
+
+ // Nothing to be written for INTRA frames.
+ Guard.IsTrue(isIntraFrame, nameof(isIntraFrame), "Still picture contains only INTRA frames.");
+ }
+
+ private static void WriteSkipModeParameters(ref Av1BitStreamWriter writer, bool isIntraFrame)
+ {
+ _ = writer;
+
+ // Nothing to be written for INTRA frames.
+ Guard.IsTrue(isIntraFrame, nameof(isIntraFrame), "Still picture contains only INTRA frames.");
+ }
+
+ private static void WriteFilmGrainFilterParameters(ref Av1BitStreamWriter writer, ObuFilmGrainParameters filmGrainInfo)
+ {
+ _ = writer;
+ _ = filmGrainInfo;
+
+ // Film grain filter not supported yet
+ }
+}