Repository navigation
refactor(gst): decide video input adapters in one place, and write down the block contract - #951
Merged
Merged
Conversation
Three mechanisms each decided what a video input needs in front of it: the WHEP Output caps-event bridge (gldownload, converter + capsfilter), the linker's GL-download retry, and the GL input front's CUDA adapter. Their rules now live in `gst::video_adapt::decide(producer caps, consumer, factory availability) -> Result<Vec<Adapter>, MissingFactory>`. Each call site keeps its own timing (CAPS-event probe, link retry, query probe + IDLE splice) and asks decide() what to insert. The WHEP bridge builds its elements through the shared `build_elements`. The rules are moved unchanged, and their unit tests with them; decide() gets a test per rule. No behaviour change intended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Vision Mixer is the maintained mixer. Blocks have no deprecation flag, so the display name and description say it. Id, properties and behaviour are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
whepserversink is not a consumer class of its own: it advertises GL memory on its video pads but cannot encode it. Drop Consumer::WebrtcSink from decide(); the bridge now asks decide() with the caps the sink can really take (raw video in system memory), so its gldownload follows from the same rule the linker uses. The NV12 conversion is a per-viewer cost saving, not a caps adaptation, so it moves back into the bridge as its own step, with its tests. Element names and order are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…LAUDE.md One source for the block contract, the memory-format rules and the output block input rules: CLAUDE.md imports the file so agents read it as before, and CONTRIBUTING.md points human contributors to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
srperens
added a commit
that referenced
this pull request
Oct 2, 2026
Resolve the conflict with #951, which moved decide() and the CUDA memory feature into gst::video_adapt. The widening tests stay; the old decide() tests already live in video_adapt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 5, 2026
srperens
added a commit
that referenced
this pull request
Oct 8, 2026
…nly while needed (#1033) * fix(vision mixer): adapt GPU inputs to any pixel format, converting only while needed A GPU Vision Mixer input starts with glupload, which uploads a fixed list of system-memory formats. A source in any other format (AYUV64, which a ProRes 4444 clip decodes to on macOS, or GBR_10LE) was refused: a flow link from a capsfilter in such a format was not even made, and a format change mid-stream failed the producer with not-negotiated. The GL input front now adapts pixel formats as well as CUDA memory. video_adapt::decide() calls for a videoconvert when the producer offers raw video in system memory in no format glupload uploads (from the change made for the stinger on feat/stinger). The converter is transient. It goes in and comes out on the input's streaming thread, in a probe on the front queue's source pad, as a CAPS event leaves the queue, so frames queued in the old format still go through the old chain. It comes out as soon as the caps are something glupload takes directly, or CUDA memory; cudadownload (CUDA-GL interop) then goes in directly in front of glupload, never in front of the converter, so NVDEC output never takes a CPU round trip. Until the CAPS event, queries are answered for the input as it will be: an ACCEPT_CAPS for a format only the converter takes is accepted, one for caps the input takes without it is answered by what follows the converter. While the converter is in, the answer to a CAPS query lists what glupload or cudadownload takes directly, CUDA memory included, ahead of what only the converter takes, so a decoder still picks GPU memory. Raw video in system memory in any format is added last to the answer only when a query offers nothing the input takes directly, or nothing at all (the linker's link check); other answers are unchanged. The CPU backend already converts on each input; the new tests show #674's cases pass there, and guard it. Fixes #674. Part of #969's direction (consumer-side adapters decided by decide(), no autovideoconvert); follows #951. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(vision mixer): list glupload's system-memory formats on GStreamer 1.24 Before GStreamer 1.24.13, glupload answers a CAPS query with only the caps of the upload method it is using, whenever those meet the filter. With the input's videoconvert in, the converter writes into glupload's own pool, the method is the GL memory one, and an unfiltered query gets GL memory only. The answer built while the converter is in then had no system-memory formats, so no CUDA alternative, and listed nothing only the converter takes. On 1.24.2, NVDEC into an input that had the converter in decoded to system memory and took a CPU round trip. Ask glupload a second time with a system-memory filter, which misses the GL memory method and makes it list every method, and merge the two answers. An unfiltered answer while the converter is in also lists the converter's system-memory caps, last, as it does while the converter is out. Tests: - The CAPS-answer assertions print the answer. - gpu_converter_answers_with_direct_formats_first counts CUDA memory as taken directly, since cudadownload takes it with no conversion. - The NVDEC test encodes with the first working NVENC H.264 encoder (nvcudah264enc, nvautogpuh264enc, nvh264enc): the legacy nvh264enc fails with "Selected preset not supported" on some driver and GPU combinations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR puts the video input adapter decisions in one place and writes the block contract down as rules. It is Option D from #682, and it follows #948 and #949.
decide(): one place for the adapter rulesThree mechanisms each worked out on their own what a video input needs in front of it. They now all ask
gst::video_adapt::decide(), and each keeps its own timing:video_input_bridge(WHEP Output)gl_link(the linker)gl_input_front(Vision Mixer GPU inputs)The rules, also given in the module doc:
GlUploadCudaDownload, orMissingFactorywhencudadownloadis not installedGlUploadAccepts(consumer caps)GlDownloadWHEP Output is treated as a consumer whose caps are corrected, not as a rule of its own.
whepserversinkadvertisesvideo/x-raw(memory:GLMemory)but cannot encode it. The bridge therefore passes the caps the sink can actually take (system-memory raw video), and the download follows from the same rule the linker uses. The NV12 conversion stays a separate, WHEP-only step in the bridge. It saves converting once per viewer and is not a caps adaptation, so it is outside the table.No behaviour change. The rules moved word for word, and every existing guard passes without edits. Two timing details differ, and neither changes the outcome:
cudadownloadregistry lookup happens just before the lock instead of under it.Compositor deprecated (text only)
The block's name is now "Video Compositor (deprecated)", and its description starts with "Deprecated: no longer maintained; use the Vision Mixer." Its id, properties and behaviour are unchanged. The Vision Mixer is the maintained mixer, so the compositor does not get the new input fronts.
Block guidelines
docs/BLOCK_GUIDELINES.md, which holds the block rules:autovideoconvertand other auto-pluggers that choose by rank, and extenddecide()instead. Behindgldownloadon GStreamer 1.24.2,autovideoconvertfailed 9 of 20 runs. See perf(whep): convert a video input once, not once per viewer #949.CLAUDE.mdimports the file (@docs/BLOCK_GUIDELINES.md), so agents read the same text as before.docs/CONTRIBUTING.mdpoints human contributors to it.Part of #682.
Type of Change
Testing
Ran locally (macOS), rebased on main after #949 and #950:
cargo test --lib: 693 passed, 4 ignored.gl_memory_link_test(7)vision_mixer_cuda_input_test(2)vision_mixer_cuda_missing_test(1)video_input_bridge_gl_test(1)whip_whep_ice_transport_policy_test(9)pipeline_lifecycle_test(3)vision_mixer_fx_test(2)vision_mixer_cpu_test(7)cargo clippy --all-targets -- -D warningsandcargo fmtare clean.Revert checks (each temporary, then restored):
cudadownloadavailability rule, the Accepts consumer check, the CUDA-only rule and the GL-download rule in turn failed 7 tests each time. Among them were the newdecide()tests and the bridge's real pipeline tests.gl_nv12_is_only_downloaded,gl_rgba_is_downloaded_then_convertedandvideo_input_bridge_gl_test.Not run: the full
cargo test --workspace.Known, not changed (it would be a behaviour change, so it is left for a follow-up): the GL input front's CUDA-only check counts a
video/x-raw(ANY)offer as CUDA-only, while the GL rule excludes ANY.🤖 Generated with Claude Code