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>
124 lines
3.7 KiB
C
124 lines
3.7 KiB
C
// Entry point for the native Luna service.
|
|
//
|
|
// Nothing interesting happens here: register on the bus, hand control to the
|
|
// glib main loop, and make sure a SIGTERM from the service launcher shuts the
|
|
// capture down cleanly so sinks get to say goodbye (HyperHDR in particular
|
|
// needs its Clear, or the LEDs freeze on the last frame we sent).
|
|
|
|
#include "common/log.h"
|
|
#include "service.h"
|
|
|
|
#include <glib-unix.h>
|
|
#include <glib.h>
|
|
#include <luna-service2/lunaservice.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#define SERVICE_NAME "org.webosbrew.audiocap.service"
|
|
|
|
// webOS 3.5 and earlier need the service registered on the public bus too.
|
|
// Declared weak so the same binary keeps loading on newer firmware where the
|
|
// symbol was removed.
|
|
extern bool LSRegisterPubPriv(const char* name, LSHandle** handle, bool public_bus,
|
|
LSError* error) __attribute__((weak));
|
|
|
|
static gboolean on_signal(gpointer user)
|
|
{
|
|
GMainLoop* loop = user;
|
|
INFO("Signal received, shutting down");
|
|
g_main_loop_quit(loop);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void log_environment(void)
|
|
{
|
|
uid_t uid = getuid();
|
|
INFO("lgtv-audio-cap service starting (uid=%d%s)", (int)uid,
|
|
uid == 0 ? ", root" : ", unprivileged");
|
|
if (uid != 0) {
|
|
// Without root the PulseAudio socket and the ALSA devices are usually
|
|
// out of reach, so say this once rather than leaving the user to
|
|
// decode a permission error later.
|
|
WARN("Not running as root: audio devices are likely inaccessible.");
|
|
WARN("Install the Homebrew Channel 'elevate-service' patch and restart the service.");
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
bool debug = false;
|
|
for (int i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--debug") == 0)
|
|
debug = true;
|
|
}
|
|
|
|
log_init(debug ? LOG_DEBUG : LOG_INFO);
|
|
log_environment();
|
|
|
|
// A client that hangs up mid-stream must not kill the service. Every send
|
|
// path already asks for MSG_NOSIGNAL; this covers the rest.
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
GMainLoop* loop = g_main_loop_new(NULL, false);
|
|
|
|
LSError lserror;
|
|
LSErrorInit(&lserror);
|
|
|
|
LSHandle* handle = NULL;
|
|
bool registered = LSRegisterPubPriv
|
|
? LSRegisterPubPriv(SERVICE_NAME, &handle, true, &lserror)
|
|
: LSRegister(SERVICE_NAME, &handle, &lserror);
|
|
if (!registered) {
|
|
ERR("Cannot register %s on the Luna bus: %s", SERVICE_NAME, lserror.message);
|
|
LSErrorFree(&lserror);
|
|
g_main_loop_unref(loop);
|
|
return 1;
|
|
}
|
|
|
|
if (!LSGmainAttach(handle, loop, &lserror)) {
|
|
ERR("Cannot attach to the main loop: %s", lserror.message);
|
|
LSErrorFree(&lserror);
|
|
LSUnregister(handle, &lserror);
|
|
g_main_loop_unref(loop);
|
|
return 1;
|
|
}
|
|
|
|
service_t* service = service_create(handle, loop);
|
|
if (!service) {
|
|
ERR("Cannot create the service");
|
|
LSUnregister(handle, &lserror);
|
|
g_main_loop_unref(loop);
|
|
return 1;
|
|
}
|
|
|
|
char err[256] = { 0 };
|
|
if (!service_register(service, err, sizeof(err))) {
|
|
ERR("%s", err);
|
|
service_destroy(service);
|
|
LSUnregister(handle, &lserror);
|
|
g_main_loop_unref(loop);
|
|
return 1;
|
|
}
|
|
|
|
g_unix_signal_add(SIGTERM, on_signal, loop);
|
|
g_unix_signal_add(SIGINT, on_signal, loop);
|
|
|
|
INFO("Registered as %s", SERVICE_NAME);
|
|
service_autostart(service);
|
|
|
|
g_main_loop_run(loop);
|
|
|
|
INFO("Shutting down");
|
|
service_destroy(service);
|
|
|
|
if (!LSUnregister(handle, &lserror)) {
|
|
ERR("Unregister failed: %s", lserror.message);
|
|
LSErrorFree(&lserror);
|
|
}
|
|
g_main_loop_unref(loop);
|
|
return 0;
|
|
}
|