DIY Ambilight for Any TV: Shield TV + scrcpy + HyperHDR, No HDMI Capture Card Required

If you've ever wanted Philips Ambilight on a TV that isn't a Philips, you've probably hit the same wall everyone else does: capturing the video signal. HDMI capture cards work great for external sources, but what about content from streaming sticks? What about when you pause the movie? What about doing it all wirelessly?

After weeks of dead ends, kernel module builds, and more v4l2loopback configurations than any human should endure, I built a fully wireless ambilight system that captures an Nvidia Shield TV's screen over ADB, maintains a rock-solid 30fps frame stream even during paused content, and drives 300 SK6812 RGBW LEDs behind a 65" Samsung S90F QD-OLED. No HDMI capture card. No camera pointed at the screen. No extra remotes. Here's how.

The Architecture

Shield TV Pro ──(ADB over wifi/ethernet)──> ZimaBoard (scrcpy + HyperHDR) ──(WiFi)──> ESP32 (HyperK) ──> SK6812 LEDs

The key insight: scrcpy runs as the Android shell user via app_process, completely outside Android's app lifecycle. Unlike every Android screen grabber app (which dies the moment you switch away from it), scrcpy survives app switches because it was never an app to begin with. Switch to Stremio, Netflix, Kodi, whatever. The capture keeps running.

The Hardware

Here's the full buy list. Everything is available on Amazon.

LED Strip and Power

SK6812 RGBW LED strip (5V, 60 LEDs/m, 5 meters, 300 LEDs) -- $25-40 The actual light strip mounted behind the TV.

Mean Well LRS-100-5 (5V, 18A, 90W) -- $18-22 AC-to-DC power supply. Takes wall power in, outputs 5V. Based on QuinLED's measured data, 300 SK6812 LEDs draw ~76W at full RGBW white. This supply gives headroom.

18 AWG silicone wire (red + black) -- $8 Power runs from PSU to strip. The thin stock wires on LED strips can't handle 15A. Also needed for power injection at the far end of the strip.

AC power cord (3-prong) -- Free (junk drawer) The Mean Well has screw terminals for AC input. Cut the device end off an old PC/monitor cord.

Signal Electronics

ESP32 dev board -- $8 Runs HyperK firmware. Receives color data from HyperHDR over WiFi and drives the LED strip.

SN74AHCT125N (DIP-14 level shifter, 12-pack) -- $8 Converts 3.3V data signal from the ESP32 to 5V for the SK6812 strip. The chip is a quad buffer; you only need one channel.

Breadboard -- $8 You'll need a wide one or a second breadboard, the ESP is slightly too big to connect properly with just one.

Dupont jumper wires -- $5 For connecting everything. Female-to-female or female-to-male depending on your board headers.

Optional: Raspberry Pi Pico (RP2040, NOT Pico 2/RP2350) -- $5-8 Alternative to the ESP32 for USB serial setups via HyperSerialPico. Not needed if using the ESP32 + HyperK WiFi approach.

Video Capture and Processing

Nvidia Shield TV Pro 2019 (the model with USB ports) -- $150-200 Streaming device that replaces your TV's built-in apps. We capture its screen via ADB. Must be the 2019 Pro model (Tegra X1+, USB ports, gigabit ethernet).

ZimaBoard (or any x86 Debian/Ubuntu box) -- $70-120 Runs CasaOS with HyperHDR in Docker, scrcpy, and the frame sustainer. A Raspberry Pi 4/5 would also work.

Why Not Just Use [Other Approach]?

Before I explain what works, here's what doesn't, so you don't waste your time:

"Just use the TV's built-in apps and capture that" Samsung Tizen has no screen capture API. Period. The only native option is the Philips Hue Sync TV app ($130 + Hue ecosystem). There is a community project called HyperTizen but it doesn't reliably work on Tizen 9 and can't capture DRM content.

"Use an Android screen grabber app on the Shield" Every grabber app (Hyperion Grabber Reloaded, hyperion-android-reborn, etc.) uses MediaProjection, which Android kills when you switch to another app. We tested MediaProjection, Scrcpy-in-app, ADB Stream, Accessibility service (can't even see video surfaces), and Screencap (3fps). All dead ends for background capture.

"Use an HDMI capture card" This works perfectly for a dedicated streaming setup. But if you also connect a PC at 4K 165Hz to the same TV, you'd need an HDMI 2.1 capture card ($150+) or separate capture paths per source. The scrcpy approach eliminates the capture card entirely for Shield content.

"Use a camera pointed at the screen" Works, but noticeably lower quality than direct capture. Color accuracy depends on ambient light, viewing angle, and camera quality.

The Software: Where the Real Work Is

The hardware assembly is straightforward soldering. The software is where this project gets interesting, because we had to solve three problems that don't have obvious solutions:

Problem 1: Capturing the Shield's Screen in the Background

Solution: scrcpy over ADB

scrcpy (Screen Copy) is an open-source tool that mirrors Android devices. When you run it from a host machine, it pushes a small Java server (scrcpy-server.jar) to the Android device and executes it via app_process. This server runs as the shell user, not as an Android app. Android's app lifecycle (the thing that kills MediaProjection when you switch apps) doesn't apply to it.

The critical command:

scrcpy -s SHIELD_IP:5555 \    --no-audio --no-control --no-window \    --v4l2-sink=/dev/video10 \    --max-size=256 --max-fps=30 \    --video-bit-rate=500K

This captures the Shield's screen at 256x144 resolution (plenty for sampling edge colors), 30fps, 500kbps, and writes it to a v4l2loopback virtual camera device. No display window needed because the ZimaBoard is headless.

Problem 2: Paused Content Kills the Frame Stream

This was the hardest problem. When you pause a video in Stremio (or any player), the screen becomes static. The Tegra X1+'s H.264 encoder responds by... stopping. It sends no frames when nothing changes. The repeat-previous-frame-after codec option is silently ignored on Tegra hardware.

HyperHDR's V4L2 grabber has a 1-second source timeout. No frames for 1 second = source marked inactive = LEDs switch off. This creates a jarring flash every few seconds during paused content.

Solution: a threaded frame sustainer

We wrote a Python script that sits between scrcpy and HyperHDR using two v4l2loopback devices:

scrcpy --> /dev/video10 (raw, may stall) --> frame-sustainer.py --> /dev/video11 (constant 30fps) --> HyperHDR

The sustainer uses two threads:

The key is that the writer never waits for the reader. It always has something to write (initialized with a black frame on startup, replaced with real frames once they arrive). HyperHDR never sees a gap in frames, so the source never goes inactive.

# Simplified core logic
last_frame = np.zeros((144, 256, 3), dtype=np.uint8)  # Start with black

def reader_thread():    while True:        ret, frame = cap.read()  # Blocks when no frames        if ret:            last_frame = frame    # Update shared frame

# Writer runs at 30fps regardless
while True:    ffmpeg.stdin.write(last_frame.tobytes())  # Always has a frame to write    sleep(1/30)

Problem 3: Boot Order and Auto-Recovery

The system needs to start automatically and in the right order:

  1. v4l2loopback kernel module loads (creates /dev/video10 and /dev/video11)
  2. shield-grabber systemd service starts (runs scrcpy + frame sustainer)
  3. HyperHDR Docker container starts (reads from /dev/video11)

If HyperHDR starts before the sustainer writes its first frame, it won't find the capture device. A separate systemd oneshot service restarts the HyperHDR container 30 seconds after the grabber starts, ensuring correct initialization.

The wrapper script around scrcpy also handles disconnections:

while true; do    adb connect $SHIELD    scrcpy [options]    echo "scrcpy exited, reconnecting in 3s..."    sleep 3
done

The Easy Way: HyperHDRGrabber

I packaged the entire scrcpy + frame sustainer + systemd setup into an install script:

https://github.com/Lpanos/HyperHDRGrabber

Run the install script, enter your Shield's IP, and it handles building scrcpy 3.3.4 from source, compiling v4l2loopback, creating both virtual devices, deploying the frame sustainer, and setting up the systemd services. You still need to configure HyperHDR to use the ShieldCap device and set up your LED hardware.

Hardware Wiring

Level Shifter (SN74AHCT125N) Connections

Hold the chip with the notch at the top. Left side pins 1-7 (top to bottom), right side pins 8-14 (bottom to top):

Power

Important Notes

HyperHDR Configuration

USB Capture Settings

Smoothing

Background

Results

The system captures every frame from the Shield TV, processes edge colors in HyperHDR, and drives 300 SK6812 RGBW LEDs at 30fps over WiFi via an ESP32 running HyperK. Content plays with responsive, accurate ambient lighting. Pausing content holds the last frame's colors indefinitely. The LEDs don't flash, don't drop out, and don't require any manual intervention.

Total latency from screen to LEDs is approximately 100-150ms (scrcpy encoding + network + HyperHDR processing + ESP32 WiFi + LED refresh). For movies and TV shows, this is imperceptible. For fast-paced gaming, you'd want a direct HDMI capture card instead.

Lessons Learned the Hard Way

  1. Samsung Tizen is a dead end for DIY ambilight. No screen capture API, no sideloading, no community workarounds that reliably work on Tizen 9. If your TV is Samsung, you need an external streaming device.

  2. Every Android screen grabber app fails in the background. MediaProjection, Accessibility, in-app ADB wrappers... all of them lose capture when another app takes foreground. Only host-side scrcpy (running via app_process as the shell user) survives app switches.

  3. The Tegra X1+ encoder lies about codec options. repeat-previous-frame-after is accepted without error but silently ignored. The encoder stops sending frames on static content. You cannot fix this from the scrcpy/encoder side; you need an external frame repeater.

  4. v4l2loopback blocks readers when no writer is active. A v4l2loopback device with no active producer causes any consumer's read() call to block indefinitely. This is why you need two devices: one that may stall (scrcpy's output) and one that never stalls (the sustainer's output).

  5. HyperHDR's V4L2 source timeout is 1 second and not configurable. If your frame stream has any gap longer than 1 second, the source goes inactive, the muxer falls back, and the LEDs flash. The frame sustainer exists specifically to work around this.

  6. Boot order matters and is fragile. v4l2loopback must load before anything opens the devices. The sustainer must write at least one frame before HyperHDR opens the device. scrcpy must connect before the sustainer can get real frames. Get the systemd dependencies right or expect to manually restart services after every reboot.

What I'd Do Differently

If I were starting over with the knowledge I have now, I'd skip the entire investigation and go straight to ADB + the two-device v4l2loopback + threaded sustainer architecture. The total software development time was about 12 hours, of which 8 were spent on approaches that turned out to be dead ends (WiFi ADB optimization, Android grabber apps, single v4l2 device configurations, ffmpeg intermediary attempts, codec option experiments).

The hardware is the easy part. The software is where this project lives and dies.

Links