This page covers the Android server component's startup: how it receives parameters, establishes connections to the client, and launches its subsystems. The server runs on the Android device as a Java process and is the counterpart to the C client application.
For how the client locates, pushes, and executes the server JAR, see ADB and Server Management. For details on what each processor does once running, see Video Capture and Encoding, Audio Capture and Encoding, and Control Message Execution.
The client executes the server on-device using app_process via an ADB shell command. The execute_server() function in app/src/server.c204-464 constructs this command and invokes it:
adb -s <serial> shell
CLASSPATH=/data/local/tmp/scrcpy-server.jar
app_process
/
com.genymobile.scrcpy.Server
<client_version>
scid=<hex>
log_level=<level>
[key=value ...]
All configuration is passed as positional key=value arguments. The first argument is always the client version string, used by the server to detect version mismatches. Only non-default values are included, so the argument list varies by session.
Diagram: Client-to-Server Command Construction
Sources: app/src/server.c204-264 app/src/server.h21-74
main and internalMainServer.java is not instantiable. It has two static entry points:
main(String... args) server/src/main/java/com/genymobile/scrcpy/Server.java212-225
The app_process JVM calls this. It wraps internalMain() in a try/catch to handle any uncaught Throwable, then calls System.exit(status) explicitly. This is necessary because the Android SDK may leave non-daemon threads running, which would otherwise prevent the process from terminating.
internalMain(String... args) server/src/main/java/com/genymobile/scrcpy/Server.java227-275
Performs the following steps in sequence:
| Step | Action | Key Call |
|---|---|---|
| 1 | Install global uncaught exception handler | Thread.setDefaultUncaughtExceptionHandler(...) |
| 2 | Prepare Android main looper (with quit allowed) | prepareMainLooper() |
| 3 | Parse command-line arguments | Options.parse(args) |
| 4 | Configure logging | Ln.disableSystemStreams(), Ln.initLogLevel(...) |
| 5 | Log device info | Build.MANUFACTURER, Build.MODEL, etc. |
| 6 | Handle list-only mode and return early | options.getList() → LogUtils.build*Message() |
| 7 | Run the mirroring session | scrcpy(options) |
prepareMainLooper() server/src/main/java/com/genymobile/scrcpy/Server.java197-210
The standard Looper.prepareMainLooper() creates a non-quittable looper, which would prevent the server from shutting down cleanly. This method uses reflection to install a quittable looper as the main looper. Later, Looper.loop() in scrcpy() will block until Looper.getMainLooper().quitSafely() is called.
Sources: server/src/main/java/com/genymobile/scrcpy/Server.java197-275
Options.parse(String... args) server/src/main/java/com/genymobile/scrcpy/Options.java292-527
The first argument is verified against BuildConfig.VERSION_NAME. A mismatch throws IllegalArgumentException, which aborts the server. All subsequent arguments are parsed as key=value pairs using a switch on the key name.
The following table lists the key parameters and their defaults:
| Key | Java Field | Default |
|---|---|---|
scid | scid | -1 |
log_level | logLevel | DEBUG |
video | video | true |
audio | audio | true |
video_codec | videoCodec | H264 |
audio_codec | audioCodec | OPUS |
video_source | videoSource | DISPLAY |
audio_source | audioSource | OUTPUT |
max_size | maxSize | 0 (unlimited) |
control | control | true |
display_id | displayId | 0 |
tunnel_forward | tunnelForward | false |
cleanup | cleanup | true |
power_on | powerOn | true |
send_device_meta | sendDeviceMeta | true |
send_frame_meta | sendFrameMeta | true |
send_dummy_byte | sendDummyByte | true |
send_codec_meta | sendCodecMeta | true |
The convenience key raw_stream=true sets sendDeviceMeta, sendFrameMeta, sendDummyByte, and sendCodecMeta all to false — useful for direct streaming without scrcpy's framing protocol.
Sources: server/src/main/java/com/genymobile/scrcpy/Options.java23-82 server/src/main/java/com/genymobile/scrcpy/Options.java292-527
scrcpy() Startup SequenceThe scrcpy(Options options) method server/src/main/java/com/genymobile/scrcpy/Server.java70-195 is the core of server initialization.
Diagram: scrcpy() Startup Sequence
Sources: server/src/main/java/com/genymobile/scrcpy/Server.java70-195
Before doing anything else, scrcpy() checks API level constraints:
VideoSource.CAMERA) requires API 31 (Android 12+)options.getNewDisplay() != null) requires API 29 (Android 10+)displayImePolicy != -1 requires API 29+A ConfigurationException is thrown for violations; internalMain() catches it and exits cleanly without printing a stack trace.
DesktopConnection SetupDesktopConnection.open() establishes the socket channel(s) back to the client. The tunnelForward flag determines direction:
tunnelForward = false (default): server connects to the client's listening socket (reverse tunnel via adb reverse)tunnelForward = true: server listens and client connects (forward tunnel via adb forward)Up to three sockets are opened depending on which streams are enabled: one for video, one for audio, and one for control. The sendDummyByte option causes the server to write one byte on the first socket immediately after connecting, which the client reads to confirm the connection is live (see connect_and_read_byte() in app/src/server.c466-483).
If sendDeviceMeta is true, the server writes the device name as a 64-byte fixed-length field on the first socket. The client reads this in device_read_info() app/src/server.c572-586 and stores it in sc_server_info.device_name.
Sources: server/src/main/java/com/genymobile/scrcpy/Server.java98-108 app/src/server.c466-586
After the connection is established, processors are assembled depending on which features are enabled:
Diagram: AsyncProcessor Assembly
The SurfaceCapture subtype selection logic is:
video_source == DISPLAY:
new_display != null → NewDisplayCapture
new_display == null → ScreenCapture
video_source == CAMERA → CameraCapture
Each processor implements AsyncProcessor and is started by calling asyncProcessor.start(completionCallback).
Sources: server/src/main/java/com/genymobile/scrcpy/Server.java110-168
Completion Mechanismserver/src/main/java/com/genymobile/scrcpy/Server.java47-64
The Completion inner class is a simple counter-based shutdown coordinator. It is initialized with the number of running processors. Each processor calls completion.addCompleted(fatalError) when it stops. When either:
running == 0), orfatalError = true...Looper.getMainLooper().quitSafely() is called, unblocking Looper.loop() and allowing scrcpy() to proceed to the finally block.
The finally block in scrcpy() server/src/main/java/com/genymobile/scrcpy/Server.java169-194 runs unconditionally after Looper.loop() returns:
cleanUp.interrupt() — signals the cleanup thread to runasyncProcessor.stop() for each processor — requests each to stopOpenGLRunner.quit() — stops the OpenGL thread if it was started (used by VideoFilter)connection.shutdown() — shuts down socketscleanUp.join() and asyncProcessor.join() for each — waits for threads to finishOpenGLRunner.join() — waits for the OpenGL threadconnection.close() — closes socket file descriptorsSources: server/src/main/java/com/genymobile/scrcpy/Server.java169-194
When options.getList() is true (any of listEncoders, listDisplays, listCameras, listCameraSizes, listApps is set), internalMain() handles the request without entering scrcpy():
CleanUp.unlinkSelf() removes the server JAR from the deviceLogUtils.buildVideoEncoderListMessage() / buildAudioEncoderListMessage() enumerate MediaCodec encodersLogUtils.buildDisplayListMessage() lists display IDsLogUtils.buildCameraListMessage() / buildAppListMessage() call Workarounds.apply() first (camera and app access require the fake context)Ln.i(), which the client reads from the ADB stdout streamThe function returns immediately after printing, so no socket connection is opened.
Sources: server/src/main/java/com/genymobile/scrcpy/Server.java245-268
Refresh this wiki
This wiki was recently refreshed. Please wait 3 days to refresh again.