Repository navigation
Conversation
|
Noted as a draft, so I am holding off on a full review until it is marked ready for |
d87273d to
6cd1ec1
Compare
srperens
left a comment
There was a problem hiding this comment.
Verdict: Comment — the diff is sound and its covering tests genuinely executed in CI (I read the job log, not just the tick). What stops this being an Approve is not in the diff: #682 is still asking where this decision belongs, and merging this answers part of that question by landing.
Claims
| Claim | Verdict | Evidence |
|---|---|---|
| The new tests ran rather than skipping past their guard | CONFIRMED |
job 104575573305 at 6cd1ec1: 19 gst::video_input_bridge::tests::* lines print ... ok, and test gl_rgba_reaches_the_peer_as_system_memory_nv12 ... ok in 1.67s. No SKIP: line — the DISPLAY/GL guard did not fire on the Linux runner |
| The probe is per-event, not per-buffer | CONFIRMED |
backend/src/gst/video_input_bridge.rs:140 — ` src_pad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, move |
| No strong pipeline/element reference is captured in the closure | CONFIRMED |
the closure captures two Strings; backend/src/gst/video_input_bridge.rs:234 — fn splice(src_pad: &gst::Pad, adapters: &[gst::Element]) -> Result<(), String> { — takes the bin from the pad at call time and keeps it local |
| This adds a third consumer of the global converter mode | CONFIRMED |
backend/src/blocks/builtin/whep.rs:1584 — let convert_factory = video_convert_mode().element_name(); |
| The CPU measurements | EXTERNAL |
backend/tests/whep_fanout_bench.rs:69 — #[ignore] — so CI never runs it. Apple Silicon debug builds, as the body states; I take the numbers at face value and cannot reproduce them here |
Diagnosis — Right layer and right mechanism. The cost is per consumer because webrtcsink builds a chain per consumer, so pinning once upstream of the fan-out is where the saving is, and the loop is CLOSED — one splice on the first CAPS event, then the probe removes itself. Coverage is BOUNDED and the bound is stated honestly in the body: encoded input, non-GL GPU memory, NV12/I420 and >8-bit are left alone, and VP9/AV1 consumers still convert once each. Resolving convert_factory outside the probe rather than inside is the right call and the comment says why — a panic there would be on a streaming thread.
Overlaps — this is the whole of my verdict. #682 is an open issue whose Ask: is unanswered as of my comment there today, and one of its options is literally "wait for #799 and #731, change nothing here". The conflict is concrete, not thematic:
- This PR replaces the runtime entry point that #682's phase 2 proposes to extract into a shared
decide(), and folds a second decision (pixel format) into the same splice. Phase 2 would then extract from a file this PR has just rewritten. - It adds the call site above to
video_convert_mode()— the API #682 phase 4 proposes to delete and #731 proposes to extend. - It re-points CLAUDE.md's memory-format convention from
gst::gl_bridgetogst::video_input_bridge, narrowing the documented reference implementation to one block while #682 is deciding whether that rule should be shared.
None of that says the diff is wrong — it may well be the right first step. It says a human picks the order, and #682 is where that choice is already teed up. I am not asking for a change here.
Radius — SHARED: one caller, but it changes what every WHEP Output viewer is fed, across input shapes the bench did not cover (NVENC and VA hosts are explicitly unmeasured).
Repo rules — none violated that I found. EVENT_DOWNSTREAM rather than BUFFER, no captured pipeline reference, queues untouched, no emoji in the log macros, and the CLAUDE.md edit is a rename of an example, not a change to a rule.
Confidence: HIGH
Eyevinn#799 makes the block read video_convert_mode() at build time, which panics until detection has run. The block tests from Eyevinn#814 and Eyevinn#832 build it without that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eyevinn#799 makes the block read video_convert_mode() at build time, which panics until detection has run. The block tests from Eyevinn#814 and Eyevinn#832 build it without that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit d4ea5d6)
Integration fix-up: Eyevinn#799 makes the WHEP Output block read video_convert_mode() at build time, which panics until detection has run. The block tests from Eyevinn#896 and Eyevinn#899 build it without that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Integration fix-up: Eyevinn#799 makes the WHEP Output block read video_convert_mode() at build time, which panics until detection has run. The block tests from Eyevinn#896 and Eyevinn#899 build it without that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eyevinn#799 makes the block read video_convert_mode() at build time, which panics until detection has run. The block tests from Eyevinn#814 and Eyevinn#832 build it without that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit d4ea5d6)
Integration fix-up: Eyevinn#799 makes the WHEP Output block read video_convert_mode() at build time, which panics until detection has run. The block tests from Eyevinn#896 and Eyevinn#899 build it without that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Integration fix-up: Eyevinn#799 makes the WHEP Output block read video_convert_mode() at build time, which panics until detection has run. The block tests from Eyevinn#896 and Eyevinn#899 build it without that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eyevinn#799 makes the block read video_convert_mode() at build time, which panics until detection has run. The block tests from Eyevinn#814 and Eyevinn#832 build it without that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit d4ea5d6)
webrtcsink builds a full encoding chain per consumer and each chain starts with its own videoconvert, so N viewers convert the same frames N times. The WHEP Output block pinned no raw format, so whatever arrived was what every consumer converted from — and each converted into whatever its encoder's caps preferred, which for VideoToolbox is 16-bit RGBA64_LE and for vp9enc is 4:4:4 Y444. The block now adapts its own video input from the negotiated caps, in the same one-shot probe that already downloaded GL memory: raw video that is not already 8-bit 4:2:0 gets a converter and an NV12 pin spliced in front of the sink. A hardware encoder's per-consumer converter then runs in passthrough. The VP9 and AV1 software encoders do not take NV12, so their consumers still convert, but from NV12 to I420 rather than from RGBA. Encoded input, other GPU memory types and formats deeper than 8 bits are left untouched, and a path that already carries NV12 or I420 gets nothing spliced at all. Measured on the block with two whepclientsrc consumers, 1080p30 RGBA in, CPU per extra consumer: H.265 through vtenc 23.7% and 39.2% of a core before, 1.4% and 1.3% after; VP9 through vp9enc 26.0% and 23.5% before, 17.2% and 14.9% after. The single conversion costs 4 to 8 points of a core once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
6cd1ec1 to
3c5c7ea
Compare
…block The WHEP Output block will read video_convert_mode() at build time once Eyevinn#799 lands, and that panics until detection has run. Detecting here is harmless today and keeps the test working in either merge order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r test block The WHEP Output block will read video_convert_mode() at build time once Eyevinn#799 lands, and that panics until detection has run. Detecting here is harmless today and keeps the test working in either merge order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r test block The WHEP Output block will read video_convert_mode() at build time once Eyevinn#799 lands, and that panics until detection has run. Detecting here is harmless today and keeps the test working in either merge order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks @wagenet. This is carried forward in #949. Your commit is included unchanged, with you as author. On top of it, #949 merges Closing in favour of #949. Claude (AI), on behalf of @srperens. |
…block The WHEP Output block will read video_convert_mode() at build time once Eyevinn#799 lands, and that panics until detection has run. Detecting here is harmless today and keeps the test working in either merge order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r test block The WHEP Output block will read video_convert_mode() at build time once Eyevinn#799 lands, and that panics until detection has run. Detecting here is harmless today and keeps the test working in either merge order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r test block The WHEP Output block will read video_convert_mode() at build time once Eyevinn#799 lands, and that panics until detection has run. Detecting here is harmless today and keeps the test working in either merge order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#814) * fix(whep): stop the appsink processing deadline delaying every viewer whepserversink ends each input in a syncing appsink that only hands buffers to the per-viewer session pipelines. BaseSink's default 20 ms processing deadline is added to the pipeline latency, waited out by that appsink, forwarded to the session's appsrc by StreamProducer, and waited out again by webrtcbin's input clocksync before the packet is sent. Setting it to zero on whepserversink's own appsinks removes 20 ms from the flow latency and from what a WHEP viewer hears. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(whep): check the processing deadline on every input appsink The latency test covers one audio input, so it passes if the fix reaches only some appsinks. Add a test that builds 2 audio + 2 video tracks and asserts each of whepserversink's input appsinks has processing-deadline 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(whep): stop the TS Offset description promising the full offset ts-offset shifts only the clock wait at whepserversink's input appsink. Each viewer's session still waits out the full pipeline latency, so -30 ms measured about 8 ms of real gain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(whep): wait for data instead of a fixed sleep in the multi-track test The appsinks exist once the inputs are linked, so the 1.5 s sleep waited for nothing. Waiting until the latency query answers live means data has reached them, which is what the check needs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(whep): wait for a buffer at every appsink before checking the deadline The latency query answers live with no data at any appsink, so polling it did not wait for streaming to start. Flag the first buffer at each appsink and check the deadline once all four have seen one, so a reset of the deadline when data starts flowing fails the test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(whep): record the processing-deadline trade-off at the handler Match the #855 WHIP twin: say what the deadline was slack against and which flow shape pays for removing it. Drop the one-flow measurement from the TS Offset UI description. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(whep): detect GPU capabilities before building the latency test block The WHEP Output block will read video_convert_mode() at build time once #799 lands, and that panics until detection has run. Detecting here is harmless today and keeps the test working in either merge order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Per Enstedt <77342700+srperens@users.noreply.github.com>
… chain (#896) * fix(whep_output): strip the offered profile from every viewer's video chain A WHEP viewer that offers H.264 Baseline gets a capsfilter in front of its payloader listing only Baseline-compatible profiles. The block strips that field so a High profile stream can pass, but it found the filter through an element-added watcher installed at payloader-setup, after webrtcsink had already added the chain. The strip only happened when another stream of the same viewer had been set up first, and webrtcsink sets streams up in HashMap order, so a viewer whose video came first failed with not-negotiated at the first keyframe. The filters are now found from the payloader itself: the one directly upstream and the ones downstream, each stripped now and on every later caps change. The hook moves to its own module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(whep): detect GPU capabilities before building the profile filter test block The WHEP Output block will read video_convert_mode() at build time once #799 lands, and that panics until detection has run. Detecting here is harmless today and keeps the test working in either merge order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
What this changes
webrtcsinkbuilds a full encoding chain per consumer, and each chain starts with its ownvideoconvert(PayloadChainBuilder::build→make_converter_for_video_caps). The WHEP Outputblock pinned no raw format, so N viewers converted the same frames N times — each into whatever its
encoder's caps preferred, which measured as 16-bit
RGBA64_LEfor the VideoToolbox encoders and4:4:4
Y444forvp9enc.The block now adapts its own video input from the negotiated caps, in the one-shot CAPS probe that
already downloaded GL memory.
gl_bridgebecomesvideo_input_bridgeand makes both decisions inone splice:
gldownloadfirst where the input is GL memory, then a converter and anNV12capsfilter where the format is not already 8-bit 4:2:0. Left alone, deliberately:
videoconvertcould not link to it.the per-consumer conversion from one set of encoders to another.
CLAUDE.mdnamedgst::gl_bridgeas the example of deciding from negotiated caps; it now namesgst::video_input_bridge.devicesrc(upstream #842) takesGL_MEMORY_FEATUREfromvideo_input_bridge, which now exports it.Why NV12
NV12 is native to the hardware encoders:
vtenc_h264/vtenc_h265, NVENC and the VA encoders, withx264enclisting it alongside I420. The VP9 and AV1 software encoders (vp9enc,av1enc,rav1enc,svtav1enc) do not accept NV12, so their consumers still convert once each — from NV12to I420 instead of from RGBA. Per extra consumer, feeding the block each format directly:
I420 measured slightly cheaper on both of these; NV12 is for NVENC and VA, which were not measured.
I420 input is passed through, not converted.
A mixer-side pin would serve several sinks with one conversion, and on the GPU backend costs nothing
(#797 makes
output_formatwork there). This pin is conditional, so it is a no-opbehind such a mixer. Three WHEP sinks fed from one CPU mixer still convert three times.
Measured
Apple Silicon, 1080p30, RGBA in, two
whepclientsrcconsumers per run each pinned to one codec, CPUof the process running the real block (
backend/tests/whep_fanout_bench.rs), A/B interleaved, perextra consumer:
vtenc_h265vp9encThe one conversion costs 4 to 8 points of a core, so a single viewer already breaks even. Encoder
input caps confirm the mechanism: before,
vtenc_h265tookRGBA64_LEandvp9enctookY444;after,
NV12andI420. These are debug builds, wherewebrtcsinkis unoptimised, so absolutefigures may run high. H.264 consumers could not be measured: a
whepclientsrcoffering only H.264gets
a=inactivehere, with or without this change.Tests
gst::video_input_bridge, 19 unit tests: 12 on the caps predicates (5 carried over fromgl_bridge), 3 on whatbuild_adaptersreturns and in what order, and 4 live pipelines throughthe bridge — RGBA converted, NV12 and I420 untouched, RGBA converted by
autovideoconvert.backend/tests/video_input_bridge_gl_test.rspushes real GL memory (gltestsrc, RGBA) throughthe bridge with
videoconvertandautovideoconvert, and asserts the splice isgldownload ! <converter> ! capsfilterand the peer gets system-memory NV12. On Linux with nodisplay it requests a surfaceless EGL context (Mesa software GL); under
STROM_REQUIRE_GST_PLUGINS=1, as in CI, a missing element or GL context fails instead ofskipping. An Ubuntu 24.04 container with CI's packages runs that pipeline headless.
blocks::builtin::whep::tests::video_input_is_converted_before_the_sinkbuilds the real block,feeds it RGBA, and asserts
whepserversink'svideo_0pad negotiates NV12.Reverts: with
needs_format_conversionstubbed tofalse, the GL test, the RGBA pipeline tests andthe block test fail; with
needs_gl_downloadstubbed, the GL test fails.Ran on macOS on this head:
cargo test --lib(591 passed), the GL test withSTROM_REQUIRE_GST_PLUGINS=1,pipeline_lifecycle_test,openapi_test, clippy-D warnings,fmt. Linux CI passed on the previous head, which lacked the GL test; this head's CI is its first
Linux run. Not run: Windows, CUDA hosts, the rest of the integration suite.
whep_fanout_bench.rsis#[ignore]d (it needswhepclientsrcand spawns consumer processes) andguards nothing. It fails a run in which any consumer received no RTP buffers.
Not validated on the rig: no browser-level confirmation.
🤖 Generated with Claude Code