scrcpy (screen copy) is a high-performance client-server application that displays and controls Android devices from a desktop computer. The client is a native C application rendering through SDL2 and FFmpeg; the server is an Android Java application injected onto the device via adb. All communication flows over adb connections (USB or TCP/IP), requiring no root access and no app installation on the device.
This guide covers architecture, build system, configuration, deployment, security considerations, and developer internals for scrcpy v3.3.4 (commit 247a37d).
Table of Contents
- Architecture Overview
- System Requirements
- Configuration Reference
- Deployment & Scaling
- Operations
- Performance Tuning
- Security
- Developer Internals
- Integration Points
Architecture Overview
scrcpy operates as a two-component system: a host-side C client and a device-side Java server. The client pushes the server JAR to the device, launches it via adb shell, and establishes socket connections for video, audio, and control streams.
┌─────────────────────────────────────────────────────────────────┐
│ HOST (Desktop) │
│ │
│ main.c ──► cli.c (parse args) ──► scrcpy.c (orchestration) │
│ │ │
│ ┌───────────────────────────┼──────────────────┐ │
│ │ │ │ │
│ server.c demuxer.c (x2) controller.c │
│ (push JAR, launch, (video + audio) (control msgs) │
│ establish sockets) │ │ │
│ │ decoder.c control_msg.c │
│ │ │ │ │
│ │ ┌───────┴────────┐ │ │
│ │ │ │ │ │
│ │ display.c / recorder.c │ │
│ │ screen.c (SDL) (FFmpeg mux) │ │
│ │ │ │ │
│ │ audio_player.c │ │
│ │ (SDL audio) │ │
│ │ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ HID subsystem │ │ │
│ │ │ hid/ (kb,mouse,pad) │ │ │
│ │ │ uhid/ (Linux UHID) │ │ │
│ │ │ usb/ (AOAv2) │ │ │
│ │ └──────────────────────┘ │ │
│ │ │ │
│ │ v4l2_sink.c (Linux) │ │
│ │ file_pusher.c (drag & drop) │ │
└────────────┼──────────────────────────────────────────┼────────┘
│ ADB (USB / TCP) │
│ ┌────────────────────┐ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ DEVICE (Android) │
│ │
│ /data/local/tmp/scrcpy-server.jar │
│ │
│ Server.java (entry point) │
│ │ │
│ ├── Options.java (parse server-side options) │
│ │ │
│ ├── video/ │
│ │ ScreenCapture / CameraCapture / NewDisplayCapture │
│ │ └── SurfaceEncoder ──► Streamer ──► socket │
│ │ │
│ ├── audio/ │
│ │ AudioDirectCapture / AudioPlaybackCapture │
│ │ └── AudioEncoder / AudioRawRecorder ──► socket │
│ │ │
│ ├── control/ │
│ │ ControlMessageReader ──► Controller │
│ │ └── injects via InputManager, ClipboardManager, etc. │
│ │ │
│ └── wrappers/ (hidden Android API access via reflection) │
│ ActivityManager, ClipboardManager, DisplayControl, │
│ DisplayManager, InputManager, PowerManager, │
│ ServiceManager, StatusBarManager, SurfaceControl, │
│ WindowManager, ContentProvider │
└─────────────────────────────────────────────────────────────────┘
Connection establishment
- The client pushes
scrcpy-server.jarto/data/local/tmp/scrcpy-server.jaron the device viaadb push. - The client starts the server via
adb shellwith appropriate arguments. - By default, the client uses
adb reverseto make the device connect back to the host (the client listens, the server connects). The--force-adb-forwardflag reverses this so the client connects to a forwarded port instead. - The socket name prefix used is
scrcpy_. - Separate socket connections are established for video, audio, and control streams.
Data flow summary
| Stream | Direction | Protocol |
|---|---|---|
| Video | Device → Host | Encoded frames (H.264/H.265/AV1) via packet stream |
| Audio | Device → Host | Encoded frames (Opus/AAC/FLAC/RAW) via packet stream |
| Control | Host → Device | Serialized control messages (touch, key, clipboard, etc.) |
| Device messages | Device → Host | Clipboard content, ACKs |
Note
scrcpy does not install anything on the device. The server JAR is pushed to a temporary location and executed via
adb shell. No permanent modifications are made.
System Requirements
Host requirements
| Component | Minimum | Notes |
|---|---|---|
| OS | Linux, macOS, Windows | V4L2 sink is Linux-only; OTG/UHID requires Linux |
| ADB | Any recent version | Must be in PATH or set via ADB env var |
| SDL2 | 2.x | Display and audio output |
| FFmpeg | libavcodec, libavformat, libavutil, libswresample | Video/audio decoding and recording |
| libusb | Optional | Required for AOAv2 (USB HID) and OTG mode |
| Meson + Ninja | Build-time only | Client build system |
Device requirements
| Component | Requirement |
|---|---|
| Android | API 21+ (Android 5.0 Lollipop) |
| USB debugging | Must be enabled in Developer Options |
| Audio capture | Android 11+ (API 30) for audio forwarding |
Build-time requirements
| Component | Purpose |
|---|---|
| Meson | Client build system |
| Ninja | Build backend |
| C compiler | GCC or Clang for client |
| pkg-config | Dependency resolution |
| Android SDK | compileSdk 36 for server |
| Gradle | Server build (or use build_without_gradle.sh) |
| Java JDK | Server compilation |
Important
The server targets
compileSdk: 36,minSdk: 21,targetSdk: 36. The server has zero runtime dependencies beyond the Android SDK itself;junit:junit:4.13.2is used for tests only.
Configuration Reference
CLI options (complete list)
All options are passed to the scrcpy client binary. The client translates them into server arguments where applicable.
Video options
| Option | Default | Description |
|---|---|---|
--video-codec |
h264 |
Video codec: h264, h265, or av1 |
--video-bit-rate |
8M |
Video encoding bit rate (e.g., 8M, 2M) |
--video-source |
display |
Video source: display or camera |
--video-buffer |
0 (ms) |
Video buffering delay for jitter compensation |
--max-size |
0 (unlimited) |
Limit both dimensions to this value (preserving aspect ratio) |
--max-fps |
(none) | Maximum framerate cap |
--capture-orientation |
0 |
Capture orientation in degrees |
--display-orientation |
0 |
Display orientation in degrees |
--record-orientation |
0 |
Recording orientation in degrees |
--display-id |
0 |
Android display ID to mirror |
--no-video |
(flag) | Disable video forwarding |
--no-video-playback |
(flag) | Disable video playback (but still allow recording) |
--video-codec-options |
(none) | Codec-specific options (key=value, comma-separated) |
--video-encoder |
(auto) | Force a specific video encoder |
Audio options
| Option | Default | Description |
|---|---|---|
--audio-codec |
opus |
Audio codec: opus, aac, flac, or raw |
--audio-bit-rate |
128K |
Audio encoding bit rate |
--audio-source |
output |
Audio source: output (device playback) or mic |
--audio-buffer |
50 (ms) |
Audio buffering delay |
--audio-output-buffer |
5 (ms) |
SDL audio output buffer size |
--no-audio |
(flag) | Disable audio forwarding |
--no-audio-playback |
(flag) | Disable audio playback (but still allow recording) |
--audio-codec-options |
(none) | Audio codec-specific options |
--audio-encoder |
(auto) | Force a specific audio encoder |
Recording options
| Option | Default | Description |
|---|---|---|
--record / -r |
(none) | Record to file (format inferred from extension) |
--record-format |
(auto) | Force recording format: mp4, mkv, m4a, mka, opus, aac, flac, wav |
--no-playback |
(flag) | Disable both video and audio playback (recording-only mode) |
Input options
| Option | Default | Description |
|---|---|---|
--keyboard |
(auto) | Keyboard input mode: sdk, uhid, aoa, disabled |
--mouse |
(auto) | Mouse input mode: sdk, uhid, aoa, disabled |
--gamepad |
(auto) | Gamepad input mode: uhid, aoa, disabled |
--mouse-bind |
bhsn:++++ (SDK) / ++++:bhsn (AOA/UHID) |
Mouse button bindings |
--shortcut-mod |
lalt,lsuper |
Shortcut modifier key(s) |
--no-control |
(flag) | Disable device control (mirror only) |
--no-key-repeat |
(flag) | Do not forward repeated key events |
Connection options
| Option | Default | Description |
|---|---|---|
--serial / -s |
(auto) | Device serial number (or ANDROID_SERIAL env) |
--tcpip |
(none) | Connect via TCP/IP (optionally specify host[:port]) |
--force-adb-forward |
(flag) | Use adb forward instead of adb reverse |
--tunnel-host |
(auto) | Host for tunnel setup |
--tunnel-port |
(auto) | Port for tunnel setup |
Display options
| Option | Default | Description |
|---|---|---|
--window-title |
(auto) | Custom window title |
--window-x |
(auto) | Initial window X position |
--window-y |
(auto) | Initial window Y position |
--window-width |
(auto) | Initial window width |
--window-height |
(auto) | Initial window height |
--window-borderless |
(flag) | Remove window decorations |
--always-on-top |
(flag) | Keep window above others |
--fullscreen / -f |
(flag) | Start in fullscreen |
--no-window |
(flag) | Do not display a window |
--rotation |
(auto) | Window rotation |
Device control options
| Option | Default | Description |
|---|---|---|
--turn-screen-off / -S |
(flag) | Turn device screen off on start |
--stay-awake / -w |
(flag) | Keep device awake while connected |
--show-touches / -t |
(flag) | Enable "Show touches" on device |
--power-off-on-close |
(flag) | Turn off device screen on close |
--no-power-on |
(flag) | Do not wake device on start |
--no-cleanup |
(flag) | Do not restore device state on exit |
--push-target |
/sdcard/Download/ |
Target directory for file pushes (drag & drop) |
--new-display |
(none) | Create a new virtual display |
Miscellaneous options
| Option | Default | Description |
|---|---|---|
--verbosity / -V |
info (debug in debug builds) |
Log level: verbose, debug, info, warn, error |
--version / -v |
(flag) | Print version |
--help / -h |
(flag) | Print help |
--pause-on-exit |
false |
Pause terminal on exit: true, false, if-error |
--prefer-text |
(flag) | Prefer text injection for input |
--raw-key-events |
(flag) | Inject raw key events |
--forward-all-clicks |
(flag) | Forward right and middle clicks to device |
--legacy-paste |
(flag) | Use legacy paste method |
--no-downsize-on-error |
(flag) | Do not retry with smaller size on encoding error |
--crop |
(none) | Crop the device screen (format: width:height:x:y) |
--render-driver |
(auto) | SDL render driver name |
--list-encoders |
(flag) | List available encoders |
--list-displays |
(flag) | List available displays |
--list-cameras |
(flag) | List available cameras |
--list-camera-sizes |
(flag) | List available camera sizes |
--otg |
(flag) | OTG mode (USB HID only, no mirroring) |
--v4l2-sink |
(none) | V4L2 device path for video output (Linux only) |
--v4l2-buffer |
0 (ms) |
V4L2 output buffer delay |
--time-limit |
(none) | Maximum session duration |
--camera-id |
(none) | Camera ID to use |
--camera-size |
(auto) | Camera capture size |
--camera-facing |
(none) | Camera facing: front, back, external |
--camera-ar |
(none) | Camera aspect ratio |
--camera-fps |
(none) | Camera capture framerate |
--camera-high-speed |
(flag) | Use high-speed camera capture |
Environment variables
| Variable | Purpose |
|---|---|
ADB |
Path to the adb binary. Overrides the default adb found in PATH. |
ANDROID_SERIAL |
Device serial number. Equivalent to --serial / -s. |
SCRCPY_SERVER_PATH |
Path to the scrcpy-server JAR on the host. Overrides the default installed location (PREFIX/share/scrcpy/scrcpy-server). |
SCRCPY_ICON_PATH |
Path to a custom window icon file. |
Exit codes
| Code | Meaning |
|---|---|
0 |
Normal exit |
1 |
Start failure (could not initialize, connect, or launch server) |
2 |
Device disconnected during session |
Default ADB port
The default ADB port for TCP/IP connections is 5555.
Deployment & Scaling
Building from source
Building the server
The server is an Android Java application built with Gradle:
# Standard Gradle build
cd server
./gradlew assembleRelease
The server can also be built without Gradle using the Android SDK directly:
cd server
./build_without_gradle.sh
Note
The server build produces a JAR file. ProGuard rules are defined in
server/proguard-rules.profor release builds.
Building the client
The client uses Meson and Ninja:
# Configure
meson setup build --buildtype=release
# Build
ninja -C build
# Install
sudo ninja -C build install
Build options are defined in meson_options.txt and can be set during configuration:
meson setup build --buildtype=release -Doption_name=value
Using install_release.sh
The install_release.sh script automates the full process: downloads a prebuilt server binary, builds the client with Meson and Ninja, and installs everything:
./install_release.sh
Cross-compilation
Cross-compilation configurations are provided for Windows targets:
| File | Target |
|---|---|
cross_win32.txt |
Windows 32-bit |
cross_win64.txt |
Windows 64-bit |
Usage:
meson setup build-win64 --cross-file cross_win64.txt --buildtype=release
ninja -C build-win64
Warning
Cross-compilation requires the appropriate cross-compiler toolchain (e.g., MinGW-w64) and cross-compiled versions of all dependencies (SDL2, FFmpeg, libusb).
Packaging
Linux desktop integration
The app/data/ directory contains standard Linux desktop integration files:
scrcpy.desktop— standard desktop entryscrcpy-console.desktop— desktop entry that keeps terminal openicon.png,icon.svg,icon.ico— application icons in multiple formatsbash-completion/scrcpy— Bash tab completionzsh-completion/_scrcpy— Zsh tab completion
Windows packaging
scrcpy-windows.manifest— Windows application manifestscrcpy-windows.rc— Windows resource file (icon, version info)open_a_terminal_here.bat— helper batch filescrcpy-console.bat— launch with console windowscrcpy-noconsole.vbs— launch without console window
Man page
A man page is provided at app/scrcpy.1 for Unix-like systems.
Server deployment on device
The server JAR is pushed to /data/local/tmp/scrcpy-server.jar on the device. On the host, the client looks for the server binary at:
- The path specified by
SCRCPY_SERVER_PATHenvironment variable PREFIX/share/scrcpy/scrcpy-server(standard install location)
Operations
Running scrcpy
Basic usage
# Mirror default device over USB
scrcpy
# Mirror specific device
scrcpy -s DEVICE_SERIAL
# Mirror over TCP/IP
scrcpy --tcpip=192.168.1.100:5555
# Record while mirroring
scrcpy --record=session.mp4
# Mirror without audio
scrcpy --no-audio
# Mirror-only, no control
scrcpy --no-control
Display modes
# Fullscreen
scrcpy --fullscreen
# Always on top, borderless
scrcpy --always-on-top --window-borderless
# No display window (recording only)
scrcpy --no-window --record=capture.mp4
# Limit resolution
scrcpy --max-size=1024
# Mirror secondary display
scrcpy --display-id=1
Camera mirroring
# List available cameras
scrcpy --list-cameras
# Mirror front camera
scrcpy --video-source=camera --camera-facing=front
# Specific camera at specific size
scrcpy --video-source=camera --camera-id=0 --camera-size=1920x1080
Monitoring
FPS counter
scrcpy includes a built-in FPS counter (fps_counter.c) that can be enabled to log frame rates during a session. Verbosity level debug or lower will show frame timing information.
scrcpy --verbosity=debug
Log verbosity levels
| Level | Content |
|---|---|
verbose |
All internal tracing |
debug |
Debug information including frame timing |
info |
Normal operational messages (default) |
warn |
Warnings only |
error |
Errors only |
Updating
scrcpy is a single-version deployment: the client and server must match. When updating:
- Build or download the new client binary.
- Ensure the matching server JAR is available at the expected path.
- The old server on the device is overwritten on each connection (pushed fresh every time).
Caution
The client and server versions must be identical. A version mismatch will cause connection failures or undefined behavior.
File transfer
Drag and drop files onto the scrcpy window to push them to the device:
- Regular files are pushed to the directory specified by
--push-target(default:/sdcard/Download/). - APK files are automatically installed via
adb install.
This is handled by file_pusher.c, which uses adb push under the hood.
Performance Tuning
Video encoding
Codec selection
| Codec | Flag | Notes |
|---|---|---|
| H.264 | --video-codec=h264 |
Default. Widest hardware support. |
| H.265 (HEVC) | --video-codec=h265 |
Better compression, may not be available on all devices. |
| AV1 | --video-codec=av1 |
Best compression, limited device support. |
Bit rate tuning
The default video bit rate is 8M (8 Mbps). Adjust based on use case:
# Low bandwidth / low latency
scrcpy --video-bit-rate=2M
# High quality recording
scrcpy --video-bit-rate=16M
Resolution limiting
# Limit to 1024px on the longest dimension
scrcpy --max-size=1024
# Cap framerate
scrcpy --max-fps=30
Tip
Reducing
--max-sizeand--max-fpssignificantly decreases encoding load on the device and bandwidth usage, improving latency on slower connections.
Audio buffering
The audio pipeline uses a regulator (audio_regulator.c) that compensates for jitter using libswresample-based sample rate adjustment. When underflow occurs, silence is inserted to prevent audio glitches.
| Parameter | Default | Effect |
|---|---|---|
--audio-buffer |
50 ms | Jitter buffer before playback. Higher = smoother but more latency. |
--audio-output-buffer |
5 ms | SDL output buffer. Higher = less CPU but more latency. |
# Minimize audio latency (may cause glitches on poor connections)
scrcpy --audio-buffer=20 --audio-output-buffer=2
# Maximize audio stability
scrcpy --audio-buffer=200 --audio-output-buffer=20
Video buffering
| Parameter | Default | Effect |
|---|---|---|
--video-buffer |
0 ms | Delay buffer for jitter compensation. 0 = no buffering (lowest latency). |
The delay buffer (delay_buffer.c) holds frames to smooth out jitter. The frame buffer (frame_buffer.c) is a thread-safe single-frame buffer that always holds the latest decoded frame, dropping older frames when the display cannot keep up.
# Add 50ms video buffer for unstable connections
scrcpy --video-buffer=50
Codec options
Pass encoder-specific options directly to the device encoder:
# Set H.264 profile and level
scrcpy --video-codec-options=profile=1,level=2048
Encoder selection
List and select specific hardware/software encoders:
# List available encoders
scrcpy --list-encoders
# Force specific encoder
scrcpy --video-encoder=OMX.qcom.video.encoder.avc
Note
The packet merger (
packet_merger.c) reassembles codec packets that may be split across multiple reads. This is transparent to the user but is relevant when debugging encoding issues.
Screen-off mode performance
Using --turn-screen-off / -S disables the device display, which can affect encoding performance on some devices (see GitHub issue #3376). If you observe performance degradation, test without this flag.
Security
Hidden Android API access
The server accesses non-public Android APIs via Java reflection through the wrappers/ package. This is the mechanism that allows scrcpy to function without root and without installing an APK.
Wrapped system services
| Wrapper | Android Service | Purpose |
|---|---|---|
ActivityManager |
activity |
Task management |
ClipboardManager |
clipboard |
Clipboard sync |
ContentProvider |
Content resolver | Content access |
DisplayControl |
Display service | Display control |
DisplayManager |
display |
Display enumeration |
InputManager |
input |
Touch/key injection |
PowerManager |
power |
Wake/sleep control |
ServiceManager |
servicemanager |
Service resolution |
StatusBarManager |
statusbar |
Status bar control |
SurfaceControl |
Surface compositor | Screen capture |
WindowManager |
window |
Window management |
Warning
These wrappers use reflection to access hidden (
@hide) Android APIs. These APIs are not part of the public Android SDK and may change or be removed in any Android version. The server targetscompileSdk: 36but must handle API differences across Android versions (API 21+).
Network security
ADB connection model
scrcpy communicates exclusively over adb. There is no independent network protocol.
- USB mode: All traffic flows over the USB cable. No network exposure.
- TCP/IP mode (
adb tcpip/--tcpip): ADB listens on port 5555 (default) on the device's network interface. This is an ADB-level concern, not scrcpy-specific.
Caution
When using ADB over TCP/IP, the ADB connection is unencrypted and unauthenticated on the network. Anyone on the same network can connect to the device. Use TCP/IP mode only on trusted networks. Consider using a VPN or SSH tunnel for remote connections.
Socket communication
- The client and server communicate via Unix domain sockets tunneled through adb (using
adb reverseoradb forward). - Socket name prefix:
scrcpy_ - No TLS or encryption is applied at the scrcpy level; security relies on the adb transport layer.
Permissions model
scrcpy requires only that USB debugging be enabled on the device. It does not require:
- Root access
- Device unlock (though initial ADB authorization requires user approval on the device)
- Any installed APK
The server runs as the shell user, which has elevated privileges compared to normal apps (this is what enables input injection, screen capture, and clipboard access without root).
Data handling
- No data persistence on device: The server JAR at
/data/local/tmp/scrcpy-server.jaris the only file written to the device. It is overwritten on each connection. - Clipboard sync: When clipboard sync is enabled (default), clipboard content is transmitted between host and device. Use
--no-controlto disable all device control including clipboard sync. - File push: Files dragged onto the window are pushed via
adb pushto the push target directory.
Developer Internals
Repository structure
scrcpy/
├── app/
│ ├── meson.build # Client build definition
│ ├── scrcpy.1 # Man page
│ ├── scrcpy-windows.manifest # Windows manifest
│ ├── scrcpy-windows.rc # Windows resource file
│ ├── data/
│ │ ├── icon.ico, icon.png, icon.svg # App icons
│ │ ├── scrcpy.desktop # Linux desktop entry
│ │ ├── scrcpy-console.desktop # Console desktop entry
│ │ ├── open_a_terminal_here.bat # Windows helper
│ │ ├── scrcpy-console.bat # Windows console launcher
│ │ ├── scrcpy-noconsole.vbs # Windows no-console launcher
│ │ ├── bash-completion/scrcpy # Bash completions
│ │ └── zsh-completion/_scrcpy # Zsh completions
│ ├── src/ # Client C source (67 files)
│ └── tests/ # Client unit tests (11 files)
├── server/
│ ├── build.gradle # Server Gradle build
│ ├── build_without_gradle.sh # Build without Gradle
│ ├── proguard-rules.pro # ProGuard config
│ ├── meson.build # Server Meson integration
│ ├── scripts/build-wrapper.sh # Build wrapper
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── aidl/ # AIDL interfaces
│ │ └── java/com/genymobile/scrcpy/
│ │ ├── Server.java # Entry point
│ │ ├── Options.java # Server options
│ │ ├── audio/ # Audio capture & encoding
│ │ ├── control/ # Control message handling
│ │ ├── device/ # Device interaction
│ │ ├── opengl/ # OpenGL support
│ │ ├── util/ # Utilities
│ │ ├── video/ # Video capture & encoding
│ │ └── wrappers/ # Hidden API wrappers
│ └── test/ # Server unit tests
├── config/
│ ├── android-checkstyle.gradle # Checkstyle Gradle integration
│ └── checkstyle/checkstyle.xml # Checkstyle rules
├── doc/ # 18 topic-specific docs
├── gradle/wrapper/ # Gradle wrapper
├── meson.build # Root Meson build
├── meson_options.txt # Client build options
├── build.gradle # Root Gradle build
├── cross_win32.txt # Win32 cross-compilation
├── cross_win64.txt # Win64 cross-compilation
└── install_release.sh # Automated install script
Module reference (client)
Core orchestration
| Module | Purpose |
|---|---|
main.c |
Entry point, argument parsing dispatch |
cli.c / cli.h |
Complete CLI option parsing and validation |
scrcpy.c / scrcpy.h |
Main orchestration: initializes all subsystems, connects components |
options.h |
Options struct definition and defaults |
Server management
| Module | Purpose |
|---|---|
server.c / server.h |
Push server JAR, launch via adb shell, establish socket connections |
adb/adb.c / adb.h |
ADB command execution, device listing, port management |
adb/adb_parser.c |
Parse ADB output (device list, etc.) |
adb/adb_tunnel.c |
Tunnel setup (reverse/forward) |
Video pipeline
| Module | Purpose |
|---|---|
demuxer.c / demuxer.h |
Receive and demultiplex encoded packets from server |
decoder.c / decoder.h |
FFmpeg-based video/audio decoding |
display.c / display.h |
SDL texture management and rendering |
screen.c / screen.h |
SDL window management, resize, rotation |
frame_buffer.c / frame_buffer.h |
Thread-safe latest-frame buffer (drops old frames) |
delay_buffer.c / delay_buffer.h |
Jitter compensation buffer |
packet_merger.c / packet_merger.h |
Reassemble split codec packets |
Audio pipeline
| Module | Purpose |
|---|---|
audio_player.c / audio_player.h |
SDL audio callback playback |
audio_regulator.c / audio_regulator.h |
Real-time audio buffering with libswresample compensation, silence insertion on underflow |
Input and control
| Module | Purpose |
|---|---|
input_manager.c / input_manager.h |
Process SDL events, dispatch to appropriate processor |
controller.c / controller.h |
Serialize and send control messages to server |
control_msg.c / control_msg.h |
Control message serialization format |
device_msg.c / device_msg.h |
Device-to-host message deserialization |
hid/hid_keyboard.c |
HID keyboard protocol |
hid/hid_mouse.c |
HID mouse protocol |
hid/hid_gamepad.c |
HID gamepad protocol |
uhid/ |
Linux UHID kernel module interface |
usb/ |
AOAv2 USB protocol, libusb integration |
usb/scrcpy_otg.c |
OTG mode (direct USB HID, no mirroring) |
Recording and output
| Module | Purpose |
|---|---|
recorder.c / recorder.h |
FFmpeg muxing to mp4/mkv/m4a/mka/opus/aac/flac/wav |
v4l2_sink.c / v4l2_sink.h |
Output to v4l2loopback device (Linux only) |
Utilities
| Module | Purpose |
|---|---|
file_pusher.c / file_pusher.h |
Drag-and-drop file/APK transfer via adb push |
fps_counter.c / fps_counter.h |
Frame rate measurement and logging |
util/str.c / strbuf.c |
String utilities and string buffer |
util/binary.c |
Binary read/write helpers |
util/vector.c / vecdeque.c |
Dynamic array and deque data structures |
Trait system (interface pattern)
scrcpy implements a trait-based interface pattern in C using function pointers in structs. This enables polymorphic composition of pipeline stages.
Frame traits
| Trait | Header | Methods |
|---|---|---|
sc_frame_sink |
trait/frame_sink.h |
open(), close(), push() -- receives decoded frames |
sc_frame_source |
trait/frame_source.h |
add_sink(), remove_sink() -- dispatches frames to sinks |
Packet traits
| Trait | Header | Methods |
|---|---|---|
sc_packet_sink |
trait/packet_sink.h |
open(), close(), push() -- receives encoded packets |
sc_packet_source |
trait/packet_source.h |
add_sink(), remove_sink() -- dispatches packets to sinks |
Input traits
| Trait | Header | Methods |
|---|---|---|
sc_key_processor |
trait/key_processor.h |
Key event processing interface |
sc_mouse_processor |
trait/mouse_processor.h |
Mouse event processing interface |
sc_gamepad_processor |
trait/gamepad_processor.h |
Gamepad event processing interface |
This system allows components to be composed flexibly. For example, a demuxer (packet source) can feed packets to both a decoder and a recorder simultaneously. A decoder (frame source) can feed frames to both a display and a V4L2 sink.
Demuxer (packet_source)
├──► Decoder (packet_sink + frame_source)
│ ├──► Display (frame_sink)
│ └──► V4L2 Sink (frame_sink)
└──► Recorder (packet_sink)
Server module reference
| Package | Key Classes | Purpose |
|---|---|---|
| (root) | Server, Options |
Entry point, option parsing |
audio/ |
AudioCapture, AudioDirectCapture, AudioPlaybackCapture, AudioEncoder, AudioRawRecorder |
Audio capture and encoding |
video/ |
ScreenCapture, CameraCapture, NewDisplayCapture, SurfaceEncoder, Streamer |
Video capture and encoding |
control/ |
Controller, ControlMessageReader |
Input injection |
device/ |
Device interaction helpers | Device state management |
opengl/ |
OpenGL utilities | GPU-accelerated processing |
util/ |
Utility classes | String utils, binary helpers |
wrappers/ |
11 wrapper classes | Hidden Android API access via reflection |
AIDL interfaces
IOnPrimaryClipChangedListener.aidl-- clipboard change notificationsIDisplayWindowListener.aidl-- display window events
Code style and quality
- Server code style: Enforced via Checkstyle (
config/checkstyle/checkstyle.xml), integrated throughconfig/android-checkstyle.gradle. - Client code style: C99 standard, consistent naming conventions (
sc_prefix for scrcpy types). - ProGuard: Release server builds use ProGuard rules from
server/proguard-rules.pro.
Testing
Client tests
Located in app/tests/, 11 test files:
| Test | Coverage |
|---|---|
test_adb_parser |
ADB output parsing |
test_audiobuf |
Audio buffer operations |
test_binary |
Binary read/write utilities |
test_cli |
CLI argument parsing |
test_control_msg_serialize |
Control message serialization |
test_device_msg_deserialize |
Device message deserialization |
test_orientation |
Orientation calculations |
test_str |
String utilities |
test_strbuf |
String buffer operations |
test_vecdeque |
Vector deque data structure |
test_vector |
Dynamic vector data structure |
Running client tests:
meson setup build
ninja -C build
meson test -C build
Server tests
Located in server/src/test/:
| Test | Coverage |
|---|---|
ControlMessageReaderTest |
Control message deserialization |
DeviceMessageWriterTest |
Device message serialization |
BinaryTest |
Binary utilities |
CodecOptionsTest |
Codec option parsing |
CommandParserTest |
Command parsing |
StringUtilsTest |
String utilities |
Running server tests:
cd server
./gradlew test
Build system details
Meson build (client)
The root meson.build defines the overall project. app/meson.build specifies the client target with all source files, dependencies, and build options.
Key Meson dependencies resolved via pkg-config:
sdl2libavcodec,libavformat,libavutil,libswresamplelibusb-1.0(optional)
Build options are defined in meson_options.txt.
Gradle build (server)
- Android Gradle Plugin:
8.13.0 compileSdk: 36minSdk: 21targetSdk: 36- Version:
3.3.4 - Test dependency:
junit:junit:4.13.2
Tip
The server can be built without Gradle using
server/build_without_gradle.sh, which invokes the Android SDK tools directly. This is useful in CI environments or when Gradle is not available.
Integration Points
V4L2 sink (Linux only)
scrcpy can output video to a V4L2 loopback device, making the Android screen appear as a virtual webcam:
# Load v4l2loopback kernel module
sudo modprobe v4l2loopback
# Stream to V4L2 device
scrcpy --v4l2-sink=/dev/video2
# With buffering
scrcpy --v4l2-sink=/dev/video2 --v4l2-buffer=50
The V4L2 sink (v4l2_sink.c) implements the sc_frame_sink trait, receiving decoded frames from the decoder and writing them to the V4L2 device. It can operate alongside the normal display window, or with --no-window for headless streaming.
# Headless V4L2 streaming (no display window)
scrcpy --v4l2-sink=/dev/video2 --no-window
# V4L2 + recording simultaneously
scrcpy --v4l2-sink=/dev/video2 --record=capture.mp4
OTG mode
OTG mode (usb/scrcpy_otg.c) provides direct USB HID input without any mirroring or ADB connection. The host sends keyboard, mouse, and gamepad input directly over USB using the AOAv2 (Android Open Accessory v2) protocol.
# OTG mode with keyboard and mouse
scrcpy --otg
# OTG mode with specific device
scrcpy --otg -s DEVICE_SERIAL
Important
OTG mode requires libusb and only works on Linux. It bypasses ADB entirely -- the device appears as a USB accessory. No video/audio mirroring occurs in OTG mode.
HID input modes
scrcpy supports three input injection methods, selectable independently for keyboard, mouse, and gamepad:
| Mode | Method | Requirements | Behavior |
|---|---|---|---|
sdk |
Android InputManager API | ADB connection | Translates to Android key/touch events. Works everywhere but may not handle all key combinations. |
uhid |
Linux UHID kernel module | Linux host, ADB | Creates virtual HID devices via /dev/uhid. Physical-level input simulation. |
aoa |
AOAv2 USB protocol | Linux host, USB, libusb | USB HID over AOAv2. Physical-level input, works even when screen is off. |
# UHID keyboard + mouse
scrcpy --keyboard=uhid --mouse=uhid
# AOA keyboard + mouse
scrcpy --keyboard=aoa --mouse=aoa
# Mixed: UHID keyboard, SDK mouse
scrcpy --keyboard=uhid --mouse=sdk
Virtual display
scrcpy can create a new virtual display on the device instead of mirroring an existing one:
# Create a new virtual display
scrcpy --new-display
# Specify virtual display resolution
scrcpy --new-display=1920x1080
This uses the NewDisplayCapture server component, which creates an Android virtual display and captures its content.
Recording integration
Recording works independently of display and can capture to multiple container formats:
| Extension | Format | Content |
|---|---|---|
.mp4 |
MP4 | Video + Audio |
.mkv |
Matroska | Video + Audio |
.m4a |
M4A | Audio only |
.mka |
Matroska Audio | Audio only |
.opus |
Opus | Audio only |
.aac |
AAC | Audio only |
.flac |
FLAC | Audio only |
.wav |
WAV | Audio only |
# Record video + audio to MP4
scrcpy --record=session.mp4
# Record audio only
scrcpy --no-video --record=audio.opus
# Record without display
scrcpy --no-playback --record=capture.mkv
Known issues (from GitHub)
| Issue | Description |
|---|---|
| #3376 | --turn-screen-off (-S) may cause performance loss on some devices |
| #4821 | Proprietary apps may show black screen (DRM-protected content) |
| #6742 | 30 FPS cap reported on Samsung MediaTek devices |
| #6682 | Screenshot triggering |
| #6632 | Native window resize behavior |
| #6767 | Custom keyboard layout support |
| #6759 | Aspect ratio during resize |
| #1706 | Using scrcpy without ADB |