Repository navigation
feat(studio): a Linux virtual camera, as a PipeWire node or a v4l2loopback device - #9
Conversation
Quest-Claim: 131a812fdb42e147
…pback device The Output panel's Virtual camera toggle shows the program as a camera: a PipeWire Video/Source node with the Camera role (the `pipewire` feature), falling back to an existing v4l2loopback device. The program is read back for it only while an app reads the camera. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Decision (maintainer, 2026-11-02): the readback goes upstream first. A public readback-only (Written by Claude Opus 5.5) |
Adapts the virtual camera to main: it reads the main canvas back through moq-video's render::readback (pinned by #69) instead of a copy here, and reopens when the canvas changes size or frame rate. The Devices panel leaves out the camera while it is on. quest/m0/vcam/linux.md keeps what remains: a camera per published scene. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One Linux dependency table, the camera's Output panel row after the canvases, and the lockfile entries for moq-v4l and pipewire. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Design change: merged main and switched the virtual camera to moq-video's public readback, as decided on 2026-11-02.
The review restarted with (Written by Claude Opus 5.5) |
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 40 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed 63720dc against 299a97a. No actionable introduced defects found.
Direction looks sound: the camera reuses the pinned upstream readback, keeps backend work outside the program-state lock, and orders canvas reopen and picture delivery on its worker. Reviewed the PipeWire and v4l2loopback paths, UI toggle/device filtering, and build/test wiring. The documented program-only scope leaves per-scene cameras for the planned follow-up.
Verification: GitHub-only static inspection, including full changed implementation files and relevant callees. No builds, tests, quest context, GPU measurements, or actual browser/PipeWire/v4l2loopback sessions were run. This does not independently validate the reported hardware checks or conferencing-app compatibility.
(Written by review (OpenAI))
The virtual camera from #9 reads the program, which published scenes keep; a camera per published scene stays quest/m0/vcam/linux.md, which no longer waits on published scenes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
Video-call and conferencing apps offer no ingest, so a creator cannot use the
studio's program in them. The virtual camera epic
starts with Linux: a PipeWire video node that PipeWire-aware apps list as a
camera, falling back to an existing v4l2loopback device. The first backend to
land also adds the studio's toggle.
Approach
rs/moq-studio/src/vcam.rs: on each frame the program clock callsCamera::offerwith the main canvas's program. Only while an app reads thecamera, it reads the program back through moq-video's
render::readback::Readback(pinned bymoq-dev/studio#69), which
never waits on the GPU and refuses a copy when its three staging buffers are
all in flight; that frame is dropped and counted. A
vcamthread waits foreach copy and hands tightly packed RGBA to the backend.
and opens it again for the new one on the
vcamthread, so the programclock never waits on it. An app reading the camera opens it again.
vcam/pipewire.rs(thepipewirefeature): aVideo/Sourcestream withmedia.role = Camera, namedmoq-studioand described as "MoQ Studio".This is what libwebrtc (Chromium and Firefox) and moq-video's own reader
filter on. It offers one fixed format, RGBA at the canvas size and frame
rate, which libwebrtc maps to
kABGR, so the studio does no conversion.The stream drives the graph: each picture triggers a cycle. It runs on its
own PipeWire main loop, fed through a one-picture slot and a wake that is
never queued twice.
Streamingturns copying on;Pausedturns it off.vcam/v4l2.rs: the fallback. It takes the first/dev/videoNwhose driveris
v4l2 loopbackand accepts the canvas size as tightly packed I420 (YU12,which the common V4L2 camera readers take). It writes one frame per
write. A device that answers with another layout is refused, with thereason.
Engine::set_cameraopens and closes the camera outside thestate lock. The Output panel gets a Virtual camera checkbox, plus where
the camera lives, the picture size and rate, whether an app is reading it,
frames sent and dropped, and any error. The Devices panel leaves out the
camera while it is on, so it cannot be added to the program it shows.
Impact
Studio" PipeWire camera (needs the
pipewirefeature), or else uses anexisting v4l2loopback device. macOS and Windows report that it is not
available yet.
pipewirefeature now also builds the virtual camera'sPipeWire node.
just check/just testnow covermoq-studio/pipewire, as AGENTS.mdalready says they do. Previously the justfile left it out, so this code
would never have been linted.
render-swalso runs the PipeWire camera test inside a privatePipeWire + WirePlumber session (
sh/pipewire-session.sh). A newv4l2loopbackjob builds the module with DKMS and runsjust v4l2loopback.readback is used as published.
quest/m0/vcam/linux.mdnow holds what remains, a camera per publishedscene, which waits on published scenes.
Decisions
vcam.rsused to copy the programinto staging buffers itself, the same shape as moq-video's private export
readback.
moq-side test, then a pin here, and switch
vcam.rsto it before thismerges. Decided by the maintainer on 2026-11-02 (the comment below and
the
quest/m0/pin-readback.mdPlan). Landed asmoq-dev/moq#8056
(
moq_video::render::readback), pinned bymoq-dev/studio#69; this PR
now uses it and drops the copy.
Choices made without asking
OBS's "Start Virtual Camera" button, and the scene file is meant to be
shared.
main gained further canvases and planned
published scenes
to replace them, and the quest's Goal grew to "the program or a published
scene". A camera per published scene needs published scenes first, so it
stays in
quest/m0/vcam/linux.mdwith that quest as its blocker, ratherthan a per-canvas toggle here that published scenes would replace.
the edit, since undo, a reload, and the Output panel can all change the
canvas while it is on.
(
pipewire:moq-studio, or the loopback's/dev/videoN), only while it ison. A listing taken while the camera was off still shows nothing of it,
since it did not exist.
and moq-video all take it, and RGBA avoids a CPU conversion and a YUV matrix
the app would have to guess. v4l2loopback uses I420, because V4L2 readers
expect YUV.
session, or a build without
pipewire), the studio logs a warning and triesv4l2loopback. If both fail, the error names why each one refused.
Verification
All on
nixos(Linux, RTX 3070 Ti, desktop PipeWire session) at head63720dc, since the PipeWire and v4l2loopback paths need Linux:just cipasses (quest gate just ci: lints includingmoq-studio/pipewire, and 278 tests).engine::tests::the_virtual_camera_reaches_a_pipewire_apppassed againstthe desktop PipeWire session (features
pipewire,openh264) and insidesh/pipewire-session.sh(featurepipewire, as nightly runs it). The teststarts the engine with a red canvas, turns the camera on, and opens
pipewire:moq-studiothrough moq-video's PipeWire camera reader, whichfilters on class and role as browsers do. It asserts the red picture
arrives,
readingflips on, and frames are counted; then it resizes thecanvas to 320x180, waits for the camera to reopen at that size, and reads
red through it again.
vcam::v4l2::tests::a_v4l2_app_reads_the_program, sincenixoshas no v4l2loopback module and loading one means changing the host'sNixOS configuration.
vcam/v4l2.rsis unchanged since it passed against areal v4l2loopback device, and in nightly
run 41543325349
(head
81c621d), which also passed the PipeWire test in the privatesession. The merged nightly's
v4l2loopbackjob covers it from here.panel's filter, which no test drives.
Alternatives
so a second format would be code with no reader.
maps DMA-BUFs back to the CPU anyway, and modifier negotiation is a lot of
surface for one copy at 30 fps.
names virtual cameras as studio surface, and the macOS and Windows backends
are app-bundle and session-lifetime objects, so the backends live here.
Review
Codex (
quest review), gpt-6.1-sol, medium effort, a new session after thereadback design change. Round 2 on head
63720dcc0f6c3270367d83a4b12929be3d78c740: no findings.(
vcam/pipewire.rs:139): refuted. The maintainer's 2026-11-02 decisionmoving the studio out of moq names virtual cameras as studio surface
(
quest/m0/README.md:56-58), the vcam epic's Plan places the backends here,and the macOS and Windows backends are app-bundle and session-lifetime
objects. The maintainer's decision on this PR moved only the readback
upstream, which this head uses. Round 2 accepted it.
Earlier session (head
fc0d1c1, before the merge): round 1's "Wait for noderegistration before reporting startup success" was fixed in
fc0d1c1;"Reuse the upstream rendering readback" is now fixed by the switch to
moq_video::render::readback.🤖 Generated with Claude Code
(Written by Claude Opus 5.5)