Genymobile/scrcpy
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/.
- The system is split into a host client (C, app/src) and an on-device server (Java, server/src); the client pushes the server and starts it, then they communicate over 1-3 sockets for video, audio and control.
- Client entrypoint: main() in app/src/main.c converts Windows UTF-16 argv to UTF-8, calls main_scrcpy() which runs scrcpy_parse_args() then either scrcpy() or scrcpy_otg() (when built with HAVE_USB and --otg is passed).
- scrcpy() in app/src/scrcpy.c is the client orchestrator: it fills a static 'struct scrcpy' containing sc_server, sc_screen, sc_audio_player, two sc_demuxer, two sc_decoder, sc_recorder, sc_video_regulator, sc_controller and sc_file_pusher, plus unions of SDK/UHID/AOA input backends.
- The client pushes the server jar to /data/local/tmp/scrcpy-server.jar and executes 'adb -s <serial> shell CLASSPATH=<jar> app_process / com.genymobile.scrcpy.Server <version> key=value...' in execute_server().
- Server-side entry: Server.main -> internalMain drops root privileges to uid 2000 (shell), installs a main Looper via reflection on Looper.sMainLooper (quitAllowed), then Options.parse() validates the client version arg before scrcpy() opens DesktopConnection and starts AsyncProcessors.
- The control protocol version is tied to the app version: the client passes SCRCPY_VERSION as argv[1] and Options.parse() throws IllegalArgumentException if it differs from BuildConfig.VERSION_NAME.
- DesktopConnection.open() creates/connects LocalSocket(s) named 'scrcpy_%08x' (scid) — listening with LocalServerSocket in tunnel-forward mode, connecting otherwise — for video, audio and control channels, optionally writing a dummy byte so the client detects a dead tunnel.
- Client dependencies are FFmpeg (libavformat, libavcodec, libavutil, libswresample), SDL3 >= 3.2.0, optionally libavdevice (V4L2) and libusb-1.0 (USB/HID/OTG); on Windows it links mingw32 and ws2_32.
- Build system: root meson.build declares project 'scrcpy' v4.1 (C11) with options compile_app/compile_server/prebuilt_server/portable/static/server_debugger/v4l2/usb; the server is produced by a Meson custom_target invoking server/scripts/build-wrapper.sh (Gradle) unless prebuilt_server is set.
- scrcpy-server is an Android Gradle module (com.android.application plugin, compileSdk 36, minSdk 21, targetSdk 36) with JUnit testImplementation and a checkstyle config applied from config/android-checkstyle.gradle.
- Threading model: one dedicated thread per socket direction — run_server (adb process + tunnel), run_demuxer per stream, run_controller + receiver for control, run_file_pusher — coordinated through SDL events posted to the main thread event_loop().
- Release pipeline: .github/workflows/release.yml (workflow_dispatch) tests server+client, builds server jar and client dists for linux-x86_64/win32/win64/macos aarch64+x86_64, then packages and checksums them via release/*.sh scripts.