diff --git a/.gitignore b/.gitignore index 32092cc..e607d12 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ test-handover test-target tests/receiver-proxy.log + +test-spatial-image diff --git a/HyperDRM.manifest.json b/HyperDRM.manifest.json index ecc9812..71d85e9 100644 --- a/HyperDRM.manifest.json +++ b/HyperDRM.manifest.json @@ -1,15 +1,15 @@ { "id": "org.webosbrew.histogramhyperion", - "version": "0.8.0", + "version": "0.8.1", "type": "web", "title": "HyperDRM", "appDescription": "Ambient lighting for webOS, Hyperion and HyperHDR", "iconUri": "https://gitea.adnau.de/matze19999/HyperDRM/raw/branch/main/app/icon.png", "sourceUrl": "https://gitea.adnau.de/matze19999/HyperDRM", "rootRequired": true, - "ipkUrl": "https://gitea.adnau.de/matze19999/HyperDRM/raw/branch/main/HyperDRM_0.8.0_arm.ipk", + "ipkUrl": "https://gitea.adnau.de/matze19999/HyperDRM/raw/branch/main/HyperDRM_0.8.1_arm.ipk", "ipkHash": { - "sha256": "7cbb9c3a6e9dc5fe0c421cff6082807231c06a697420767761735f405855ae4a" + "sha256": "c5a8125d95eeba517f8efe190de8d5304ec2663768db69552e4ea2f0e0b23cc0" }, - "ipkSize": 1173194 + "ipkSize": 1173862 } diff --git a/HyperDRM_0.8.0_arm.ipk b/HyperDRM_0.8.1_arm.ipk similarity index 91% rename from HyperDRM_0.8.0_arm.ipk rename to HyperDRM_0.8.1_arm.ipk index c92b0d9..bf0257b 100644 Binary files a/HyperDRM_0.8.0_arm.ipk and b/HyperDRM_0.8.1_arm.ipk differ diff --git a/README.md b/README.md index 567de77..cb96f5b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Ambient Light for webOS, Hyperion and HyperHDR. An own webOS app and native ARM Tested on a rooted LG G4 with webOS 24, Homebrew Channel and Hyperion 2.0.16 and HyperHDR 22.0.0 tested on the TV. The native service requires root elevation and the TV's existing `libvtcapture`, Luna, GLib and JSON-C libraries. Other TV models or firmware versions have not been validated. -DRM playback color output was confirmed on the test TV. Support depends on LG's capture implementation; this does not guarantee support on other devices or apps. HyperDRM transmits colors, not captured video frames. The video preview in Hyperion therefore does not show the TV picture. +DRM playback color output was confirmed on the test TV. Support depends on LG's capture implementation; this does not guarantee support on other devices or apps. HyperDRM transmits colors, not captured video frames. The video preview does not show the TV picture. HyperHDR may show a generated histogram color plane instead. The 16 histogram samples are independent measurements; interpolation produces smoother transitions without adding independently captured detail. Sample positions are approximate G4 calibration values. Top/bottom bar detection is supported; side bars, arbitrary bar thickness and HDR color calibration remain limited. @@ -25,7 +25,7 @@ Only the own service is modified. Optional Auto PicCap can start and stop the in ## Installation -The current package is [HyperDRM_0.8.0_arm.ipk](HyperDRM_0.8.0_arm.ipk). Install it using the TV's webOS/Homebrew package installation workflow. +The current package is [HyperDRM_0.8.1_arm.ipk](HyperDRM_0.8.1_arm.ipk). Install it using the TV's webOS/Homebrew package installation workflow. Elevate the installed own service through Homebrew Channel: @@ -102,7 +102,7 @@ LG may revoke histogram capture during playback transitions. From 0.7.1, HyperDR ## Hyperion and HyperHDR receiver settings -Choose **Receiver: Hyperion/HyperHDR**, enter an IPv4 or IPv6 address and the JSON TCP port, then select **Save receiver**. These receivers use the same color, clear and serverinfo commands. Use the JSON port (usually 19444), not the FlatBuffer port (usually 19400) or web interface port. Numeric IP addresses are supported; hostnames and scoped IPv6 addresses are not currently accepted. Layout and LED count come from the selected receiver, including remote receivers. +Choose **Receiver: Hyperion/HyperHDR**, enter an IPv4 or IPv6 address and the JSON TCP port, then select **Save receiver**. Hyperion uses per-LED JSON color arrays; HyperHDR uses a synthetic spatial RGB image because HyperHDR 22 treats the color command as a solid color. Both use clear and serverinfo. Use the JSON port (usually 19444), not the FlatBuffer port (usually 19400) or web interface port. Numeric IP addresses are supported; hostnames and scoped IPv6 addresses are not currently accepted. Layout and LED count come from the selected receiver, including remote receivers. Settings are saved in `target.json` inside the own service container and loaded after service restart/boot. Saving valid settings switches a running session to the new receiver and clears the own priority at the old receiver. Invalid settings leave the current target intact. Saving while manually stopped does not start a session. Uninstalling removes the saved settings with the container. @@ -111,3 +111,9 @@ For a receiver requiring authentication, enter its API token. The token file is Auto PicCap remains available for the default local receiver at 127.0.0.1/::1:19444. For custom targets, automatic PicCap capture is paused and HyperDRM supplies colors for all playback; the PicCap preference remains saved. PicCap has a separate FlatBuffer endpoint managed by its own app, which HyperDRM does not rewrite. `native/test_target.c` verifies validation, token retention/removal, private persistence and TCP connection/refusal. `tests/receiver-proxy.py` is a test-only relay with synthetic token login and fragmented replies. Local HyperHDR 22.0.0 and a remote relay using a different IP/port were tested on the G4; the relay forwarded real color updates to HyperHDR without changing its configuration. + +## HyperHDR spatial output (0.8.1) + +HyperHDR 22's JSON `color` handler ultimately registers only the first RGB triplet as a solid color. Version 0.8.1 uses JSON `image` output for the HyperHDR receiver selection. A cached 160x90 geometry interpolates the calibrated histogram samples into a synthetic color plane; HyperHDR applies its configured LED rectangles to that plane. The payload contains generated colors, not captured or decrypted video frames. HyperHDR's image processing and color calibration affect the resulting LED colors. Select a spatial image mapping mode in HyperHDR; a global single-color mapping mode will still give uniform LEDs. + +Hyperion retains its original per-LED color-array output. Receiver IP, JSON port, optional token, priority, timeout and reconnection behavior remain shared. Tests cover exact RGB base64 encoding, image command metadata and spatial edge variation. Output must be verified through the receiver's LED stream, not solely a successful command acknowledgment. diff --git a/app/app.js b/app/app.js index 649e235..22960ba 100644 --- a/app/app.js +++ b/app/app.js @@ -28,7 +28,7 @@ function display(result){ } if(result.piccapHandover)document.getElementById('status').textContent+=' · HyperDRM · '+result.playbackProtection; if(result.piccapHandover && !result.piccapTargetCompatible)document.getElementById('status').textContent+=' · Auto PicCap paused for custom target'; - if(result.colorValid)document.getElementById('status').textContent+=result.spatialOutput?' · '+result.ledCount+' LEDs from 16 color regions':' · Output RGB: '+result.outputColor.join(', '); + if(result.colorValid)document.getElementById('status').textContent+=result.spatialOutput?' · '+result.ledCount+' LEDs from 16 color regions'+(result.receiver==='HyperHDR'?' · Spatial image output':''):' · Output RGB: '+result.outputColor.join(', '); else if(result.returnValue!==false)document.getElementById('status').textContent+=' · No current color output'; if(result.blackBarDetection && result.running)document.getElementById('status').textContent+=result.letterboxDetected?' · Top/bottom black bars detected':' · Full-screen capture'; if(result.spatialInterpolation)document.getElementById('status').textContent+=' · Smooth color transitions'; diff --git a/app/appinfo.json b/app/appinfo.json index 14dc44b..29e1fa1 100644 --- a/app/appinfo.json +++ b/app/appinfo.json @@ -1 +1 @@ -{"id": "org.webosbrew.histogramhyperion", "version": "0.8.0", "vendor": "Local development", "type": "web", "main": "index.html", "title": "HyperDRM", "icon": "icon.png", "resolution": "1920x1080", "disableBackHistoryAPI": true, "largeIcon": "large-icon.png", "appDescription": "Ambient lighting for webOS, Hyperion and HyperHDR", "sourceUrl": "https://gitea.adnau.de/matze19999/HyperDRM"} +{"id": "org.webosbrew.histogramhyperion", "version": "0.8.1", "vendor": "Local development", "type": "web", "main": "index.html", "title": "HyperDRM", "icon": "icon.png", "resolution": "1920x1080", "disableBackHistoryAPI": true, "largeIcon": "large-icon.png", "appDescription": "Ambient lighting for webOS, Hyperion and HyperHDR", "sourceUrl": "https://gitea.adnau.de/matze19999/HyperDRM"} diff --git a/app/index.html b/app/index.html index a43b398..c18ecda 100644 --- a/app/index.html +++ b/app/index.html @@ -12,5 +12,5 @@

Use the receiver's JSON TCP port, usually 19444. IPv4 and IPv6 addresses are supported. Leave the token blank to keep a saved token. Save applies the new target to a running session. Use Up/Down to move between fields; Left/Right edit text.

Auto PicCap requires the default local receiver; custom targets use HyperDRM for all playback. PicCap settings are managed in PicCap.

Priority 149 · 10 updates per second

-

The Hyperion layout determines the number and positions of LEDs. Colors come from 16 LG histogram regions with interpolated edge colors. Restart the service after changing the LED layout.

+

The Hyperion layout determines the number and positions of LEDs. Colors come from 16 LG histogram regions with interpolated edge colors. HyperHDR receives a generated color plane, which its image mapping converts to LED colors. Restart the service after changing the LED layout.

diff --git a/docs/publishing-review.md b/docs/publishing-review.md index dd166bc..20db84f 100644 --- a/docs/publishing-review.md +++ b/docs/publishing-review.md @@ -68,6 +68,14 @@ ARM retry and handover regression tests passed on the G4, as did remote navigati The app can save receiver type, numeric IPv4/IPv6 address, JSON TCP port and an optional API token. Targets and tokens are validated before replacing the configuration or interrupting capture. The own container stores `target.json` atomically with mode 0600; status omits the token value. TCP connection attempts are bounded to one second. Authentication and remote layout validation happen before acquiring LG capture. The receiver's own authorization and LED-device settings are not changed. Auto PicCap remains limited to the default local target; custom targets use histogram capture rather than sending PicCap data to an independently configured receiver. -Native target tests passed on the G4, including invalid values, token retention/removal, private persistence and TCP success/refusal. Handover and input-field remote-navigation regression tests passed. HyperDRM 0.8.0 was installed and tested against the user's HyperHDR 22.0.0 on localhost:19444: acknowledged spatial color updates for its 120-LED layout. A temporary test relay on another LAN host at port 19445 exercised synthetic token login and fragmented replies, forwarding actual layout/color/clear commands to HyperHDR. Omitting the token preserved it; an invalid target was rejected without changing the active target. A verified own-service process restart loaded the saved HyperHDR target. The test relay was stopped and the target restored to localhost:19444 with no saved token. Network tests used IPv4; IPv6 parsing is covered by unit tests, but an IPv6 receiver was not available for an end-to-end test. Cold boot remains untested. +Native target tests passed on the G4, including invalid values, token retention/removal, private persistence and TCP success/refusal. Handover and input-field remote-navigation regression tests passed. HyperDRM 0.8.0 was installed and tested against the user's HyperHDR 22.0.0 on localhost:19444: acknowledged color-array updates for its 120-LED layout. The 0.8.0 test did not check per-LED output diversity; the later 0.8.1 investigation found that HyperHDR rendered these arrays as a solid color. A temporary test relay on another LAN host at port 19445 exercised synthetic token login and fragmented replies, forwarding actual layout/color/clear commands to HyperHDR. Omitting the token preserved it; an invalid target was rejected without changing the active target. A verified own-service process restart loaded the saved HyperHDR target. The test relay was stopped and the target restored to localhost:19444 with no saved token. Network tests used IPv4; IPv6 parsing is covered by unit tests, but an IPv6 receiver was not available for an end-to-end test. Cold boot remains untested. The API compatibility was checked against official HyperHDR `sources/api/HyperAPI.cpp` and `sources/base/HyperHdrInstance.cpp`: serverinfo includes the LED layout, color accepts RGB arrays, and token authorization precedes API commands. + +## Actual HyperHDR spatial output correction (0.8.1) + +HyperHDR 22's `HyperHdrInstance::setColor` registers `ledColors[0]` as a solid color, despite the API accepting a longer array. Live output confirmed 120 LEDs with one unique color. The earlier 0.8.0 acknowledgment-only validation was insufficient to establish spatial output. + +For HyperHDR selection, 0.8.1 sends an RGB JSON image generated from the calibrated histogram edge samples on a cached 160x90 geometry. The receiver samples the synthetic plane using its LED rectangles and image processing. Hyperion retains per-LED color-array output. No TV video pixels are captured or transmitted by the generated plane. Original saturation/brightness gains remain 1.0. Receiver configuration is not modified. + +On the G4 with HyperHDR 22.0.0, a temporary spatial test input produced 120 unique colors for 120 LEDs and was cleared. Exact RGB/base64, image command and spatial geometry tests passed on ARM. After installation of 0.8.1, the real histogram source was visible as IMAGE at priority 149. Eleven LED-stream samples from actual playback contained 87-100 unique colors across the 120 LEDs, confirming spatial output rather than a solid color. This plane has approximate calibrated spatial resolution, not full video-frame detail; HyperHDR's configured mapping and calibration affect the output. diff --git a/native/service.c b/native/service.c index b98f8c8..6cc56d3 100644 --- a/native/service.c +++ b/native/service.c @@ -25,6 +25,7 @@ #include "autostart.h" #include "retry.h" #include "target.h" +#include "spatial_image.h" #define SERVICE "org.webosbrew.histogramhyperion.service" #define PRIORITY 149 typedef struct {int code;char *message;const char *file;int line;const char *func;void *padding;unsigned long magic;} LSError; @@ -196,7 +197,7 @@ static bool status(void *h,void *m,void *ctx){ (void)ctx;char json[4096],safe[512],endpoint[80];size_t n=0; snprintf(endpoint,sizeof(endpoint),strchr(target.host,':')?"[%s]:%u":"%s:%u",target.host,target.port); for(size_t i=0;last_error[i] && n+2=32)safe[n++]=c;}safe[n]=0; - int used=snprintf(json,sizeof(json),"{\"returnValue\":true,\"version\":\"0.8.0\",\"running\":%s,\"colorValid\":%s,\"frames\":%lu,\"captureRecoveries\":%u,\"lastRecoveryMs\":%llu,\"endpoint\":\"%s\",\"host\":\"%s\",\"port\":%u,\"receiver\":\"%s\",\"tokenSet\":%s,\"piccapTargetCompatible\":%s,\"priority\":%d,\"uid\":%u,\"drmVerified\":false,\"mappingVerified\":false,\"outputColor\":[%u,%u,%u],\"error\":\"%s\",\"regions\":[",(capture||capture_source==SOURCE_PICCAP)?"true":"false",color_valid?"true":"false",frames,capture_recoveries,(unsigned long long)last_recovery_ms,endpoint,target.host,target.port,target.receiver,target.token[0]?"true":"false",target_piccap_local(&target)?"true":"false",PRIORITY,(unsigned)geteuid(),sent_rgb[0],sent_rgb[1],sent_rgb[2],safe); + int used=snprintf(json,sizeof(json),"{\"returnValue\":true,\"version\":\"0.8.1\",\"running\":%s,\"colorValid\":%s,\"frames\":%lu,\"captureRecoveries\":%u,\"lastRecoveryMs\":%llu,\"endpoint\":\"%s\",\"host\":\"%s\",\"port\":%u,\"receiver\":\"%s\",\"tokenSet\":%s,\"piccapTargetCompatible\":%s,\"priority\":%d,\"uid\":%u,\"drmVerified\":false,\"mappingVerified\":false,\"outputColor\":[%u,%u,%u],\"error\":\"%s\",\"regions\":[",(capture||capture_source==SOURCE_PICCAP)?"true":"false",color_valid?"true":"false",frames,capture_recoveries,(unsigned long long)last_recovery_ms,endpoint,target.host,target.port,target.receiver,target.token[0]?"true":"false",target_piccap_local(&target)?"true":"false",PRIORITY,(unsigned)geteuid(),sent_rgb[0],sent_rgb[1],sent_rgb[2],safe); for(int i=0;i<16;i++)used+=snprintf(json+used,sizeof(json)-used,"%s[%u,%u,%u]",i?",":"",rgb[i][0],rgb[i][1],rgb[i][2]); used+=snprintf(json+used,sizeof(json)-used,"],\"ledCount\":%u,\"spatialOutput\":%s,\"blackPercent\":[",led_count,led_count?"true":"false"); for(int i=0;i<16;i++)used+=snprintf(json+used,sizeof(json)-used,"%s%u",i?",":"",black_percent[i]); @@ -246,8 +247,10 @@ static int tick(void *unused){ } for(int c=0;c<3;c++)sent_rgb[c]=(unsigned char)(aggregate[c]/16); unsigned char boosted_mean[3];boost_color(sent_rgb,boosted_mean);memcpy(sent_rgb,boosted_mean,3); - size_t capacity=256+(size_t)led_count*12;char *line=malloc(capacity); + bool image_output=strcmp(target.receiver,"HyperHDR")==0; + size_t capacity=256+(size_t)led_count*12;char *line=image_output?spatial_image_command(rgb,PRIORITY):malloc(capacity); if(!line){set_error("Color buffer allocation failed");stop_capture();return 1;} + if(!image_output){ size_t written=(size_t)snprintf(line,capacity,"{\"command\":\"color\",\"priority\":%d,\"duration\":1500,\"origin\":\"HyperDRM\",\"color\":[",PRIORITY); for(unsigned i=0;i +#include +#include "layout.h" +#include "interpolation.h" +#include "color_boost.h" +enum {SPATIAL_WIDTH=160,SPATIAL_HEIGHT=90,SPATIAL_PIXELS=SPATIAL_WIDTH*SPATIAL_HEIGHT}; +/* Synthetic color plane from histogram samples, never captured video pixels. + * HyperHDR 22 treats JSON color as a solid color; image input retains space. + * The receiver samples this plane using its configured LED rectangles. */ +static char *spatial_image_command(const unsigned char regions[16][3],unsigned priority){ + static LedBlend blends[SPATIAL_PIXELS];static bool initialized; + if(!initialized){ + for(unsigned y=0;y>2];line[used++]=alphabet[((color[0]&3)<<4)|(color[1]>>4)]; + line[used++]=alphabet[((color[1]&15)<<2)|(color[2]>>6)];line[used++]=alphabet[color[2]&63]; + } + line[used++]='"';line[used++]='}';line[used++]='\n';line[used]=0;return line; +} +#endif diff --git a/native/test_spatial_image.c b/native/test_spatial_image.c new file mode 100644 index 0000000..995807f --- /dev/null +++ b/native/test_spatial_image.c @@ -0,0 +1,17 @@ +#include +#include +#include "spatial_image.h" +int main(void){ + (void)layout_from_json; + unsigned char regions[16][3];for(unsigned i=0;i<16;i++){regions[i][0]=11;regions[i][1]=22;regions[i][2]=33;} + char *command=spatial_image_command(regions,149);assert(command); + assert(strstr(command,"\"command\":\"image\"") && strstr(command,"\"priority\":149") && strstr(command,"\"format\":\"rgb\"")); + const char *encoded=strstr(command,"\"imagedata\":\"")+13; + for(unsigned i=0;i