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