Add --capture-orientation
Deprecate --lock-video-orientation in favor of a more general option --capture-orientation, which supports all possible orientations (0, 90, 180, 270, flip0, flip90, flip180, flip270), and a "locked" flag via a '@' prefix. All the old "locked video orientations" are supported: - --lock-video-orientation -> --capture-orientation=@ - --lock-video-orientation=0 -> --capture-orientation=@0 - --lock-video-orientation=90 -> --capture-orientation=@90 - --lock-video-orientation=180 -> --capture-orientation=@180 - --lock-video-orientation=270 -> --capture-orientation=@270 In addition, --capture-orientation can rotate/flip the display without locking, so that it follows the physical device rotation. For example: scrcpy --capture-orientation=flip90 always flips and rotates the capture by 90° clockwise. The arguments are consistent with --orientation (which provides a separate client-side orientation).
This commit is contained in:
parent
378300b325
commit
784222edf6
@ -17,6 +17,7 @@ _scrcpy() {
|
|||||||
--camera-fps=
|
--camera-fps=
|
||||||
--camera-high-speed
|
--camera-high-speed
|
||||||
--camera-size=
|
--camera-size=
|
||||||
|
--capture-orientation=
|
||||||
--crop=
|
--crop=
|
||||||
-d --select-usb
|
-d --select-usb
|
||||||
--disable-screensaver
|
--disable-screensaver
|
||||||
@ -37,8 +38,6 @@ _scrcpy() {
|
|||||||
--list-cameras
|
--list-cameras
|
||||||
--list-displays
|
--list-displays
|
||||||
--list-encoders
|
--list-encoders
|
||||||
--lock-video-orientation
|
|
||||||
--lock-video-orientation=
|
|
||||||
-m --max-size=
|
-m --max-size=
|
||||||
-M
|
-M
|
||||||
--max-fps=
|
--max-fps=
|
||||||
|
@ -24,6 +24,7 @@ arguments=(
|
|||||||
'--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)'
|
'--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)'
|
||||||
'--camera-fps=[Specify the camera capture frame rate]'
|
'--camera-fps=[Specify the camera capture frame rate]'
|
||||||
'--camera-size=[Specify an explicit camera capture size]'
|
'--camera-size=[Specify an explicit camera capture size]'
|
||||||
|
'--capture-orientation=[Set the capture video orientation]:orientation:(0 90 180 270 flip0 flip90 flip180 flip270 #0 #90 #180 #270 #flip0 #flip90 #flip180 #flip270)'
|
||||||
'--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]'
|
||||||
@ -44,7 +45,6 @@ arguments=(
|
|||||||
'--list-cameras[List cameras available on the device]'
|
'--list-cameras[List cameras available on the device]'
|
||||||
'--list-displays[List displays available on the device]'
|
'--list-displays[List displays available on the device]'
|
||||||
'--list-encoders[List video and audio encoders available on the device]'
|
'--list-encoders[List video and audio encoders available on the device]'
|
||||||
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)'
|
|
||||||
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
||||||
'-M[Use UHID/AOA mouse (same as --mouse=uhid or --mouse=aoa, depending on OTG mode)]'
|
'-M[Use UHID/AOA mouse (same as --mouse=uhid or --mouse=aoa, depending on OTG mode)]'
|
||||||
'--max-fps=[Limit the frame rate of screen capture]'
|
'--max-fps=[Limit the frame rate of screen capture]'
|
||||||
|
22
app/scrcpy.1
22
app/scrcpy.1
@ -121,6 +121,18 @@ If not specified, Android's default frame rate (30 fps) is used.
|
|||||||
.BI "\-\-camera\-size " width\fRx\fIheight
|
.BI "\-\-camera\-size " width\fRx\fIheight
|
||||||
Specify an explicit camera capture size.
|
Specify an explicit camera capture size.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-capture\-orientation " value
|
||||||
|
Possible values are 0, 90, 180, 270, flip0, flip90, flip180 and flip270, possibly prefixed by '@'.
|
||||||
|
|
||||||
|
The number represents the clockwise rotation in degrees; the "flip" keyword applies a horizontal flip before the rotation.
|
||||||
|
|
||||||
|
If a leading '@' is passed (@90) for display capture, then the rotation is locked, and is relative to the natural device orientation.
|
||||||
|
|
||||||
|
If '@' is passed alone, then the rotation is locked to the initial device orientation.
|
||||||
|
|
||||||
|
Default is 0.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
|
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
|
||||||
Crop the device screen on the server.
|
Crop the device screen on the server.
|
||||||
@ -241,16 +253,6 @@ List video and audio encoders available on the device.
|
|||||||
.B \-\-list\-displays
|
.B \-\-list\-displays
|
||||||
List displays available on the device.
|
List displays available on the device.
|
||||||
|
|
||||||
.TP
|
|
||||||
\fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR]
|
|
||||||
Lock capture video orientation to \fIvalue\fR.
|
|
||||||
|
|
||||||
Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 90, 180, and 270. The values represent the clockwise rotation from the natural device orientation, in degrees.
|
|
||||||
|
|
||||||
Default is "unlocked".
|
|
||||||
|
|
||||||
Passing the option without argument is equivalent to passing "initial".
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-m, \-\-max\-size " value
|
.BI "\-m, \-\-max\-size " value
|
||||||
Limit both the width and height of the video to \fIvalue\fR. The other dimension is computed so that the device aspect\-ratio is preserved.
|
Limit both the width and height of the video to \fIvalue\fR. The other dimension is computed so that the device aspect\-ratio is preserved.
|
||||||
|
130
app/src/cli.c
130
app/src/cli.c
@ -107,6 +107,7 @@ enum {
|
|||||||
OPT_LIST_APPS,
|
OPT_LIST_APPS,
|
||||||
OPT_START_APP,
|
OPT_START_APP,
|
||||||
OPT_SCREEN_OFF_TIMEOUT,
|
OPT_SCREEN_OFF_TIMEOUT,
|
||||||
|
OPT_CAPTURE_ORIENTATION,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@ -471,18 +472,27 @@ static const struct sc_option options[] = {
|
|||||||
.text = "List video and audio encoders available on the device.",
|
.text = "List video and audio encoders available on the device.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
.longopt_id = OPT_CAPTURE_ORIENTATION,
|
||||||
|
.longopt = "capture-orientation",
|
||||||
|
.argdesc = "value",
|
||||||
|
.text = "Set the capture video orientation.\n"
|
||||||
|
"Possible values are 0, 90, 180, 270, flip0, flip90, flip180 "
|
||||||
|
"and flip270, possibly prefixed by '@'.\n"
|
||||||
|
"The number represents the clockwise rotation in degrees; the "
|
||||||
|
"flip\" keyword applies a horizontal flip before the "
|
||||||
|
"rotation.\n"
|
||||||
|
"If a leading '@' is passed (@90) for display capture, then "
|
||||||
|
"the rotation is locked, and is relative to the natural device "
|
||||||
|
"orientation.\n"
|
||||||
|
"If '@' is passed alone, then the rotation is locked to the "
|
||||||
|
"initial device orientation.\n"
|
||||||
|
"Default is 0.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// deprecated
|
||||||
.longopt_id = OPT_LOCK_VIDEO_ORIENTATION,
|
.longopt_id = OPT_LOCK_VIDEO_ORIENTATION,
|
||||||
.longopt = "lock-video-orientation",
|
.longopt = "lock-video-orientation",
|
||||||
.argdesc = "value",
|
.argdesc = "value",
|
||||||
.optional_arg = true,
|
|
||||||
.text = "Lock capture video orientation to value.\n"
|
|
||||||
"Possible values are \"unlocked\", \"initial\" (locked to the "
|
|
||||||
"initial orientation), 0, 90, 180 and 270. The values "
|
|
||||||
"represent the clockwise rotation from the natural device "
|
|
||||||
"orientation, in degrees.\n"
|
|
||||||
"Default is \"unlocked\".\n"
|
|
||||||
"Passing the option without argument is equivalent to passing "
|
|
||||||
"\"initial\".",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'm',
|
.shortopt = 'm',
|
||||||
@ -1582,66 +1592,6 @@ parse_audio_output_buffer(const char *s, sc_tick *tick) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_lock_video_orientation(const char *s,
|
|
||||||
enum sc_lock_video_orientation *lock_mode) {
|
|
||||||
if (!s || !strcmp(s, "initial")) {
|
|
||||||
// Without argument, lock the initial orientation
|
|
||||||
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(s, "unlocked")) {
|
|
||||||
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(s, "0")) {
|
|
||||||
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(s, "90")) {
|
|
||||||
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_90;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(s, "180")) {
|
|
||||||
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_180;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(s, "270")) {
|
|
||||||
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_270;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(s, "1")) {
|
|
||||||
LOGW("--lock-video-orientation=1 is deprecated, use "
|
|
||||||
"--lock-video-orientation=270 instead.");
|
|
||||||
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_270;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(s, "2")) {
|
|
||||||
LOGW("--lock-video-orientation=2 is deprecated, use "
|
|
||||||
"--lock-video-orientation=180 instead.");
|
|
||||||
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_180;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(s, "3")) {
|
|
||||||
LOGW("--lock-video-orientation=3 is deprecated, use "
|
|
||||||
"--lock-video-orientation=90 instead.");
|
|
||||||
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_90;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGE("Unsupported --lock-video-orientation value: %s (expected initial, "
|
|
||||||
"unlocked, 0, 90, 180 or 270).", s);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_rotation(const char *s, uint8_t *rotation) {
|
parse_rotation(const char *s, uint8_t *rotation) {
|
||||||
long value;
|
long value;
|
||||||
@ -1693,6 +1643,32 @@ parse_orientation(const char *s, enum sc_orientation *orientation) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_capture_orientation(const char *s, enum sc_orientation *orientation,
|
||||||
|
enum sc_orientation_lock *lock) {
|
||||||
|
if (*s == '\0') {
|
||||||
|
LOGE("Capture orientation may not be empty (expected 0, 90, 180, 270, "
|
||||||
|
"flip0, flip90, flip180 or flip270, possibly prefixed by '@')");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock the orientation by a leading '@'
|
||||||
|
if (s[0] == '@') {
|
||||||
|
// Consume '@'
|
||||||
|
++s;
|
||||||
|
if (*s == '\0') {
|
||||||
|
// Only '@': lock to the initial orientation (orientation is unused)
|
||||||
|
*lock = SC_ORIENTATION_LOCKED_INITIAL;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*lock = SC_ORIENTATION_LOCKED_VALUE;
|
||||||
|
} else {
|
||||||
|
*lock = SC_ORIENTATION_UNLOCKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse_orientation(s, orientation);
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_window_position(const char *s, int16_t *position) {
|
parse_window_position(const char *s, int16_t *position) {
|
||||||
// special value for "auto"
|
// special value for "auto"
|
||||||
@ -2367,8 +2343,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
"--mouse=uhid instead.");
|
"--mouse=uhid instead.");
|
||||||
return false;
|
return false;
|
||||||
case OPT_LOCK_VIDEO_ORIENTATION:
|
case OPT_LOCK_VIDEO_ORIENTATION:
|
||||||
if (!parse_lock_video_orientation(optarg,
|
LOGE("--lock-video-orientation has been removed, use "
|
||||||
&opts->lock_video_orientation)) {
|
"--capture-orientation instead.");
|
||||||
|
return false;
|
||||||
|
case OPT_CAPTURE_ORIENTATION:
|
||||||
|
if (!parse_capture_orientation(optarg,
|
||||||
|
&opts->capture_orientation,
|
||||||
|
&opts->capture_orientation_lock)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -2852,13 +2833,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts->lock_video_orientation ==
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
|
||||||
LOGI("Video orientation is locked for v4l2 sink. "
|
|
||||||
"See --lock-video-orientation.");
|
|
||||||
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// V4L2 could not handle size change.
|
// V4L2 could not handle size change.
|
||||||
// Do not log because downsizing on error is the default behavior,
|
// Do not log because downsizing on error is the default behavior,
|
||||||
// not an explicit request from the user.
|
// not an explicit request from the user.
|
||||||
|
@ -50,7 +50,8 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.video_bit_rate = 0,
|
.video_bit_rate = 0,
|
||||||
.audio_bit_rate = 0,
|
.audio_bit_rate = 0,
|
||||||
.max_fps = NULL,
|
.max_fps = NULL,
|
||||||
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
|
.capture_orientation = SC_ORIENTATION_0,
|
||||||
|
.capture_orientation_lock = SC_ORIENTATION_UNLOCKED,
|
||||||
.display_orientation = SC_ORIENTATION_0,
|
.display_orientation = SC_ORIENTATION_0,
|
||||||
.record_orientation = SC_ORIENTATION_0,
|
.record_orientation = SC_ORIENTATION_0,
|
||||||
.window_x = SC_WINDOW_POSITION_UNDEFINED,
|
.window_x = SC_WINDOW_POSITION_UNDEFINED,
|
||||||
|
@ -84,6 +84,12 @@ enum sc_orientation { // v v v
|
|||||||
SC_ORIENTATION_FLIP_270, // 1 1 1
|
SC_ORIENTATION_FLIP_270, // 1 1 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum sc_orientation_lock {
|
||||||
|
SC_ORIENTATION_UNLOCKED,
|
||||||
|
SC_ORIENTATION_LOCKED_VALUE, // lock to specified orientation
|
||||||
|
SC_ORIENTATION_LOCKED_INITIAL, // lock to initial device orientation
|
||||||
|
};
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
sc_orientation_is_mirror(enum sc_orientation orientation) {
|
sc_orientation_is_mirror(enum sc_orientation orientation) {
|
||||||
assert(!(orientation & ~7));
|
assert(!(orientation & ~7));
|
||||||
@ -130,16 +136,6 @@ sc_orientation_get_name(enum sc_orientation orientation) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum sc_lock_video_orientation {
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
|
|
||||||
// lock the current orientation when scrcpy starts
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_90 = 3,
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_180 = 2,
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_270 = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum sc_keyboard_input_mode {
|
enum sc_keyboard_input_mode {
|
||||||
SC_KEYBOARD_INPUT_MODE_AUTO,
|
SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||||
SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
||||||
@ -251,7 +247,8 @@ struct scrcpy_options {
|
|||||||
uint32_t video_bit_rate;
|
uint32_t video_bit_rate;
|
||||||
uint32_t audio_bit_rate;
|
uint32_t audio_bit_rate;
|
||||||
const char *max_fps; // float to be parsed by the server
|
const char *max_fps; // float to be parsed by the server
|
||||||
enum sc_lock_video_orientation lock_video_orientation;
|
enum sc_orientation capture_orientation;
|
||||||
|
enum sc_orientation_lock capture_orientation_lock;
|
||||||
enum sc_orientation display_orientation;
|
enum sc_orientation display_orientation;
|
||||||
enum sc_orientation record_orientation;
|
enum sc_orientation record_orientation;
|
||||||
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
|
@ -429,7 +429,8 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.audio_bit_rate = options->audio_bit_rate,
|
.audio_bit_rate = options->audio_bit_rate,
|
||||||
.max_fps = options->max_fps,
|
.max_fps = options->max_fps,
|
||||||
.screen_off_timeout = options->screen_off_timeout,
|
.screen_off_timeout = options->screen_off_timeout,
|
||||||
.lock_video_orientation = options->lock_video_orientation,
|
.capture_orientation = options->capture_orientation,
|
||||||
|
.capture_orientation_lock = options->capture_orientation_lock,
|
||||||
.control = options->control,
|
.control = options->control,
|
||||||
.display_id = options->display_id,
|
.display_id = options->display_id,
|
||||||
.new_display = options->new_display,
|
.new_display = options->new_display,
|
||||||
|
@ -274,9 +274,17 @@ execute_server(struct sc_server *server,
|
|||||||
VALIDATE_STRING(params->max_fps);
|
VALIDATE_STRING(params->max_fps);
|
||||||
ADD_PARAM("max_fps=%s", params->max_fps);
|
ADD_PARAM("max_fps=%s", params->max_fps);
|
||||||
}
|
}
|
||||||
if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
if (params->capture_orientation_lock != SC_ORIENTATION_UNLOCKED
|
||||||
ADD_PARAM("lock_video_orientation=%" PRIi8,
|
|| params->capture_orientation != SC_ORIENTATION_0) {
|
||||||
params->lock_video_orientation);
|
if (params->capture_orientation_lock == SC_ORIENTATION_LOCKED_INITIAL) {
|
||||||
|
ADD_PARAM("capture_orientation=@");
|
||||||
|
} else {
|
||||||
|
const char *orient =
|
||||||
|
sc_orientation_get_name(params->capture_orientation);
|
||||||
|
bool locked =
|
||||||
|
params->capture_orientation_lock != SC_ORIENTATION_UNLOCKED;
|
||||||
|
ADD_PARAM("capture_orientation=%s%s", locked ? "@" : "", orient);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (server->tunnel.forward) {
|
if (server->tunnel.forward) {
|
||||||
ADD_PARAM("tunnel_forward=true");
|
ADD_PARAM("tunnel_forward=true");
|
||||||
|
@ -46,7 +46,8 @@ struct sc_server_params {
|
|||||||
uint32_t audio_bit_rate;
|
uint32_t audio_bit_rate;
|
||||||
const char *max_fps; // float to be parsed by the server
|
const char *max_fps; // float to be parsed by the server
|
||||||
sc_tick screen_off_timeout;
|
sc_tick screen_off_timeout;
|
||||||
int8_t lock_video_orientation;
|
enum sc_orientation capture_orientation;
|
||||||
|
enum sc_orientation_lock capture_orientation_lock;
|
||||||
bool control;
|
bool control;
|
||||||
uint32_t display_id;
|
uint32_t display_id;
|
||||||
const char *new_display;
|
const char *new_display;
|
||||||
|
@ -51,7 +51,6 @@ static void test_options(void) {
|
|||||||
"--fullscreen",
|
"--fullscreen",
|
||||||
"--max-fps", "30",
|
"--max-fps", "30",
|
||||||
"--max-size", "1024",
|
"--max-size", "1024",
|
||||||
"--lock-video-orientation=2", // optional arguments require '='
|
|
||||||
// "--no-control" is not compatible with "--turn-screen-off"
|
// "--no-control" is not compatible with "--turn-screen-off"
|
||||||
// "--no-playback" is not compatible with "--fulscreen"
|
// "--no-playback" is not compatible with "--fulscreen"
|
||||||
"--port", "1234:1236",
|
"--port", "1234:1236",
|
||||||
@ -80,7 +79,6 @@ static void test_options(void) {
|
|||||||
assert(opts->fullscreen);
|
assert(opts->fullscreen);
|
||||||
assert(!strcmp(opts->max_fps, "30"));
|
assert(!strcmp(opts->max_fps, "30"));
|
||||||
assert(opts->max_size == 1024);
|
assert(opts->max_size == 1024);
|
||||||
assert(opts->lock_video_orientation == 2);
|
|
||||||
assert(opts->port_range.first == 1234);
|
assert(opts->port_range.first == 1234);
|
||||||
assert(opts->port_range.last == 1236);
|
assert(opts->port_range.last == 1236);
|
||||||
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
|
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
|
||||||
|
32
doc/video.md
32
doc/video.md
@ -103,21 +103,39 @@ The orientation may be applied at 3 different levels:
|
|||||||
- The [shortcut](shortcuts.md) <kbd>MOD</kbd>+<kbd>r</kbd> requests the
|
- The [shortcut](shortcuts.md) <kbd>MOD</kbd>+<kbd>r</kbd> requests the
|
||||||
device to switch between portrait and landscape (the current running app may
|
device to switch between portrait and landscape (the current running app may
|
||||||
refuse, if it does not support the requested orientation).
|
refuse, if it does not support the requested orientation).
|
||||||
- `--lock-video-orientation` changes the mirroring orientation (the orientation
|
- `--capture-orientation` changes the mirroring orientation (the orientation
|
||||||
of the video sent from the device to the computer). This affects the
|
of the video sent from the device to the computer). This affects the
|
||||||
recording.
|
recording.
|
||||||
- `--orientation` is applied on the client side, and affects display and
|
- `--orientation` is applied on the client side, and affects display and
|
||||||
recording. For the display, it can be changed dynamically using
|
recording. For the display, it can be changed dynamically using
|
||||||
[shortcuts](shortcuts.md).
|
[shortcuts](shortcuts.md).
|
||||||
|
|
||||||
To lock the mirroring orientation (on the capture side):
|
To capture the video with a specific orientation:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --lock-video-orientation # initial (current) orientation
|
scrcpy --capture-orientation=0
|
||||||
scrcpy --lock-video-orientation=0 # natural orientation
|
scrcpy --capture-orientation=90 # 90° clockwise
|
||||||
scrcpy --lock-video-orientation=90 # 90° clockwise
|
scrcpy --capture-orientation=180 # 180°
|
||||||
scrcpy --lock-video-orientation=180 # 180°
|
scrcpy --capture-orientation=270 # 270° clockwise
|
||||||
scrcpy --lock-video-orientation=270 # 270° clockwise
|
scrcpy --capture-orientation=flip0 # hflip
|
||||||
|
scrcpy --capture-orientation=flip90 # hflip + 90° clockwise
|
||||||
|
scrcpy --capture-orientation=flip180 # hflip + 180°
|
||||||
|
scrcpy --capture-orientation=flip270 # hflip + 270° clockwise
|
||||||
|
```
|
||||||
|
|
||||||
|
The capture orientation can be locked by using `@`, so that a physical device
|
||||||
|
rotation does not change the captured video orientation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --capture-orientation=@ # locked to the initial orientation
|
||||||
|
scrcpy --capture-orientation=@0 # locked to 0°
|
||||||
|
scrcpy --capture-orientation=@90 # locked to 90° clockwise
|
||||||
|
scrcpy --capture-orientation=@180 # locked to 180°
|
||||||
|
scrcpy --capture-orientation=@270 # locked to 270° clockwise
|
||||||
|
scrcpy --capture-orientation=@flip0 # locked to hflip
|
||||||
|
scrcpy --capture-orientation=@flip90 # locked to hflip + 90° clockwise
|
||||||
|
scrcpy --capture-orientation=@flip180 # locked to hflip + 180°
|
||||||
|
scrcpy --capture-orientation=@flip270 # locked to hflip + 270° clockwise
|
||||||
```
|
```
|
||||||
|
|
||||||
To orient the video (on the rendering side):
|
To orient the video (on the rendering side):
|
||||||
|
@ -4,6 +4,7 @@ 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.Device;
|
||||||
import com.genymobile.scrcpy.device.NewDisplay;
|
import com.genymobile.scrcpy.device.NewDisplay;
|
||||||
|
import com.genymobile.scrcpy.device.Orientation;
|
||||||
import com.genymobile.scrcpy.device.Size;
|
import com.genymobile.scrcpy.device.Size;
|
||||||
import com.genymobile.scrcpy.util.CodecOption;
|
import com.genymobile.scrcpy.util.CodecOption;
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
@ -13,6 +14,7 @@ import com.genymobile.scrcpy.video.VideoCodec;
|
|||||||
import com.genymobile.scrcpy.video.VideoSource;
|
import com.genymobile.scrcpy.video.VideoSource;
|
||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -32,7 +34,6 @@ 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 boolean tunnelForward;
|
private boolean tunnelForward;
|
||||||
private Rect crop;
|
private Rect crop;
|
||||||
private boolean control = true;
|
private boolean control = true;
|
||||||
@ -59,6 +60,9 @@ public class Options {
|
|||||||
|
|
||||||
private NewDisplay newDisplay;
|
private NewDisplay newDisplay;
|
||||||
|
|
||||||
|
private Orientation.Lock captureOrientationLock = Orientation.Lock.Unlocked;
|
||||||
|
private Orientation captureOrientation = Orientation.Orient0;
|
||||||
|
|
||||||
private boolean listEncoders;
|
private boolean listEncoders;
|
||||||
private boolean listDisplays;
|
private boolean listDisplays;
|
||||||
private boolean listCameras;
|
private boolean listCameras;
|
||||||
@ -123,10 +127,6 @@ public class Options {
|
|||||||
return maxFps;
|
return maxFps;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLockVideoOrientation() {
|
|
||||||
return lockVideoOrientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTunnelForward() {
|
public boolean isTunnelForward() {
|
||||||
return tunnelForward;
|
return tunnelForward;
|
||||||
}
|
}
|
||||||
@ -219,6 +219,14 @@ public class Options {
|
|||||||
return newDisplay;
|
return newDisplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Orientation getCaptureOrientation() {
|
||||||
|
return captureOrientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Orientation.Lock getCaptureOrientationLock() {
|
||||||
|
return captureOrientationLock;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getList() {
|
public boolean getList() {
|
||||||
return listEncoders || listDisplays || listCameras || listCameraSizes || listApps;
|
return listEncoders || listDisplays || listCameras || listCameraSizes || listApps;
|
||||||
}
|
}
|
||||||
@ -341,9 +349,6 @@ public class Options {
|
|||||||
case "max_fps":
|
case "max_fps":
|
||||||
options.maxFps = parseFloat("max_fps", value);
|
options.maxFps = parseFloat("max_fps", value);
|
||||||
break;
|
break;
|
||||||
case "lock_video_orientation":
|
|
||||||
options.lockVideoOrientation = Integer.parseInt(value);
|
|
||||||
break;
|
|
||||||
case "tunnel_forward":
|
case "tunnel_forward":
|
||||||
options.tunnelForward = Boolean.parseBoolean(value);
|
options.tunnelForward = Boolean.parseBoolean(value);
|
||||||
break;
|
break;
|
||||||
@ -448,6 +453,11 @@ public class Options {
|
|||||||
case "new_display":
|
case "new_display":
|
||||||
options.newDisplay = parseNewDisplay(value);
|
options.newDisplay = parseNewDisplay(value);
|
||||||
break;
|
break;
|
||||||
|
case "capture_orientation":
|
||||||
|
Pair<Orientation.Lock, Orientation> pair = parseCaptureOrientation(value);
|
||||||
|
options.captureOrientationLock = pair.first;
|
||||||
|
options.captureOrientation = pair.second;
|
||||||
|
break;
|
||||||
case "send_device_meta":
|
case "send_device_meta":
|
||||||
options.sendDeviceMeta = Boolean.parseBoolean(value);
|
options.sendDeviceMeta = Boolean.parseBoolean(value);
|
||||||
break;
|
break;
|
||||||
@ -571,4 +581,25 @@ public class Options {
|
|||||||
|
|
||||||
return new NewDisplay(size, dpi);
|
return new NewDisplay(size, dpi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Pair<Orientation.Lock, Orientation> parseCaptureOrientation(String value) {
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Empty capture orientation string");
|
||||||
|
}
|
||||||
|
|
||||||
|
Orientation.Lock lock;
|
||||||
|
if (value.charAt(0) == '@') {
|
||||||
|
// Consume '@'
|
||||||
|
value = value.substring(1);
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
// Only '@': lock to the initial orientation (orientation is unused)
|
||||||
|
return Pair.create(Orientation.Lock.LockedInitial, Orientation.Orient0);
|
||||||
|
}
|
||||||
|
lock = Orientation.Lock.LockedValue;
|
||||||
|
} else {
|
||||||
|
lock = Orientation.Lock.Unlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair.create(lock, Orientation.getByName(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,9 +40,6 @@ public final class Device {
|
|||||||
public static final int INJECT_MODE_WAIT_FOR_RESULT = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT;
|
public static final int INJECT_MODE_WAIT_FOR_RESULT = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT;
|
||||||
public static final int INJECT_MODE_WAIT_FOR_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH;
|
public static final int INJECT_MODE_WAIT_FOR_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH;
|
||||||
|
|
||||||
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
|
|
||||||
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
|
|
||||||
|
|
||||||
private Device() {
|
private Device() {
|
||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
package com.genymobile.scrcpy.device;
|
||||||
|
|
||||||
|
public enum Orientation {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
Orient0("0"),
|
||||||
|
Orient90("90"),
|
||||||
|
Orient180("180"),
|
||||||
|
Orient270("270"),
|
||||||
|
Flip0("flip0"),
|
||||||
|
Flip90("flip90"),
|
||||||
|
Flip180("flip180"),
|
||||||
|
Flip270("flip270");
|
||||||
|
|
||||||
|
public enum Lock {
|
||||||
|
Unlocked, LockedInitial, LockedValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
Orientation(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Orientation getByName(String name) {
|
||||||
|
for (Orientation orientation : values()) {
|
||||||
|
if (orientation.name.equals(name)) {
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Unknown orientation: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Orientation fromRotation(int rotation) {
|
||||||
|
assert rotation >= 0 && rotation < 4;
|
||||||
|
return values()[rotation];
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFlipped() {
|
||||||
|
return (ordinal() & 4) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRotation() {
|
||||||
|
return this.ordinal() & 3;
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import com.genymobile.scrcpy.control.PositionMapper;
|
|||||||
import com.genymobile.scrcpy.device.ConfigurationException;
|
import com.genymobile.scrcpy.device.ConfigurationException;
|
||||||
import com.genymobile.scrcpy.device.Device;
|
import com.genymobile.scrcpy.device.Device;
|
||||||
import com.genymobile.scrcpy.device.DisplayInfo;
|
import com.genymobile.scrcpy.device.DisplayInfo;
|
||||||
|
import com.genymobile.scrcpy.device.Orientation;
|
||||||
import com.genymobile.scrcpy.device.Size;
|
import com.genymobile.scrcpy.device.Size;
|
||||||
import com.genymobile.scrcpy.opengl.AffineOpenGLFilter;
|
import com.genymobile.scrcpy.opengl.AffineOpenGLFilter;
|
||||||
import com.genymobile.scrcpy.opengl.OpenGLFilter;
|
import com.genymobile.scrcpy.opengl.OpenGLFilter;
|
||||||
@ -30,7 +31,8 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
private final int displayId;
|
private final int displayId;
|
||||||
private int maxSize;
|
private int maxSize;
|
||||||
private final Rect crop;
|
private final Rect crop;
|
||||||
private int lockVideoOrientation;
|
private Orientation.Lock captureOrientationLock;
|
||||||
|
private Orientation captureOrientation;
|
||||||
|
|
||||||
private DisplayInfo displayInfo;
|
private DisplayInfo displayInfo;
|
||||||
private Size videoSize;
|
private Size videoSize;
|
||||||
@ -49,7 +51,10 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
assert displayId != Device.DISPLAY_ID_NONE;
|
assert displayId != Device.DISPLAY_ID_NONE;
|
||||||
this.maxSize = options.getMaxSize();
|
this.maxSize = options.getMaxSize();
|
||||||
this.crop = options.getCrop();
|
this.crop = options.getCrop();
|
||||||
this.lockVideoOrientation = options.getLockVideoOrientation();
|
this.captureOrientationLock = options.getCaptureOrientationLock();
|
||||||
|
this.captureOrientation = options.getCaptureOrientation();
|
||||||
|
assert captureOrientationLock != null;
|
||||||
|
assert captureOrientation != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -72,9 +77,10 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
Size displaySize = displayInfo.getSize();
|
Size displaySize = displayInfo.getSize();
|
||||||
displaySizeMonitor.setSessionDisplaySize(displaySize);
|
displaySizeMonitor.setSessionDisplaySize(displaySize);
|
||||||
|
|
||||||
if (lockVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) {
|
if (captureOrientationLock == Orientation.Lock.LockedInitial) {
|
||||||
// The user requested to lock the video orientation to the current orientation
|
// The user requested to lock the video orientation to the current orientation
|
||||||
lockVideoOrientation = displayInfo.getRotation();
|
captureOrientationLock = Orientation.Lock.LockedValue;
|
||||||
|
captureOrientation = Orientation.fromRotation(displayInfo.getRotation());
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoFilter filter = new VideoFilter(displaySize);
|
VideoFilter filter = new VideoFilter(displaySize);
|
||||||
@ -84,9 +90,8 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
filter.addCrop(crop, transposed);
|
filter.addCrop(crop, transposed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lockVideoOrientation != Device.LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
boolean locked = captureOrientationLock != Orientation.Lock.Unlocked;
|
||||||
filter.addLockVideoOrientation(lockVideoOrientation, displayInfo.getRotation());
|
filter.addOrientation(displayInfo.getRotation(), locked, captureOrientation);
|
||||||
}
|
|
||||||
|
|
||||||
transform = filter.getInverseTransform();
|
transform = filter.getInverseTransform();
|
||||||
videoSize = filter.getOutputSize().limit(maxSize).round8();
|
videoSize = filter.getOutputSize().limit(maxSize).round8();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.genymobile.scrcpy.video;
|
package com.genymobile.scrcpy.video;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.device.Orientation;
|
||||||
import com.genymobile.scrcpy.device.Size;
|
import com.genymobile.scrcpy.device.Size;
|
||||||
import com.genymobile.scrcpy.util.AffineMatrix;
|
import com.genymobile.scrcpy.util.AffineMatrix;
|
||||||
|
|
||||||
@ -72,9 +73,20 @@ public class VideoFilter {
|
|||||||
size = size.rotate();
|
size = size.rotate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void addOrientation(Orientation captureOrientation) {
|
||||||
public void addLockVideoOrientation(int lockVideoOrientation, int displayRotation) {
|
if (captureOrientation.isFlipped()) {
|
||||||
int ccwRotation = (4 + lockVideoOrientation - displayRotation) % 4;
|
transform = AffineMatrix.hflip().multiply(transform);
|
||||||
|
}
|
||||||
|
int ccwRotation = (4 - captureOrientation.getRotation()) % 4;
|
||||||
addRotation(ccwRotation);
|
addRotation(ccwRotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addOrientation(int displayRotation, boolean locked, Orientation captureOrientation) {
|
||||||
|
if (locked) {
|
||||||
|
// flip/rotate the current display from the natural device orientation (i.e. where display rotation is 0)
|
||||||
|
int reverseDisplayRotation = (4 - displayRotation) % 4;
|
||||||
|
addRotation(reverseDisplayRotation);
|
||||||
|
}
|
||||||
|
addOrientation(captureOrientation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user