SpawnDev.RTC ships a cross-platform video-track path: capture video on either browser or desktop, H.264-encode on desktop (via the OS MediaFoundation encoder), and send over WebRTC to any peer (browser or desktop) from the same C# code.
Core types:
IVideoTrack(from SpawnDev.MultiMedia) — platform-abstract video source. ProducesVideoFrameevents (NV12 preferred for zero-copy encoding).MultiMediaVideoSource(inSpawnDev.RTC.Desktop) — adapter wrapping anIVideoTrackas a SipSorceryIVideoSource. Runs each raw frame throughVideoEncoderFactory.CreateH264(Windows: MediaFoundation H.264 MFT) and emits H.264 Annex-B NAL units for SipSorcery's RTP H.264 packetizer (RFC 6184).DesktopRTCPeerConnection.AddTrack— two video overloads:AddTrack(IVideoTrack)(auto-wraps) orAddTrack(MultiMediaVideoSource)(preconstructed for bitrate control).
Browser consumes video via native WebRTC (getUserMedia → RTCPeerConnection.addTrack). Desktop uses the bridge below.
Capture a webcam and send to a peer. Constraints / media stream types are SpawnDev.MultiMedia's; the peer connection is SpawnDev.RTC.Desktop's. Both namespaces declare a MediaStreamConstraints / MediaTrackConstraints type, so qualify the multimedia ones explicitly to avoid resolution ambiguity:
using SpawnDev.RTC.Desktop; // DesktopRTCPeerConnection
using SpawnDev.MultiMedia; // IVideoTrack, VideoPixelFormat
using MM = SpawnDev.MultiMedia; // alias to disambiguate constraints types
var pc = new DesktopRTCPeerConnection();
// Any IVideoTrack - webcam, screen capture, synthetic, etc.
var stream = await MM.MediaDevices.GetUserMedia(new MM.MediaStreamConstraints
{
Video = new MM.MediaTrackConstraints
{
Width = 640,
Height = 480,
FrameRate = 30,
PixelFormat = VideoPixelFormat.NV12, // zero-copy into MFT
}
});
// IMediaStream.GetVideoTracks() returns IMediaStreamTrack[] (the base interface).
// Cast to IVideoTrack - WindowsVideoTrack / BrowserMediaStreamTrack both implement it.
var videoTrack = (IVideoTrack)stream.GetVideoTracks()[0];
pc.AddTrack(videoTrack); // wraps internally in MultiMediaVideoSource
// ...normal offer/answer SDP exchange via the signaling layer of your choice...Receiver peer's RTCPeerConnection.OnTrack fires with the remote video MediaStreamTrack.
At SpawnDev.RTC.Desktop/MultiMediaVideoSource.cs, the bridge:
- Subscribes to
IVideoTrack.OnFrame. - Confirms the frame is NV12 (MediaFoundation's preferred input; other formats fall through to
SpawnDev.MultiMedia.PixelFormatConverterupstream - the bridge deliberately stays thin and does not embed a conversion pipeline). - Pushes the NV12 bytes into
IVideoEncoder.Encode(Windows →WindowsH264Encoder→H264EncoderMFT). - Receives H.264 Annex-B NAL units and fires
OnVideoSourceEncodedSample, which SipSorcery'sRTPSessionpacketizes per RFC 6184 (Single NAL / FU-A fragmentation) and sends as RTP to the remote peer. - Exposes
EncodedFrameCount/EncodedByteCountdiagnostics and aBitrateBpssetter.
- Codec: H.264, baseline profile (
eAVEncH264VProfile_Base = 66) — widest browser compatibility. Main/High profile are a simpleH264MFT.MF_MT_MPEG2_PROFILEbump if/when a use case emerges. - Rate control: CBR (
eAVEncCommonRateControlMode_CBR = 0). Default bitrate 1.5 Mbps; override viaMultiMediaVideoSource.BitrateBps. - Low latency:
CODECAPI_AVLowLatencyMode = true. No frame buffering / no B-frame reordering — encoder emits every frame as soon as it's done (sub-millisecond on hardware-accelerated MFTs like Intel Quick Sync / NVIDIA NVENC / AMD VCE). - Key frames: Call
source.ForceKeyFrame()to request an IDR. Today's implementation restarts the encoder to guarantee a fresh SPS+PPS+IDR on the next frame (cheap on hardware; millisecond-level). A cleanerCODECAPI_AVEncVideoForceKeyFramepath can replace this if the restart cost ever matters. - RTP clock: 90 kHz (WebRTC video standard, all codecs).
One end-to-end test in SpawnDev.RTC.Demo.Shared/UnitTests/RTCTestBase.Phase4MediaTests.cs:
Phase4b_Desktop_VideoBridge_EncodesAndNegotiatesH264: twoDesktopRTCPeerConnectioninstances exchange a synthetic 320×240 @ 30 fps NV12 pattern, assertOnTrack(video)fires, SDP containsm=video+H264, sender-sideMultiMediaVideoSource.EncodedFrameCount >= 5andEncodedByteCount >= 1000. Completes in ~500 ms on a reasonable dev box.
Plus three lower-level MFT tests in SpawnDev.MultiMedia.Demo.Shared/UnitTests/MultiMediaTestBase.H264Encoder.cs:
H264Encoder_FirstOutput_ContainsSpsPpsIdr— parses Annex-B start codes + NAL-type bytes, asserts types 7 (SPS) + 8 (PPS) + 5 (IDR) all present in the first non-empty output. 21 ms.H264Encoder_MultipleFrames_ProduceIncreasingTimestamps— 30-frame feed + Drain, asserts > 0 outputs + > 100 bytes. 33 ms.H264Encoder_Dispose_DoesNotThrow— encode + dispose + double-dispose. 18 ms.
Plus VideoEncoderFactory_CreateH264_ReturnsWorkingEncoder proves the IVideoEncoder abstraction surface byte-identically to the raw MFT wrapper.
- Windows: ✅ Phase 4b shipped (this doc). MediaFoundation H.264 Encoder MFT via P/Invoke. Zero external NuGet media deps.
- Linux: 🚧 Phase 5 — VAAPI (
libva+libva-drm) or x264 fallback behind the sameVideoEncoderFactory.CreateH264facade. - macOS: 🚧 Phase 5 — VideoToolbox (
VTCompressionSession*). - Browser: ✅ Native WebRTC stack (no bridge needed;
RTCPeerConnection.addTrackon aMediaStreamTrackreturned bygetUserMediajust works).
| Path | Role |
|---|---|
SpawnDev.MultiMedia/Windows/H264MFTInterop.cs |
P/Invoke for MediaFoundation H.264 MFT + ICodecAPI + PROPVARIANT + MFT_OUTPUT_DATA_BUFFER |
SpawnDev.MultiMedia/Windows/H264EncoderMFT.cs |
Thin MFT wrapper: set output/input types, configure via ICodecAPI (low-latency + CBR), ProcessInput / ProcessOutput + Drain + Dispose |
SpawnDev.MultiMedia/Windows/WindowsH264Encoder.cs |
IVideoEncoder implementation on top of H264EncoderMFT |
SpawnDev.MultiMedia/IVideoEncoder.cs |
Platform-agnostic encoder interface + VideoEncoderFactory.CreateH264 dispatch |
SpawnDev.RTC.Desktop/MultiMediaVideoSource.cs |
Bridge: IVideoTrack → IVideoEncoder → IVideoSource (SipSorcery) |
SpawnDev.RTC.Desktop/DesktopRTCPeerConnection.cs |
AddTrack(IVideoTrack) + AddTrack(MultiMediaVideoSource) overloads |
SipSorcery RtpVideoFramer.cs + MediaStream.cs (fork) |
RFC 6184 RTP H.264 packetization (Single NAL / FU-A / STAP-A) - unchanged from upstream |
audio-tracks.md— Phase 4a audio bridge (Opus via Concentus).sctp-tuning.md— Data-channel throughput fix shipped alongside Phase 4a/4b.Plans/PLAN-H264-Encoder.md(in SpawnDev.MultiMedia repo) — Original 6-step Phase 4b execution plan. All 4 implementation steps now shipped; WPF demo integration (step 5) and formal video codec negotiation docs (step 6) remain.