Compare commits

..

1 Commits

Author SHA1 Message Date
b47e3ec018 Update developer documentation 2023-06-21 23:35:54 +02:00
36 changed files with 311 additions and 466 deletions

8
FAQ.md
View File

@ -159,8 +159,6 @@ In developer options, enable:
> **USB debugging (Security settings)** > **USB debugging (Security settings)**
> _Allow granting permissions and simulating input via USB debugging_ > _Allow granting permissions and simulating input via USB debugging_
Rebooting the device is necessary once this option is set.
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 [simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
@ -170,12 +168,12 @@ The default text injection method is [limited to ASCII characters][text-input].
A trick allows to also inject some [accented characters][accented-characters], A trick allows to also inject some [accented characters][accented-characters],
but that's all. See [#37]. but that's all. See [#37].
It is also possible to simulate a [physical keyboard][hid] (HID). Since scrcpy v1.20, it is possible to simulate a [physical keyboard][hid] (HID).
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode [text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters [accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37 [#37]: https://github.com/Genymobile/scrcpy/issues/37
[hid]: doc/hid-otg.md [hid]: README.md#physical-keyboard-simulation-hid
## Client issues ## Client issues
@ -231,4 +229,4 @@ Translations of this FAQ in other languages are available in the [wiki].
[wiki]: https://github.com/Genymobile/scrcpy/wiki [wiki]: https://github.com/Genymobile/scrcpy/wiki
Only this FAQ file is guaranteed to be up-to-date. Only this README file is guaranteed to be up-to-date.

View File

@ -1,11 +1,11 @@
# scrcpy (v2.1.1) # scrcpy (v2.0)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" /> <img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
_pronounced "**scr**een **c**o**py**"_ _pronounced "**scr**een **c**o**py**"_
This application mirrors Android devices (video and audio) connected via This application mirrors Android devices (video and audio) connected via
USB or [over TCP/IP](doc/connection.md#tcpip-wireless), and allows to control the USB or [over TCP/IP](doc/device.md#tcpip-wireless), and allows to control the
device with the keyboard and the mouse of the computer. It does not require any device with the keyboard and the mouse of the computer. It does not require any
_root_ access. It works on _Linux_, _Windows_ and _macOS_. _root_ access. It works on _Linux_, _Windows_ and _macOS_.
@ -30,7 +30,7 @@ Its features include:
- mirroring with [Android device screen off](doc/device.md#turn-screen-off) - mirroring with [Android device screen off](doc/device.md#turn-screen-off)
- [copy-paste](doc/control.md#copy-paste) in both directions - [copy-paste](doc/control.md#copy-paste) in both directions
- [configurable quality](doc/video.md) - [configurable quality](doc/video.md)
- Android device screen [as a webcam (V4L2)](doc/v4l2.md) (Linux-only) - Android device [as a webcam (V4L2)](doc/v4l2.md) (Linux-only)
- [physical keyboard/mouse simulation (HID)](doc/hid-otg.md) - [physical keyboard/mouse simulation (HID)](doc/hid-otg.md)
- [OTG mode](doc/hid-otg.md#otg) - [OTG mode](doc/hid-otg.md#otg)
- and more… - and more…
@ -39,7 +39,7 @@ Its features include:
The Android device requires at least API 21 (Android 5.0). The Android device requires at least API 21 (Android 5.0).
[Audio forwarding](doc/audio.md) is supported for API >= 30 (Android 11+). [Audio forwarding](doc/audio.md) is supported from API 30 (Android 11).
Make sure you [enabled USB debugging][enable-adb] on your device(s). Make sure you [enabled USB debugging][enable-adb] on your device(s).
@ -47,14 +47,10 @@ Make sure you [enabled USB debugging][enable-adb] on your device(s).
On some devices, you also need to enable [an additional option][control] `USB On some devices, you also need to enable [an additional option][control] `USB
debugging (Security Settings)` (this is an item different from `USB debugging`) debugging (Security Settings)` (this is an item different from `USB debugging`)
to control it using a keyboard and mouse. Rebooting the device is necessary once to control it using a keyboard and mouse.
this option is set.
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323 [control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
Note that USB debugging is not required to run scrcpy in [OTG
mode](doc/hid-otg.md#otg).
## Get the app ## Get the app
@ -68,11 +64,10 @@ mode](doc/hid-otg.md#otg).
The application provides a lot of features and configuration options. They are The application provides a lot of features and configuration options. They are
documented in the following pages: documented in the following pages:
- [Connection](doc/connection.md) - [Device](doc/device.md)
- [Video](doc/video.md) - [Video](doc/video.md)
- [Audio](doc/audio.md) - [Audio](doc/audio.md)
- [Control](doc/control.md) - [Control](doc/control.md)
- [Device](doc/device.md)
- [Window](doc/window.md) - [Window](doc/window.md)
- [Recording](doc/recording.md) - [Recording](doc/recording.md)
- [Tunnels](doc/tunnels.md) - [Tunnels](doc/tunnels.md)
@ -118,10 +113,7 @@ For general questions or discussions, you can also use:
I'm [@rom1v](https://github.com/rom1v), the author and maintainer of _scrcpy_. I'm [@rom1v](https://github.com/rom1v), the author and maintainer of _scrcpy_.
If you appreciate this application, you can [support my open source If you appreciate this application, you can [support my open source
work][donate]: work][donate].
- [GitHub Sponsors](https://github.com/sponsors/rom1v)
- [Liberapay](https://liberapay.com/rom1v/)
- [PayPal](https://paypal.me/rom2v)
[donate]: https://blog.rom1v.com/about/#support-my-open-source-work [donate]: https://blog.rom1v.com/about/#support-my-open-source-work

View File

@ -6,10 +6,10 @@ cd "$DIR"
mkdir -p "$PREBUILT_DATA_DIR" mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR" cd "$PREBUILT_DATA_DIR"
DEP_DIR=platform-tools-34.0.3 DEP_DIR=platform-tools-34.0.1
FILENAME=platform-tools_r34.0.3-windows.zip FILENAME=platform-tools_r34.0.1-windows.zip
SHA256SUM=fce992e93eb786fc9f47df93d83a7b912c46742d45c39d712c02e06d05b72e2b SHA256SUM=5dd9c2be744c224fa3a7cbe30ba02d2cb378c763bd0f797a7e47e9f3156a5daa
if [[ -d "$DEP_DIR" ]] if [[ -d "$DEP_DIR" ]]
then then

View File

@ -13,7 +13,7 @@ BEGIN
VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe" VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy" VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "2.1.1" VALUE "ProductVersion", "2.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@ -21,7 +21,7 @@ Make scrcpy window always on top (above other windows).
.TP .TP
.BI "\-\-audio\-bit\-rate " value .BI "\-\-audio\-bit\-rate " value
Encode the audio at the given bit rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). Encode the audio at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
Default is 128K (128000). Default is 128K (128000).
@ -71,7 +71,7 @@ Default is 5.
.TP .TP
.BI "\-b, \-\-video\-bit\-rate " value .BI "\-b, \-\-video\-bit\-rate " value
Encode the video at the given bit rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000). Encode the video at the given bit\-rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
Default is 8M (8000000). Default is 8M (8000000).

View File

@ -628,8 +628,8 @@ sc_adb_select_device(struct sc_intr *intr,
return false; return false;
} }
LOGI("ADB device found:"); LOGD("ADB device found:");
sc_adb_devices_log(SC_LOG_LEVEL_INFO, vec.data, vec.size); sc_adb_devices_log(SC_LOG_LEVEL_DEBUG, vec.data, vec.size);
// Move devics into out_device (do not destroy device) // Move devics into out_device (do not destroy device)
sc_adb_device_move(out_device, device); sc_adb_device_move(out_device, device);

View File

@ -124,7 +124,7 @@ static const struct sc_option options[] = {
.longopt_id = OPT_AUDIO_BIT_RATE, .longopt_id = OPT_AUDIO_BIT_RATE,
.longopt = "audio-bit-rate", .longopt = "audio-bit-rate",
.argdesc = "value", .argdesc = "value",
.text = "Encode the audio at the given bit rate, expressed in bits/s. " .text = "Encode the audio at the given bit-rate, expressed in bits/s. "
"Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
"Default is 128K (128000).", "Default is 128K (128000).",
}, },
@ -185,7 +185,7 @@ static const struct sc_option options[] = {
.shortopt = 'b', .shortopt = 'b',
.longopt = "video-bit-rate", .longopt = "video-bit-rate",
.argdesc = "value", .argdesc = "value",
.text = "Encode the video at the given bit rate, expressed in bits/s. " .text = "Encode the video at the given bit-rate, expressed in bits/s. "
"Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" "Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
"Default is 8M (8000000).", "Default is 8M (8000000).",
}, },
@ -2022,6 +2022,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->audio_playback = false; opts->audio_playback = false;
} }
if (!opts->video_playback && !otg) {
// If video playback is disabled and OTG are disabled, then there is
// no way to control the device.
opts->control = false;
}
if (opts->video && !opts->video_playback && !opts->record_filename if (opts->video && !opts->video_playback && !opts->record_filename
&& !v4l2) { && !v4l2) {
LOGI("No video playback, no recording, no V4L2 sink: video disabled"); LOGI("No video playback, no recording, no V4L2 sink: video disabled");

View File

@ -53,7 +53,7 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
display->mipmaps = true; display->mipmaps = true;
} else { } else {
LOGW("Trilinear filtering disabled " LOGW("Trilinear filtering disabled "
"(OpenGL 3.0+ or ES 2.0+ required)"); "(OpenGL 3.0+ or ES 2.0+ required");
} }
} else { } else {
LOGI("Trilinear filtering disabled"); LOGI("Trilinear filtering disabled");

View File

@ -252,9 +252,7 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer,
// Contrary to the video demuxer, keep mirroring if only the audio fails // Contrary to the video demuxer, keep mirroring if only the audio fails
// (unless --require-audio is set). // (unless --require-audio is set).
if (status == SC_DEMUXER_STATUS_EOS) { if (status == SC_DEMUXER_STATUS_ERROR
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
} else if (status == SC_DEMUXER_STATUS_ERROR
|| (status == SC_DEMUXER_STATUS_DISABLED || (status == SC_DEMUXER_STATUS_DISABLED
&& options->require_audio)) { && options->require_audio)) {
PUSH_EVENT(SC_EVENT_DEMUXER_ERROR); PUSH_EVENT(SC_EVENT_DEMUXER_ERROR);
@ -450,7 +448,9 @@ scrcpy(struct scrcpy_options *options) {
struct sc_file_pusher *fp = NULL; struct sc_file_pusher *fp = NULL;
if (options->video_playback && options->control) { // control implies video playback
assert(!options->control || options->video_playback);
if (options->control) {
if (!sc_file_pusher_init(&s->file_pusher, serial, if (!sc_file_pusher_init(&s->file_pusher, serial,
options->push_target)) { options->push_target)) {
goto end; goto end;

View File

@ -488,7 +488,6 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
} }
SDL_ShowWindow(screen->window); SDL_ShowWindow(screen->window);
sc_screen_update_content_rect(screen);
} }
void void
@ -849,8 +848,6 @@ sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t w = screen->content_size.width; int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height; int32_t h = screen->content_size.height;
// screen->rect must be initialized to avoid a division by zero
assert(screen->rect.w && screen->rect.h);
x = (int64_t) (x - screen->rect.x) * w / screen->rect.w; x = (int64_t) (x - screen->rect.x) * w / screen->rect.w;
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h; y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;

View File

@ -533,8 +533,8 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
if (audio_socket == SC_SOCKET_NONE) { if (audio_socket == SC_SOCKET_NONE) {
goto fail; goto fail;
} }
bool ok = net_connect_intr(&server->intr, audio_socket, bool ok = net_connect_intr(&server->intr, audio_socket, tunnel_host,
tunnel_host, tunnel_port); tunnel_port);
if (!ok) { if (!ok) {
goto fail; goto fail;
} }

View File

@ -105,6 +105,10 @@ scrcpy_otg(struct scrcpy_options *options) {
usb_device_initialized = true; usb_device_initialized = true;
LOGI("USB device: %s (%04x:%04x) %s %s", usb_device.serial,
(unsigned) usb_device.vid, (unsigned) usb_device.pid,
usb_device.manufacturer, usb_device.product);
ok = sc_usb_connect(&s->usb, usb_device.device, &cbs, NULL); ok = sc_usb_connect(&s->usb, usb_device.device, &cbs, NULL);
if (!ok) { if (!ok) {
goto end; goto end;

View File

@ -213,8 +213,8 @@ sc_usb_select_device(struct sc_usb *usb, const char *serial,
assert(sel_count == 1); // sel_idx is valid only if sel_count == 1 assert(sel_count == 1); // sel_idx is valid only if sel_count == 1
struct sc_usb_device *device = &vec.data[sel_idx]; struct sc_usb_device *device = &vec.data[sel_idx];
LOGI("USB device found:"); LOGD("USB device found:");
sc_usb_devices_log(SC_LOG_LEVEL_INFO, vec.data, vec.size); sc_usb_devices_log(SC_LOG_LEVEL_DEBUG, vec.data, vec.size);
// Move device into out_device (do not destroy device) // Move device into out_device (do not destroy device)
sc_usb_device_move(out_device, device); sc_usb_device_move(out_device, device);

View File

@ -71,20 +71,6 @@ scrcpy --audio-codec=aac
scrcpy --audio-codec=raw scrcpy --audio-codec=raw
``` ```
In particular, if you get the following error:
> Failed to initialize audio/opus, error 0xfffffffe
then your device has no Opus encoder: try `scrcpy --audio-codec=aac`.
For advanced usage, to pass arbitrary parameters to the [`MediaFormat`],
check `--audio-codec-options` in the manpage or in `scrcpy --help`.
[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat
## Encoder
Several encoders may be available on the device. They can be listed by: Several encoders may be available on the device. They can be listed by:
```bash ```bash
@ -93,14 +79,19 @@ scrcpy --list-encoders
To select a specific encoder: To select a specific encoder:
```bash ```
scrcpy --audio-codec=opus --audio-encoder='c2.android.opus.encoder' scrcpy --audio-codec=opus --audio-encoder='c2.android.opus.encoder'
``` ```
For advanced usage, to pass arbitrary parameters to the [`MediaFormat`],
check `--audio-codec-options` in the manpage or in `scrcpy --help`.
[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat
## Bit rate ## Bit rate
The default audio bit rate is 128Kbps. To change it: The default video bit-rate is 128Kbps. To change it:
```bash ```bash
scrcpy --audio-bit-rate=64K scrcpy --audio-bit-rate=64K

View File

@ -77,7 +77,7 @@ pip3 install meson
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies # client build dependencies
sudo dnf install SDL2-devel ffms2-devel libusb1-devel meson gcc make sudo dnf install SDL2-devel ffms2-devel libusb-devel meson gcc make
# server build dependencies # server build dependencies
sudo dnf install java-devel sudo dnf install java-devel
@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v2.1.1`][direct-scrcpy-server] - [`scrcpy-server-v2.0`][direct-scrcpy-server]
<sub>SHA-256: `9558db6c56743a1dc03b38f59801fb40e91cc891f8fc0c89e5b0b067761f148e`</sub> <sub>SHA-256: `9e241615f578cd690bb43311000debdecf6a9c50a7082b001952f18f6f21ddc2`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-server-v2.1.1 [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-server-v2.0
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:

View File

@ -1,125 +0,0 @@
# Connection
## Selection
If exactly one device is connected (i.e. listed by `adb devices`), then it is
automatically selected.
However, if there are multiple devices connected, you must specify the one to
use in one of 4 ways:
- by its serial:
```bash
scrcpy --serial=0123456789abcdef
scrcpy -s 0123456789abcdef # short version
# the serial is the ip:port if connected over TCP/IP (same behavior as adb)
scrcpy --serial=192.168.1.1:5555
```
- the one connected over USB (if there is exactly one):
```bash
scrcpy --select-usb
scrcpy -d # short version
```
- the one connected over TCP/IP (if there is exactly one):
```bash
scrcpy --select-tcpip
scrcpy -e # short version
```
- a device already listening on TCP/IP (see [below](#tcpip-wireless)):
```bash
scrcpy --tcpip=192.168.1.1:5555
scrcpy --tcpip=192.168.1.1 # default port is 5555
```
The serial may also be provided via the environment variable `ANDROID_SERIAL`
(also used by `adb`):
```bash
# in bash
export ANDROID_SERIAL=0123456789abcdef
scrcpy
```
```cmd
:: in cmd
set ANDROID_SERIAL=0123456789abcdef
scrcpy
```
```powershell
# in PowerShell
$env:ANDROID_SERIAL = '0123456789abcdef'
scrcpy
```
## TCP/IP (wireless)
_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a
device over TCP/IP. The device must be connected on the same network as the
computer.
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
### Automatic
An option `--tcpip` allows to configure the connection automatically. There are
two variants.
If the device (accessible at 192.168.1.1 in this example) already listens on a
port (typically 5555) for incoming _adb_ connections, then run:
```bash
scrcpy --tcpip=192.168.1.1 # default port is 5555
scrcpy --tcpip=192.168.1.1:5555
```
If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP
address), connect the device over USB, then run:
```bash
scrcpy --tcpip # without arguments
```
It will automatically find the device IP address and adb port, enable TCP/IP
mode if necessary, then connect to the device before starting.
### Manual
Alternatively, it is possible to enable the TCP/IP connection manually using
`adb`:
1. Plug the device into a USB port on your computer.
2. Connect the device to the same Wi-Fi network as your computer.
3. Get your device IP address, in Settings → About phone → Status, or by
executing this command:
```bash
adb shell ip route | awk '{print $9}'
```
4. Enable `adb` over TCP/IP on your device: `adb tcpip 5555`.
5. Unplug your device.
6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`
with the device IP address you found)_.
7. Run `scrcpy` as usual.
8. Run `adb disconnect` once you're done.
Since Android 11, a [wireless debugging option][adb-wireless] allows to bypass
having to physically connect your device directly to your computer.
[adb-wireless]: https://developer.android.com/studio/command-line/adb#wireless-android11-command-line
## Autostart
A small tool (by the scrcpy author) allows to run arbitrary commands whenever a
new Android device is connected: [AutoAdb]. It can be used to start scrcpy:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb

View File

@ -359,7 +359,7 @@ metadata]:
- the initial video width (`u32`) - the initial video width (`u32`)
- the initial video height (`u32`) - the initial video height (`u32`)
- On the _audio_ socket, 4 bytes: - On the _audio_ socket, 4 bytes:
- the codec id (`u32`) (OPUS, AAC or RAW) - the codec id ('u32`) (OPUS, AAC or RAW)
[codec metadata]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/Streamer.java#L33-L51 [codec metadata]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/Streamer.java#L33-L51

View File

@ -1,9 +1,156 @@
# Device # Device
## Selection
If exactly one device is connected (i.e. listed by `adb devices`), then it is
automatically selected.
However, if there are multiple devices connected, you must specify the one to
use in one of 4 ways:
- by its serial:
```bash
scrcpy --serial=0123456789abcdef
scrcpy -s 0123456789abcdef # short version
# the serial is the ip:port if connected over TCP/IP (same behavior as adb)
scrcpy --serial=192.168.1.1:5555
```
- the one connected over USB (if there is exactly one):
```bash
scrcpy --select-usb
scrcpy -d # short version
```
- the one connected over TCP/IP (if there is exactly one):
```bash
scrcpy --select-tcpip
scrcpy -e # short version
```
- a device already listening on TCP/IP (see [below](#tcpip-wireless)):
```bash
scrcpy --tcpip=192.168.1.1:5555
scrcpy --tcpip=192.168.1.1 # default port is 5555
```
The serial may also be provided via the environment variable `ANDROID_SERIAL`
(also used by `adb`):
```bash
# in bash
export ANDROID_SERIAL=0123456789abcdef
scrcpy
```
```cmd
:: in cmd
set ANDROID_SERIAL=0123456789abcdef
scrcpy
```
```powershell
# in PowerShell
$env:ANDROID_SERIAL = '0123456789abcdef'
scrcpy
```
## TCP/IP (wireless)
_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a
device over TCP/IP. The device must be connected on the same network as the
computer.
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
### Automatic
An option `--tcpip` allows to configure the connection automatically. There are
two variants.
If the device (accessible at 192.168.1.1 in this example) already listens on a
port (typically 5555) for incoming _adb_ connections, then run:
```bash
scrcpy --tcpip=192.168.1.1 # default port is 5555
scrcpy --tcpip=192.168.1.1:5555
```
If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP
address), connect the device over USB, then run:
```bash
scrcpy --tcpip # without arguments
```
It will automatically find the device IP address and adb port, enable TCP/IP
mode if necessary, then connect to the device before starting.
### Manual
Alternatively, it is possible to enable the TCP/IP connection manually using
`adb`:
1. Plug the device into a USB port on your computer.
2. Connect the device to the same Wi-Fi network as your computer.
3. Get your device IP address, in Settings → About phone → Status, or by
executing this command:
```bash
adb shell ip route | awk '{print $9}'
```
4. Enable `adb` over TCP/IP on your device: `adb tcpip 5555`.
5. Unplug your device.
6. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`
with the device IP address you found)_.
7. Run `scrcpy` as usual.
8. Run `adb disconnect` once you're done.
Since Android 11, a [wireless debugging option][adb-wireless] allows to bypass
having to physically connect your device directly to your computer.
[adb-wireless]: https://developer.android.com/studio/command-line/adb#wireless-android11-command-line
## Autostart
A small tool (by the scrcpy author) allows to run arbitrary commands whenever a
new Android device is connected: [AutoAdb]. It can be used to start scrcpy:
```bash
autoadb scrcpy -s '{}'
```
[AutoAdb]: https://github.com/rom1v/autoadb
## Display
If several displays are available on the Android device, it is possible to
select the display to mirror:
```bash
scrcpy --display=1
```
The list of display ids can be retrieved by:
```bash
scrcpy --list-displays
```
A secondary display may only be controlled if the device runs at least Android
10 (otherwise it is mirrored as read-only).
## Actions
Some command line arguments perform actions on the device itself while scrcpy is Some command line arguments perform actions on the device itself while scrcpy is
running. running.
## Stay awake
### Stay awake
To prevent the device from sleeping after a delay **when the device is plugged To prevent the device from sleeping after a delay **when the device is plugged
in**: in**:
@ -19,7 +166,7 @@ If the device is not plugged in (i.e. only connected over TCP/IP),
`--stay-awake` has no effect (this is the Android behavior). `--stay-awake` has no effect (this is the Android behavior).
## Turn screen off ### Turn screen off
It is possible to turn the device screen off while mirroring on start with a It is possible to turn the device screen off while mirroring on start with a
command-line option: command-line option:
@ -47,7 +194,7 @@ scrcpy -Sw # short version
``` ```
## Show touches ### Show touches
For presentations, it may be useful to show physical touches (on the physical For presentations, it may be useful to show physical touches (on the physical
device). Android exposes this feature in _Developers options_. device). Android exposes this feature in _Developers options_.
@ -63,7 +210,7 @@ scrcpy -t # short version
Note that it only shows _physical_ touches (by a finger on the device). Note that it only shows _physical_ touches (by a finger on the device).
## Power off on close ### Power off on close
To turn the device screen off when closing _scrcpy_: To turn the device screen off when closing _scrcpy_:
@ -71,10 +218,11 @@ To turn the device screen off when closing _scrcpy_:
scrcpy --power-off-on-close scrcpy --power-off-on-close
``` ```
## Power on on start ### Power on on start
By default, on start, the device is powered on. To prevent this behavior: By default, on start, the device is powered on. To prevent this behavior:
```bash ```bash
scrcpy --no-power-on scrcpy --no-power-on
``` ```

View File

@ -106,7 +106,3 @@ scrcpy --otg # keyboard and mouse
Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is
connected over USB. connected over USB.
## HID/OTG issues on Windows
See [FAQ](/FAQ.md#hidotg-issues-on-windows).

View File

@ -9,10 +9,12 @@ Scrcpy is packaged in several distributions and package managers:
- Debian/Ubuntu: `apt install scrcpy` - Debian/Ubuntu: `apt install scrcpy`
- Arch Linux: `pacman -S scrcpy` - Arch Linux: `pacman -S scrcpy`
- Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy` - Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy`
- Gentoo: `emerge scrcpy` - Gentoo: [ebuild][ebuild-link] file
- Snap: `snap install scrcpy` - Snap: `snap install scrcpy`
- … (see [repology](https://repology.org/project/scrcpy/versions)) - … (see [repology](https://repology.org/project/scrcpy/versions))
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
### Latest version ### Latest version
However, the packaged version is not always the latest release. To install the However, the packaged version is not always the latest release. To install the

View File

@ -29,7 +29,7 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd> | Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_ | Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_ | Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
| Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| <kbd>MOD</kbd>+<kbd>Backspace</kbd> \| _Right-click²_ | Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_
| Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4th-click³_ | Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4th-click³_
| Click on `MENU` (unlock screen)⁴ | <kbd>MOD</kbd>+<kbd>m</kbd> | Click on `MENU` (unlock screen)⁴ | <kbd>MOD</kbd>+<kbd>m</kbd>
| Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd></kbd> _(up)_ | Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd></kbd> _(up)_

View File

@ -21,7 +21,7 @@ If encoding fails, scrcpy automatically tries again with a lower definition
## Bit rate ## Bit rate
The default video bit rate is 8 Mbps. To change it: The default video bit-rate is 8 Mbps. To change it:
```bash ```bash
scrcpy --video-bit-rate=2M scrcpy --video-bit-rate=2M
@ -66,14 +66,6 @@ scrcpy --video-codec=av1
H265 may provide better quality, but H264 should provide lower latency. H265 may provide better quality, but H264 should provide lower latency.
AV1 encoders are not common on current Android devices. AV1 encoders are not common on current Android devices.
For advanced usage, to pass arbitrary parameters to the [`MediaFormat`],
check `--video-codec-options` in the manpage or in `scrcpy --help`.
[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat
## Encoder
Several encoders may be available on the device. They can be listed by: Several encoders may be available on the device. They can be listed by:
```bash ```bash
@ -87,6 +79,11 @@ try another one:
scrcpy --video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc' scrcpy --video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc'
``` ```
For advanced usage, to pass arbitrary parameters to the [`MediaFormat`],
check `--video-codec-options` in the manpage or in `scrcpy --help`.
[`MediaFormat`]: https://developer.android.com/reference/android/media/MediaFormat
## Rotation ## Rotation
@ -137,25 +134,6 @@ phone, landscape for a tablet).
If `--max-size` is also specified, resizing is applied after cropping. If `--max-size` is also specified, resizing is applied after cropping.
## Display
If several displays are available on the Android device, it is possible to
select the display to mirror:
```bash
scrcpy --display=1
```
The list of display ids can be retrieved by:
```bash
scrcpy --list-displays
```
A secondary display may only be controlled if the device runs at least Android
10 (otherwise it is mirrored as read-only).
## Buffering ## Buffering
By default, there is no video buffering, to get the lowest possible latency. By default, there is no video buffering, to get the lowest possible latency.

View File

@ -4,24 +4,18 @@
Download the [latest release]: Download the [latest release]:
- [`scrcpy-win64-v2.1.1.zip`][direct-win64] (64-bit) - [`scrcpy-win64-v2.0.zip`][direct-win64] (64-bit)
<sub>SHA-256: `f77281e1bce2f9934617699c581f063d5b327f012eff602ee98fb2ef550c25c2`</sub> <sub>SHA-256: `ae4c8d37a496b43f8974ba8f07f708e22a9570ba0cddc3dc3a36edbccd4d2a20`</sub>
- [`scrcpy-win32-v2.1.1.zip`][direct-win32] (32-bit) - [`scrcpy-win32-v2.0.zip`][direct-win32] (32-bit)
<sub>SHA-256: `ef7ae7fbe9449f2643febdc2244fb186d1a746a3c736394150cfd14f06d3c943`</sub> <sub>SHA-256: `15d98c02cb0e0bbd84f8b5d54991e0f6925569b1286a86a40743944fcb1c2d8c`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest [latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-win64-v2.1.1.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-win64-v2.0.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-win32-v2.1.1.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-win32-v2.0.zip
and extract it. and extract it.
Alternatively, you could install it from packages manager, like [Winget]: Alternatively, you could install it from packages manager, like [Chocolatey]:
```bash
winget install scrcpy
```
or [Chocolatey]:
```bash ```bash
choco install scrcpy choco install scrcpy
@ -36,7 +30,6 @@ scoop install scrcpy
scoop install adb # if you don't have it yet scoop install adb # if you don't have it yet
``` ```
[Winget]: https://github.com/microsoft/winget-cli
[Chocolatey]: https://chocolatey.org/ [Chocolatey]: https://chocolatey.org/
[Scoop]: https://scoop.sh [Scoop]: https://scoop.sh

View File

@ -2,8 +2,8 @@
set -e set -e
BUILDDIR=build-auto BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.1.1/scrcpy-server-v2.1.1 PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.0/scrcpy-server-v2.0
PREBUILT_SERVER_SHA256=9558db6c56743a1dc03b38f59801fb40e91cc891f8fc0c89e5b0b067761f148e PREBUILT_SERVER_SHA256=9e241615f578cd690bb43311000debdecf6a9c50a7082b001952f18f6f21ddc2
echo "[scrcpy] Downloading prebuilt server..." echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@ -1,5 +1,5 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '2.1.1', version: '2.0',
meson_version: '>= 0.48', meson_version: '>= 0.48',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',

View File

@ -98,9 +98,9 @@ dist-win32: build-server build-win32
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avcodec-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/avformat-60.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win32/bin/swresample-4.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/SDL2-2.28.0/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.28.0/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/bin/msys-usb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
@ -116,9 +116,9 @@ dist-win64: build-server build-win64
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avcodec-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/avformat-60.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/ffmpeg-6.0-scrcpy-4/win64/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-34.0.1/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/SDL2-2.28.0/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.28.0/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/bin/msys-usb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/"

View File

@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 33 targetSdkVersion 33
versionCode 20101 versionCode 20000
versionName "2.1.1" versionName "2.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View File

@ -12,7 +12,7 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=2.1.1 SCRCPY_VERSION_NAME=2.0
PLATFORM=${ANDROID_PLATFORM:-33} PLATFORM=${ANDROID_PLATFORM:-33}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-33.0.0}
@ -48,7 +48,6 @@ cd "$SERVER_DIR/src/main/aidl"
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IRotationWatcher.aidl "$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IRotationWatcher.aidl
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" \ "$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" \
android/content/IOnPrimaryClipChangedListener.aidl android/content/IOnPrimaryClipChangedListener.aidl
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IDisplayFoldListener.aidl
echo "Compiling java sources..." echo "Compiling java sources..."
cd ../java cd ../java

View File

@ -118,7 +118,7 @@ public final class AudioCapture {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
startWorkaroundAndroid11(); startWorkaroundAndroid11();
try { try {
tryStartRecording(5, 100); tryStartRecording(3, 100);
} finally { } finally {
stopWorkaroundAndroid11(); stopWorkaroundAndroid11();
} }

View File

@ -64,6 +64,8 @@ public final class DesktopConnection implements Closeable {
throws IOException { throws IOException {
String socketName = getSocketName(scid); String socketName = getSocketName(scid);
LocalSocket firstSocket = null;
LocalSocket videoSocket = null; LocalSocket videoSocket = null;
LocalSocket audioSocket = null; LocalSocket audioSocket = null;
LocalSocket controlSocket = null; LocalSocket controlSocket = null;
@ -72,28 +74,24 @@ public final class DesktopConnection implements Closeable {
try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) { try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) {
if (video) { if (video) {
videoSocket = localServerSocket.accept(); videoSocket = localServerSocket.accept();
if (sendDummyByte) { firstSocket = videoSocket;
// send one byte so the client may read() to detect a connection error
videoSocket.getOutputStream().write(0);
sendDummyByte = false;
}
} }
if (audio) { if (audio) {
audioSocket = localServerSocket.accept(); audioSocket = localServerSocket.accept();
if (sendDummyByte) { if (firstSocket == null) {
// send one byte so the client may read() to detect a connection error firstSocket = audioSocket;
audioSocket.getOutputStream().write(0);
sendDummyByte = false;
} }
} }
if (control) { if (control) {
controlSocket = localServerSocket.accept(); controlSocket = localServerSocket.accept();
if (sendDummyByte) { if (firstSocket == null) {
// send one byte so the client may read() to detect a connection error firstSocket = controlSocket;
controlSocket.getOutputStream().write(0);
sendDummyByte = false;
} }
} }
if (sendDummyByte) {
// send one byte so the client may read() to detect a connection error
firstSocket.getOutputStream().write(0);
}
} }
} else { } else {
if (video) { if (video) {

View File

@ -99,32 +99,25 @@ public final class Device {
} }
}, displayId); }, displayId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() {
ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() { @Override
@Override public void onDisplayFoldChanged(int displayId, boolean folded) {
public void onDisplayFoldChanged(int displayId, boolean folded) { synchronized (Device.this) {
if (Device.this.displayId != displayId) { DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
// Ignore events related to other display ids if (displayInfo == null) {
Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage());
return; return;
} }
synchronized (Device.this) { screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(),
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); options.getMaxSize(), options.getLockVideoOrientation());
if (displayInfo == null) { // notify
Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage()); if (foldListener != null) {
return; foldListener.onFoldChanged(displayId, folded);
}
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(),
options.getMaxSize(), options.getLockVideoOrientation());
// notify
if (foldListener != null) {
foldListener.onFoldChanged(displayId, folded);
}
} }
} }
}); }
} });
if (options.getControl() && options.getClipboardAutosync()) { if (options.getControl() && options.getClipboardAutosync()) {
// If control and autosync are enabled, synchronize Android clipboard to the computer automatically // If control and autosync are enabled, synchronize Android clipboard to the computer automatically

View File

@ -1,83 +0,0 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.SurfaceControl;
import android.graphics.Rect;
import android.os.Build;
import android.os.IBinder;
import android.view.Surface;
public class ScreenCapture extends SurfaceCapture implements Device.RotationListener, Device.FoldListener {
private final Device device;
private IBinder display;
public ScreenCapture(Device device) {
this.device = device;
}
@Override
public void init() {
display = createDisplay();
device.setRotationListener(this);
device.setFoldListener(this);
}
@Override
public void release() {
device.setRotationListener(null);
device.setFoldListener(null);
SurfaceControl.destroyDisplay(display);
}
@Override
public Size getSize() {
return device.getScreenInfo().getVideoSize();
}
@Override
public void setMaxSize(int size) {
device.setMaxSize(size);
}
@Override
public void setSurface(Surface surface) {
ScreenInfo screenInfo = device.getScreenInfo();
Rect contentRect = screenInfo.getContentRect();
// does not include the locked video orientation
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation();
int layerStack = device.getLayerStack();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
}
@Override
public void onFoldChanged(int displayId, boolean folded) {
requestReset();
}
@Override
public void onRotationChanged(int rotation) {
requestReset();
}
private static IBinder createDisplay() {
// Since Android 12 (preview), secure displays could not be created with shell permissions anymore.
// On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S".
boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S".equals(
Build.VERSION.CODENAME));
return SurfaceControl.createDisplay("scrcpy", secure);
}
private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect, int layerStack) {
SurfaceControl.openTransaction();
try {
SurfaceControl.setDisplaySurface(display, surface);
SurfaceControl.setDisplayProjection(display, orientation, deviceRect, displayRect);
SurfaceControl.setDisplayLayerStack(display, layerStack);
} finally {
SurfaceControl.closeTransaction();
}
}
}

View File

@ -8,7 +8,6 @@ import android.media.MediaCodecInfo;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import android.view.Surface; import android.view.Surface;
@ -17,7 +16,7 @@ import java.nio.ByteBuffer;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class SurfaceEncoder implements AsyncProcessor { public class ScreenEncoder implements Device.RotationListener, Device.FoldListener, AsyncProcessor {
private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
@ -27,7 +26,9 @@ public class SurfaceEncoder implements AsyncProcessor {
private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800}; private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800};
private static final int MAX_CONSECUTIVE_ERRORS = 3; private static final int MAX_CONSECUTIVE_ERRORS = 3;
private final SurfaceCapture capture; private final AtomicBoolean resetCapture = new AtomicBoolean();
private final Device device;
private final Streamer streamer; private final Streamer streamer;
private final String encoderName; private final String encoderName;
private final List<CodecOption> codecOptions; private final List<CodecOption> codecOptions;
@ -41,9 +42,9 @@ public class SurfaceEncoder implements AsyncProcessor {
private Thread thread; private Thread thread;
private final AtomicBoolean stopped = new AtomicBoolean(); private final AtomicBoolean stopped = new AtomicBoolean();
public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, int videoBitRate, int maxFps, List<CodecOption> codecOptions, String encoderName, public ScreenEncoder(Device device, Streamer streamer, int videoBitRate, int maxFps, List<CodecOption> codecOptions, String encoderName,
boolean downsizeOnError) { boolean downsizeOnError) {
this.capture = capture; this.device = device;
this.streamer = streamer; this.streamer = streamer;
this.videoBitRate = videoBitRate; this.videoBitRate = videoBitRate;
this.maxFps = maxFps; this.maxFps = maxFps;
@ -52,29 +53,51 @@ public class SurfaceEncoder implements AsyncProcessor {
this.downsizeOnError = downsizeOnError; this.downsizeOnError = downsizeOnError;
} }
@Override
public void onFoldChanged(int displayId, boolean folded) {
resetCapture.set(true);
}
@Override
public void onRotationChanged(int rotation) {
resetCapture.set(true);
}
private boolean consumeResetCapture() {
return resetCapture.getAndSet(false);
}
private void streamScreen() throws IOException, ConfigurationException { private void streamScreen() throws IOException, ConfigurationException {
Codec codec = streamer.getCodec(); Codec codec = streamer.getCodec();
MediaCodec mediaCodec = createMediaCodec(codec, encoderName); MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions); MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
IBinder display = createDisplay();
device.setRotationListener(this);
device.setFoldListener(this);
capture.init(); streamer.writeVideoHeader(device.getScreenInfo().getVideoSize());
boolean alive;
try { try {
streamer.writeVideoHeader(capture.getSize());
boolean alive;
do { do {
Size size = capture.getSize(); ScreenInfo screenInfo = device.getScreenInfo();
format.setInteger(MediaFormat.KEY_WIDTH, size.getWidth()); Rect contentRect = screenInfo.getContentRect();
format.setInteger(MediaFormat.KEY_HEIGHT, size.getHeight());
// include the locked video orientation
Rect videoRect = screenInfo.getVideoSize().toRect();
format.setInteger(MediaFormat.KEY_WIDTH, videoRect.width());
format.setInteger(MediaFormat.KEY_HEIGHT, videoRect.height());
Surface surface = null; Surface surface = null;
try { try {
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
surface = mediaCodec.createInputSurface(); surface = mediaCodec.createInputSurface();
capture.setSurface(surface); // does not include the locked video orientation
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation();
int layerStack = device.getLayerStack();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
mediaCodec.start(); mediaCodec.start();
@ -83,7 +106,7 @@ public class SurfaceEncoder implements AsyncProcessor {
mediaCodec.stop(); mediaCodec.stop();
} catch (IllegalStateException | IllegalArgumentException e) { } catch (IllegalStateException | IllegalArgumentException e) {
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
if (!prepareRetry(size)) { if (!prepareRetry(device, screenInfo)) {
throw e; throw e;
} }
Ln.i("Retrying..."); Ln.i("Retrying...");
@ -97,11 +120,13 @@ public class SurfaceEncoder implements AsyncProcessor {
} while (alive); } while (alive);
} finally { } finally {
mediaCodec.release(); mediaCodec.release();
capture.release(); device.setRotationListener(null);
device.setFoldListener(null);
SurfaceControl.destroyDisplay(display);
} }
} }
private boolean prepareRetry(Size currentSize) { private boolean prepareRetry(Device device, ScreenInfo screenInfo) {
if (firstFrameSent) { if (firstFrameSent) {
++consecutiveErrors; ++consecutiveErrors;
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) { if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
@ -121,7 +146,8 @@ public class SurfaceEncoder implements AsyncProcessor {
// Downsizing on error is only enabled if an encoding failure occurs before the first frame (downsizing later could be surprising) // Downsizing on error is only enabled if an encoding failure occurs before the first frame (downsizing later could be surprising)
int newMaxSize = chooseMaxSizeFallback(currentSize); int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize());
Ln.i("newMaxSize = " + newMaxSize);
if (newMaxSize == 0) { if (newMaxSize == 0) {
// Must definitively fail // Must definitively fail
return false; return false;
@ -129,7 +155,7 @@ public class SurfaceEncoder implements AsyncProcessor {
// Retry with a smaller device size // Retry with a smaller device size
Ln.i("Retrying with -m" + newMaxSize + "..."); Ln.i("Retrying with -m" + newMaxSize + "...");
capture.setMaxSize(newMaxSize); device.setMaxSize(newMaxSize);
return true; return true;
} }
@ -150,14 +176,14 @@ public class SurfaceEncoder implements AsyncProcessor {
boolean alive = true; boolean alive = true;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!capture.consumeReset() && !eof) { while (!consumeResetCapture() && !eof) {
if (stopped.get()) { if (stopped.get()) {
alive = false; alive = false;
break; break;
} }
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1); int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
try { try {
if (capture.consumeReset()) { if (consumeResetCapture()) {
// must restart encoding with new size // must restart encoding with new size
break; break;
} }
@ -241,8 +267,8 @@ public class SurfaceEncoder implements AsyncProcessor {
private static IBinder createDisplay() { private static IBinder createDisplay() {
// Since Android 12 (preview), secure displays could not be created with shell permissions anymore. // Since Android 12 (preview), secure displays could not be created with shell permissions anymore.
// On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S". // On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S".
boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S".equals( boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S"
Build.VERSION.CODENAME)); .equals(Build.VERSION.CODENAME));
return SurfaceControl.createDisplay("scrcpy", secure); return SurfaceControl.createDisplay("scrcpy", secure);
} }
@ -260,10 +286,6 @@ public class SurfaceEncoder implements AsyncProcessor {
@Override @Override
public void start(TerminationListener listener) { public void start(TerminationListener listener) {
thread = new Thread(() -> { thread = new Thread(() -> {
// Some devices (Meizu) deadlock if the video encoding thread has no Looper
// <https://github.com/Genymobile/scrcpy/issues/4143>
Looper.prepare();
try { try {
streamScreen(); streamScreen();
} catch (ConfigurationException e) { } catch (ConfigurationException e) {

View File

@ -132,8 +132,7 @@ public final class Server {
if (video) { if (video) {
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(), Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(),
options.getSendFrameMeta()); options.getSendFrameMeta());
ScreenCapture screenCapture = new ScreenCapture(device); ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
SurfaceEncoder screenEncoder = new SurfaceEncoder(screenCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError());
asyncProcessors.add(screenEncoder); asyncProcessors.add(screenEncoder);
} }

View File

@ -1,61 +0,0 @@
package com.genymobile.scrcpy;
import android.view.Surface;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A video source which can be rendered on a Surface for encoding.
*/
public abstract class SurfaceCapture {
private final AtomicBoolean resetCapture = new AtomicBoolean();
/**
* Request the encoding session to be restarted, for example if the capture implementation detects that the video source size has changed (on
* device rotation for example).
*/
protected void requestReset() {
resetCapture.set(true);
}
/**
* Consume the reset request (intended to be called by the encoder).
*
* @return {@code true} if a reset request was pending, {@code false} otherwise.
*/
public boolean consumeReset() {
return resetCapture.getAndSet(false);
}
/**
* Called once before the capture starts.
*/
public abstract void init();
/**
* Called after the capture ends (if and only if {@link #init()} has been called).
*/
public abstract void release();
/**
* Return the video size
*
* @return the video size
*/
public abstract Size getSize();
/**
* Set the maximum capture size (set by the encoder if it does not support the current size).
*
* @param size Maximum size
*/
public abstract void setMaxSize(int size);
/**
* Set the target surface.
*
* @param surface the surface which will be encoded
*/
public abstract void setSurface(Surface surface);
}

View File

@ -2,7 +2,6 @@ package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Ln;
import android.annotation.TargetApi;
import android.os.IInterface; import android.os.IInterface;
import android.view.IRotationWatcher; import android.view.IRotationWatcher;
import android.view.IDisplayFoldListener; import android.view.IDisplayFoldListener;
@ -107,17 +106,16 @@ public final class WindowManager {
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher); cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
} }
} catch (Exception e) { } catch (Exception e) {
Ln.e("Could not register rotation watcher", e); throw new AssertionError(e);
} }
} }
@TargetApi(29)
public void registerDisplayFoldListener(IDisplayFoldListener foldListener) { public void registerDisplayFoldListener(IDisplayFoldListener foldListener) {
try { try {
Class<?> cls = manager.getClass(); Class<?> cls = manager.getClass();
cls.getMethod("registerDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener); cls.getMethod("registerDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener);
} catch (Exception e) { } catch (Exception e) {
Ln.e("Could not register display fold listener", e); throw new AssertionError(e);
} }
} }
} }