Files
Rene KievitsandClaude Opus 5 7529a60650 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>
2026-08-26 10:21:00 +02:00

144 lines
4.3 KiB
Bash
Executable File

#!/bin/sh
# Reports what audio a rooted webOS TV actually exposes.
#
# Which capture backend works depends on the model and firmware: some sets run
# PulseAudio with a monitor source, some only offer ALSA, some need an external
# helper. Run this once on the TV and the answer is usually obvious.
#
# On the TV:
# sh tv-probe.sh
#
# From here, over the Homebrew Channel's ssh:
# ssh -p 9922 root@TV-IP 'sh -s' < tools/tv-probe.sh
# ares-shell --device tv -r "$(cat tools/tv-probe.sh)"
#
# Reads only. Nothing here changes the TV.
header() {
printf '\n=== %s\n' "$1"
}
have() {
command -v "$1" >/dev/null 2>&1
}
show() {
# show <label> <file>
if [ -r "$2" ]; then
printf -- '--- %s\n' "$1"
cat "$2"
fi
}
header "Identity"
printf 'uid: %s\n' "$(id -u 2>/dev/null)"
printf 'kernel: %s\n' "$(uname -r 2>/dev/null)"
printf 'machine: %s\n' "$(uname -m 2>/dev/null)"
for f in /etc/starfish-release /etc/os-release; do
show "$f" "$f"
done
if [ -r /var/run/nyx/device_info.json ]; then
printf -- '--- device info\n'
cat /var/run/nyx/device_info.json
printf '\n'
fi
header "Sound devices"
if [ -d /dev/snd ]; then
ls -l /dev/snd
else
echo "no /dev/snd — the ALSA backend cannot work"
fi
header "ALSA"
show "cards" /proc/asound/cards
show "pcm" /proc/asound/pcm
show "modules" /proc/asound/modules
if [ -d /proc/asound ]; then
for dir in /proc/asound/card*/pcm*c; do
[ -d "$dir" ] || continue
printf -- '--- capture stream %s\n' "$dir"
[ -r "$dir/info" ] && cat "$dir/info"
done
else
echo "no /proc/asound — no ALSA on this firmware"
fi
header "Tools"
for tool in arecord aplay amixer pactl parec pacat pulseaudio gst-launch-1.0 ffmpeg luna-send; do
if have "$tool"; then
printf '%-16s %s\n' "$tool" "$(command -v "$tool")"
else
printf '%-16s -\n' "$tool"
fi
done
header "Libraries"
# The service dlopen()s these rather than linking them, so what matters is
# whether the file exists at all and under which soname.
for pattern in \
/usr/lib/libasound.so* /lib/libasound.so* \
/usr/lib/libpulse.so* /lib/libpulse.so* \
/usr/lib/libpulse-simple.so* /lib/libpulse-simple.so*
do
for lib in $pattern; do
[ -e "$lib" ] && ls -l "$lib"
done
done 2>/dev/null
header "PulseAudio"
ps ax 2>/dev/null | grep -i '[p]ulseaudio' || ps 2>/dev/null | grep -i '[p]ulseaudio' \
|| echo "no pulseaudio process found"
printf -- '--- sockets\n'
for sock in /var/run/pulse/native /run/pulse/native /tmp/pulse-*/native \
/var/run/pulse/*.socket /dev/socket/pulse/*
do
[ -e "$sock" ] && ls -l "$sock"
done 2>/dev/null
printf -- '--- environment of the audio daemon\n'
for pid in $(ps ax 2>/dev/null | grep -i '[a]udiod\|[p]ulseaudio' | awk '{print $1}'); do
if [ -r "/proc/$pid/environ" ]; then
printf 'pid %s: ' "$pid"
tr '\0' '\n' < "/proc/$pid/environ" | grep -i 'PULSE\|XDG_RUNTIME' | tr '\n' ' '
printf '\n'
fi
done
if have pactl; then
printf -- '--- pactl info\n'
PULSE_SERVER="${PULSE_SERVER:-unix:/var/run/pulse/native}" pactl info 2>&1 | head -20
printf -- '--- sources (monitors are what to capture)\n'
PULSE_SERVER="${PULSE_SERVER:-unix:/var/run/pulse/native}" pactl list short sources 2>&1
fi
if have arecord; then
header "ALSA capture devices (arecord -l)"
arecord -l 2>&1
printf -- '--- PCMs (arecord -L)\n'
arecord -L 2>&1 | head -40
fi
header "What to put in the app"
cat <<'EOF'
Look at the output above and set Capture > Backend accordingly:
PulseAudio a pulseaudio process and a socket exist, and pactl lists a
source ending in ".monitor". Use that name as the Device, or
leave Device blank to take the default monitor.
ALSA /proc/asound lists a card with a capture PCM. Use hw:X,Y with
the card and device numbers from "arecord -l".
Command neither works but arecord/parec/gst-launch is present. Set the
Command to something that writes raw S16LE to stdout, e.g.
arecord -D hw:0,0 -f S16_LE -r 48000 -c 2 -t raw
Test tone nothing at all. Use this to prove the network path works while
you keep looking.
If everything is missing, the audio path on this firmware is locked inside the
proprietary audio daemon. Every sink depends on captured audio, so the test
tone is all that will run until a capture route is found — use it to prove the
network side, then come back to this list.
EOF