Build the service in a container, and fix what the target compiler found
The openlgtv NDK is a Linux toolchain with no macOS or Windows build, so
tools/build.sh could not produce a binary anywhere else. docker-build.sh
bakes the SDK into an image and compiles there; packaging and deploy stay
on the host, where the TV is reachable. The SDK ships aarch64 as well as
x86_64, so the image picks the one matching the daemon and Apple Silicon
builds natively rather than under emulation.
Cross-compiling for real turned up three things the host compiler did
not:
sink_hyperhdr_viz.c read p->width and p->height to format the error
message after free(p)
sink_hyperhdr.c an SDP connection line of 128 bytes cannot hold
"IN IP4 " plus a 127-byte host plus "/255", so a
long hostname would silently lose its TTL suffix
common/log.c the log body was sized to the whole ring line,
leaving nothing for the prefix; budget for it so
the bound is provable rather than left to
snprintf
A clean cross-compile is now warning-free, and readelf confirms the
design rule holds: luna-service2, glib, PmLogLib and libc, with no
libpulse or libasound.
Also: @webosose/ares-cli was pinned to ^3.0.0, which does not exist
(latest is 2.4.0), so npm install failed outright. build.sh now puts
node_modules/.bin on PATH so a local install is enough.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7529a60650
commit
3e4d0e17bc
@@ -4,3 +4,4 @@ node_modules/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.DS_Store
|
||||
.vscode/
|
||||
|
||||
@@ -61,7 +61,16 @@ Channel's *Install from file*, or from a workstation:
|
||||
ares-install --device tv out/org.webosbrew.audiocap_1.0.0_all.ipk
|
||||
```
|
||||
|
||||
**From source.** See [docs/development.md](docs/development.md).
|
||||
**From source.** The NDK is Linux-only, so on macOS or Windows the compile goes
|
||||
through Docker:
|
||||
|
||||
```sh
|
||||
npm install # ares-cli
|
||||
./tools/docker-build.sh # or ./tools/build.sh native on Linux
|
||||
./tools/build.sh package # -> out/*.ipk
|
||||
```
|
||||
|
||||
See [docs/development.md](docs/development.md) for the rest.
|
||||
|
||||
## First run
|
||||
|
||||
|
||||
+29
-6
@@ -4,17 +4,32 @@
|
||||
|
||||
| For | Install |
|
||||
| --- | --- |
|
||||
| the native service | the [openlgtv buildroot NDK](https://github.com/openlgtv/buildroot-nc4/releases), `arm-webos-linux-gnueabi_sdk-buildroot` |
|
||||
| packaging | `npm install -g @webosose/ares-cli` |
|
||||
| the native service | the [openlgtv buildroot NDK](https://github.com/openlgtv/buildroot-nc4/releases) — or Docker, see below |
|
||||
| packaging | `npm install` (ares-cli), or `npm install -g @webosose/ares-cli` |
|
||||
| the tests | a host C compiler, Python 3, Node (optional: `flatbuffers`, `jsdom`) |
|
||||
|
||||
Unpack the NDK and relocate it once:
|
||||
### The toolchain
|
||||
|
||||
The NDK is a **Linux** toolchain — there is no macOS or Windows build of it. On
|
||||
Linux, unpack and relocate it once:
|
||||
|
||||
```sh
|
||||
tar xf arm-webos-linux-gnueabi_sdk-buildroot.tar.gz -C "$HOME"
|
||||
tar xf arm-webos-linux-gnueabi_sdk-buildroot-x86_64.tar.gz -C "$HOME"
|
||||
"$HOME/arm-webos-linux-gnueabi_sdk-buildroot/relocate-sdk.sh"
|
||||
```
|
||||
|
||||
Everywhere else, build the native part in a container:
|
||||
|
||||
```sh
|
||||
./tools/docker-build.sh # -> build/native/audiocap-service
|
||||
```
|
||||
|
||||
That bakes the SDK into an image, so it downloads once and later builds start
|
||||
immediately. There are aarch64 and x86_64 SDK builds and the image picks
|
||||
whichever matches the container, so on Apple Silicon it runs natively rather
|
||||
than under emulation. Only the compile happens in the container; packaging and
|
||||
deployment run on the host, where the TV is reachable.
|
||||
|
||||
Register the TV with ares once, using the Homebrew Channel's ssh (port 9922,
|
||||
root):
|
||||
|
||||
@@ -32,11 +47,19 @@ ares-setup-device --add tv \
|
||||
./tools/build.sh logs
|
||||
```
|
||||
|
||||
With the container toolchain it is two steps, since `build.sh` only knows how
|
||||
to drive a local NDK:
|
||||
|
||||
```sh
|
||||
./tools/docker-build.sh && ./tools/build.sh package
|
||||
```
|
||||
|
||||
`DEVICE=livingroom ./tools/build.sh install` targets a different device;
|
||||
`WEBOS_SDK=/opt/webos-sdk ./tools/build.sh` a differently placed NDK.
|
||||
|
||||
The same commands exist as npm scripts (`npm run build`, `npm run deploy`, …)
|
||||
if that is more your habit.
|
||||
if that is more your habit. `build.sh` puts `node_modules/.bin` on PATH first,
|
||||
so a local `npm install` of ares-cli is enough — no global install needed.
|
||||
|
||||
### What the packaging step does
|
||||
|
||||
@@ -92,7 +115,7 @@ npm run serve # http://localhost:8000
|
||||
|
||||
```sh
|
||||
python3 tools/make-manifest.py \
|
||||
--base-url https://github.com/you/lgtv-audio-cap/releases/download/v1.0.0
|
||||
--base-url https://git.crylia.de/Crylia/lgtv_audio_cap/releases/download/v1.0.0
|
||||
```
|
||||
|
||||
5. Submit the manifest URL to [webosbrew/repo](https://github.com/webosbrew/repo).
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#define RING_LINES 200
|
||||
#define RING_LINE_LEN 256
|
||||
// "HH:MM:SS.mmm [level] file.c:1234 " — 64 covers it with room to spare.
|
||||
#define RING_LINE_PREFIX 64
|
||||
|
||||
static log_level_t s_level = LOG_INFO;
|
||||
static pthread_mutex_t s_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
@@ -56,7 +58,10 @@ void log_printf(log_level_t level, const char* file, int line, const char* fmt,
|
||||
snprintf(stamp, sizeof(stamp), "%02d:%02d:%02d.%03d", tm.tm_hour, tm.tm_min,
|
||||
tm.tm_sec, (int)(tv.tv_usec / 1000));
|
||||
|
||||
char body[RING_LINE_LEN];
|
||||
// A ring line is a fixed size, so an over-long message is truncated rather
|
||||
// than allowed to grow the buffer. Budgeting for the prefix here keeps the
|
||||
// final snprintf provably within bounds instead of relying on it to clip.
|
||||
char body[RING_LINE_LEN - RING_LINE_PREFIX];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(body, sizeof(body), fmt, ap);
|
||||
|
||||
@@ -79,7 +79,9 @@ static int build_sdp(hh_priv_t* p, char* out, size_t cap)
|
||||
char src_str[INET_ADDRSTRLEN];
|
||||
snprintf(src_str, sizeof(src_str), "%s", inet_ntoa(src));
|
||||
|
||||
char conn[128];
|
||||
// Wide enough for the longest host plus the prefix and the TTL suffix, so
|
||||
// a long hostname cannot quietly lose its "/255" to truncation.
|
||||
char conn[sizeof(p->host) + 16];
|
||||
if (p->multicast) {
|
||||
// The /255 suffix is the TTL, required for multicast connection lines.
|
||||
snprintf(conn, sizeof(conn), "IN IP4 %s/255", p->host);
|
||||
|
||||
@@ -388,9 +388,9 @@ static sink_t* viz_open(const json_value_t* cfg, const audio_format_t* fmt, char
|
||||
p->frame_bytes = (size_t)p->width * (size_t)p->height * 3;
|
||||
p->frame = calloc(1, p->frame_bytes);
|
||||
if (!p->frame) {
|
||||
snprintf(err, errlen, "out of memory allocating %dx%d frame", p->width, p->height);
|
||||
free(p);
|
||||
free(s);
|
||||
snprintf(err, errlen, "out of memory allocating %dx%d frame", p->width, p->height);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
Generated
+5512
File diff suppressed because it is too large
Load Diff
+11
-3
@@ -2,12 +2,20 @@
|
||||
"name": "lgtv-audio-cap",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Captures audio on an LG webOS 5/6 TV and streams it out — HyperHDR first, plus raw UDP, TCP and HTTP.",
|
||||
"keywords": ["webos", "lgtv", "hyperhdr", "hyperion", "audio", "webosbrew"],
|
||||
"description": "Captures audio on an LG webOS 5/6 TV and streams it out \u2014 HyperHDR first, plus raw UDP, TCP and HTTP.",
|
||||
"keywords": [
|
||||
"webos",
|
||||
"lgtv",
|
||||
"hyperhdr",
|
||||
"hyperion",
|
||||
"audio",
|
||||
"webosbrew"
|
||||
],
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "tools/build.sh",
|
||||
"native": "tools/build.sh native",
|
||||
"docker": "tools/docker-build.sh",
|
||||
"stage": "tools/build.sh stage",
|
||||
"package": "tools/build.sh package",
|
||||
"deploy": "tools/build.sh install && tools/build.sh launch",
|
||||
@@ -21,7 +29,7 @@
|
||||
"serve": "python3 -m http.server 8000 --directory frontend"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@webosose/ares-cli": "^3.0.0",
|
||||
"@webosose/ares-cli": "^2.4.0",
|
||||
"jsdom": "^24.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
# `npm install` puts ares-cli here rather than on PATH, so a local install works
|
||||
# without also needing the global one.
|
||||
[ -d "$ROOT/node_modules/.bin" ] && PATH="$ROOT/node_modules/.bin:$PATH"
|
||||
|
||||
APP_ID=org.webosbrew.audiocap
|
||||
SERVICE_ID=$APP_ID.service
|
||||
BINARY=audiocap-service
|
||||
@@ -52,6 +56,11 @@ Download and unpack the buildroot NDK, then point WEBOS_SDK at it:
|
||||
\$HOME/arm-webos-linux-gnueabi_sdk-buildroot/relocate-sdk.sh
|
||||
|
||||
WEBOS_SDK=\$HOME/arm-webos-linux-gnueabi_sdk-buildroot ./tools/build.sh
|
||||
|
||||
The SDK only runs on Linux. On macOS or Windows, compile in a container
|
||||
instead and then come back here to package:
|
||||
|
||||
./tools/docker-build.sh && ./tools/build.sh package
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
# Cross-compiles the service inside a container, for hosts that cannot run the
|
||||
# NDK directly. The openlgtv buildroot SDK is a Linux toolchain, so on macOS or
|
||||
# Windows this is the only way to build the native part.
|
||||
#
|
||||
# ./tools/docker-build.sh # build the service into build/native/
|
||||
# ./tools/docker-build.sh shell # a prompt inside the SDK, for poking about
|
||||
# ./tools/docker-build.sh clean # drop the image
|
||||
#
|
||||
# The SDK is baked into an image, so it is downloaded once (~350 MB) and every
|
||||
# build after the first starts instantly. There are aarch64 and x86_64 builds
|
||||
# of the SDK and the image picks the one matching the container, so this runs
|
||||
# natively on Apple Silicon rather than under emulation.
|
||||
#
|
||||
# NDK_RELEASE pins the SDK release; DOCKER is podman if you prefer.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
DOCKER="${DOCKER:-docker}"
|
||||
NDK_RELEASE="${NDK_RELEASE:-webos-a38c582}"
|
||||
IMAGE="${IMAGE:-lgtv-audiocap-sdk:$NDK_RELEASE}"
|
||||
SDK=/opt/webos-sdk
|
||||
TOOLCHAIN="$SDK/share/buildroot/toolchainfile.cmake"
|
||||
BUILD_DIR=build/native
|
||||
|
||||
say() { printf '%s\n' "$*"; }
|
||||
step() { printf '\n== %s\n' "$*"; }
|
||||
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
command -v "$DOCKER" >/dev/null 2>&1 || die "$DOCKER is not installed"
|
||||
|
||||
build_image() {
|
||||
if "$DOCKER" image inspect "$IMAGE" >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
step "Building the SDK image (once, ~350 MB download)"
|
||||
# The arch is resolved inside the build so the image is right for whatever
|
||||
# the daemon runs, without the caller having to know.
|
||||
"$DOCKER" build --tag "$IMAGE" - <<EOF
|
||||
FROM debian:bookworm-slim
|
||||
RUN apt-get update \\
|
||||
&& apt-get install -y --no-install-recommends \\
|
||||
ca-certificates curl cmake make file python3 \\
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN case "\$(uname -m)" in \\
|
||||
aarch64|arm64) arch=aarch64 ;; \\
|
||||
x86_64|amd64) arch=x86_64 ;; \\
|
||||
*) echo "no webOS SDK for \$(uname -m)" >&2; exit 1 ;; \\
|
||||
esac \\
|
||||
&& mkdir -p $SDK \\
|
||||
&& curl -fL "https://github.com/openlgtv/buildroot-nc4/releases/download/$NDK_RELEASE/arm-webos-linux-gnueabi_sdk-buildroot-\$arch.tar.gz" \\
|
||||
| tar xz -C $SDK --strip-components=1 \\
|
||||
&& $SDK/relocate-sdk.sh
|
||||
EOF
|
||||
}
|
||||
|
||||
# --user keeps build/ owned by the caller instead of root. HOME has to point
|
||||
# somewhere writable or cmake complains about its package registry.
|
||||
run() {
|
||||
"$DOCKER" run --rm \
|
||||
--volume "$ROOT:/src" \
|
||||
--workdir /src \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--env HOME=/tmp \
|
||||
"$@"
|
||||
}
|
||||
|
||||
compile() {
|
||||
build_image
|
||||
|
||||
# A cache from a differently placed toolchain cannot be reused.
|
||||
if [ -f "$BUILD_DIR/CMakeCache.txt" ] \
|
||||
&& ! grep -q "CMAKE_TOOLCHAIN_FILE:.*=$TOOLCHAIN" "$BUILD_DIR/CMakeCache.txt"; then
|
||||
say "discarding a cmake cache from a different toolchain"
|
||||
rm -rf "$BUILD_DIR"
|
||||
fi
|
||||
|
||||
step "Cross-compiling the service"
|
||||
run --interactive=false "$IMAGE" sh -c "
|
||||
set -e
|
||||
cmake -S native -B $BUILD_DIR \
|
||||
-DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build $BUILD_DIR --parallel
|
||||
file $BUILD_DIR/audiocap-service
|
||||
"
|
||||
|
||||
[ -f "$BUILD_DIR/audiocap-service" ] || die "the build produced no binary"
|
||||
say ""
|
||||
say "built $BUILD_DIR/audiocap-service"
|
||||
say "now: ./tools/build.sh package"
|
||||
}
|
||||
|
||||
case "${1:-build}" in
|
||||
build) compile ;;
|
||||
shell)
|
||||
build_image
|
||||
step "A shell in the SDK image; the toolchain is at $SDK"
|
||||
run --interactive --tty "$IMAGE" bash
|
||||
;;
|
||||
clean)
|
||||
"$DOCKER" image rm "$IMAGE" 2>/dev/null && say "removed $IMAGE" || say "no image to remove"
|
||||
;;
|
||||
-h|--help)
|
||||
awk 'NR == 1 { next } /^#/ { sub(/^# ?/, ""); print; next } { exit }' "$0" ;;
|
||||
*) die "unknown command '$1' (try --help)" ;;
|
||||
esac
|
||||
@@ -22,7 +22,7 @@ import sys
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.join(HERE, os.pardir)
|
||||
|
||||
SOURCE_URL = "https://github.com/webosbrew/lgtv-audio-cap"
|
||||
SOURCE_URL = "https://git.crylia.de/Crylia/lgtv_audio_cap"
|
||||
|
||||
|
||||
def newest_ipk(directory):
|
||||
|
||||
Reference in New Issue
Block a user