feat(pipeline,interop): add RawFrame, ProcessedFrame, IFrameClock and INdiInterop test seam
Some checks failed
CI / build-and-test (push) Has been cancelled
Some checks failed
CI / build-and-test (push) Has been cancelled
This commit is contained in:
parent
3f8b5f1a7b
commit
f562303b47
8 changed files with 116 additions and 0 deletions
32
src/TeamsISO.Engine/Interop/INdiInterop.cs
Normal file
32
src/TeamsISO.Engine/Interop/INdiInterop.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
using TeamsISO.Engine.Pipeline;
|
||||||
|
|
||||||
|
namespace TeamsISO.Engine.Interop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test seam over the NDI SDK. Production: P/Invoke shim. Tests: <c>FakeNdiInterop</c>.
|
||||||
|
/// All methods are synchronous; the engine threads are responsible for orchestration.
|
||||||
|
/// </summary>
|
||||||
|
public interface INdiInterop
|
||||||
|
{
|
||||||
|
// ----- Discovery -----
|
||||||
|
NdiFindHandle CreateFinder();
|
||||||
|
|
||||||
|
/// <summary>Snapshots the currently-known sources visible to the finder.</summary>
|
||||||
|
IReadOnlyList<string> GetCurrentSources(NdiFindHandle finder);
|
||||||
|
|
||||||
|
// ----- Receive -----
|
||||||
|
NdiReceiverHandle CreateReceiver(string sourceFullName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Blocks for up to <paramref name="timeoutMs"/> waiting for a frame.
|
||||||
|
/// Returns null on timeout. Returned <see cref="RawFrame"/> ownership transfers to the caller.
|
||||||
|
/// </summary>
|
||||||
|
RawFrame? CaptureFrame(NdiReceiverHandle receiver, int timeoutMs);
|
||||||
|
|
||||||
|
// ----- Send -----
|
||||||
|
NdiSenderHandle CreateSender(string outputName);
|
||||||
|
void SendFrame(NdiSenderHandle sender, ProcessedFrame frame);
|
||||||
|
|
||||||
|
// ----- Runtime probe -----
|
||||||
|
string GetRuntimeVersion();
|
||||||
|
}
|
||||||
7
src/TeamsISO.Engine/Interop/NdiFindHandle.cs
Normal file
7
src/TeamsISO.Engine/Interop/NdiFindHandle.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace TeamsISO.Engine.Interop;
|
||||||
|
|
||||||
|
/// <summary>Opaque handle to an NDI Find instance. Implementation-private.</summary>
|
||||||
|
public abstract class NdiFindHandle : IDisposable
|
||||||
|
{
|
||||||
|
public abstract void Dispose();
|
||||||
|
}
|
||||||
6
src/TeamsISO.Engine/Interop/NdiReceiverHandle.cs
Normal file
6
src/TeamsISO.Engine/Interop/NdiReceiverHandle.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace TeamsISO.Engine.Interop;
|
||||||
|
|
||||||
|
public abstract class NdiReceiverHandle : IDisposable
|
||||||
|
{
|
||||||
|
public abstract void Dispose();
|
||||||
|
}
|
||||||
6
src/TeamsISO.Engine/Interop/NdiSenderHandle.cs
Normal file
6
src/TeamsISO.Engine/Interop/NdiSenderHandle.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace TeamsISO.Engine.Interop;
|
||||||
|
|
||||||
|
public abstract class NdiSenderHandle : IDisposable
|
||||||
|
{
|
||||||
|
public abstract void Dispose();
|
||||||
|
}
|
||||||
14
src/TeamsISO.Engine/Pipeline/IFrameClock.cs
Normal file
14
src/TeamsISO.Engine/Pipeline/IFrameClock.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
namespace TeamsISO.Engine.Pipeline;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test seam over the wall clock. Production: <see cref="PeriodicTimerFrameClock"/>.
|
||||||
|
/// Tests: <c>FakeFrameClock</c> in TeamsISO.Engine.Tests.
|
||||||
|
/// </summary>
|
||||||
|
public interface IFrameClock
|
||||||
|
{
|
||||||
|
/// <summary>Current monotonic time as ticks (100 ns).</summary>
|
||||||
|
long NowTicks { get; }
|
||||||
|
|
||||||
|
/// <summary>Awaits the next tick at the current period.</summary>
|
||||||
|
ValueTask<bool> WaitForNextTickAsync(CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
21
src/TeamsISO.Engine/Pipeline/PeriodicTimerFrameClock.cs
Normal file
21
src/TeamsISO.Engine/Pipeline/PeriodicTimerFrameClock.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
namespace TeamsISO.Engine.Pipeline;
|
||||||
|
|
||||||
|
public sealed class PeriodicTimerFrameClock : IFrameClock, IDisposable
|
||||||
|
{
|
||||||
|
private readonly PeriodicTimer _timer;
|
||||||
|
|
||||||
|
public PeriodicTimerFrameClock(double framesPerSecond)
|
||||||
|
{
|
||||||
|
if (framesPerSecond <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(framesPerSecond));
|
||||||
|
var periodMs = 1000.0 / framesPerSecond;
|
||||||
|
_timer = new PeriodicTimer(TimeSpan.FromMilliseconds(periodMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
public long NowTicks => DateTime.UtcNow.Ticks;
|
||||||
|
|
||||||
|
public ValueTask<bool> WaitForNextTickAsync(CancellationToken cancellationToken) =>
|
||||||
|
_timer.WaitForNextTickAsync(cancellationToken);
|
||||||
|
|
||||||
|
public void Dispose() => _timer.Dispose();
|
||||||
|
}
|
||||||
11
src/TeamsISO.Engine/Pipeline/ProcessedFrame.cs
Normal file
11
src/TeamsISO.Engine/Pipeline/ProcessedFrame.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
namespace TeamsISO.Engine.Pipeline;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A frame after framerate, resolution, and aspect normalization. Ready to send.
|
||||||
|
/// </summary>
|
||||||
|
public sealed record ProcessedFrame(
|
||||||
|
int Width,
|
||||||
|
int Height,
|
||||||
|
long TimestampTicks,
|
||||||
|
ReadOnlyMemory<byte> Pixels,
|
||||||
|
PixelFormat Format);
|
||||||
19
src/TeamsISO.Engine/Pipeline/RawFrame.cs
Normal file
19
src/TeamsISO.Engine/Pipeline/RawFrame.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
namespace TeamsISO.Engine.Pipeline;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A frame as captured from an NDI receiver. Pixel buffer is opaque to the engine — its
|
||||||
|
/// shape is determined by the NDI receive format. Timestamp is the source's reported time.
|
||||||
|
/// </summary>
|
||||||
|
public sealed record RawFrame(
|
||||||
|
int Width,
|
||||||
|
int Height,
|
||||||
|
long TimestampTicks,
|
||||||
|
ReadOnlyMemory<byte> Pixels,
|
||||||
|
PixelFormat Format);
|
||||||
|
|
||||||
|
public enum PixelFormat
|
||||||
|
{
|
||||||
|
Bgra,
|
||||||
|
Uyvy,
|
||||||
|
Rgba
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue