mirror of
https://github.com/Genymobile/scrcpy
synced 2024-12-17 21:04:50 +00:00
Add --angle
Add an option to rotate the video content by a custom angle. Fixes #4135 <https://github.com/Genymobile/scrcpy/issues/4135> Fixes #4345 <https://github.com/Genymobile/scrcpy/issues/4345> Refs #4658 <https://github.com/Genymobile/scrcpy/pull/4658> PR #5455 <https://github.com/Genymobile/scrcpy/pull/5455>
This commit is contained in:
parent
d19045628e
commit
adb674a5c8
@ -2,6 +2,7 @@ _scrcpy() {
|
|||||||
local cur prev words cword
|
local cur prev words cword
|
||||||
local opts="
|
local opts="
|
||||||
--always-on-top
|
--always-on-top
|
||||||
|
--angle
|
||||||
--audio-bit-rate=
|
--audio-bit-rate=
|
||||||
--audio-buffer=
|
--audio-buffer=
|
||||||
--audio-codec=
|
--audio-codec=
|
||||||
|
@ -9,6 +9,7 @@ local arguments
|
|||||||
|
|
||||||
arguments=(
|
arguments=(
|
||||||
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
|
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
|
||||||
|
'--angle=[Rotate the video content by a custom angle, in degrees]'
|
||||||
'--audio-bit-rate=[Encode the audio at the given bit-rate]'
|
'--audio-bit-rate=[Encode the audio at the given bit-rate]'
|
||||||
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
|
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
|
||||||
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'
|
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'
|
||||||
|
@ -19,6 +19,10 @@ provides display and control of Android devices connected on USB (or over TCP/IP
|
|||||||
.B \-\-always\-on\-top
|
.B \-\-always\-on\-top
|
||||||
Make scrcpy window always on top (above other windows).
|
Make scrcpy window always on top (above other windows).
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-angle " degrees
|
||||||
|
Rotate the video content by a custom angle, in degrees (clockwise).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-audio\-bit\-rate " value
|
.BI "\-\-audio\-bit\-rate " value
|
||||||
Encode the audio at the given bit rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
Encode the audio at the given bit rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
||||||
|
@ -108,6 +108,7 @@ enum {
|
|||||||
OPT_START_APP,
|
OPT_START_APP,
|
||||||
OPT_SCREEN_OFF_TIMEOUT,
|
OPT_SCREEN_OFF_TIMEOUT,
|
||||||
OPT_CAPTURE_ORIENTATION,
|
OPT_CAPTURE_ORIENTATION,
|
||||||
|
OPT_ANGLE,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@ -149,6 +150,13 @@ static const struct sc_option options[] = {
|
|||||||
.longopt = "always-on-top",
|
.longopt = "always-on-top",
|
||||||
.text = "Make scrcpy window always on top (above other windows).",
|
.text = "Make scrcpy window always on top (above other windows).",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_ANGLE,
|
||||||
|
.longopt = "angle",
|
||||||
|
.argdesc = "degrees",
|
||||||
|
.text = "Rotate the video content by a custom angle, in degrees "
|
||||||
|
"(clockwise).",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_AUDIO_BIT_RATE,
|
.longopt_id = OPT_AUDIO_BIT_RATE,
|
||||||
.longopt = "audio-bit-rate",
|
.longopt = "audio-bit-rate",
|
||||||
@ -2689,6 +2697,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case OPT_ANGLE:
|
||||||
|
opts->angle = optarg;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
@ -107,6 +107,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.audio_dup = false,
|
.audio_dup = false,
|
||||||
.new_display = NULL,
|
.new_display = NULL,
|
||||||
.start_app = NULL,
|
.start_app = NULL,
|
||||||
|
.angle = NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_orientation
|
enum sc_orientation
|
||||||
|
@ -247,6 +247,7 @@ 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
|
||||||
|
const char *angle; // float to be parsed by the server
|
||||||
enum sc_orientation capture_orientation;
|
enum sc_orientation capture_orientation;
|
||||||
enum sc_orientation_lock capture_orientation_lock;
|
enum sc_orientation_lock capture_orientation_lock;
|
||||||
enum sc_orientation display_orientation;
|
enum sc_orientation display_orientation;
|
||||||
|
@ -428,6 +428,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.video_bit_rate = options->video_bit_rate,
|
.video_bit_rate = options->video_bit_rate,
|
||||||
.audio_bit_rate = options->audio_bit_rate,
|
.audio_bit_rate = options->audio_bit_rate,
|
||||||
.max_fps = options->max_fps,
|
.max_fps = options->max_fps,
|
||||||
|
.angle = options->angle,
|
||||||
.screen_off_timeout = options->screen_off_timeout,
|
.screen_off_timeout = options->screen_off_timeout,
|
||||||
.capture_orientation = options->capture_orientation,
|
.capture_orientation = options->capture_orientation,
|
||||||
.capture_orientation_lock = options->capture_orientation_lock,
|
.capture_orientation_lock = options->capture_orientation_lock,
|
||||||
|
@ -274,6 +274,10 @@ 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->angle) {
|
||||||
|
VALIDATE_STRING(params->angle);
|
||||||
|
ADD_PARAM("angle=%s", params->angle);
|
||||||
|
}
|
||||||
if (params->capture_orientation_lock != SC_ORIENTATION_UNLOCKED
|
if (params->capture_orientation_lock != SC_ORIENTATION_UNLOCKED
|
||||||
|| params->capture_orientation != SC_ORIENTATION_0) {
|
|| params->capture_orientation != SC_ORIENTATION_0) {
|
||||||
if (params->capture_orientation_lock == SC_ORIENTATION_LOCKED_INITIAL) {
|
if (params->capture_orientation_lock == SC_ORIENTATION_LOCKED_INITIAL) {
|
||||||
|
@ -45,6 +45,7 @@ struct sc_server_params {
|
|||||||
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
|
||||||
|
const char *angle; // float to be parsed by the server
|
||||||
sc_tick screen_off_timeout;
|
sc_tick screen_off_timeout;
|
||||||
enum sc_orientation capture_orientation;
|
enum sc_orientation capture_orientation;
|
||||||
enum sc_orientation_lock capture_orientation_lock;
|
enum sc_orientation_lock capture_orientation_lock;
|
||||||
|
11
doc/video.md
11
doc/video.md
@ -159,6 +159,17 @@ to the MP4 or MKV target file. Flipping is not supported, so only the 4 first
|
|||||||
values are allowed when recording.
|
values are allowed when recording.
|
||||||
|
|
||||||
|
|
||||||
|
## Angle
|
||||||
|
|
||||||
|
To rotate the video content by a custom angle (in degrees, clockwise):
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy --angle=23
|
||||||
|
```
|
||||||
|
|
||||||
|
The center of rotation is the center of the visible area (after cropping).
|
||||||
|
|
||||||
|
|
||||||
## Crop
|
## Crop
|
||||||
|
|
||||||
The device screen may be cropped to mirror only part of the screen.
|
The device screen may be cropped to mirror only part of the screen.
|
||||||
|
@ -34,6 +34,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 float angle;
|
||||||
private boolean tunnelForward;
|
private boolean tunnelForward;
|
||||||
private Rect crop;
|
private Rect crop;
|
||||||
private boolean control = true;
|
private boolean control = true;
|
||||||
@ -127,6 +128,10 @@ public class Options {
|
|||||||
return maxFps;
|
return maxFps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float getAngle() {
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isTunnelForward() {
|
public boolean isTunnelForward() {
|
||||||
return tunnelForward;
|
return tunnelForward;
|
||||||
}
|
}
|
||||||
@ -349,6 +354,9 @@ public class Options {
|
|||||||
case "max_fps":
|
case "max_fps":
|
||||||
options.maxFps = parseFloat("max_fps", value);
|
options.maxFps = parseFloat("max_fps", value);
|
||||||
break;
|
break;
|
||||||
|
case "angle":
|
||||||
|
options.angle = parseFloat("angle", value);
|
||||||
|
break;
|
||||||
case "tunnel_forward":
|
case "tunnel_forward":
|
||||||
options.tunnelForward = Boolean.parseBoolean(value);
|
options.tunnelForward = Boolean.parseBoolean(value);
|
||||||
break;
|
break;
|
||||||
|
@ -62,6 +62,7 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
private final boolean highSpeed;
|
private final boolean highSpeed;
|
||||||
private final Rect crop;
|
private final Rect crop;
|
||||||
private final Orientation captureOrientation;
|
private final Orientation captureOrientation;
|
||||||
|
private final float angle;
|
||||||
|
|
||||||
private String cameraId;
|
private String cameraId;
|
||||||
private Size captureSize;
|
private Size captureSize;
|
||||||
@ -88,6 +89,7 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
this.crop = options.getCrop();
|
this.crop = options.getCrop();
|
||||||
this.captureOrientation = options.getCaptureOrientation();
|
this.captureOrientation = options.getCaptureOrientation();
|
||||||
assert captureOrientation != null;
|
assert captureOrientation != null;
|
||||||
|
this.angle = options.getAngle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -131,6 +133,8 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
filter.addOrientation(captureOrientation);
|
filter.addOrientation(captureOrientation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filter.addAngle(angle);
|
||||||
|
|
||||||
transform = filter.getInverseTransform();
|
transform = filter.getInverseTransform();
|
||||||
videoSize = filter.getOutputSize().limit(maxSize).round8();
|
videoSize = filter.getOutputSize().limit(maxSize).round8();
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||||||
private final Rect crop;
|
private final Rect crop;
|
||||||
private final boolean captureOrientationLocked;
|
private final boolean captureOrientationLocked;
|
||||||
private final Orientation captureOrientation;
|
private final Orientation captureOrientation;
|
||||||
|
private final float angle;
|
||||||
|
|
||||||
private VirtualDisplay virtualDisplay;
|
private VirtualDisplay virtualDisplay;
|
||||||
private Size videoSize;
|
private Size videoSize;
|
||||||
@ -70,6 +71,7 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||||||
this.captureOrientationLocked = options.getCaptureOrientationLock() != Orientation.Lock.Unlocked;
|
this.captureOrientationLocked = options.getCaptureOrientationLock() != Orientation.Lock.Unlocked;
|
||||||
this.captureOrientation = options.getCaptureOrientation();
|
this.captureOrientation = options.getCaptureOrientation();
|
||||||
assert captureOrientation != null;
|
assert captureOrientation != null;
|
||||||
|
this.angle = options.getAngle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -122,6 +124,7 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filter.addOrientation(displayRotation, captureOrientationLocked, captureOrientation);
|
filter.addOrientation(displayRotation, captureOrientationLocked, captureOrientation);
|
||||||
|
filter.addAngle(angle);
|
||||||
|
|
||||||
eventTransform = filter.getInverseTransform();
|
eventTransform = filter.getInverseTransform();
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
private final Rect crop;
|
private final Rect crop;
|
||||||
private Orientation.Lock captureOrientationLock;
|
private Orientation.Lock captureOrientationLock;
|
||||||
private Orientation captureOrientation;
|
private Orientation captureOrientation;
|
||||||
|
private final float angle;
|
||||||
|
|
||||||
private DisplayInfo displayInfo;
|
private DisplayInfo displayInfo;
|
||||||
private Size videoSize;
|
private Size videoSize;
|
||||||
@ -55,6 +56,7 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
this.captureOrientation = options.getCaptureOrientation();
|
this.captureOrientation = options.getCaptureOrientation();
|
||||||
assert captureOrientationLock != null;
|
assert captureOrientationLock != null;
|
||||||
assert captureOrientation != null;
|
assert captureOrientation != null;
|
||||||
|
this.angle = options.getAngle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -92,6 +94,7 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
|
|
||||||
boolean locked = captureOrientationLock != Orientation.Lock.Unlocked;
|
boolean locked = captureOrientationLock != Orientation.Lock.Unlocked;
|
||||||
filter.addOrientation(displayInfo.getRotation(), locked, captureOrientation);
|
filter.addOrientation(displayInfo.getRotation(), locked, captureOrientation);
|
||||||
|
filter.addAngle(angle);
|
||||||
|
|
||||||
transform = filter.getInverseTransform();
|
transform = filter.getInverseTransform();
|
||||||
videoSize = filter.getOutputSize().limit(maxSize).round8();
|
videoSize = filter.getOutputSize().limit(maxSize).round8();
|
||||||
|
@ -95,4 +95,12 @@ public class VideoFilter {
|
|||||||
}
|
}
|
||||||
addOrientation(captureOrientation);
|
addOrientation(captureOrientation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addAngle(double cwAngle) {
|
||||||
|
if (cwAngle == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double ccwAngle = -cwAngle;
|
||||||
|
transform = AffineMatrix.rotate(ccwAngle).withAspectRatio(size).fromCenter().multiply(transform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user