Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

apps: add Unix Domain Socket for runtime camera control in rpicam-vid#914

Closed
Kletternaut wants to merge 2 commits into
raspberrypi:mainraspberrypi/rpicam-apps:mainfrom
Kletternaut:feature/runtime-control-socketKletternaut/rpicam-apps:feature/runtime-control-socketCopy head branch name to clipboard
Closed

apps: add Unix Domain Socket for runtime camera control in rpicam-vid#914
Kletternaut wants to merge 2 commits into
raspberrypi:mainraspberrypi/rpicam-apps:mainfrom
Kletternaut:feature/runtime-control-socketKletternaut/rpicam-apps:feature/runtime-control-socketCopy head branch name to clipboard

Conversation

@Kletternaut

@Kletternaut Kletternaut commented May 9, 2026

Copy link
Copy Markdown
Contributor

Demo Video

A demo video showing the Qt5 GUI and TUI controlling a live rpicam-vid stream in real time is attached.

rpicam-vid-gui.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. Applications that embed rpicam-vid (e.g. GUI front-ends) must restart the process to change these values, causing stream interruption.
This patch eliminates that requirement.

Implementation

A new ControlSocket class (apps/control_socket.hpp) listens on a UNIX stream socket. The socket path is derived automatically from the --camera index: camera 0 uses /tmp/rpicam-vid0.sock, camera 1 uses /tmp/rpicam-vid1.sock. This allows two rpicam-vid instances to run simultaneously with independent control sockets.

Commands are plain text, newline-terminated, in the form key:value:

brightness:0.3
contrast:1.5
roi:0.25,0.25,0.5,0.5
awb:daylight
...

Multiple commands can be sent in a single write. The socket is SOCK_NONBLOCK throughout — it never blocks the capture loop. Inside event_loop() in rpicam_vid.cpp, AcceptConnections() and ReadControls() are called once per frame and the resulting ControlList is passed to the existing SetControls() API.

A SupportsScalerCrops() helper is added to RPiCamApp to 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 legacy ScalerCrop path even though the platform is PISP. ROI commands use whichever control the camera actually supports.

Supported commands

Command Value Range / options
brightness float -1.0 … 1.0
ev float -3.0 … 3.0
contrast float 0.0 … 3.0
saturation float 0.0 … 3.0
sharpness float 0.0 … 16.0
gain float 1.0 … 16.0 (sets AnalogueGainModeManual automatically)
awb string auto | incandescent | tungsten | fluorescent | indoor | daylight | cloudy
awbgains float,float manual colour gains r,b — disables auto AWB
roi float,float,float,float x,y,w,h relative 0.0–1.0
metering string centre | spot | average | custom
exposure string normal | sport
denoise string auto | off | cdn_off | cdn_fast | cdn_hq
hdr string off | auto | sensor | single-exp

Extensibility

ControlSocket is implemented as a self-contained header (apps/control_socket.hpp) with no dependency on rpicam-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, call AcceptConnections() and ReadControls() once per frame, and pass the returned ControlList to SetControls(). The command vocabulary and wire format are identical regardless of which app hosts the socket.

Per-camera state persistence

Both reference implementations (GUI and TUI) maintain independent state per camera index and persist it to /tmp/rpicam-vid{N}.state (JSON). This ensures:

  • No state bleeding when switching cameras at runtime — each camera remembers its own last settings.
  • GUI/TUI interoperability — both tools share the same state file format, so switching from TUI to GUI (or vice versa) preserves the last values.
  • Correct display after reconnect — on reconnect the GUI re-reads the state file; if the file is absent (because rpicam-vid started a new session), defaults are shown and sent.

Session isolation

ControlSocket deletes the companion .state file both on construction (before bind()) and in its destructor (alongside the .sock file). This guarantees that every new rpicam-vid session starts from hardware defaults, independent of what a previous session had set.

rpicam_vid.cpp now catches SIGTERM (in addition to the existing SIGINT) and routes it through the normal event-loop exit path. This ensures the ControlSocket destructor runs — and both .sock and .state files are cleaned up — when rpicam-vid is stopped via systemctl stop, pkill, or kill. (SIGKILL cannot 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,
  "awbIdx": 0, "meteringIdx": 0, "exposureIdx": 0,
  "denoiseIdx": 0, "hdrIdx": 0
}

Values are stored as integers scaled to the slider resolution. The format is identical between GUI and TUI.

Compatibility

  • No breaking changes. All existing CLI options continue to work identically.
  • Fully additive. No existing code path is modified; the socket is purely additional logic layered on top of the existing SetControls() API.
  • Graceful degradation. If bind() fails, a warning is logged and rpicam-vid continues normally without socket support.
  • Locale-safe. Float parsing uses std::locale::classic() explicitly.
  • Non-blocking. SOCK_NONBLOCK and accept4() throughout — the capture loop is never stalled.

AWB correctness

awb:<mode> explicitly sends AwbEnable=true alongside the mode, and awbgains:<r>,<b> sends AwbEnable=false. This prevents libcamera from retaining stale ColourGains state when switching between manual and auto AWB.

ev:<value> explicitly sends AeEnable=true to ensure the exposure compensation is applied even when the auto-exposure pipeline is in a particular state.

HDR modes

Only HdrModeOff and HdrModeSingleExposure are functional on imx477 at runtime. The hdr: command accepts off|auto|sensor|single-exp, matching the rpicam-apps CLI convention (both auto and sensor map to HdrModeSingleExposure).

Multi-camera support

Two rpicam-vid instances can run simultaneously on separate cameras. Each gets its own socket derived from the --camera index:

rpicam-vid --camera 0 -o /dev/null &   # listens on /tmp/rpicam-vid0.sock
rpicam-vid --camera 1 -o /dev/null &   # listens on /tmp/rpicam-vid1.sock

Both rpicam-vid-tui and rpicam-vid-gui support switching between cameras at runtime without restarting the tool.

Files changed

File Change
apps/control_socket.hpp New — self-contained ControlSocket class; deletes companion .state file on init/destroy
apps/rpicam_vid.cpp +19 lines — socket path from --camera index; per-frame poll; SIGTERM handler
core/rpicam_app.hpp +2 lines — SupportsScalerCrops() declaration
core/rpicam_app.cpp +21 lines — SupportsScalerCrops() implementation
meson.build +1 line — utils/ subdir added
meson_options.txt +5 lines — enable_control_gui option
utils/meson.build +3 lines — rpicam_vid_gui subdir
utils/rpicam_vid_gui/meson.build New — Qt6/Qt5 build rules for rpicam-vid-gui
utils/rpicam_vid_gui/main.cpp New — Qt6/Qt5 control panel with per-camera state
utils/rpicam-vid-tui New — terminal ASCII slider interface (Python 3) with per-camera state
utils/README.md New — documentation for GUI and TUI tools
README.md +62 lines — per-camera state and runtime control documentation

Usage

# Camera 0 (default)
rpicam-vid --camera 0 -o /dev/null &

echo "brightness:0.5"            | nc -U /tmp/rpicam-vid0.sock
echo "awb:daylight"              | nc -U /tmp/rpicam-vid0.sock
echo "roi:0.25,0.25,0.5,0.5"    | nc -U /tmp/rpicam-vid0.sock
echo "awbgains:1.6,1.2"          | nc -U /tmp/rpicam-vid0.sock
printf "contrast:1.4\nev:-1.0\n" | nc -U /tmp/rpicam-vid0.sock

Reference implementations

Qt control panel — utils/rpicam_vid_gui/ (rpicam-vid-gui)

A Qt6/Qt5 GUI with sliders for all continuous parameters, dropdowns for AWB / metering / exposure / denoise / HDR, and manual AWB R/B gain sliders. A camera selector switches between camera 0 and camera 1 at runtime. Connects automatically and reconnects if rpicam-vid is restarted.

Keyboard shortcuts: R reset, Q quit, C switch camera.

Build via Meson (requires Qt6 or Qt5 Widgets + Network):

meson configure build -Denable_control_gui=enabled
ninja -C build

Terminal UI — utils/rpicam-vid-tui

ASCII slider interface in pure Python 3 (no dependencies beyond stdlib curses).

./utils/rpicam-vid-tui        # camera 0
./utils/rpicam-vid-tui 1      # camera 1
Key Action
/ Move between parameters
/ Decrease / increase value (or switch camera when Camera row is selected)
C Cycle to next camera
R Reset all parameters to defaults
Q Quit

@Kletternaut
Kletternaut force-pushed the feature/runtime-control-socket branch 13 times, most recently from df9237a to f1d4c95 Compare May 12, 2026 09:04
@Kletternaut
Kletternaut force-pushed the feature/runtime-control-socket branch from f1d4c95 to 071511e Compare May 12, 2026 13:55
@Kletternaut
Kletternaut force-pushed the feature/runtime-control-socket branch from 9a88020 to aa3673c Compare May 14, 2026 05:58
Adds a Unix Domain Socket server to rpicam-vid that accepts text
commands at runtime, allowing camera parameters to be changed without
restarting the process.

The socket path is derived from the camera index, e.g. --camera 1
uses /tmp/rpicam-vid1.sock (default: /tmp/rpicam-vid0.sock).

Supported commands include: brightness, ev, contrast, saturation,
gain (0 = AE auto, >0 = manual), sharpness, roi, awb, awbgains,
metering, exposure, denoise, hdr, shutter, camera.

Two client tools are provided:
- utils/rpicam-vid-tui: curses-based TUI with sliders and combos
- utils/rpicam_vid_gui/: Qt GUI with sliders, combos and reset button

gain:0 restores AnalogueGainModeAuto so connecting the GUI at default
settings does not override AE-controlled gain or an active --roi.

Signed-off-by: Kletternaut <tomge68@gmail.com>
@Kletternaut
Kletternaut force-pushed the feature/runtime-control-socket branch from aa3673c to 2bedc97 Compare May 15, 2026 09:31
- control_socket.hpp: HasNewClient/ClearNewClient/SendToClient
- rpicam_vid.cpp: send caps:maxfps=N,hasaf=0|1 after ConfigureVideo
- rpicam_vid_gui: integer fps (1 fps steps), log-scale shutter,
  auto-select cam1 via socket check, clamp slider from caps
- rpicam-vid-tui: rewrite matching GUI; shutter/framerate sliders,
  caps polling, auto-select cam1, GUI-compatible state format
- utils/README: caps protocol, shutter/framerate cmds, AF note
- README: shutter/framerate cmds, GUI/TUI features, AF note

Signed-off-by: Kletternaut <tomge68@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Morty Proxy This is a proxified and sanitized view of the page, visit original site.