This page describes the overall client-server architecture of scrcpy: how the two processes are structured, how they communicate, and what data flows between them. For details about individual subsystems, see the related pages:
scrcpy operates as two cooperating processes:
| Process | Language | Location | Entry Point |
|---|---|---|---|
| Client | C | Host computer | app/src/main.c → scrcpy() in app/src/scrcpy.c |
| Server | Java | Android device | server/src/main/java/com/genymobile/scrcpy/Server.java |
The client orchestrates the session: it selects a device, pushes the server JAR, launches it on the device, establishes socket connections, then processes the resulting media streams and forwards input.
The server runs on the Android device under the com.android.shell identity via app_process, captures screen/audio, encodes it, and injects input events into the Android framework.
Sources: app/src/scrcpy.c48-92 server/src/main/java/com/genymobile/scrcpy/Server.java70-195
Diagram: scrcpy Two-Process Overview
Sources: app/src/scrcpy.c380-497 server/src/main/java/com/genymobile/scrcpy/Server.java100-168 app/src/server.h76-98
The client and server communicate exclusively over TCP sockets that are tunneled through ADB. The client sets up the tunnel before the server starts, using one of two mechanisms:
| Mode | ADB Command | Direction | Default |
|---|---|---|---|
| Reverse | adb reverse | Device connects to client | Yes |
| Forward | adb forward | Client connects to device | Fallback (or --force-adb-forward) |
Diagram: Server Startup and Socket Connection
Sources: app/src/server.c57-70 app/src/server.c204-464 app/src/server.c588-742 server/src/main/java/com/genymobile/scrcpy/Server.java93-104
The server is launched using this adb shell command, constructed in execute_server():
adb -s <serial> shell CLASSPATH=/data/local/tmp/scrcpy-server.jar \
app_process / com.genymobile.scrcpy.Server <version> [key=value ...]
All session parameters (codec, bitrate, display ID, etc.) are passed as key=value arguments on the command line, parsed by Options.parse() in Options.java.
Sources: app/src/server.c204-249 server/src/main/java/com/genymobile/scrcpy/Options.java292-310
Once the tunnel is established, up to three TCP sockets are opened — one per enabled data stream:
| Socket Field | Direction | Purpose |
|---|---|---|
sc_server.video_socket | Device → Client | Encoded video bitstream |
sc_server.audio_socket | Device → Client | Encoded audio bitstream |
sc_server.control_socket | Bidirectional | Control messages (client → device) and device events (device → client) |
In forward-tunnel mode, the client polls the port with retries until the server begins listening. In reverse-tunnel mode, the client calls accept() for each socket in sequence.
The control socket has Nagle's algorithm disabled (TCP_NODELAY) to minimize input latency.
Sources: app/src/server.h92-95 app/src/server.c602-682 app/src/server.c684-690
The struct scrcpy in app/src/scrcpy.c is the top-level container for all client subsystems. Its fields map directly to the logical pipeline stages.
Diagram: Client Component Relationships (struct scrcpy)
Sources: app/src/scrcpy.c48-92 app/src/scrcpy.c576-871
Each media pipeline segment uses two thin interfaces for loose coupling:
packet_source / packet_sink: connect sc_demuxer output to sc_decoder and sc_recorder inputs (AVPacket-level)frame_source / frame_sink: connect sc_decoder output to sc_screen, sc_audio_player, and sc_v4l2_sink (decoded AVFrame-level)On the Android side, Server.java creates AsyncProcessor instances and starts them. All run concurrently on their own threads.
Diagram: Server Component Relationships
Sources: server/src/main/java/com/genymobile/scrcpy/Server.java110-168
The video source is selected at runtime:
VideoSource.DISPLAY + no newDisplay → ScreenCapture
VideoSource.DISPLAY + newDisplay → NewDisplayCapture
VideoSource.CAMERA → CameraCapture
Sources: server/src/main/java/com/genymobile/scrcpy/Server.java138-158
Diagram: End-to-End Video Data Flow
Control messages are bidirectional on a single socket:
sc_control_msg_serialize(), queued through sc_controller, and deserialized by ControlMessageReader on the device.UHID output. Read by sc_receiver on the client.Sources: app/src/scrcpy.c635-796 server/src/main/java/com/genymobile/scrcpy/Server.java110-116
Each pipeline stage runs on its own thread. The SDL event loop on the main thread coordinates shutdown via custom SDL events.
| Thread | Owner | Role |
|---|---|---|
| Main thread | scrcpy() / event_loop() | SDL event dispatch, shutdown coordination |
| Server thread | sc_server | ADB operations, tunnel setup, socket accept |
| Video demuxer thread | sc_demuxer (video) | Read frames from video_socket, push to sinks |
| Audio demuxer thread | sc_demuxer (audio) | Read frames from audio_socket, push to sinks |
| Controller send thread | sc_controller | Drain outbound control message queue → socket |
| Receiver thread | sc_receiver | Read inbound device events from control_socket |
| Recorder thread | sc_recorder | Mux video+audio packets to file |
| Screen render thread | sc_screen | SDL rendering |
| Audio player thread | sc_audio_player | SDL audio output |
| V4L2 thread (opt.) | sc_v4l2_sink | Write frames to /dev/videoN |
| AOA thread (opt.) | sc_aoa | USB HID event sending |
Sources: app/src/scrcpy.c396-950
Shutdown is cooperative: the main thread closes sockets and signals stop flags; the demuxer and controller threads detect EOF or interruption and exit, triggering SDL events that cause other components to stop.
Sources: app/src/scrcpy.c958-1068
In --otg mode (see OTG Mode and USB Input), the server is not used at all. The client connects directly to the Android device over USB using the AOA (Android Open Accessory) protocol via libusb. There are no ADB sockets and no media streams — only HID input injection via sc_aoa.
Sources: app/src/scrcpy.c32-38
Refresh this wiki
This wiki was recently refreshed. Please wait 3 days to refresh again.