Romain Vimont 2024-11-15 19:17:04 +01:00
parent d19045628e
commit adb674a5c8
15 changed files with 62 additions and 0 deletions

View File

@ -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=

View File

@ -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)'

View File

@ -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).

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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,

View File

@ -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) {

View File

@ -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;

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. 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.

View File

@ -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;

View File

@ -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();
} }

View File

@ -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();

View File

@ -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();

View File

@ -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);
}
} }