rpicam-vid: add Unix Domain Socket runtime control and companion tools#917
Open
Kletternaut wants to merge 2 commits into
Open
rpicam-vid: add Unix Domain Socket runtime control and companion tools#917Kletternaut wants to merge 2 commits into
Kletternaut wants to merge 2 commits into
Conversation
Enables real-time adjustment of camera parameters while rpicam-vid
is running — without interrupting the video stream or restarting the
process. Parameters such as brightness, EV, shutter, framerate,
contrast, saturation, gain, sharpness, AWB gains, zoom, AWB mode,
metering, exposure mode, denoise and HDR can be changed via a Unix
Domain Socket.
Changes to rpicam-apps core:
- apps/control_socket.hpp (new): self-contained Unix Domain Socket
server; one socket per camera index (/tmp/rpicam-vid{N}.sock);
HasNewClient/SendToClient for pushing caps to new clients; deletes
the companion .state file on init/destroy for a clean session start.
- apps/rpicam_vid.cpp: ControlSocket integrated into the capture
loop; socket path derived from the --camera index; after
ConfigureVideo() the active sensor mode's caps are pushed to new
clients: caps:maxfps=N,hasaf=0|1
- core/rpicam_app.hpp/.cpp: SupportsScalerCrops() added to detect
whether the active camera supports autofocus.
Two companion tools connect to that socket:
rpicam-ctrl (utils/rpicam_ctrl/): Qt6/Qt5 graphical control panel
with sliders for brightness, EV, shutter (log-scale), framerate,
contrast, saturation, gain, sharpness, AWB gains and zoom, plus
dropdowns for AWB, metering, exposure, denoise and HDR modes.
Includes a .desktop launcher and XDG icon.
rpicam-ctrl-cli (utils/rpicam-ctrl-cli): equivalent terminal ASCII
slider interface in pure Python 3 (stdlib curses, no dependencies).
Both tools:
- connect automatically and reconnect on rpicam-vid restart
- parse caps: to clamp the framerate slider maximum
- maintain per-camera state in /tmp/rpicam-vid{N}.state
- auto-select camera 1 if camera 0 socket is absent on startup
- re-send recalculated shutter us when framerate is changed
Meson: new enable_rpicam_ctrl feature option (default: disabled)
builds rpicam-ctrl; requires Qt6 or Qt5 Widgets + Network.
Signed-off-by: Kletternaut <tomge68@gmail.com>
The option previously defaulted to 'auto', which caused rpicam-ctrl to be built automatically whenever Qt was present on the build system. This broke the Debian package build: rpicam-ctrl.desktop and rpicam-ctrl.svg were installed to debian/tmp but are not listed in any debian/*.install file, causing dh_missing to abort. Change the default to 'disabled' so rpicam-ctrl is never built unless explicitly requested: meson setup build -Denable_rpicam_ctrl=enabled Also guard the entire Qt dependency search inside a 'not enable_rpicam_ctrl.disabled()' block so that Qt is not even probed when the feature is disabled, preventing any inadvertent side-effects in the build summary. Signed-off-by: Kletternaut <tomge68@gmail.com>
Contributor
Author
|
@naushir Hi Naushir, since it’s been a while, I just wanted to check if there is any technical criticism or architectural feedback regarding this implementation. I took great care to keep changes to the existing codebase to a minimum in order to avoid side effects. However, if there is anything I should change, improve, or adjust to meet your standards, please let me know. I would really appreciate your feedback! I understand, of course, if a lack of time is the reason for the delay. Also, just a quick heads-up: it looks like there is currently 1 workflow awaiting approval from a maintainer to let the final checks finish. Thanks, Tom |
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.
This patch adds a Unix Domain Socket interface to
rpicam-vidthat allows camera parameters to be adjusted at runtime — without stopping and restarting the process. Two companion tools are included:rpicam-ctrl(Qt6/Qt5 GUI) andrpicam-ctrl-cli(Python terminal UI).Demo Video
A demo video showing
rpicam-ctrl-cli(terminal UI) andrpicam-ctrl(Qt GUI) controlling liverpicam-vidstreams in real time.rpicam-ctrl.mp4
Motivation
Several camera controls — brightness, contrast, saturation, exposure compensation, AWB mode, AWB gains, ROI/digital zoom and HDR mode — are currently only applied at startup. Changing any of these values requires stopping and restarting
rpicam-vid, interrupting the stream. This patch eliminates that requirement by allowing parameters to be adjusted at runtime without restarting the process.For parameters like shutter speed, AWB gains, and ROI/zoom, finding the optimal value is only practical when the result is visible in the live stream — a stop/restart cycle makes iterative tuning tedious or impossible.
This is especially true for manual focus: in microscopy, macroscopy, or astrophotography, focus can only be set correctly while observing the live image. The autofocus controls (
lens_position) are already implemented in this patch but currently hidden in the UI and untested — AF-capable Raspberry Pi camera modules are required to validate and complete that feature, which is planned for a follow-up contribution.Implementation
A new
ControlSocketclass (apps/control_socket.hpp) listens on a UNIX stream socket. The socket path is derived automatically from the--cameraindex: camera 0 uses/tmp/rpicam-vid0.sock, camera 1 uses/tmp/rpicam-vid1.sock. This allows tworpicam-vidinstances to run simultaneously with independent control sockets.Commands are plain text, newline-terminated, in the form
key:value:Multiple commands can be sent in a single write. The socket is
SOCK_NONBLOCKthroughout — it never blocks the capture loop. Insideevent_loop()inrpicam_vid.cpp,AcceptConnections()andReadControls()are called once per frame and the resultingControlListis passed to the existingSetControls()API.A
SupportsScalerCrops()helper is added toRPiCamAppto detect at runtime whether the attached camera supports the PISP multi-channel crop control (rpi::ScalerCrops). This is more reliable than platform detection — sensors like imx477 on Pi 5 use the legacyScalerCroppath even though the platform is PISP. ROI commands use whichever control the camera actually supports.Supported commands
brightnessevcontrastsaturationsharpnessgainAnalogueGainModeManualautomatically)awbauto|incandescent|tungsten|fluorescent|indoor|daylight|cloudyawbgainsroimeteringcentre|spot|average|customexposurenormal|sportdenoiseauto|off|cdn_off|cdn_fast|cdn_hqhdroff|auto|sensor|single-expshutter0= autoframerate0= auto (sensor maximum)Caps protocol (server → client)
When a client connects,
rpicam-vidimmediately sends a single line describing the active sensor mode:maxfpshasaf1if the camera supports autofocus hardware,0otherwiseBoth reference tools parse this line on connect and clamp the framerate slider maximum accordingly. When the framerate is changed and the shutter is not on auto, the new shutter µs (recalculated from the updated one-frame period) is also sent automatically — so the camera always reflects the slider display.
Extensibility
ControlSocketis implemented as a self-contained header (apps/control_socket.hpp) with no dependency onrpicam-vid-specific code. Integrating it into any other rpicam-app (rpicam-still,rpicam-jpeg,rpicam-raw, …) requires only three lines in that app's event loop: construct the socket with the desired path, callAcceptConnections()andReadControls()once per frame, and pass the returnedControlListtoSetControls(). The command vocabulary and wire format are identical regardless of which app hosts the socket.Per-camera state persistence
Both reference implementations (
rpicam-ctrlandrpicam-ctrl-cli) maintain independent state per camera index and persist it to/tmp/rpicam-vid{N}.state(JSON). This ensures:rpicam-ctrl-cliandrpicam-ctrlpreserves the last values.rpicam-vidstarted a new session), defaults are shown and sent.Session isolation
ControlSocketdeletes the companion.statefile both on construction (beforebind()) and in its destructor (alongside the.sockfile). This guarantees that every newrpicam-vidsession starts from hardware defaults, independent of what a previous session had set.rpicam_vid.cppnow catchesSIGTERM(in addition to the existingSIGINT) and routes it through the normal event-loop exit path. This ensures theControlSocketdestructor runs — and both.sockand.statefiles are cleaned up — whenrpicam-vidis stopped viasystemctl stop,pkill, orkill. (SIGKILLcannot be caught; the constructor-time delete of any stale files serves as a fallback.)State file format
{ "brightness": 0, "ev": 0, "contrast": 100, "saturation": 100, "zoom": 10, "gain": 10, "sharpness": 10, "awbGainR": 150, "awbGainB": 120, "shutter": 0, "framerateIdx": 0, "awbIdx": 0, "meteringIdx": 0, "exposureIdx": 0, "denoiseIdx": 0, "hdrIdx": 0 }shutteris the log-scale slider position (0 = auto, 1–200 maps to 100 µs … one-frame period). The upper bound of the shutter range (one-frame period) is derived fromframerateIdx: when the framerate slider is non-zero, the maximum shutter duration is1 000 000 / framerateIdxµs; when framerate is 0 (auto), the sensor mode'smaxfpsfrom thecaps:line is used instead.framerateIdxis the target fps directly (0 = auto, 1–N fps).Compatibility
SetControls()API.bind()fails, a warning is logged andrpicam-vidcontinues normally without socket support.std::locale::classic()explicitly.SOCK_NONBLOCKandaccept4()throughout — the capture loop is never stalled.AWB correctness
awb:<mode>explicitly sendsAwbEnable=truealongside the mode, andawbgains:<r>,<b>sendsAwbEnable=false. This prevents libcamera from retaining staleColourGainsstate when switching between manual and auto AWB.ev:<value>explicitly sendsAeEnable=trueto ensure the exposure compensation is applied even when the auto-exposure pipeline is in a particular state.HDR modes
Only
HdrModeOffandHdrModeSingleExposureare functional on imx477 at runtime. Thehdr:command acceptsoff|auto|sensor|single-exp, matching the rpicam-apps CLI convention (bothautoandsensormap toHdrModeSingleExposure).Multi-camera support
Two
rpicam-vidinstances can run simultaneously on separate cameras. Each gets its own socket derived from the--cameraindex:Both
rpicam-ctrl-cliandrpicam-ctrlsupport switching between cameras at runtime without restarting the tool.Files changed
apps/control_socket.hppControlSocketclass;HasNewClient/SendToClientfor caps push; deletes companion.statefile on init/destroyapps/rpicam_vid.cpp--cameraindex; per-frame poll;SIGTERMhandler; sendscaps:maxfps=N,hasaf=0|1to new clients afterConfigureVideo()core/rpicam_app.hppSupportsScalerCrops()declarationcore/rpicam_app.cppSupportsScalerCrops()implementationmeson.buildutils/subdir addedmeson_options.txtenable_rpicam_ctrloptionutils/meson.buildrpicam_ctrlsubdirutils/rpicam_ctrl/meson.buildrpicam-ctrlutils/rpicam_ctrl/main.cpputils/rpicam-ctrl-cliutils/README.mdrpicam-ctrlandrpicam-ctrl-cliREADME.mdUsage
Reference implementations
Qt control panel —
utils/rpicam_ctrl/(rpicam-ctrl)A Qt6/Qt5 GUI with sliders for all continuous parameters, dropdowns for AWB / metering / exposure / denoise / HDR, and manual AWB R/B gain sliders.
Slider details:
caps:message.maxfpsreceived on connect.Camera selection: a camera selector switches between camera 0 and camera 1 at runtime. If camera 0's socket is absent at startup but camera 1's is present, camera 1 is selected automatically — useful when camera 0 is unavailable.
Autofocus (AF) controls are implemented in code but hidden in the UI — untested due to missing hardware (no AF-capable lens available).
ROI: the Zoom slider applies a symmetric centre crop (
roi:x,y,w,hwith equal margins on all sides), effectively implementing digital zoom. Interactive ROI selection — allowing the user to drag an arbitrary region on the preview — is planned as a follow-up PR.Connects automatically and reconnects if
rpicam-vidis restarted. Keyboard shortcuts:Rreset,Qquit,Cswitch camera.Build via Meson (requires Qt6 or Qt5 Widgets + Network):
Terminal UI (CLI) —
utils/rpicam-ctrl-cliASCII slider interface in pure Python 3 (no dependencies beyond stdlib
curses). Includes all the same controls as the GUI (brightness, EV, shutter, framerate, contrast, saturation, gain, sharpness, AWB gains, zoom, and all mode combos) in the same order as the Qt layout.caps:lines from the socket in the main loop; adjusts framerate slider maximum and shows[max N fps]in the status bar.↑/↓←/→CR