Audio capture and streaming app for webOS 5/6

Captures the TV's audio and sends it out over several transports. The
primary one is HyperHDR: RTP/L16 to a host-side loopback device, since
HyperHDR has no network audio input of its own. A second route renders
the spectrum on the TV and sends FlatBuffers images to port 19400
instead, for setups where touching the host's sound config is not an
option.

  native/       the service: capture backends (PulseAudio, ALSA, exec,
                test tone, all dlopen-based), DSP, and one file per sink
  frontend/     D-pad driven UI at a fixed 1920x1080
  servicefiles/ native service manifest plus the boot script
  host/         RTP receiver and the loopback installer for the HyperHDR
                machine
  tools/        build/package, asset generation, Homebrew Channel
                manifest, on-TV probe
  test/         host-side suites: FlatBuffers and RTP verified against
                real decoders, the engine end to end, the page in jsdom

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Rene Kievits
2026-08-26 10:21:00 +02:00
co-authored by Claude Opus 5
commit 7529a60650
73 changed files with 12826 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
// Emits the exact bytes hyperion.c would put on the wire, so the FlatBuffers
// encoding can be checked against the reference implementation.
//
// ./fb_dump register out.bin
// ./fb_dump image out.bin
#include "../native/src/net/hyperion.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char** argv)
{
if (argc < 3) {
fprintf(stderr, "usage: %s <register|image> <output-file>\n", argv[0]);
return 2;
}
size_t len = 0;
uint8_t* buf = NULL;
if (strcmp(argv[1], "register") == 0) {
buf = hyperion_build_register("lgtv-audio-cap", 150, &len);
} else if (strcmp(argv[1], "image") == 0) {
// 4x2 RGB gradient: distinctive enough that a wrong vector offset or
// a swapped width/height shows up immediately.
const int w = 4, h = 2;
uint8_t rgb[4 * 2 * 3];
for (int i = 0; i < w * h; i++) {
rgb[i * 3 + 0] = (uint8_t)(i * 10);
rgb[i * 3 + 1] = (uint8_t)(i * 10 + 1);
rgb[i * 3 + 2] = (uint8_t)(i * 10 + 2);
}
buf = hyperion_build_image(rgb, w, h, &len);
} else {
fprintf(stderr, "unknown message '%s'\n", argv[1]);
return 2;
}
if (!buf) {
fprintf(stderr, "build failed\n");
return 1;
}
FILE* f = fopen(argv[2], "wb");
if (!f) {
perror("fopen");
free(buf);
return 1;
}
fwrite(buf, 1, len, f);
fclose(f);
free(buf);
fprintf(stderr, "wrote %zu bytes to %s\n", len, argv[2]);
return 0;
}