From fe0fc0de3713d4297712de7575271ced976a0d61 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 14 Nov 2024 20:19:40 +0100 Subject: [PATCH] Add --capture-orientation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- app/data/bash-completion/scrcpy | 3 +- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 22 +-- app/src/cli.c | 130 +++++++----------- app/src/options.c | 3 +- app/src/options.h | 19 ++- app/src/scrcpy.c | 3 +- app/src/server.c | 14 +- app/src/server.h | 3 +- app/tests/test_cli.c | 2 - doc/video.md | 32 ++++- .../java/com/genymobile/scrcpy/Options.java | 47 +++++-- .../com/genymobile/scrcpy/device/Device.java | 3 - .../genymobile/scrcpy/device/Orientation.java | 47 +++++++ .../scrcpy/video/ScreenCapture.java | 19 ++- .../genymobile/scrcpy/video/VideoFilter.java | 18 ++- 16 files changed, 229 insertions(+), 138 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/device/Orientation.java diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index d9ad4c8d..a954fd6c 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -17,6 +17,7 @@ _scrcpy() { --camera-fps= --camera-high-speed --camera-size= + --capture-orientation= --crop= -d --select-usb --disable-screensaver @@ -37,8 +38,6 @@ _scrcpy() { --list-cameras --list-displays --list-encoders - --lock-video-orientation - --lock-video-orientation= -m --max-size= -M --max-fps= diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 430e8000..154ddef0 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -24,6 +24,7 @@ arguments=( '--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)' '--camera-fps=[Specify the camera capture frame rate]' '--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]' {-d,--select-usb}'[Use USB device]' '--disable-screensaver[Disable screensaver while scrcpy is running]' @@ -44,7 +45,6 @@ arguments=( '--list-cameras[List cameras available on the device]' '--list-displays[List displays 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[Use UHID/AOA mouse (same as --mouse=uhid or --mouse=aoa, depending on OTG mode)]' '--max-fps=[Limit the frame rate of screen capture]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 76e36dcb..29b53d98 100644 --- a/app/scrcpy.1 +++ b/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 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 .BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy Crop the device screen on the server. @@ -241,16 +253,6 @@ List video and audio encoders available on the device. .B \-\-list\-displays 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 .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. diff --git a/app/src/cli.c b/app/src/cli.c index e67192bf..1a5e30fd 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -107,6 +107,7 @@ enum { OPT_LIST_APPS, OPT_START_APP, OPT_SCREEN_OFF_TIMEOUT, + OPT_CAPTURE_ORIENTATION, }; struct sc_option { @@ -471,18 +472,27 @@ static const struct sc_option options[] = { .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 = "lock-video-orientation", .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', @@ -1582,66 +1592,6 @@ parse_audio_output_buffer(const char *s, sc_tick *tick) { 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 parse_rotation(const char *s, uint8_t *rotation) { long value; @@ -1693,6 +1643,32 @@ parse_orientation(const char *s, enum sc_orientation *orientation) { 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 parse_window_position(const char *s, int16_t *position) { // special value for "auto" @@ -2367,8 +2343,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], "--mouse=uhid instead."); return false; case OPT_LOCK_VIDEO_ORIENTATION: - if (!parse_lock_video_orientation(optarg, - &opts->lock_video_orientation)) { + LOGE("--lock-video-orientation has been removed, use " + "--capture-orientation instead."); + return false; + case OPT_CAPTURE_ORIENTATION: + if (!parse_capture_orientation(optarg, + &opts->capture_orientation, + &opts->capture_orientation_lock)) { return false; } break; @@ -2852,13 +2833,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], 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. // Do not log because downsizing on error is the default behavior, // not an explicit request from the user. diff --git a/app/src/options.c b/app/src/options.c index 3cad9d9f..69f8f64d 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -50,7 +50,8 @@ const struct scrcpy_options scrcpy_options_default = { .video_bit_rate = 0, .audio_bit_rate = 0, .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, .record_orientation = SC_ORIENTATION_0, .window_x = SC_WINDOW_POSITION_UNDEFINED, diff --git a/app/src/options.h b/app/src/options.h index 9236c3f8..945fcdf7 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -84,6 +84,12 @@ enum sc_orientation { // v v v 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 sc_orientation_is_mirror(enum sc_orientation orientation) { 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 { SC_KEYBOARD_INPUT_MODE_AUTO, 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 audio_bit_rate; 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 record_orientation; int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto" diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 2721c0d8..5528910a 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -429,7 +429,8 @@ scrcpy(struct scrcpy_options *options) { .audio_bit_rate = options->audio_bit_rate, .max_fps = options->max_fps, .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, .display_id = options->display_id, .new_display = options->new_display, diff --git a/app/src/server.c b/app/src/server.c index 41f0bf27..9c12500e 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -274,9 +274,17 @@ execute_server(struct sc_server *server, VALIDATE_STRING(params->max_fps); ADD_PARAM("max_fps=%s", params->max_fps); } - if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { - ADD_PARAM("lock_video_orientation=%" PRIi8, - params->lock_video_orientation); + if (params->capture_orientation_lock != SC_ORIENTATION_UNLOCKED + || params->capture_orientation != SC_ORIENTATION_0) { + 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) { ADD_PARAM("tunnel_forward=true"); diff --git a/app/src/server.h b/app/src/server.h index 7059be7f..20d998e9 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -46,7 +46,8 @@ struct sc_server_params { uint32_t audio_bit_rate; const char *max_fps; // float to be parsed by the server sc_tick screen_off_timeout; - int8_t lock_video_orientation; + enum sc_orientation capture_orientation; + enum sc_orientation_lock capture_orientation_lock; bool control; uint32_t display_id; const char *new_display; diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index 14765792..de605cb9 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -51,7 +51,6 @@ static void test_options(void) { "--fullscreen", "--max-fps", "30", "--max-size", "1024", - "--lock-video-orientation=2", // optional arguments require '=' // "--no-control" is not compatible with "--turn-screen-off" // "--no-playback" is not compatible with "--fulscreen" "--port", "1234:1236", @@ -80,7 +79,6 @@ static void test_options(void) { assert(opts->fullscreen); assert(!strcmp(opts->max_fps, "30")); assert(opts->max_size == 1024); - assert(opts->lock_video_orientation == 2); assert(opts->port_range.first == 1234); assert(opts->port_range.last == 1236); assert(!strcmp(opts->push_target, "/sdcard/Movies")); diff --git a/doc/video.md b/doc/video.md index 74ec74dd..c00b6602 100644 --- a/doc/video.md +++ b/doc/video.md @@ -103,21 +103,39 @@ The orientation may be applied at 3 different levels: - The [shortcut](shortcuts.md) MOD+r requests the device to switch between portrait and landscape (the current running app may 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 recording. - `--orientation` is applied on the client side, and affects display and recording. For the display, it can be changed dynamically using [shortcuts](shortcuts.md). -To lock the mirroring orientation (on the capture side): +To capture the video with a specific orientation: ```bash -scrcpy --lock-video-orientation # initial (current) orientation -scrcpy --lock-video-orientation=0 # natural orientation -scrcpy --lock-video-orientation=90 # 90° clockwise -scrcpy --lock-video-orientation=180 # 180° -scrcpy --lock-video-orientation=270 # 270° clockwise +scrcpy --capture-orientation=0 +scrcpy --capture-orientation=90 # 90° clockwise +scrcpy --capture-orientation=180 # 180° +scrcpy --capture-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): diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index c1620432..e1b3b9af 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -4,6 +4,7 @@ import com.genymobile.scrcpy.audio.AudioCodec; import com.genymobile.scrcpy.audio.AudioSource; import com.genymobile.scrcpy.device.Device; import com.genymobile.scrcpy.device.NewDisplay; +import com.genymobile.scrcpy.device.Orientation; import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.util.CodecOption; import com.genymobile.scrcpy.util.Ln; @@ -13,6 +14,7 @@ import com.genymobile.scrcpy.video.VideoCodec; import com.genymobile.scrcpy.video.VideoSource; import android.graphics.Rect; +import android.util.Pair; import java.util.List; import java.util.Locale; @@ -32,7 +34,6 @@ public class Options { private int videoBitRate = 8000000; private int audioBitRate = 128000; private float maxFps; - private int lockVideoOrientation = -1; private boolean tunnelForward; private Rect crop; private boolean control = true; @@ -59,6 +60,9 @@ public class Options { private NewDisplay newDisplay; + private Orientation.Lock captureOrientationLock = Orientation.Lock.Unlocked; + private Orientation captureOrientation = Orientation.Orient0; + private boolean listEncoders; private boolean listDisplays; private boolean listCameras; @@ -123,10 +127,6 @@ public class Options { return maxFps; } - public int getLockVideoOrientation() { - return lockVideoOrientation; - } - public boolean isTunnelForward() { return tunnelForward; } @@ -219,6 +219,14 @@ public class Options { return newDisplay; } + public Orientation getCaptureOrientation() { + return captureOrientation; + } + + public Orientation.Lock getCaptureOrientationLock() { + return captureOrientationLock; + } + public boolean getList() { return listEncoders || listDisplays || listCameras || listCameraSizes || listApps; } @@ -341,9 +349,6 @@ public class Options { case "max_fps": options.maxFps = parseFloat("max_fps", value); break; - case "lock_video_orientation": - options.lockVideoOrientation = Integer.parseInt(value); - break; case "tunnel_forward": options.tunnelForward = Boolean.parseBoolean(value); break; @@ -448,6 +453,11 @@ public class Options { case "new_display": options.newDisplay = parseNewDisplay(value); break; + case "capture_orientation": + Pair pair = parseCaptureOrientation(value); + options.captureOrientationLock = pair.first; + options.captureOrientation = pair.second; + break; case "send_device_meta": options.sendDeviceMeta = Boolean.parseBoolean(value); break; @@ -571,4 +581,25 @@ public class Options { return new NewDisplay(size, dpi); } + + private static Pair 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)); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/device/Device.java b/server/src/main/java/com/genymobile/scrcpy/device/Device.java index 09c7d2b6..cd713499 100644 --- a/server/src/main/java/com/genymobile/scrcpy/device/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/device/Device.java @@ -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_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() { // not instantiable } diff --git a/server/src/main/java/com/genymobile/scrcpy/device/Orientation.java b/server/src/main/java/com/genymobile/scrcpy/device/Orientation.java new file mode 100644 index 00000000..78296a36 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/device/Orientation.java @@ -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; + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java index d8c1d3cc..8cbb00d0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java @@ -6,6 +6,7 @@ import com.genymobile.scrcpy.control.PositionMapper; import com.genymobile.scrcpy.device.ConfigurationException; import com.genymobile.scrcpy.device.Device; import com.genymobile.scrcpy.device.DisplayInfo; +import com.genymobile.scrcpy.device.Orientation; import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.opengl.AffineOpenGLFilter; import com.genymobile.scrcpy.opengl.OpenGLFilter; @@ -35,7 +36,8 @@ public class ScreenCapture extends DisplayCapture { private final int displayId; private int maxSize; private final Rect crop; - private int lockVideoOrientation; + private Orientation.Lock captureOrientationLock; + private Orientation captureOrientation; private DisplayInfo displayInfo; private Size videoSize; @@ -62,7 +64,10 @@ public class ScreenCapture extends DisplayCapture { assert displayId != Device.DISPLAY_ID_NONE; this.maxSize = options.getMaxSize(); this.crop = options.getCrop(); - this.lockVideoOrientation = options.getLockVideoOrientation(); + this.captureOrientationLock = options.getCaptureOrientationLock(); + this.captureOrientation = options.getCaptureOrientation(); + assert captureOrientationLock != null; + assert captureOrientation != null; } @Override @@ -106,9 +111,10 @@ public class ScreenCapture extends DisplayCapture { Size displaySize = displayInfo.getSize(); 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 - lockVideoOrientation = displayInfo.getRotation(); + captureOrientationLock = Orientation.Lock.LockedValue; + captureOrientation = Orientation.fromRotation(displayInfo.getRotation()); } VideoFilter filter = new VideoFilter(displaySize); @@ -118,9 +124,8 @@ public class ScreenCapture extends DisplayCapture { filter.addCrop(crop, transposed); } - if (lockVideoOrientation != Device.LOCK_VIDEO_ORIENTATION_UNLOCKED) { - filter.addLockVideoOrientation(lockVideoOrientation, displayInfo.getRotation()); - } + boolean locked = captureOrientationLock != Orientation.Lock.Unlocked; + filter.addOrientation(displayInfo.getRotation(), locked, captureOrientation); transform = filter.getInverseTransform(); videoSize = filter.getOutputSize().limit(maxSize).round8(); diff --git a/server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java b/server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java index 2bcfc895..05170930 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/VideoFilter.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy.video; +import com.genymobile.scrcpy.device.Orientation; import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.util.AffineMatrix; @@ -72,9 +73,20 @@ public class VideoFilter { size = size.rotate(); } } - - public void addLockVideoOrientation(int lockVideoOrientation, int displayRotation) { - int ccwRotation = (4 + lockVideoOrientation - displayRotation) % 4; + public void addOrientation(Orientation captureOrientation) { + if (captureOrientation.isFlipped()) { + transform = AffineMatrix.hflip().multiply(transform); + } + int ccwRotation = (4 - captureOrientation.getRotation()) % 4; 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); + } }