Compare commits
19 Commits
issue1468
...
encoder_na
Author | SHA1 | Date | |
---|---|---|---|
576814bcec | |||
42ab8fd611 | |||
363eeea19e | |||
76c2c6e69d | |||
d5f059c7cb | |||
adc547fa6e | |||
5dcfc0ebab | |||
ad5f567f07 | |||
83082406d3 | |||
2edf192e3a | |||
d50ecf40b6 | |||
56d237f152 | |||
acc65f8c9d | |||
a65ebceac1 | |||
d662f73bdc | |||
1c44dc2259 | |||
02a882a0a2 | |||
cf7bf3148c | |||
ae758f99d6 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ build/
|
|||||||
.idea/
|
.idea/
|
||||||
.gradle/
|
.gradle/
|
||||||
/x/
|
/x/
|
||||||
|
local.properties
|
||||||
|
31
README.md
31
README.md
@ -203,6 +203,22 @@ scrcpy --lock-video-orientation 3 # 90° clockwise
|
|||||||
This affects recording orientation.
|
This affects recording orientation.
|
||||||
|
|
||||||
|
|
||||||
|
#### Encoder
|
||||||
|
|
||||||
|
Some devices have more than one encoder, and some of them may cause issues or
|
||||||
|
crash. It is possible to select a different encoder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --encoder OMX.qcom.video.encoder.avc
|
||||||
|
```
|
||||||
|
|
||||||
|
To list the available encoders, you could pass an invalid encoder name, the
|
||||||
|
error will give the available encoders:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --encoder _
|
||||||
|
```
|
||||||
|
|
||||||
### Recording
|
### Recording
|
||||||
|
|
||||||
It is possible to record the screen while mirroring:
|
It is possible to record the screen while mirroring:
|
||||||
@ -548,6 +564,11 @@ into the device clipboard. As a consequence, any Android application could read
|
|||||||
its content. You should avoid to paste sensitive content (like passwords) that
|
its content. You should avoid to paste sensitive content (like passwords) that
|
||||||
way.
|
way.
|
||||||
|
|
||||||
|
Some devices do not behave as expected when setting the device clipboard
|
||||||
|
programmatically. An option `--legacy-paste` is provided to change the behavior
|
||||||
|
of <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> so that they
|
||||||
|
also inject the computer clipboard text as a sequence of key events (the same
|
||||||
|
way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
|
||||||
|
|
||||||
#### Pinch-to-zoom
|
#### Pinch-to-zoom
|
||||||
|
|
||||||
@ -595,6 +616,16 @@ scrcpy --no-key-repeat
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Right-click and middle-click
|
||||||
|
|
||||||
|
By default, right-click triggers BACK (or POWER on) and middle-click triggers
|
||||||
|
HOME. To disable these shortcuts and forward the clicks to the device instead:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --forward-all-clicks
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### File drop
|
### File drop
|
||||||
|
|
||||||
#### Install APK
|
#### Install APK
|
||||||
|
22
app/scrcpy.1
22
app/scrcpy.1
@ -56,10 +56,18 @@ The list of possible display ids can be listed by "adb shell dumpsys display"
|
|||||||
|
|
||||||
Default is 0.
|
Default is 0.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-encoder " name
|
||||||
|
Use a specific MediaCodec encoder (must be a H.264 encoder).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-force\-adb\-forward
|
.B \-\-force\-adb\-forward
|
||||||
Do not attempt to use "adb reverse" to connect to the device.
|
Do not attempt to use "adb reverse" to connect to the device.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-forward\-all\-clicks
|
||||||
|
By default, right-click triggers BACK (or POWER on) and middle-click triggers HOME. This option disables these shortcuts and forward the clicks to the device instead.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-f, \-\-fullscreen
|
.B \-f, \-\-fullscreen
|
||||||
Start in fullscreen.
|
Start in fullscreen.
|
||||||
@ -68,6 +76,12 @@ Start in fullscreen.
|
|||||||
.B \-h, \-\-help
|
.B \-h, \-\-help
|
||||||
Print this help.
|
Print this help.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-legacy\-paste
|
||||||
|
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
|
||||||
|
|
||||||
|
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-lock\-video\-orientation " value
|
.BI "\-\-lock\-video\-orientation " value
|
||||||
Lock video orientation to \fIvalue\fR. Possible values are -1 (unlocked), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise.
|
Lock video orientation to \fIvalue\fR. Possible values are -1 (unlocked), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise.
|
||||||
@ -92,14 +106,14 @@ Disable device control (mirror the device in read\-only).
|
|||||||
.B \-N, \-\-no\-display
|
.B \-N, \-\-no\-display
|
||||||
Do not display device (only when screen recording is enabled).
|
Do not display device (only when screen recording is enabled).
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-no\-mipmaps
|
|
||||||
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-no\-key\-repeat
|
.B \-\-no\-key\-repeat
|
||||||
Do not forward repeated key events when a key is held down.
|
Do not forward repeated key events when a key is held down.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-no\-mipmaps
|
||||||
|
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-p, \-\-port " port[:port]
|
.BI "\-p, \-\-port " port[:port]
|
||||||
Set the TCP port (range) used by the client to listen.
|
Set the TCP port (range) used by the client to listen.
|
||||||
|
@ -53,16 +53,30 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
" Default is 0.\n"
|
" Default is 0.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" --encoder name\n"
|
||||||
|
" Use a specific MediaCodec encoder (must be a H.264 encoder).\n"
|
||||||
|
"\n"
|
||||||
" --force-adb-forward\n"
|
" --force-adb-forward\n"
|
||||||
" Do not attempt to use \"adb reverse\" to connect to the\n"
|
" Do not attempt to use \"adb reverse\" to connect to the\n"
|
||||||
" the device.\n"
|
" the device.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" --forward-all-clicks\n"
|
||||||
|
" By default, right-click triggers BACK (or POWER on) and\n"
|
||||||
|
" middle-click triggers HOME. This option disables these\n"
|
||||||
|
" shortcuts and forward the clicks to the device instead.\n"
|
||||||
|
"\n"
|
||||||
" -f, --fullscreen\n"
|
" -f, --fullscreen\n"
|
||||||
" Start in fullscreen.\n"
|
" Start in fullscreen.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -h, --help\n"
|
" -h, --help\n"
|
||||||
" Print this help.\n"
|
" Print this help.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" --legacy-paste\n"
|
||||||
|
" Inject computer clipboard text as a sequence of key events\n"
|
||||||
|
" on Ctrl+v (like MOD+Shift+v).\n"
|
||||||
|
" This is a workaround for some devices not behaving as\n"
|
||||||
|
" expected when setting the device clipboard programmatically.\n"
|
||||||
|
"\n"
|
||||||
" --lock-video-orientation value\n"
|
" --lock-video-orientation value\n"
|
||||||
" Lock video orientation to value.\n"
|
" Lock video orientation to value.\n"
|
||||||
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n"
|
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n"
|
||||||
@ -87,14 +101,14 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" Do not display device (only when screen recording is\n"
|
" Do not display device (only when screen recording is\n"
|
||||||
" enabled).\n"
|
" enabled).\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
" --no-key-repeat\n"
|
||||||
|
" Do not forward repeated key events when a key is held down.\n"
|
||||||
|
"\n"
|
||||||
" --no-mipmaps\n"
|
" --no-mipmaps\n"
|
||||||
" If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then\n"
|
" If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then\n"
|
||||||
" mipmaps are automatically generated to improve downscaling\n"
|
" mipmaps are automatically generated to improve downscaling\n"
|
||||||
" quality. This option disables the generation of mipmaps.\n"
|
" quality. This option disables the generation of mipmaps.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --no-key-repeat\n"
|
|
||||||
" Do not forward repeated key events when a key is held down.\n"
|
|
||||||
"\n"
|
|
||||||
" -p, --port port[:port]\n"
|
" -p, --port port[:port]\n"
|
||||||
" Set the TCP port (range) used by the client to listen.\n"
|
" Set the TCP port (range) used by the client to listen.\n"
|
||||||
" Default is %d:%d.\n"
|
" Default is %d:%d.\n"
|
||||||
@ -651,6 +665,9 @@ guess_record_format(const char *filename) {
|
|||||||
#define OPT_DISABLE_SCREENSAVER 1020
|
#define OPT_DISABLE_SCREENSAVER 1020
|
||||||
#define OPT_SHORTCUT_MOD 1021
|
#define OPT_SHORTCUT_MOD 1021
|
||||||
#define OPT_NO_KEY_REPEAT 1022
|
#define OPT_NO_KEY_REPEAT 1022
|
||||||
|
#define OPT_FORWARD_ALL_CLICKS 1023
|
||||||
|
#define OPT_LEGACY_PASTE 1024
|
||||||
|
#define OPT_ENCODER_NAME 1025
|
||||||
|
|
||||||
bool
|
bool
|
||||||
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||||
@ -662,18 +679,22 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
{"disable-screensaver", no_argument, NULL,
|
{"disable-screensaver", no_argument, NULL,
|
||||||
OPT_DISABLE_SCREENSAVER},
|
OPT_DISABLE_SCREENSAVER},
|
||||||
{"display", required_argument, NULL, OPT_DISPLAY_ID},
|
{"display", required_argument, NULL, OPT_DISPLAY_ID},
|
||||||
|
{"encoder", required_argument, NULL, OPT_ENCODER_NAME},
|
||||||
{"force-adb-forward", no_argument, NULL,
|
{"force-adb-forward", no_argument, NULL,
|
||||||
OPT_FORCE_ADB_FORWARD},
|
OPT_FORCE_ADB_FORWARD},
|
||||||
|
{"forward-all-clicks", no_argument, NULL,
|
||||||
|
OPT_FORWARD_ALL_CLICKS},
|
||||||
{"fullscreen", no_argument, NULL, 'f'},
|
{"fullscreen", no_argument, NULL, 'f'},
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
|
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
|
||||||
{"lock-video-orientation", required_argument, NULL,
|
{"lock-video-orientation", required_argument, NULL,
|
||||||
OPT_LOCK_VIDEO_ORIENTATION},
|
OPT_LOCK_VIDEO_ORIENTATION},
|
||||||
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
|
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
|
||||||
{"max-size", required_argument, NULL, 'm'},
|
{"max-size", required_argument, NULL, 'm'},
|
||||||
{"no-control", no_argument, NULL, 'n'},
|
{"no-control", no_argument, NULL, 'n'},
|
||||||
{"no-display", no_argument, NULL, 'N'},
|
{"no-display", no_argument, NULL, 'N'},
|
||||||
{"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS},
|
|
||||||
{"no-key-repeat", no_argument, NULL, OPT_NO_KEY_REPEAT},
|
{"no-key-repeat", no_argument, NULL, OPT_NO_KEY_REPEAT},
|
||||||
|
{"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS},
|
||||||
{"port", required_argument, NULL, 'p'},
|
{"port", required_argument, NULL, 'p'},
|
||||||
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
|
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
|
||||||
{"push-target", required_argument, NULL, OPT_PUSH_TARGET},
|
{"push-target", required_argument, NULL, OPT_PUSH_TARGET},
|
||||||
@ -845,6 +866,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
case OPT_CODEC_OPTIONS:
|
case OPT_CODEC_OPTIONS:
|
||||||
opts->codec_options = optarg;
|
opts->codec_options = optarg;
|
||||||
break;
|
break;
|
||||||
|
case OPT_ENCODER_NAME:
|
||||||
|
opts->encoder_name = optarg;
|
||||||
|
break;
|
||||||
case OPT_FORCE_ADB_FORWARD:
|
case OPT_FORCE_ADB_FORWARD:
|
||||||
opts->force_adb_forward = true;
|
opts->force_adb_forward = true;
|
||||||
break;
|
break;
|
||||||
@ -856,6 +880,12 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case OPT_FORWARD_ALL_CLICKS:
|
||||||
|
opts->forward_all_clicks = true;
|
||||||
|
break;
|
||||||
|
case OPT_LEGACY_PASTE:
|
||||||
|
opts->legacy_paste = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
@ -60,6 +60,8 @@ input_manager_init(struct input_manager *im,
|
|||||||
im->control = options->control;
|
im->control = options->control;
|
||||||
im->forward_key_repeat = options->forward_key_repeat;
|
im->forward_key_repeat = options->forward_key_repeat;
|
||||||
im->prefer_text = options->prefer_text;
|
im->prefer_text = options->prefer_text;
|
||||||
|
im->forward_all_clicks = options->forward_all_clicks;
|
||||||
|
im->legacy_paste = options->legacy_paste;
|
||||||
|
|
||||||
const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods;
|
const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods;
|
||||||
assert(shortcut_mods->count);
|
assert(shortcut_mods->count);
|
||||||
@ -440,7 +442,7 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
return;
|
return;
|
||||||
case SDLK_v:
|
case SDLK_v:
|
||||||
if (control && !repeat && down) {
|
if (control && !repeat && down) {
|
||||||
if (shift) {
|
if (shift || im->legacy_paste) {
|
||||||
// inject the text as input events
|
// inject the text as input events
|
||||||
clipboard_paste(controller);
|
clipboard_paste(controller);
|
||||||
} else {
|
} else {
|
||||||
@ -504,6 +506,11 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ctrl && !shift && keycode == SDLK_v && down && !repeat) {
|
if (ctrl && !shift && keycode == SDLK_v && down && !repeat) {
|
||||||
|
if (im->legacy_paste) {
|
||||||
|
// inject the text as input events
|
||||||
|
clipboard_paste(controller);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Synchronize the computer clipboard to the device clipboard before
|
// Synchronize the computer clipboard to the device clipboard before
|
||||||
// sending Ctrl+v, to allow seamless copy-paste.
|
// sending Ctrl+v, to allow seamless copy-paste.
|
||||||
set_device_clipboard(controller, false);
|
set_device_clipboard(controller, false);
|
||||||
@ -629,7 +636,7 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||||
if (down) {
|
if (!im->forward_all_clicks && down) {
|
||||||
if (control && event->button == SDL_BUTTON_RIGHT) {
|
if (control && event->button == SDL_BUTTON_RIGHT) {
|
||||||
press_back_or_turn_screen_on(im->controller);
|
press_back_or_turn_screen_on(im->controller);
|
||||||
return;
|
return;
|
||||||
|
@ -25,6 +25,8 @@ struct input_manager {
|
|||||||
bool control;
|
bool control;
|
||||||
bool forward_key_repeat;
|
bool forward_key_repeat;
|
||||||
bool prefer_text;
|
bool prefer_text;
|
||||||
|
bool forward_all_clicks;
|
||||||
|
bool legacy_paste;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||||
|
@ -100,7 +100,7 @@ main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
#if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE)
|
#if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE)
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
fprintf(stderr, "Press any key to continue...\n");
|
fprintf(stderr, "Press Enter to continue...\n");
|
||||||
getchar();
|
getchar();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -361,12 +361,14 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) {
|
|||||||
|
|
||||||
if (recorder->failed) {
|
if (recorder->failed) {
|
||||||
// reject any new packet (this will stop the stream)
|
// reject any new packet (this will stop the stream)
|
||||||
|
mutex_unlock(recorder->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct record_packet *rec = record_packet_new(packet);
|
struct record_packet *rec = record_packet_new(packet);
|
||||||
if (!rec) {
|
if (!rec) {
|
||||||
LOGC("Could not allocate record packet");
|
LOGC("Could not allocate record packet");
|
||||||
|
mutex_unlock(recorder->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,6 +318,7 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
.show_touches = options->show_touches,
|
.show_touches = options->show_touches,
|
||||||
.stay_awake = options->stay_awake,
|
.stay_awake = options->stay_awake,
|
||||||
.codec_options = options->codec_options,
|
.codec_options = options->codec_options,
|
||||||
|
.encoder_name = options->encoder_name,
|
||||||
.force_adb_forward = options->force_adb_forward,
|
.force_adb_forward = options->force_adb_forward,
|
||||||
};
|
};
|
||||||
if (!server_start(&server, options->serial, ¶ms)) {
|
if (!server_start(&server, options->serial, ¶ms)) {
|
||||||
|
@ -51,6 +51,7 @@ struct scrcpy_options {
|
|||||||
const char *push_target;
|
const char *push_target;
|
||||||
const char *render_driver;
|
const char *render_driver;
|
||||||
const char *codec_options;
|
const char *codec_options;
|
||||||
|
const char *encoder_name;
|
||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
enum sc_record_format record_format;
|
enum sc_record_format record_format;
|
||||||
struct sc_port_range port_range;
|
struct sc_port_range port_range;
|
||||||
@ -79,6 +80,8 @@ struct scrcpy_options {
|
|||||||
bool force_adb_forward;
|
bool force_adb_forward;
|
||||||
bool disable_screensaver;
|
bool disable_screensaver;
|
||||||
bool forward_key_repeat;
|
bool forward_key_repeat;
|
||||||
|
bool forward_all_clicks;
|
||||||
|
bool legacy_paste;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SCRCPY_OPTIONS_DEFAULT { \
|
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||||
@ -89,6 +92,7 @@ struct scrcpy_options {
|
|||||||
.push_target = NULL, \
|
.push_target = NULL, \
|
||||||
.render_driver = NULL, \
|
.render_driver = NULL, \
|
||||||
.codec_options = NULL, \
|
.codec_options = NULL, \
|
||||||
|
.encoder_name = NULL, \
|
||||||
.log_level = SC_LOG_LEVEL_INFO, \
|
.log_level = SC_LOG_LEVEL_INFO, \
|
||||||
.record_format = SC_RECORD_FORMAT_AUTO, \
|
.record_format = SC_RECORD_FORMAT_AUTO, \
|
||||||
.port_range = { \
|
.port_range = { \
|
||||||
@ -123,6 +127,8 @@ struct scrcpy_options {
|
|||||||
.force_adb_forward = false, \
|
.force_adb_forward = false, \
|
||||||
.disable_screensaver = false, \
|
.disable_screensaver = false, \
|
||||||
.forward_key_repeat = true, \
|
.forward_key_repeat = true, \
|
||||||
|
.forward_all_clicks = false, \
|
||||||
|
.legacy_paste = false, \
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@ -294,6 +294,7 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
params->show_touches ? "true" : "false",
|
params->show_touches ? "true" : "false",
|
||||||
params->stay_awake ? "true" : "false",
|
params->stay_awake ? "true" : "false",
|
||||||
params->codec_options ? params->codec_options : "-",
|
params->codec_options ? params->codec_options : "-",
|
||||||
|
params->encoder_name ? params->encoder_name : "-",
|
||||||
};
|
};
|
||||||
#ifdef SERVER_DEBUGGER
|
#ifdef SERVER_DEBUGGER
|
||||||
LOGI("Server debugger waiting for a client on device port "
|
LOGI("Server debugger waiting for a client on device port "
|
||||||
|
@ -48,6 +48,7 @@ struct server_params {
|
|||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
const char *crop;
|
const char *crop;
|
||||||
const char *codec_options;
|
const char *codec_options;
|
||||||
|
const char *encoder_name;
|
||||||
struct sc_port_range port_range;
|
struct sc_port_range port_range;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
|
@ -14,7 +14,6 @@ struct video_buffer;
|
|||||||
|
|
||||||
struct stream {
|
struct stream {
|
||||||
socket_t socket;
|
socket_t socket;
|
||||||
struct video_buffer *video_buffer;
|
|
||||||
SDL_Thread *thread;
|
SDL_Thread *thread;
|
||||||
struct decoder *decoder;
|
struct decoder *decoder;
|
||||||
struct recorder *recorder;
|
struct recorder *recorder;
|
||||||
|
@ -19,6 +19,9 @@ allprojects {
|
|||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
options.compilerArgs << "-Xlint:deprecation"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
url="$1"
|
url="$1"
|
||||||
sum="$2"
|
sum="$2"
|
||||||
|
2
run
2
run
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# Run scrcpy generated in the specified BUILDDIR.
|
# Run scrcpy generated in the specified BUILDDIR.
|
||||||
#
|
#
|
||||||
# This provides the same feature as "ninja run", except that it is possible to
|
# This provides the same feature as "ninja run", except that it is possible to
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server" "$MESON_BUILD_ROOT/app/scrcpy"
|
SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server" "$MESON_BUILD_ROOT/app/scrcpy"
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode 19
|
versionCode 19
|
||||||
versionName "1.16"
|
versionName "1.16"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# This script generates the scrcpy binary "manually" (without gradle).
|
# This script generates the scrcpy binary "manually" (without gradle).
|
||||||
#
|
#
|
||||||
@ -14,8 +14,8 @@ set -e
|
|||||||
SCRCPY_DEBUG=false
|
SCRCPY_DEBUG=false
|
||||||
SCRCPY_VERSION_NAME=1.16
|
SCRCPY_VERSION_NAME=1.16
|
||||||
|
|
||||||
PLATFORM=${ANDROID_PLATFORM:-29}
|
PLATFORM=${ANDROID_PLATFORM:-30}
|
||||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-29.0.2}
|
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0}
|
||||||
|
|
||||||
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
||||||
CLASSES_DIR="$BUILD_DIR/classes"
|
CLASSES_DIR="$BUILD_DIR/classes"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# Wrapper script to invoke gradle from meson
|
# Wrapper script to invoke gradle from meson
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
@ -205,9 +205,13 @@ public class Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Right-click and middle-click only work if the source is a mouse
|
||||||
|
boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0;
|
||||||
|
int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN;
|
||||||
|
|
||||||
MotionEvent event = MotionEvent
|
MotionEvent event = MotionEvent
|
||||||
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0, source,
|
||||||
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
0);
|
||||||
return device.injectEvent(event);
|
return device.injectEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.media.MediaCodecInfo;
|
||||||
|
|
||||||
|
public class InvalidEncoderException extends RuntimeException {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final MediaCodecInfo[] availableEncoders;
|
||||||
|
|
||||||
|
public InvalidEncoderException(String name, MediaCodecInfo[] availableEncoders) {
|
||||||
|
super("There is no encoder having name '" + name + '"');
|
||||||
|
this.name = name;
|
||||||
|
this.availableEncoders = availableEncoders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaCodecInfo[] getAvailableEncoders() {
|
||||||
|
return availableEncoders;
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@ public class Options {
|
|||||||
private boolean showTouches;
|
private boolean showTouches;
|
||||||
private boolean stayAwake;
|
private boolean stayAwake;
|
||||||
private String codecOptions;
|
private String codecOptions;
|
||||||
|
private String encoderName;
|
||||||
|
|
||||||
public Ln.Level getLogLevel() {
|
public Ln.Level getLogLevel() {
|
||||||
return logLevel;
|
return logLevel;
|
||||||
@ -120,4 +121,12 @@ public class Options {
|
|||||||
public void setCodecOptions(String codecOptions) {
|
public void setCodecOptions(String codecOptions) {
|
||||||
this.codecOptions = codecOptions;
|
this.codecOptions = codecOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getEncoderName() {
|
||||||
|
return encoderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEncoderName(String encoderName) {
|
||||||
|
this.encoderName = encoderName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
|
import android.media.MediaCodecList;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
@ -12,6 +13,8 @@ import android.view.Surface;
|
|||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
@ -26,17 +29,19 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
private final AtomicBoolean rotationChanged = new AtomicBoolean();
|
private final AtomicBoolean rotationChanged = new AtomicBoolean();
|
||||||
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
|
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
|
||||||
|
|
||||||
|
private String encoderName;
|
||||||
private List<CodecOption> codecOptions;
|
private List<CodecOption> codecOptions;
|
||||||
private int bitRate;
|
private int bitRate;
|
||||||
private int maxFps;
|
private int maxFps;
|
||||||
private boolean sendFrameMeta;
|
private boolean sendFrameMeta;
|
||||||
private long ptsOrigin;
|
private long ptsOrigin;
|
||||||
|
|
||||||
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions) {
|
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions, String encoderName) {
|
||||||
this.sendFrameMeta = sendFrameMeta;
|
this.sendFrameMeta = sendFrameMeta;
|
||||||
this.bitRate = bitRate;
|
this.bitRate = bitRate;
|
||||||
this.maxFps = maxFps;
|
this.maxFps = maxFps;
|
||||||
this.codecOptions = codecOptions;
|
this.codecOptions = codecOptions;
|
||||||
|
this.encoderName = encoderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -69,7 +74,7 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
boolean alive;
|
boolean alive;
|
||||||
try {
|
try {
|
||||||
do {
|
do {
|
||||||
MediaCodec codec = createCodec();
|
MediaCodec codec = createCodec(encoderName);
|
||||||
IBinder display = createDisplay();
|
IBinder display = createDisplay();
|
||||||
ScreenInfo screenInfo = device.getScreenInfo();
|
ScreenInfo screenInfo = device.getScreenInfo();
|
||||||
Rect contentRect = screenInfo.getContentRect();
|
Rect contentRect = screenInfo.getContentRect();
|
||||||
@ -150,8 +155,30 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
IO.writeFully(fd, headerBuffer);
|
IO.writeFully(fd, headerBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaCodec createCodec() throws IOException {
|
private static MediaCodecInfo[] listEncoders() {
|
||||||
return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
|
List<MediaCodecInfo> result = new ArrayList<>();
|
||||||
|
MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
||||||
|
for (MediaCodecInfo codecInfo : list.getCodecInfos()) {
|
||||||
|
if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(MediaFormat.MIMETYPE_VIDEO_AVC)) {
|
||||||
|
result.add(codecInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toArray(new MediaCodecInfo[result.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MediaCodec createCodec(String encoderName) throws IOException {
|
||||||
|
if (encoderName != null) {
|
||||||
|
Ln.d("Creating encoder by name: '" + encoderName + "'");
|
||||||
|
try {
|
||||||
|
return MediaCodec.createByCodecName(encoderName);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
MediaCodecInfo[] encoders = listEncoders();
|
||||||
|
throw new InvalidEncoderException(encoderName, encoders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
|
||||||
|
Ln.d("Using encoder: '" + codec.getName() + "'");
|
||||||
|
return codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setCodecOption(MediaFormat format, CodecOption codecOption) {
|
private static void setCodecOption(MediaFormat format, CodecOption codecOption) {
|
||||||
|
@ -4,6 +4,7 @@ import com.genymobile.scrcpy.wrappers.ContentProvider;
|
|||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
|
import android.media.MediaCodecInfo;
|
||||||
import android.os.BatteryManager;
|
import android.os.BatteryManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
@ -54,7 +55,8 @@ public final class Server {
|
|||||||
boolean tunnelForward = options.isTunnelForward();
|
boolean tunnelForward = options.isTunnelForward();
|
||||||
|
|
||||||
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
|
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
|
||||||
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions);
|
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions,
|
||||||
|
options.getEncoderName());
|
||||||
|
|
||||||
if (options.getControl()) {
|
if (options.getControl()) {
|
||||||
final Controller controller = new Controller(device, connection);
|
final Controller controller = new Controller(device, connection);
|
||||||
@ -120,7 +122,7 @@ public final class Server {
|
|||||||
"The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
|
"The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
final int expectedParameters = 14;
|
final int expectedParameters = 15;
|
||||||
if (args.length != expectedParameters) {
|
if (args.length != expectedParameters) {
|
||||||
throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters");
|
throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters");
|
||||||
}
|
}
|
||||||
@ -167,6 +169,9 @@ public final class Server {
|
|||||||
String codecOptions = args[13];
|
String codecOptions = args[13];
|
||||||
options.setCodecOptions(codecOptions);
|
options.setCodecOptions(codecOptions);
|
||||||
|
|
||||||
|
String encoderName = "-".equals(args[14]) ? null : args[14];
|
||||||
|
options.setEncoderName(encoderName);
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,6 +211,15 @@ public final class Server {
|
|||||||
Ln.e(" scrcpy --display " + id);
|
Ln.e(" scrcpy --display " + id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (e instanceof InvalidEncoderException) {
|
||||||
|
InvalidEncoderException iee = (InvalidEncoderException) e;
|
||||||
|
MediaCodecInfo[] encoders = iee.getAvailableEncoders();
|
||||||
|
if (encoders != null && encoders.length > 0) {
|
||||||
|
Ln.e("Try to use one of the available encoders:");
|
||||||
|
for (MediaCodecInfo encoder : encoders) {
|
||||||
|
Ln.e(" scrcpy --encoder-name '" + encoder.getName() + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ public final class Workarounds {
|
|||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public static void prepareMainLooper() {
|
public static void prepareMainLooper() {
|
||||||
// Some devices internally create a Handler when creating an input Surface, causing an exception:
|
// Some devices internally create a Handler when creating an input Surface, causing an exception:
|
||||||
// "Can't create handler inside thread that has not called Looper.prepare()"
|
// "Can't create handler inside thread that has not called Looper.prepare()"
|
||||||
|
@ -35,7 +35,7 @@ public class ContentProvider implements Closeable {
|
|||||||
private final IBinder token;
|
private final IBinder token;
|
||||||
|
|
||||||
private Method callMethod;
|
private Method callMethod;
|
||||||
private boolean callMethodLegacy;
|
private int callMethodVersion;
|
||||||
|
|
||||||
ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) {
|
ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
@ -46,12 +46,20 @@ public class ContentProvider implements Closeable {
|
|||||||
|
|
||||||
private Method getCallMethod() throws NoSuchMethodException {
|
private Method getCallMethod() throws NoSuchMethodException {
|
||||||
if (callMethod == null) {
|
if (callMethod == null) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
callMethod = provider.getClass()
|
||||||
|
.getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
|
||||||
|
callMethodVersion = 0;
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// old versions
|
||||||
try {
|
try {
|
||||||
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
|
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
|
||||||
} catch (NoSuchMethodException e) {
|
callMethodVersion = 1;
|
||||||
// old version
|
} catch (NoSuchMethodException e2) {
|
||||||
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
|
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
|
||||||
callMethodLegacy = true;
|
callMethodVersion = 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return callMethod;
|
return callMethod;
|
||||||
@ -61,10 +69,16 @@ public class ContentProvider implements Closeable {
|
|||||||
try {
|
try {
|
||||||
Method method = getCallMethod();
|
Method method = getCallMethod();
|
||||||
Object[] args;
|
Object[] args;
|
||||||
if (!callMethodLegacy) {
|
switch (callMethodVersion) {
|
||||||
|
case 0:
|
||||||
|
args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras};
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
|
args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
|
||||||
} else {
|
break;
|
||||||
|
default:
|
||||||
args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras};
|
args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras};
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return (Bundle) method.invoke(provider, args);
|
return (Bundle) method.invoke(provider, args);
|
||||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
Reference in New Issue
Block a user