Skip to content

Instantly share code, notes, and snippets.

@framp
Created October 14, 2026 14:16
Show Gist options
  • Select an option

  • Save framp/805d52ca6f32e2837d32f86af45f2fb4 to your computer and use it in GitHub Desktop.

Select an option

Save framp/805d52ca6f32e2837d32f86af45f2fb4 to your computer and use it in GitHub Desktop.
phonecam: Android phone as a USB webcam on Linux (scrcpy + v4l2loopback + systemd user service)

phonecam: Android phone as a webcam over USB (Linux)

A systemd user service that waits for an Android phone to be plugged in over USB, then uses scrcpy to stream the phone's camera into a v4l2loopback device (/dev/video0). Any app (Meet, Zoom, OBS, browser) then sees it as a normal webcam. Unplug the phone and the service goes back to waiting; plug it in again and the camera comes back on its own.

How it works

phone (USB, adb) --scrcpy --video-source=camera--> /dev/video0 (v4l2loopback) --> any webcam app
  • ExecStartPre=adb wait-for-usb-device blocks until a phone is connected (TimeoutStartSec=infinity).
  • scrcpy exits when the phone is unplugged; Restart=always puts the service back into waiting.
  • StartLimitIntervalSec=0 stops systemd from giving up after repeated restarts.

1. Install packages (Arch)

sudo pacman -S scrcpy android-tools v4l2loopback-dkms v4l2loopback-utils linux-headers

(Debian/Ubuntu: sudo apt install scrcpy adb v4l2loopback-dkms v4l2loopback-utils. Camera mirroring needs scrcpy ≥ 2.2 and Android 12+.)

2. Set up v4l2loopback

Load the module at boot (/etc/modules-load.d/v4l2loopback.conf):

v4l2loopback

Module options (/etc/modprobe.d/v4l2loopback.conf):

options v4l2loopback devices=2 video_nr=0,10 card_label="Phone Camera","AI Camera" exclusive_caps=1,1
  • video_nr=0 puts the phone feed at /dev/video0 (the second device, /dev/video10, is a spare for a processed feed; use devices=1 video_nr=0 card_label="Phone Camera" exclusive_caps=1 if you only need one).
  • exclusive_caps=1 is required for Chrome/WebRTC apps to list the device as a camera.

Apply without rebooting:

sudo modprobe -r v4l2loopback; sudo modprobe v4l2loopback
v4l2-ctl --list-devices   # should show "Phone Camera (platform:v4l2loopback-000): /dev/video0"

3. Prepare the phone

  1. Settings → About phone → tap Build number 7× to enable Developer options.
  2. Developer options → enable USB debugging.
  3. Plug it in, run adb devices, and accept the "Allow USB debugging" prompt (tick Always allow).
  4. Test manually:
    scrcpy --list-cameras
    scrcpy --video-source=camera --camera-facing=back --v4l2-sink=/dev/video0 --no-window

4. Install the service

mkdir -p ~/.config/systemd/user
cp phonecam.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now phonecam.service

Check / debug:

systemctl --user status phonecam    # "activating (start-pre)" = waiting for the phone
journalctl --user -u phonecam -f

Adjust adb's path in ExecStartPre if it isn't /usr/bin/adb (e.g. /opt/android-sdk/platform-tools/adb).

Tweaks (scrcpy flags in ExecStart)

Flag What it does
--camera-facing=back|front which camera
--camera-size=1920x1080 resolution (see scrcpy --list-camera-sizes)
--camera-zoom=2 zoom factor (scrcpy ≥ 3.x)
--no-window --no-control --no-audio headless: video only into the loopback device
-d use the USB device (not a TCP/IP one)

Gotchas

  • Phone shows up as "unauthorized" → re-accept the USB debugging prompt on the phone.
  • App doesn't list the camera → check exclusive_caps=1, and that nothing else is writing to /dev/video0.
  • Screen lock on the phone doesn't stop the camera feed, but some vendors kill background camera use: keep the phone unlocked if the feed freezes.
options v4l2loopback devices=2 video_nr=0,10 card_label="Phone Camera","AI Camera" exclusive_caps=1,1
[Unit]
Description=Phone camera via scrcpy -> /dev/video0 (waits for USB phone)
StartLimitIntervalSec=0
[Service]
ExecStartPre=/usr/bin/adb wait-for-usb-device
ExecStart=/usr/bin/scrcpy -d --video-source=camera --camera-size=1920x1080 --camera-facing=back --camera-zoom=2 --v4l2-sink=/dev/video0 --no-window --no-control --no-audio
# scrcpy exits on unplug; go back to waiting for the phone.
Restart=always
RestartSec=2
# The pre-start wait can last indefinitely.
TimeoutStartSec=infinity
[Install]
WantedBy=default.target
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment