Files
lgtv_audio_cap/README.md
T
Rene KievitsandClaude Opus 5 aae5a33283 Add a brightness-only sink: keep the grabber's colour, pulse with sound
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>
2026-08-26 14:36:31 +02:00

138 lines
6.6 KiB
Markdown

# LG TV Audio Cap
Captures the audio playing on an LG webOS 5 or 6 TV and streams it off the set.
The reason it exists is HyperHDR: ambient lighting that reacts to what the TV is
actually playing, without a microphone in the room or an audio splitter behind
the telly.
It installs through the [Homebrew Channel](https://github.com/webosbrew/webos-homebrew-channel)
and runs as a native background service with a remote-friendly UI in front of
it.
```
┌────────────────────── LG webOS TV ──────────────────────┐
│ PulseAudio / ALSA / a command │
│ │ │
│ capture ─► level + 16-band analysis │
│ │ │
│ ├─► RTP/L16 ──────────────► HyperHDR host │ ← the main path
│ ├─► Flatbuffers images ───► HyperHDR │
│ ├─► raw PCM over UDP │
│ ├─► raw PCM over TCP │
│ └─► WAV over HTTP │
└─────────────────────────────────────────────────────────┘
```
## Why it works this way
HyperHDR has **no network audio input**. Its sound-reactive effects read a
*local* capture device. So the main path here does not try to talk to HyperHDR
at all: it sends the TV's audio to the HyperHDR machine as RTP/L16, and a small
receiver there turns it into a normal sound device that HyperHDR can select.
That is [`host/lgtv-audiocap-receiver.py`](host/lgtv-audiocap-receiver.py), and
[`host/install-loopback.sh`](host/install-loopback.sh) sets up the loopback for
it.
If you would rather not run anything on the HyperHDR machine, there is a second
path: the TV does the frequency analysis itself and sends finished images to
HyperHDR's Flatbuffers port. Fewer moving parts, but the lights react to the
TV's idea of the spectrum rather than to real audio.
See [docs/hyperhdr.md](docs/hyperhdr.md) for both, step by step.
## Requirements
- An LG TV on webOS 5 or 6, rooted, with the Homebrew Channel installed.
- Root for the service. The TV's audio devices are not readable otherwise; the
app has a **Grant root access** button that calls the Homebrew Channel's
`elevate-service` for you.
- For the main HyperHDR path: a Linux machine running HyperHDR with either
`snd-aloop` or PulseAudio/PipeWire available.
## Installing
This app is not in the official Homebrew Channel repository (that needs a
review submission — see [docs/development.md](docs/development.md)). Two ways
to get it onto the TV instead, neither needing SSH or Developer Mode:
**Add it as a custom repository (recommended).** The Homebrew Channel can
browse-and-install from any repository URL you give it, not just the official
one. Build the ipk, publish it plus a generated repo index, then add that URL
on the TV:
```sh
npm install # ares-cli, only needed for step 2
./tools/docker-build.sh # cross-compile; Linux: ./tools/build.sh native
./tools/build.sh package # -> out/*.ipk
python3 tools/make-manifest.py --base-url <where you'll host the release assets>
# -> out/repo.json, out/manifest.json
```
Attach the ipk, `frontend/assets/icon.png` and `out/repo.json` to a release at
that URL, then on the TV: **Homebrew Channel → ⚙ → Add repository** → paste the
`repo.json` URL → back to Browse → *Audio Cap* → Install. Full walkthrough,
including why the URLs have to match exactly, in
[docs/development.md](docs/development.md#publishing-to-the-homebrew-channel).
**Install the ipk directly**, if you do have ares-cli talking to the TV (e.g.
LG Developer Mode's ssh on port 9922, or a root SSH server you've enabled):
```sh
ares-install --device tv out/org.webosbrew.audiocap_1.0.0_all.ipk
```
## First run
1. Launch **Audio Cap** on the TV.
2. **System → Root access**: press *Grant root access* if it says the service is
not root. It restarts itself.
3. **Capture → Backend**: leave it on *Automatic* to begin with. If nothing is
captured, run [`tools/tv-probe.sh`](tools/tv-probe.sh) on the TV to see what
your firmware actually offers, then pick a backend by hand.
4. **Outputs → HyperHDR audio (RTP/L16)**: turn it on and enter the address of
the machine running HyperHDR.
5. On that machine: `sudo ./host/install-loopback.sh --install-service`, then
point HyperHDR's sound capture at the device it prints.
6. Press **Start** on the TV. The level meter should move.
Nothing captured, no idea why? [docs/troubleshooting.md](docs/troubleshooting.md).
## The other outputs
Each can run at the same time as the others.
| Output | What it is | Use it for |
| --- | --- | --- |
| **HyperHDR audio** | RTP/L16, port 5004 | the main path; also readable by PulseAudio's `module-rtp-recv` with no custom software |
| **HyperHDR visualiser** | Flatbuffers images, port 19400 | HyperHDR with nothing installed on the host |
| **HyperHDR brightness** | JSON-RPC, port 19444 | an existing grabber/ambilight setup — keeps its colour, only pulses brightness |
| **Raw PCM over UDP** | S16LE datagrams, port 4010 | your own scripts; lowest latency |
| **Raw PCM over TCP** | S16LE stream, port 4011 | anything that would rather connect than listen |
| **HTTP WAV** | `http://tv:4012/audio.wav` | opening the TV's audio in VLC |
## Layout
```
native/ the webOS service: capture, DSP, sinks, Luna API (C)
frontend/ the on-TV app (plain HTML/CSS/JS, no framework)
servicefiles/ services.json, package.json and the boot script
host/ the receiver and loopback setup for the HyperHDR machine
docker/ the receiver, packaged as a container (e.g. for Unraid)
unraid/ the plugin for the one part a container can't do: the
ALSA loopback kernel module, persisted across reboots
tools/ build, packaging, asset generation, on-TV probe
test/ host-side tests: wire formats, the capture pipeline, the UI
docs/ the longer explanations
```
## Documentation
- [docs/hyperhdr.md](docs/hyperhdr.md) — connecting it to HyperHDR, both ways
- [docs/configuration.md](docs/configuration.md) — every setting, and the Luna API
- [docs/development.md](docs/development.md) — building, testing, packaging, publishing
- [docs/troubleshooting.md](docs/troubleshooting.md) — when it does not work
## License
MIT.