test(engine): pin audio FourCC constants to ASCII packing
All checks were successful
CI / build-and-test (push) Successful in 28s

Adds regression tests that would have caught the PCMs16 FourCC typo
(0x73334d50 'PM3s' vs correct 0x734D4350 'PCMs'). The prior tests
referenced the symbol on both sides of the assertion, so a wrong literal
stayed self-consistent and green. These new tests:

  1. Assert each public FourCC constant equals the little-endian ASCII
     packing of its documented characters (the same convention the NDI
     SDK and the video FourCCs use).
  2. Feed a raw PCM16 buffer tagged with the *literal* 0x734D4350 and
     assert the peak is decoded — proving a real Teams legacy sender's
     FourCC routes to the int16 decoder rather than the unknown branch.
This commit is contained in:
Zac Gaetano 2026-06-13 00:21:55 -04:00
parent 915510c938
commit e5f10f2667

View file

@ -22,7 +22,7 @@ public class AudioPeakComputerTests
[Fact] [Fact]
public void UnknownFourCC_ReturnsZero_RatherThanThrow() public void UnknownFourCC_ReturnsZero_RatherThanThrow()
{ {
// Receiver loop must never crash on an unrecognized format — better to // Receiver loop must never crash on an unrecognized format better to
// show silence on the meter than to take down the pipeline. // show silence on the meter than to take down the pipeline.
var floats = new[] { 0.5f, -0.5f }; var floats = new[] { 0.5f, -0.5f };
var bytes = AsBytes(floats); var bytes = AsBytes(floats);
@ -77,7 +77,7 @@ public class AudioPeakComputerTests
public void Fltp_TotalSamplesSmallerThanBuffer_OnlyConsumesReportedRange() public void Fltp_TotalSamplesSmallerThanBuffer_OnlyConsumesReportedRange()
{ {
// The reported range covers only the first 3 floats. The 4th // The reported range covers only the first 3 floats. The 4th
// (largest) is past `totalSamples` and must be ignored — otherwise we'd // (largest) is past `totalSamples` and must be ignored otherwise we'd
// be reading beyond what the source said it wrote. // be reading beyond what the source said it wrote.
var floats = new[] { 0.1f, -0.2f, 0.3f, 0.99f }; var floats = new[] { 0.1f, -0.2f, 0.3f, 0.99f };
var bytes = AsBytes(floats); var bytes = AsBytes(floats);
@ -128,10 +128,59 @@ public class AudioPeakComputerTests
var samples = new[] { (short)0, (short)16384, (short)-16383 }; var samples = new[] { (short)0, (short)16384, (short)-16383 };
var bytes = AsBytes(samples); var bytes = AsBytes(samples);
var peak = AudioPeakComputer.ComputePeak(bytes, AudioPeakComputer.FourCC_PCMs16, samples.Length); var peak = AudioPeakComputer.ComputePeak(bytes, AudioPeakComputer.FourCC_PCMs16, samples.Length);
// 16384 / 32767 ≈ 0.500015; tolerate small precision drift. // 16384 / 32767 0.500015; tolerate small precision drift.
Assert.InRange(peak, 0.49, 0.51); Assert.InRange(peak, 0.49, 0.51);
} }
// ============================================================
// FourCC constant integrity — regression guard
// ============================================================
//
// The NDI SDK packs FourCCs little-endian: the first ASCII character
// lands in the least-significant byte. These tests pin each public
// constant to the little-endian packing of its documented characters,
// so a transposed/typo'd literal (the PCMs16 0x73334d50 → 0x734D4350
// fix) fails loudly instead of silently routing real audio to the
// "unknown format → 0.0" branch.
[Fact]
public void FourCC_FLTP_MatchesAsciiPacking()
{
AudioPeakComputer.FourCC_FLTP.Should().Be(Pack('F', 'L', 'T', 'p'));
}
[Fact]
public void FourCC_FLT_MatchesAsciiPacking()
{
AudioPeakComputer.FourCC_FLT.Should().Be(Pack('F', 'L', 'T', ' '));
}
[Fact]
public void FourCC_PCMs16_MatchesAsciiPacking()
{
// Regression: was 0x73334d50 ('P','M','3','s'), should be 'P','C','M','s'.
AudioPeakComputer.FourCC_PCMs16.Should().Be(Pack('P', 'C', 'M', 's'));
AudioPeakComputer.FourCC_PCMs16.Should().Be(0x734D4350u);
}
[Fact]
public void Pcms16_DecodedViaLiteralFourCC_NotJustSymbol()
{
// Belt-and-braces: feed a PCM16 buffer tagged with the *literal* FourCC
// a real legacy Teams/NDI sender emits, not the symbol. If the constant
// ever drifts from the wire value again, this peak collapses to 0.0
// because ComputePeak falls through to the unknown branch.
const uint wirePcms16 = 0x734D4350; // little-endian 'P','C','M','s'
var samples = new[] { (short)0, (short)16384, (short)-200 };
var bytes = AsBytes(samples);
var peak = AudioPeakComputer.ComputePeak(bytes, wirePcms16, samples.Length);
peak.Should().BeInRange(0.49, 0.51);
}
/// <summary>Little-endian ASCII FourCC packing: first char in the low byte.</summary>
private static uint Pack(char a, char b, char c, char d) =>
(uint)a | ((uint)b << 8) | ((uint)c << 16) | ((uint)d << 24);
private static byte[] AsBytes<T>(T[] arr) where T : struct private static byte[] AsBytes<T>(T[] arr) where T : struct
{ {
var span = MemoryMarshal.Cast<T, byte>(arr.AsSpan()); var span = MemoryMarshal.Cast<T, byte>(arr.AsSpan());