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
+77
View File
@@ -0,0 +1,77 @@
// Harness for verify_rtp.py: opens the real HyperHDR RTP sink, points it at a
// port on the loopback and writes a deterministic ramp through it. The Python
// side receives the datagrams and checks that what comes out of the wire is
// exactly what went in.
//
// rtp_send <port> <blocks> [frames-per-block] [sap]
#include "../native/src/common/audio.h"
#include "../native/src/common/json.h"
#include "../native/src/common/log.h"
#include "../native/src/dsp.h"
#include "../native/src/sinks/sink.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Must match sample_at() in verify_rtp.py.
static int16_t sample_at(long index)
{
return (int16_t)((index * 251) % 65536 - 32768);
}
int main(int argc, char** argv)
{
if (argc < 3) {
fprintf(stderr, "usage: %s <port> <blocks> [frames]\n", argv[0]);
return 2;
}
int port = atoi(argv[1]);
int blocks = atoi(argv[2]);
int frames = argc > 3 ? atoi(argv[3]) : AUDIO_BLOCK_FRAMES;
bool sap = argc > 4 && strcmp(argv[4], "sap") == 0;
log_set_level(LOG_ERROR);
char cfg_text[256];
snprintf(cfg_text, sizeof(cfg_text),
"{\"hyperhdr\":{\"host\":\"127.0.0.1\",\"port\":%d,"
"\"multicast\":false,\"sapAnnounce\":%s}}",
port, sap ? "true" : "false");
json_value_t* cfg = json_parse(cfg_text);
if (!cfg) {
fprintf(stderr, "bad config\n");
return 1;
}
audio_format_t fmt = { .rate = 48000, .channels = 2 };
char err[256] = { 0 };
sink_t* sink = sink_open("hyperhdr", cfg, &fmt, err, sizeof(err));
if (!sink) {
fprintf(stderr, "sink_open failed: %s\n", err);
json_free(cfg);
return 1;
}
int16_t* pcm = malloc((size_t)frames * fmt.channels * sizeof(int16_t));
dsp_levels_t levels;
memset(&levels, 0, sizeof(levels));
long index = 0;
for (int b = 0; b < blocks; b++) {
for (int i = 0; i < frames * fmt.channels; i++)
pcm[i] = sample_at(index++);
sink->write(sink, pcm, frames, &levels);
}
printf("%ld\n", index); // samples written, for the receiver to expect
fflush(stdout);
free(pcm);
sink_close(sink);
json_free(cfg);
return 0;
}