Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b56c57795 | ||
|
|
a16cf95b8e | ||
|
|
71fd238b0a | ||
|
|
d795144a36 | ||
|
|
c287826f8e |
@@ -234,10 +234,10 @@ You can then [run](README.md#run) _scrcpy_.
|
|||||||
|
|
||||||
## Prebuilt server
|
## Prebuilt server
|
||||||
|
|
||||||
- [`scrcpy-server-v1.7.jar`][direct-scrcpy-server]
|
- [`scrcpy-server-v1.8.jar`][direct-scrcpy-server]
|
||||||
_(SHA-256: ee86ec8424f7dc50cacdf927312bdb46e0aa0d68611da584dc4b16d8057bc25e)_
|
_(SHA-256: 839055ef905903bf98ead1b9b8a127fe402b39ad657a81f9a914b2dbcb2ce5c0)_
|
||||||
|
|
||||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.7/scrcpy-server-v1.7.jar
|
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-server-v1.8.jar
|
||||||
|
|
||||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||||
configuration:
|
configuration:
|
||||||
|
|||||||
+54
-25
@@ -32,7 +32,7 @@ The server is a Java application (with a [`public static void main(String...
|
|||||||
args)`][main] method), compiled against the Android framework, and executed as
|
args)`][main] method), compiled against the Android framework, and executed as
|
||||||
`shell` on the Android device.
|
`shell` on the Android device.
|
||||||
|
|
||||||
[main]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/Server.java#L61
|
[main]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/Server.java#L100
|
||||||
|
|
||||||
To run such a Java application, the classes must be [_dexed_][dex] (typically,
|
To run such a Java application, the classes must be [_dexed_][dex] (typically,
|
||||||
to `classes.dex`). If `my.package.MainClass` is the main class, compiled to
|
to `classes.dex`). If `my.package.MainClass` is the main class, compiled to
|
||||||
@@ -65,8 +65,8 @@ They can be called using reflection though. The communication with hidden
|
|||||||
components is provided by [_wrappers_ classes][wrappers] and [aidl].
|
components is provided by [_wrappers_ classes][wrappers] and [aidl].
|
||||||
|
|
||||||
[hidden]: https://stackoverflow.com/a/31908373/1987178
|
[hidden]: https://stackoverflow.com/a/31908373/1987178
|
||||||
[wrappers]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/wrappers
|
[wrappers]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/wrappers
|
||||||
[aidl]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/aidl/android/view
|
[aidl]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/aidl/android/view
|
||||||
|
|
||||||
|
|
||||||
### Threading
|
### Threading
|
||||||
@@ -89,9 +89,9 @@ The video is encoded using the [`MediaCodec`] API. The codec takes its input
|
|||||||
from a [surface] associated to the display, and writes the resulting H.264
|
from a [surface] associated to the display, and writes the resulting H.264
|
||||||
stream to the provided output stream (the socket connected to the client).
|
stream to the provided output stream (the socket connected to the client).
|
||||||
|
|
||||||
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
|
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
|
||||||
[`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html
|
[`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html
|
||||||
[surface]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L63-L64
|
[surface]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L69-L70
|
||||||
|
|
||||||
On device [rotation], the codec, surface and display are reinitialized, and a
|
On device [rotation], the codec, surface and display are reinitialized, and a
|
||||||
new video stream is produced.
|
new video stream is produced.
|
||||||
@@ -105,8 +105,9 @@ because it avoids to send unnecessary frames, but there are drawbacks:
|
|||||||
Both problems are [solved][repeat] by the flag
|
Both problems are [solved][repeat] by the flag
|
||||||
[`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag].
|
[`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag].
|
||||||
|
|
||||||
[rotation]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L89-L92
|
[rotation]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L90
|
||||||
[repeat]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L125-L126
|
[repeat]:
|
||||||
|
https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L147-L148
|
||||||
[repeat-flag]: https://developer.android.com/reference/android/media/MediaFormat.html#KEY_REPEAT_PREVIOUS_FRAME_AFTER
|
[repeat-flag]: https://developer.android.com/reference/android/media/MediaFormat.html#KEY_REPEAT_PREVIOUS_FRAME_AFTER
|
||||||
|
|
||||||
|
|
||||||
@@ -124,11 +125,11 @@ All of them may need to inject input events to the system. To do so, they use
|
|||||||
the _hidden_ method [`InputManager.injectInputEvent`] (exposed by our
|
the _hidden_ method [`InputManager.injectInputEvent`] (exposed by our
|
||||||
[`InputManager` wrapper][inject-wrapper]).
|
[`InputManager` wrapper][inject-wrapper]).
|
||||||
|
|
||||||
[`EventController`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/EventController.java#L70
|
[`EventController`]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/EventController.java#L66
|
||||||
[`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html
|
[`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html
|
||||||
[`MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent.html
|
[`MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent.html
|
||||||
[`InputManager.injectInputEvent`]: https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/hardware/input/InputManager.java#857
|
[`InputManager.injectInputEvent`]: https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/hardware/input/InputManager.java#857
|
||||||
[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
|
[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -153,7 +154,7 @@ Note that the client-server roles are expressed at the application level:
|
|||||||
- the server _serves_ video stream and handle requests from the client,
|
- the server _serves_ video stream and handle requests from the client,
|
||||||
- the client _controls_ the device through the server.
|
- the client _controls_ the device through the server.
|
||||||
|
|
||||||
However, the roles are inverted at the network level:
|
However, the roles are reversed at the network level:
|
||||||
|
|
||||||
- the client opens a server socket and listen on a port before starting the
|
- the client opens a server socket and listen on a port before starting the
|
||||||
server,
|
server,
|
||||||
@@ -162,6 +163,9 @@ However, the roles are inverted at the network level:
|
|||||||
This role inversion guarantees that the connection will not fail due to race
|
This role inversion guarantees that the connection will not fail due to race
|
||||||
conditions, and avoids polling.
|
conditions, and avoids polling.
|
||||||
|
|
||||||
|
_(Note that over TCP/IP, the roles are not reversed, due to a bug in `adb
|
||||||
|
reverse`. See commit [1038bad] and [issue #5].)_
|
||||||
|
|
||||||
Once the server is connected, it sends the device information (name and initial
|
Once the server is connected, it sends the device information (name and initial
|
||||||
screen dimensions). Thus, the client may init the window and renderer, before
|
screen dimensions). Thus, the client may init the window and renderer, before
|
||||||
the first frame is available.
|
the first frame is available.
|
||||||
@@ -169,6 +173,8 @@ the first frame is available.
|
|||||||
To minimize startup time, SDL initialization is performed while listening for
|
To minimize startup time, SDL initialization is performed while listening for
|
||||||
the connection from the server (see commit [90a46b4]).
|
the connection from the server (see commit [90a46b4]).
|
||||||
|
|
||||||
|
[1038bad]: https://github.com/Genymobile/scrcpy/commit/1038bad3850f18717a048a4d5c0f8110e54ee172
|
||||||
|
[issue #5]: https://github.com/Genymobile/scrcpy/issues/5
|
||||||
[90a46b4]: https://github.com/Genymobile/scrcpy/commit/90a46b4c45637d083e877020d85ade52a9a5fa8e
|
[90a46b4]: https://github.com/Genymobile/scrcpy/commit/90a46b4c45637d083e877020d85ade52a9a5fa8e
|
||||||
|
|
||||||
|
|
||||||
@@ -177,17 +183,25 @@ the connection from the server (see commit [90a46b4]).
|
|||||||
The client uses 3 threads:
|
The client uses 3 threads:
|
||||||
|
|
||||||
- the **main** thread, executing the SDL event loop,
|
- the **main** thread, executing the SDL event loop,
|
||||||
- the **decoder** thread, decoding video frames,
|
- the **stream** thread, receiving the video and used for decoding and
|
||||||
|
recording,
|
||||||
- the **controller** thread, sending _control events_ to the server.
|
- the **controller** thread, sending _control events_ to the server.
|
||||||
|
|
||||||
|
In addition, another thread can be started if necessary to handle APK
|
||||||
|
installation or file push requests (via drag&drop on the main window).
|
||||||
|
|
||||||
### Decoder
|
|
||||||
|
|
||||||
The [decoder] runs in a separate thread. It uses _libav_ to decode the H.264
|
|
||||||
stream from the socket, and notifies the main thread when a new frame is
|
|
||||||
available.
|
|
||||||
|
|
||||||
There are two [frames] simultaneously in memory:
|
### Stream
|
||||||
|
|
||||||
|
The video [stream] is received from the socket (connected to the server on the
|
||||||
|
device) in a separate thread.
|
||||||
|
|
||||||
|
If a [decoder] is present (i.e. `--no-display` is not set), then it uses _libav_
|
||||||
|
to decode the H.264 stream from the socket, and notifies the main thread when a
|
||||||
|
new frame is available.
|
||||||
|
|
||||||
|
There are two [frames][video_buffer] simultaneously in memory:
|
||||||
- the **decoding** frame, written by the decoder from the decoder thread,
|
- the **decoding** frame, written by the decoder from the decoder thread,
|
||||||
- the **rendering** frame, rendered in a texture from the main thread.
|
- the **rendering** frame, rendered in a texture from the main thread.
|
||||||
|
|
||||||
@@ -195,9 +209,23 @@ When a new decoded frame is available, the decoder _swaps_ the decoding and
|
|||||||
rendering frame (with proper synchronization). Thus, it immediatly starts
|
rendering frame (with proper synchronization). Thus, it immediatly starts
|
||||||
to decode a new frame while the main thread renders the last one.
|
to decode a new frame while the main thread renders the last one.
|
||||||
|
|
||||||
[decoder]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/decoder.c
|
If a [recorder] is present (i.e. `--record` is enabled), then its muxes the raw
|
||||||
[frames]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/frames.h
|
H.264 packet to the output video file.
|
||||||
|
|
||||||
|
[stream]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/stream.h
|
||||||
|
[decoder]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/decoder.h
|
||||||
|
[video_buffer]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/video_buffer.h
|
||||||
|
[recorder]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/recorder.h
|
||||||
|
|
||||||
|
```
|
||||||
|
+----------+ +----------+
|
||||||
|
---> | decoder | ---> | screen |
|
||||||
|
+---------+ / +----------+ +----------+
|
||||||
|
socket ---> | stream | ----
|
||||||
|
+---------+ \ +----------+
|
||||||
|
---> | recorder |
|
||||||
|
+----------+
|
||||||
|
```
|
||||||
|
|
||||||
### Controller
|
### Controller
|
||||||
|
|
||||||
@@ -211,10 +239,10 @@ events_ to a blocking queue hold by the controller. On its own thread, the
|
|||||||
controller takes events from the queue, that it serializes and sends to the
|
controller takes events from the queue, that it serializes and sends to the
|
||||||
client.
|
client.
|
||||||
|
|
||||||
[controller]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controller.h
|
[controller]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/controller.h
|
||||||
[controlevent]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controlevent.h
|
[controlevent]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/control_event.h
|
||||||
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/inputmanager.h
|
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/input_manager.h
|
||||||
[convert]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/convert.h
|
[convert]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/convert.h
|
||||||
|
|
||||||
|
|
||||||
### UI and event loop
|
### UI and event loop
|
||||||
@@ -225,9 +253,10 @@ thread.
|
|||||||
Events are handled in the [event loop], which either updates the [screen] or
|
Events are handled in the [event loop], which either updates the [screen] or
|
||||||
delegates to the [input manager][inputmanager].
|
delegates to the [input manager][inputmanager].
|
||||||
|
|
||||||
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c
|
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/scrcpy.c
|
||||||
[event loop]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c#L38
|
[event loop]:
|
||||||
[screen]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/screen.h
|
https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/scrcpy.c#L187
|
||||||
|
[screen]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/screen.h
|
||||||
|
|
||||||
|
|
||||||
## Hack
|
## Hack
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# scrcpy (v1.7)
|
# scrcpy (v1.8)
|
||||||
|
|
||||||
This application provides display and control of Android devices connected on
|
This application provides display and control of Android devices connected on
|
||||||
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
|
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
|
||||||
@@ -29,12 +29,10 @@ control it using keyboard and mouse.
|
|||||||
On Linux, you typically need to [build the app manually][BUILD]. Don't worry,
|
On Linux, you typically need to [build the app manually][BUILD]. Don't worry,
|
||||||
it's not that hard.
|
it's not that hard.
|
||||||
|
|
||||||
For Arch Linux, two [AUR] packages have been created by users:
|
For Arch Linux, an [AUR] package is available: [`scrcpy`][aur-link].
|
||||||
|
|
||||||
- [`scrcpy`](https://aur.archlinux.org/packages/scrcpy/)
|
|
||||||
- [`scrcpy-prebuiltserver`](https://aur.archlinux.org/packages/scrcpy-prebuiltserver/)
|
|
||||||
|
|
||||||
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||||
|
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
|
||||||
|
|
||||||
For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
|
For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
|
||||||
|
|
||||||
@@ -47,13 +45,13 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
|
|||||||
For Windows, for simplicity, prebuilt archives with all the dependencies
|
For Windows, for simplicity, prebuilt archives with all the dependencies
|
||||||
(including `adb`) are available:
|
(including `adb`) are available:
|
||||||
|
|
||||||
- [`scrcpy-win32-v1.7.zip`][direct-win32]
|
- [`scrcpy-win32-v1.8.zip`][direct-win32]
|
||||||
_(SHA-256: 98ae36f2da0b8212c07066fd93139650554274f863d4cee0781501a0c84f7c23)_
|
_(SHA-256: c0c29ed1c66deaa73bdadacd09e598aafb3a117929cf7a314cce1cc45e34de53)_
|
||||||
- [`scrcpy-win64-v1.7.zip`][direct-win64]
|
- [`scrcpy-win64-v1.8.zip`][direct-win64]
|
||||||
_(SHA-256: b41416547521062f19e3f3f539e89a70e713bd086e69ef1b29c128993f7aa462)_
|
_(SHA-256: 9cc980d07bd8f036ae4e91d0bc6fc3281d7fa8f9752d4913b643c0fb72a19fb7)_
|
||||||
|
|
||||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.7/scrcpy-win32-v1.7.zip
|
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-win32-v1.8.zip
|
||||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.7/scrcpy-win64-v1.7.zip
|
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-win64-v1.8.zip
|
||||||
|
|
||||||
You can also [build the app manually][BUILD].
|
You can also [build the app manually][BUILD].
|
||||||
|
|
||||||
@@ -171,6 +169,7 @@ To disable mirroring while recording:
|
|||||||
scrcpy --no-display --record file.mp4
|
scrcpy --no-display --record file.mp4
|
||||||
scrcpy -Nr file.mkv
|
scrcpy -Nr file.mkv
|
||||||
# interrupt recording with Ctrl+C
|
# interrupt recording with Ctrl+C
|
||||||
|
# Ctrl+C does not terminate properly on Windows, so disconnect the device
|
||||||
```
|
```
|
||||||
|
|
||||||
"Skipped frames" are recorded, even if they are not displayed in real time (for
|
"Skipped frames" are recorded, even if they are not displayed in real time (for
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
public void streamScreen(Device device, FileDescriptor fd) throws IOException {
|
public void streamScreen(Device device, FileDescriptor fd) throws IOException {
|
||||||
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
|
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
|
||||||
device.setRotationListener(this);
|
device.setRotationListener(this);
|
||||||
|
IBinder d = SurfaceControl.getBuiltInDisplay(0);
|
||||||
|
SurfaceControl.setDisplayPowerMode(d, SurfaceControl.POWER_MODE_OFF);
|
||||||
boolean alive;
|
boolean alive;
|
||||||
try {
|
try {
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ public final class SurfaceControl {
|
|||||||
|
|
||||||
private static final Class<?> CLASS;
|
private static final Class<?> CLASS;
|
||||||
|
|
||||||
|
public static final int POWER_MODE_OFF = 0;
|
||||||
|
public static final int POWER_MODE_DOZE = 1;
|
||||||
|
public static final int POWER_MODE_NORMAL = 2;
|
||||||
|
public static final int POWER_MODE_DOZE_SUSPEND = 3;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
CLASS = Class.forName("android.view.SurfaceControl");
|
CLASS = Class.forName("android.view.SurfaceControl");
|
||||||
@@ -71,6 +76,22 @@ public final class SurfaceControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IBinder getBuiltInDisplay(int builtInDisplayId) {
|
||||||
|
try {
|
||||||
|
return (IBinder) CLASS.getMethod("getBuiltInDisplay", int.class).invoke(null, builtInDisplayId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDisplayPowerMode(IBinder displayToken, int mode) {
|
||||||
|
try {
|
||||||
|
CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class).invoke(null, displayToken, mode);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void destroyDisplay(IBinder displayToken) {
|
public static void destroyDisplay(IBinder displayToken) {
|
||||||
try {
|
try {
|
||||||
CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken);
|
CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken);
|
||||||
|
|||||||
Reference in New Issue
Block a user