Tested against a real HyperHDR instance rather than trusting the schema further. scaleOutput (from the current schema-adjustment.json) produced no visible change and no trace in serverinfo's echoed-back adjustment state. brightness (0-100, absent from that same schema) round-tripped correctly through serverinfo and visibly dimmed real LEDs -- confirmed live, with piccap as the sole colour source and only this sink's JSON-RPC calls changing anything. A schema documents what a command accepts; it does not guarantee what a given build actually does with each field, and this is a mismatch between HyperHDR's current dev-branch schema and whatever build the target instance is actually running. Switched the sink to brightness (int 0-100) throughout: wire format, config defaults (minBrightness/maxBrightness, 20-100), status fields, and the reset sent on close. Renamed the frontend fields and mock to match. Cross-compiles clean. Bumped to 1.0.4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8.8 KiB
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
/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 dlopened 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 with
a brightness value (0-100) — a post-processing stage that applies
regardless of which priority is currently active — so an existing grabber or
capture app keeps deciding colour and only overall brightness reacts to
sound. Confirmed against a real HyperHDR instance by watching brightness
round-trip through serverinfo and the LEDs visibly respond; HyperHDR's own
schema-adjustment.json also documents a scaleOutput float (0.0-2.0), but
that field produced no effect on the same instance — what a schema declares
and what a given build actually acts on are not always the same thing.
| 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) |
minBrightness |
20 |
brightness (0-100) during silence |
maxBrightness |
100 |
brightness (0-100) at full level; HyperHDR does not go above 100 |
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 S16LE — 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 /.
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
{
"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/minBrightness/maxBrightness/lastError
for the brightness sink.
Diagnostics
{
"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
# 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 '{}'