Skip to content

Pivot: companion APK camera backend for phones scrcpy cannot use #1

Description

@opariffazman

Why this repo exists, and what the first milestone is.

Background

android-cam-tui streams a phone camera into a v4l2loopback sink by driving scrcpy --video-source=camera. That path has a hard floor and a hard wall:

  • Floor: upstream scrcpy gates camera capture at Android 12 / API 31 (Add option to mirror camera Genymobile/scrcpy#4213). On Android ≤11 the camera service only serves clients it considers foreground, and the shell uid cannot reliably be one.
  • Wall: a patched server that fakes foreground status with an invisible overlay window was tried and does not help on vendor builds that revoke the CAMERA permission from com.android.shell outright.

Measured on OPPO CPH1931 (ColorOS, Android 10, unrooted), with the patched server from officialdad/scrcpy@camera-a10 loaded via SCRCPY_SERVER_PATH:

SecurityException: validateClientPermissionsLocked:1076: Caller "com.android.shell"
(PID 2000, UID 19157) cannot open camera "0" without camera permission

The overlay window itself was created fine — the refusal happens earlier, on permissions. pm grant com.android.shell android.permission.CAMERA is refused too (shell lacks GRANT_RUNTIME_PERMISSIONS on this ROM), and there is no root. Full writeup: officialdad/android-cam-tui#4.

Conclusion: everything that runs as the shell uid is a dead end on this class of device. Only code running as an installed app, holding its own user-granted CAMERA permission, can open the camera.

The pivot

A small companion APK that owns the camera and streams frames to the Linux host, with android-cam-tui gaining a second backend behind the same interface it already uses for StreamRunner.

Sketch, deliberately minimal for v1:

  • Capture: Camera2 → MediaCodec H.264, same shape as scrcpy's own encoder.
  • Transport: raw Annex-B over a TCP socket on localhost, reached from the host with adb forward tcp:<port> tcp:<port>. USB only, no Wi-Fi, no pairing, no discovery — adb is already a hard dependency of the TUI.
  • Host side: existing pipeline, unchanged in spirit — decode and write into the v4l2loopback sink.
  • Control: none in v1. Launch with adb shell am start, hardcode back camera at 1280x720@30. Camera/size/fps selection comes later, once the stream works at all.

Open questions to settle before writing much code

  1. Background camera access. Android 9+ blocks camera from the background. A foreground service with foregroundServiceType="camera" is the documented route (API 29+), but it still needs the app foregrounded once to start. Does that survive the screen going off?
  2. Keyguard interaction. android-cam-tui deliberately never wakes or unlocks the phone: waking a locked device starts face unlock, which opens a camera of its own and pushes the device past the system open-camera limit, so our own open gets rejected. Whatever launch sequence this app needs must not reintroduce that.
  3. minSdk. API 21 is cheap to target and covers everything the scrcpy path cannot. Confirm nothing in the encoder path forces higher.
  4. Latency budget. scrcpy's camera path is the baseline to beat, or at least match; worth measuring before committing to a socket format.

First milestone

One APK, one hardcoded resolution, one socket, frames landing in /dev/video0 on the OPPO CPH1931 — the exact device that no scrcpy-based path can serve. Everything else is deferred until that works end to end.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions