Add --angle

Add an option to rotate the video content by a custom angle.
This commit is contained in:
Romain Vimont 2024-11-15 19:17:04 +01:00
parent 5eaf072a85
commit 1ccc7b201d
15 changed files with 62 additions and 0 deletions

View File

@ -2,6 +2,7 @@ _scrcpy() {
local cur prev words cword
local opts="
--always-on-top
--angle
--audio-bit-rate=
--audio-buffer=
--audio-codec=

View File

@ -9,6 +9,7 @@ local arguments
arguments=(
'--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-buffer=[Configure the audio buffering delay (in milliseconds)]'
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'

View File

@ -19,6 +19,10 @@ provides display and control of Android devices connected on USB (or over TCP/IP
.B \-\-always\-on\-top
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
.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).

View File

@ -108,6 +108,7 @@ enum {
OPT_START_APP,
OPT_SCREEN_OFF_TIMEOUT,
OPT_CAPTURE_ORIENTATION,
OPT_ANGLE,
};
struct sc_option {
@ -149,6 +150,13 @@ static const struct sc_option options[] = {
.longopt = "always-on-top",
.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 = "audio-bit-rate",
@ -2691,6 +2699,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
break;
case OPT_ANGLE:
opts->angle = optarg;
break;
default:
// getopt prints the error message on stderr
return false;

View File

@ -107,6 +107,7 @@ const struct scrcpy_options scrcpy_options_default = {
.audio_dup = false,
.new_display = NULL,
.start_app = NULL,
.angle = NULL,
};
enum sc_orientation

View File

@ -247,6 +247,7 @@ struct scrcpy_options {
uint32_t video_bit_rate;
uint32_t audio_bit_rate;
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_lock capture_orientation_lock;
enum sc_orientation display_orientation;

View File

@ -428,6 +428,7 @@ scrcpy(struct scrcpy_options *options) {
.video_bit_rate = options->video_bit_rate,
.audio_bit_rate = options->audio_bit_rate,
.max_fps = options->max_fps,
.angle = options->angle,
.screen_off_timeout = options->screen_off_timeout,
.capture_orientation = options->capture_orientation,
.capture_orientation_lock = options->capture_orientation_lock,

View File

@ -274,6 +274,10 @@ execute_server(struct sc_server *server,
VALIDATE_STRING(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
|| params->capture_orientation != SC_ORIENTATION_0) {
if (params->capture_orientation_lock == SC_ORIENTATION_LOCKED_INITIAL) {

View File

@ -45,6 +45,7 @@ struct sc_server_params {
uint32_t video_bit_rate;
uint32_t audio_bit_rate;
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;
enum sc_orientation capture_orientation;
enum sc_orientation_lock capture_orientation_lock;

View File

@ -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.
## 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
The device screen may be cropped to mirror only part of the screen.

View File

@ -34,6 +34,7 @@ public class Options {
private int videoBitRate = 8000000;
private int audioBitRate = 128000;
private float maxFps;
private float angle;
private boolean tunnelForward;
private Rect crop;
private boolean control = true;
@ -127,6 +128,10 @@ public class Options {
return maxFps;
}
public float getAngle() {
return angle;
}
public boolean isTunnelForward() {
return tunnelForward;
}
@ -349,6 +354,9 @@ public class Options {
case "max_fps":
options.maxFps = parseFloat("max_fps", value);
break;
case "angle":
options.angle = parseFloat("angle", value);
break;
case "tunnel_forward":
options.tunnelForward = Boolean.parseBoolean(value);
break;

View File

@ -62,6 +62,7 @@ public class CameraCapture extends SurfaceCapture {
private final boolean highSpeed;
private final Rect crop;
private final Orientation captureOrientation;
private final float angle;
private String cameraId;
private Size captureSize;
@ -88,6 +89,7 @@ public class CameraCapture extends SurfaceCapture {
this.crop = options.getCrop();
this.captureOrientation = options.getCaptureOrientation();
assert captureOrientation != null;
this.angle = options.getAngle();
}
@Override
@ -131,6 +133,8 @@ public class CameraCapture extends SurfaceCapture {
filter.addOrientation(captureOrientation);
}
filter.addAngle(angle);
transform = filter.getInverseTransform();
videoSize = filter.getOutputSize().limit(maxSize).round8();
}

View File

@ -56,6 +56,7 @@ public class NewDisplayCapture extends DisplayCapture {
private final Rect crop;
private final boolean captureOrientationLocked;
private final Orientation captureOrientation;
private final float angle;
private VirtualDisplay virtualDisplay;
private Size videoSize;
@ -74,6 +75,7 @@ public class NewDisplayCapture extends DisplayCapture {
this.captureOrientationLocked = options.getCaptureOrientationLock() != Orientation.Lock.Unlocked;
this.captureOrientation = options.getCaptureOrientation();
assert captureOrientation != null;
this.angle = options.getAngle();
}
@Override
@ -126,6 +128,7 @@ public class NewDisplayCapture extends DisplayCapture {
}
filter.addOrientation(displayRotation, captureOrientationLocked, captureOrientation);
filter.addAngle(angle);
eventTransform = filter.getInverseTransform();

View File

@ -38,6 +38,7 @@ public class ScreenCapture extends DisplayCapture {
private final Rect crop;
private Orientation.Lock captureOrientationLock;
private Orientation captureOrientation;
private final float angle;
private DisplayInfo displayInfo;
private Size videoSize;
@ -68,6 +69,7 @@ public class ScreenCapture extends DisplayCapture {
this.captureOrientation = options.getCaptureOrientation();
assert captureOrientationLock != null;
assert captureOrientation != null;
this.angle = options.getAngle();
}
@Override
@ -126,6 +128,7 @@ public class ScreenCapture extends DisplayCapture {
boolean locked = captureOrientationLock != Orientation.Lock.Unlocked;
filter.addOrientation(displayInfo.getRotation(), locked, captureOrientation);
filter.addAngle(angle);
transform = filter.getInverseTransform();
videoSize = filter.getOutputSize().limit(maxSize).round8();

View File

@ -89,4 +89,12 @@ public class VideoFilter {
}
addOrientation(captureOrientation);
}
public void addAngle(double cwAngle) {
if (cwAngle == 0) {
return;
}
double ccwAngle = -cwAngle;
transform = AffineMatrix.rotate(ccwAngle).withAspectRatio(size).fromCenter().multiply(transform);
}
}