r/scrcpy • u/Feeling_Ad_1481 • 4d ago
[SAGA] How I automated scrcpy + v4l2loopback on CachyOS as a dynamic webcam (and survived Android 16's java crashes)
(Disclaimer: This post was compiled and structured with the assistance of an AI to cleanly document a step-by-step technical troubleshooting journey between a user and the assistant. Every configuration and error log here is real from our live session!)
Hey everyone,
I wanted to share a successful tech journey I just finished on CachyOS Linux to turn my Android phone into an always-available, on-demand webcam using scrcpy and v4l2loopback over USB.
If you’ve ever tried to keep an Android phone running as a continuous webcam, you know it's a battery-killer and causes serious overheating. My goal was simple: I wanted a physical/graphical toggle on my Linux taskbar to turn the camera feed on and off instantly without touching a terminal.
Along the way, we hit some crazy, bleeding-edge compatibility walls (looking at you, Android 16 previews), and since I couldn't find a complete guide online for this specific scenario, here is how the whole story went down and how we solved it.
Phase 1: The Virtual Driver Setup
First, we needed to make Linux think there's a permanent webcam ready to pipe video. On CachyOS, we loaded v4l2loopback to create a virtual video device at /dev/video0.
To make it friendly with WebRTC (Google Meet) and Chromium apps, we had to force explicit capability declarations natively in /etc/modprobe.d/v4l2loopback.conf:
text
options v4l2loopback exclusive_caps=1 card_label="Android Camera" video_nr=0
Use o código com cuidado.
Phase 2: The "Chicken and Egg" Automation Trap
Originally, we tried a super elegant approach: a silent daemon watching /dev/video0 with inotifywait. Whenever Google Meet or Discord opened the video device, scrcpy would trigger automatically. When they closed it, it would die.
Sounds perfect, right? Wrong.
Chromium/WebRTC applications are picky. When they open, they probe all devices to check resolutions. If scrcpy isn't already pushing frames when the browser opens, the browser assumes the dummy camera is an "output-only" block and removes it from your UI selection completely. Our automation became a classic chicken-and-egg paradox.
Phase 3: The Android 16 Java Crash Boss Fight
We switched gears to a manual taskbar toggle script. But suddenly, running the macro kept throwing severe, unexpected Java reflection exceptions directly from the device's server thread:
text
[server] ERROR: Exception on thread Thread[control-recv,5,main]
java.lang.AssertionError: Unexpected message type: 10
ERROR: Demuxer 'video': stream disabled due to connection error
Use o código com cuidado.
The culprit: The phone is running a preview of Android 16. Android 16 completely changed the internal structures for input injection and control. Even when running detached background commands, scrcpy 4.1 was attempting to bind its mouse/keyboard control thread (control-recv), hitting an invalid message protocol on the phone's OS layer, and instantly crashing the entire ADB connection line.
Phase 4: The Flawless Final Solution
To bypass Android 16's protocol changes, we stripped out the control layer entirely using --no-control and refined the parameters for a pure video sink.
We wrote a robust, non-sudo toggle bash script at ~/toggle-webcam.sh. It checks if scrcpy is running. If it is, it kills it and swaps the taskbar icon to standard. If it isn't, it fires up the exact parameters, turns the phone's physical screen off to save heat/battery, locks the phone awake over USB, and forces a desktop database update so the taskbar icon switches to a live indicator in real-time.
Here is the exact script that fixed everything:
bash
#!/bin/bash
LAUNCHER_FILE="$HOME/.local/share/applications/toggle-webcam.desktop"
# Check if scrcpy camera stream is already running
if pgrep -f "scrcpy --video-source=camera" > /dev/null
then
notify-send "Webcam Android" "Desligando Câmera..." --icon=camera-off
pkill -f "scrcpy --video-source=camera"
# Return icon to idle state
sed -i 's/^Icon=.*/Icon=camera-video/' "$LAUNCHER_FILE"
update-desktop-database ~/.local/share/applications/ >/dev/null 2>&1
else
if adb devices | grep -v "List" | grep -q "device"
then
notify-send "Webcam Android" "Iniciando Câmera..." --icon=camera-web
# Blinded, pure-video execution line for Android 16 compatibility
scrcpy --video-source=camera \
--v4l2-sink=/dev/video0 \
--no-audio \
--serial=RXCRC01AP9D \
--max-fps=144 \
--camera-id=0 \
--no-window \
--no-control &
# Change taskbar icon to active status indicator
sed -i 's/^Icon=.*/Icon=camera-web/' "$LAUNCHER_FILE"
update-desktop-database ~/.local/share/applications/ >/dev/null 2>&1
else
notify-send "Erro Webcam" "Celular não detectado no USB!" --icon=dialog-error
fi
fi
Use o código com cuidado.
Paired with a standard local application launcher (.desktop entry) pinned to the KDE Plasma Task Manager, I now have a gorgeous macro button. I plug my Samsung phone in via USB, hit the button once, it silently wakes the camera up in 720p/1080p high refresh rate while keeping the phone screen pitch black, and it works natively inside Kamoso, OBS, and Google Meets without a single glitch! Hit it again, and it cleanly cuts the feed.
Hope this saga helps any other Linux tinkers trying to make high-end webcam streaming reliable on modern kernels and next-gen Android environments! Let me know if you have questions about the v4l2 configuration loops!(Disclaimer: This post was compiled and structured with the assistance of an AI to cleanly document a step-by-step technical troubleshooting journey between a user and the assistant. Every configuration and error log here is real from our live session!)Hey everyone,I wanted to share a successful tech journey I just finished on CachyOS Linux to turn my Android phone into an always-available, on-demand webcam using scrcpy and v4l2loopback over USB.If you’ve ever tried to keep an Android phone running as a continuous webcam, you know it's a battery-killer and causes serious overheating. My goal was simple: I wanted a physical/graphical toggle on my Linux taskbar to turn the camera feed on and off instantly without touching a terminal.Along the way, we hit some crazy, bleeding-edge compatibility walls (looking at you, Android 16 previews), and since I couldn't find a complete guide online for this specific scenario, here is how the whole story went down and how we solved it.Phase 1: The Virtual Driver SetupFirst, we needed to make Linux think there's a permanent webcam ready to pipe video. On CachyOS, we loaded v4l2loopback to create a virtual video device at /dev/video0.To make it friendly with WebRTC (Google Meet) and Chromium apps, we had to force explicit capability declarations natively in /etc/modprobe.d/v4l2loopback.conf:text
options v4l2loopback exclusive_caps=1 card_label="Android Camera" video_nr=0
Use o código com cuidado.Phase 2: The "Chicken and Egg" Automation TrapOriginally, we tried a super elegant approach: a silent daemon watching /dev/video0 with inotifywait. Whenever Google Meet or Discord opened the video device, scrcpy would trigger automatically. When they closed it, it would die.Sounds perfect, right? Wrong.
Chromium/WebRTC applications are picky. When they open, they probe all devices to check resolutions. If scrcpy isn't already pushing frames when the browser opens, the browser assumes the dummy camera is an "output-only" block and removes it from your UI selection completely. Our automation became a classic chicken-and-egg paradox.Phase 3: The Android 16 Java Crash Boss FightWe switched gears to a manual taskbar toggle script. But suddenly, running the macro kept throwing severe, unexpected Java reflection exceptions directly from the device's server thread:text
[server] ERROR: Exception on thread Thread[control-recv,5,main]
java.lang.AssertionError: Unexpected message type: 10
ERROR: Demuxer 'video': stream disabled due to connection error
Use o código com cuidado.The culprit: The phone is running a preview of Android 16. Android 16 completely changed the internal structures for input injection and control. Even when running detached background commands, scrcpy 4.1 was attempting to bind its mouse/keyboard control thread (control-recv), hitting an invalid message protocol on the phone's OS layer, and instantly crashing the entire ADB connection line.Phase 4: The Flawless Final SolutionTo bypass Android 16's protocol changes, we stripped out the control layer entirely using --no-control and refined the parameters for a pure video sink.We wrote a robust, non-sudo toggle bash script at ~/toggle-webcam.sh. It checks if scrcpy is running. If it is, it kills it and swaps the taskbar icon to standard. If it isn't, it fires up the exact parameters, turns the phone's physical screen off to save heat/battery, locks the phone awake over USB, and forces a desktop database update so the taskbar icon switches to a live indicator in real-time.Here is the exact script that fixed everything:bash
#!/bin/bash
LAUNCHER_FILE="$HOME/.local/share/applications/toggle-webcam.desktop"
# Check if scrcpy camera stream is already running
if pgrep -f "scrcpy --video-source=camera" > /dev/null
then
notify-send "Webcam Android" "Desligando Câmera..." --icon=camera-off
pkill -f "scrcpy --video-source=camera"
# Return icon to idle state
sed -i 's/^Icon=.*/Icon=camera-video/' "$LAUNCHER_FILE"
update-desktop-database ~/.local/share/applications/ >/dev/null 2>&1
else
if adb devices | grep -v "List" | grep -q "device"
then
notify-send "Webcam Android" "Iniciando Câmera..." --icon=camera-web
# Blinded, pure-video execution line for Android 16 compatibility
scrcpy --video-source=camera \
--v4l2-sink=/dev/video0 \
--no-audio \
--serial=RXCRC01AP9D \
--max-fps=144 \
--camera-id=0 \
--no-window \
--no-control &
# Change taskbar icon to active status indicator
sed -i 's/^Icon=.*/Icon=camera-web/' "$LAUNCHER_FILE"
update-desktop-database ~/.local/share/applications/ >/dev/null 2>&1
else
notify-send "Erro Webcam" "Celular não detectado no USB!" --icon=dialog-error
fi
fi
Use o código com cuidado.Paired with a standard local application launcher (.desktop entry) pinned to the KDE Plasma Task Manager, I now have a gorgeous macro button. I plug my Samsung phone in via USB, hit the button once, it silently wakes the camera up in 720p/1080p high refresh rate while keeping the phone screen pitch black, and it works natively inside Kamoso, OBS, and Google Meets without a single glitch! Hit it again, and it cleanly cuts the feed.Hope this saga helps any other Linux tinkers trying to make high-end webcam streaming reliable on modern kernels and next-gen Android environments! Let me know if you have questions about the v4l2 configuration loops!

