CodeVetter
← All unpacks
CodeVetter · Repo Unpack evidence

Genymobile/scrcpy

Scanned commit 19c1261d2e dated 2026-09-05 — last upstream commit. Generated by codevetter 1.12.0 on 2026-09-22.

github.com/Genymobile/scrcpy ↗ · 367 files · 1.7 MB

[!WARNING] This GitHub repo ( ) is the only official source for the project.

Deep analysis by swe-2 via the devin CLI on 2026-09-22 (runtime not recorded) — every claim below links to its cited source files.

Overview

scrcpy is a two-process Android screen-mirroring and remote-control tool: a native C/SDL3+FFmpeg client (app/) runs on Linux/Windows/macOS and pushes a Java 'server' (server/) onto the device via adb, where it is executed with app_process to capture and encode screen/camera video and device audio. Streams travel over up to three sockets (video, audio, control) tunnelled through adb, while a bidirectional control channel injects keyboard/mouse/gamepad events and syncs the clipboard. It is designed for low latency (~35-70ms), no root, and nothing permanently installed on the device.

System map

The repo contains two deployables: the 'scrcpy' client binary in C (app/src) built by Meson, and 'scrcpy-server', an Android Java program (server/) built by Gradle into a dexed jar pushed to /data/local/tmp and run via app_process. The client entrypoint is app/src/main.c -> scrcpy() in app/src/scrcpy.c, which owns a single static struct aggregating server connection, screen, audio player, demuxers, decoders, recorder, controller and optional USB/V4L2 subsystems. The server entrypoint is Server.main -> internalMain -> scrcpy() in Server.java, which wires AsyncProcessors (Controller, audio encoder/recorder, SurfaceEncoder) onto sockets opened through DesktopConnection. Communication is over adb-tunnelled TCP/local sockets (adb reverse by default, forward with --force-adb-forward). CI/release is driven by .github/workflows/release.yml calling scripts in release/.

Feature catalog

Feature surface is almost entirely CLI-driven: cli.c defines a table of ~100 long options (many with short forms) parsed via getopt_long into scrcpy_options. Major features: video mirroring (display or camera, five codecs), audio forwarding (four codecs, many sources), input control via three injection modes (SDK/UHID/AOA), screen recording to several container formats, virtual/flex displays, app launching, file push and APK install by drag-and-drop, clipboard autosync, TCP/IP wireless setup, OTG mode without adb debugging, V4L2 webcam sink, and --list-* introspection commands executed server-side.

All 14 feature catalog claims →

Data flow

Outbound (device->host): capture (screen/camera/mic) -> MediaCodec encoder -> Streamer writes a 4-byte codec id then 12-byte frame/session headers + raw packets onto the video/audio sockets -> client demuxer threads parse headers into AVPackets -> packet_source fans out to sinks (decoder -> screen/v4l2, recorder). Inbound (host->device): SDL input events -> processors build sc_control_msg -> controller queue -> serialized onto the control socket -> ControlMessageReader -> Controller.handleEvent -> Device/InputManager injection or UHID writes. The control socket is also used device->client for DeviceMessages (clipboard, ack, UHID output).

All 13 data flow claims →

Behavior traces

Three end-to-end flows were traced. (1) Startup: CLI parse -> SDL init -> server thread does adb start-server, device selection, push, tunnel open, app_process exec, socket handshake, device meta read -> demuxer/decoder/screen init -> SDL event loop. (2) Input: SDL key/mouse event -> input_manager/keyboard_sdk/mouse_sdk -> sc_control_msg -> controller queue -> serialized write -> server ControlMessageReader -> Controller.handleEvent -> Device.injectEvent or UhidManager. (3) Shutdown/disconnect: SDL_QUIT or demuxer EOS -> sc_server_stop interrupts sockets -> adb process watchdog (1s grace then SIGKILL) -> threads joined in careful order (demuxers before screen destroy).

Testing signals

Client tests are 12 assert-based C programs in app/tests, each compiled as a Meson test() executable — but only when buildtype=debug so asserts execute. They cover adb parsing, binary helpers, CLI parsing, control-message serialization, device-message deserialization, orientation parsing, string/strbuf/vecdeque/vector containers and Windows command building. Server tests are JUnit4 in server/src/test covering ControlMessageReader, DeviceMessageWriter, CodecOptions, BinarySearch, Binary, CommandParser and StringUtils. CI runs release/test_server.sh (gradle test) and release/test_client.sh (meson test) plus a build-without-gradle check; there is no end-to-end socket-protocol or UI test coverage.

Risk map

The riskiest areas are: (1) shell-injection-adjacent code — server params are interpolated into an 'adb shell ... app_process' command line and only mitigated by a denylist VALIDATE_STRING, with an explicit comment that args are not properly escaped on Windows; (2) heavy reflection into private Android APIs (Workarounds, FakeContext, wrappers) which can break per Android release; (3) fragile lifecycle ordering — a large goto-cleanup chain in scrcpy() and static buffers/structs; (4) privileged-environment handling (root uid drop, killing the adb server, writing to /data/local/tmp and /sdcard); and (5) untested protocol duality where the same wire format is hand-maintained in C and Java.

All 13 risk map claims →

Extension points

Extension is deliberately table-driven. Client CLI: add an OPT_* enum + options[] row + case in the getopt switch + field in scrcpy_options. Server options: mirror the param as an ADD_PARAM in execute_server() and a case in Options.parse(). New control messages require a coordinated change in enum sc_control_msg_type, sc_control_msg_serialize/is_droppable/destroy/log, ControlMessage type constants, ControlMessageReader parsing and Controller.handleEvent dispatch. New media sinks plug into the packet_source/packet_sink and frame_source/frame_sink trait interfaces; new input modalities implement key/mouse/gamepad processor interfaces (SDK/UHID/AOA are the three existing backends). Server-side work units implement AsyncProcessor; captures implement SurfaceCapture; audio sources implement AudioCapture.

Contributor handoff

Build with meson+ninja (tests only exist in debug buildtype); the server needs the Android SDK via Gradle or can be skipped with -Dprebuilt_server=path. The code is dense C11 with an sc_ prefix convention, callback-struct vtables, vecdeque containers, and a strict init/start/stop/join/destroy lifecycle; every thread posts SDL events rather than touching shared UI state. The single most important invariant is that the C wire protocol and the Java wire protocol are manually kept in sync — always change both sides. Read doc/develop.md, app/src/scrcpy.c, app/src/server.c and Server.java before editing anything cross-cutting.

Languages

LanguageFilesShare of source
C18060%
Java9833%
Markdown27docs/config
Shell207%
YAML2docs/config

Start reading here

Probable source entrypoint: app/ — inferred from the directory tree and manifest, not the upstream docs.

Source roots

app/ (201 files)

Source layout — app/

PathFiles
app/data/7
app/deps/10
app/src/168
app/tests/12
app/meson.build1
app/scrcpy-windows.manifest1
app/scrcpy-windows.rc1
app/scrcpy.11

Repository structure

Top-level pathFiles
app/201
server/107
doc/22
release/12
.github/5
config/2
gradle/2

Docs found: README.md

CI & config entrypoints

Structural health signals

Average score 8/10 across 96 analyzed source files; 22 hotspots surfaced.

These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.

Recent history