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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
aae5a33283
commit
e9f6c87d27
+13
-8
@@ -95,18 +95,23 @@ payload so nothing fragments on a normal Ethernet MTU.
|
|||||||
|
|
||||||
### `hyperhdrAdjust` — brightness only, via HyperHDR's JSON-RPC
|
### `hyperhdrAdjust` — brightness only, via HyperHDR's JSON-RPC
|
||||||
|
|
||||||
Sends no image at all. Instead it calls HyperHDR's `adjustment` command —
|
Sends no image at all. Instead it calls HyperHDR's `adjustment` command with
|
||||||
a post-processing stage that scales output brightness regardless of which
|
a `brightness` value (0-100) — a post-processing stage that applies
|
||||||
priority is currently active — so an existing grabber or effect keeps
|
regardless of which priority is currently active — so an existing grabber or
|
||||||
deciding colour and only overall brightness reacts to sound.
|
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 |
|
| Key | Default | Meaning |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `host` | `""` | HyperHDR's address |
|
| `host` | `""` | HyperHDR's address |
|
||||||
| `port` | `19444` | HyperHDR's classic JSON-RPC port (not 8090, not 19400) |
|
| `port` | `19444` | HyperHDR's classic JSON-RPC port (not 8090, not 19400) |
|
||||||
| `level` | `"rms"` | `rms` (steadier) or `peak` (punchier) |
|
| `level` | `"rms"` | `rms` (steadier) or `peak` (punchier) |
|
||||||
| `minScale` | `0.3` | brightness during silence; `1.0` is HyperHDR's normal level |
|
| `minBrightness` | `20` | brightness (0-100) during silence |
|
||||||
| `maxScale` | `1.3` | brightness at full level; HyperHDR accepts up to `2.0` |
|
| `maxBrightness` | `100` | brightness (0-100) at full level; HyperHDR does not go above 100 |
|
||||||
|
|
||||||
### `udp`, `tcp`, `http`
|
### `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
|
Each sink adds its own fields. `packetsSent`/`bytesSent`/`sendErrors` for the
|
||||||
datagram sinks, `clients`/`droppedBytes` for the stream servers,
|
datagram sinks, `clients`/`droppedBytes` for the stream servers,
|
||||||
`connected`/`registered`/`framesSent`/`connectFailures`/`lastError` for the
|
`connected`/`registered`/`framesSent`/`connectFailures`/`lastError` for the
|
||||||
visualiser, `connected`/`updatesSent`/`minScale`/`maxScale`/`lastError` for
|
visualiser, `connected`/`updatesSent`/`minBrightness`/`maxBrightness`/`lastError`
|
||||||
the brightness sink.
|
for the brightness sink.
|
||||||
|
|
||||||
### Diagnostics
|
### Diagnostics
|
||||||
|
|
||||||
|
|||||||
+22
-12
@@ -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
|
## 4. Keep your grabber's colour, only pulse the brightness
|
||||||
|
|
||||||
For an ambilight-style setup that already has a real colour source — a
|
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
|
screen grabber, a USB capture card, a webOS capture app like piccap — routes
|
||||||
the same problem: they compete for HyperHDR's priority and *replace* that
|
1–3 all have the same problem: they compete for HyperHDR's priority and
|
||||||
colour with something audio-derived. This route doesn't touch colour at all.
|
*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
|
HyperHDR has a JSON-RPC `adjustment` command that sets output brightness
|
||||||
a post-processing step, applied on top of whatever priority is currently
|
(0-100) as a post-processing step, applied on top of whatever priority is
|
||||||
active. This sink sends nothing but that: no image, no priority
|
currently active. This sink sends nothing but that: no image, no priority
|
||||||
registration, so the grabber keeps deciding hue and this only turns the
|
registration, so the grabber (or piccap, or whatever else) keeps deciding
|
||||||
result up and down with the sound.
|
hue and this only turns the result up and down with the sound.
|
||||||
|
|
||||||
```
|
```
|
||||||
TV ──RTP or local──► audiocap-service ──JSON-RPC "adjustment"──► HyperHDR
|
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 |
|
| HyperHDR address | the HyperHDR machine's IP |
|
||||||
| JSON-RPC port | 19444 (HyperHDR's classic control port — not 8090, the web UI; not 19400, Flatbuffers) |
|
| 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) |
|
| Follows | Average level (steadier) or Peak level (punchier) |
|
||||||
| Minimum brightness | `1.0` = HyperHDR's normal brightness; lower dims during quiet parts |
|
| Minimum brightness | `0`-`100`; applied during quiet parts |
|
||||||
| Maximum brightness | up to `2.0`; boosts past normal on loud peaks |
|
| 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
|
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
|
of this document for picking one. On close, the sink resets brightness to
|
||||||
`1.0` rather than leaving the LEDs stuck at whatever it last sent.
|
`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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"id": "org.webosbrew.audiocap",
|
"id": "org.webosbrew.audiocap",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"vendor": "Homebrew",
|
"vendor": "Homebrew",
|
||||||
"type": "web",
|
"type": "web",
|
||||||
"main": "index.html",
|
"main": "index.html",
|
||||||
|
|||||||
+4
-4
@@ -401,12 +401,12 @@
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'hyperhdrAdjust.minScale', label: 'Minimum brightness', type: 'number',
|
path: 'hyperhdrAdjust.minBrightness', label: 'Minimum brightness', type: 'number',
|
||||||
hint: '1.0 is HyperHDR\'s normal brightness. Below that dims during quiet parts.',
|
hint: '0-100. Applied during quiet parts. 100 is HyperHDR\'s normal brightness.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'hyperhdrAdjust.maxScale', label: 'Maximum brightness', type: 'number',
|
path: 'hyperhdrAdjust.maxBrightness', label: 'Maximum brightness', type: 'number',
|
||||||
hint: 'Above 1.0 boosts past normal on loud peaks. HyperHDR accepts up to 2.0.',
|
hint: '0-100. Applied at full level. HyperHDR does not go above 100.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@
|
|||||||
mode: 'spectrum', saturation: 1.0, minBrightness: 0.02,
|
mode: 'spectrum', saturation: 1.0, minBrightness: 0.02,
|
||||||
},
|
},
|
||||||
hyperhdrAdjust: {
|
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 },
|
udp: { host: '', port: 4010, multicastTtl: 4 },
|
||||||
tcp: { port: 4011, maxClients: 4 },
|
tcp: { port: 4011, maxClients: 4 },
|
||||||
|
|||||||
+2
-2
@@ -51,8 +51,8 @@ static const char* DEFAULTS_JSON =
|
|||||||
" \"hyperhdrAdjust\": {"
|
" \"hyperhdrAdjust\": {"
|
||||||
" \"host\": \"\","
|
" \"host\": \"\","
|
||||||
" \"port\": 19444,"
|
" \"port\": 19444,"
|
||||||
" \"minScale\": 0.3,"
|
" \"minBrightness\": 20,"
|
||||||
" \"maxScale\": 1.3,"
|
" \"maxBrightness\": 100,"
|
||||||
" \"level\": \"rms\""
|
" \"level\": \"rms\""
|
||||||
" },"
|
" },"
|
||||||
" \"udp\": { \"host\": \"\", \"port\": 4010, \"multicastTtl\": 4 },"
|
" \"udp\": { \"host\": \"\", \"port\": 4010, \"multicastTtl\": 4 },"
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
// Global brightness modulation via HyperHDR's own JSON-RPC "adjustment"
|
// Global brightness modulation via HyperHDR's own JSON-RPC "adjustment"
|
||||||
// command, so a real picture source (HyperHDR's screen grabber, USB capture
|
// command, so a real picture source (HyperHDR's screen grabber, a webOS
|
||||||
// card, whatever is already driving the LEDs) keeps deciding colour, and
|
// capture app like piccap, whatever is already driving the LEDs) keeps
|
||||||
// only the overall brightness reacts to sound. Unlike every other sink here,
|
// deciding colour, and only the overall brightness reacts to sound. Unlike
|
||||||
// this one deliberately sends no picture at all: send one would mean
|
// every other sink here, this one deliberately sends no picture at all:
|
||||||
// competing for priority against the grabber, replacing its colour outright
|
// sending one would mean competing for priority against that source,
|
||||||
// instead of layering on top of it. "adjustment" is a post-processing stage
|
// replacing its colour outright instead of layering on top of it.
|
||||||
// that applies regardless of which priority is currently active, which is
|
// "adjustment" is a post-processing stage that applies regardless of which
|
||||||
// exactly the layering this needs.
|
// 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,
|
// Plain newline-delimited JSON over TCP -- HyperHDR's classic control port,
|
||||||
// default 19444 -- a world simpler than the Flatbuffers image protocol the
|
// default 19444 -- a world simpler than the Flatbuffers image protocol the
|
||||||
@@ -19,6 +29,7 @@
|
|||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <math.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
@@ -38,8 +49,8 @@ typedef struct {
|
|||||||
char host[128];
|
char host[128];
|
||||||
int port;
|
int port;
|
||||||
|
|
||||||
float min_scale;
|
int min_brightness; // 0-100
|
||||||
float max_scale;
|
int max_brightness; // 0-100
|
||||||
bool use_rms; // rms is steadier than peak, which reacts to single transients
|
bool use_rms; // rms is steadier than peak, which reacts to single transients
|
||||||
|
|
||||||
int fd; // -1 when not connected or still connecting
|
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;
|
level = 0;
|
||||||
if (level > 1)
|
if (level > 1)
|
||||||
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];
|
char msg[128];
|
||||||
int n = snprintf(msg, sizeof(msg),
|
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))
|
if (n <= 0 || (size_t)n >= sizeof(msg))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -183,8 +195,8 @@ static void adjust_status(sink_t* s, json_writer_t* w)
|
|||||||
jw_str(w, "target", p->host);
|
jw_str(w, "target", p->host);
|
||||||
jw_int(w, "port", p->port);
|
jw_int(w, "port", p->port);
|
||||||
jw_bool(w, "connected", p->connected);
|
jw_bool(w, "connected", p->connected);
|
||||||
jw_num(w, "minScale", p->min_scale);
|
jw_int(w, "minBrightness", p->min_brightness);
|
||||||
jw_num(w, "maxScale", p->max_scale);
|
jw_int(w, "maxBrightness", p->max_brightness);
|
||||||
jw_int(w, "updatesSent", (long long)p->updates_sent);
|
jw_int(w, "updatesSent", (long long)p->updates_sent);
|
||||||
jw_int(w, "connectFailures", (long long)p->connect_failures);
|
jw_int(w, "connectFailures", (long long)p->connect_failures);
|
||||||
if (p->last_error[0])
|
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
|
// Best effort: hand brightness back to normal rather than leaving
|
||||||
// the LEDs stuck at whatever scale was last sent.
|
// the LEDs stuck at whatever scale was last sent.
|
||||||
if (p->connected) {
|
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);
|
send(p->fd, reset, sizeof(reset) - 1, MSG_NOSIGNAL);
|
||||||
}
|
}
|
||||||
adjust_disconnect(p);
|
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 json_value_t* sc = json_get(cfg, "hyperhdrAdjust");
|
||||||
const char* host = json_str(sc, "host", NULL);
|
const char* host = json_str(sc, "host", NULL);
|
||||||
int port = json_int(sc, "port", 19444);
|
int port = json_int(sc, "port", 19444);
|
||||||
float min_scale = (float)json_num(sc, "minScale", 0.3);
|
int min_brightness = json_int(sc, "minBrightness", 20);
|
||||||
float max_scale = (float)json_num(sc, "maxScale", 1.3);
|
int max_brightness = json_int(sc, "maxBrightness", 100);
|
||||||
const char* level_source = json_str(sc, "level", "rms");
|
const char* level_source = json_str(sc, "level", "rms");
|
||||||
|
|
||||||
if (!host || !*host) {
|
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);
|
snprintf(err, errlen, "invalid HyperHDR JSON port %d", port);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (max_scale <= min_scale) {
|
if (min_brightness < 0 || max_brightness > 100 || max_brightness <= min_brightness) {
|
||||||
snprintf(err, errlen, "maxScale must be greater than minScale");
|
snprintf(err, errlen, "brightness range must be 0-100 with max greater than min");
|
||||||
return NULL;
|
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);
|
snprintf(p->host, sizeof(p->host), "%s", host);
|
||||||
p->port = port;
|
p->port = port;
|
||||||
p->min_scale = min_scale;
|
p->min_brightness = min_brightness;
|
||||||
p->max_scale = max_scale;
|
p->max_brightness = max_brightness;
|
||||||
p->use_rms = strcmp(level_source, "peak") != 0;
|
p->use_rms = strcmp(level_source, "peak") != 0;
|
||||||
|
|
||||||
s->driver = &sink_driver_hyperhdr_adjust;
|
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->status = adjust_status;
|
||||||
s->close = adjust_close;
|
s->close = adjust_close;
|
||||||
|
|
||||||
INFO("HyperHDR adjustment sink: %s:%d, scaleOutput %.2f..%.2f from %s", host, port,
|
INFO("HyperHDR adjustment sink: %s:%d, brightness %d..%d from %s", host, port,
|
||||||
min_scale, max_scale, p->use_rms ? "rms" : "peak");
|
min_brightness, max_brightness, p->use_rms ? "rms" : "peak");
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lgtv-audio-cap",
|
"name": "lgtv-audio-cap",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"private": true,
|
"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.",
|
"description": "Captures audio on an LG webOS 5/6 TV and streams it out \u2014 HyperHDR first, plus raw UDP, TCP and HTTP.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"id": "org.webosbrew.audiocap.service",
|
"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",
|
"description": "Captures TV audio and streams it to HyperHDR and other receivers",
|
||||||
"main": "audiocap-service"
|
"main": "audiocap-service"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user