Browse Source

Add AV1 codec configuration writing

pull/2633/head
James Jackson-South 1 month ago
parent
commit
f28ed01f43
  1. 1
      HEIF_IMPLEMENTATION_PLAN.md
  2. 54
      src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs
  3. 33
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs

1
HEIF_IMPLEMENTATION_PLAN.md

@ -869,6 +869,7 @@ Encoder verification contract:
### 7. Write complete AVIF output ### 7. Write complete AVIF output
- [~] The encoder-side AV1 codec configuration is now derived directly from the encoded sequence header and writes the fixed four-byte `av1C` record with empty `configOBUs`. The image payload retains the required sequence header, so the property introduces no sequence-header allocation, retention, or copy. Four production-header cases cover main, high, and professional profiles; 8-, 10-, and 12-bit precision; monochrome, 4:2:0, 4:2:2, and 4:4:4 sampling; exact fixed bytes; decoder reparsing; and header/property equivalence through direct net11 Release VSTest. Property-container emission and public AVIF activation remain open.
- [ ] Write the correct AVIF file type, item information, locations, references, properties, AV1 configuration, dimensions, color, alpha, metadata, and media data. - [ ] Write the correct AVIF file type, item information, locations, references, properties, AV1 configuration, dimensions, color, alpha, metadata, and media data.
- [ ] Support single images, alpha auxiliary images, grids, multiple extents, and bounded image sequences in the final public scope. - [ ] Support single images, alpha auxiliary images, grids, multiple extents, and bounded image sequences in the final public scope.
- [ ] Preserve ICC, Exif, and XMP according to encoder options. - [ ] Preserve ICC, Exif, and XMP according to encoder options.

54
src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs

@ -12,6 +12,11 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// </summary> /// </summary>
internal sealed class Av1CodecConfiguration internal sealed class Av1CodecConfiguration
{ {
/// <summary>
/// The number of bytes in the fixed AV1 codec-configuration record.
/// </summary>
public const int FixedHeaderSize = 4;
/// <summary> /// <summary>
/// The optional sequence-header payload retained from the configuration open bitstream units. /// The optional sequence-header payload retained from the configuration open bitstream units.
/// </summary> /// </summary>
@ -40,7 +45,7 @@ internal sealed class Av1CodecConfiguration
/// <param name="options">The general options governing metadata validation.</param> /// <param name="options">The general options governing metadata validation.</param>
public Av1CodecConfiguration(Span<byte> boxBuffer, DecoderOptions options) public Av1CodecConfiguration(Span<byte> boxBuffer, DecoderOptions options)
{ {
if (boxBuffer.Length < 4) if (boxBuffer.Length < FixedHeaderSize)
{ {
throw new InvalidImageContentException("The AV1 codec configuration is truncated."); throw new InvalidImageContentException("The AV1 codec configuration is truncated.");
} }
@ -83,7 +88,7 @@ internal sealed class Av1CodecConfiguration
// The delay syntax is consumed to validate the fixed record, but it describes sample presentation and has // The delay syntax is consumed to validate the fixed record, but it describes sample presentation and has
// no meaning for the independently presented image item supported by this bounded container implementation. // no meaning for the independently presented image item supported by this bounded container implementation.
ReadOnlySpan<byte> configObus = boxBuffer[4..]; ReadOnlySpan<byte> configObus = boxBuffer[FixedHeaderSize..];
int sequenceHeaderCount = ScanObus( int sequenceHeaderCount = ScanObus(
configObus, configObus,
true, true,
@ -114,6 +119,30 @@ internal sealed class Av1CodecConfiguration
} }
} }
/// <summary>
/// Initializes a new instance of the <see cref="Av1CodecConfiguration"/> class from an encoded sequence header.
/// </summary>
/// <param name="sequenceHeader">The sequence header describing the encoded image item.</param>
public Av1CodecConfiguration(ObuSequenceHeader sequenceHeader)
{
ObuOperatingPoint operatingPoint = sequenceHeader.OperatingPoint[0];
ObuColorConfig colorConfig = sequenceHeader.ColorConfig;
this.SequenceProfile = (byte)sequenceHeader.SequenceProfile;
this.SequenceLevelIndex = (byte)operatingPoint.SequenceLevelIndex;
this.SequenceTier = operatingPoint.SequenceTier != 0;
this.HighBitDepth = colorConfig.BitDepth is Av1BitDepth.TenBit or Av1BitDepth.TwelveBit;
this.TwelveBit = colorConfig.BitDepth == Av1BitDepth.TwelveBit;
this.IsMonochrome = colorConfig.IsMonochrome;
this.ChromaSubsamplingX = colorConfig.SubSamplingX;
this.ChromaSubsamplingY = colorConfig.SubSamplingY;
this.ChromaSamplePosition = (byte)colorConfig.ChromaSamplePosition;
this.configSequenceHeader = [];
this.configSequenceHeaderExtension = -1;
this.configContentLightLevel = null;
this.configMasteringDisplayColorVolume = null;
}
/// <summary> /// <summary>
/// Gets the sequence profile declared for the coded image. /// Gets the sequence profile declared for the coded image.
/// </summary> /// </summary>
@ -164,6 +193,27 @@ internal sealed class Av1CodecConfiguration
/// </summary> /// </summary>
public byte ChromaSamplePosition { get; } public byte ChromaSamplePosition { get; }
/// <summary>
/// Writes the fixed AV1 codec-configuration record without optional configuration OBUs.
/// </summary>
/// <param name="destination">The destination receiving the four-byte record.</param>
public void WriteFixedHeader(Span<byte> destination)
{
// The image item payload already begins with its required sequence header. Keeping configOBUs empty avoids
// retaining or copying the same OBU into the configuration property.
destination[0] = 0x81;
destination[1] = (byte)((this.SequenceProfile << 5) | this.SequenceLevelIndex);
destination[2] = (byte)(
(this.SequenceTier ? 1 << 7 : 0)
| (this.HighBitDepth ? 1 << 6 : 0)
| (this.TwelveBit ? 1 << 5 : 0)
| (this.IsMonochrome ? 1 << 4 : 0)
| (this.ChromaSubsamplingX ? 1 << 3 : 0)
| (this.ChromaSubsamplingY ? 1 << 2 : 0)
| this.ChromaSamplePosition);
destination[3] = 0;
}
/// <summary> /// <summary>
/// Validates the AV1 image item OBU layout and metadata against its item properties and configuration record. /// Validates the AV1 image item OBU layout and metadata against its item properties and configuration record.
/// </summary> /// </summary>

33
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs

@ -1,6 +1,7 @@
// 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;
using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline;
@ -251,6 +252,38 @@ public class Av1EncoderFrameTests
Assert.Equal(allocation.AllocationId, returned.AllocationId); Assert.Equal(allocation.AllocationId, returned.AllocationId);
} }
[Theory]
[InlineData(EightBit, Yuv400, 0x1F, 0x1C)]
[InlineData(TenBit, Yuv420, 0x1F, 0x4C)]
[InlineData(TenBit, Yuv444, 0x3F, 0x40)]
[InlineData(TwelveBit, Yuv422, 0x5F, 0x68)]
public void CodecConfigurationWritesFixedHeaderFromEncodedSequenceHeader(
int bitDepthValue,
int colorFormatValue,
byte expectedProfileAndLevel,
byte expectedColorFlags)
{
Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue;
Av1ColorFormat colorFormat = (Av1ColorFormat)colorFormatValue;
using Image<Rgba32> source = new(8, 8);
using MemoryStream stream = new();
ObuSequenceHeader sequenceHeader = Av1FrameEncoder.Encode(
Configuration.Default,
source.Frames.RootFrame,
stream,
CreateColorConfig(bitDepth, colorFormat),
qIndex: 37);
Av1CodecConfiguration configuration = new(sequenceHeader);
byte[] fixedHeader = new byte[Av1CodecConfiguration.FixedHeaderSize];
configuration.WriteFixedHeader(fixedHeader);
Assert.Equal([0x81, expectedProfileAndLevel, expectedColorFlags, 0x00], fixedHeader);
Av1CodecConfiguration parsed = new(fixedHeader, new DecoderOptions());
Assert.True(configuration.HasMatchingImageConfiguration(parsed));
parsed.Validate(sequenceHeader);
}
[Fact] [Fact]
public void ScreenContentDetectorMatchesLibaomFeatureThresholds() public void ScreenContentDetectorMatchesLibaomFeatureThresholds()
{ {

Loading…
Cancel
Save