# How scrcpy's Client-Server Architecture Works Internally: A Deep Dive into the Source Code

> Explore scrcpy's client-server architecture. Learn how it pushes a Java server, uses ADB tunnel sockets for video, audio, and control, and streams media for a seamless Android mirroring experience.

- Repository: [Genymobile/scrcpy](https://github.com/Genymobile/scrcpy)
- Tags: deep-dive
- Published: 2026-02-25

---

**scrcpy operates through a lightweight client-server architecture where the desktop client pushes a Java server to the Android device, establishes three ADB tunnel sockets for video, audio, and control, and streams encoded media while relaying input events back to the device.**

The Genymobile/scrcpy project implements this architecture by splitting functionality across two distinct binaries: a native desktop client and a Java-based Android server. This design requires no root privileges or device-side installation, leveraging only standard ADB and Android APIs to achieve low-latency screen mirroring.

## The Two-Sided Architecture

scrcpy's implementation cleanly separates concerns between the controlling desktop machine and the controlled Android device.

### Desktop Client (app/src/)

The desktop component resides in the `app/src/` directory, with entry points in **[`app/src/main.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/main.c)** and server management logic in **[`app/src/server.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/server.c)**. This binary handles:

- Parsing command-line arguments and initializing SDL/FFmpeg
- Pushing the server JAR to the device via ADB
- Establishing forward tunnels for the three communication channels
- Decoding video (FFmpeg) and audio streams for display/playback
- Capturing user input and serializing it to the control channel

### Android Server (server/src/)

The device-side component is a Java archive located at `scrcpy-server.jar`, with sources in **`server/src/main/java/com/genymobile/scrcpy/`**. Key classes include:

- **[`Server.java`](https://github.com/Genymobile/scrcpy/blob/main/Server.java)**: Entry point that parses parameters and initializes the system
- **[`DesktopConnection.java`](https://github.com/Genymobile/scrcpy/blob/main/DesktopConnection.java)**: Manages the three abstract Unix sockets
- **[`Streamer.java`](https://github.com/Genymobile/scrcpy/blob/main/Streamer.java)**: Writes encoded packets to file descriptors
- **[`SurfaceEncoder.java`](https://github.com/Genymobile/scrcpy/blob/main/SurfaceEncoder.java)** and **[`AudioEncoder.java`](https://github.com/Genymobile/scrcpy/blob/main/AudioEncoder.java)**: Capture and encode media
- **[`Controller.java`](https://github.com/Genymobile/scrcpy/blob/main/Controller.java)**: Listens for input events and injects them via `InputManager`

## Phase 1: Server Installation and Launch

The handshake begins with the desktop client preparing the environment on the Android device.

### Pushing the Server JAR

The function `push_server()` in **[`app/src/server.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/server.c)** copies the server binary to `/data/local/tmp/`:

```c
bool ok = sc_adb_push(intr, serial, server_path,
                      SC_DEVICE_SERVER_PATH, 0);   // src/server.c#L67-L68

```

### Building the app_process Command

The `execute_server()` function constructs the command line to launch the Java server via Android's `app_process` wrapper:

```c
cmd[count++] = "app_process";
cmd[count++] = "/";                     // unused directory argument
cmd[count++] = "com.genymobile.scrcpy.Server";
cmd[count++] = SCRCPY_VERSION;          // src/server.c#L47-L50

```

This command includes the *socket identifier* (`scid`) parameter, which both sides use to agree on socket names.

### Creating ADB Forward Tunnels

For each channel (video, audio, control), the client issues `adb forward` commands to map local TCP ports to **abstract Unix sockets** on the device. The socket name derives from the `scid`:

```java
private static String getSocketName(int scid) {
    return scid == -1 ? "scrcpy"
                      : "scrcpy_" + String.format("_%08x", scid);
}   // server/src/main/java/com/genymobile/scrcpy/server/Server.java#L47-L55

```

## Phase 2: Server Bootstrap on Device

Once launched, the Java server initializes its communication endpoints.

### Opening the Desktop Connection

The `Server` class calls `DesktopConnection.open()`, passing the `scid` and feature flags:

```java
DesktopConnection connection = DesktopConnection.open(
        scid, tunnelForward, video, audio, control, sendDummyByte);

```

### Establishing Socket Connections

[`DesktopConnection.java`](https://github.com/Genymobile/scrcpy/blob/main/DesktopConnection.java) creates three `LocalSocket` instances connected to the abstract socket name:

```java
LocalSocket videoSocket = connect(socketName);
LocalSocket audioSocket = connect(socketName);
LocalSocket controlSocket = connect(socketName);
// server/src/main/java/com/genymobile/scrcpy/device/DesktopConnection.java#L56-L99

```

These sockets expose raw file descriptors that the server uses for streaming.

## Phase 3: Data Flow and Streaming

With connections established, the system enters its operational state, handling three distinct data streams.

### Video Stream

The **video channel** carries H.264 encoded frames from the device to the desktop:

- **Server side**: `SurfaceEncoder` captures the display surface, encodes via `MediaCodec`, and writes packets to a `Streamer` object
- **Streamer**: Writes to the video file descriptor using `Streamer.writePacket`
- **Client side**: `VideoDecoder` (FFmpeg) reads from the TCP socket, decodes frames, and renders via SDL

### Audio Stream

The **audio channel** transmits Opus-encoded audio (or raw PCM):

- **Server side**: `AudioCapture` records system audio, `AudioEncoder` encodes it, and a `Streamer` forwards packets to the audio fd
- **Client side**: `AudioPlayer` reads the socket, decodes, and plays through the OS audio API

### Control Stream

The **control channel** handles bidirectional command and input:

- **Desktop to Device**: `ControlSender` serializes user actions (touch, key presses, clipboard) into `DeviceMessage` objects and writes them through the control fd
- **Device to Desktop**: `Controller` reads `ControlMessage` objects via `ControlChannel` and injects events using Android's `InputManager`

The protocol uses length-prefixed binary messages defined in **[`ControlMessage.java`](https://github.com/Genymobile/scrcpy/blob/main/ControlMessage.java)**, **[`DeviceMessage.java`](https://github.com/Genymobile/scrcpy/blob/main/DeviceMessage.java)**, and their respective Reader/Writer classes.

## Phase 4: Shutdown Sequence

When the desktop client terminates, it triggers a clean shutdown:

1. The client closes its TCP sockets
2. `DesktopConnection.shutdown()` on the server closes the three `LocalSocket` instances:
   ```java
   public void shutdown() throws IOException {
       if (videoSocket != null) { videoSocket.shutdownInput(); videoSocket.shutdownOutput(); }
       if (audioSocket != null) { audioSocket.shutdownInput(); audioSocket.shutdownOutput(); }
       if (controlSocket != null) { controlSocket.shutdownInput(); controlSocket.shutdownOutput(); }
   }   // DesktopConnection.java#L28-L41
   ```

3. Socket closure causes the `Looper` to quit (`Looper.getMainLooper().quitSafely()`), stopping encoders and the controller
4. The Java process terminates, and the client removes the temporary JAR from the device

## Summary

- **scrcpy's client-server architecture** splits functionality between a native desktop client and a Java server pushed temporarily to the Android device.
- **Three ADB tunnels** (video, audio, control) connect the sides using abstract Unix sockets identified by a unique `scid`.
- **Media streaming** uses `SurfaceEncoder` and `AudioEncoder` on the device, with FFmpeg/SDL decoding on the desktop.
- **Input injection** travels from desktop to device via a binary protocol implemented in `ControlChannel` and `Controller`.
- **No installation required**: The server runs via `app_process` and cleans up on exit, requiring only standard ADB access.

## Frequently Asked Questions

### What is the role of the scid parameter in scrcpy?

The **scid** (socket connection identifier) is a hexadecimal value generated by the desktop client that ensures unique socket names when multiple scrcpy instances run simultaneously. According to the source code in [`Server.java`](https://github.com/Genymobile/scrcpy/blob/main/Server.java), the socket name becomes `scrcpy_<scid>` (or just `scrcpy` for legacy compatibility), preventing collisions between separate sessions on the same device.

### How does scrcpy establish communication without root access?

scrcpy leverages the **ADB daemon** running on the device, which operates with sufficient privileges to open abstract Unix sockets and access the `InputManager` service. The desktop client uses standard `adb forward` commands to tunnel TCP ports to these sockets, while the server runs inside a sandboxed `app_process` shell with the `CLASSPATH` set to the temporary JAR. This design requires only standard developer permissions, not root.

### What protocols does scrcpy use for video and audio streaming?

scrcpy uses **H.264** for video encoding via Android's `MediaCodec` API, with the desktop client decoding via FFmpeg. For audio, it supports **Opus** encoding (when available) or raw PCM capture, depending on the Android version and options. Both streams travel over raw TCP sockets tunneled through ADB, using a simple packet-based protocol where [`Streamer.java`](https://github.com/Genymobile/scrcpy/blob/main/Streamer.java) writes length-prefixed frames to the file descriptors.

### How does the desktop client handle user input events?

User input is captured by SDL on the desktop side and serialized into binary `DeviceMessage` objects by `ControlSender`. These messages travel through the control socket (the third ADB tunnel) to the device's `ControlChannel`, where `ControlMessageReader` deserializes them. The `Controller` class then injects the events using Android's `InputManager` system service, supporting touch, keyboard, clipboard, and hardware button events.