mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-02-25 15:54:28 +01:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a8d851090 | ||
|
|
0e019f8ab8 | ||
|
|
f7d02cad4b | ||
|
|
a7fe9ad779 | ||
|
|
b2c3df7550 | ||
|
|
46fec41b7b | ||
|
|
2876463d39 | ||
|
|
6dc6ec05d5 | ||
|
|
b5e630eea3 | ||
|
|
a17f1116ce | ||
|
|
e4cf152b26 | ||
|
|
bd32016632 | ||
|
|
77b620e1d0 |
14
BUILD.md
14
BUILD.md
@@ -43,7 +43,7 @@ Install the required packages from your package manager.
|
||||
sudo apt install ffmpeg libsdl2-2.0.0
|
||||
|
||||
# client build dependencies
|
||||
sudo apt install make gcc pkg-config meson \
|
||||
sudo apt install make gcc pkg-config meson ninja-build \
|
||||
libavcodec-dev libavformat-dev libavutil-dev \
|
||||
libsdl2-dev
|
||||
|
||||
@@ -199,6 +199,12 @@ cd x
|
||||
ninja
|
||||
```
|
||||
|
||||
_Note: `ninja` [must][ninja-user] be run as a non-root user (only `ninja
|
||||
install` must be run as root)._
|
||||
|
||||
[ninja-user]: https://github.com/Genymobile/scrcpy/commit/4c49b27e9f6be02b8e63b508b60535426bd0291a
|
||||
|
||||
|
||||
### Run
|
||||
|
||||
To run without installing:
|
||||
@@ -228,10 +234,10 @@ You can then [run](README.md#run) _scrcpy_.
|
||||
|
||||
## Prebuilt server
|
||||
|
||||
- [`scrcpy-server-v1.4.jar`][direct-scrcpy-server]
|
||||
_(SHA-256: 1ff7a72fcfe81dadccfab9d6f86c971cd7c7f38f17196748fe05480e301b443d)_
|
||||
- [`scrcpy-server-v1.5.jar`][direct-scrcpy-server]
|
||||
_(SHA-256: d97aab6f60294e33e7ff79c2856ad3e01f912892395131f4f337e9ece03c24de)_
|
||||
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.4/scrcpy-server-v1.4.jar
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.5-fixversion/scrcpy-server-v1.5.jar
|
||||
|
||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||
configuration:
|
||||
|
||||
161
README.md
161
README.md
@@ -1,4 +1,4 @@
|
||||
# scrcpy (v1.4)
|
||||
# scrcpy (v1.5)
|
||||
|
||||
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.
|
||||
@@ -15,6 +15,11 @@ Make sure you [enabled adb debugging][enable-adb] on your device(s).
|
||||
|
||||
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||
|
||||
On some devices, you also need to enable [an additional option][control] to
|
||||
control it using keyboard and mouse.
|
||||
|
||||
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||
|
||||
|
||||
## Get the app
|
||||
|
||||
@@ -42,13 +47,13 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
|
||||
For Windows, for simplicity, prebuilt archives with all the dependencies
|
||||
(including `adb`) are available:
|
||||
|
||||
- [`scrcpy-win32-v1.4.zip`][direct-win32]
|
||||
_(SHA-256: 1f72fa520980727e8943b7214b64c66b00b9b5267f7cffefb64fa37c3ca803cf)_
|
||||
- [`scrcpy-win64-v1.4.zip`][direct-win64]
|
||||
_(SHA-256: 382f02bd8ed3db2cc7ab15aabdb83674744993b936d602b01e6959a150584a79)_
|
||||
- [`scrcpy-win32-v1.5.zip`][direct-win32]
|
||||
_(SHA-256: 46ae0d4c1c6bd049ec4a30080d2ad91a32b31d3f758afdca2c3a915ecabf02c1)_
|
||||
- [`scrcpy-win64-v1.5.zip`][direct-win64]
|
||||
_(SHA-256: 89daa07325129617cf943a84bc4e304ee5e57118416fe265b9b5d4a1bf87c501)_
|
||||
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.4/scrcpy-win32-v1.4.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.4/scrcpy-win64-v1.4.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.5-fixversion/scrcpy-win32-v1.5.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.5-fixversion/scrcpy-win64-v1.5.zip
|
||||
|
||||
You can also [build the app manually][BUILD].
|
||||
|
||||
@@ -86,43 +91,157 @@ It accepts command-line arguments, listed by:
|
||||
scrcpy --help
|
||||
```
|
||||
|
||||
For example, to decrease video bitrate to 2Mbps (default is 8Mbps):
|
||||
## Features
|
||||
|
||||
|
||||
### Reduce size
|
||||
|
||||
Sometimes, it is useful to mirror an Android device at a lower definition to
|
||||
increase performances.
|
||||
|
||||
To limit both width and height to some value (e.g. 1024):
|
||||
|
||||
```bash
|
||||
scrcpy -b 2M
|
||||
scrcpy --max-size 1024
|
||||
scrcpy -m 1024 # short version
|
||||
```
|
||||
|
||||
To limit the video dimensions (e.g. if the device is 2540×1440, but the host
|
||||
screen is smaller, or cannot decode such a high definition):
|
||||
The other dimension is computed to that the device aspect-ratio is preserved.
|
||||
That way, a device in 1920×1080 will be mirrored at 1024×576.
|
||||
|
||||
|
||||
### Change bit-rate
|
||||
|
||||
The default bit-rate is 8Mbps. To change the video bitrate (e.g. to 2Mbps):
|
||||
|
||||
```bash
|
||||
scrcpy -m 1024
|
||||
scrcpy --bit-rate 2M
|
||||
scrcpy -b 2M # short version
|
||||
```
|
||||
|
||||
The device screen may be cropped to mirror only part of the screen:
|
||||
|
||||
### Crop
|
||||
|
||||
The device screen may be cropped to mirror only part of the screen.
|
||||
|
||||
This is useful for example to mirror only 1 eye of the Oculus Go:
|
||||
|
||||
```bash
|
||||
scrcpy -c 1224:1440:0:0 # 1224x1440 at offset (0,0)
|
||||
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
|
||||
scrcpy -c 1224:1440:0:0 # short version
|
||||
```
|
||||
|
||||
If `--max-size` is also specified, resizing is applied after cropping.
|
||||
|
||||
|
||||
### Wireless
|
||||
|
||||
_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a
|
||||
device over TCP/IP:
|
||||
|
||||
1. Connect the device to the same Wi-Fi as your computer.
|
||||
2. Get your device IP address (in Settings → About phone → Status).
|
||||
3. Enable adb over TCP/IP on your device: `adb tcpip 5555`.
|
||||
4. Unplug your device.
|
||||
5. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_.
|
||||
6. Run `scrcpy` as usual.
|
||||
|
||||
It may be useful to decrease the bit-rate and the definition:
|
||||
|
||||
```bash
|
||||
scrcpy --bit-rate 2M --max-size 800
|
||||
scrcpy -b2M -m800 # short version
|
||||
```
|
||||
|
||||
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
|
||||
|
||||
|
||||
### Record screen
|
||||
|
||||
It is possible to record the screen while mirroring:
|
||||
|
||||
```bash
|
||||
scrcpy --record file.mp4
|
||||
scrcpy -r file.mp4
|
||||
```
|
||||
|
||||
"Skipped frames" are recorded, even if they are not displayed in real time (for
|
||||
performance reasons). Frames are _timestamped_ on the device, so [packet delay
|
||||
variation] does not impact the recorded file.
|
||||
|
||||
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||
|
||||
|
||||
### Multi-devices
|
||||
|
||||
If several devices are listed in `adb devices`, you must specify the _serial_:
|
||||
|
||||
```bash
|
||||
scrcpy -s 0123456789abcdef
|
||||
scrcpy --serial 0123456789abcdef
|
||||
scrcpy -s 0123456789abcdef # short version
|
||||
```
|
||||
|
||||
To show physical touches while scrcpy is running:
|
||||
You can start several instances of _scrcpy_ for several devices.
|
||||
|
||||
```bash
|
||||
scrcpy -t
|
||||
```
|
||||
|
||||
### Fullscreen
|
||||
|
||||
The app may be started directly in fullscreen:
|
||||
|
||||
```bash
|
||||
scrcpy --fullscreen
|
||||
scrcpy -f # short version
|
||||
```
|
||||
scrcpy -f
|
||||
|
||||
Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
|
||||
|
||||
|
||||
### Show touches
|
||||
|
||||
For presentations, it may be useful to show physical touches (on the physical
|
||||
device).
|
||||
|
||||
Android provides this feature in _Developers options_.
|
||||
|
||||
_Scrcpy_ provides an option to enable this feature on start and disable on exit:
|
||||
|
||||
```bash
|
||||
scrcpy --show-touches
|
||||
scrcpy -t
|
||||
```
|
||||
|
||||
Note that it only shows _physical_ touches (with the finger on the device).
|
||||
|
||||
|
||||
### Install APK
|
||||
|
||||
To install an APK, drag & drop an APK file (ending with `.apk`) to the _scrcpy_
|
||||
window.
|
||||
|
||||
There is no visual feedback, a log is printed to the console.
|
||||
|
||||
|
||||
### Push file to device
|
||||
|
||||
To push a file to `/sdcard/` on the device, drag & drop a (non-APK) file to the
|
||||
_scrcpy_ window.
|
||||
|
||||
There is no visual feedback, a log is printed to the console.
|
||||
|
||||
|
||||
### Forward audio
|
||||
|
||||
Audio is not forwarded by _scrcpy_.
|
||||
|
||||
There is a limited solution using [AOA], implemented in the [`audio`] branch. If
|
||||
you are interested, see [issue 14].
|
||||
|
||||
|
||||
[AOA]: https://source.android.com/devices/accessories/aoa2
|
||||
[`audio`]: https://github.com/Genymobile/scrcpy/commits/audio
|
||||
[issue 14]: https://github.com/Genymobile/scrcpy/issues/14
|
||||
|
||||
|
||||
## Shortcuts
|
||||
|
||||
| Action | Shortcut |
|
||||
@@ -140,8 +259,6 @@ scrcpy -f
|
||||
| turn screen on | _Right-click²_ |
|
||||
| paste computer clipboard to device | `Ctrl`+`v` |
|
||||
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
|
||||
| install APK from computer | drag & drop APK file |
|
||||
| push file to `/sdcard/` | drag & drop non-APK file |
|
||||
|
||||
_¹Double-click on black borders to remove them._
|
||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||
|
||||
@@ -86,7 +86,7 @@ conf = configuration_data()
|
||||
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
|
||||
|
||||
# the version, updated on release
|
||||
conf.set_quoted('SCRCPY_VERSION', '1.4')
|
||||
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
|
||||
|
||||
# the prefix used during configuration (meson --prefix=PREFIX)
|
||||
conf.set_quoted('PREFIX', get_option('prefix'))
|
||||
|
||||
@@ -80,12 +80,15 @@ static int read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
|
||||
if (!state->remaining) {
|
||||
#define HEADER_SIZE 12
|
||||
uint8_t header[HEADER_SIZE];
|
||||
ssize_t ret = net_recv_all(decoder->video_socket, header, HEADER_SIZE);
|
||||
if (ret <= 0) {
|
||||
return ret;
|
||||
ssize_t r = net_recv_all(decoder->video_socket, header, HEADER_SIZE);
|
||||
if (r == -1) {
|
||||
return AVERROR(errno);
|
||||
}
|
||||
if (r == 0) {
|
||||
return AVERROR_EOF;
|
||||
}
|
||||
// no partial read (net_recv_all())
|
||||
SDL_assert_release(ret == HEADER_SIZE);
|
||||
SDL_assert_release(r == HEADER_SIZE);
|
||||
|
||||
uint64_t pts = buffer_read64be(header);
|
||||
state->remaining = buffer_read32be(&header[8]);
|
||||
@@ -93,7 +96,7 @@ static int read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
|
||||
if (pts != NO_PTS && !receiver_state_push_meta(state, pts)) {
|
||||
LOGE("Could not store PTS for recording");
|
||||
// we cannot save the PTS, the recording would be broken
|
||||
return -1;
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,20 +105,30 @@ static int read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
|
||||
if (buf_size > state->remaining)
|
||||
buf_size = state->remaining;
|
||||
|
||||
ssize_t ret = net_recv(decoder->video_socket, buf, buf_size);
|
||||
if (ret <= 0) {
|
||||
return ret;
|
||||
ssize_t r = net_recv(decoder->video_socket, buf, buf_size);
|
||||
if (r == -1) {
|
||||
return AVERROR(errno);
|
||||
}
|
||||
if (r == 0) {
|
||||
return AVERROR_EOF;
|
||||
}
|
||||
|
||||
SDL_assert(state->remaining >= ret);
|
||||
state->remaining -= ret;
|
||||
SDL_assert(state->remaining >= r);
|
||||
state->remaining -= r;
|
||||
|
||||
return ret;
|
||||
return r;
|
||||
}
|
||||
|
||||
static int read_raw_packet(void *opaque, uint8_t *buf, int buf_size) {
|
||||
struct decoder *decoder = opaque;
|
||||
return net_recv(decoder->video_socket, buf, buf_size);
|
||||
ssize_t r = net_recv(decoder->video_socket, buf, buf_size);
|
||||
if (r == -1) {
|
||||
return AVERROR(errno);
|
||||
}
|
||||
if (r == 0) {
|
||||
return AVERROR_EOF;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// set the decoded frame as ready for rendering, and notify
|
||||
|
||||
@@ -18,16 +18,9 @@ static void convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int
|
||||
*y = (int) (*y / scale_y) - viewport.y;
|
||||
}
|
||||
|
||||
static struct point get_mouse_point(struct screen *screen) {
|
||||
int x;
|
||||
int y;
|
||||
SDL_GetMouseState(&x, &y);
|
||||
convert_to_renderer_coordinates(screen->renderer, &x, &y);
|
||||
SDL_assert_release(x >= 0 && x < 0x10000 && y >= 0 && y < 0x10000);
|
||||
return (struct point) {
|
||||
.x = (Uint16) x,
|
||||
.y = (Uint16) y,
|
||||
};
|
||||
static void get_mouse_point(struct screen *screen, int *x, int *y) {
|
||||
SDL_GetMouseState(x, y);
|
||||
convert_to_renderer_coordinates(screen->renderer, x, y);
|
||||
}
|
||||
|
||||
static const int ACTION_DOWN = 1;
|
||||
@@ -271,8 +264,18 @@ void input_manager_process_mouse_motion(struct input_manager *input_manager,
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_bool is_outside_device_screen(struct input_manager *input_manager,
|
||||
int x, int y)
|
||||
{
|
||||
return x < 0 || x >= input_manager->screen->frame_size.width ||
|
||||
y < 0 || y >= input_manager->screen->frame_size.height;
|
||||
}
|
||||
|
||||
void input_manager_process_mouse_button(struct input_manager *input_manager,
|
||||
const SDL_MouseButtonEvent *event) {
|
||||
SDL_bool outside_device_screen = is_outside_device_screen(input_manager,
|
||||
event->x,
|
||||
event->y);
|
||||
if (event->type == SDL_MOUSEBUTTONDOWN) {
|
||||
if (event->button == SDL_BUTTON_RIGHT) {
|
||||
press_back_or_turn_screen_on(input_manager->controller);
|
||||
@@ -283,17 +286,19 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
|
||||
return;
|
||||
}
|
||||
// double-click on black borders resize to fit the device screen
|
||||
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
||||
SDL_bool outside_device_screen =
|
||||
event->x < 0 || event->x >= input_manager->screen->frame_size.width ||
|
||||
event->y < 0 || event->y >= input_manager->screen->frame_size.height;
|
||||
if (outside_device_screen) {
|
||||
screen_resize_to_fit(input_manager->screen);
|
||||
return;
|
||||
}
|
||||
// otherwise, send the click event to the device
|
||||
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2
|
||||
&& outside_device_screen) {
|
||||
screen_resize_to_fit(input_manager->screen);
|
||||
return;
|
||||
}
|
||||
// otherwise, send the click event to the device
|
||||
}
|
||||
|
||||
if (outside_device_screen) {
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
|
||||
struct control_event control_event;
|
||||
if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) {
|
||||
if (!controller_push_event(input_manager->controller, &control_event)) {
|
||||
@@ -304,9 +309,22 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
|
||||
|
||||
void input_manager_process_mouse_wheel(struct input_manager *input_manager,
|
||||
const SDL_MouseWheelEvent *event) {
|
||||
int x;
|
||||
int y;
|
||||
get_mouse_point(input_manager->screen, &x, &y);
|
||||
if (is_outside_device_screen(input_manager, x, y)) {
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_assert_release(x >= 0 && x < 0x10000 && y >= 0 && y < 0x10000);
|
||||
|
||||
struct position position = {
|
||||
.screen_size = input_manager->screen->frame_size,
|
||||
.point = get_mouse_point(input_manager->screen),
|
||||
.point = {
|
||||
.x = (Uint16) x,
|
||||
.y = (Uint16) y,
|
||||
},
|
||||
};
|
||||
struct control_event control_event;
|
||||
if (mouse_wheel_from_sdl_to_android(event, position, &control_event)) {
|
||||
|
||||
@@ -124,7 +124,7 @@ static void usage(const char *arg0) {
|
||||
}
|
||||
|
||||
static void print_version(void) {
|
||||
fprintf(stderr, "scrcpy v%s\n\n", SCRCPY_VERSION);
|
||||
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
|
||||
|
||||
fprintf(stderr, "dependencies:\n");
|
||||
fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL);
|
||||
|
||||
@@ -81,6 +81,7 @@ SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) {
|
||||
ostream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
ostream->codec->width = recorder->declared_frame_size.width;
|
||||
ostream->codec->height = recorder->declared_frame_size.height;
|
||||
ostream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
#endif
|
||||
ostream->time_base = (AVRational) {1, 1000000}; // timestamps in us
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
project('scrcpy', 'c', meson_version: '>= 0.37', default_options : 'c_std=c11')
|
||||
project('scrcpy', 'c',
|
||||
version: '1.5',
|
||||
meson_version: '>= 0.37',
|
||||
default_options: 'c_std=c11')
|
||||
|
||||
if get_option('build_app')
|
||||
subdir('app')
|
||||
|
||||
@@ -6,8 +6,8 @@ android {
|
||||
applicationId "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 5
|
||||
versionName "1.4"
|
||||
versionCode 6
|
||||
versionName "1.5"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
Reference in New Issue
Block a user