Skip to main content

Project Structure

Scrcpy for Android is built with a two-module Gradle architecture that separates client and server responsibilities:
scrcpy-for-android/
├── app/              # Client module (Android UI application)
├── server/           # Server module (Screen capture service)
├── build.gradle      # Root build configuration
└── settings.gradle   # Module configuration

Module Configuration

The project uses Gradle’s multi-module setup defined in settings.gradle:
include ':server', ':app'
The client module depends on the server module. During build, the server APK is packaged as scrcpy-server.jar and embedded into the client app’s assets.

Build System

Root Configuration

The root build.gradle configures common settings for all modules:
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:8.0.0'
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url "https://jitpack.io" }
    }
}

Client Module (app/)

  • Package: org.client.scrcpy
  • Min SDK: 21 (Android 5.0)
  • Target SDK: 31 (Android 12)
  • Build dependency: Automatically builds and packages the server module
Key build configuration from app/build.gradle:49-54:
tasks.whenTaskAdded { task ->
    def buildType = gradle.startParameter.taskNames.any { 
        it.endsWith('Release') } ? 'Release' : 'Debug'
    
    task.dependsOn ":server:assemble${buildType}"
    task.dependsOn ":server:copyServer"
}

Server Module (server/)

  • Package: org.server.scrcpy
  • Output: APK renamed to scrcpy-server.jar
  • Copied to: app/src/main/assets/
The server module includes a custom Gradle task that packages the built APK:
tasks.register('copyServer', Copy) {
    // Copies server APK to client assets as scrcpy-server.jar
    rename { fileName -> 'scrcpy-server.jar' }
}

Client-Server Communication Protocol

Scrcpy for Android uses a local TCP socket-based protocol for bidirectional communication between the client and server.

Connection Flow

1

ADB Connection

Client establishes ADB connection to remote device using SendCommands.java
2

Server Deployment

Server JAR is pushed to /data/local/tmp/scrcpy-server.jar on the remote device
3

Port Forwarding

ADB forwards local port 7008 to remote port 7007: adb forward tcp:7008 tcp:7007
4

Server Launch

Server process starts on remote device via app_process
5

Socket Connection

Client connects to 127.0.0.1:7008, server listens on port 7007

Data Streams

The protocol supports two types of data streams:

Downstream (Server → Client)

  1. Device Resolution (16 bytes): Initial handshake sends width and height
  2. Video Packets: H.264 encoded video frames with metadata
  3. Audio Packets: AAC encoded audio frames with metadata
Packet structure from model/MediaPacket.java:
// Packet Header
[Type: 1 byte][Flag: 1 byte][Timestamp: 8 bytes][Data Length: 4 bytes][Data: N bytes]

Upstream (Client → Server)

Touch Events (20 bytes per event):
[Action: 4 bytes][Button: 4 bytes][X: 4 bytes][Y: 4 bytes][PointerID: 4 bytes]
Key Events (4 bytes):
[KeyCode: 4 bytes]

Connection Code Example

From Scrcpy.java:227-290, the connection establishment:
Socket socket = new Socket();
socket.connect(new InetSocketAddress(ip, port), 5000);

dataInputStream = new DataInputStream(socket.getInputStream());
dataOutputStream = new DataOutputStream(socket.getOutputStream());

// Read device resolution (16 bytes)
byte[] buf = new byte[16];
dataInputStream.read(buf, 0, 16);
for (int i = 0; i < remote_dev_resolution.length; i++) {
    remote_dev_resolution[i] = bytesToInt(buf, i * 4);
}

Development Workflow

Building the Project

# Build debug version
./gradlew assembleDebug

# Build release version
./gradlew assembleRelease
The build process automatically:
  1. Compiles the server module
  2. Packages server APK as JAR
  3. Copies JAR to client assets
  4. Builds the client APK with embedded server

Server Deployment

When the client app connects to a remote device (MainActivity.java:746-769):
  1. Extracts scrcpy-server.jar from assets
  2. Writes to local storage: context.getExternalFilesDir("scrcpy")
  3. Pushes to remote device via ADB
  4. Executes with app_process:
CLASSPATH=/data/local/tmp/scrcpy-server.jar \
app_process / org.server.scrcpy.Server /<ip> <size> <bitrate>

Code Organization

Client Module

  • UI and lifecycle management
  • Video/audio decoding
  • Input event handling
  • ADB communication

Server Module

  • Screen capture
  • Video/audio encoding
  • Input event injection
  • System service wrappers

Key Design Patterns

Service-Based Architecture

The client uses an Android Service (Scrcpy.java) for background streaming, ensuring continuity when the activity is paused.

Asynchronous Processing

Both encoding (server) and decoding (client) use worker threads to prevent blocking the main thread:
  • VideoDecoder.Worker - Client-side decoding thread
  • AudioDecoder.Worker - Client-side audio playback thread
  • ScreenEncoder - Server-side video encoding
  • AudioEncoder.EncoderCallback - Server-side audio encoding

Resource Cleanup

Server auto-deletes JAR on startup (Server.java:91-92):
Process cmd = Runtime.getRuntime().exec(
    "rm /data/local/tmp/scrcpy-server.jar");
cmd.waitFor();
This prevents stale server versions from persisting.

Next Steps

Client Module Deep Dive

Explore MainActivity, decoders, and input handling

Server Module Deep Dive

Learn about encoders, capture, and event injection

Build docs developers (and LLMs) love