Every existing HyperHDR route replaces whatever else is on the LEDs: routes 1/2 hand HyperHDR's own audio effect a device to read, route 3 sends a synthetic spectrum image, and both take over via HyperHDR's priority system. For a setup that already has a real colour source (a screen grabber, a USB capture card) feeding an ambilight-style LED run, none of that is what's wanted -- the colour should stay put and only brightness should react. Read HyperHDR's own source (sources/api/JSONRPC_schema/schema-adjustment.json) rather than guess: "adjustment" is a post-processing command with a scaleOutput parameter (0-2.0) that applies regardless of which priority is currently active. Confirmed the wire format too -- sources/jsonserver/JsonClientConnection.cpp frames it as plain newline-delimited JSON over TCP (default port 19444), nothing like the length-prefixed Flatbuffers protocol the visualiser sink speaks, and with no handshake or registration needed before the first write. sink_hyperhdr_adjust.c sends only that: no image, no priority, so it never competes with an existing grabber. Non-blocking connect with the same poll()+SO_ERROR pattern net/hyperion.c already uses, reconnects every 5s, rate-limited to 20 Hz (a HyperHDR command every audio block would be pointless flooding), and resets scaleOutput to 1.0 on close rather than leaving the LEDs stuck at whatever it last sent. Cross- compiles clean under -Wall -Wextra on the real webOS toolchain. Wired through the same path every other sink follows: registered in sink.c/sink.h, defaults in config.c, fields in frontend/js/app.js (SINK_FIELDS/SINK_HELP), mock.js and ui_smoke.js updated for the new sink card. Bumped to 1.0.3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
239 lines
8.4 KiB
Markdown
239 lines
8.4 KiB
Markdown
# Configuration and the Luna API
|
|
|
|
Everything the UI does goes through the service's Luna API, so anything the UI
|
|
can do you can also do from an ssh session with `luna-send`.
|
|
|
|
## Where the settings live
|
|
|
|
```text
|
|
/var/lib/webosbrew/audiocap/config.json when the service can write there
|
|
/tmp/audiocap-config.json fallback, lost on reboot
|
|
$AUDIOCAP_CONFIG overrides both
|
|
```
|
|
|
|
The service falls back to `/tmp` when it is not running as root, and the UI says
|
|
so under *System → Settings file*. Settings written there survive the session
|
|
but not the TV.
|
|
|
|
Edits are merged, not replaced: sending `{"hyperhdr":{"port":5005}}` changes the
|
|
port and leaves everything else alone. Writes are atomic — a temporary file,
|
|
`fsync`, `rename` — so a power cut during a save cannot leave a truncated
|
|
config behind.
|
|
|
|
## Settings
|
|
|
|
### Top level
|
|
|
|
| Key | Default | Meaning |
|
|
| --- | --- | --- |
|
|
| `autoStart` | `false` | start capturing as soon as the service starts |
|
|
| `logLevel` | `"info"` | `error`, `warn`, `info` or `debug` |
|
|
| `sinks` | `["hyperhdr"]` | which outputs to open |
|
|
|
|
`autoStart` only matters if something starts the service at boot — that is what
|
|
the *Start on boot* toggle installs, a script in `/var/lib/webosbrew/init.d/`
|
|
that pokes the service so the Homebrew Channel launches it.
|
|
|
|
### `capture`
|
|
|
|
| Key | Default | Meaning |
|
|
| --- | --- | --- |
|
|
| `backend` | `"auto"` | `auto`, `pulse`, `alsa`, `exec` or `tone` |
|
|
| `device` | `""` | PulseAudio source, or an ALSA PCM like `hw:0,0` |
|
|
| `server` | `""` | PulseAudio server address; blank autodetects |
|
|
| `command` | `""` | for the `exec` backend |
|
|
| `rate` | `48000` | 44100 or 48000 |
|
|
| `channels` | `2` | 1 or 2 |
|
|
|
|
`auto` tries PulseAudio, then ALSA. It never tries `exec` — that one needs a
|
|
command only you can supply.
|
|
|
|
The backends:
|
|
|
|
| id | Name | Notes |
|
|
| --- | --- | --- |
|
|
| `pulse` | PulseAudio monitor | records the monitor source of the active sink; `libpulse.so.0` is `dlopen`ed at run time, so a TV without it simply reports the backend unavailable |
|
|
| `alsa` | ALSA PCM | same arrangement with `libasound.so.2` |
|
|
| `exec` | External command | runs a command and reads raw interleaved S16LE from its stdout, e.g. `arecord -D hw:0,0 -f S16_LE -r 48000 -c 2 -t raw` |
|
|
| `tone` | Test tone | a sweep; proves the network path without touching the TV's audio at all |
|
|
|
|
### `dsp`
|
|
|
|
| Key | Default | Meaning |
|
|
| --- | --- | --- |
|
|
| `attack` | `0.6` | seconds for the reported level to catch a rise |
|
|
| `release` | `0.12` | seconds for it to fall away |
|
|
|
|
These shape the numbers in the status document and the on-TV visualiser. They do
|
|
not touch the audio sent to any sink.
|
|
|
|
### `hyperhdr` — RTP/L16 audio
|
|
|
|
| Key | Default | Meaning |
|
|
| --- | --- | --- |
|
|
| `host` | `""` | the receiving machine |
|
|
| `port` | `5004` | UDP port |
|
|
| `multicast` | `false` | send to a group instead of a host |
|
|
| `multicastTtl` | `4` | hop limit when multicasting |
|
|
| `sapAnnounce` | `true` | announce over SAP so PulseAudio can find the stream |
|
|
|
|
Payload type 96, 16-bit big-endian PCM, packets kept under 1400 bytes of
|
|
payload so nothing fragments on a normal Ethernet MTU.
|
|
|
|
### `hyperhdrViz` — Flatbuffers images
|
|
|
|
| Key | Default | Meaning |
|
|
| --- | --- | --- |
|
|
| `host` | `""` | HyperHDR's address |
|
|
| `port` | `19400` | Flatbuffers port |
|
|
| `priority` | `150` | HyperHDR priority; lower wins |
|
|
| `width` / `height` | `64` / `36` | image size |
|
|
| `fps` | `30` | frames per second |
|
|
| `mode` | `"spectrum"` | `spectrum`, `level` or `pulse` |
|
|
| `saturation` | `1.0` | colour intensity |
|
|
| `minBrightness` | `0.02` | floor so the lights never go fully black |
|
|
|
|
### `hyperhdrAdjust` — brightness only, via HyperHDR's JSON-RPC
|
|
|
|
Sends no image at all. Instead it calls HyperHDR's `adjustment` command —
|
|
a post-processing stage that scales output brightness regardless of which
|
|
priority is currently active — so an existing grabber or effect keeps
|
|
deciding colour and only overall brightness reacts to sound.
|
|
|
|
| Key | Default | Meaning |
|
|
| --- | --- | --- |
|
|
| `host` | `""` | HyperHDR's address |
|
|
| `port` | `19444` | HyperHDR's classic JSON-RPC port (not 8090, not 19400) |
|
|
| `level` | `"rms"` | `rms` (steadier) or `peak` (punchier) |
|
|
| `minScale` | `0.3` | brightness during silence; `1.0` is HyperHDR's normal level |
|
|
| `maxScale` | `1.3` | brightness at full level; HyperHDR accepts up to `2.0` |
|
|
|
|
### `udp`, `tcp`, `http`
|
|
|
|
| Key | Default | Meaning |
|
|
| --- | --- | --- |
|
|
| `udp.host` | `""` | destination; a host, a multicast group, or `255.255.255.255` |
|
|
| `udp.port` | `4010` | |
|
|
| `udp.multicastTtl` | `4` | |
|
|
| `tcp.port` | `4011` | the TV listens on this |
|
|
| `tcp.maxClients` | `4` | |
|
|
| `http.port` | `4012` | `/audio.wav` and `/audio.raw` |
|
|
| `http.maxClients` | `4` | |
|
|
|
|
All three carry interleaved S16**LE** — little-endian, unlike the RTP sink,
|
|
because that is what everything reading a raw pipe expects.
|
|
|
|
---
|
|
|
|
## The Luna API
|
|
|
|
Service name `org.webosbrew.audiocap.service`, all methods on `/`.
|
|
|
|
```sh
|
|
luna-send -n 1 -f luna://org.webosbrew.audiocap.service/getStatus '{}'
|
|
```
|
|
|
|
| Method | Payload | Reply |
|
|
| --- | --- | --- |
|
|
| `start` | optional settings patch, applied and saved first | the status document |
|
|
| `stop` | `{}` | the status document |
|
|
| `getStatus` | `{"subscribe":true}` for a feed every 100 ms | the status document |
|
|
| `isRunning` | `{}` | `{isRunning, state}` |
|
|
| `getConfig` | `{}` | `{path, persistent, settings}` |
|
|
| `setConfig` | a patch, bare or under `settings` | `{saved, restartRequired, settings}` |
|
|
| `resetConfig` | `{}` | `{saved, settings}` |
|
|
| `listBackends` | `{}` | `{backends:[{id,name,description,available}]}` |
|
|
| `listSinks` | `{}` | `{sinks:[{id,name,description}]}` |
|
|
| `getDiagnostics` | `{}` | `{backends, system}` — see below |
|
|
| `getLogs` | `{"clear":true}` optional | `{logs}` |
|
|
| `quit` | `{}` | ends the process; the next call starts a new one |
|
|
|
|
`setConfig` reports `restartRequired: true` when the capture is running, because
|
|
most settings are read when a run starts.
|
|
|
|
### The status document
|
|
|
|
```json
|
|
{
|
|
"returnValue": true,
|
|
"state": "running",
|
|
"running": true,
|
|
"error": null,
|
|
"capture": {
|
|
"backend": "pulse",
|
|
"backendName": "PulseAudio monitor",
|
|
"device": "…monitor",
|
|
"rate": 48000,
|
|
"channels": 2,
|
|
"frames": 4915200,
|
|
"blocks": 9600,
|
|
"timeouts": 0,
|
|
"uptimeMs": 102400
|
|
},
|
|
"levels": {
|
|
"peak": 0.42, "rms": 0.19,
|
|
"peakDb": -7.5, "rmsDb": -14.4,
|
|
"clipping": false,
|
|
"bands": [0.0, "…16 values…"]
|
|
},
|
|
"sinks": [
|
|
{ "id": "hyperhdr", "ok": true, "name": "HyperHDR audio (RTP)",
|
|
"error": null, "target": "192.168.1.50", "port": 5004,
|
|
"packetsSent": 12345, "bytesSent": 4321000, "sendErrors": 0 }
|
|
],
|
|
"configPath": "/var/lib/webosbrew/audiocap/config.json",
|
|
"configPersistent": true
|
|
}
|
|
```
|
|
|
|
`state` is `stopped`, `starting`, `running` or `error`. A sink that failed to
|
|
open reports `ok: false` and an `error`, and the run continues without it — one
|
|
broken output does not take the others down.
|
|
|
|
Each sink adds its own fields. `packetsSent`/`bytesSent`/`sendErrors` for the
|
|
datagram sinks, `clients`/`droppedBytes` for the stream servers,
|
|
`connected`/`registered`/`framesSent`/`connectFailures`/`lastError` for the
|
|
visualiser, `connected`/`updatesSent`/`minScale`/`maxScale`/`lastError` for
|
|
the brightness sink.
|
|
|
|
### Diagnostics
|
|
|
|
```json
|
|
{
|
|
"backends": [{ "id": "pulse", "name": "…", "available": false }],
|
|
"system": {
|
|
"root": true,
|
|
"uid": 0,
|
|
"libraries": { "libpulse.so.0": "/usr/lib/libpulse.so.0",
|
|
"libasound.so.2": null },
|
|
"binaries": { "parec": false, "pactl": true },
|
|
"pulseSockets": ["/var/run/pulse/native"],
|
|
"pactlSources": "…",
|
|
"alsaCards": ["0 [Loopback]: …"],
|
|
"alsaCapturePcms": ["00-01: …"]
|
|
}
|
|
}
|
|
```
|
|
|
|
This is the fastest way to find out why a backend reports itself unavailable.
|
|
`tools/tv-probe.sh` collects the same picture from a shell, plus a few things
|
|
the service does not look at.
|
|
|
|
## Doing it from a shell
|
|
|
|
```sh
|
|
# change settings and start in one call (the patch is saved, like setConfig)
|
|
luna-send -n 1 -f luna://org.webosbrew.audiocap.service/start \
|
|
'{"capture":{"backend":"tone"},"sinks":["http"]}'
|
|
|
|
# point the RTP sink somewhere else and keep it
|
|
luna-send -n 1 -f luna://org.webosbrew.audiocap.service/setConfig \
|
|
'{"settings":{"hyperhdr":{"host":"192.168.1.50"}}}'
|
|
|
|
# watch the level
|
|
luna-send -i -f luna://org.webosbrew.audiocap.service/getStatus '{"subscribe":true}'
|
|
|
|
# what went wrong
|
|
luna-send -n 1 -f luna://org.webosbrew.audiocap.service/getLogs '{}'
|
|
```
|