Recover quickly from LG capture revocation without clearing recent colors

This commit is contained in:
matze19999 committed 2026-10-06 16:13:40 +02:00
1 parent 84db8490ee
commit 81087448b2
10 files changed
+43 -15

No files matched your search

+4 -4
View File
@@ -1,15 +1,15 @@
{
"id": "org.webosbrew.histogramhyperion",
"version": "0.7.0",
"version": "0.7.1",
"type": "web",
"title": "HyperDRM",
"appDescription": "Ambient lighting for webOS and Hyperion",
"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.7.0_arm.ipk",
"ipkUrl": "https://gitea.adnau.de/matze19999/HyperDRM/raw/branch/main/HyperDRM_0.7.1_arm.ipk",
"ipkHash": {
"sha256": "0668d8abab98d81d51368863c7bc920f85f6107ec3872ea1810a332c2ddc87f5"
"sha256": "05d266c4ab36a1b1a5fb7f952a67ea9c9af61ef12cf0a1e27eafafd5189cb90f"
},
"ipkSize": 1169660
"ipkSize": 1170022
}
Binary file not shown.
+5 -1
View File
@@ -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.7.0_arm.ipk](HyperDRM_0.7.0_arm.ipk). Install it using the TV's webOS/Homebrew package installation workflow.
The current package is [HyperDRM_0.7.1_arm.ipk](HyperDRM_0.7.1_arm.ipk). Install it using the TV's webOS/Homebrew package installation workflow.
Elevate the installed own service through Homebrew Channel:
@@ -95,3 +95,7 @@ When the session is started, the controller polls LG's active media resources. T
The switch is saved in the own service container and applies after reboot when Autostart is enabled. Stop pauses both managed capture paths until Start. Turning Auto PicCap off stops a PicCap session started by this controller and restores HyperDRM. Avoid starting another capture service independently while automatic handover is active.
The app displays the selected source and PicCap's Hyperion connection status. Histogram region swatches are hidden while PicCap supplies the colors. Playback classification and isolated controller tests are in `native/test_playback.c` and `native/test_handover.c`; link both with `-ldl`.
## Capture interruption recovery
LG may revoke histogram capture during playback transitions. From 0.7.1, HyperDRM releases capture while leaving its last color update to expire normally after 1.5 seconds, then retries initialization after 200 ms and up to two additional 400 ms intervals. Successful recovery clears this rapid retry budget. Persistent failure returns to the existing five-second retry interval; manual Stop, PicCap handover and shutdown still explicitly clear the own color source. Status exposes `captureRecoveries` and `lastRecoveryMs` for diagnosis.
+1 -1
View File
@@ -1 +1 @@
{"id": "org.webosbrew.histogramhyperion", "version": "0.7.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 and Hyperion", "sourceUrl": "https://gitea.adnau.de/matze19999/HyperDRM"}
{"id": "org.webosbrew.histogramhyperion", "version": "0.7.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 and Hyperion", "sourceUrl": "https://gitea.adnau.de/matze19999/HyperDRM"}
+8
View File
@@ -55,3 +55,11 @@ A second live disconnect test confirmed retryScheduled:true while Hyperion was u
The default-off Auto PicCap switch stores its preference in the own service container. If enabled, it uses the installed PicCap service's existing start/stop/status methods; it does not rewrite PicCap settings or its autostart hook. Only foreground media decoder resources without `SVP_CPB` are eligible, after two samples. Any secure resource or unknown/stale state restores the histogram path. VT capture is released and HyperDRM priority cleared before starting PicCap; stopped status is required before histogram capture resumes. Manual Stop pauses both managed paths. Shutdown attempts to stop a managed PicCap session before unregistering; a forced kill or unresponsive external service cannot guarantee this cleanup.
On the G4, playback-classification and isolated controller tests passed: secure/clear/unknown states, mixed pipelines, malformed records, two-sample gating, exclusive transitions, manual Stop, disabling handover and timeout cancellation. The fifth control passed remote-navigation regression tests. Version 0.7.0 was installed and elevated; with Disney+ playing, enabling handover reported protected playback, maintained histogram color output and left PicCap stopped. The test preference was restored to off. Live handover was then verified with the user's unprotected test video: PicCap reported videoRunning:true and connected:true, and Hyperion selected its visible FlatBuffer input at priority 150 with spatial output for the current 120-LED layout. Disabling handover stopped PicCap and restored histogram output. Re-enabling handover and switching back to DRM automatically stopped PicCap and restored HyperDRM as Hyperion's visible priority-149 source. The preference was restored to off after the complete test.
## Capture blackout diagnosis and recovery (0.7.1)
A live 0.7.0 observation reproduced the reported blackout while Auto PicCap was disabled and PicCap was stopped. LG revoked histogram capture; the controller cleared priority 149 and waited five seconds before reinitializing. Hyperion LED-stream measurements recorded an all-black interval of approximately 4.9 seconds. WLED was reachable and reported an active DDP stream from the TV; no WLED settings were changed.
Version 0.7.1 preserves the last update's existing 1.5-second expiry during a revocation recovery, instead of explicitly clearing it. Initialization retries begin after 200 ms with two additional 400 ms retry intervals before returning to normal five-second pacing. This does not extend stale colors indefinitely. Explicit capture stops and PicCap handovers retain their clear behavior. Status records recovery count and elapsed initialization time.
ARM retry and handover regression tests passed on the G4, as did remote navigation tests. Version 0.7.1 was installed and elevated on the TV. A later actual LG revocation recovered in 762 ms, including one refused initialization attempt before success. A 55-second Hyperion observation contained 125 color samples with no all-black output. These measurements do not guarantee that prolonged LG capture unavailability or actual black video scenes cannot darken the LEDs.
+8 -2
View File
@@ -5,7 +5,7 @@
#include <limits.h>
#define RETRY_DELAY_MS 5000
/* Continuous autostart retries and bounded manual source recovery. */
typedef struct {uint64_t due;unsigned attempts;bool automatic,paused,blocked;} RetryState;
typedef struct {uint64_t due;unsigned attempts,quick_retries;bool automatic,paused,blocked;} RetryState;
static void retry_reset(RetryState *state){*state=(RetryState){0};}
static void retry_pause(RetryState *state){retry_reset(state);state->paused=true;}
static void retry_update(RetryState *state,bool enabled,bool running,uint64_t now){
@@ -19,6 +19,12 @@ static bool retry_take(RetryState *state,uint64_t now){
state->due=0;if(state->attempts<UINT_MAX)state->attempts++;return true;
}
static void retry_failure(RetryState *state,uint64_t now){
if(!state->paused && !state->blocked && (state->automatic || state->attempts<6))state->due=now+RETRY_DELAY_MS;
if(!state->paused && !state->blocked && (state->automatic || state->attempts<6)){
if(state->quick_retries){state->quick_retries--;state->due=now+400;}
else state->due=now+RETRY_DELAY_MS;
}
}
static inline void retry_source_transition(RetryState *state,uint64_t now){
if(!state->paused && !state->blocked){state->attempts=0;state->quick_retries=2;state->due=now+200;}
}
#endif
+9 -5
View File
@@ -61,6 +61,8 @@ static unsigned led_count;
static LedBlend *led_blends;
static bool interpolation_enabled;
static RetryState retries;
static unsigned capture_recoveries;
static uint64_t recovery_started,last_recovery_ms;
static uint64_t milliseconds(void){struct timespec t;clock_gettime(CLOCK_MONOTONIC,&t);return (uint64_t)t.tv_sec*1000+t.tv_nsec/1000000;}
static char last_error[256];
static volatile sig_atomic_t revoked,quitting;
@@ -111,12 +113,13 @@ static bool read_layout(void){
}
bool ok=complete && layout_from_json(data,&led_blends,&led_count);free(data);interpolation_enabled=ok;return ok;
}
static void stop_capture(void){
static void release_capture(bool clear_output){
free(led_blends);led_blends=NULL;led_count=0;interpolation_enabled=false;
color_valid=false;memset(rgb,0,sizeof(rgb));memset(sent_rgb,0,sizeof(sent_rgb));
if(output>=0){if(frames){char line[80];snprintf(line,sizeof(line),"{\"command\":\"clear\",\"priority\":%d}\n",PRIORITY);send_line(line);}close(output);output=-1;}
if(output>=0){if(clear_output && frames){char line[80];snprintf(line,sizeof(line),"{\"command\":\"clear\",\"priority\":%d}\n",PRIORITY);send_line(line);}close(output);output=-1;}
if(capture){vt_finish(capture);capture=NULL;}
}
static void stop_capture(void){release_capture(true);}
static bool respond(void *h,void *m,const char *s){if(!h||!m)return true;LSError e;error_init(&e);bool ok=reply(h,m,s,&e);error_free(&e);return ok;}
#include "handover.h"
static bool start_failure(void *h,void *m,const char *message){
@@ -143,7 +146,8 @@ static bool start(void *h,void *m,void *ctx){
if(!capture){stop_capture();set_error("LG refused histogram initialization");return start_failure(h,m,"LG refused histogram initialization. Another capture source may hold VT_CAPTURE.");}
if(vt_preset(capture,0)!=0){stop_capture();set_error("LG histogram preset initialization failed");return start_failure(h,m,"Histogram preset failed");}
capture_preset=letterbox_mode=settling=0;pending_letterbox=-1;next_border_probe=0;
retries.due=0;retries.attempts=0;
retries.due=0;retries.attempts=0;retries.quick_retries=0;
if(recovery_started){last_recovery_ms=milliseconds()-recovery_started;recovery_started=0;}
return respond(h,m,"{\"returnValue\":true,\"running\":true}");
}
static bool stop(void *h,void *m,void *ctx){(void)ctx;retry_pause(&retries);stop_capture();return respond(h,m,"{\"returnValue\":true,\"running\":false}");}
@@ -171,7 +175,7 @@ static bool handover(void *h,void *m,void *ctx){
static bool status(void *h,void *m,void *ctx){
(void)ctx;char json[4096],safe[512];size_t n=0;
for(size_t i=0;last_error[i] && n+2<sizeof(safe);i++){char c=last_error[i];if(c=='"'||c=='\\')safe[n++]='\\';if((unsigned char)c>=32)safe[n++]=c;}safe[n]=0;
int used=snprintf(json,sizeof(json),"{\"returnValue\":true,\"version\":\"0.7.0\",\"running\":%s,\"colorValid\":%s,\"frames\":%lu,\"endpoint\":\"127.0.0.1:19444\",\"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,PRIORITY,(unsigned)geteuid(),sent_rgb[0],sent_rgb[1],sent_rgb[2],safe);
int used=snprintf(json,sizeof(json),"{\"returnValue\":true,\"version\":\"0.7.1\",\"running\":%s,\"colorValid\":%s,\"frames\":%lu,\"captureRecoveries\":%u,\"lastRecoveryMs\":%llu,\"endpoint\":\"127.0.0.1:19444\",\"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,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]);
@@ -188,7 +192,7 @@ static int tick(void *unused){
}
return 1;
}
if(revoked){set_error("LG revoked capture; retrying initialization after source transition");stop_capture();retries.attempts=0;retry_failure(&retries,milliseconds());return 1;}
if(revoked){set_error("LG revoked capture; recovering after source transition");capture_recoveries++;recovery_started=milliseconds();release_capture(false);retry_source_transition(&retries,milliseconds());return 1;}
if(settling){settling--;return 1;}
if(letterbox_mode && capture_preset==1 && milliseconds()>=next_border_probe){
if(vt_preset(capture,0)!=0){set_error("Border probe preset failed");stop_capture();return 1;}
+7 -1
View File
@@ -21,5 +21,11 @@ int main(void){
assert(state.attempts==6 && !state.due);
retry_reset(&state);retry_update(&state,true,true,now);assert(!state.due);
retry_update(&state,true,false,now);assert(state.due==now+5000);
puts("PASS: 10000 delayed-start attempts, five-second pacing, Stop pause, autostart off, safety block, bounded manual recovery, disconnect retry");
retry_reset(&state);retry_source_transition(&state,now);assert(state.due==now+200);
assert(!retry_take(&state,now+199));assert(retry_take(&state,now+200));retry_failure(&state,now+200);assert(state.due==now+600);
assert(retry_take(&state,now+600));retry_failure(&state,now+600);assert(state.due==now+1000);
assert(retry_take(&state,now+1000));retry_failure(&state,now+1000);assert(state.due==now+6000);
retry_pause(&state);retry_source_transition(&state,now);assert(!state.due);
retry_reset(&state);state.blocked=true;retry_source_transition(&state,now);assert(!state.due);
puts("PASS: startup retries, bounded rapid source recovery, Stop pause and cleanup safety block");
}
+1 -1
View File
@@ -1 +1 @@
{"id": "org.webosbrew.histogramhyperion", "version": "0.7.0", "vendor": "Local development", "app": "org.webosbrew.histogramhyperion", "services": ["org.webosbrew.histogramhyperion.service"], "sourceUrl": "https://gitea.adnau.de/matze19999/HyperDRM"}
{"id": "org.webosbrew.histogramhyperion", "version": "0.7.1", "vendor": "Local development", "app": "org.webosbrew.histogramhyperion", "services": ["org.webosbrew.histogramhyperion.service"], "sourceUrl": "https://gitea.adnau.de/matze19999/HyperDRM"}
Binary file not shown.