From e9f6c87d2712ae87a4f7008e97f1badc440f9ac5 Mon Sep 17 00:00:00 2001 From: Rene Kievits Date: Wed, 26 Aug 2026 14:59:51 +0200 Subject: [PATCH] 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 --- docs/configuration.md | 21 +++++---- docs/hyperhdr.md | 34 ++++++++++----- frontend/appinfo.json | 2 +- frontend/js/app.js | 8 ++-- frontend/js/mock.js | 2 +- native/src/config.c | 4 +- native/src/sinks/sink_hyperhdr_adjust.c | 58 +++++++++++++++---------- package.json | 2 +- servicefiles/package.json | 2 +- 9 files changed, 80 insertions(+), 53 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 8f9e887..9ce4cf8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -95,18 +95,23 @@ payload so nothing fragments on a normal Ethernet MTU. ### `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. +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) | -| `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` | +| `minBrightness` | `20` | brightness (0-100) during silence | +| `maxBrightness` | `100` | brightness (0-100) at full level; HyperHDR does not go above 100 | ### `udp`, `tcp`, `http` @@ -193,8 +198,8 @@ 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. +visualiser, `connected`/`updatesSent`/`minBrightness`/`maxBrightness`/`lastError` +for the brightness sink. ### Diagnostics diff --git a/docs/hyperhdr.md b/docs/hyperhdr.md index 04a2a00..78fcdec 100644 --- a/docs/hyperhdr.md +++ b/docs/hyperhdr.md @@ -169,15 +169,16 @@ lights go fully dark between beats, which looks dramatic and slightly broken. ## 4. Keep your grabber's colour, only pulse the brightness For an ambilight-style setup that already has a real colour source — a -screen grabber, a USB capture card, an HDMI splitter — routes 1–3 all have -the same problem: they compete for HyperHDR's priority and *replace* that -colour with something audio-derived. This route doesn't touch colour at all. +screen grabber, a USB capture card, a webOS capture app like piccap — routes +1–3 all have the same problem: they compete for HyperHDR's priority and +*replace* that colour with something audio-derived. This route doesn't touch +colour at all. -HyperHDR has a JSON-RPC `adjustment` command that scales output brightness as -a post-processing step, applied on top of whatever priority is currently -active. This sink sends nothing but that: no image, no priority -registration, so the grabber keeps deciding hue and this only turns the -result up and down with the sound. +HyperHDR has a JSON-RPC `adjustment` command that sets output brightness +(0-100) as a post-processing step, applied on top of whatever priority is +currently active. This sink sends nothing but that: no image, no priority +registration, so the grabber (or piccap, or whatever else) keeps deciding +hue and this only turns the result up and down with the sound. ``` TV ──RTP or local──► audiocap-service ──JSON-RPC "adjustment"──► HyperHDR @@ -192,12 +193,21 @@ TV ──RTP or local──► audiocap-service ──JSON-RPC "adjustment"─ | HyperHDR address | the HyperHDR machine's IP | | JSON-RPC port | 19444 (HyperHDR's classic control port — not 8090, the web UI; not 19400, Flatbuffers) | | Follows | Average level (steadier) or Peak level (punchier) | -| Minimum brightness | `1.0` = HyperHDR's normal brightness; lower dims during quiet parts | -| Maximum brightness | up to `2.0`; boosts past normal on loud peaks | +| Minimum brightness | `0`-`100`; applied during quiet parts | +| Maximum brightness | `0`-`100`; applied at full level. HyperHDR does not go above 100 | Needs a working capture source the same as every other route — see the top -of this document for picking one. On close, the sink resets `scaleOutput` to -`1.0` rather than leaving the LEDs stuck at whatever it last sent. +of this document for picking one. On close, the sink resets brightness to +`100` rather than leaving the LEDs stuck at whatever it last sent. + +The field is `brightness`, confirmed by watching it round-trip through +`serverinfo` and the LEDs visibly respond on a real HyperHDR/Docker +instance. HyperHDR's current `schema-adjustment.json` documents a +`scaleOutput` float (0.0-2.0) instead, which looked like the obvious choice +and is what this sink sent originally — it had no visible or server-reported +effect on that same instance. If a future HyperHDR version drops +`brightness`, this needs re-verifying the same way, not just re-reading the +schema. --- diff --git a/frontend/appinfo.json b/frontend/appinfo.json index 08c8942..7c9d7bb 100644 --- a/frontend/appinfo.json +++ b/frontend/appinfo.json @@ -1,6 +1,6 @@ { "id": "org.webosbrew.audiocap", - "version": "1.0.3", + "version": "1.0.4", "vendor": "Homebrew", "type": "web", "main": "index.html", diff --git a/frontend/js/app.js b/frontend/js/app.js index e0a8111..abf1b4f 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -401,12 +401,12 @@ ], }, { - path: 'hyperhdrAdjust.minScale', label: 'Minimum brightness', type: 'number', - hint: '1.0 is HyperHDR\'s normal brightness. Below that dims during quiet parts.', + path: 'hyperhdrAdjust.minBrightness', label: 'Minimum brightness', type: 'number', + hint: '0-100. Applied during quiet parts. 100 is HyperHDR\'s normal brightness.', }, { - path: 'hyperhdrAdjust.maxScale', label: 'Maximum brightness', type: 'number', - hint: 'Above 1.0 boosts past normal on loud peaks. HyperHDR accepts up to 2.0.', + path: 'hyperhdrAdjust.maxBrightness', label: 'Maximum brightness', type: 'number', + hint: '0-100. Applied at full level. HyperHDR does not go above 100.', }, ], diff --git a/frontend/js/mock.js b/frontend/js/mock.js index 175f5ad..82f12b6 100644 --- a/frontend/js/mock.js +++ b/frontend/js/mock.js @@ -16,7 +16,7 @@ mode: 'spectrum', saturation: 1.0, minBrightness: 0.02, }, hyperhdrAdjust: { - host: '', port: 19444, minScale: 0.3, maxScale: 1.3, level: 'rms', + host: '', port: 19444, minBrightness: 20, maxBrightness: 100, level: 'rms', }, udp: { host: '', port: 4010, multicastTtl: 4 }, tcp: { port: 4011, maxClients: 4 }, diff --git a/native/src/config.c b/native/src/config.c index 7367f7d..7bb2c7f 100644 --- a/native/src/config.c +++ b/native/src/config.c @@ -51,8 +51,8 @@ static const char* DEFAULTS_JSON = " \"hyperhdrAdjust\": {" " \"host\": \"\"," " \"port\": 19444," - " \"minScale\": 0.3," - " \"maxScale\": 1.3," + " \"minBrightness\": 20," + " \"maxBrightness\": 100," " \"level\": \"rms\"" " }," " \"udp\": { \"host\": \"\", \"port\": 4010, \"multicastTtl\": 4 }," diff --git a/native/src/sinks/sink_hyperhdr_adjust.c b/native/src/sinks/sink_hyperhdr_adjust.c index 85d532c..584eb18 100644 --- a/native/src/sinks/sink_hyperhdr_adjust.c +++ b/native/src/sinks/sink_hyperhdr_adjust.c @@ -1,12 +1,22 @@ // Global brightness modulation via HyperHDR's own JSON-RPC "adjustment" -// command, so a real picture source (HyperHDR's screen grabber, USB capture -// card, whatever is already driving the LEDs) keeps deciding colour, and -// only the overall brightness reacts to sound. Unlike every other sink here, -// this one deliberately sends no picture at all: send one would mean -// competing for priority against the grabber, replacing its colour outright -// instead of layering on top of it. "adjustment" is a post-processing stage -// that applies regardless of which priority is currently active, which is -// exactly the layering this needs. +// command, so a real picture source (HyperHDR's screen grabber, a webOS +// capture app like piccap, whatever is already driving the LEDs) keeps +// deciding colour, and only the overall brightness reacts to sound. Unlike +// every other sink here, this one deliberately sends no picture at all: +// sending one would mean competing for priority against that source, +// replacing its colour outright instead of layering on top of it. +// "adjustment" is a post-processing stage that applies regardless of which +// priority is currently active, which is exactly the layering this needs. +// +// The field is "brightness", an integer 0-100 -- confirmed against a real +// HyperHDR instance (serverinfo echoes it back, and the LEDs visibly +// responded). The "scaleOutput" float (0-2.0) in HyperHDR's current +// schema-adjustment.json looked like the obvious candidate and is what an +// earlier version of this file sent, but it produced no visible or +// server-reported effect on that same instance -- API docs describe the +// schema; they do not guarantee what a given build actually does with it. +// If a future HyperHDR drops "brightness" in favour of "scaleOutput", this +// will need re-verifying the same way, not just re-reading the schema. // // Plain newline-delimited JSON over TCP -- HyperHDR's classic control port, // default 19444 -- a world simpler than the Flatbuffers image protocol the @@ -19,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -38,8 +49,8 @@ typedef struct { char host[128]; int port; - float min_scale; - float max_scale; + int min_brightness; // 0-100 + int max_brightness; // 0-100 bool use_rms; // rms is steadier than peak, which reacts to single transients int fd; // -1 when not connected or still connecting @@ -154,11 +165,12 @@ static void adjust_write(sink_t* s, const int16_t* pcm, int frames, const dsp_le level = 0; if (level > 1) level = 1; - float scale = p->min_scale + level * (p->max_scale - p->min_scale); + int brightness = p->min_brightness + + (int)lroundf(level * (float)(p->max_brightness - p->min_brightness)); char msg[128]; int n = snprintf(msg, sizeof(msg), - "{\"command\":\"adjustment\",\"adjustment\":{\"scaleOutput\":%.3f}}\n", (double)scale); + "{\"command\":\"adjustment\",\"adjustment\":{\"brightness\":%d}}\n", brightness); if (n <= 0 || (size_t)n >= sizeof(msg)) return; @@ -183,8 +195,8 @@ static void adjust_status(sink_t* s, json_writer_t* w) jw_str(w, "target", p->host); jw_int(w, "port", p->port); jw_bool(w, "connected", p->connected); - jw_num(w, "minScale", p->min_scale); - jw_num(w, "maxScale", p->max_scale); + jw_int(w, "minBrightness", p->min_brightness); + jw_int(w, "maxBrightness", p->max_brightness); jw_int(w, "updatesSent", (long long)p->updates_sent); jw_int(w, "connectFailures", (long long)p->connect_failures); if (p->last_error[0]) @@ -200,7 +212,7 @@ static void adjust_close(sink_t* s) // Best effort: hand brightness back to normal rather than leaving // the LEDs stuck at whatever scale was last sent. if (p->connected) { - static const char reset[] = "{\"command\":\"adjustment\",\"adjustment\":{\"scaleOutput\":1.0}}\n"; + static const char reset[] = "{\"command\":\"adjustment\",\"adjustment\":{\"brightness\":100}}\n"; send(p->fd, reset, sizeof(reset) - 1, MSG_NOSIGNAL); } adjust_disconnect(p); @@ -214,8 +226,8 @@ static sink_t* adjust_open(const json_value_t* cfg, const audio_format_t* fmt, c const json_value_t* sc = json_get(cfg, "hyperhdrAdjust"); const char* host = json_str(sc, "host", NULL); int port = json_int(sc, "port", 19444); - float min_scale = (float)json_num(sc, "minScale", 0.3); - float max_scale = (float)json_num(sc, "maxScale", 1.3); + int min_brightness = json_int(sc, "minBrightness", 20); + int max_brightness = json_int(sc, "maxBrightness", 100); const char* level_source = json_str(sc, "level", "rms"); if (!host || !*host) { @@ -226,8 +238,8 @@ static sink_t* adjust_open(const json_value_t* cfg, const audio_format_t* fmt, c snprintf(err, errlen, "invalid HyperHDR JSON port %d", port); return NULL; } - if (max_scale <= min_scale) { - snprintf(err, errlen, "maxScale must be greater than minScale"); + if (min_brightness < 0 || max_brightness > 100 || max_brightness <= min_brightness) { + snprintf(err, errlen, "brightness range must be 0-100 with max greater than min"); return NULL; } @@ -261,8 +273,8 @@ static sink_t* adjust_open(const json_value_t* cfg, const audio_format_t* fmt, c snprintf(p->host, sizeof(p->host), "%s", host); p->port = port; - p->min_scale = min_scale; - p->max_scale = max_scale; + p->min_brightness = min_brightness; + p->max_brightness = max_brightness; p->use_rms = strcmp(level_source, "peak") != 0; s->driver = &sink_driver_hyperhdr_adjust; @@ -272,8 +284,8 @@ static sink_t* adjust_open(const json_value_t* cfg, const audio_format_t* fmt, c s->status = adjust_status; s->close = adjust_close; - INFO("HyperHDR adjustment sink: %s:%d, scaleOutput %.2f..%.2f from %s", host, port, - min_scale, max_scale, p->use_rms ? "rms" : "peak"); + INFO("HyperHDR adjustment sink: %s:%d, brightness %d..%d from %s", host, port, + min_brightness, max_brightness, p->use_rms ? "rms" : "peak"); return s; } diff --git a/package.json b/package.json index 29a6326..4e0cb9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lgtv-audio-cap", - "version": "1.0.3", + "version": "1.0.4", "private": true, "description": "Captures audio on an LG webOS 5/6 TV and streams it out \u2014 HyperHDR first, plus raw UDP, TCP and HTTP.", "keywords": [ diff --git a/servicefiles/package.json b/servicefiles/package.json index dbfc9f4..2975049 100644 --- a/servicefiles/package.json +++ b/servicefiles/package.json @@ -1,6 +1,6 @@ { "id": "org.webosbrew.audiocap.service", - "version": "1.0.3", + "version": "1.0.4", "description": "Captures TV audio and streams it to HyperHDR and other receivers", "main": "audiocap-service" }