feat(engine): expose framerate as an exact rational (N/D)
Some checks failed
CI / build-and-test (push) Has been cancelled

FramerateHz collapses 59.94 etc. to a lossy double. The NDI sender needs
the exact frame_rate_N / frame_rate_D rational the broadcast standard
defines (59.94 = 60000/1001, not 59.94/1). Adds a FrameRate (N, D)
property mapping each TargetFramerate to its canonical rational so the
sender can advertise the correct cadence instead of a rounded value.
Pure + unit-testable; no behavior change on its own.
This commit is contained in:
Zac Gaetano 2026-06-13 08:27:50 -04:00
parent b2e22a1186
commit 5a496c93a0

View file

@ -23,6 +23,27 @@ public sealed record FrameProcessingSettings(
_ => throw new InvalidOperationException($"Unknown framerate: {Framerate}") _ => throw new InvalidOperationException($"Unknown framerate: {Framerate}")
}; };
/// <summary>
/// Returns the framerate as an exact rational (numerator / denominator), as
/// required by the NDI sender's <c>frame_rate_N</c> / <c>frame_rate_D</c> and
/// by container muxers. The broadcast fractional rates are 1000/1001 pulldowns
/// of their integer parents (e.g. 59.94 = 60000/1001), so we must carry the
/// rational rather than a rounded <see cref="FramerateHz"/> double — advertising
/// 60/1 for a 59.94 stream causes drift and A/V desync downstream.
/// </summary>
public (int Numerator, int Denominator) FrameRate => Framerate switch
{
TargetFramerate.Fps23_976 => (24000, 1001),
TargetFramerate.Fps24 => (24, 1),
TargetFramerate.Fps25 => (25, 1),
TargetFramerate.Fps29_97 => (30000, 1001),
TargetFramerate.Fps30 => (30, 1),
TargetFramerate.Fps50 => (50, 1),
TargetFramerate.Fps59_94 => (60000, 1001),
TargetFramerate.Fps60 => (60, 1),
_ => throw new InvalidOperationException($"Unknown framerate: {Framerate}")
};
/// <summary>Returns the resolution as (width, height).</summary> /// <summary>Returns the resolution as (width, height).</summary>
public (int Width, int Height) ResolutionSize => Resolution switch public (int Width, int Height) ResolutionSize => Resolution switch
{ {