Compare commits
2 Commits
1066f793e2
...
44090c1d6d
| Author | SHA1 | Date | |
|---|---|---|---|
| 44090c1d6d | |||
| 8c4cd69443 |
24
README.md
24
README.md
@@ -15,18 +15,18 @@ Designed to run on resource-constrained hardware (Raspberry Pi capturing raw MJP
|
||||
|
||||
- [docs/cli/media_ctrl_cli.md](docs/cli/media_ctrl_cli.md) — media device and topology tool
|
||||
- [docs/cli/v4l2_ctrl_cli.md](docs/cli/v4l2_ctrl_cli.md) — V4L2 camera control tool
|
||||
- `transport_cli` — send/receive framed messages, inspect transport frame headers
|
||||
- `discovery_cli` — announce and discover peers over UDP multicast; print found/lost events
|
||||
- `config_cli` — load an INI config file and print the resolved values after applying schema defaults
|
||||
- `protocol_cli` — send and receive typed protocol messages; inspect frame payloads
|
||||
- `query_cli` — wait for first discovered node, send ENUM_DEVICES, print results
|
||||
- `test_image_cli` — generate test patterns and write PPM output for visual inspection
|
||||
- `xorg_cli` — display a test pattern in the viewer window; exercises scale/anchor modes and text overlays
|
||||
- `v4l2_view_cli` — live camera viewer; auto-selects highest-FPS format, displays FPS overlay
|
||||
- `stream_send_cli` — capture MJPEG from V4L2, connect to a receiver over TCP, stream VIDEO_FRAME messages with per-stream stats
|
||||
- `stream_recv_cli` — listen for incoming TCP stream, display received MJPEG frames with fps/Mbps overlay
|
||||
- `reconciler_cli` — simulated state machine experiment; validate the reconciler with fake resources before wiring into the node
|
||||
- `controller_cli` — interactive REPL; connects to nodes by peer index or host:port; enum-devices, enum-controls, get/set-control, start/stop-ingest, start/stop-display
|
||||
- [docs/cli/transport_cli.md](docs/cli/transport_cli.md) — send/receive framed messages, inspect transport frame headers
|
||||
- [docs/cli/discovery_cli.md](docs/cli/discovery_cli.md) — announce and discover peers over UDP multicast; print found/lost events
|
||||
- [docs/cli/config_cli.md](docs/cli/config_cli.md) — load an INI config file and print the resolved values after applying schema defaults
|
||||
- [docs/cli/protocol_cli.md](docs/cli/protocol_cli.md) — send and receive typed protocol messages; inspect frame payloads
|
||||
- [docs/cli/query_cli.md](docs/cli/query_cli.md) — wait for first discovered node, send ENUM_DEVICES, print results
|
||||
- [docs/cli/test_image_cli.md](docs/cli/test_image_cli.md) — generate test patterns and write PPM output for visual inspection
|
||||
- [docs/cli/xorg_cli.md](docs/cli/xorg_cli.md) — display a test pattern in the viewer window; exercises scale/anchor modes and text overlays
|
||||
- [docs/cli/v4l2_view_cli.md](docs/cli/v4l2_view_cli.md) — live camera viewer; auto-selects highest-FPS format, displays FPS overlay
|
||||
- [docs/cli/stream_send_cli.md](docs/cli/stream_send_cli.md) — capture MJPEG from V4L2, connect to a receiver over TCP, stream VIDEO_FRAME messages with per-stream stats
|
||||
- [docs/cli/stream_recv_cli.md](docs/cli/stream_recv_cli.md) — listen for incoming TCP stream, display received MJPEG frames with fps/Mbps overlay
|
||||
- [docs/cli/reconciler_cli.md](docs/cli/reconciler_cli.md) — simulated state machine experiment; validate the reconciler with fake resources before wiring into the node
|
||||
- [docs/cli/controller_cli.md](docs/cli/controller_cli.md) — interactive REPL; connects to nodes by peer index or host:port; enum-devices, enum-controls, get/set-control, start/stop-ingest, start/stop-display
|
||||
|
||||
## Structure
|
||||
|
||||
|
||||
@@ -137,14 +137,17 @@ static void on_video_node(
|
||||
uint8_t pflags, uint8_t is_capture,
|
||||
void *ud)
|
||||
{
|
||||
(void)eflags; (void)pflags; (void)ud;
|
||||
(void)eflags; (void)pflags;
|
||||
int *idx = ud;
|
||||
char caps[128];
|
||||
caps_str(dcaps, caps, sizeof(caps));
|
||||
printf(" video %.*s entity=%.*s type=0x%08x caps=[%s]%s\n",
|
||||
printf(" [%d] video %.*s entity=%.*s type=0x%08x caps=[%s]%s\n",
|
||||
*idx,
|
||||
(int)path_len, path,
|
||||
(int)ename_len, ename,
|
||||
etype, caps,
|
||||
is_capture ? " [capture]" : "");
|
||||
(*idx)++;
|
||||
}
|
||||
|
||||
static void on_standalone(
|
||||
@@ -152,10 +155,12 @@ static void on_standalone(
|
||||
const char *name, uint8_t name_len,
|
||||
void *ud)
|
||||
{
|
||||
(void)ud;
|
||||
printf(" standalone %.*s card=%.*s\n",
|
||||
int *idx = ud;
|
||||
printf(" [%d] standalone %.*s card=%.*s\n",
|
||||
*idx,
|
||||
(int)path_len, path,
|
||||
(int)name_len, name);
|
||||
(*idx)++;
|
||||
}
|
||||
|
||||
static const char *scale_name(uint8_t s)
|
||||
@@ -170,6 +175,7 @@ static const char *scale_name(uint8_t s)
|
||||
}
|
||||
|
||||
static void on_display(
|
||||
uint16_t device_id,
|
||||
uint16_t stream_id,
|
||||
int16_t win_x, int16_t win_y,
|
||||
uint16_t win_w, uint16_t win_h,
|
||||
@@ -177,8 +183,8 @@ static void on_display(
|
||||
void *ud)
|
||||
{
|
||||
(void)ud;
|
||||
printf(" display stream=%u pos=%d,%d size=%ux%u scale=%s anchor=%s\n",
|
||||
stream_id, win_x, win_y, win_w, win_h,
|
||||
printf(" [%u] display stream=%u pos=%d,%d size=%ux%u scale=%s anchor=%s\n",
|
||||
device_id, stream_id, win_x, win_y, win_w, win_h,
|
||||
scale_name(scale),
|
||||
anchor == 0 ? "center" : "topleft");
|
||||
}
|
||||
@@ -231,9 +237,10 @@ static void on_frame(struct Transport_Conn *conn,
|
||||
switch (cs->pending_cmd) {
|
||||
case PROTO_CMD_ENUM_DEVICES: {
|
||||
struct Proto_Response_Header hdr;
|
||||
int dev_idx = 0;
|
||||
struct App_Error e = proto_read_enum_devices_response(
|
||||
frame->payload, frame->payload_length, &hdr,
|
||||
on_media_device, on_video_node, on_standalone, on_display, NULL);
|
||||
on_media_device, on_video_node, on_standalone, on_display, &dev_idx);
|
||||
if (!APP_IS_OK(e)) { app_error_print(&e); }
|
||||
else if (hdr.status != PROTO_STATUS_OK) {
|
||||
fprintf(stderr, "ENUM_DEVICES: status=%u\n", hdr.status);
|
||||
|
||||
80
docs/cli/config_cli.md
Normal file
80
docs/cli/config_cli.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# config_cli
|
||||
|
||||
A development tool for loading and inspecting INI configuration files against the node's schema. Useful for verifying that a config file parses correctly and seeing what values would be applied.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make cli
|
||||
```
|
||||
|
||||
The binary is placed in `build/cli/`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Load a config file
|
||||
|
||||
```sh
|
||||
./config_cli <file>
|
||||
```
|
||||
|
||||
Parses the file and prints all resolved values. Values absent from the file are filled with schema defaults.
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
./config_cli /etc/video-node/node.cfg
|
||||
```
|
||||
|
||||
Example output:
|
||||
|
||||
```
|
||||
[node]
|
||||
name = camera:0
|
||||
site_id = 0
|
||||
tcp_port = 8000
|
||||
function = source
|
||||
|
||||
[discovery]
|
||||
interval_ms = 5000
|
||||
timeout_intervals = 3
|
||||
|
||||
[transport]
|
||||
max_connections = 16
|
||||
```
|
||||
|
||||
### Print schema defaults
|
||||
|
||||
```sh
|
||||
./config_cli --defaults
|
||||
```
|
||||
|
||||
Prints all keys with their default values and types, without reading any file.
|
||||
|
||||
---
|
||||
|
||||
## Config Schema
|
||||
|
||||
| Section | Key | Type | Default |
|
||||
|---|---|---|---|
|
||||
| `node` | `name` | string | `unnamed:0` |
|
||||
| `node` | `site_id` | uint16 | `0` |
|
||||
| `node` | `tcp_port` | uint16 | `0` (auto) |
|
||||
| `node` | `function` | flags | `source` |
|
||||
| `discovery` | `interval_ms` | uint32 | `5000` |
|
||||
| `discovery` | `timeout_intervals` | uint32 | `3` |
|
||||
| `transport` | `max_connections` | uint32 | `16` |
|
||||
|
||||
`function` accepts a comma-separated list of role flags: `source`, `relay`, `sink`, `controller`.
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Video Routing System
|
||||
|
||||
`config_cli` exercises the `config` module used by the video node at startup. Both the node binary and `config_cli` share the same schema definition, so this tool is an accurate preview of how the node will interpret a config file.
|
||||
172
docs/cli/controller_cli.md
Normal file
172
docs/cli/controller_cli.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# controller_cli
|
||||
|
||||
An interactive REPL for controlling video nodes. Connects to a running node over TCP and lets you enumerate devices and controls, adjust camera parameters, start and stop ingest streams, and open and close display windows. Uses readline for line editing and history.
|
||||
|
||||
> **Note:** `controller_cli` is a temporary development tool. The long-term replacement is a dedicated `controller` binary that maintains simultaneous connections to all discovered nodes rather than switching between them.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make cli
|
||||
```
|
||||
|
||||
The binary is placed in `build/cli/`. Requires `libreadline`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
./controller_cli [--host HOST] [--port PORT]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|---|---|---|
|
||||
| `--host HOST` | — | Connect to this host on startup |
|
||||
| `--port PORT` | `8000` | TCP port to use with `--host` |
|
||||
|
||||
Without `--host`, the tool starts in discovery mode and waits for nodes to appear. Connect to a discovered node from the REPL using the `connect` command.
|
||||
|
||||
---
|
||||
|
||||
## REPL Commands
|
||||
|
||||
### Discovery
|
||||
|
||||
```
|
||||
peers
|
||||
```
|
||||
|
||||
List all currently discovered nodes with their index, name, host, and port.
|
||||
|
||||
```
|
||||
connect [idx | host:port]
|
||||
```
|
||||
|
||||
Connect to a node. With no argument, connects to the first discovered peer. Examples:
|
||||
|
||||
```
|
||||
connect # first discovered peer
|
||||
connect 1 # peer at index 1 in the peers list
|
||||
connect 192.168.1.42:8000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Device enumeration
|
||||
|
||||
```
|
||||
enum-devices
|
||||
```
|
||||
|
||||
List all devices on the connected node: media controller devices and their video nodes (with device index), standalone V4L2 devices, and active display windows. Each entry shows its flat device index `[N]` for use in control commands.
|
||||
|
||||
```
|
||||
enum-controls <device_index>
|
||||
```
|
||||
|
||||
List all controls for a device. For V4L2 devices this returns camera parameters; for display windows it returns display controls.
|
||||
|
||||
**Display device controls:**
|
||||
|
||||
| ID | Name | Range | Description |
|
||||
|---|---|---|---|
|
||||
| `0x00D00001` | Scale | 0–3 | 0=stretch 1=fit 2=fill 3=1:1 |
|
||||
| `0x00D00002` | Anchor | 0–1 | 0=center 1=topleft |
|
||||
| `0x00D00003` | No-signal FPS | 1–60 | No-signal animation frame rate |
|
||||
|
||||
---
|
||||
|
||||
### Control get/set
|
||||
|
||||
```
|
||||
get-control <device_index> <control_id_hex>
|
||||
set-control <device_index> <control_id_hex> <value>
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
get-control 0 0x00980900 # read Brightness on device 0
|
||||
set-control 0 0x00980900 200 # set Brightness to 200
|
||||
set-control 3 0x00D00001 1 # set Scale to 'fit' on display device 3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Ingest (source node)
|
||||
|
||||
```
|
||||
start-ingest <stream_id> <device_path> <dest_host> <dest_port>
|
||||
[format] [width] [height] [fps_n] [fps_d]
|
||||
```
|
||||
|
||||
Tell the connected node to open a V4L2 device and stream to `dest_host:dest_port`. Format, resolution, and frame rate default to auto-select (best MJPEG) when omitted.
|
||||
|
||||
```
|
||||
stop-ingest <stream_id>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Display (sink node)
|
||||
|
||||
```
|
||||
start-display <stream_id> [win_x] [win_y] [win_w] [win_h] [no_signal_fps]
|
||||
```
|
||||
|
||||
Tell the connected node to open a display window for `stream_id`. Position and size default to `0,0 1280×720`. No-signal FPS defaults to 15.
|
||||
|
||||
```
|
||||
stop-display <stream_id>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Other
|
||||
|
||||
```
|
||||
help show command list
|
||||
quit exit
|
||||
exit exit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example session
|
||||
|
||||
```
|
||||
$ ./controller_cli
|
||||
listening for nodes...
|
||||
[discovered 0] camera:0 192.168.1.42:8000
|
||||
> connect
|
||||
connected to 192.168.1.42:8000
|
||||
> enum-devices
|
||||
devices:
|
||||
media /dev/media0 driver=unicam model=unicam ... (1 video node)
|
||||
[0] video /dev/video0 entity=unicam-image ... [capture]
|
||||
> start-ingest 1 /dev/video0 192.168.1.55 8001
|
||||
ok
|
||||
> connect 1
|
||||
[discovered 1] display:0 192.168.1.55:8000
|
||||
connected to 192.168.1.55:8000
|
||||
> start-display 1
|
||||
ok
|
||||
> enum-devices
|
||||
devices:
|
||||
[3] display stream=1 pos=0,0 size=1280x720 scale=stretch anchor=center
|
||||
> set-control 3 0x00D00001 1
|
||||
ok
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Video Routing System
|
||||
|
||||
`controller_cli` is the primary human interface for the video routing system during development. It exercises the full control channel: discovery, TCP transport, and every protocol command type. It is the reference implementation for how a controller interacts with nodes.
|
||||
|
||||
See also: [`query_cli.md`](query_cli.md) for a simpler read-only query; [`stream_send_cli.md`](stream_send_cli.md) / [`stream_recv_cli.md`](stream_recv_cli.md) for testing streams without a full node.
|
||||
53
docs/cli/discovery_cli.md
Normal file
53
docs/cli/discovery_cli.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# discovery_cli
|
||||
|
||||
A development tool for testing the UDP multicast discovery layer. Announces a node on the local network and prints peers as they appear and disappear.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make cli
|
||||
```
|
||||
|
||||
The binary is placed in `build/cli/`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
discovery_cli <name> <tcp_port> [flags]
|
||||
```
|
||||
|
||||
| Argument | Description |
|
||||
|---|---|
|
||||
| `name` | Node name in `namespace:instance` form, e.g. `v4l2:microscope` |
|
||||
| `tcp_port` | The TCP port this node listens on for transport connections |
|
||||
| `flags` | Comma-separated role list: `source`, `relay`, `sink`, `controller` (default: `source`) |
|
||||
|
||||
Example — announce a source node and watch for peers:
|
||||
|
||||
```sh
|
||||
./discovery_cli camera:0 8000 source
|
||||
```
|
||||
|
||||
Example output as peers are found and lost:
|
||||
|
||||
```
|
||||
found: unnamed:0 192.168.1.42:8001 site=0 flags=source
|
||||
found: display:0 192.168.1.55:8002 site=0 flags=sink
|
||||
lost: unnamed:0 192.168.1.42:8001
|
||||
```
|
||||
|
||||
Run two instances on the same machine (different ports) to verify mutual discovery.
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Video Routing System
|
||||
|
||||
`discovery_cli` exercises the `discovery` module, which the video node uses to find peers on the LAN without configuration. The node starts a discovery listener on startup; controllers use discovered peer tables to locate nodes before connecting.
|
||||
|
||||
See also: [`query_cli.md`](query_cli.md) for a combined discovery + protocol query.
|
||||
64
docs/cli/protocol_cli.md
Normal file
64
docs/cli/protocol_cli.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# protocol_cli
|
||||
|
||||
A development tool for testing the protocol layer in isolation. Runs a server that decodes and prints all incoming control messages, or a client that connects and sends a sample STREAM_OPEN request.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make cli
|
||||
```
|
||||
|
||||
The binary is placed in `build/cli/`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Server mode
|
||||
|
||||
```sh
|
||||
./protocol_cli --server [port]
|
||||
```
|
||||
|
||||
Default port: `8000`. Listens for connections and prints a decoded description of every received frame.
|
||||
|
||||
Example output on an incoming STREAM_OPEN:
|
||||
|
||||
```
|
||||
CONTROL_REQUEST request_id=1 STREAM_OPEN stream_id=1 format=0x0001 pixel_format=0x0000 origin=0x0001
|
||||
```
|
||||
|
||||
Recognised message types and commands:
|
||||
|
||||
| Type | Description |
|
||||
|---|---|
|
||||
| `VIDEO_FRAME` | stream_id + compressed payload |
|
||||
| `STREAM_EVENT` | stream_id + event code (INTERRUPTED / RESUMED) |
|
||||
| `CONTROL_REQUEST` | request_id + command (STREAM_OPEN, STREAM_CLOSE, ENUM_DEVICES, ENUM_CONTROLS, GET_CONTROL, SET_CONTROL, ENUM_MONITORS) |
|
||||
| `CONTROL_RESPONSE` | request_id + status (OK, ERROR, UNKNOWN_CMD, INVALID_PARAMS, NOT_FOUND) |
|
||||
|
||||
### Client mode
|
||||
|
||||
```sh
|
||||
./protocol_cli --client <host> <port>
|
||||
```
|
||||
|
||||
Connects to the server and sends a single STREAM_OPEN request, then waits for the response.
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
./protocol_cli --client 127.0.0.1 8000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Video Routing System
|
||||
|
||||
`protocol_cli` exercises the `protocol` module that all nodes use to encode and decode wire messages. Pairing the server with a running node lets you inspect raw control traffic; pairing the client with a running node lets you inject individual commands for debugging.
|
||||
|
||||
See also: [`transport_cli.md`](transport_cli.md) for raw frame-level testing without protocol parsing.
|
||||
64
docs/cli/query_cli.md
Normal file
64
docs/cli/query_cli.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# query_cli
|
||||
|
||||
An integration smoke test that combines discovery and protocol. Waits for a video node to appear on the local network, then sends an ENUM_DEVICES query and prints the results. Optionally enumerates controls on a specific device.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make cli
|
||||
```
|
||||
|
||||
The binary is placed in `build/cli/`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
./query_cli [--timeout ms] [--controls device_index]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|---|---|---|
|
||||
| `--timeout ms` | `5000` | How long to wait for a node to appear (milliseconds) |
|
||||
| `--controls idx` | — | After enumeration, also query controls for this device index |
|
||||
|
||||
Example — query the first node found within 5 s:
|
||||
|
||||
```sh
|
||||
./query_cli
|
||||
```
|
||||
|
||||
Example output:
|
||||
|
||||
```
|
||||
discovered: camera:0 192.168.1.42:8000
|
||||
querying devices...
|
||||
media /dev/media0 driver=unicam model=unicam bus=platform:fe801000.csi (1 video node)
|
||||
[0] video /dev/video0 entity=unicam-image type=0x00010001 caps=[video-capture] [capture]
|
||||
[1] standalone /dev/video1 card=USB Camera
|
||||
```
|
||||
|
||||
Example with controls:
|
||||
|
||||
```sh
|
||||
./query_cli --controls 1
|
||||
```
|
||||
|
||||
```
|
||||
ctrl id=0x00980900 Brightness int min=0 max=255 step=1 default=128 current=127
|
||||
ctrl id=0x00980901 Contrast int min=0 max=255 step=1 default=128 current=128
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Video Routing System
|
||||
|
||||
`query_cli` is an end-to-end integration test: it exercises discovery, transport, and protocol in one shot. It is the simplest way to verify that a node is reachable and responding correctly to control requests before using `controller_cli` for full interaction.
|
||||
|
||||
See also: [`discovery_cli.md`](discovery_cli.md), [`controller_cli.md`](controller_cli.md).
|
||||
83
docs/cli/reconciler_cli.md
Normal file
83
docs/cli/reconciler_cli.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# reconciler_cli
|
||||
|
||||
An interactive REPL for exploring the reconciler module. Sets up a simulated three-resource state machine (device, transport, stream) with declared dependencies and lets you drive reconciliation manually — useful for understanding reconciler behaviour before wiring it into the node.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make cli
|
||||
```
|
||||
|
||||
The binary is placed in `build/cli/`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
./reconciler_cli
|
||||
```
|
||||
|
||||
Drops into an interactive prompt:
|
||||
|
||||
```
|
||||
reconciler> _
|
||||
```
|
||||
|
||||
### Demo resources
|
||||
|
||||
Three resources are pre-configured:
|
||||
|
||||
| Resource | States | Notes |
|
||||
|---|---|---|
|
||||
| `device` | CLOSED → OPEN → STREAMING | Simulates a V4L2 device |
|
||||
| `transport` | DISCONNECTED → CONNECTED | Depends on device=OPEN |
|
||||
| `stream` | INACTIVE → ACTIVE | Depends on transport=CONNECTED and device=STREAMING |
|
||||
|
||||
Dependencies are checked before each transition: the reconciler will not advance a resource until all its prerequisites are met. Blocked resources show the unmet dependency in the status output.
|
||||
|
||||
### Commands
|
||||
|
||||
```
|
||||
status print all resources with current/wanted state and transition status
|
||||
want <name> <state> set the desired state (by number or case-insensitive name)
|
||||
tick run one reconciler tick
|
||||
run tick until stable (max 20 ticks) or a failure occurs
|
||||
fail <name> make the next action for this resource fail (simulates an error)
|
||||
help show commands
|
||||
quit / exit exit
|
||||
```
|
||||
|
||||
### Example session
|
||||
|
||||
```
|
||||
reconciler> want device STREAMING
|
||||
reconciler> want transport CONNECTED
|
||||
reconciler> want stream ACTIVE
|
||||
reconciler> run
|
||||
[device] CLOSED → OPEN ok
|
||||
[device] OPEN → STREAMING ok
|
||||
[transport] DISCONNECTED → CONNECTED ok
|
||||
[stream] INACTIVE → ACTIVE ok
|
||||
reconciler> status
|
||||
device current=STREAMING wanted=STREAMING
|
||||
transport current=CONNECTED wanted=CONNECTED
|
||||
stream current=ACTIVE wanted=ACTIVE
|
||||
reconciler> fail transport
|
||||
reconciler> tick
|
||||
[transport] CONNECTED → DISCONNECTED error (simulated)
|
||||
reconciler> status
|
||||
device current=STREAMING wanted=STREAMING
|
||||
transport current=DISCONNECTED wanted=CONNECTED (will retry)
|
||||
stream current=ACTIVE wanted=ACTIVE (blocked: transport != CONNECTED)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Video Routing System
|
||||
|
||||
The reconciler module manages device open/close and transport connect/disconnect in the video node. `reconciler_cli` lets you exercise the BFS pathfinding, dependency checking, and event-driven tick logic without any real hardware — the same code paths that run in the node on every incoming command or timer event.
|
||||
67
docs/cli/stream_recv_cli.md
Normal file
67
docs/cli/stream_recv_cli.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# stream_recv_cli
|
||||
|
||||
Listens for an incoming TCP stream of `VIDEO_FRAME` protocol messages and displays the frames in an X11 window with a per-stream fps/Mbps overlay. Pair with [`stream_send_cli`](stream_send_cli.md) to test end-to-end video transport without a full node.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make cli
|
||||
```
|
||||
|
||||
The binary is placed in `build/cli/`.
|
||||
|
||||
Requires GLFW, OpenGL, and libjpeg-turbo.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
./stream_recv_cli [--port PORT] [--stream-id N]
|
||||
[--scale stretch|fit|fill|1:1]
|
||||
[--anchor center|topleft]
|
||||
[--x N] [--y N]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|---|---|---|
|
||||
| `--port PORT` | `7700` | TCP port to listen on |
|
||||
| `--stream-id N` | `0` | Filter by stream ID; `0` accepts any stream |
|
||||
| `--scale` | `fit` | Frame scaling in window |
|
||||
| `--anchor` | `center` | Frame alignment in window |
|
||||
| `--x N` | `0` | Window X position |
|
||||
| `--y N` | `0` | Window Y position |
|
||||
|
||||
Press **Q** or **Escape** to close the window.
|
||||
|
||||
### Example
|
||||
|
||||
```sh
|
||||
./stream_recv_cli --port 7700 --scale fit
|
||||
```
|
||||
|
||||
### Statistics overlay
|
||||
|
||||
Updated every 0.5 seconds:
|
||||
|
||||
```
|
||||
stream 1: 30.1 fps 18.3 Mbps
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture note
|
||||
|
||||
The tool is multi-threaded: transport receive runs on a background thread and deposits frames into a shared slot (mutex + condition variable); the main thread owns the GLFW/OpenGL context and pulls from that slot on each render cycle. This is the same pattern used by the node's display sink.
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Video Routing System
|
||||
|
||||
`stream_recv_cli` tests the sink end of the display pipeline: `VIDEO_FRAME` receive → MJPEG decode → xorg render. The node's display sink (started via `START_DISPLAY`) performs the same operation, driven by the control channel.
|
||||
|
||||
See also: [`stream_send_cli.md`](stream_send_cli.md) for the send side; [`controller_cli.md`](controller_cli.md) to use the full node pipeline.
|
||||
64
docs/cli/stream_send_cli.md
Normal file
64
docs/cli/stream_send_cli.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# stream_send_cli
|
||||
|
||||
Captures MJPEG from a V4L2 device and streams it to a receiver over TCP as `VIDEO_FRAME` protocol messages. Prints per-stream throughput statistics. Pair with [`stream_recv_cli`](stream_recv_cli.md) to test an end-to-end stream without running a full node.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make cli
|
||||
```
|
||||
|
||||
The binary is placed in `build/cli/`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
./stream_send_cli [--device PATH] [--host HOST] [--port PORT] [--stream-id N]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|---|---|---|
|
||||
| `--device PATH` | `/dev/video0` | V4L2 capture device |
|
||||
| `--host HOST` | `127.0.0.1` | Receiver hostname or IP |
|
||||
| `--port PORT` | `7700` | Receiver TCP port |
|
||||
| `--stream-id N` | `1` | Stream ID embedded in each `VIDEO_FRAME` message |
|
||||
|
||||
### Example
|
||||
|
||||
```sh
|
||||
# Terminal 1 — start a receiver
|
||||
./stream_recv_cli --port 7700
|
||||
|
||||
# Terminal 2 — start the sender
|
||||
./stream_send_cli --device /dev/video0 --host 127.0.0.1 --port 7700 --stream-id 1
|
||||
```
|
||||
|
||||
### Statistics output
|
||||
|
||||
Printed to stderr every 0.5 seconds:
|
||||
|
||||
```
|
||||
stream 1: 30.2 fps 18.4 Mbps
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- The tool opens the device and selects the highest-FPS MJPEG mode automatically.
|
||||
- Frames are sent as-is (raw MJPEG from the kernel) wrapped in `VIDEO_FRAME` messages — no re-encoding.
|
||||
- The connection is outbound: `stream_send_cli` connects to the receiver, not the other way around. This mirrors the node's START_INGEST behaviour.
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Video Routing System
|
||||
|
||||
`stream_send_cli` tests the source end of the ingest pipeline: V4L2 capture → transport send → `VIDEO_FRAME` messages. The node's ingest module performs the same operation, but driven by `START_INGEST` commands over the control channel.
|
||||
|
||||
See also: [`stream_recv_cli.md`](stream_recv_cli.md) for the receive side; [`v4l2_view_cli.md`](v4l2_view_cli.md) for local-only viewing.
|
||||
70
docs/cli/test_image_cli.md
Normal file
70
docs/cli/test_image_cli.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# test_image_cli
|
||||
|
||||
A development tool for generating test images and writing them to PPM files. Used to verify the `test_image` module's pattern generators and pixel format output without needing a camera or display.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make cli
|
||||
```
|
||||
|
||||
The binary is placed in `build/cli/`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
./test_image_cli [--pattern bars|ramp|grid]
|
||||
[--width N] [--height N]
|
||||
[--format yuv420|yuv422|bgra]
|
||||
--out FILE.ppm
|
||||
```
|
||||
|
||||
All options are optional except `--out`.
|
||||
|
||||
| Option | Default | Description |
|
||||
|---|---|---|
|
||||
| `--pattern` | `bars` | Test pattern to generate |
|
||||
| `--width N` | `1280` | Image width in pixels |
|
||||
| `--height N` | `720` | Image height in pixels |
|
||||
| `--format` | `yuv420` | Internal pixel format before PPM conversion |
|
||||
| `--out FILE` | required | Output file path |
|
||||
|
||||
### Patterns
|
||||
|
||||
| Name | Description |
|
||||
|---|---|
|
||||
| `bars` | Colour bars (SMPTE-style) |
|
||||
| `ramp` | Luminance ramp from black to white |
|
||||
| `grid` | Crosshatch grid |
|
||||
|
||||
### Formats
|
||||
|
||||
| Name | Description |
|
||||
|---|---|
|
||||
| `yuv420` | Planar YUV 4:2:0 |
|
||||
| `yuv422` | Packed YUV 4:2:2 |
|
||||
| `bgra` | Packed BGRA 8-bit |
|
||||
|
||||
The output is always written as a PPM (RGB) file regardless of format; the internal format affects how the pattern is generated and converted.
|
||||
|
||||
### Example
|
||||
|
||||
```sh
|
||||
./test_image_cli --pattern bars --width 1920 --height 1080 --format yuv420 --out bars.ppm
|
||||
```
|
||||
|
||||
Open with any image viewer that supports PPM (e.g. `feh`, `eog`, GIMP).
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Video Routing System
|
||||
|
||||
`test_image_cli` exercises the `test_image` module used by development tools to inject synthetic frames without a camera. The same module drives the xorg test pattern display in `xorg_cli`.
|
||||
|
||||
See also: [`xorg_cli.md`](xorg_cli.md) for live window rendering.
|
||||
69
docs/cli/transport_cli.md
Normal file
69
docs/cli/transport_cli.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# transport_cli
|
||||
|
||||
A development tool for exercising the transport layer. Starts a framed-TCP server (which echoes received frames) or a client that connects and sends a sequence of test frames.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make cli
|
||||
```
|
||||
|
||||
The binary is placed in `build/cli/`.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Server mode
|
||||
|
||||
Listen on a port and echo back every frame received:
|
||||
|
||||
```sh
|
||||
./transport_cli server <port> [max_connections]
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
./transport_cli server 8000
|
||||
```
|
||||
|
||||
Output (per received frame):
|
||||
|
||||
```
|
||||
received frame: type=0x0001 len=8
|
||||
```
|
||||
|
||||
### Client mode
|
||||
|
||||
Connect to a server and send three test frames:
|
||||
|
||||
```sh
|
||||
./transport_cli client <host> <port>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
./transport_cli client 127.0.0.1 8000
|
||||
```
|
||||
|
||||
Each frame carries a fixed payload with a counter:
|
||||
|
||||
```
|
||||
frame 0: payload = deadbeef 00000000
|
||||
frame 1: payload = deadbeef 00000001
|
||||
frame 2: payload = deadbeef 00000002
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Video Routing System
|
||||
|
||||
`transport_cli` exercises the `transport` module, which provides the framed TCP stream used by all node-to-node communication. The frame header (message type + payload length) and single-write send are the building blocks for the protocol layer.
|
||||
|
||||
See also: [`protocol_cli.md`](protocol_cli.md) for typed message-level testing.
|
||||
69
docs/cli/v4l2_view_cli.md
Normal file
69
docs/cli/v4l2_view_cli.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# v4l2_view_cli
|
||||
|
||||
A live camera viewer. Opens a V4L2 capture device, selects the best available format, and displays the video stream in an X11 window with a real-time FPS and format overlay. Bypasses the node system entirely — useful for verifying a camera works before wiring it into a node.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make cli
|
||||
```
|
||||
|
||||
The binary is placed in `build/cli/`.
|
||||
|
||||
Requires GLFW, OpenGL, libjpeg-turbo, and a V4L2-capable kernel.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
./v4l2_view_cli [--device PATH]
|
||||
[--width N --height N]
|
||||
[--format mjpeg|yuyv]
|
||||
[--scale stretch|fit|fill|1:1]
|
||||
[--anchor center|topleft]
|
||||
[--x N] [--y N]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|---|---|---|
|
||||
| `--device PATH` | `/dev/video0` | V4L2 device to open |
|
||||
| `--width N` | auto | Capture width; if omitted, selects highest-FPS mode |
|
||||
| `--height N` | auto | Capture height |
|
||||
| `--format` | auto | Prefer `mjpeg` or `yuyv`; auto selects best available |
|
||||
| `--scale` | `fit` | Frame scaling in window |
|
||||
| `--anchor` | `center` | Frame alignment in window |
|
||||
| `--x N` | `0` | Window X position |
|
||||
| `--y N` | `0` | Window Y position |
|
||||
|
||||
Press **Q** or **Escape** to close the window.
|
||||
|
||||
### Auto format selection
|
||||
|
||||
Without `--width`/`--height`, the tool selects the format with the highest frame rate, and within that the largest resolution. This is the same logic the node's ingest module uses.
|
||||
|
||||
### Overlay
|
||||
|
||||
Every 0.5 seconds the overlay updates with:
|
||||
|
||||
```
|
||||
MJPEG 1280x720 @ 30.0 fps
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
```sh
|
||||
./v4l2_view_cli --device /dev/video0 --scale fit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Video Routing System
|
||||
|
||||
`v4l2_view_cli` is a standalone sanity-check tool. It exercises the same V4L2 format enumeration, mmap capture, MJPEG decode, and xorg rendering path that the node's ingest + display pipeline uses — but without any transport or protocol overhead.
|
||||
|
||||
See also: [`stream_send_cli.md`](stream_send_cli.md) to capture and send over the network; [`xorg_cli.md`](xorg_cli.md) for static test patterns.
|
||||
73
docs/cli/xorg_cli.md
Normal file
73
docs/cli/xorg_cli.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# xorg_cli
|
||||
|
||||
A development tool for testing the xorg viewer sink. Opens an X11 window using GLFW/OpenGL, renders a test pattern at the chosen scale and anchor, and displays a text overlay showing the current date. The window stays open until the user presses Q, Escape, or closes it.
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
make cli
|
||||
```
|
||||
|
||||
The binary is placed in `build/cli/`.
|
||||
|
||||
Requires the GLFW, OpenGL, and libjpeg-turbo libraries. If compiled without `HAVE_GLFW`, the binary will print an error and exit.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
./xorg_cli [--pattern bars|ramp|grid]
|
||||
[--width N] [--height N]
|
||||
[--format yuv420|bgra]
|
||||
[--scale stretch|fit|fill|1:1]
|
||||
[--anchor center|topleft]
|
||||
[--x N] [--y N]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|---|---|---|
|
||||
| `--pattern` | `bars` | Test pattern to render |
|
||||
| `--width N` | `1280` | Window width in pixels |
|
||||
| `--height N` | `720` | Window height in pixels |
|
||||
| `--format` | `yuv420` | Frame pixel format |
|
||||
| `--scale` | `stretch` | How the frame fills the window |
|
||||
| `--anchor` | `center` | Frame alignment within the window |
|
||||
| `--x N` | `0` | Window X position on screen |
|
||||
| `--y N` | `0` | Window Y position on screen |
|
||||
|
||||
### Scale modes
|
||||
|
||||
| Mode | Description |
|
||||
|---|---|
|
||||
| `stretch` | Fill the window, ignoring aspect ratio |
|
||||
| `fit` | Largest rect that fits, preserving aspect ratio (black bars) |
|
||||
| `fill` | Smallest rect that covers, preserving aspect ratio (crops edges) |
|
||||
| `1:1` | Native pixel size, no scaling |
|
||||
|
||||
### Anchor modes
|
||||
|
||||
Anchor applies when the frame does not fill the window (fit, fill, 1:1 modes):
|
||||
|
||||
| Mode | Description |
|
||||
|---|---|
|
||||
| `center` | Centre the frame in the window |
|
||||
| `topleft` | Align frame to the top-left corner |
|
||||
|
||||
### Example
|
||||
|
||||
```sh
|
||||
./xorg_cli --pattern grid --scale fit --anchor center --width 1920 --height 1080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relationship to the Video Routing System
|
||||
|
||||
`xorg_cli` exercises the `xorg` module used by the video node for its display sink role. The same viewer, scale/anchor logic, and text overlay system are used by `stream_recv_cli` and the node's `START_DISPLAY` command.
|
||||
|
||||
See also: [`v4l2_view_cli.md`](v4l2_view_cli.md) for live camera feed display; [`stream_recv_cli.md`](stream_recv_cli.md) for network stream display.
|
||||
@@ -149,9 +149,11 @@ struct Proto_Standalone_Device_Info {
|
||||
|
||||
/*
|
||||
* An active display window (video sink role).
|
||||
* device_id is the flat device index (follows all V4L2 devices).
|
||||
* stream_id is the stream being displayed; win_* are current geometry.
|
||||
*/
|
||||
struct Proto_Display_Device_Info {
|
||||
uint16_t device_id;
|
||||
uint16_t stream_id;
|
||||
int16_t win_x, win_y;
|
||||
uint16_t win_w, win_h;
|
||||
@@ -159,6 +161,15 @@ struct Proto_Display_Device_Info {
|
||||
uint8_t anchor;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Display device pseudo-control IDs — used in ENUM_CONTROLS / GET_CONTROL /
|
||||
* SET_CONTROL for display device indices returned by ENUM_DEVICES.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define PROTO_DISPLAY_CTRL_SCALE 0x00D00001u /* int 0-3: stretch/fit/fill/1:1 */
|
||||
#define PROTO_DISPLAY_CTRL_ANCHOR 0x00D00002u /* int 0-1: center/topleft */
|
||||
#define PROTO_DISPLAY_CTRL_NO_SIGNAL_FPS 0x00D00003u /* int 1-60: no-signal animation fps */
|
||||
|
||||
struct Proto_Monitor_Info {
|
||||
int32_t x, y;
|
||||
uint32_t width, height;
|
||||
@@ -495,6 +506,7 @@ struct App_Error proto_read_enum_devices_response(
|
||||
const char *name, uint8_t name_len,
|
||||
void *userdata),
|
||||
void (*on_display)(
|
||||
uint16_t device_id,
|
||||
uint16_t stream_id,
|
||||
int16_t win_x, int16_t win_y,
|
||||
uint16_t win_w, uint16_t win_h,
|
||||
|
||||
@@ -414,6 +414,7 @@ struct App_Error proto_write_enum_devices_response(struct Transport_Conn *conn,
|
||||
e = wbuf_u16(&b, display_count); if (!APP_IS_OK(e)) { goto fail; }
|
||||
for (uint16_t i = 0; i < display_count; i++) {
|
||||
const struct Proto_Display_Device_Info *d = &displays[i];
|
||||
e = wbuf_u16(&b, d->device_id); if (!APP_IS_OK(e)) { goto fail; }
|
||||
e = wbuf_u16(&b, d->stream_id); if (!APP_IS_OK(e)) { goto fail; }
|
||||
e = wbuf_i16(&b, d->win_x); if (!APP_IS_OK(e)) { goto fail; }
|
||||
e = wbuf_i16(&b, d->win_y); if (!APP_IS_OK(e)) { goto fail; }
|
||||
@@ -791,6 +792,7 @@ struct App_Error proto_read_enum_devices_response(
|
||||
uint16_t display_count = cur_u16(&c);
|
||||
CUR_CHECK(c);
|
||||
for (uint16_t i = 0; i < display_count; i++) {
|
||||
uint16_t device_id = cur_u16(&c);
|
||||
uint16_t stream_id = cur_u16(&c);
|
||||
int16_t win_x = (int16_t)cur_u16(&c);
|
||||
int16_t win_y = (int16_t)cur_u16(&c);
|
||||
@@ -800,7 +802,7 @@ struct App_Error proto_read_enum_devices_response(
|
||||
uint8_t anchor = cur_u8(&c);
|
||||
CUR_CHECK(c);
|
||||
if (on_display) {
|
||||
on_display(stream_id, win_x, win_y,
|
||||
on_display(device_id, stream_id, win_x, win_y,
|
||||
win_w, win_h, scale, anchor, userdata);
|
||||
}
|
||||
}
|
||||
@@ -830,6 +832,8 @@ struct App_Error proto_read_enum_controls_response(
|
||||
|
||||
header_out->request_id = cur_u16(&c);
|
||||
header_out->status = cur_u16(&c);
|
||||
CUR_CHECK(c);
|
||||
if (header_out->status != PROTO_STATUS_OK) { return APP_OK; }
|
||||
uint16_t count = cur_u16(&c);
|
||||
CUR_CHECK(c);
|
||||
|
||||
|
||||
127
src/node/main.c
127
src/node/main.c
@@ -510,6 +510,14 @@ static void display_loop_tick(struct Node *node)
|
||||
|
||||
if (d->current_state != DISP_OPEN || !d->viewer) { continue; }
|
||||
|
||||
/* Sync scale/anchor — may be updated live via SET_CONTROL */
|
||||
pthread_mutex_lock(&d->mutex);
|
||||
Xorg_Scale cur_scale = d->scale;
|
||||
Xorg_Anchor cur_anchor = d->anchor;
|
||||
pthread_mutex_unlock(&d->mutex);
|
||||
xorg_viewer_set_scale(d->viewer, cur_scale);
|
||||
xorg_viewer_set_anchor(d->viewer, cur_anchor);
|
||||
|
||||
/* Deliver pending frame (no lock held during decode/upload) */
|
||||
pthread_mutex_lock(&d->mutex);
|
||||
uint8_t *fdata = NULL;
|
||||
@@ -787,6 +795,43 @@ static const char *resolve_device_path(struct Node *node, int idx)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Count all V4L2 device indices (media vnodes + standalone). */
|
||||
static int count_v4l2_devices(struct Node *node)
|
||||
{
|
||||
int n = 0;
|
||||
for (int i = 0; i < node->devices.media_count; i++) {
|
||||
n += node->devices.media[i].vnode_count;
|
||||
}
|
||||
for (int i = 0; i < node->devices.vnode_count; i++) {
|
||||
if (!node->devices.vnodes[i].claimed) { n++; }
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
* If idx falls in the display device range (>= count_v4l2_devices), return
|
||||
* the corresponding active Display_Slot, or NULL if out of range.
|
||||
* The slot may be read without holding its mutex for the non-mutex fields;
|
||||
* callers should lock the mutex for fields that require it.
|
||||
*/
|
||||
static struct Display_Slot *find_display_by_device_idx(struct Node *node, int idx)
|
||||
{
|
||||
int base = count_v4l2_devices(node);
|
||||
if (idx < base) { return NULL; }
|
||||
int disp_idx = idx - base;
|
||||
int found = 0;
|
||||
for (int i = 0; i < MAX_DISPLAYS; i++) {
|
||||
struct Display_Slot *d = &node->displays[i];
|
||||
pthread_mutex_lock(&d->mutex);
|
||||
int active = d->allocated && d->wanted_state == DISP_OPEN;
|
||||
pthread_mutex_unlock(&d->mutex);
|
||||
if (!active) { continue; }
|
||||
if (found == disp_idx) { return d; }
|
||||
found++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Request handlers
|
||||
* ------------------------------------------------------------------------- */
|
||||
@@ -829,6 +874,8 @@ static void handle_enum_devices(struct Node *node,
|
||||
standalone_count++;
|
||||
}
|
||||
|
||||
int total_v4l2 = vnode_offset + standalone_count;
|
||||
|
||||
struct Proto_Display_Device_Info disp_infos[MAX_DISPLAYS];
|
||||
int disp_count = 0;
|
||||
for (int i = 0; i < MAX_DISPLAYS; i++) {
|
||||
@@ -836,6 +883,7 @@ static void handle_enum_devices(struct Node *node,
|
||||
pthread_mutex_lock(&d->mutex);
|
||||
int snap = d->allocated && d->wanted_state == DISP_OPEN;
|
||||
struct Proto_Display_Device_Info info = {
|
||||
.device_id = (uint16_t)(total_v4l2 + disp_count),
|
||||
.stream_id = d->stream_id,
|
||||
.win_x = (int16_t)d->win_x,
|
||||
.win_y = (int16_t)d->win_y,
|
||||
@@ -869,7 +917,33 @@ static void handle_enum_controls(struct Node *node,
|
||||
}
|
||||
const char *path = resolve_device_path(node, (int)req.device_index);
|
||||
if (!path) {
|
||||
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
|
||||
struct Display_Slot *disp = find_display_by_device_idx(node, (int)req.device_index);
|
||||
if (!disp) {
|
||||
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(&disp->mutex);
|
||||
int scale = (int)disp->scale;
|
||||
int anchor = (int)disp->anchor;
|
||||
int no_signal_fps = disp->no_signal_fps > 0 ? disp->no_signal_fps : 15;
|
||||
pthread_mutex_unlock(&disp->mutex);
|
||||
struct Proto_Control_Info ctrls[] = {
|
||||
{ .id = PROTO_DISPLAY_CTRL_SCALE,
|
||||
.type = 1, .name = "Scale",
|
||||
.min = 0, .max = 3, .step = 1, .default_val = 1,
|
||||
.current_val = scale },
|
||||
{ .id = PROTO_DISPLAY_CTRL_ANCHOR,
|
||||
.type = 1, .name = "Anchor",
|
||||
.min = 0, .max = 1, .step = 1, .default_val = 0,
|
||||
.current_val = anchor },
|
||||
{ .id = PROTO_DISPLAY_CTRL_NO_SIGNAL_FPS,
|
||||
.type = 1, .name = "No-signal FPS",
|
||||
.min = 1, .max = 60, .step = 1, .default_val = 15,
|
||||
.current_val = no_signal_fps },
|
||||
};
|
||||
e = proto_write_enum_controls_response(conn,
|
||||
req.request_id, PROTO_STATUS_OK, ctrls, 3);
|
||||
if (!APP_IS_OK(e)) { app_error_print(&e); }
|
||||
return;
|
||||
}
|
||||
struct V4l2_Ctrl_Handle *handle;
|
||||
@@ -898,7 +972,27 @@ static void handle_get_control(struct Node *node,
|
||||
}
|
||||
const char *path = resolve_device_path(node, (int)req.device_index);
|
||||
if (!path) {
|
||||
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
|
||||
struct Display_Slot *disp = find_display_by_device_idx(node, (int)req.device_index);
|
||||
if (!disp) {
|
||||
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(&disp->mutex);
|
||||
int32_t value = 0;
|
||||
int found = 1;
|
||||
switch (req.control_id) {
|
||||
case PROTO_DISPLAY_CTRL_SCALE: value = (int32_t)disp->scale; break;
|
||||
case PROTO_DISPLAY_CTRL_ANCHOR: value = (int32_t)disp->anchor; break;
|
||||
case PROTO_DISPLAY_CTRL_NO_SIGNAL_FPS: value = disp->no_signal_fps > 0 ? disp->no_signal_fps : 15; break;
|
||||
default: found = 0; break;
|
||||
}
|
||||
pthread_mutex_unlock(&disp->mutex);
|
||||
if (!found) {
|
||||
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
|
||||
return;
|
||||
}
|
||||
e = proto_write_get_control_response(conn, req.request_id, PROTO_STATUS_OK, value);
|
||||
if (!APP_IS_OK(e)) { app_error_print(&e); }
|
||||
return;
|
||||
}
|
||||
struct V4l2_Ctrl_Handle *handle;
|
||||
@@ -930,7 +1024,34 @@ static void handle_set_control(struct Node *node,
|
||||
}
|
||||
const char *path = resolve_device_path(node, (int)req.device_index);
|
||||
if (!path) {
|
||||
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
|
||||
struct Display_Slot *disp = find_display_by_device_idx(node, (int)req.device_index);
|
||||
if (!disp) {
|
||||
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(&disp->mutex);
|
||||
int found = 1;
|
||||
switch (req.control_id) {
|
||||
case PROTO_DISPLAY_CTRL_SCALE:
|
||||
if (req.value >= 0 && req.value <= 3) {
|
||||
disp->scale = (Xorg_Scale)req.value;
|
||||
}
|
||||
break;
|
||||
case PROTO_DISPLAY_CTRL_ANCHOR:
|
||||
if (req.value >= 0 && req.value <= 1) {
|
||||
disp->anchor = (Xorg_Anchor)req.value;
|
||||
}
|
||||
break;
|
||||
case PROTO_DISPLAY_CTRL_NO_SIGNAL_FPS:
|
||||
if (req.value >= 1 && req.value <= 60) {
|
||||
disp->no_signal_fps = (int)req.value;
|
||||
}
|
||||
break;
|
||||
default: found = 0; break;
|
||||
}
|
||||
pthread_mutex_unlock(&disp->mutex);
|
||||
uint16_t status = found ? PROTO_STATUS_OK : PROTO_STATUS_NOT_FOUND;
|
||||
proto_write_control_response(conn, req.request_id, status, NULL, 0);
|
||||
return;
|
||||
}
|
||||
struct V4l2_Ctrl_Handle *handle;
|
||||
|
||||
Reference in New Issue
Block a user