diff --git a/src/TeamsISO.Engine/Pipeline/IFrameScaler.cs b/src/TeamsISO.Engine/Pipeline/IFrameScaler.cs new file mode 100644 index 0000000..925b385 --- /dev/null +++ b/src/TeamsISO.Engine/Pipeline/IFrameScaler.cs @@ -0,0 +1,8 @@ +using TeamsISO.Engine.Domain; + +namespace TeamsISO.Engine.Pipeline; + +public interface IFrameScaler +{ + ProcessedFrame Scale(RawFrame source, int targetWidth, int targetHeight, AspectMode aspect, long timestampTicks); +} diff --git a/src/TeamsISO.Engine/Pipeline/PassthroughFrameScaler.cs b/src/TeamsISO.Engine/Pipeline/PassthroughFrameScaler.cs new file mode 100644 index 0000000..a26c5e4 --- /dev/null +++ b/src/TeamsISO.Engine/Pipeline/PassthroughFrameScaler.cs @@ -0,0 +1,20 @@ +using TeamsISO.Engine.Domain; + +namespace TeamsISO.Engine.Pipeline; + +/// +/// Phase A scaler. Copies the source frame's pixel buffer through unchanged and tags the +/// output with the requested target dimensions. Real scaling is added in Phase B against libyuv. +/// +public sealed class PassthroughFrameScaler : IFrameScaler +{ + public ProcessedFrame Scale(RawFrame source, int targetWidth, int targetHeight, AspectMode aspect, long timestampTicks) + { + return new ProcessedFrame( + Width: targetWidth, + Height: targetHeight, + TimestampTicks: timestampTicks, + Pixels: source.Pixels, + Format: source.Format == PixelFormat.Bgra ? PixelFormat.Bgra : PixelFormat.Bgra); + } +} diff --git a/src/TeamsISO.Engine/Pipeline/SolidFrameRenderer.cs b/src/TeamsISO.Engine/Pipeline/SolidFrameRenderer.cs new file mode 100644 index 0000000..7457d82 --- /dev/null +++ b/src/TeamsISO.Engine/Pipeline/SolidFrameRenderer.cs @@ -0,0 +1,20 @@ +namespace TeamsISO.Engine.Pipeline; + +/// +/// Generates a solid-color BGRA frame for use as a "no signal" slate. +/// +public sealed class SolidFrameRenderer +{ + public ProcessedFrame Render(int width, int height, byte b, byte g, byte r, byte a, long timestampTicks) + { + var pixels = new byte[width * height * 4]; + for (var i = 0; i < pixels.Length; i += 4) + { + pixels[i + 0] = b; + pixels[i + 1] = g; + pixels[i + 2] = r; + pixels[i + 3] = a; + } + return new ProcessedFrame(width, height, timestampTicks, pixels, PixelFormat.Bgra); + } +} diff --git a/src/tests/TeamsISO.Engine.Tests/Pipeline/SolidFrameRendererTests.cs b/src/tests/TeamsISO.Engine.Tests/Pipeline/SolidFrameRendererTests.cs new file mode 100644 index 0000000..5976e43 --- /dev/null +++ b/src/tests/TeamsISO.Engine.Tests/Pipeline/SolidFrameRendererTests.cs @@ -0,0 +1,24 @@ +using TeamsISO.Engine.Pipeline; + +namespace TeamsISO.Engine.Tests.Pipeline; + +public class SolidFrameRendererTests +{ + [Fact] + public void Render_ProducesBgraFrameOfTargetSize_FilledWithColor() + { + var renderer = new SolidFrameRenderer(); + var frame = renderer.Render(width: 1920, height: 1080, b: 0x80, g: 0x80, r: 0x80, a: 0xFF, timestampTicks: 12345); + + frame.Width.Should().Be(1920); + frame.Height.Should().Be(1080); + frame.Format.Should().Be(PixelFormat.Bgra); + frame.Pixels.Length.Should().Be(1920 * 1080 * 4); + frame.TimestampTicks.Should().Be(12345); + + var span = frame.Pixels.Span; + span[0].Should().Be(0x80); span[1].Should().Be(0x80); span[2].Should().Be(0x80); span[3].Should().Be(0xFF); + var last = span.Length - 4; + span[last].Should().Be(0x80); span[last + 3].Should().Be(0xFF); + } +}