A working log of getting Hyperion-NG capture validated on old hardware. Splitter arrives tomorrow, LEDs not yet wired, but the capture pipeline is fully proven end to end.
The goal
Build a Govee/Philips-Hue-Sync style TV backlight that mirrors on-screen colors to an LED strip behind the TV. Source-agnostic (works with anything plugged into HDMI, not just smart TV apps), running fully local, no cloud, no subscription.
The architecture
Apple TV (4K) → HDCP-stripping HDMI splitter ─┬→ Samsung TV (4K passthrough)
└→ Auvidea B101 (1080p) →
Pi 3 (Hyperion-NG) →
Ethernet →
ESP32 (WLED) →
SK6812 LED strip
Three things matter about this architecture:
- The splitter strips HDCP so the capture device sees a clean signal. Without this, modern sources (Apple TV, PS5, anything streaming) refuse to output to a non-compliant capture path.
- The splitter downscales 4K → 1080p on Output 2 because the B101 caps at 1080p. The TV still gets full 4K HDR on Output 1.
- Pi handles capture and processing, ESP32 handles LEDs. Splitting the work this way is more reliable than one device doing both.
Hardware inventory
What I had on hand:
- Raspberry Pi 3 Model B v1.2
- Auvidea B101 HDMI-to-CSI capture board (TC358743 chip)
- CSI ribbon cable
What I ordered:
- HBAVLINK 1x2 HDMI Splitter (auto-downscaling, HDCP 2.2/2.3 bypass) — the model that explicitly lists “Apple TV 4K, Elgato HD60s/x/Pro, Retrotink” compatibility on the listing
- Still need: SK6812 RGBW strip (60 LED/m, 5m), ESP32-WROOM-32, 5V 10A PSU, capacitors/resistor for clean wiring
The Pi 3 + B101 combo is the reference Hyperion setup. The B101 plugs into the Pi’s CSI camera port via ribbon cable, which means capture goes through the GPU/ISP path instead of USB. That offloads enough work from the CPU that a Pi 3 can keep up with 1080p60 capture, which surprised me. I’d written off the Pi 3 initially.
OS setup
Flashed Raspberry Pi OS Lite (64-bit), Trixie release using Raspberry Pi Imager on a Mac.
The 64-bit version runs fine on the Pi 3 (BCM2837 is a 64-bit ARM Cortex-A53), and Hyperion has better-tuned 64-bit ARM builds. Lite, not Desktop, because this box is headless and you don’t want a GUI competing for resources.
What didn’t work the first time
First flash attempt: the Imager OS customization settings (hostname, SSH key, username) didn’t actually get written to the SD card. Booted into a vanilla install with no SSH, default user, default hostname. Cause was probably skipping or dismissing the “Apply OS customisation settings” dialog that pops up after clicking write.
Lesson: that dialog is mandatory for headless setup. If you miss it, you have to reflash or configure on the box directly with a keyboard.
Second flash: configured everything in the customization dialog upfront. Pi booted, joined WiFi, accepted SSH on first boot.
Diagnosing the failed boot
When the Pi only showed a red PWR LED (no green ACT activity), I ran through:
- Re-seat the SD card (friction fit, no click on Pi 3)
- Check power supply (Pi 3 needs a real 2.5A 5V source)
- Check ACT LED behavior on a working boot (rapid blinking during first ~30s, then sporadic)
- Plug in HDMI to a monitor as a fallback to see boot messages
Re-flashing solved it. Most likely cause: SD card flash verification silently failed.
Initial Pi configuration
Once SSH was working, set hostname and updated:
sudo apt update && sudo apt upgrade -y
sudo reboot
Then SSH’d back in as derek@hyperion.local.
Enabling the B101
Edit /boot/firmware/config.txt (note: not /boot/config.txt — the path changed in Bookworm and stayed in Trixie):
dtoverlay=tc358743
gpu_mem=128
The gpu_mem=128 is critical. Default 64MB is too tight on a Pi 3 for CSI capture buffers.
After reboot, verified the chip was detected:
dmesg | grep -i tc358743
Output:
[ 0.041913] /soc/csi@7e801000: Fixed dependency cycle(s) with /soc/i2c0mux/i2c@1/tc358743@f
[ 0.042020] /soc/i2c0mux/i2c@1/tc358743@f: Fixed dependency cycle(s) with /soc/csi@7e801000
[ 0.043604] /soc/csi@7e801000: Fixed dependency cycle(s) with /soc/i2c0mux/i2c@1/tc358743@f
[ 0.045271] /soc/i2c0mux/i2c@1/tc358743@f: Fixed dependency cycle(s) with /soc/csi@7e801000
[ 12.091266] tc358743 10-000f: tc358743 found @ 0x1e (i2c-11-mux (chan_id 1))
The “Fixed dependency cycle(s)” messages are harmless boot-time noise. The line that matters: tc358743 found @ 0x1e. Driver bound.
ls /dev/video*
Output: /dev/video0 exists alongside the Pi’s GPU encoder/decoder devices (video10–23, 31). Hyperion only cares about video0.
EDID setup (where things got messy)
The TC358743 needs an EDID telling source devices what resolutions it accepts. Without one, Apple TV (and most sources) won’t output anything.
What didn’t work
The Hyperion forum tutorials and Github gists all reference repos that 404 in 2026:
github.com/hyperion-project/RPi-Tools— gonegithub.com/peterpan007/RPi-tc358743-EDID— gonegithub.com/hyperion-project/HyperBian/raw/master/edid/— gone
I tried a manual hex-dump approach (writing the EDID bytes to a file via xxd -r), but my hex-dump output didn’t pass v4l2-ctl’s validation:
/home/derek/edid.bin contained an empty EDID, ignoring.
256 bytes on disk but invalid structure. Not worth debugging when there’s a better path.
Also hit some flag changes in newer v4l-utils (1.30+):
--fix-edid-checksumsno longer recognized- The
-dflag now needs to come before--set-edid - The
--set-edidargument now requirespad=0to be specified
What worked
The cleanest path: v4l2-ctl ships with built-in EDID generators. No external file needed.
v4l2-ctl -d /dev/v4l-subdev0 --set-edid=type=hdmi,pad=0
(Confirmed /dev/v4l-subdev0 was the right path via v4l2-ctl --list-devices — only one subdev existed and it was bound to the unicam/CSI device.)
Silent output = success. The B101 now advertises 1080p capability to whatever source is connected.
The validation hack: HDMI loopback
Splitter doesn’t arrive until tomorrow, but I wanted to validate the capture pipeline tonight. So I plugged the Pi’s own HDMI output back into the B101’s HDMI input.
This isn’t a “real” test (the Pi running headless Lite is just outputting a console framebuffer, no HDCP, no real content) but it does prove the capture path works end to end.
v4l2-ctl --query-dv-timings -d /dev/video0
Output:
Active width: 1920
Active height: 1080
Total width: 2200
Total height: 1125
Frame format: progressive
Polarities: -vsync -hsync
Pixelclock: 148500000 Hz (60.00 frames per second)
Horizontal frontporch: 0
Horizontal sync: 280
Horizontal backporch: 0
Vertical frontporch: 0
Vertical sync: 45
Vertical backporch: 0
1920x1080p60, 148.5 MHz pixel clock. Standard 1080p60 timing. B101 capturing successfully.
Then locked the timings and checked format:
v4l2-ctl --set-dv-bt-timings query -d /dev/video0
v4l2-ctl --get-fmt-video -d /dev/video0
Output:
BT timings set
Format Video Capture:
Width/Height : 1920/1080
Pixel Format : 'BGR3' (24-bit BGR 8-8-8)
Field : None
Bytes per Line : 5760
Size Image : 6220800
Colorspace : sRGB
Transfer Function : Default (maps to sRGB)
YCbCr/HSV Encoding: Default (maps to ITU-R 601)
Quantization : Default (maps to Full Range)
This is the ideal format for Hyperion on a Pi 3:
- BGR3 24-bit = uncompressed RGB, no decode overhead
- 5760 bytes/line × 1080 = 6.2MB/frame = correct for raw RGB at 1080p
- sRGB, full range = correct for HDMI source
- No CPU cycles wasted on YUV → RGB conversion
Additional source testing (the night got long)
After the Pi loopback validated capture, I got curious about whether any non-HDCP source I had lying around could capture without the splitter. Three more tests, three different failure modes — all educational.
Chromecast 3rd gen (white pill)
v4l2-ctl --query-dv-timings -d /dev/video0
Output:
VIDIOC_QUERY_DV_TIMINGS: failed: Link has been severed
Active width: 0
[...all zeros...]
“Link has been severed” = the source negotiated and then refused. Classic HDCP rejection. The Chromecast detected the B101 isn’t HDCP-compliant and killed the link. This is the exact failure mode the HDCP-stripping splitter solves.
This is good news disguised as bad news. It’s a clean confirmation that HDCP is the only thing standing between the Apple TV and the B101. Hardware, driver, EDID, capture format — all fine. Just need the splitter.
MacBook Air via USB-C → HDMI adapter
Mirrored desktop, no protected content playing, B101 plugged in via USB-C dongle.
Same Link has been severed error.
macOS appears to enforce HDCP even on a mirrored desktop in some configurations. Or the USB-C dongle is doing something weird with EDID negotiation. Either way, not a useful test source.
Home Assistant OS box (mini PC)
Different error this time:
VIDIOC_QUERY_DV_TIMINGS: failed: No locks available
“No locks available” means the B101 isn’t seeing a stable signal at all, vs. the Chromecast’s explicit handshake rejection.
Investigation:
# SSH'd into HAOS console
cat /sys/class/drm/*/status
# Output: disconnected
HAOS wasn’t driving the HDMI port at all. The kernel reported the DRM output as disconnected. Likely either headless boot config (display not detected at boot, port stays disabled) or HAOS just doesn’t bother outputting console video the way a regular Linux distro does.
Different failure mode, unrelated to HDCP. Just a non-functional source.
What the three failures teach
| Source | Error | Cause | Splitter fixes? |
|---|---|---|---|
| Pi loopback | (none, worked) | n/a | n/a |
| Chromecast | Link severed | HDCP rejection | Yes |
| Mac via USB-C | Link severed | HDCP rejection | Yes |
| HA box | No locks available | No HDMI output | n/a (broken source) |
Two distinct error messages map to two distinct problem categories. Worth knowing for future debugging:
- “Link severed” = handshake started, rejected. HDCP, EDID conflict, or content protection.
- “No locks available” = no stable signal seen. Source not outputting, cable issue, or extreme resolution mismatch.
What’s validated so far
- Pi 3 boots, on network, SSH working
- TC358743 driver loaded and bound
/dev/video0exists and captures- EDID applied correctly via
v4l2-ctl --set-edid=type=hdmi,pad=0 - 1080p60 timings detected and locked (via Pi loopback)
- Capture format is BGR3 (best case for Hyperion)
- HDCP confirmed as the blocker for real sources (justifies the splitter purchase retroactively)
Trying to capture an actual frame (educational rabbit hole)
After validation passed, I wanted to see a captured frame, not just confirm timings. Tried two paths.
Path 1: Raw v4l2-ctl frame grab
v4l2-ctl --set-fmt-video=width=1920,height=1080,pixelformat=BGR3 -d /dev/video0
v4l2-ctl --stream-mmap --stream-count=1 --stream-to=/tmp/frame.raw -d /dev/video0
Result: 0-byte file. The format set silently but streaming returned VIDIOC_STREAMON: Invalid argument.
Tried with explicit buffer count (--stream-mmap=3), same error.
The TC358743 driver advertises BGR3 as a supported format on this kernel but rejects it at STREAMON time. Underlying issue: the chip outputs UYVY natively and BGR3 conversion needs to happen in the ISP pipeline, which the unicam driver isn’t fully wiring up. Common quirk on this hardware.
Path 2: Force UYVY format
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=UYVY
v4l2-ctl --get-fmt-video -d /dev/video0
Format set successfully:
Pixel Format : 'UYVY' (UYVY 4:2:2)
Bytes per Line : 3840
Size Image : 4147200
Colorspace : SMPTE 170M
This is the chip’s actual native format. But Hyperion overrides this on its own startup, so the manual format setting doesn’t persist into Hyperion’s grabber.
Installing Hyperion-NG
The official install script URL is dead in 2026:
bash <(curl -sL https://releases.hyperion-project.org/install)
# Returns HTML 404, breaks bash with syntax error
The current method is direct .deb install from GitHub releases. APT repo also broken for both Trixie and Bookworm (Release file not found).
What worked:
curl -s https://api.github.com/repos/hyperion-project/hyperion.ng/releases/latest | grep "browser_download_url"
Showed available builds for the latest release (2.2.1). For 64-bit Pi OS, grab the arm64 deb (not aarch64 — naming changed):
cd /tmp
wget https://github.com/hyperion-project/hyperion.ng/releases/download/2.2.1/Hyperion-2.2.1-Linux-arm64.deb
sudo apt install ./Hyperion-2.2.1-Linux-arm64.deb -y
Installs cleanly. Service auto-starts as hyperion@<username>. Web UI at port 8090.
sudo systemctl status hyperion@derek
# Active: active (running)
Hyperion + B101 streaming issue
Web UI auto-discovered the B101 as unicam-image in USB Capture settings. Configured:
- Activate: yes
- Device: unicam-image
- FPS: 30
- Size decimation: 4 (default 8 was way too aggressive for 1080p input)
Saved settings. Same VIDIOC_STREAMON error in Hyperion logs:
hyperiond: VIDIOC_STREAMON failed: Invalid argument
hyperiond: Throws error nr: VIDIOC_STREAMON error code 22, Invalid argument
hyperiond: Throws error nr: VIDIOC_DQBUF error code 22, Invalid argument
Pre-setting UYVY via v4l2-ctl before starting Hyperion didn’t help — Hyperion’s V4L2 grabber resets the format on open. There’s no exposed format selection in the web UI on this Hyperion version.
This is a known TC358743 + Hyperion compatibility issue that needs proper research:
- May resolve with a real source signal (Apple TV via splitter) vs the Pi’s loopback HDMI which has known weird timing parameters
- May need a Hyperion config file edit to force the format
- May need a different Hyperion build or a kernel module workaround
Stopping here for the day. The streaming format issue is the next debug target.
Current state of the project
What works
- Pi 3 booted, networked, SSH key-only auth
- B101 detected, driver bound, EDID applied
/dev/video0exists and reports correct timings (1080p60)- Capture pipeline validated at the protocol level (timings, BT timings lock, format negotiation possible)
- Hyperion installed, running as systemd service, web UI accessible
- Hyperion auto-discovers the B101 as a capture device
What doesn’t work yet
- VIDIOC_STREAMON fails for Hyperion’s V4L2 grabber on this Hyperion + TC358743 + Trixie combo
- Live preview not yet visible in Hyperion UI
- HDCP-protected sources (Apple TV, Chromecast) can’t connect directly — need splitter
What’s pending
- Splitter arrival
- LED hardware order
- Resolution of the STREAMON format issue
- Then: WLED on ESP32, DDP config, LED layout calibration
LED hardware order
TV: Samsung QN65Q80D, 65", outer dimensions 1446.5 × 829.3 mm, active panel ~1428 × 803 mm.
LED counts at 60 LED/m (16.67mm pitch):
- Top: 86
- Right: 48
- Bottom: 86
- Left: 48
- Total: 268 LEDs
The 5m reel (300 LEDs) gives ~32 LEDs of buffer for cuts and corner gaps. Power budget: 268 SK6812 RGBW LEDs at typical brightness pulls ~5-7A with 10-12A spikes during bright scenes.
The order
| Item | Source | Cost |
|---|---|---|
| BTF-Lighting SK6812 RGBW Natural White, 5m, 60 LED/m, IP30 | Amazon | ~$45 |
| HiLetgo ESP32-WROOM-32, 2-pack (USB-C) | Amazon | ~$15 |
| ALITOVE 5V 15A 75W PSU (upgraded from 10A for headroom) | Amazon | ~$30 |
| Barrel jack to screw terminal adapters, 5-pack | Amazon | ~$7 |
| 18 AWG silicone wire kit, red/black 25ft each | Amazon | ~$13 |
| Electronic components kit (capacitors, resistors) | Amazon | ~$13 |
| Total | ~$123 |
Why these specific choices:
- SK6812 RGBW > WS2812B RGB — true white channel for bias lighting and skin tones, $10 more
- 60 LED/m > 30 LED/m — smoother color transitions, no visible gaps at typical TV viewing distance
- Natural white (4000K) — closer to typical TV color temp than warm or cool variants
- 15A PSU — 268 LEDs at edge of 10A capacity, 15A gives headroom without forcing brightness caps
- Power injection at both ends mandatory — voltage drop across 5m of strip causes far-end dimming and color shift; this is non-negotiable for >2m runs
- ESP32-WROOM-32 > ESP32-S2/S3/C3 — WLED most stable on original ESP32
What’s left when I’m back
- Plug in HBAVLINK splitter, validate Apple TV → splitter → B101 chain
- Debug the Hyperion VIDIOC_STREAMON format issue (may resolve with real source)
- Confirm live preview in Hyperion web UI
- Flash WLED to ESP32, configure for SK6812 RGBW + 268 LEDs
- Wire up strip with power injection at both ends, capacitor on PSU input, resistor on data line
- Configure Hyperion → DDP → ESP32 over Ethernet
- Calibrate LED layout (top: 86, right: 48, bottom: 86, left: 48)
- Color/gamma/saturation tuning
- Samsung Anynet+ + Apple TV “Control TVs and Receivers” for seamless power UX
Lessons so far
Documentation rot is real. Every Hyperion tutorial I found referenced repos and tools that have been moved, renamed, or deleted. The community has fragmented since the Hyperion-Project forum’s heyday. The official Hyperion-NG repo is still maintained, but supporting tooling (EDID files, install scripts on third-party mirrors, the APT repo) is mostly broken in 2026. The reliable path is GitHub Releases directly.
v4l2-ctl is your friend, but only up to a point. When external EDID files don’t work, the built-in generators (type=hdmi) are reliable. When format negotiation fails, you can force formats. But Hyperion overrides v4l2-ctl settings on its own startup, so manual format setting doesn’t persist into the actual capture pipeline.
The Pi 3 + B101 combo is more capable than expected at the protocol level. The CSI capture path keeps the CPU free enough to read 1080p60 BGR3 frames via the driver. Whether Hyperion can actually grab those frames is a different question.
Validate piece by piece. Capture pipeline is proven independently from Hyperion. When the splitter shows up, the only new variable is HDCP handshake and downscaling. Everything else is locked in. Same approach helped diagnose the streaming format issue — it’s not the hardware, not the driver, not the EDID, not the timings, it’s the format negotiation between Hyperion and the unicam driver.
HDCP enforcement is real and granular. Different sources fail differently:
- Chromecast 3rd gen: explicit handshake rejection
- macOS USB-C HDMI: same rejection (HDCP enforced even on mirrored desktop)
- Pi loopback: works fine (no HDCP enforced)
- HAOS: didn’t even output HDMI (unrelated failure mode)
Knowing which error means what saves debugging time later.
Hardware loopbacks are useful but not real tests. The Pi 3 outputting to its own B101 input validated the capture path at the timing/format query level but might not be a clean enough source to validate streaming. Modern TVs and streaming devices output much cleaner HDMI signals than a Pi headless console.
Setup commands reference
For my own future reference, the commands that actually worked in order:
# After Imager flash with customization, SSH in and update
sudo apt update && sudo apt upgrade -y
sudo reboot
# Edit boot config
sudo nano /boot/firmware/config.txt
# Add at bottom:
# dtoverlay=tc358743
# gpu_mem=128
sudo reboot
# Verify driver bound
dmesg | grep -i tc358743
ls /dev/video*
ls /dev/v4l-subdev*
v4l2-ctl --list-devices
# Install missing tools
sudo apt install v4l-utils -y
# Set EDID via built-in generator (skip the external EDID file approach, all the URLs are dead)
v4l2-ctl -d /dev/v4l-subdev0 --set-edid=type=hdmi,pad=0
# Validate capture (need a connected source)
v4l2-ctl --query-dv-timings -d /dev/video0
v4l2-ctl --set-dv-bt-timings query -d /dev/video0
v4l2-ctl --get-fmt-video -d /dev/video0
# Install Hyperion (skip the dead install script, go direct to GitHub releases)
cd /tmp
curl -s https://api.github.com/repos/hyperion-project/hyperion.ng/releases/latest | grep "browser_download_url"
# Pick arm64.deb URL from output
wget https://github.com/hyperion-project/hyperion.ng/releases/download/2.2.1/Hyperion-2.2.1-Linux-arm64.deb
sudo apt install ./Hyperion-2.2.1-Linux-arm64.deb -y
# Verify service
sudo systemctl status hyperion@$USER
# Web UI: http://hyperion.local:8090
# Watch logs while debugging
sudo journalctl -u hyperion@$USER -f
# Force pixel format (workaround attempt for VIDIOC_STREAMON issues)
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=UYVY
To be continued when the splitter arrives and after I’m back from travel. Next focus: cracking the VIDIOC_STREAMON format issue with a real source signal.