Skip to content

Repository files navigation

android-cam-tui

ci release license: MIT

Terminal UI for using an Android phone as a Linux webcam via scrcpy and v4l2loopback.

Probes your phone's real cameras (lenses, resolutions, fps, zoom ranges), streams the one you pick into a virtual webcam device, and supervises the stream — auto-restarting when Android evicts the camera (face unlock, camera app opened, phone rebooted).

setup screen

Requirements

android-cam-tui --doctor checks all of these and prints the install command for your distro:

  • Linux with v4l2loopback loaded
  • scrcpy ≥ 3.0, adb, v4l2-ctl on PATH
  • Phone: USB debugging enabled, plugged in, and authorized for this computer. The screen may be off and locked — camera capture works through the keyguard, and waking a locked phone hands the camera to face unlock

When something on that list is missing the setup screen shows the same report instead of starting. c puts every command on the clipboard as a shell script — the TUI holds mouse tracking, so they cannot be selected out of it — then fix it in another terminal and press r to re-check. Problems that only degrade things — an old scrcpy, no preview player — are one yellow line on the setup screen and do not stop you.

c needs wl-copy (wayland) or xclip (x11); without one, --doctor prints the same thing to stdout.

Older phones

Camera mirroring needs Android 12 (API 31) — scrcpy's --video-source=camera doesn't work below it. android-cam-tui covers those phones with a companion app instead: a foreground service, vendored into this binary, that opens the camera itself and pipes H.264 to a socket for ffmpeg to decode into the sink. It has no activity, so the phone is never woken or unlocked to run it. Source: officialdad/android-cam.

--doctor detects a phone below API 31 on its own and adds three checks: ffmpeg on PATH, the app installed, and the app holding camera permission. The APK ships inside this binary and is written out to ~/.cache/android-cam-tui/android-cam.apk the moment an old phone shows up, so the install fix is just adb install -r -g ~/.cache/android-cam-tui/android-cam.apkc puts it on the clipboard like any other fix line. -g grants every runtime permission the app asks for, which here is only the camera; the ROMs that ignore the flag need a manual adb shell pm grant com.officialdad.androidcam android.permission.CAMERA, which is its own check. All three drop to warnings when a modern phone is attached too — they still describe the old handset, which you can still select. Below Android 8 they are not raised at all: the service is launched with am start-foreground-service, which does not exist there, so that phone is a hard block with no install that fixes it.

The APK reports its real cameras — ids, facing, sizes, frame rates, zoom ceiling, sensor orientation — over dumpsys, so the picker shows what the phone actually has: camera selection, real sizes and fps options, a real zoom range, and the rotate option pre-selected from the sensor's orientation. Camera, size, fps, bitrate and digital zoom ride the service's start intent; rotation and aspect crop happen host-side in ffmpeg. There is no torch, and an APK too old to answer the probe falls back to fixed 1280x720@30 requests.

Install

curl -fsSL https://raw.githubusercontent.com/officialdad/android-cam-tui/main/install.sh | sh

Downloads the latest release for your architecture, verifies its checksum, installs it to ~/.local/bin (override with PREFIX=), adds that directory to your shell's PATH if it is missing, and finishes by reporting any runtime dependency you still need.

Arch and derivatives can use the AUR package instead:

paru -S android-cam-tui-bin

Or take the tarball by hand from the latest releaselinux-x64 and linux-arm64, each with a .sha256 beside it, no Bun needed at runtime. From source instead (needs Bun ≥ 1.3):

git clone https://github.com/officialdad/android-cam-tui
cd android-cam-tui && bun install
bun start

bun run build rebuilds the same binary into dist/.

Run

android-cam-tui            # setup screen
android-cam-tui --start    # skip setup, stream the last-used config

From source, bun start and bun start --start.

Setup screen: a device row, then three columns — camera, resolution, and output (fps/zoom/bitrate/rotate/aspect/buffer/sink), all probed live. The same menu on both backends; only buffer is scrcpy-specific. Tab/Shift-Tab move between fields, 1-3 jump to a column, ↑↓ pick from a list, ←→ adjust, / filters a list (Esc clears), Enter starts. Under 100 columns wide it shows one section at a time.

Rates marked hs are Android high-speed capture (--camera-high-speed), which is where 120/240 fps live — they only appear on the resolutions that support them, so pick the resolution first. Actual output can fall below the requested rate in low light, since auto-exposure lengthens the exposure time.

rotate maps to scrcpy's --capture-orientation, so it rotates what reaches the v4l2 device (not just a preview window) — use it when the phone sits in a mount. aspect is a centered crop to 16:9, 4:3 or 1:1 (scrcpy's --crop; an ffmpeg crop on the companion backend), applied before the rotation on both.

Wireless

w on the setup screen promotes the selected USB device to WiFi: it runs adb tcpip 5555, reads the phone's WLAN address, connects, and re-probes. The cable can then be unplugged. Every scrcpy/adb call is scoped with -s <serial>, so a USB and a wireless device can be attached at once without "more than one device" errors.

w again on a wireless device goes back: adb usb restarts adbd on USB and adb disconnect drops the TCP link, so the phone stops listening on 5555. Plug the cable in firstadb usb travels over the very connection it closes, so with no cable the phone drops off entirely and you have to replug (or reboot it) to get it back. Equivalent by hand:

adb -s 192.168.1.42:5555 usb
adb disconnect 192.168.1.42:5555

Until you do that (or the phone reboots), port 5555 stays open on your LAN — anyone on the network who is already adb-authorised on that phone can connect.

Wireless adds jitter — raise buffer (--v4l2-buffer, in ms) if the feed stutters.

Dashboard

z cycle zoom presets, l cycle camera, t toggle the camera torch (fill light — scrcpy backend only, the companion app has no torch), p open a preview window, r restart, s back to setup, q quit.

Every camera parameter is fixed when the capture session opens, so zoom, torch and camera changes restart the stream — the sink drops for about a second.

p shells out to whichever of ffplay, mpv or vlc is installed and points it at the sink, since scrcpy itself runs with --no-window. The preview window is detached: close it yourself, it survives stream restarts.

Recovering from failures

The stream is supervised. When it dies, the exit code and scrcpy's own last error line are logged, then it restarts with an exponential backoff, giving up after 6 attempts so a config the phone rejects can't respawn forever. r clears the count and retries. A stream that stayed up over a minute before dying starts from a fresh budget, so a phone that locks once an hour never exhausts it.

Quitting — q, Ctrl+C, or a crash — kills scrcpy with it, so the camera and the v4l2 sink are released rather than left busy for the next run.

To check the UI without a phone attached, bun run demo renders the setup screen against fixture cameras. That is also what the GIF above records — vhs docs/demo.tape regenerates it (needs vhs, ttyd and ffmpeg).

Last-used config persists at ~/.config/android-cam-tui/config.json.

License

MIT

About

Terminal UI for using an Android phone as a Linux webcam, via scrcpy and v4l2loopback

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages