Browse Source

Document and correct HEIF quantization

pull/2633/head
James Jackson-South 2 months ago
parent
commit
726b559fcf
  1. 46
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs
  2. 26
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizationLookup.cs
  3. 52
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs
  4. 45
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs

46
src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs

@ -5,14 +5,28 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
/// <summary>
/// Stores the AV1 DC and AC dequantization values for every segment and color plane in a frame.
/// </summary>
internal class Av1DeQuantizationContext internal class Av1DeQuantizationContext
{ {
/// <summary>
/// The DC dequantization values indexed by segment and then plane.
/// </summary>
private readonly short[][] dcContent; private readonly short[][] dcContent;
/// <summary>
/// The AC dequantization values indexed by segment and then plane.
/// </summary>
private readonly short[][] acContent; private readonly short[][] acContent;
/// <remarks> /// <summary>
/// SVT: svt_aom_setup_segmentation_dequant /// Initializes a new instance of the <see cref="Av1DeQuantizationContext"/> class from the frame's base quantizer,
/// </remarks> /// segment adjustments, plane deltas, and coded bit depth.
/// </summary>
/// <param name="sequenceHeader">The sequence header that supplies the coded bit depth.</param>
/// <param name="frameHeader">The frame header that supplies segmentation and quantization parameters.</param>
/// <remarks>SVT-AV1: <c>svt_aom_setup_segmentation_dequant</c>.</remarks>
public Av1DeQuantizationContext(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) public Av1DeQuantizationContext(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
Av1BitDepth bitDepth = sequenceHeader.ColorConfig.BitDepth; Av1BitDepth bitDepth = sequenceHeader.ColorConfig.BitDepth;
@ -35,15 +49,39 @@ internal class Av1DeQuantizationContext
} }
} }
/// <summary>
/// Gets the DC dequantization value for a segment and color plane.
/// </summary>
/// <param name="segmentId">The zero-based AV1 segment identifier.</param>
/// <param name="plane">The color plane.</param>
/// <returns>The DC dequantization value.</returns>
public short GetDc(int segmentId, Av1Plane plane) public short GetDc(int segmentId, Av1Plane plane)
=> this.dcContent[segmentId][(int)plane]; => this.dcContent[segmentId][(int)plane];
/// <summary>
/// Gets the AC dequantization value for a segment and color plane.
/// </summary>
/// <param name="segmentId">The zero-based AV1 segment identifier.</param>
/// <param name="plane">The color plane.</param>
/// <returns>The AC dequantization value.</returns>
public short GetAc(int segmentId, Av1Plane plane) public short GetAc(int segmentId, Av1Plane plane)
=> this.acContent[segmentId][(int)plane]; => this.acContent[segmentId][(int)plane];
/// <summary>
/// Sets the AC dequantization value for a segment and color plane.
/// </summary>
/// <param name="segmentId">The zero-based AV1 segment identifier.</param>
/// <param name="plane">The color plane.</param>
/// <param name="value">The AC dequantization value.</param>
public void SetAc(int segmentId, Av1Plane plane, short value) public void SetAc(int segmentId, Av1Plane plane, short value)
=> this.dcContent[segmentId][(int)plane] = value; => this.acContent[segmentId][(int)plane] = value;
/// <summary>
/// Sets the DC dequantization value for a segment and color plane.
/// </summary>
/// <param name="segmentId">The zero-based AV1 segment identifier.</param>
/// <param name="plane">The color plane.</param>
/// <param name="value">The DC dequantization value.</param>
public void SetDc(int segmentId, Av1Plane plane, short value) public void SetDc(int segmentId, Av1Plane plane, short value)
=> this.dcContent[segmentId][(int)plane] = value; => this.dcContent[segmentId][(int)plane] = value;
} }

26
src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizationLookup.cs

@ -6,22 +6,23 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
/// <summary>
/// Provides the normative AV1 inverse quantization matrices for each matrix level, plane class, and transform size.
/// </summary>
internal class Av1InverseQuantizationLookup internal class Av1InverseQuantizationLookup
{ {
// AV1 reuses the adjusted matrix for 64-pixel transform dimensions, while the stored tables omit those duplicate entries. /// <summary>
/// Maps each AV1 transform size to its stored matrix index; sizes with a 64-pixel dimension reuse the adjusted 32-pixel matrix.
/// </summary>
private static readonly byte[] TransformMatrixIndices = private static readonly byte[] TransformMatrixIndices =
[ [
0, 1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 3, 3, 10, 11, 12, 13, 8, 9 0, 1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 3, 3, 10, 11, 12, 13, 8, 9
]; ];
/// <summary> /// <summary>
/// Gets 16 sets of quantization matrices for chroma and luma and each TX size. /// The inverse matrix weights indexed by matrix level, plane class, adjusted transform-size index, and raster coefficient.
/// Matrices for different TX sizes are in fact sub-sampled from the 32x32 and 16x16 sizes, /// Luma and chroma have separate matrices; U and V share the chroma set. Size-specific matrices are subsampled from the
/// but explicitly defined here for convenience. Intra and inter matrix sets are the /// normative 32x32 and 16x16 bases, and matrix levels represent different frequency-domain weighting strengths.
/// same but changing DEFAULT_QM_INTER_OFFSET from zero allows for different matrices
/// for inter and intra blocks in the same frame.
/// Matrices for different QM levels have been rescaled in the frequency domain according
/// to different nominal viewing distances.
/// </summary> /// </summary>
private static readonly int[][][][] InverseWeightTable = private static readonly int[][][][] InverseWeightTable =
[ [
@ -6802,9 +6803,18 @@ internal class Av1InverseQuantizationLookup
] ]
]; ];
/// <summary>
/// Gets the inverse quantization matrix for a matrix level, color plane, and transform size.
/// </summary>
/// <param name="level">The quantization-matrix level.</param>
/// <param name="plane">The color plane; U and V select the shared chroma matrix.</param>
/// <param name="transformSize">The transform size whose raster coefficient weights are requested.</param>
/// <returns>The inverse matrix weights in raster coefficient order.</returns>
public static ReadOnlySpan<int> GetQuantizationMatrix(int level, Av1Plane plane, Av1TransformSize transformSize) public static ReadOnlySpan<int> GetQuantizationMatrix(int level, Av1Plane plane, Av1TransformSize transformSize)
{ {
int[][][] levelMatrices = InverseWeightTable[level]; int[][][] levelMatrices = InverseWeightTable[level];
// The table stores one luma plane class and one shared chroma plane class.
int[][] planeMatrices = levelMatrices[Math.Min(1, (int)plane)]; int[][] planeMatrices = levelMatrices[Math.Min(1, (int)plane)];
int transformMatrixIndex = TransformMatrixIndices[(int)transformSize]; int transformMatrixIndex = TransformMatrixIndices[(int)transformSize];

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

@ -7,12 +7,31 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
/// <summary>
/// Reconstructs AV1 transform coefficients from quantized coefficient levels.
/// </summary>
internal class Av1InverseQuantizer internal class Av1InverseQuantizer
{ {
/// <summary>
/// The sequence-level color configuration that determines coefficient precision.
/// </summary>
private readonly ObuSequenceHeader sequenceHeader; private readonly ObuSequenceHeader sequenceHeader;
/// <summary>
/// The frame-level segmentation and quantization configuration.
/// </summary>
private readonly ObuFrameHeader frameHeader; private readonly ObuFrameHeader frameHeader;
/// <summary>
/// The current per-segment, per-plane dequantization values, including any superblock delta-Q update.
/// </summary>
private Av1DeQuantizationContext deQuantsDeltaQ; private Av1DeQuantizationContext deQuantsDeltaQ;
/// <summary>
/// Initializes a new instance of the <see cref="Av1InverseQuantizer"/> class.
/// </summary>
/// <param name="sequenceHeader">The sequence header that supplies coded bit depth and color configuration.</param>
/// <param name="frameHeader">The frame header that supplies segmentation and quantization parameters.</param>
public Av1InverseQuantizer(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) public Av1InverseQuantizer(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{ {
this.sequenceHeader = sequenceHeader; this.sequenceHeader = sequenceHeader;
@ -20,6 +39,11 @@ internal class Av1InverseQuantizer
this.deQuantsDeltaQ = new(sequenceHeader, frameHeader); this.deQuantsDeltaQ = new(sequenceHeader, frameHeader);
} }
/// <summary>
/// Updates the active dequantization context for a superblock, applying its delta-Q value when signaled.
/// </summary>
/// <param name="deQuants">The frame dequantization context to update and retain.</param>
/// <param name="superblockInfo">The superblock whose quantizer adjustment is applied.</param>
public void UpdateDequant(Av1DeQuantizationContext deQuants, Av1SuperblockInfo superblockInfo) public void UpdateDequant(Av1DeQuantizationContext deQuants, Av1SuperblockInfo superblockInfo)
{ {
Av1BitDepth bitDepth = this.sequenceHeader.ColorConfig.BitDepth; Av1BitDepth bitDepth = this.sequenceHeader.ColorConfig.BitDepth;
@ -44,19 +68,32 @@ internal class Av1InverseQuantizer
} }
/// <summary> /// <summary>
/// SVT: svt_aom_inverse_quantize /// Converts scan-ordered quantized levels into clamped, raster-ordered transform coefficients.
/// </summary> /// </summary>
/// <param name="mode">The block mode information containing the active segment identifier.</param>
/// <param name="level">The packed coefficient buffer: the first element is the coefficient count and the remaining elements are scan-ordered levels.</param>
/// <param name="qCoefficients">The destination for raster-ordered dequantized coefficients.</param>
/// <param name="transformType">The transform type that selects the coefficient scan and matrix class.</param>
/// <param name="transformSize">The transform dimensions and scale.</param>
/// <param name="plane">The color plane whose quantizer and matrix are used.</param>
/// <returns>The number of coefficient levels consumed.</returns>
/// <remarks>SVT-AV1: <c>svt_aom_inverse_quantize</c>.</remarks>
public int InverseQuantize(Av1BlockModeInfo mode, Span<int> level, Span<int> qCoefficients, Av1TransformType transformType, Av1TransformSize transformSize, Av1Plane plane) public int InverseQuantize(Av1BlockModeInfo mode, Span<int> level, Span<int> qCoefficients, Av1TransformType transformType, Av1TransformSize transformSize, Av1Plane plane)
{ {
Guard.NotNull(this.deQuantsDeltaQ); Guard.NotNull(this.deQuantsDeltaQ);
Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType);
ReadOnlySpan<short> scanIndices = scanOrder.Scan; ReadOnlySpan<short> scanIndices = scanOrder.Scan;
// AV1 bounds reconstructed coefficients to a signed range with seven headroom bits beyond pixel precision.
int maxValue = (1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount())) - 1; int maxValue = (1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount())) - 1;
int minValue = -(1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount())); int minValue = -(1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount()));
bool usingQuantizationMatrix = this.frameHeader.QuantizationParameters.IsUsingQMatrix; bool usingQuantizationMatrix = this.frameHeader.QuantizationParameters.IsUsingQMatrix;
bool lossless = this.frameHeader.LosslessArray[mode.SegmentId]; bool lossless = this.frameHeader.LosslessArray[mode.SegmentId];
short dequantDc = this.deQuantsDeltaQ.GetDc(mode.SegmentId, plane); short dequantDc = this.deQuantsDeltaQ.GetDc(mode.SegmentId, plane);
short dequantAc = this.deQuantsDeltaQ.GetAc(mode.SegmentId, plane); short dequantAc = this.deQuantsDeltaQ.GetAc(mode.SegmentId, plane);
// The final matrix level is flat. Lossless blocks, frames without matrices, and one-dimensional transforms
// must use it so coefficient frequency does not change the signaled dequantization value.
int qmLevel = lossless || !usingQuantizationMatrix int qmLevel = lossless || !usingQuantizationMatrix
? Av1ScanOrderConstants.QuantizationMatrixLevelCount - 1 ? Av1ScanOrderConstants.QuantizationMatrixLevelCount - 1
: this.frameHeader.SegmentationParameters.QMLevel[(int)plane][mode.SegmentId]; : this.frameHeader.SegmentationParameters.QMLevel[(int)plane][mode.SegmentId];
@ -67,6 +104,7 @@ internal class Av1InverseQuantizer
int shift = transformSize.GetScale(); int shift = transformSize.GetScale();
// Entropy decoding stores the populated coefficient count in the leading slot and the levels after it.
int coefficientCount = level[0]; int coefficientCount = level[0];
level = level[1..]; level = level[1..];
int lev = level[0]; int lev = level[0];
@ -74,6 +112,8 @@ internal class Av1InverseQuantizer
if (lev != 0) if (lev != 0)
{ {
int pos = scanIndices[0]; int pos = scanIndices[0];
// Preserve the AV1 24-bit dequantization intermediate before removing transform-size scaling.
qCoefficient = (int)(((long)Math.Abs(lev) * GetDeQuantizedValue(dequantDc, pos, iqMatrix)) & 0xffffff); qCoefficient = (int)(((long)Math.Abs(lev) * GetDeQuantizedValue(dequantDc, pos, iqMatrix)) & 0xffffff);
qCoefficient >>= shift; qCoefficient >>= shift;
@ -91,6 +131,8 @@ internal class Av1InverseQuantizer
if (lev != 0) if (lev != 0)
{ {
int pos = scanIndices[i]; int pos = scanIndices[i];
// AC levels arrive in entropy scan order but the inverse transform consumes raster positions.
qCoefficient = (int)(((long)Math.Abs(lev) * GetDeQuantizedValue(dequantAc, pos, iqMatrix)) & 0xffffff); qCoefficient = (int)(((long)Math.Abs(lev) * GetDeQuantizedValue(dequantAc, pos, iqMatrix)) & 0xffffff);
qCoefficient >>= shift; qCoefficient >>= shift;
@ -107,10 +149,16 @@ internal class Av1InverseQuantizer
} }
/// <summary> /// <summary>
/// SVT: get_dqv /// Applies an inverse quantization-matrix weight to a plane dequantization value.
/// </summary> /// </summary>
/// <param name="dequant">The unweighted DC or AC dequantization value.</param>
/// <param name="coefficientIndex">The raster coefficient index into the inverse matrix.</param>
/// <param name="iqMatrix">The inverse quantization matrix for the current level, plane, and transform size.</param>
/// <returns>The matrix-weighted dequantization value.</returns>
/// <remarks>SVT-AV1: <c>get_dqv</c>.</remarks>
private static int GetDeQuantizedValue(short dequant, int coefficientIndex, ReadOnlySpan<int> iqMatrix) private static int GetDeQuantizedValue(short dequant, int coefficientIndex, ReadOnlySpan<int> iqMatrix)
{ {
// Matrix elements use fixed-point precision; adding half a unit produces nearest-integer rounding on shift.
const int bias = 1 << (Av1Constants.QuantizationMatrixElementBitCount - 1); const int bias = 1 << (Av1Constants.QuantizationMatrixElementBitCount - 1);
int deQuantifiedValue = dequant; int deQuantifiedValue = dequant;

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

@ -5,6 +5,9 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
/// <summary>
/// Provides the normative AV1 DC and AC dequantization values for each quantizer index and supported bit depth.
/// </summary>
internal class Av1QuantizationLookup internal class Av1QuantizationLookup
{ {
// Coefficient scaling and quantization with AV1 TX are tailored to // Coefficient scaling and quantization with AV1 TX are tailored to
@ -35,6 +38,10 @@ internal class Av1QuantizationLookup
// expects quantizers to be larger for higher-bitdepth input. In // expects quantizers to be larger for higher-bitdepth input. In
// addition, the minimum allowable quantizer is 4; smaller values will // addition, the minimum allowable quantizer is 4; smaller values will
// underflow to 0 in the actual quantization routines. // underflow to 0 in the actual quantization routines.
/// <summary>
/// The Q3 AC dequantization values for 8-bit samples, indexed by quantizer index.
/// </summary>
private static readonly short[] AcQlookup8 = [ private static readonly short[] AcQlookup8 = [
4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
@ -52,6 +59,9 @@ internal class Av1QuantizationLookup
1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828, 1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828,
]; ];
/// <summary>
/// The Q3 AC dequantization values for 10-bit samples, indexed by quantizer index.
/// </summary>
private static readonly short[] AcQlookup10 = [ private static readonly short[] AcQlookup10 = [
4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63, 4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63,
67, 71, 75, 79, 83, 88, 92, 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, 140, 145, 67, 71, 75, 79, 83, 88, 92, 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, 140, 145,
@ -69,6 +79,9 @@ internal class Av1QuantizationLookup
6268, 6388, 6512, 6640, 6768, 6900, 7036, 7172, 7312, 6268, 6388, 6512, 6640, 6768, 6900, 7036, 7172, 7312,
]; ];
/// <summary>
/// The Q3 AC dequantization values for 12-bit samples, indexed by quantizer index.
/// </summary>
private static readonly short[] AcQlookup12 = [ private static readonly short[] AcQlookup12 = [
4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168, 4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168,
183, 199, 214, 230, 247, 263, 280, 297, 314, 331, 349, 366, 384, 402, 420, 438, 183, 199, 214, 230, 247, 263, 280, 297, 314, 331, 349, 366, 384, 402, 420, 438,
@ -88,6 +101,9 @@ internal class Av1QuantizationLookup
21902, 22334, 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, 28143, 28687, 29247, 21902, 22334, 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, 28143, 28687, 29247,
]; ];
/// <summary>
/// The Q3 DC dequantization values for 8-bit samples, indexed by quantizer index.
/// </summary>
private static readonly short[] DcQlookup8 = [ private static readonly short[] DcQlookup8 = [
4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, 4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23,
24, 25, 26, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, 40, 24, 25, 26, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, 40,
@ -104,6 +120,9 @@ internal class Av1QuantizationLookup
796, 819, 843, 869, 896, 925, 955, 988, 1022, 1058, 1098, 1139, 1184, 1232, 1282, 1336, 796, 819, 843, 869, 896, 925, 955, 988, 1022, 1058, 1098, 1139, 1184, 1232, 1282, 1336,
]; ];
/// <summary>
/// The Q3 DC dequantization values for 10-bit samples, indexed by quantizer index.
/// </summary>
private static readonly short[] DcQlookup10 = [ private static readonly short[] DcQlookup10 = [
4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57, 4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57,
60, 64, 68, 71, 75, 78, 82, 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, 124, 128, 60, 64, 68, 71, 75, 78, 82, 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, 124, 128,
@ -121,6 +140,9 @@ internal class Av1QuantizationLookup
3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130, 5347, 3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130, 5347,
]; ];
/// <summary>
/// The Q3 DC dequantization values for 12-bit samples, indexed by quantizer index.
/// </summary>
private static readonly short[] DcQlookup12 = [ private static readonly short[] DcQlookup12 = [
4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153, 4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153,
166, 180, 194, 208, 222, 237, 251, 266, 281, 296, 312, 327, 343, 358, 374, 390, 166, 180, 194, 208, 222, 237, 251, 266, 281, 296, 312, 327, 343, 358, 374, 390,
@ -140,8 +162,16 @@ internal class Av1QuantizationLookup
12750, 13118, 13501, 13913, 14343, 14807, 15290, 15812, 16356, 16943, 17575, 18237, 18949, 19718, 20521, 21387, 12750, 13118, 13501, 13913, 14343, 14807, 15290, 15812, 16356, 16943, 17575, 18237, 18949, 19718, 20521, 21387,
]; ];
/// <summary>
/// Gets the DC dequantization value after applying a plane delta to the frame quantizer index.
/// </summary>
/// <param name="qIndex">The frame or segment quantizer index.</param>
/// <param name="dcDeltaQ">The signed DC quantizer adjustment for the selected plane.</param>
/// <param name="bitDepth">The coded sample bit depth.</param>
/// <returns>The Q3 DC dequantization value.</returns>
public static short GetDcQuant(int qIndex, int dcDeltaQ, Av1BitDepth bitDepth) public static short GetDcQuant(int qIndex, int dcDeltaQ, Av1BitDepth bitDepth)
{ {
// Plane deltas may move beyond the signaled 8-bit quantizer domain, where AV1 requires endpoint clamping.
int qClamped = Av1Math.Clamp(qIndex + dcDeltaQ, 0, Av1Constants.MaxQ); int qClamped = Av1Math.Clamp(qIndex + dcDeltaQ, 0, Av1Constants.MaxQ);
switch (bitDepth) switch (bitDepth)
{ {
@ -157,8 +187,16 @@ internal class Av1QuantizationLookup
} }
} }
/// <summary>
/// Gets the AC dequantization value after applying a plane delta to the frame quantizer index.
/// </summary>
/// <param name="qIndex">The frame or segment quantizer index.</param>
/// <param name="dcDeltaQ">The signed AC quantizer adjustment for the selected plane.</param>
/// <param name="bitDepth">The coded sample bit depth.</param>
/// <returns>The Q3 AC dequantization value.</returns>
public static short GetAcQuant(int qIndex, int dcDeltaQ, Av1BitDepth bitDepth) public static short GetAcQuant(int qIndex, int dcDeltaQ, Av1BitDepth bitDepth)
{ {
// Plane deltas may move beyond the signaled 8-bit quantizer domain, where AV1 requires endpoint clamping.
int qClamped = Av1Math.Clamp(qIndex + dcDeltaQ, 0, Av1Constants.MaxQ); int qClamped = Av1Math.Clamp(qIndex + dcDeltaQ, 0, Av1Constants.MaxQ);
switch (bitDepth) switch (bitDepth)
{ {
@ -174,6 +212,13 @@ internal class Av1QuantizationLookup
} }
} }
/// <summary>
/// Gets the quantizer index for a segment, including its alternative-quantizer feature when active.
/// </summary>
/// <param name="segmentationParameters">The frame segmentation configuration.</param>
/// <param name="segmentId">The zero-based AV1 segment identifier.</param>
/// <param name="baseQIndex">The frame's base quantizer index.</param>
/// <returns>The segment quantizer index clamped to the AV1 quantizer domain.</returns>
public static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) public static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex)
{ {
if (segmentationParameters.IsFeatureActive(segmentId, ObuSegmentationLevelFeature.AlternativeQuantizer)) if (segmentationParameters.IsFeatureActive(segmentId, ObuSegmentationLevelFeature.AlternativeQuantizer))

Loading…
Cancel
Save