Local-first, peer-to-peer real-time media for .NET. Move hardware-encoded H.265 video and Opus audio between machines over WebRTC, on a first-party WebRTC stack (ICE / STUN / DTLS-SRTP / RTP / RTCP / SDP) built on the BCL, no SIP stack, NativeAOT-friendly.
capture (camera / Spout / Syphon / PipeWire) -> HW H.265 -> WebRTC P2P -> HW H.265 -> publish
This is an early
0.1.2-alpha. The core works and is exercised by tests, but it is largely untested in the real world, unpolished in places, and has lots of room to improve. Expect breaking changes and sharp edges. Please try it and file issues, just don't ship it to production yet.
- A complete, dependency-light WebRTC stack: ICE, STUN, DTLS-SRTP (GCM), RTP/RTCP, SDP/JSEP, and a
PeerConnection, BCL crypto, with one BouncyCastle dependency for the DTLS handshake. - Hardware H.265 encode/decode through FFmpeg (NVENC, AMF, QSV, VAAPI, VideoToolbox) with a software fallback, plus pure-managed Opus audio on the same connection, kept in lip-sync.
- Resilience for real links: SCReAM congestion control (RFC 8298 / 8888), sequence-aware H.265 reassembly,
NACK/RTX and FlexFEC loss recovery, and ICE-restart / hot-standby mobility, selected by one media profile
(
InteractiveP2P,ScreenShare,IrlContribution). - Transparency through an opaque codec: side-by-side alpha packing carries a BGRA frame's alpha channel
as full-resolution luma in a
2W x Hframe, so it survives 4:2:0 encode crisply and needs no codec-level alpha support. GPU implementations ship for all three platforms (D3D11 / Metal / Vulkan compute) with a CPU reference that is byte-compatible with them, so a frame packed on one platform unpacks correctly on another. - Per-link telemetry:
MediaPublisher.PeerMetricsgives a per-subscriber snapshot of loss, RTT, target and pacing bitrate;MediaSubscriber.CurrentHealth/CurrentLossStatsgive the receive side. - NativeAOT-friendly: self-contained, trimmed binaries on Windows, Linux, and macOS.
- Capture,
IVideoFrameSource/IVideoFrameSink/IAudioFrameSource. The transport never knows where frames come from, so the GPU-interop libraries below live in the consumer. What the transport does ship is the GPU surface transforms those consumers need — NV12 to BGRA and the side-by-side alpha pack/unpack, in D3D11, Metal, and Vulkan compute. They used to live in the agent sample; keeping them here means every consumer gets the same pixel-exact result instead of reimplementing the colour matrix. - Signaling,
ISignalingChannel. The transport never knows how SDP/ICE are delivered (a WebSocket, a SignalR hub, a tunnel); the host owns reachability.
Zero-copy GPU sharing libraries that feed the capture seam, published separately:
| Library | Platform | Mechanism |
|---|---|---|
| Spout2.NET | Windows | Spout (DirectX 11 shared texture) |
| Syphon.NET | macOS | Syphon (IOSurface / Metal) |
| PipeWire.NET | Linux | PipeWire (DMA-BUF) |
| Package | What it is |
|---|---|
Agash.StreamTransport |
The transport: WebRTC + FFmpeg hardware codecs + room client + GPU surface transforms. |
Agash.StreamTransport.Abstractions |
Capture + signaling contracts. BCL only. |
Agash.StreamTransport.WebRtc |
The WebRTC core: ICE, STUN, SRTP, RTP/RTCP, SDP, PeerConnection. |
Agash.StreamTransport.WebRtc.Abstractions |
Seams for the WebRTC stack. |
Agash.StreamTransport.WebRtc.Dtls |
DTLS-SRTP handshake (the one BouncyCastle dependency). |
Agash.StreamTransport.WebRtc.CongestionControl |
SCReAM controller + pacing. |
Agash.StreamTransport.WebRtc.DependencyInjection |
AddStreamTransportWebRtc() wiring. |
Agash.StreamTransport.Signaling |
Room router + a WebSocket signaling transport. |
Agash.StreamTransport.Stun |
STUN binding server + ICE server providers. |
Needs the .NET 11 preview SDK. FFmpeg 8.1 natives are fetched per platform first:
./eng/fetch-ffmpeg.ps1 -Rids win-x64 # or linux-x64 / linux-arm64 / osx-arm64
dotnet build StreamTransport.slnx -c Release
dotnet test StreamTransport.slnx -c Release --filter "TestCategory!=Integration"A self-hostable signaling relay and a sender/receiver agent live in samples/:
dotnet run --project samples/StreamTransport.Relay # WS :8080/ws + STUN :3478
dotnet run --project samples/StreamTransport.Agent -- send --relay ws://localhost:8080/ws --room demo
dotnet run --project samples/StreamTransport.Agent -- receive --relay ws://localhost:8080/ws --room demoThe agent is a thin consumer now: the GPU alpha and colour-conversion compute it used to carry lives in
Agash.StreamTransport (Codecs/), so the sample shows wiring rather than reimplementation.
See samples/StreamTransport.Agent for capture setup (Spout / Syphon /
PipeWire), device selection, and publishing into OBS.
MIT. See LICENSE.