// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Formats.Heif.Components; using SixLabors.ImageSharp.Formats.Heif.Components.Alpha; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; public class Av1EncoderFrameTests { private const int EightBit = (int)Av1BitDepth.EightBit; private const int TenBit = (int)Av1BitDepth.TenBit; private const int TwelveBit = (int)Av1BitDepth.TwelveBit; private const int Yuv400 = (int)Av1ColorFormat.Yuv400; private const int Yuv420 = (int)Av1ColorFormat.Yuv420; private const int Yuv422 = (int)Av1ColorFormat.Yuv422; private const int Yuv444 = (int)Av1ColorFormat.Yuv444; [Theory] [InlineData(8, 8, false, EightBit, Yuv400)] [InlineData(8, 8, true, EightBit, Yuv400)] [InlineData(16, 16, false, EightBit, Yuv400)] [InlineData(16, 16, true, EightBit, Yuv400)] [InlineData(8, 8, false, TenBit, Yuv400)] [InlineData(8, 8, true, TenBit, Yuv400)] [InlineData(8, 8, false, TwelveBit, Yuv400)] [InlineData(8, 8, true, TwelveBit, Yuv400)] [InlineData(16, 16, false, EightBit, Yuv420)] [InlineData(16, 16, true, EightBit, Yuv420)] [InlineData(13, 11, true, EightBit, Yuv420)] [InlineData(16, 16, false, TenBit, Yuv420)] [InlineData(16, 16, true, TenBit, Yuv420)] [InlineData(16, 16, false, TwelveBit, Yuv420)] [InlineData(16, 16, true, TwelveBit, Yuv420)] [InlineData(16, 16, false, EightBit, Yuv422)] [InlineData(16, 16, true, EightBit, Yuv422)] [InlineData(13, 11, true, EightBit, Yuv422)] [InlineData(16, 16, false, TenBit, Yuv422)] [InlineData(16, 16, true, TenBit, Yuv422)] [InlineData(16, 16, false, TwelveBit, Yuv422)] [InlineData(16, 16, true, TwelveBit, Yuv422)] [InlineData(16, 16, false, EightBit, Yuv444)] [InlineData(16, 16, true, EightBit, Yuv444)] [InlineData(13, 11, true, EightBit, Yuv444)] [InlineData(16, 16, false, TenBit, Yuv444)] [InlineData(16, 16, true, TenBit, Yuv444)] [InlineData(16, 16, false, TwelveBit, Yuv444)] [InlineData(16, 16, true, TwelveBit, Yuv444)] public void EncodeWritesReducedStillPictureConsumedByProductionDecoder(int width, int height, bool hasGradient, int bitDepthValue, int colorFormatValue) { Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue; Av1ColorFormat colorFormat = (Av1ColorFormat)colorFormatValue; using Image source = new(width, height); for (int y = 0; y < height; y++) { Span row = source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y); for (int x = 0; x < width; x++) { if (colorFormat == Av1ColorFormat.Yuv400) { byte value = hasGradient ? (byte)((x * 13) + (y * 17)) : (byte)128; row[x] = new Rgba32(value, value, value); } else { byte red = hasGradient ? (byte)((x * 13) + (y * 17)) : (byte)192; byte green = hasGradient ? (byte)((x * 7) + (y * 5)) : (byte)64; byte blue = hasGradient ? (byte)((x * 3) + (y * 11)) : (byte)32; row[x] = new Rgba32(red, green, blue); } } } ObuColorConfig colorConfig = CreateColorConfig(bitDepth, colorFormat); using MemoryStream stream = new(); ObuSequenceHeader encodedHeader = Av1FrameEncoder.Encode( Configuration.Default, source.Frames.RootFrame, stream, colorConfig, qIndex: 37, effort: 5); byte[] payload = stream.ToArray(); string outputDirectory = Path.Combine( TestEnvironment.ActualOutputDirectoryFullPath, "Formats", "Heif", "Av1"); Directory.CreateDirectory(outputDirectory); string contentName = hasGradient ? "gradient" : "constant"; int bitCount = bitDepth.GetBitCount(); string colorName = colorFormat.ToString()[3..]; string fileName = $"encoder-frame-{width}x{height}-{bitCount}b-{colorName}-{contentName}.obu"; File.WriteAllBytes(Path.Combine(outputDirectory, fileName), payload); using Av1Decoder decoder = new(Configuration.Default); using Image decoded = decoder.Decode(payload); Assert.Equal(width, decoded.Width); Assert.Equal(height, decoded.Height); ObuSequenceProfile expectedProfile = bitDepth == Av1BitDepth.TwelveBit || colorFormat == Av1ColorFormat.Yuv422 ? ObuSequenceProfile.Professional : colorFormat == Av1ColorFormat.Yuv444 ? ObuSequenceProfile.High : ObuSequenceProfile.Main; Assert.Equal(expectedProfile, encodedHeader.SequenceProfile); Assert.True(encodedHeader.IsReducedStillPictureHeader); Rgba32 first = decoded[0, 0]; Assert.Equal(byte.MaxValue, first.A); if (colorFormat == Av1ColorFormat.Yuv400) { Assert.Equal(first.R, first.G); Assert.Equal(first.R, first.B); } else { Rgba32 center = decoded[width / 2, height / 2]; Assert.True(center.R != center.G || center.G != center.B); } if (hasGradient) { Assert.NotEqual(first, decoded[width - 1, height - 1]); } else { if (colorFormat == Av1ColorFormat.Yuv400) { Assert.InRange(first.R, 120, 136); for (int y = 0; y < height; y++) { foreach (Rgba32 pixel in decoded.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y)) { Assert.Equal(first, pixel); } } } } } [Theory] [InlineData(EightBit)] [InlineData(TenBit)] [InlineData(TwelveBit)] public void EncodeAlphaWritesMonochromeReducedStillPicture(int bitDepthValue) { const int Width = 16; const int Height = 16; Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue; using Image source = new(Width, Height); for (int y = 0; y < Height; y++) { Span row = source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y); for (int x = 0; x < Width; x++) { ushort alpha = (ushort)(((x + y) * ushort.MaxValue) / (Width + Height - 2)); row[x] = new Rgba64(ushort.MaxValue, 0, 0, alpha); } } using MemoryStream stream = new(); ObuSequenceHeader encodedHeader = Av1FrameEncoder.EncodeAlpha( Configuration.Default, source.Frames.RootFrame, stream, CreateColorConfig(bitDepth), qIndex: 37, effort: 5); byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); using Image decoded = decoder.Decode(payload); Assert.True(encodedHeader.ColorConfig.IsMonochrome); Assert.Equal( bitDepth == Av1BitDepth.TwelveBit ? ObuSequenceProfile.Professional : ObuSequenceProfile.Main, encodedHeader.SequenceProfile); Assert.Equal(new Size(Width, Height), decoded.Size); Assert.True(decoded[0, 0].R < decoded[Width - 1, Height - 1].R); Assert.Equal(decoded[0, 0].R, decoded[0, 0].G); Assert.Equal(decoded[0, 0].R, decoded[0, 0].B); Assert.Equal(ushort.MaxValue, decoded[0, 0].A); string outputDirectory = Path.Combine( TestEnvironment.ActualOutputDirectoryFullPath, "Formats", "Heif", "Av1"); Directory.CreateDirectory(outputDirectory); File.WriteAllBytes( Path.Combine(outputDirectory, $"encoder-alpha-{Width}x{Height}-{bitDepth.GetBitCount()}b.obu"), payload); } [Fact] public void AlphaConversionUsesOnePooledRowAndPreservesTwelveBitPrecision() { const int Width = 19; const int Border = Av1EncoderFrame.LumaBorder; using Image image = new(Width, 1); ushort[] expected = new ushort[Width]; for (int x = 0; x < Width; x++) { ushort alpha = (ushort)((x * (long)ushort.MaxValue) / (Width - 1)); image[x, 0] = new Rgba64(0, 0, 0, alpha); expected[x] = (ushort)(((alpha * 4095L) + (ushort.MaxValue / 2)) / ushort.MaxValue); } using Av1EncoderFrameBuffer frameBuffer = new( Configuration.Default, Width, 1, 12, Av1ColorFormat.Yuv400, 0, 0); TestMemoryAllocator allocator = new(); allocator.EnableNonThreadSafeLogging(); Configuration configuration = Configuration.Default.Clone(); configuration.MemoryAllocator = allocator; HeifPlanarAlphaEncoder.Convert< Rgba64, Av1EncoderFrame.PlanarView, ushort, HeifUShortSampleConverter>( configuration, image.Frames.RootFrame, frameBuffer.Frame.View); frameBuffer.Frame.ExtendBorders(); AssertReplicatedSingleRow(frameBuffer.Luma, Border, expected); TestMemoryAllocator.AllocationRequest allocation = Assert.Single(allocator.AllocationLog); Assert.Equal(typeof(float), allocation.ElementType); Assert.Equal(Width * 3, allocation.Length); TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); 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 source = new(8, 8); using MemoryStream stream = new(); ObuSequenceHeader sequenceHeader = Av1FrameEncoder.Encode( Configuration.Default, source.Frames.RootFrame, stream, CreateColorConfig(bitDepth, colorFormat), qIndex: 37, effort: 5); 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] public void ScreenContentDetectorMatchesLibaomFeatureThresholds() { const int width = 160; const int height = 16; using Av1EncoderFrameBuffer byteFrame = new( Configuration.Default, width, height, 8, Av1ColorFormat.Yuv400, 0, 0); for (int row = 0; row < height; row++) { Span samples = byteFrame.Frame.View.GetLumaRowSpan(row)[..width]; samples.Fill(96); for (int column = 0; column < 16; column++) { samples[column] = column < 8 ? (byte)32 : (byte)224; } } // One qualifying block is exactly ten percent of this frame, and the reference threshold is strict. Assert.False(Av1ScreenContentDetector.IsPaletteLikely(byteFrame.Frame)); Av1ScreenContentDetector.Detect( byteFrame.Frame, out bool allowScreenContentTools, out bool allowIntraBlockCopy); Assert.False(allowScreenContentTools); Assert.False(allowIntraBlockCopy); for (int row = 0; row < height; row++) { Span samples = byteFrame.Frame.View.GetLumaRowSpan(row); for (int column = 16; column < 32; column++) { samples[column] = column < 24 ? (byte)48 : (byte)208; } } Assert.True(Av1ScreenContentDetector.IsPaletteLikely(byteFrame.Frame)); Av1ScreenContentDetector.Detect( byteFrame.Frame, out allowScreenContentTools, out allowIntraBlockCopy); Assert.True(allowScreenContentTools); Assert.True(allowIntraBlockCopy); using Av1EncoderFrameBuffer highBitDepthFrame = new( Configuration.Default, 16, 16, 10, Av1ColorFormat.Yuv400, 0, 0); for (int row = 0; row < 16; row++) { Span samples = highBitDepthFrame.Frame.View.GetLumaRowSpan(row); for (int column = 0; column < 16; column++) { samples[column] = column < 8 ? (ushort)128 : (ushort)131; } } Assert.False(Av1ScreenContentDetector.IsPaletteLikely(highBitDepthFrame.Frame)); Av1ScreenContentDetector.Detect( highBitDepthFrame.Frame, out allowScreenContentTools, out allowIntraBlockCopy); Assert.False(allowScreenContentTools); Assert.False(allowIntraBlockCopy); for (int row = 0; row < 16; row++) { Span samples = highBitDepthFrame.Frame.View.GetLumaRowSpan(row); samples[8..16].Fill(640); } Assert.True(Av1ScreenContentDetector.IsPaletteLikely(highBitDepthFrame.Frame)); Av1ScreenContentDetector.Detect( highBitDepthFrame.Frame, out allowScreenContentTools, out allowIntraBlockCopy); Assert.True(allowScreenContentTools); Assert.True(allowIntraBlockCopy); for (int row = 0; row < 16; row++) { Span samples = highBitDepthFrame.Frame.View.GetLumaRowSpan(row); for (int column = 0; column < 16; column++) { samples[column] = (ushort)((column % 5) * 200); } } Assert.False(Av1ScreenContentDetector.IsPaletteLikely(highBitDepthFrame.Frame)); Av1ScreenContentDetector.Detect( highBitDepthFrame.Frame, out allowScreenContentTools, out allowIntraBlockCopy); Assert.False(allowScreenContentTools); Assert.False(allowIntraBlockCopy); } [Fact] public void ScreenContentDetectorMatchesLibaomIntraBlockCopyVarianceThreshold() { const int Width = 16; const int Height = 16; using Av1EncoderFrameBuffer frame = new( Configuration.Default, Width, Height, 8, Av1ColorFormat.Yuv400, 0, 0); Buffer2DRegion luma = frame.Frame.View.GetPlane(Av1Plane.Y); for (int row = 0; row < Height; row++) { luma.DangerousGetRowSpan(row).Fill(96); } // A single delta of eleven leaves total variance below half a sample after per-pixel rounding. luma.DangerousGetRowSpan(0)[0] = 107; Av1ScreenContentDetector.Detect( frame.Frame, out bool allowScreenContentTools, out bool allowIntraBlockCopy); Assert.True(allowScreenContentTools); Assert.False(allowIntraBlockCopy); // Raising that delta to twelve crosses the exact integer rounding boundary used by libaom. luma.DangerousGetRowSpan(0)[0] = 108; Av1ScreenContentDetector.Detect( frame.Frame, out allowScreenContentTools, out allowIntraBlockCopy); Assert.True(allowScreenContentTools); Assert.True(allowIntraBlockCopy); } [Fact] public void EncodeActivatesScreenContentTools() { const int width = 16; const int height = 16; using Image source = new(width, height); for (int row = 0; row < height; row++) { Span pixels = source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(row); for (int column = 0; column < width; column++) { pixels[column] = (((column >> 2) + (row >> 2)) & 1) == 0 ? new Rgba32(224, 32, 32) : new Rgba32(32, 32, 224); } } ObuColorConfig colorConfig = CreateColorConfig(Av1BitDepth.EightBit, Av1ColorFormat.Yuv444); using MemoryStream stream = new(); _ = Av1FrameEncoder.Encode( Configuration.Default, source.Frames.RootFrame, stream, colorConfig, qIndex: 37, effort: 5); byte[] payload = stream.ToArray(); Av1BitStreamReader reader = new(payload); Av1TileDecoderStub tileReader = new(); ObuReader obuReader = new(); obuReader.ReadAll(ref reader, payload.Length, () => tileReader); ObuFrameHeader frameHeader = Assert.IsType(obuReader.FrameHeader); Assert.True(frameHeader.AllowScreenContentTools); Assert.True(frameHeader.AllowIntraBlockCopy); using Av1Decoder decoder = new(Configuration.Default); using Image decoded = decoder.Decode(payload); Assert.Equal(new Size(width, height), decoded.Size); string outputDirectory = Path.Combine( TestEnvironment.ActualOutputDirectoryFullPath, "Formats", "Heif", "Av1"); Directory.CreateDirectory(outputDirectory); File.WriteAllBytes(Path.Combine(outputDirectory, "encoder-frame-16x16-8b-444-palette.obu"), payload); } [Theory] [InlineData(0, false, false, false)] [InlineData(1, false, false, false)] [InlineData(2, false, false, false)] [InlineData(3, false, false, false)] [InlineData(4, true, false, false)] [InlineData(5, true, true, false)] [InlineData(6, true, true, true)] public void EncodeEffortControlsSearchFeatures( int effort, bool enableFilterIntra, bool enableScreenContentTools, bool selectTransformSize) { const int width = 16; const int height = 16; using Image source = new(width, height); for (int row = 0; row < height; row++) { Span pixels = source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(row); for (int column = 0; column < width; column++) { pixels[column] = (((column >> 2) + (row >> 2)) & 1) == 0 ? new Rgba32(224, 32, 32) : new Rgba32(32, 32, 224); } } ObuColorConfig colorConfig = CreateColorConfig(Av1BitDepth.EightBit, Av1ColorFormat.Yuv444); using MemoryStream stream = new(); _ = Av1FrameEncoder.Encode( Configuration.Default, source.Frames.RootFrame, stream, colorConfig, qIndex: 37, effort); byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); using Image decoded = decoder.Decode(payload); ObuSequenceHeader sequenceHeader = Assert.IsType(decoder.SequenceHeader); ObuFrameHeader frameHeader = Assert.IsType(decoder.FrameHeader); Av1FrameInfo frameInfo = Assert.IsType(decoder.FrameInfo); Assert.Equal(enableFilterIntra, sequenceHeader.EnableFilterIntra); Assert.Equal(enableScreenContentTools, frameHeader.AllowScreenContentTools); Assert.Equal(enableScreenContentTools, frameHeader.AllowIntraBlockCopy); Assert.Equal( selectTransformSize ? Av1TransformMode.Select : Av1TransformMode.Largest, frameHeader.TransformMode); Assert.Equal(new Size(width, height), decoded.Size); int modeCount = 0; foreach (Av1BlockModeInfo modeInfo in frameInfo.GetSuperblock(Point.Empty).GetModeInfos()) { modeCount++; if (effort == 0) { Assert.Equal(Av1PredictionMode.DC, modeInfo.YMode); Assert.Equal(Av1ChromaPredictionMode.DC, modeInfo.UvMode); } if (effort <= 1) { Assert.Equal(0, modeInfo.GetAngleDelta(Av1Plane.Y)); Assert.Equal(0, modeInfo.GetAngleDelta(Av1Plane.U)); } if (effort < 4) { Assert.False(modeInfo.UseFilterIntra); } if (effort < 5) { Assert.False(modeInfo.UseIntraBlockCopy); Assert.Equal(0, modeInfo.GetPaletteSize(Av1Plane.Y)); Assert.Equal(0, modeInfo.GetPaletteSize(Av1Plane.U)); } } Assert.NotEqual(0, modeCount); string outputDirectory = Path.Combine( TestEnvironment.ActualOutputDirectoryFullPath, "Formats", "Heif", "Av1"); Directory.CreateDirectory(outputDirectory); File.WriteAllBytes(Path.Combine(outputDirectory, $"encoder-frame-16x16-8b-444-effort-{effort}.obu"), payload); } [Fact] public void EncodeEffortSixSelectsFourByFourLumaTransforms() { const int Width = 16; const int Height = 16; using Image source = new(Width, Height); for (int row = 0; row < Height; row++) { Span pixels = source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(row); for (int column = 0; column < Width; column++) { byte value = (byte)(16 + ((((row >> 2) * 4) + (column >> 2)) * 14)); pixels[column] = new Rgba32(value, value, value); } } ObuColorConfig colorConfig = CreateColorConfig(Av1BitDepth.EightBit, Av1ColorFormat.Yuv400); using MemoryStream stream = new(); _ = Av1FrameEncoder.Encode( Configuration.Default, source.Frames.RootFrame, stream, colorConfig, qIndex: 37, effort: 6); byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); using Image decoded = decoder.Decode(payload); Assert.NotNull(decoder.FrameHeader); Assert.Equal(Av1TransformMode.Select, decoder.FrameHeader.TransformMode); Assert.NotNull(decoder.FrameInfo); bool foundSplitTransform = false; foreach (Av1BlockModeInfo modeInfo in decoder.FrameInfo.GetSuperblock(Point.Empty).GetModeInfos()) { foundSplitTransform |= modeInfo.GetTransformUnitCount(Av1Plane.Y) == 4; } Assert.True(foundSplitTransform); Assert.Equal(new Size(Width, Height), decoded.Size); string outputDirectory = Path.Combine( TestEnvironment.ActualOutputDirectoryFullPath, "Formats", "Heif", "Av1"); Directory.CreateDirectory(outputDirectory); File.WriteAllBytes( Path.Combine(outputDirectory, "encoder-frame-16x16-8b-400-transform-size-select.obu"), payload); } [Theory] [InlineData(5, false)] [InlineData(6, true)] public void EncodeSelectsIntraBlockCopyForRepeatedScreenContent( int effort, bool selectTransformSize) { const int Width = 328; const int Height = 16; const ulong Pattern = 0xD6A5_3C97_E18B_4F20UL; using Image source = new(Width, Height); for (int row = 0; row < Height; row++) { Span pixels = source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(row); for (int column = 0; column < Width; column++) { int patternIndex = ((row & 7) * 8) + (column & 7); pixels[column] = ((Pattern >> patternIndex) & 1) == 0 ? new Rgba32(224, 32, 32) : new Rgba32(32, 32, 224); } } ObuColorConfig colorConfig = CreateColorConfig(Av1BitDepth.EightBit, Av1ColorFormat.Yuv444); using MemoryStream stream = new(); _ = Av1FrameEncoder.Encode( Configuration.Default, source.Frames.RootFrame, stream, colorConfig, qIndex: 37, effort); byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); using Image decoded = decoder.Decode(payload); Assert.NotNull(decoder.FrameHeader); Assert.True(decoder.FrameHeader.AllowScreenContentTools); Assert.True(decoder.FrameHeader.AllowIntraBlockCopy); Assert.Equal( selectTransformSize ? Av1TransformMode.Select : Av1TransformMode.Largest, decoder.FrameHeader.TransformMode); Assert.NotNull(decoder.FrameInfo); Av1SuperblockInfo targetSuperblock = decoder.FrameInfo.GetSuperblock(new Point(5, 0)); bool usesIntraBlockCopy = false; foreach (Av1BlockModeInfo modeInfo in targetSuperblock.GetModeInfos()) { usesIntraBlockCopy |= modeInfo.UseIntraBlockCopy; } Assert.True(usesIntraBlockCopy); Assert.Equal(new Size(Width, Height), decoded.Size); string outputDirectory = Path.Combine( TestEnvironment.ActualOutputDirectoryFullPath, "Formats", "Heif", "Av1"); Directory.CreateDirectory(outputDirectory); string fileName = effort == 5 ? "encoder-frame-328x16-8b-444-intrabc.obu" : "encoder-frame-328x16-8b-444-intrabc-effort-6.obu"; File.WriteAllBytes(Path.Combine(outputDirectory, fileName), payload); } [Fact] public void PrepareSourceConvertsRgba32DirectlyIntoBorderedEightBitPlane() { const int width = 4; const int height = 1; const int border = Av1EncoderFrame.LumaBorder; using Image image = new(width, height); image[0, 0] = new Rgba32(byte.MaxValue, 0, 0, 0); image[1, 0] = new Rgba32(0, byte.MaxValue, 0); image[2, 0] = new Rgba32(0, 0, byte.MaxValue); image[3, 0] = new Rgba32(byte.MaxValue, byte.MaxValue, byte.MaxValue); using Av1EncoderFrameBuffer frameBuffer = new( Configuration.Default, width, height, 8, Av1ColorFormat.Yuv400, 0, 0); ObuColorConfig colorConfig = CreateColorConfig(Av1BitDepth.EightBit); Av1FrameEncoder.PrepareSource(Configuration.Default, image.Frames.RootFrame, frameBuffer.Frame, colorConfig); byte[] expected = [76, 150, 29, 255]; AssertReplicatedSingleRow(frameBuffer.Luma, border, expected); } [Fact] public void PrepareSourcePreservesHighBitDepthPrecision() { const int width = 4; const int height = 1; const int border = Av1EncoderFrame.LumaBorder; using Image image = new(width, height); image[0, 0] = new Rgba64(ushort.MaxValue, 0, 0, 0); image[1, 0] = new Rgba64(0, ushort.MaxValue, 0, ushort.MaxValue); image[2, 0] = new Rgba64(0, 0, ushort.MaxValue, ushort.MaxValue); image[3, 0] = new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); using Av1EncoderFrameBuffer frameBuffer = new( Configuration.Default, width, height, 10, Av1ColorFormat.Yuv400, 0, 0); ObuColorConfig colorConfig = CreateColorConfig(Av1BitDepth.TenBit); Av1FrameEncoder.PrepareSource(Configuration.Default, image.Frames.RootFrame, frameBuffer.Frame, colorConfig); ushort[] expected = [306, 601, 117, 1023]; AssertReplicatedSingleRow(frameBuffer.Luma, border, expected); } [Fact] public void ExtendBordersReplicatesEveryPhysicalPlaneEdge() { const int visibleWidth = 5; const int visibleHeight = 3; const int lumaBorder = Av1EncoderFrame.LumaBorder; const int chromaBorder = lumaBorder / 2; using Av1EncoderFrameBuffer frameBuffer = new( Configuration.Default, visibleWidth, visibleHeight, 8, Av1ColorFormat.Yuv420, 1, 1); Buffer2D luma = frameBuffer.Luma; Buffer2D chromaBlue = Assert.IsType>(frameBuffer.ChromaBlue); Buffer2D chromaRed = Assert.IsType>(frameBuffer.ChromaRed); FillVisible(luma, lumaBorder, lumaBorder, visibleWidth, visibleHeight, 10); FillVisible(chromaBlue, chromaBorder, chromaBorder, (visibleWidth + 1) / 2, (visibleHeight + 1) / 2, 80); FillVisible(chromaRed, chromaBorder, chromaBorder, (visibleWidth + 1) / 2, (visibleHeight + 1) / 2, 120); frameBuffer.Frame.ExtendBorders(); AssertReplicatedPlane(luma, lumaBorder, lumaBorder, visibleWidth, visibleHeight, 10); AssertReplicatedPlane(chromaBlue, chromaBorder, chromaBorder, (visibleWidth + 1) / 2, (visibleHeight + 1) / 2, 80); AssertReplicatedPlane(chromaRed, chromaBorder, chromaBorder, (visibleWidth + 1) / 2, (visibleHeight + 1) / 2, 120); } [Theory] [InlineData(5, 3, 0, 0, 160, 136)] [InlineData(5, 3, 1, 0, 80, 136)] [InlineData(5, 3, 1, 1, 80, 68)] [InlineData(1921, 1081, 0, 0, 2080, 1216)] [InlineData(1921, 1081, 1, 1, 1040, 608)] public void GetPlaneBufferSizeMatchesLibaomLayout( int width, int height, int subsamplingX, int subsamplingY, int expectedWidth, int expectedHeight) { Size actual = Av1EncoderFrame.GetPlaneBufferSize(width, height, subsamplingX, subsamplingY); Assert.Equal(new Size(expectedWidth, expectedHeight), actual); } [Fact] public void FrameBufferUsesOneExactSizeOwnerForAllPlanes() { TestMemoryAllocator allocator = new(); allocator.EnableNonThreadSafeLogging(); Configuration configuration = Configuration.Default.Clone(); configuration.MemoryAllocator = allocator; TestMemoryAllocator.AllocationRequest allocation; using (Av1EncoderFrameBuffer frameBuffer = new( configuration, 64, 64, 8, Av1ColorFormat.Yuv420, 1, 1)) { allocation = Assert.Single(allocator.AllocationLog); Assert.Empty(allocator.ReturnLog); Assert.Equal(typeof(byte), allocation.ElementType); Assert.Equal(55_296, allocation.Length); Assert.Single(frameBuffer.Luma.MemoryGroup); Assert.Single(Assert.IsType>(frameBuffer.ChromaBlue).MemoryGroup); Assert.Single(Assert.IsType>(frameBuffer.ChromaRed).MemoryGroup); } TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); Assert.Equal(allocation.AllocationId, returned.AllocationId); } [Fact] public void EncodeReturnsEveryOperationAllocationAndUsesOneLibaomSizedTileReservation() { const int Width = 64; const int Height = 64; const int ExpectedTileOutputLength = 60 * 1024; using Image source = new(Width, Height); for (int y = 0; y < Height; y++) { Span row = source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y); for (int x = 0; x < Width; x++) { row[x] = new Rgba32( (byte)((x * 3) + y), (byte)(x + (y * 5)), (byte)((x * 7) + (y * 11))); } } TestMemoryAllocator allocator = new(); allocator.EnableNonThreadSafeLogging(); Configuration configuration = Configuration.Default.Clone(); configuration.MemoryAllocator = allocator; using MemoryStream storage = new(); using NonSeekableStream destination = new(storage); _ = Av1FrameEncoder.Encode( configuration, source.Frames.RootFrame, destination, CreateColorConfig(Av1BitDepth.TwelveBit, Av1ColorFormat.Yuv444), qIndex: 37, effort: 5); Assert.False(destination.CanSeek); Assert.NotEqual(0, storage.Length); TestMemoryAllocator.AllocationRequest tileOutput = Assert.Single( allocator.AllocationLog, allocation => allocation.ElementType == typeof(byte) && allocation.Length == ExpectedTileOutputLength); Assert.Equal(ExpectedTileOutputLength, tileOutput.Length); Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); Assert.Equal( allocator.AllocationLog.Select(allocation => allocation.AllocationId).Order(), allocator.ReturnLog.Select(returned => returned.AllocationId).Order()); } private static ObuColorConfig CreateColorConfig( Av1BitDepth bitDepth, Av1ColorFormat colorFormat = Av1ColorFormat.Yuv400) => new() { IsColorDescriptionPresent = true, IsMonochrome = colorFormat == Av1ColorFormat.Yuv400, ColorPrimaries = ObuColorPrimaries.Bt601, TransferCharacteristics = ObuTransferCharacteristics.Bt601, MatrixCoefficients = ObuMatrixCoefficients.Bt601, ColorRange = true, SubSamplingX = colorFormat != Av1ColorFormat.Yuv444, SubSamplingY = colorFormat == Av1ColorFormat.Yuv400 || colorFormat == Av1ColorFormat.Yuv420, ChromaSamplePosition = ObuChromoSamplePosition.Unknown, BitDepth = bitDepth }; private static void FillVisible(Buffer2D plane, int originX, int originY, int width, int height, int seed) { for (int y = 0; y < height; y++) { Span row = plane.DangerousGetRowSpan(originY + y); for (int x = 0; x < width; x++) { row[originX + x] = (byte)(seed + (y * width) + x); } } } private static void AssertReplicatedPlane(Buffer2D plane, int originX, int originY, int width, int height, int seed) { for (int y = 0; y < plane.Height; y++) { ReadOnlySpan row = plane.DangerousGetRowSpan(y); int sourceY = Math.Clamp(y - originY, 0, height - 1); for (int x = 0; x < row.Length; x++) { int sourceX = Math.Clamp(x - originX, 0, width - 1); Assert.Equal((byte)(seed + (sourceY * width) + sourceX), row[x]); } } } private static void AssertReplicatedSingleRow( Buffer2D plane, int originX, ReadOnlySpan expected) where TSample : unmanaged, IEquatable { for (int y = 0; y < plane.Height; y++) { ReadOnlySpan row = plane.DangerousGetRowSpan(y); for (int x = 0; x < row.Length; x++) { int sourceX = Math.Clamp(x - originX, 0, expected.Length - 1); Assert.Equal(expected[sourceX], row[x]); } } } }