fix(engine): guard scaler against zero-dimension source frames
All checks were successful
CI / build-and-test (push) Successful in 31s
All checks were successful
CI / build-and-test (push) Successful in 31s
A source frame reporting width or height == 0 (a malformed/glitched NDI frame, or a sender mid-renegotiation) drove ComputeFitRect into a division by zero: srcAspect = srcW/srcH with srcH==0 yields Infinity → NaN width/height, and the Stretch path produced a degenerate copy loop. Either way a single bad frame could throw out of Scale and bubble up to the pipeline supervisor as a failure, costing a restart + reconnect. Now a zero-area source short-circuits to a black, fully-opaque frame at the target resolution — the same visual the slate path would show — so a transient bad frame is absorbed silently instead of tearing down the ISO.
This commit is contained in:
parent
e36b928c69
commit
5b3bf7d5e8
1 changed files with 17 additions and 0 deletions
|
|
@ -15,6 +15,15 @@ public sealed class ManagedNearestNeighborFrameScaler : IFrameScaler
|
||||||
$"ManagedNearestNeighborFrameScaler only supports BGRA input (got {source.Format}). " +
|
$"ManagedNearestNeighborFrameScaler only supports BGRA input (got {source.Format}). " +
|
||||||
"v1.0 receivers request BGRA from NDI; UYVY support comes in v1.5.");
|
"v1.0 receivers request BGRA from NDI; UYVY support comes in v1.5.");
|
||||||
|
|
||||||
|
// Degenerate source guard: a frame reporting a zero (or negative) dimension
|
||||||
|
// would divide-by-zero in ComputeFitRect (srcW/srcH) and produce NaN extents,
|
||||||
|
// or drive a degenerate copy loop on the Stretch path. Rather than throw out
|
||||||
|
// of the hot path and cost the pipeline a supervisor restart, emit a black
|
||||||
|
// opaque frame at the target size — identical to what the no-signal slate
|
||||||
|
// would render — so a single bad frame is absorbed without a reconnect.
|
||||||
|
if (source.Width <= 0 || source.Height <= 0)
|
||||||
|
return BlackFrame(targetWidth, targetHeight, timestampTicks);
|
||||||
|
|
||||||
var output = new byte[targetWidth * targetHeight * 4];
|
var output = new byte[targetWidth * targetHeight * 4];
|
||||||
|
|
||||||
// Compute the destination rectangle within the target according to aspect mode.
|
// Compute the destination rectangle within the target according to aspect mode.
|
||||||
|
|
@ -56,6 +65,14 @@ public sealed class ManagedNearestNeighborFrameScaler : IFrameScaler
|
||||||
return new ProcessedFrame(targetWidth, targetHeight, timestampTicks, output, PixelFormat.Bgra);
|
return new ProcessedFrame(targetWidth, targetHeight, timestampTicks, output, PixelFormat.Bgra);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Allocates a black, fully-opaque BGRA frame at the requested size.</summary>
|
||||||
|
private static ProcessedFrame BlackFrame(int width, int height, long timestampTicks)
|
||||||
|
{
|
||||||
|
var output = new byte[width * height * 4];
|
||||||
|
for (var i = 3; i < output.Length; i += 4) output[i] = 0xFF; // alpha; BGR stay 0
|
||||||
|
return new ProcessedFrame(width, height, timestampTicks, output, PixelFormat.Bgra);
|
||||||
|
}
|
||||||
|
|
||||||
private static (int X, int Y, int W, int H) ComputeFitRect(int srcW, int srcH, int dstW, int dstH, AspectMode aspect)
|
private static (int X, int Y, int W, int H) ComputeFitRect(int srcW, int srcH, int dstW, int dstH, AspectMode aspect)
|
||||||
{
|
{
|
||||||
switch (aspect)
|
switch (aspect)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue