Files
lgtv_audio_cap/docs/configuration.md
T
Rene KievitsandClaude Opus 5 e9f6c87d27 Fix hyperhdrAdjust: use "brightness", not "scaleOutput"
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>
2026-08-26 14:59:51 +02:00

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 '{}'