Compare commits

...

10 Commits

Author SHA1 Message Date
c29ecd0314 Rename --display-buffer to --video-buffer
For consistency with --audio-buffer, rename --display-buffer to
--video-buffer.

Fixes #5403 <https://github.com/Genymobile/scrcpy/issues/5403>
PR #5420 <https://github.com/Genymobile/scrcpy/pull/5420>
2024-10-31 19:57:52 +01:00
d62fa8880e Disable broken options on Android 14
The options --lock-video-orientation and --crop are broken since Android
14. Hopefully, they will be reimplemented differently.

Meanwhile, when running Android >= 14, fail with an error to prevent
incorrect behavior.

Refs #4011 <https://github.com/Genymobile/scrcpy/issues/4011>
Refs #4162 <https://github.com/Genymobile/scrcpy/issues/4162>
PR #5417 <https://github.com/Genymobile/scrcpy/pull/5417>
2024-10-31 19:55:47 +01:00
1f6634ea87 Document adb shell settings commands
Some scrcpy features change Android settings with `adb shell settings`.
Document the commands to execute manually.
2024-10-30 22:23:53 +01:00
58ba00fa06 Adapt "turn screen off" for Android 15
Android 15 introduced an easy way to set the display power:
<fd8b5efc7f%5E!/#F17>

Refs #3927 <https://github.com/Genymobile/scrcpy/issues/3927>
Refs <https://issuetracker.google.com/issues/303565669>
PR #5418 <https://github.com/Genymobile/scrcpy/pull/5418>
2024-10-30 19:38:35 +01:00
569c37cec1 Disable display power for virtual displays
If displayId == Device.DISPLAY_ID_NONE, then the display is virtual: its
power mode cannot be changed.

PR #5418 <https://github.com/Genymobile/scrcpy/pull/5418>
2024-10-30 19:38:07 +01:00
58a0fbbf2e Refactor display power mode
Accept a single boolean "on" rather than a "mode" (which, in practice,
could only take 2 values: NORMAL and OFF).

Also rename "screen power mode" to "display power".

PR #5418 <https://github.com/Genymobile/scrcpy/pull/5418>
2024-10-30 19:38:04 +01:00
acff5b005c Add more details to --list-encoders output
Add more information about each codec (hw/sw, vendor, alias).

Before:

    [server] INFO: List of video encoders:
        --video-codec=h264 --video-encoder=c2.exynos.h264.encoder
        --video-codec=h264 --video-encoder=c2.android.avc.encoder
        --video-codec=h264 --video-encoder=OMX.google.h264.encoder
        --video-codec=h265 --video-encoder=c2.exynos.hevc.encoder
        --video-codec=h265 --video-encoder=c2.android.hevc.encoder
        --video-codec=av1 --video-encoder=c2.google.av1.encoder
        --video-codec=av1 --video-encoder=c2.android.av1.encoder
    // audio encoders omitted

After:

    [server] INFO: List of video encoders:
        --video-codec=h264 --video-encoder=c2.exynos.h264.encoder         (hw) [vendor]
        --video-codec=h264 --video-encoder=c2.android.avc.encoder         (sw)
        --video-codec=h264 --video-encoder=OMX.google.h264.encoder        (sw) (alias for c2.android.avc.encoder)
        --video-codec=h265 --video-encoder=c2.exynos.hevc.encoder         (hw) [vendor]
        --video-codec=h265 --video-encoder=c2.android.hevc.encoder        (sw)
        --video-codec=av1 --video-encoder=c2.google.av1.encoder           (hw) [vendor]
        --video-codec=av1 --video-encoder=c2.android.av1.encoder          (sw)
    // audio encoders omitted

PR #5416 <https://github.com/Genymobile/scrcpy/pull/5416>
2024-10-30 18:51:00 +01:00
5474ae6bd6 Factorize codec info listing
Make the listing of video and audio encoders share the same code.

PR #5416 <https://github.com/Genymobile/scrcpy/pull/5416>
2024-10-30 18:50:47 +01:00
2c25fd7a80 Disable mouse by default if no video playback
If video playback is disabled, then SDK mouse (which uses absolute
positions) could not be used, so the default mouse mode was
automatically switched to UHID.

But UHID does not work on all devices, so it could make the whole scrcpy
session fail.

Instead, disable the mouse by default. It is still possible to pass -M
or --mouse=uhid to enable it explicitly.

Fixes #5410 <https://github.com/Genymobile/scrcpy/issues/5410>
2024-10-29 18:59:29 +01:00
ce21f515e3 Remove unnecessary '\n' in log 2024-10-29 18:58:54 +01:00
27 changed files with 264 additions and 198 deletions

View File

@ -20,7 +20,6 @@ _scrcpy() {
--crop= --crop=
-d --select-usb -d --select-usb
--disable-screensaver --disable-screensaver
--display-buffer=
--display-id= --display-id=
--display-orientation= --display-orientation=
-e --select-tcpip -e --select-tcpip
@ -90,6 +89,7 @@ _scrcpy() {
--v4l2-sink= --v4l2-sink=
-v --version -v --version
-V --verbosity= -V --verbosity=
--video-buffer=
--video-codec= --video-codec=
--video-codec-options= --video-codec-options=
--video-encoder= --video-encoder=
@ -191,7 +191,6 @@ _scrcpy() {
|--camera-size \ |--camera-size \
|--crop \ |--crop \
|--display-id \ |--display-id \
|--display-buffer \
|--max-fps \ |--max-fps \
|-m|--max-size \ |-m|--max-size \
|-p|--port \ |-p|--port \
@ -201,6 +200,7 @@ _scrcpy() {
|--tunnel-port \ |--tunnel-port \
|--v4l2-buffer \ |--v4l2-buffer \
|--v4l2-sink \ |--v4l2-sink \
|--video-buffer \
|--video-codec-options \ |--video-codec-options \
|--video-encoder \ |--video-encoder \
|--tcpip \ |--tcpip \

View File

@ -27,7 +27,6 @@ arguments=(
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
{-d,--select-usb}'[Use USB device]' {-d,--select-usb}'[Use USB device]'
'--disable-screensaver[Disable screensaver while scrcpy is running]' '--disable-screensaver[Disable screensaver while scrcpy is running]'
'--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]'
'--display-id=[Specify the display id to mirror]' '--display-id=[Specify the display id to mirror]'
'--display-orientation=[Set the initial display orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)' '--display-orientation=[Set the initial display orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
{-e,--select-tcpip}'[Use TCP/IP device]' {-e,--select-tcpip}'[Use TCP/IP device]'
@ -92,6 +91,7 @@ arguments=(
'--v4l2-sink=[\[\/dev\/videoN\] Output to v4l2loopback device]' '--v4l2-sink=[\[\/dev\/videoN\] Output to v4l2loopback device]'
{-v,--version}'[Print the version of scrcpy]' {-v,--version}'[Print the version of scrcpy]'
{-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)' {-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)'
'--video-buffer=[Add a buffering delay \(in milliseconds\) before displaying video frames]'
'--video-codec=[Select the video codec]:codec:(h264 h265 av1)' '--video-codec=[Select the video codec]:codec:(h264 h265 av1)'
'--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]' '--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]'
'--video-encoder=[Use a specific MediaCodec video encoder]' '--video-encoder=[Use a specific MediaCodec video encoder]'

View File

@ -139,12 +139,6 @@ Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
.BI "\-\-disable\-screensaver" .BI "\-\-disable\-screensaver"
Disable screensaver while scrcpy is running. Disable screensaver while scrcpy is running.
.TP
.BI "\-\-display\-buffer " ms
Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter.
Default is 0 (no buffering).
.TP .TP
.BI "\-\-display\-id " id .BI "\-\-display\-id " id
Specify the device display id to mirror. Specify the device display id to mirror.
@ -560,7 +554,15 @@ It requires to lock the video orientation (see \fB\-\-lock\-video\-orientation\f
.BI "\-\-v4l2-buffer " ms .BI "\-\-v4l2-buffer " ms
Add a buffering delay (in milliseconds) before pushing frames. This increases latency to compensate for jitter. Add a buffering delay (in milliseconds) before pushing frames. This increases latency to compensate for jitter.
This option is similar to \fB\-\-display\-buffer\fR, but specific to V4L2 sink. This option is similar to \fB\-\-video\-buffer\fR, but specific to V4L2 sink.
Default is 0 (no buffering).
.TP
.BI "\-\-video\-buffer " ms
Add a buffering delay (in milliseconds) before displaying video frames.
This increases latency to compensate for jitter.
Default is 0 (no buffering). Default is 0 (no buffering).

View File

@ -50,6 +50,7 @@ enum {
OPT_POWER_OFF_ON_CLOSE, OPT_POWER_OFF_ON_CLOSE,
OPT_V4L2_SINK, OPT_V4L2_SINK,
OPT_DISPLAY_BUFFER, OPT_DISPLAY_BUFFER,
OPT_VIDEO_BUFFER,
OPT_V4L2_BUFFER, OPT_V4L2_BUFFER,
OPT_TUNNEL_HOST, OPT_TUNNEL_HOST,
OPT_TUNNEL_PORT, OPT_TUNNEL_PORT,
@ -321,12 +322,10 @@ static const struct sc_option options[] = {
.argdesc = "id", .argdesc = "id",
}, },
{ {
// deprecated
.longopt_id = OPT_DISPLAY_BUFFER, .longopt_id = OPT_DISPLAY_BUFFER,
.longopt = "display-buffer", .longopt = "display-buffer",
.argdesc = "ms", .argdesc = "ms",
.text = "Add a buffering delay (in milliseconds) before displaying. "
"This increases latency to compensate for jitter.\n"
"Default is 0 (no buffering).",
}, },
{ {
.longopt_id = OPT_DISPLAY_ID, .longopt_id = OPT_DISPLAY_ID,
@ -898,11 +897,20 @@ static const struct sc_option options[] = {
.argdesc = "ms", .argdesc = "ms",
.text = "Add a buffering delay (in milliseconds) before pushing " .text = "Add a buffering delay (in milliseconds) before pushing "
"frames. This increases latency to compensate for jitter.\n" "frames. This increases latency to compensate for jitter.\n"
"This option is similar to --display-buffer, but specific to " "This option is similar to --video-buffer, but specific to "
"V4L2 sink.\n" "V4L2 sink.\n"
"Default is 0 (no buffering).\n" "Default is 0 (no buffering).\n"
"This option is only available on Linux.", "This option is only available on Linux.",
}, },
{
.longopt_id = OPT_VIDEO_BUFFER,
.longopt = "video-buffer",
.argdesc = "ms",
.text = "Add a buffering delay (in milliseconds) before displaying "
"video frames.\n"
"This increases latency to compensate for jitter.\n"
"Default is 0 (no buffering).",
},
{ {
.longopt_id = OPT_VIDEO_CODEC, .longopt_id = OPT_VIDEO_CODEC,
.longopt = "video-codec", .longopt = "video-codec",
@ -2549,7 +2557,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->power_off_on_close = true; opts->power_off_on_close = true;
break; break;
case OPT_DISPLAY_BUFFER: case OPT_DISPLAY_BUFFER:
if (!parse_buffering_time(optarg, &opts->display_buffer)) { LOGW("--display-buffer is deprecated, use --video-buffer "
"instead.");
// fall through
case OPT_VIDEO_BUFFER:
if (!parse_buffering_time(optarg, &opts->video_buffer)) {
return false; return false;
} }
break; break;
@ -2812,7 +2824,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
LOGI("Video orientation is locked for v4l2 sink. " LOGI("Video orientation is locked for v4l2 sink. "
"See --lock-video-orientation."); "See --lock-video-orientation.");
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL; opts->lock_video_orientation =
SC_LOCK_VIDEO_ORIENTATION_INITIAL_AUTO;
} }
// V4L2 could not handle size change. // V4L2 could not handle size change.
@ -2822,7 +2835,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
if (opts->v4l2_buffer && !opts->v4l2_device) { if (opts->v4l2_buffer && !opts->v4l2_device) {
LOGE("V4L2 buffer value without V4L2 sink\n"); LOGE("V4L2 buffer value without V4L2 sink");
return false; return false;
} }
#endif #endif
@ -2841,8 +2854,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
if (otg) { if (otg) {
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA; opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
} else if (!opts->video_playback) { } else if (!opts->video_playback) {
LOGI("No video mirroring, mouse mode switched to UHID"); LOGI("No video mirroring, SDK mouse disabled");
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID; opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_DISABLED;
} else { } else {
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK; opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK;
} }

View File

@ -22,9 +22,6 @@
#define MOTIONEVENT_ACTION_LABEL(value) \ #define MOTIONEVENT_ACTION_LABEL(value) \
ENUM_TO_LABEL(android_motionevent_action_labels, value) ENUM_TO_LABEL(android_motionevent_action_labels, value)
#define SCREEN_POWER_MODE_LABEL(value) \
ENUM_TO_LABEL(screen_power_mode_labels, value)
static const char *const android_keyevent_action_labels[] = { static const char *const android_keyevent_action_labels[] = {
"down", "down",
"up", "up",
@ -47,14 +44,6 @@ static const char *const android_motionevent_action_labels[] = {
"btn-release", "btn-release",
}; };
static const char *const screen_power_mode_labels[] = {
"off",
"doze",
"normal",
"doze-suspend",
"suspend",
};
static const char *const copy_key_labels[] = { static const char *const copy_key_labels[] = {
"none", "none",
"copy", "copy",
@ -158,8 +147,8 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
size_t len = write_string(&buf[10], msg->set_clipboard.text, size_t len = write_string(&buf[10], msg->set_clipboard.text,
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH); SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
return 10 + len; return 10 + len;
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: case SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER:
buf[1] = msg->set_screen_power_mode.mode; buf[1] = msg->set_display_power.on;
return 2; return 2;
case SC_CONTROL_MSG_TYPE_UHID_CREATE: case SC_CONTROL_MSG_TYPE_UHID_CREATE:
sc_write16be(&buf[1], msg->uhid_create.id); sc_write16be(&buf[1], msg->uhid_create.id);
@ -268,9 +257,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
msg->set_clipboard.paste ? "paste" : "nopaste", msg->set_clipboard.paste ? "paste" : "nopaste",
msg->set_clipboard.text); msg->set_clipboard.text);
break; break;
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: case SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER:
LOG_CMSG("power mode %s", LOG_CMSG("display power %s",
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode)); msg->set_display_power.on ? "on" : "off");
break; break;
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
LOG_CMSG("expand notification panel"); LOG_CMSG("expand notification panel");

View File

@ -35,7 +35,7 @@ enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS, SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
SC_CONTROL_MSG_TYPE_GET_CLIPBOARD, SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD, SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER,
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE, SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
SC_CONTROL_MSG_TYPE_UHID_CREATE, SC_CONTROL_MSG_TYPE_UHID_CREATE,
SC_CONTROL_MSG_TYPE_UHID_INPUT, SC_CONTROL_MSG_TYPE_UHID_INPUT,
@ -44,12 +44,6 @@ enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_START_APP, SC_CONTROL_MSG_TYPE_START_APP,
}; };
enum sc_screen_power_mode {
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
SC_SCREEN_POWER_MODE_OFF = 0,
SC_SCREEN_POWER_MODE_NORMAL = 2,
};
enum sc_copy_key { enum sc_copy_key {
SC_COPY_KEY_NONE, SC_COPY_KEY_NONE,
SC_COPY_KEY_COPY, SC_COPY_KEY_COPY,
@ -95,8 +89,8 @@ struct sc_control_msg {
bool paste; bool paste;
} set_clipboard; } set_clipboard;
struct { struct {
enum sc_screen_power_mode mode; bool on;
} set_screen_power_mode; } set_display_power;
struct { struct {
uint16_t id; uint16_t id;
const char *name; // pointer to static data const char *name; // pointer to static data

View File

@ -203,13 +203,12 @@ set_device_clipboard(struct sc_input_manager *im, bool paste,
} }
static void static void
set_screen_power_mode(struct sc_input_manager *im, set_display_power(struct sc_input_manager *im, bool on) {
enum sc_screen_power_mode mode) {
assert(im->controller); assert(im->controller);
struct sc_control_msg msg; struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.type = SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER;
msg.set_screen_power_mode.mode = mode; msg.set_display_power.on = on;
if (!sc_controller_push_msg(im->controller, &msg)) { if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'set screen power mode'"); LOGW("Could not request 'set screen power mode'");
@ -415,10 +414,8 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return; return;
case SDLK_o: case SDLK_o:
if (control && !repeat && down && !paused) { if (control && !repeat && down && !paused) {
enum sc_screen_power_mode mode = shift bool on = shift;
? SC_SCREEN_POWER_MODE_NORMAL set_display_power(im, on);
: SC_SCREEN_POWER_MODE_OFF;
set_screen_power_mode(im, mode);
} }
return; return;
case SDLK_z: case SDLK_z:

View File

@ -58,7 +58,7 @@ const struct scrcpy_options scrcpy_options_default = {
.window_width = 0, .window_width = 0,
.window_height = 0, .window_height = 0,
.display_id = 0, .display_id = 0,
.display_buffer = 0, .video_buffer = 0,
.audio_buffer = -1, // depends on the audio format, .audio_buffer = -1, // depends on the audio format,
.audio_output_buffer = SC_TICK_FROM_MS(5), .audio_output_buffer = SC_TICK_FROM_MS(5),
.time_limit = 0, .time_limit = 0,

View File

@ -134,6 +134,8 @@ enum sc_lock_video_orientation {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1, SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts // lock the current orientation when scrcpy starts
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2, SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
// like SC_LOCK_VIDEO_ORIENTATION_INITIAL, but set automatically
SC_LOCK_VIDEO_ORIENTATION_INITIAL_AUTO = -3,
SC_LOCK_VIDEO_ORIENTATION_0 = 0, SC_LOCK_VIDEO_ORIENTATION_0 = 0,
SC_LOCK_VIDEO_ORIENTATION_90 = 3, SC_LOCK_VIDEO_ORIENTATION_90 = 3,
SC_LOCK_VIDEO_ORIENTATION_180 = 2, SC_LOCK_VIDEO_ORIENTATION_180 = 2,
@ -259,7 +261,7 @@ struct scrcpy_options {
uint16_t window_width; uint16_t window_width;
uint16_t window_height; uint16_t window_height;
uint32_t display_id; uint32_t display_id;
sc_tick display_buffer; sc_tick video_buffer;
sc_tick audio_buffer; sc_tick audio_buffer;
sc_tick audio_output_buffer; sc_tick audio_output_buffer;
sc_tick time_limit; sc_tick time_limit;

View File

@ -53,7 +53,7 @@ struct scrcpy {
struct sc_decoder video_decoder; struct sc_decoder video_decoder;
struct sc_decoder audio_decoder; struct sc_decoder audio_decoder;
struct sc_recorder recorder; struct sc_recorder recorder;
struct sc_delay_buffer display_buffer; struct sc_delay_buffer video_buffer;
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
struct sc_v4l2_sink v4l2_sink; struct sc_v4l2_sink v4l2_sink;
struct sc_delay_buffer v4l2_buffer; struct sc_delay_buffer v4l2_buffer;
@ -815,11 +815,11 @@ aoa_complete:
if (options->video_playback) { if (options->video_playback) {
struct sc_frame_source *src = &s->video_decoder.frame_source; struct sc_frame_source *src = &s->video_decoder.frame_source;
if (options->display_buffer) { if (options->video_buffer) {
sc_delay_buffer_init(&s->display_buffer, sc_delay_buffer_init(&s->video_buffer,
options->display_buffer, true); options->video_buffer, true);
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink); sc_frame_source_add_sink(src, &s->video_buffer.frame_sink);
src = &s->display_buffer.frame_source; src = &s->video_buffer.frame_source;
} }
sc_frame_source_add_sink(src, &s->screen.frame_sink); sc_frame_source_add_sink(src, &s->screen.frame_sink);
@ -873,11 +873,11 @@ aoa_complete:
// everything is set up // everything is set up
if (options->control && options->turn_screen_off) { if (options->control && options->turn_screen_off) {
struct sc_control_msg msg; struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE; msg.type = SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER;
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF; msg.set_display_power.on = false;
if (!sc_controller_push_msg(&s->controller, &msg)) { if (!sc_controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'"); LOGW("Could not request 'set display power'");
} }
} }

View File

@ -289,11 +289,11 @@ static void test_serialize_set_clipboard_long(void) {
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
static void test_serialize_set_screen_power_mode(void) { static void test_serialize_set_display_power(void) {
struct sc_control_msg msg = { struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, .type = SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER,
.set_screen_power_mode = { .set_display_power = {
.mode = SC_SCREEN_POWER_MODE_NORMAL, .on = true,
}, },
}; };
@ -302,8 +302,8 @@ static void test_serialize_set_screen_power_mode(void) {
assert(size == 2); assert(size == 2);
const uint8_t expected[] = { const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER,
0x02, // SC_SCREEN_POWER_MODE_NORMAL 0x01, // true
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
@ -423,7 +423,7 @@ int main(int argc, char *argv[]) {
test_serialize_get_clipboard(); test_serialize_get_clipboard();
test_serialize_set_clipboard(); test_serialize_set_clipboard();
test_serialize_set_clipboard_long(); test_serialize_set_clipboard_long();
test_serialize_set_screen_power_mode(); test_serialize_set_display_power();
test_serialize_rotate_device(); test_serialize_rotate_device();
test_serialize_uhid_create(); test_serialize_uhid_create();
test_serialize_uhid_input(); test_serialize_uhid_input();

View File

@ -170,7 +170,7 @@ latency (for both [video](video.md#buffering) and audio) might be preferable to
avoid glitches and smooth the playback: avoid glitches and smooth the playback:
``` ```
scrcpy --display-buffer=200 --audio-buffer=200 scrcpy --video-buffer=200 --audio-buffer=200
``` ```
It is also possible to configure another audio buffer (the audio output buffer), It is also possible to configure another audio buffer (the audio output buffer),

View File

@ -21,9 +21,9 @@ the client and on the server.
If video is enabled, then the server sends a raw video stream (H.264 by default) If video is enabled, then the server sends a raw video stream (H.264 by default)
of the device screen, with some additional headers for each packet. The client of the device screen, with some additional headers for each packet. The client
decodes the video frames, and displays them as soon as possible, without decodes the video frames, and displays them as soon as possible, without
buffering (unless `--display-buffer=delay` is specified) to minimize latency. buffering (unless `--video-buffer=delay` is specified) to minimize latency. The
The client is not aware of the device rotation (which is handled by the server), client is not aware of the device rotation (which is handled by the server), it
it just knows the dimensions of the video frames it receives. just knows the dimensions of the video frames it receives.
Similarly, if audio is enabled, then the server sends a raw audio stream (OPUS Similarly, if audio is enabled, then the server sends a raw audio stream (OPUS
by default) of the device audio output (or the microphone if by default) of the device audio output (or the microphone if

View File

@ -18,6 +18,21 @@ The initial state is restored when _scrcpy_ is closed.
If the device is not plugged in (i.e. only connected over TCP/IP), 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).
This changes the value of [`stay_on_while_plugged_in`], setting which can be
changed manually:
[`stay_on_while_plugged_in`]: https://developer.android.com/reference/android/provider/Settings.Global#STAY_ON_WHILE_PLUGGED_IN
```bash
# get the current show_touches value
adb shell settings get global stay_on_while_plugged_in
# enable for AC/USB/wireless chargers
adb shell settings put global stay_on_while_plugged_in 7
# disable
adb shell settings put global stay_on_while_plugged_in 0
```
## Turn screen off ## Turn screen off
@ -46,6 +61,15 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw # short version scrcpy -Sw # short version
``` ```
Since Android 15, it is possible to change this setting manually:
```
# turn screen off (0 for main display)
adb shell cmd display power-off 0
# turn screen on
adb shell cmd display power-on 0
```
## Show touches ## Show touches
@ -62,6 +86,16 @@ 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).
It is possible to change this setting manually:
```bash
# get the current show_touches value
adb shell settings get system show_touches
# enable show_touches
adb shell settings put system show_touches 1
# disable show_touches
adb shell settings put system show_touches 0
```
## Power off on close ## Power off on close

View File

@ -189,15 +189,15 @@ The configuration is available independently for the display,
[v4l2 sinks](video.md#video4linux) and [audio](audio.md#buffering) playback. [v4l2 sinks](video.md#video4linux) and [audio](audio.md#buffering) playback.
```bash ```bash
scrcpy --display-buffer=50 # add 50ms buffering for display scrcpy --video-buffer=50 # add 50ms buffering for video playback
scrcpy --v4l2-buffer=300 # add 300ms buffering for v4l2 sink
scrcpy --audio-buffer=200 # set 200ms buffering for audio playback scrcpy --audio-buffer=200 # set 200ms buffering for audio playback
scrcpy --v4l2-buffer=300 # add 300ms buffering for v4l2 sink
``` ```
They can be applied simultaneously: They can be applied simultaneously:
```bash ```bash
scrcpy --display-buffer=50 --v4l2-buffer=300 scrcpy --video-buffer=50 --v4l2-buffer=300
``` ```

View File

@ -19,7 +19,7 @@ public final class CleanUp {
private static final int MSG_TYPE_MASK = 0b11; private static final int MSG_TYPE_MASK = 0b11;
private static final int MSG_TYPE_RESTORE_STAY_ON = 0; private static final int MSG_TYPE_RESTORE_STAY_ON = 0;
private static final int MSG_TYPE_DISABLE_SHOW_TOUCHES = 1; private static final int MSG_TYPE_DISABLE_SHOW_TOUCHES = 1;
private static final int MSG_TYPE_RESTORE_NORMAL_POWER_MODE = 2; private static final int MSG_TYPE_RESTORE_DISPLAY_POWER = 2;
private static final int MSG_TYPE_POWER_OFF_SCREEN = 3; private static final int MSG_TYPE_POWER_OFF_SCREEN = 3;
private static final int MSG_PARAM_SHIFT = 2; private static final int MSG_PARAM_SHIFT = 2;
@ -63,8 +63,8 @@ public final class CleanUp {
return sendMessage(MSG_TYPE_DISABLE_SHOW_TOUCHES, disableOnExit ? 1 : 0); return sendMessage(MSG_TYPE_DISABLE_SHOW_TOUCHES, disableOnExit ? 1 : 0);
} }
public boolean setRestoreNormalPowerMode(boolean restoreOnExit) { public boolean setRestoreDisplayPower(boolean restoreOnExit) {
return sendMessage(MSG_TYPE_RESTORE_NORMAL_POWER_MODE, restoreOnExit ? 1 : 0); return sendMessage(MSG_TYPE_RESTORE_DISPLAY_POWER, restoreOnExit ? 1 : 0);
} }
public boolean setPowerOffScreen(boolean powerOffScreenOnExit) { public boolean setPowerOffScreen(boolean powerOffScreenOnExit) {
@ -86,7 +86,7 @@ public final class CleanUp {
int restoreStayOn = -1; int restoreStayOn = -1;
boolean disableShowTouches = false; boolean disableShowTouches = false;
boolean restoreNormalPowerMode = false; boolean restoreDisplayPower = false;
boolean powerOffScreen = false; boolean powerOffScreen = false;
try { try {
@ -102,8 +102,8 @@ public final class CleanUp {
case MSG_TYPE_DISABLE_SHOW_TOUCHES: case MSG_TYPE_DISABLE_SHOW_TOUCHES:
disableShowTouches = param != 0; disableShowTouches = param != 0;
break; break;
case MSG_TYPE_RESTORE_NORMAL_POWER_MODE: case MSG_TYPE_RESTORE_DISPLAY_POWER:
restoreNormalPowerMode = param != 0; restoreDisplayPower = param != 0;
break; break;
case MSG_TYPE_POWER_OFF_SCREEN: case MSG_TYPE_POWER_OFF_SCREEN:
powerOffScreen = param != 0; powerOffScreen = param != 0;
@ -137,15 +137,13 @@ public final class CleanUp {
} }
} }
if (Device.isScreenOn()) { if (Device.isScreenOn() && displayId != Device.DISPLAY_ID_NONE) {
if (powerOffScreen) { if (powerOffScreen) {
if (displayId != Device.DISPLAY_ID_NONE) { Ln.i("Power off screen");
Ln.i("Power off screen"); Device.powerOffScreen(displayId);
Device.powerOffScreen(displayId); } else if (restoreDisplayPower) {
} Ln.i("Restoring display power");
} else if (restoreNormalPowerMode) { Device.setDisplayPower(displayId, true);
Ln.i("Restoring normal power mode");
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
} }
} }

View File

@ -2,6 +2,7 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.audio.AudioCodec; import com.genymobile.scrcpy.audio.AudioCodec;
import com.genymobile.scrcpy.audio.AudioSource; import com.genymobile.scrcpy.audio.AudioSource;
import com.genymobile.scrcpy.device.Device;
import com.genymobile.scrcpy.device.NewDisplay; import com.genymobile.scrcpy.device.NewDisplay;
import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.util.CodecOption; import com.genymobile.scrcpy.util.CodecOption;
@ -31,7 +32,7 @@ public class Options {
private int videoBitRate = 8000000; private int videoBitRate = 8000000;
private int audioBitRate = 128000; private int audioBitRate = 128000;
private float maxFps; private float maxFps;
private int lockVideoOrientation = -1; private int lockVideoOrientation = Device.LOCK_VIDEO_ORIENTATION_UNLOCKED;
private boolean tunnelForward; private boolean tunnelForward;
private Rect crop; private Rect crop;
private boolean control = true; private boolean control = true;
@ -253,6 +254,10 @@ public class Options {
return sendCodecMeta; return sendCodecMeta;
} }
public void resetLockVideoOrientation() {
this.lockVideoOrientation = Device.LOCK_VIDEO_ORIENTATION_UNLOCKED;
}
@SuppressWarnings("MethodLength") @SuppressWarnings("MethodLength")
public static Options parse(String... args) { public static Options parse(String... args) {
if (args.length < 1) { if (args.length < 1) {

View File

@ -132,6 +132,23 @@ public final class Server {
throw new ConfigurationException("New virtual display is not supported"); throw new ConfigurationException("New virtual display is not supported");
} }
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
int lockVideoOrientation = options.getLockVideoOrientation();
if (lockVideoOrientation != Device.LOCK_VIDEO_ORIENTATION_UNLOCKED) {
if (lockVideoOrientation != Device.LOCK_VIDEO_ORIENTATION_INITIAL_AUTO) {
Ln.e("--lock-video-orientation is broken on Android >= 14: <https://github.com/Genymobile/scrcpy/issues/4011>");
throw new ConfigurationException("--lock-video-orientation is broken on Android >= 14");
} else {
// If the flag has been set automatically (because v4l2 sink is enabled), do not fail
Ln.w("--lock-video-orientation is ignored on Android >= 14: <https://github.com/Genymobile/scrcpy/issues/4011>");
}
}
if (options.getCrop() != null) {
Ln.e("--crop is broken on Android >= 14: <https://github.com/Genymobile/scrcpy/issues/4162>");
throw new ConfigurationException("Crop is not broken on Android >= 14");
}
}
CleanUp cleanUp = null; CleanUp cleanUp = null;
Thread initThread = null; Thread initThread = null;

View File

@ -17,7 +17,7 @@ public final class ControlMessage {
public static final int TYPE_COLLAPSE_PANELS = 7; public static final int TYPE_COLLAPSE_PANELS = 7;
public static final int TYPE_GET_CLIPBOARD = 8; public static final int TYPE_GET_CLIPBOARD = 8;
public static final int TYPE_SET_CLIPBOARD = 9; public static final int TYPE_SET_CLIPBOARD = 9;
public static final int TYPE_SET_SCREEN_POWER_MODE = 10; public static final int TYPE_SET_DISPLAY_POWER = 10;
public static final int TYPE_ROTATE_DEVICE = 11; public static final int TYPE_ROTATE_DEVICE = 11;
public static final int TYPE_UHID_CREATE = 12; public static final int TYPE_UHID_CREATE = 12;
public static final int TYPE_UHID_INPUT = 13; public static final int TYPE_UHID_INPUT = 13;
@ -34,7 +34,7 @@ public final class ControlMessage {
private int type; private int type;
private String text; private String text;
private int metaState; // KeyEvent.META_* private int metaState; // KeyEvent.META_*
private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_* private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_*
private int keycode; // KeyEvent.KEYCODE_* private int keycode; // KeyEvent.KEYCODE_*
private int actionButton; // MotionEvent.BUTTON_* private int actionButton; // MotionEvent.BUTTON_*
private int buttons; // MotionEvent.BUTTON_* private int buttons; // MotionEvent.BUTTON_*
@ -49,6 +49,7 @@ public final class ControlMessage {
private long sequence; private long sequence;
private int id; private int id;
private byte[] data; private byte[] data;
private boolean on;
private ControlMessage() { private ControlMessage() {
} }
@ -116,13 +117,10 @@ public final class ControlMessage {
return msg; return msg;
} }
/** public static ControlMessage createSetDisplayPower(boolean on) {
* @param mode one of the {@code Device.SCREEN_POWER_MODE_*} constants
*/
public static ControlMessage createSetScreenPowerMode(int mode) {
ControlMessage msg = new ControlMessage(); ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_SCREEN_POWER_MODE; msg.type = TYPE_SET_DISPLAY_POWER;
msg.action = mode; msg.on = on;
return msg; return msg;
} }
@ -234,4 +232,8 @@ public final class ControlMessage {
public byte[] getData() { public byte[] getData() {
return data; return data;
} }
public boolean getOn() {
return on;
}
} }

View File

@ -39,8 +39,8 @@ public class ControlMessageReader {
return parseGetClipboard(); return parseGetClipboard();
case ControlMessage.TYPE_SET_CLIPBOARD: case ControlMessage.TYPE_SET_CLIPBOARD:
return parseSetClipboard(); return parseSetClipboard();
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: case ControlMessage.TYPE_SET_DISPLAY_POWER:
return parseSetScreenPowerMode(); return parseSetDisplayPower();
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL: case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
case ControlMessage.TYPE_COLLAPSE_PANELS: case ControlMessage.TYPE_COLLAPSE_PANELS:
@ -134,9 +134,9 @@ public class ControlMessageReader {
return ControlMessage.createSetClipboard(sequence, text, paste); return ControlMessage.createSetClipboard(sequence, text, paste);
} }
private ControlMessage parseSetScreenPowerMode() throws IOException { private ControlMessage parseSetDisplayPower() throws IOException {
int mode = dis.readUnsignedByte(); boolean on = dis.readBoolean();
return ControlMessage.createSetScreenPowerMode(mode); return ControlMessage.createSetDisplayPower(on);
} }
private ControlMessage parseUhidCreate() throws IOException { private ControlMessage parseUhidCreate() throws IOException {

View File

@ -91,7 +91,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS]; private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS]; private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
private boolean keepPowerModeOff; private boolean keepDisplayPowerOff;
public Controller(int displayId, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) { public Controller(int displayId, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) {
this.displayId = displayId; this.displayId = displayId;
@ -270,16 +270,16 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
case ControlMessage.TYPE_SET_CLIPBOARD: case ControlMessage.TYPE_SET_CLIPBOARD:
setClipboard(msg.getText(), msg.getPaste(), msg.getSequence()); setClipboard(msg.getText(), msg.getPaste(), msg.getSequence());
break; break;
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: case ControlMessage.TYPE_SET_DISPLAY_POWER:
if (supportsInputEvents) { if (supportsInputEvents && displayId != Device.DISPLAY_ID_NONE) {
int mode = msg.getAction(); boolean on = msg.getOn();
boolean setPowerModeOk = Device.setScreenPowerMode(mode); boolean setDisplayPowerOk = Device.setDisplayPower(displayId, on);
if (setPowerModeOk) { if (setDisplayPowerOk) {
keepPowerModeOff = mode == Device.POWER_MODE_OFF; keepDisplayPowerOff = !on;
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); Ln.i("Device display turned " + (on ? "on" : "off"));
if (cleanUp != null) { if (cleanUp != null) {
boolean mustRestoreOnExit = mode != Device.POWER_MODE_NORMAL; boolean mustRestoreOnExit = !on;
cleanUp.setRestoreNormalPowerMode(mustRestoreOnExit); cleanUp.setRestoreDisplayPower(mustRestoreOnExit);
} }
} }
} }
@ -310,8 +310,9 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
} }
private boolean injectKeycode(int action, int keycode, int repeat, int metaState) { private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
if (keepPowerModeOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) { if (keepDisplayPowerOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) {
schedulePowerModeOff(); assert displayId != Device.DISPLAY_ID_NONE;
scheduleDisplayPowerOff(displayId);
} }
return injectKeyEvent(action, keycode, repeat, metaState, Device.INJECT_MODE_ASYNC); return injectKeyEvent(action, keycode, repeat, metaState, Device.INJECT_MODE_ASYNC);
} }
@ -488,12 +489,12 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
} }
/** /**
* Schedule a call to set power mode to off after a small delay. * Schedule a call to set display power to off after a small delay.
*/ */
private static void schedulePowerModeOff() { private static void scheduleDisplayPowerOff(int displayId) {
EXECUTOR.schedule(() -> { EXECUTOR.schedule(() -> {
Ln.i("Forcing screen off"); Ln.i("Forcing display off");
Device.setScreenPowerMode(Device.POWER_MODE_OFF); Device.setDisplayPower(displayId, false);
}, 200, TimeUnit.MILLISECONDS); }, 200, TimeUnit.MILLISECONDS);
} }
@ -509,8 +510,9 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
return true; return true;
} }
if (keepPowerModeOff) { if (keepDisplayPowerOff) {
schedulePowerModeOff(); assert displayId != Device.DISPLAY_ID_NONE;
scheduleDisplayPowerOff(displayId);
} }
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC); return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
} }

View File

@ -42,6 +42,8 @@ public final class Device {
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1; public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2; public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
// like SC_LOCK_VIDEO_ORIENTATION_INITIAL, but set automatically
public static final int LOCK_VIDEO_ORIENTATION_INITIAL_AUTO = -3;
private Device() { private Device() {
// not instantiable // not instantiable
@ -126,10 +128,13 @@ public final class Device {
return clipboardManager.setText(text); return clipboardManager.setText(text);
} }
/** public static boolean setDisplayPower(int displayId, boolean on) {
* @param mode one of the {@code POWER_MODE_*} constants assert displayId != Device.DISPLAY_ID_NONE;
*/
public static boolean setScreenPowerMode(int mode) { if (Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) {
return ServiceManager.getDisplayManager().requestDisplayPower(displayId, on);
}
boolean applyToMultiPhysicalDisplays = Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10; boolean applyToMultiPhysicalDisplays = Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10;
if (applyToMultiPhysicalDisplays if (applyToMultiPhysicalDisplays
@ -142,6 +147,7 @@ public final class Device {
applyToMultiPhysicalDisplays = false; applyToMultiPhysicalDisplays = false;
} }
int mode = on ? POWER_MODE_NORMAL : POWER_MODE_OFF;
if (applyToMultiPhysicalDisplays) { if (applyToMultiPhysicalDisplays) {
// On Android 14, these internal methods have been moved to DisplayControl // On Android 14, these internal methods have been moved to DisplayControl
boolean useDisplayControl = boolean useDisplayControl =

View File

@ -1,8 +1,5 @@
package com.genymobile.scrcpy.util; package com.genymobile.scrcpy.util;
import com.genymobile.scrcpy.audio.AudioCodec;
import com.genymobile.scrcpy.video.VideoCodec;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo;
import android.media.MediaCodecList; import android.media.MediaCodecList;
import android.media.MediaFormat; import android.media.MediaFormat;
@ -13,24 +10,6 @@ import java.util.List;
public final class CodecUtils { public final class CodecUtils {
public static final class DeviceEncoder {
private final Codec codec;
private final MediaCodecInfo info;
DeviceEncoder(Codec codec, MediaCodecInfo info) {
this.codec = codec;
this.info = info;
}
public Codec getCodec() {
return codec;
}
public MediaCodecInfo getInfo() {
return info;
}
}
private CodecUtils() { private CodecUtils() {
// not instantiable // not instantiable
} }
@ -47,7 +26,7 @@ public final class CodecUtils {
} }
} }
private static MediaCodecInfo[] getEncoders(MediaCodecList codecs, String mimeType) { public static MediaCodecInfo[] getEncoders(MediaCodecList codecs, String mimeType) {
List<MediaCodecInfo> result = new ArrayList<>(); List<MediaCodecInfo> result = new ArrayList<>();
for (MediaCodecInfo codecInfo : codecs.getCodecInfos()) { for (MediaCodecInfo codecInfo : codecs.getCodecInfos()) {
if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) { if (codecInfo.isEncoder() && Arrays.asList(codecInfo.getSupportedTypes()).contains(mimeType)) {
@ -56,26 +35,4 @@ public final class CodecUtils {
} }
return result.toArray(new MediaCodecInfo[result.size()]); return result.toArray(new MediaCodecInfo[result.size()]);
} }
public static List<DeviceEncoder> listVideoEncoders() {
List<DeviceEncoder> encoders = new ArrayList<>();
MediaCodecList codecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (VideoCodec codec : VideoCodec.values()) {
for (MediaCodecInfo info : getEncoders(codecs, codec.getMimeType())) {
encoders.add(new DeviceEncoder(codec, info));
}
}
return encoders;
}
public static List<DeviceEncoder> listAudioEncoders() {
List<DeviceEncoder> encoders = new ArrayList<>();
MediaCodecList codecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (AudioCodec codec : AudioCodec.values()) {
for (MediaCodecInfo info : getEncoders(codecs, codec.getMimeType())) {
encoders.add(new DeviceEncoder(codec, info));
}
}
return encoders;
}
} }

View File

@ -1,19 +1,26 @@
package com.genymobile.scrcpy.util; package com.genymobile.scrcpy.util;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.audio.AudioCodec;
import com.genymobile.scrcpy.device.Device; import com.genymobile.scrcpy.device.Device;
import com.genymobile.scrcpy.device.DeviceApp; import com.genymobile.scrcpy.device.DeviceApp;
import com.genymobile.scrcpy.device.DisplayInfo; import com.genymobile.scrcpy.device.DisplayInfo;
import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.video.VideoCodec;
import com.genymobile.scrcpy.wrappers.DisplayManager; import com.genymobile.scrcpy.wrappers.DisplayManager;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.graphics.Rect; import android.graphics.Rect;
import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraManager;
import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.os.Build;
import android.util.Range; import android.util.Range;
import java.util.Collections; import java.util.Collections;
@ -28,32 +35,54 @@ public final class LogUtils {
// not instantiable // not instantiable
} }
public static String buildVideoEncoderListMessage() { private static String buildEncoderListMessage(String type, Codec[] codecs) {
StringBuilder builder = new StringBuilder("List of video encoders:"); StringBuilder builder = new StringBuilder("List of ").append(type).append(" encoders:");
List<CodecUtils.DeviceEncoder> videoEncoders = CodecUtils.listVideoEncoders(); MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
if (videoEncoders.isEmpty()) { for (Codec codec : codecs) {
builder.append("\n (none)"); MediaCodecInfo[] encoders = CodecUtils.getEncoders(codecList, codec.getMimeType());
} else { for (MediaCodecInfo info : encoders) {
for (CodecUtils.DeviceEncoder encoder : videoEncoders) { int lineStart = builder.length();
builder.append("\n --video-codec=").append(encoder.getCodec().getName()); builder.append("\n --").append(type).append("-codec=").append(codec.getName());
builder.append(" --video-encoder=").append(encoder.getInfo().getName()); builder.append(" --").append(type).append("-encoder=").append(info.getName());
if (Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10) {
int lineLength = builder.length() - lineStart;
final int column = 70;
if (lineLength < column) {
int padding = column - lineLength;
builder.append(String.format("%" + padding + "s", " "));
}
builder.append(" (").append(getHwCodecType(info)).append(')');
if (info.isVendor()) {
builder.append(" [vendor]");
}
if (info.isAlias()) {
builder.append(" (alias for ").append(info.getCanonicalName()).append(')');
}
}
} }
} }
return builder.toString(); return builder.toString();
} }
public static String buildVideoEncoderListMessage() {
return buildEncoderListMessage("video", VideoCodec.values());
}
public static String buildAudioEncoderListMessage() { public static String buildAudioEncoderListMessage() {
StringBuilder builder = new StringBuilder("List of audio encoders:"); return buildEncoderListMessage("audio", AudioCodec.values());
List<CodecUtils.DeviceEncoder> audioEncoders = CodecUtils.listAudioEncoders(); }
if (audioEncoders.isEmpty()) {
builder.append("\n (none)"); @TargetApi(AndroidVersions.API_29_ANDROID_10)
} else { private static String getHwCodecType(MediaCodecInfo info) {
for (CodecUtils.DeviceEncoder encoder : audioEncoders) { if (info.isSoftwareOnly()) {
builder.append("\n --audio-codec=").append(encoder.getCodec().getName()); return "sw";
builder.append(" --audio-encoder=").append(encoder.getInfo().getName());
}
} }
return builder.toString(); if (info.isHardwareAccelerated()) {
return "hw";
}
return "hybrid";
} }
public static String buildDisplayListMessage() { public static String buildDisplayListMessage() {

View File

@ -64,7 +64,7 @@ public final class ScreenInfo {
} }
public static ScreenInfo computeScreenInfo(int rotation, Size deviceSize, Rect crop, int maxSize, int lockedVideoOrientation) { public static ScreenInfo computeScreenInfo(int rotation, Size deviceSize, Rect crop, int maxSize, int lockedVideoOrientation) {
if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) { if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL || lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL_AUTO) {
// The user requested to lock the video orientation to the current orientation // The user requested to lock the video orientation to the current orientation
lockedVideoOrientation = rotation; lockedVideoOrientation = rotation;
} }

View File

@ -1,5 +1,6 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.FakeContext; import com.genymobile.scrcpy.FakeContext;
import com.genymobile.scrcpy.device.DisplayInfo; import com.genymobile.scrcpy.device.DisplayInfo;
import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.device.Size;
@ -7,6 +8,7 @@ import com.genymobile.scrcpy.util.Command;
import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.Ln;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.hardware.display.VirtualDisplay; import android.hardware.display.VirtualDisplay;
import android.view.Display; import android.view.Display;
@ -22,6 +24,7 @@ import java.util.regex.Pattern;
public final class DisplayManager { public final class DisplayManager {
private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal
private Method createVirtualDisplayMethod; private Method createVirtualDisplayMethod;
private Method requestDisplayPowerMethod;
static DisplayManager create() { static DisplayManager create() {
try { try {
@ -137,4 +140,22 @@ public final class DisplayManager {
android.hardware.display.DisplayManager dm = ctor.newInstance(FakeContext.get()); android.hardware.display.DisplayManager dm = ctor.newInstance(FakeContext.get());
return dm.createVirtualDisplay(name, width, height, dpi, surface, flags); return dm.createVirtualDisplay(name, width, height, dpi, surface, flags);
} }
private Method getRequestDisplayPowerMethod() throws NoSuchMethodException {
if (requestDisplayPowerMethod == null) {
requestDisplayPowerMethod = manager.getClass().getMethod("requestDisplayPower", int.class, boolean.class);
}
return requestDisplayPowerMethod;
}
@TargetApi(AndroidVersions.API_35_ANDROID_15)
public boolean requestDisplayPower(int displayId, boolean on) {
try {
Method method = getRequestDisplayPowerMethod();
return (boolean) method.invoke(manager, displayId, on);
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
return false;
}
}
} }

View File

@ -1,7 +1,5 @@
package com.genymobile.scrcpy.control; package com.genymobile.scrcpy.control;
import com.genymobile.scrcpy.device.Device;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import org.junit.Assert; import org.junit.Assert;
@ -285,19 +283,19 @@ public class ControlMessageReaderTest {
} }
@Test @Test
public void testParseSetScreenPowerMode() throws IOException { public void testParseSetDisplayPower() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos); DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_SET_SCREEN_POWER_MODE); dos.writeByte(ControlMessage.TYPE_SET_DISPLAY_POWER);
dos.writeByte(Device.POWER_MODE_NORMAL); dos.writeBoolean(true);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(packet); ByteArrayInputStream bis = new ByteArrayInputStream(packet);
ControlMessageReader reader = new ControlMessageReader(bis); ControlMessageReader reader = new ControlMessageReader(bis);
ControlMessage event = reader.read(); ControlMessage event = reader.read();
Assert.assertEquals(ControlMessage.TYPE_SET_SCREEN_POWER_MODE, event.getType()); Assert.assertEquals(ControlMessage.TYPE_SET_DISPLAY_POWER, event.getType());
Assert.assertEquals(Device.POWER_MODE_NORMAL, event.getAction()); Assert.assertTrue(event.getOn());
Assert.assertEquals(-1, bis.read()); // EOS Assert.assertEquals(-1, bis.read()); // EOS
} }