camera-stream
Low-latency, multi-camera image broadcast for trusted Linux networks.
[!TIP]
📹 camera-stream | Project Card
camera-stream is a lightweight Linux multi-camera streaming service. It broadcasts local camera images over ZeroMQ for trusted internal networks, designed for real-time-first machine-vision and robotics workloads where the newest frame is more valuable than retaining every frame.
Core capability Design 📷 Device support V4L2/OpenCV cameras, Intel RealSense, and Orbbec cameras 📡 Low-latency broadcast One-to-many ZeroMQ PUB/SUB with independently subscribable camera topics ⚡ Real-time policy Capacity-one, latest-frame-wins stages discard stale frames instead of accumulating latency 🖼️ Image format JPEG or lossless raw_bgr8color, plus RealSenseraw_z16metric depth💤 On-demand operation Topic-demand idle sleep/wake stops unused camera capture and encoding 📊 Operations Status events and periodic snapshots on the stream endpoint, plus an optional Rich monitoring dashboard 🎯 Best suited to: real-time robotic perception, multi-camera intranet distribution, and shared image sources for multiple algorithm nodes. It is a live-streaming service, not a recording or replay system.
flowchart LR
A[📷 Local cameras] --> B[⚙️ camera-stream]
B --> C[📡 ZeroMQ PUB/SUB]
C --> D[🖥️ Visual client]
C --> E[🧠 Vision applications]
C --> G[🌐 Optional Web Gateway]
G --> H[Browser]
B -. "status/" .-> F[🔎 Topic diagnostics]
classDef source fill:#e8f4ea,stroke:#2f7d45,color:#173b21
classDef server fill:#e8f0fb,stroke:#3d6ea8,color:#1c3554
classDef consumer fill:#fff4df,stroke:#b47720,color:#4c3210
class A source
class B,C server
class D,E,F,G,H consumer
| Start here | Command | What it gives you |
|---|---|---|
| 🖥️ Publish cameras | uvx camera-stream server --config ./config.yaml |
Server and optional Rich TUI |
| 🌐 Preview in a browser | Enable web.enabled, then open http://HOST:8080 |
No client-side install |
| 👀 Inspect live video | uvx camera-stream client --endpoint tcp://HOST:5555 |
Graphical multi-camera monitor |
| 🔎 Diagnose streams | uvx camera-stream topic list --endpoint tcp://HOST:5555 |
Topics, status, FPS, and bandwidth |
| 🧩 Embed in Python | from camera_stream import StreamClient |
Decoded latest-frame client API |
🚀 Quick Start
uvx is Python/uv's equivalent of npx: it downloads a PyPI package into an
isolated cached environment and runs its command without a manual install.
1. 📡 Run a server with uvx
Start an OpenCV/V4L2 deployment without cloning this repository:
uvx camera-stream server --download-template
# Edit ./config.yaml for local devices and endpoints.
uvx camera-stream server --config ./config.yaml
RealSense and Orbbec drivers are package extras. Select those required by the configuration:
uvx --from 'camera-stream[realsense,orbbec]' \
camera-stream server --config /absolute/path/to/config.yaml
--download-template writes a starter OpenCV/V4L2 config.yaml into the
current directory and refuses to overwrite an existing file. Adapt device
paths, serial numbers, encoding, endpoints, and idle policy before starting.
🌈 RealSense color and depth
Use streams when one physical RealSense should publish synchronized color
and depth topics. alignment: color explicitly runs the RealSense SDK aligner,
so base_camera/depth[y, x] and base_camera/color[y, x] refer to the same
image ray:
- name: base_camera
driver: realsense
device: {serial: "347522072196"}
streams:
color:
profile: {width: 640, height: 480, fps: 30}
encoding: {codec: jpeg, jpeg_quality: 85}
depth:
profile: {width: 640, height: 480, fps: 30}
encoding: {codec: raw_z16}
alignment: color # native | color
The server publishes base_camera/color and base_camera/depth from the same
SDK frameset with a shared frameset_sequence. Depth remains little-endian
uint16; multiply a sample by header field depth_scale_m to obtain metres.
native avoids resampling and its CPU cost, but depth pixels then do not map
directly to color pixels. Raw 640x480x16-bit depth at 30 FPS is about 147 Mbps
before transport overhead, independent of the JPEG color bitrate.
Subscribe to both topics and pair frames by frameset_sequence. The two
read() calls are independent latest-frame reads, so adjacent returns are not
automatically a synchronized pair:
from camera_stream import StreamClient
def read_pair(color, depth, timeout=1):
color_frame = color.read(timeout=timeout)
depth_frame = depth.read(timeout=timeout)
while True:
color_set = color_frame.header["frameset_sequence"]
depth_set = depth_frame.header["frameset_sequence"]
if color_set == depth_set:
return color_frame, depth_frame
if color_set < depth_set:
color_frame = color.read(timeout=timeout)
else:
depth_frame = depth.read(timeout=timeout)
with StreamClient("tcp://192.168.5.24:5555") as client:
color = client.subscribe("base_camera/color")
depth = client.subscribe("base_camera/depth")
while True:
color_frame, depth_frame = read_pair(color, depth)
x, y = 320, 240
bgr = color_frame.image[y, x]
distance_m = depth_frame.distance_at(x, y) # None means invalid depth
With alignment: color, the same (x, y) addresses corresponding color and
depth rays after pairing. color_frame.image is uint8 HxWx3; the client API
keeps depth_frame.image as the original uint16 HxW data.
1b. 📤 Push cameras from another LAN host
Enable the server ingest endpoint and optionally set a shared token:
endpoints:
stream_pub: tcp://0.0.0.0:5555
ingest_api: tcp://0.0.0.0:5557
ingest_policy:
token: replace-with-a-long-random-secret # optional on a trusted LAN
topic_lease_s: 60
On a camera host, use the same camera block format but only ingest_api is
required. The first valid frame atomically claims <camera>/color; a local
server camera always wins a name collision. Remote push currently accepts
color-only camera blocks; RealSense depth is available on server-local cameras.
uvx camera-stream push --download-template
# Edit ./config.yaml: set the reachable server ingest_api and local camera.
CAMERA_STREAM_INGEST_TOKEN='replace-with-a-long-random-secret' \
uvx camera-stream push --config ./config.yaml
uvx camera-stream push --config ./config.yaml --camera front --token "$TOKEN"
The push command isolates cameras, keeps only one pending encoded frame per
topic, and reconnects automatically. A remote topic disappears after
ingest_policy.topic_lease_s without a valid frame. The server never decodes,
transforms, or re-encodes the pushed image payload.
For an application-owned camera loop, use the same publisher module. It hides the private ingest protocol, lease, and reconnect lifecycle:
from camera_stream import StreamPublisher
with StreamPublisher("tcp://192.168.5.24:5557", token=None) as publisher:
stream = publisher.open_stream(camera="front", codec="jpeg", jpeg_quality=85)
while True:
stream.publish(image) # NumPy BGR image; synchronous encode, nonblocking send
Read stream.state, stream.error, and stream.metrics for asynchronous
server feedback and local drop measurements.
1c. 🌐 Enable the browser dashboard
The Web dashboard is optional. Install its dependency extra and enable it in the server configuration:
web:
enabled: true
host: 0.0.0.0
port: 8080
max_clients: 16
# token: replace-with-a-long-random-secret
uvx --from "camera-stream[web]" \
camera-stream server --config ./config.yaml
Open http://SERVER_IP:8080 from another machine. camera-stream serves the
page and its assets, so viewers need no Python, Node.js, extension, CDN, or
local installation. When token is configured, the page prompts for it. The
token and video still travel over plain HTTP on the trusted LAN; add a TLS
reverse proxy before exposing the endpoint beyond that network.
web.enabled is the only runtime switch. The [web] extra only installs the
optional dependency and does not enable Web by itself. Enabling Web without
the extra exits before camera workers start and prints the correct uvx
command.
The gateway runs in a supervised spawn process and subscribes to the existing
XPUB endpoint. All browser viewers share that one ZeroMQ connection. It always
subscribes to status/, but subscribes to an image topic only while a browser
is viewing it, preserving camera idle sleep. Each browser/topic has a
capacity-one latest-frame slot, so a slow tab drops stale images instead of
accumulating latency.
| Browser diagnostic | Meaning |
|---|---|
FPS / AVG / 1% LOW |
Instantaneous, 100-frame average, and slowest-percent receive rate |
INTERVAL, p95, jitter |
Local frame spacing, tail spacing, and spacing variation |
BITRATE |
Image payload received during the latest one-second window, in Mbps |
FRAME AGE* |
now - captured_utc_ns; valid only with NTP/PTP clock synchronization |
decode, draw, rx→draw |
Browser decode, canvas drawing, and receive-to-display cost |
gap, gateway drop, local drop |
Sequence loss, gateway overwrites, and browser slot overwrites as rates |
The overlay includes a 100-frame FPS chart. Double-click a camera for aspect-ratio-preserving fullscreen, use its download button for a PNG snapshot, or export the current status and metrics as JSON.
🪵 Headless logs and TUI
Without --tui, the server writes concise lifecycle logs to stderr: service
startup, worker spawn/recovery, camera state transitions, subscriber connects,
idle sleep/wake, and a 30-second health line. Frame-by-frame logging is
deliberately omitted to preserve real-time performance.
uvx camera-stream server --config ./config.yaml
uvx camera-stream server --config ./config.yaml --tui # Rich dashboard
For a systemd service, follow the same output with:
journalctl -fu camera-stream-server
2. 👀 View every camera with uvx
The graphical client discovers configured cameras and displays all color and depth streams with live diagnostics; depth is locally rendered as false color:
uvx camera-stream client --endpoint tcp://192.168.5.24:5555
Use the server's reachable IP address, not its bind address 0.0.0.0.
3. 🔎 Inspect topics with uvx
The package also provides ROS-like read-only diagnostics. These commands need no repository checkout and connect only to the public stream endpoint:
uvx camera-stream topic list --endpoint tcp://192.168.5.24:5555
uvx camera-stream topic list --endpoint tcp://192.168.5.24:5555 --verbose
uvx camera-stream topic info base_camera/color --endpoint tcp://192.168.5.24:5555
uvx camera-stream topic echo base_camera/color --endpoint tcp://192.168.5.24:5555 --count 1
uvx camera-stream topic hz base_camera/color --endpoint tcp://192.168.5.24:5555
uvx camera-stream topic bw base_camera/color --endpoint tcp://192.168.5.24:5555
uvx camera-stream topic info base_camera/depth --endpoint tcp://192.168.5.24:5555
list reads the status directory and lists every configured color/depth topic
without waking cameras. info prints the latest status and a real frame
header. echo, hz, and bw subscribe to the selected image topic and wake
that camera under idle policy. hz reports received-frame rate and bw
reports encoded image payload Mbps. Pass --count N for a bounded run;
hz and bw also accept --window SECONDS.
🧩 Integrate a Client
The endpoints in config.yaml are server bind addresses. A remote client must
replace 0.0.0.0 with the server's reachable IP address. With the bundled
configuration, use tcp://192.168.5.24:5555 for frames and status.
🐍 Use the client package
For applications that need decoded frames without managing ZeroMQ sockets,
use the camera-stream package's latest-frame-wins interface:
from camera_stream import StreamClient
with StreamClient("tcp://192.168.5.24:5555") as client:
# subscribe() waits for the first decoded frame by default.
camera = client.subscribe("base_camera/color")
camera.wait_for_state("ONLINE", timeout=5)
while True:
frame = camera.read(timeout=1)
image = frame.image # NumPy BGR image
print(frame.sequence, frame.age_ms, camera.metrics["average_fps"])
Depth uses the same API and remains an unmodified np.uint16 image:
with StreamClient("tcp://192.168.5.24:5555") as client:
depth = client.subscribe("base_camera/depth")
frame = depth.read(timeout=1)
distance_m = frame.distance_at(x=320, y=240) # None for an invalid zero sample
read() returns the newest unread frame and discards older unread frames.
Use read(block=False) for a non-blocking snapshot of the most recently
received frame; it is equivalent to latest() and returns None only before
the first frame arrives. read(timeout=N) waits up to N seconds and raises
TimeoutError on expiry. latest() and last_frame do not consume the frame,
so they continue to return it until a newer one arrives. state, error, status, metrics, and
wait_for_state() expose server and local receive diagnostics.
subscribe() warms up a new stream by default: it returns only after a valid
first frame arrives, so read(block=False) is immediately usable. Pass
warm_up_timeout=N to bound that wait, or warm_up=False to return before a
frame is available. camera.warm_up(timeout=N) provides the same wait for an
existing stream.
📬 Discover camera topics and status
Use the bundled CLI for topic discovery and diagnostics. It owns the wire protocol and latest-frame settings, so application code does not need to manage ZeroMQ sockets or parse status messages.
| Need | Recommended command | Camera wake-up |
|---|---|---|
| List available camera topics | camera-stream topic list --endpoint tcp://HOST:5555 |
No |
| List topics with lifecycle state | camera-stream topic list --verbose --endpoint tcp://HOST:5555 |
No |
| Inspect one stream's status and frame header | camera-stream topic info base_camera/color --endpoint tcp://HOST:5555 |
Yes, temporarily |
| Watch headers or measure FPS / Mbps | topic echo, topic hz, topic bw |
Yes, while running |
list and list --verbose read the periodic status snapshot and do not create
camera demand. info, echo, hz, and bw subscribe to an image topic, so
they wake that camera when the idle policy is enabled.
🖼️ Subscribe to a camera stream
For applications, use StreamClient; it decodes JPEG, raw_bgr8, or
raw_z16, keeps only the newest frame, and updates status in the background. The full usage example
above is the recommended integration path.
| Need | CameraStream API |
|---|---|
| Wait for a new frame | camera.read(timeout=1) |
| Inspect the newest retained frame | camera.read(block=False) |
| Observe lifecycle / error | camera.state, camera.error, camera.status |
| Inspect local receive and drop metrics | camera.metrics |
| Stop one image topic | camera.unsubscribe() |
Use camera-stream client to view all configured cameras interactively. The raw
ZeroMQ multipart layout is documented below only as a protocol reference for
advanced interoperable implementations.
💤 Idle camera policy
config.yaml enables the following policy by default:
idle_policy:
enabled: true
sleep_after_s: 60
The server uses XPUB internally to observe topic demand, not TCP connection
demand: a client that subscribes only to status/ does not wake a camera.
After the last matching <camera>/color or <camera>/depth subscription disappears, the camera
remains active for sleep_after_s, then stops its worker, closes the SDK, and
stops capture and encoding. A matching image subscription wakes only that
camera. A subscription to either <camera>/color or <camera>/depth keeps the
same physical camera awake. A b"" subscription is a prefix match for every topic and wakes all
cameras.
IDLE_PENDING -> SLEEPING -> WAKING -> ONLINE occurs only if demand remains
absent until the worker stops. If demand returns during IDLE_PENDING, the
still-running worker resumes its previous state, usually ONLINE, without
reopening the camera. Set enabled: false for continuous capture and the
lowest first-frame latency.
🛠️ Run from a Checkout
Install the drivers used by config.yaml, then run the service:
uv sync --extra realsense --extra orbbec
uv run camera-stream server --config config.yaml
Run the local client source with the workspace command:
uv run camera-stream client \
--endpoint=tcp://127.0.0.1:5555
uv run camera-stream client uses the current checkout source.
Use the bundled V4L2 demo and the in-process server TUI when developing:
uv run camera-stream server --config config.demo.yaml --tui
--tui renders the Rich server dashboard in the same process. Without it, the
service remains headless and suitable for systemd.
⚙️ systemd Deployment
Synchronize the environment with required camera drivers, then install and start the service:
uv sync --extra realsense --extra orbbec
sudo scripts/install_camera_stream_service.sh --config "$PWD/config.yaml"
The installer resolves absolute paths for uv, the project, and YAML
configuration; installs camera-stream.service; and starts it without the
TUI. By default it runs as the user who invoked sudo, which needs camera
permissions.
systemctl status camera-stream.service
journalctl -u camera-stream.service -f
Use --user robot, --unit-name NAME, or --no-start as needed. Rerun the
installer after moving the checkout or configuration.
📦 Publish the Package
scripts/publish_camera_stream.sh
export UV_PUBLISH_TOKEN='pypi-...'
scripts/publish_camera_stream.sh --publish
Use --testpypi --publish with a TestPyPI token before production. The script
rejects a dirty worktree unless --allow-dirty is explicitly set.
🏗️ Architecture
The server is one camera-stream process with two logical data-plane stages:
the Supervisor aggregates frames from spawned camera workers, then the Service
publishes the live stream and exposes status. The TUI reads the same in-process
snapshot and does not create another ZeroMQ client.
flowchart LR
Config["config.yaml\nexplicit stream_pub"]
subgraph Workers["spawn camera workers"]
W1["Camera worker\nOpenCV / RealSense / Orbbec"]
Driver["driver.read()\nsynchronized frameset\nlatest-set slot"]
Encode["color: JPEG / raw_bgr8\ndepth: raw_z16\nPUSH HWM 1"]
W1 --> Driver --> Encode
end
subgraph Server["camera-stream server process"]
Supervisor["SUPERVISOR\nIPC PULL HWM 1\ncontrol ROUTER"]
Demand["Topic demand\nXPUB subscription events"]
Service["SERVICE\nXPUB SNDHWM 1\nPUB/SUB compatible\nstatus events + 1 s snapshots"]
TUI["Rich TUI\n--tui\nin-process snapshot"]
Supervisor -. "logical handoff\nper-frame cost" .-> Service
Demand --> Supervisor
Supervisor --> TUI
Service --> TUI
end
ClientA["Client A\nSUB"]
ClientB["Client B\nSUB"]
Web["Optional Web Gateway\nspawn process\nSUB + HTTP/WebSocket"]
Browser["Browser\nzero-install diagnostics"]
Config --> Workers
Config --> Server
Encode -->|"IPC PUSH\nframe header + payload"| Supervisor
W1 -. "DEALER control\nhello/state/heartbeat" .-> Supervisor
ClientA -. "SUB topic demand" .-> Demand
ClientB -. "SUB topic demand" .-> Demand
Service -->|"TCP PUB/SUB\n<camera>/{color,depth} + status/\nJPEG / BGR / Z16"| ClientA
Service --> ClientB
Service -->|"dynamic image subscriptions\nstatus subscription"| Web
Web -->|"HTTP + WebSocket\nlatest frame per viewer/topic"| Browser
classDef worker fill:#e8f4ea,stroke:#2f7d45,color:#173b21
classDef supervisor fill:#f2eafa,stroke:#7b4aa5,color:#321b4d
classDef service fill:#e8f0fb,stroke:#3d6ea8,color:#1c3554
classDef client fill:#fff4df,stroke:#b47720,color:#4c3210
class W1,Driver,Encode worker
class Supervisor,Demand supervisor
class Service,TUI service
class ClientA,ClientB,Web,Browser client
⚡ Data-flow guarantees
- Every frame path is bounded: the capture slot, IPC PUSH/PULL and XPUB socket use capacity-one behavior, so old frames are dropped instead of queued. A RealSense color/depth pair occupies one capture slot and one atomic IPC multipart message, preventing one stream from lagging behind the other.
- Camera workers use the
spawnmultiprocessing start method. A worker owns its camera SDK and reportshello, state transitions and heartbeat metrics through the internal ROUTER/DEALER control channel. stream_pubis the single external one-to-many ZeroMQ PUB/SUB endpoint. Internally it is XPUB solely to observe subscription events for idle policy; clients use ordinary SUB sockets and do not compete for frames. It publishesstatus/camera/<camera-name>state events immediately and a fullstatus/snapshotevery second and on a new snapshot subscription. Those status messages are best-effort, like frames; a status-only subscription never creates camera demand.- The dashboard's
costvalues are processing costs: camera read, Supervisor PULL-to-PUB preparation and local PUB enqueue. Client receive/decode latency and actual client-side drops are not observable from PUB/SUB alone. - With
web.enabled: true, a supervised child process bridges that same public stream to bundled HTTP/WebSocket assets. Its failure or restart does not stop capture, remote ingest, XPUB publication, TUI operation, or native clients.
📊 TUI Dashboard
Run camera-stream server --config config.yaml --tui to render the following
in-process topology view. Press q to stop the server cleanly. Nodes are
vertically centered against their adjacent node stacks; each arrow is shown as
protocol, direction and transport labels.
flowchart LR
subgraph Screen["CAMERA STREAM uptime HH:MM:SS"]
direction LR
subgraph Cameras["Camera nodes (one panel per configured camera)"]
direction TB
Cam1["front_camera [ONLINE]<br/>realsense capture 30 fps<br/>color JPEG 640x480 @30<br/>pub 30 fps · age 4 ms · sub 1<br/>depth RAW_Z16 640x480 @30<br/>pub 30 fps · age 4 ms · sub 1<br/>align color · cost 0.62 ms<br/>ipc 0.40 ms · drops slot 2 ipc 0<br/>subtitle: cost 3 ms | demand 2"]
Cam2["side_camera [SLEEPING]<br/>opencv capture 0 fps<br/>color JPEG 1280x720 @30<br/>pub 0 fps · age - · sub 0<br/>ipc - · drops slot 0 ipc 0<br/>subtitle: cost - | demand 0"]
end
Ipc["IPC<br/>>>>>>>><br/>PUSH / PULL"]
Supervisor["SUPERVISOR<br/>frame PULL, HWM 1<br/>control ROUTER<br/>workers N<br/>subtitle: cost N ms"]
Zmq["ZeroMQ<br/>>>>>>>><br/>XPUB / SUB"]
Service["SERVICE<br/>XPUB tcp://host:5555<br/>ingest tcp://host:5557<br/>web http://host:8080<br/>status PUB snapshot 1s<br/>rate N Mbps<br/>egress N Mbps<br/>clients N<br/>subtitle: cost N ms"]
Pub["PUB<br/>>>>>>>><br/>SUB"]
subgraph Clients["Connected clients (dynamic, vertical)"]
direction TB
Client1["192.168.5.21<br/>codec JPEG<br/>est rx N Mbps<br/>peer 54321/TCP<br/>subtitle: up HH:MM:SS"]
Client2["192.168.5.22<br/>codec JPEG<br/>est rx N Mbps<br/>peer 54322/TCP<br/>subtitle: up HH:MM:SS"]
end
Cameras --> Ipc --> Supervisor --> Zmq --> Service --> Pub --> Clients
end
classDef camera fill:#e8f4ea,stroke:#2f7d45,color:#173b21
classDef offline fill:#fce8e6,stroke:#b44b3e,color:#5a1e18
classDef supervisor fill:#f2eafa,stroke:#7b4aa5,color:#321b4d
classDef service fill:#e8f0fb,stroke:#3d6ea8,color:#1c3554
classDef client fill:#fff4df,stroke:#b47720,color:#4c3210
class Cam1 camera
class Cam2 offline
class Supervisor supervisor
class Service service
class Client1,Client2 client
🧾 Panel fields
- Camera: physical-camera state, driver and capture FPS. Each color/depth
row shows codec/profile, PUB FPS, capture-to-PUB age and independent demand.
A depth row also shows alignment mode and SDK alignment cost. The panel
includes shared IPC encode/send cost and drop counters. Its subtitle includes the
current matching image-topic
demand:0means no image subscriber is keeping the camera awake. With idle policy enabled,IDLE_PENDING,SLEEPING, andWAKINGshow demand-driven lifecycle state. Itscostis the measureddriver.read()cost. - SUPERVISOR: IPC PULL and control ROUTER roles plus worker count. Its
active/totalworker count reveals cameras currently kept awake. Its subtitle is time from complete IPC receipt to beginning PUB forwarding. - SERVICE: connectable XPUB and ingest endpoints, optional Web URL,
periodic status snapshot cadence, current publish rate,
estimated egress (
rate × connected clients) and client count. Its subtitle is the local PUB enqueue cost. - Client: remote IP and TCP port, available codecs, estimated receive rate and connection uptime. PUB/SUB cannot expose the client's actual subscriptions, receive rate, drops or decode latency without an additional client telemetry channel.
stream_pub publishes camera frames as three-part ZeroMQ messages:
[topic UTF-8] [header JSON UTF-8] [JPEG, BGR, or Z16 bytes]
Topics are <camera-name>/color and, when configured, <camera-name>/depth.
The header declares schema_version, stream/frame sequence, capture timestamps,
dimensions, pixel format and codec.
🧬 Frame header reference
The second ZeroMQ message part is UTF-8 JSON. For example:
{
"camera": "base_camera",
"captured_monotonic_ns": 77378702275284,
"captured_utc_ns": 1787108850771291701,
"codec": "jpeg",
"frameset_sequence": 44005,
"height": 480,
"payload_size": 56182,
"pixel_format": "bgr8",
"schema_version": 1,
"sequence": 44005,
"stream": "color",
"timestamp_source": "host",
"width": 640
}
| Field | Example | Meaning and client use |
|---|---|---|
schema_version |
1 |
Header contract version. Reject or explicitly handle unknown versions before decoding a frame. |
camera |
base_camera |
Configured camera name. Together with stream, it determines the topic base_camera/color. |
stream |
color |
Stream kind: color or depth; together with camera, it selects the topic. |
sequence |
44005 |
Per-stream frame counter, beginning at 1 when a worker starts. A jump indicates skipped frames; it resets after a worker restart. |
frameset_sequence |
44005 |
Physical capture-set counter. Color and depth from the same RealSense SDK frameset carry the same value. |
captured_monotonic_ns |
77378702275284 |
Host monotonic-clock timestamp at capture, in nanoseconds. Use only for elapsed-time calculations on the same server host; it has no UTC epoch and cannot be compared across hosts or persisted as wall-clock time. |
captured_utc_ns |
1787108850771291701 |
Host wall-clock UTC timestamp at capture, in nanoseconds since Unix epoch. This sample is 2026-08-19T03:07:30.771291701Z. It is suitable for logging and cross-machine correlation, subject to host clock synchronization. |
timestamp_source |
host |
Both timestamps are produced by the server host after driver.read() returns, not by a camera hardware clock. |
width / height |
640 / 480 |
Image dimensions in pixels. For raw_bgr8, expected payload length is width * height * 3. |
pixel_format |
bgr8 |
Pixel layout of the decoded image: 8-bit blue, green, red channels. JPEG payloads should decode to this layout with OpenCV. |
codec |
jpeg |
Payload encoding. jpeg requires image decoding; raw_bgr8 is a directly reshaped BGR buffer. |
payload_size |
56182 |
Byte count of the third ZeroMQ message part. Verify len(payload) == payload_size before decoding; it is 56,182 bytes in this sample. |
For a jpeg frame, decode the third part with
cv2.imdecode(np.frombuffer(payload, dtype=np.uint8), cv2.IMREAD_COLOR). For
raw_bgr8, first verify payload_size == width * height * 3, then reshape it
to (height, width, 3) with np.uint8. A depth frame uses raw_z16, z16,
and a little-endian width * height * 2 payload. Its additional fields are
frameset_sequence, depth_scale_m, alignment, aligned_to, endianness,
device_timestamp_ms, and device_frame_number. Color and depth captured from
the same SDK frameset share frameset_sequence; sequence remains per stream.
The stream endpoint publishes a complete status snapshot every second, and
also when a status/snapshot subscription becomes active, on status/snapshot.
It sends each camera state change immediately on
status/camera/<camera-name>. Each snapshot includes demand_subscriptions
(matching <camera>/{color,depth} subscriptions, not connected-client count) and
idle_after_s; the service includes active_worker_count and its effective
idle_policy. A later snapshot repairs a missed state event, but PUB/SUB does
not guarantee delivery. When idle policy is disabled, a camera remains
STARTING until its worker captures a first frame, then changes to ONLINE
without any stream subscriber. When it is enabled, only a matching image-topic
subscription keeps that camera awake or wakes it from SLEEPING; status/
alone does not.
The service is intentionally live-only: it performs no recording or replay.
Image processing is limited to JPEG encoding and the explicitly configured
RealSense alignment: color operation; raw depth is otherwise unchanged.
Every internal data stage has capacity one, so a slow encoder or subscriber
loses an old synchronized frameset instead of building a queue.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file camera_stream-0.3.8.tar.gz.
File metadata
- Download URL: camera_stream-0.3.8.tar.gz
- Upload date:
- Size: 118.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a16cf2c7794224af061008c819d639e1883ea12ef096d2c30ae9b01779e14146
|
|
| MD5 |
79e62653a660d0081e8c3474e0c3d951
|
|
| BLAKE2b-256 |
24f18db083dc48114ea43aa37672aa1cda2163b55e772089fac9fcd703779023
|
File details
Details for the file camera_stream-0.3.8-py3-none-any.whl.
File metadata
- Download URL: camera_stream-0.3.8-py3-none-any.whl
- Upload date:
- Size: 91.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d3b949f2f7d24b44382a29c1197a90ab89b08b58a9b4f2d034a4239193f085d4
|
|
| MD5 |
b5b1ee98fbdc7bf9407b45c7e2351a1e
|
|
| BLAKE2b-256 |
e412b2606fcb9f8adf08d8d5286aa4b5147cf262c0bf8be5f926b1ab3a42c9a6
|