mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-12-18 05:54:20 +01:00
Add option to lock video orientation
PR #1151 <https://github.com/Genymobile/scrcpy/pull/1151> Signed-off-by: Romain Vimont <rom@rom1v.com>
This commit is contained in:
committed by
Romain Vimont
parent
ef56cc6ff7
commit
1982bc439b
@@ -22,10 +22,12 @@ public final class Device {
|
||||
|
||||
private final ServiceManager serviceManager = new ServiceManager();
|
||||
|
||||
private final int lockedVideoOrientation;
|
||||
private ScreenInfo screenInfo;
|
||||
private RotationListener rotationListener;
|
||||
|
||||
public Device(Options options) {
|
||||
lockedVideoOrientation = options.getLockedVideoOrientation();
|
||||
screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize());
|
||||
registerRotationWatcher(new IRotationWatcher.Stub() {
|
||||
@Override
|
||||
@@ -48,11 +50,11 @@ public final class Device {
|
||||
|
||||
private ScreenInfo computeScreenInfo(Rect crop, int maxSize) {
|
||||
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
|
||||
boolean rotated = (displayInfo.getRotation() & 1) != 0;
|
||||
int rotation = displayInfo.getRotation();
|
||||
Size deviceSize = displayInfo.getSize();
|
||||
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
||||
if (crop != null) {
|
||||
if (rotated) {
|
||||
if (rotation % 2 != 0) { // 180s preserve dimensions
|
||||
// the crop (provided by the user) is expressed in the natural orientation
|
||||
crop = flipRect(crop);
|
||||
}
|
||||
@@ -64,7 +66,7 @@ public final class Device {
|
||||
}
|
||||
|
||||
Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize);
|
||||
return new ScreenInfo(contentRect, videoSize, rotated);
|
||||
return new ScreenInfo(contentRect, videoSize, rotation);
|
||||
}
|
||||
|
||||
private static String formatCrop(Rect rect) {
|
||||
@@ -99,22 +101,56 @@ public final class Device {
|
||||
return new Size(w, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rotation to apply to the device rotation to get the requested locked video orientation
|
||||
*
|
||||
* @param deviceRotation the device rotation
|
||||
* @return the rotation offset
|
||||
*/
|
||||
public int getVideoRotation(int deviceRotation) {
|
||||
if (lockedVideoOrientation == -1) {
|
||||
// no offset
|
||||
return 0;
|
||||
}
|
||||
return (deviceRotation + 4 - lockedVideoOrientation) % 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rotation to apply to the requested locked video orientation to get the device rotation
|
||||
*
|
||||
* @param deviceRotation the device rotation
|
||||
* @return the (reverse) rotation offset
|
||||
*/
|
||||
private int getReverseVideoRotation(int deviceRotation) {
|
||||
if (lockedVideoOrientation == -1) {
|
||||
// no offset
|
||||
return 0;
|
||||
}
|
||||
return (lockedVideoOrientation + 4 - deviceRotation) % 4;
|
||||
}
|
||||
|
||||
public Point getPhysicalPoint(Position position) {
|
||||
// it hides the field on purpose, to read it with a lock
|
||||
@SuppressWarnings("checkstyle:HiddenField")
|
||||
ScreenInfo screenInfo = getScreenInfo(); // read with synchronization
|
||||
Size videoSize = screenInfo.getVideoSize();
|
||||
Size clientVideoSize = position.getScreenSize();
|
||||
|
||||
int deviceRotation = screenInfo.getRotation();
|
||||
int reverseVideoRotation = getReverseVideoRotation(deviceRotation);
|
||||
// reverse the video rotation to apply the events
|
||||
Position devicePosition = position.rotate(reverseVideoRotation);
|
||||
|
||||
Size clientVideoSize = devicePosition.getScreenSize();
|
||||
if (!videoSize.equals(clientVideoSize)) {
|
||||
// The client sends a click relative to a video with wrong dimensions,
|
||||
// the device may have been rotated since the event was generated, so ignore the event
|
||||
return null;
|
||||
}
|
||||
Rect contentRect = screenInfo.getContentRect();
|
||||
Point point = position.getPoint();
|
||||
int scaledX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth();
|
||||
int scaledY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight();
|
||||
return new Point(scaledX, scaledY);
|
||||
Point point = devicePosition.getPoint();
|
||||
int convertedX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth();
|
||||
int convertedY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight();
|
||||
return new Point(convertedX, convertedY);
|
||||
}
|
||||
|
||||
public static String getDeviceName() {
|
||||
|
||||
@@ -6,6 +6,7 @@ public class Options {
|
||||
private int maxSize;
|
||||
private int bitRate;
|
||||
private int maxFps;
|
||||
private int lockedVideoOrientation;
|
||||
private boolean tunnelForward;
|
||||
private Rect crop;
|
||||
private boolean sendFrameMeta; // send PTS so that the client may record properly
|
||||
@@ -35,6 +36,14 @@ public class Options {
|
||||
this.maxFps = maxFps;
|
||||
}
|
||||
|
||||
public int getLockedVideoOrientation() {
|
||||
return lockedVideoOrientation;
|
||||
}
|
||||
|
||||
public void setLockedVideoOrientation(int lockedVideoOrientation) {
|
||||
this.lockedVideoOrientation = lockedVideoOrientation;
|
||||
}
|
||||
|
||||
public boolean isTunnelForward() {
|
||||
return tunnelForward;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,19 @@ public class Position {
|
||||
return screenSize;
|
||||
}
|
||||
|
||||
public Position rotate(int rotation) {
|
||||
switch (rotation) {
|
||||
case 1:
|
||||
return new Position(new Point(screenSize.getHeight() - point.getY(), point.getX()), screenSize.rotate());
|
||||
case 2:
|
||||
return new Position(new Point(screenSize.getWidth() - point.getX(), screenSize.getHeight() - point.getY()), screenSize);
|
||||
case 3:
|
||||
return new Position(new Point(point.getY(), screenSize.getWidth() - point.getX()), screenSize.rotate());
|
||||
default:
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
|
||||
@@ -28,19 +28,21 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
|
||||
private int bitRate;
|
||||
private int maxFps;
|
||||
private int lockedVideoOrientation;
|
||||
private int iFrameInterval;
|
||||
private boolean sendFrameMeta;
|
||||
private long ptsOrigin;
|
||||
|
||||
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int iFrameInterval) {
|
||||
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int iFrameInterval) {
|
||||
this.sendFrameMeta = sendFrameMeta;
|
||||
this.bitRate = bitRate;
|
||||
this.maxFps = maxFps;
|
||||
this.lockedVideoOrientation = lockedVideoOrientation;
|
||||
this.iFrameInterval = iFrameInterval;
|
||||
}
|
||||
|
||||
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) {
|
||||
this(sendFrameMeta, bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL);
|
||||
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation) {
|
||||
this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, DEFAULT_I_FRAME_INTERVAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,10 +68,11 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
ScreenInfo screenInfo = device.getScreenInfo();
|
||||
Rect contentRect = screenInfo.getContentRect();
|
||||
Rect videoRect = screenInfo.getVideoSize().toRect();
|
||||
setSize(format, videoRect.width(), videoRect.height());
|
||||
int videoRotation = device.getVideoRotation(screenInfo.getRotation());
|
||||
setSize(format, videoRotation, videoRect.width(), videoRect.height());
|
||||
configure(codec, format);
|
||||
Surface surface = codec.createInputSurface();
|
||||
setDisplaySurface(display, surface, contentRect, videoRect);
|
||||
setDisplaySurface(display, surface, videoRotation, contentRect, videoRect);
|
||||
codec.start();
|
||||
try {
|
||||
alive = encode(codec, fd);
|
||||
@@ -168,16 +171,21 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
}
|
||||
|
||||
private static void setSize(MediaFormat format, int width, int height) {
|
||||
format.setInteger(MediaFormat.KEY_WIDTH, width);
|
||||
format.setInteger(MediaFormat.KEY_HEIGHT, height);
|
||||
private static void setSize(MediaFormat format, int orientation, int width, int height) {
|
||||
if (orientation % 2 == 0) {
|
||||
format.setInteger(MediaFormat.KEY_WIDTH, width);
|
||||
format.setInteger(MediaFormat.KEY_HEIGHT, height);
|
||||
return;
|
||||
}
|
||||
format.setInteger(MediaFormat.KEY_WIDTH, height);
|
||||
format.setInteger(MediaFormat.KEY_HEIGHT, width);
|
||||
}
|
||||
|
||||
private static void setDisplaySurface(IBinder display, Surface surface, Rect deviceRect, Rect displayRect) {
|
||||
private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect) {
|
||||
SurfaceControl.openTransaction();
|
||||
try {
|
||||
SurfaceControl.setDisplaySurface(display, surface);
|
||||
SurfaceControl.setDisplayProjection(display, 0, deviceRect, displayRect);
|
||||
SurfaceControl.setDisplayProjection(display, orientation, deviceRect, displayRect);
|
||||
SurfaceControl.setDisplayLayerStack(display, 0);
|
||||
} finally {
|
||||
SurfaceControl.closeTransaction();
|
||||
|
||||
@@ -5,12 +5,12 @@ import android.graphics.Rect;
|
||||
public final class ScreenInfo {
|
||||
private final Rect contentRect; // device size, possibly cropped
|
||||
private final Size videoSize;
|
||||
private final boolean rotated;
|
||||
private final int rotation;
|
||||
|
||||
public ScreenInfo(Rect contentRect, Size videoSize, boolean rotated) {
|
||||
public ScreenInfo(Rect contentRect, Size videoSize, int rotation) {
|
||||
this.contentRect = contentRect;
|
||||
this.videoSize = videoSize;
|
||||
this.rotated = rotated;
|
||||
this.rotation = rotation;
|
||||
}
|
||||
|
||||
public Rect getContentRect() {
|
||||
@@ -21,11 +21,25 @@ public final class ScreenInfo {
|
||||
return videoSize;
|
||||
}
|
||||
|
||||
public ScreenInfo withRotation(int rotation) {
|
||||
boolean newRotated = (rotation & 1) != 0;
|
||||
if (rotated == newRotated) {
|
||||
public int getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public ScreenInfo withRotation(int newRotation) {
|
||||
if (newRotation == rotation) {
|
||||
return this;
|
||||
}
|
||||
return new ScreenInfo(Device.flipRect(contentRect), videoSize.rotate(), newRotated);
|
||||
// true if changed between portrait and landscape
|
||||
boolean orientationChanged = (rotation + newRotation) % 2 != 0;
|
||||
Rect newContentRect;
|
||||
Size newVideoSize;
|
||||
if (orientationChanged) {
|
||||
newContentRect = Device.flipRect(contentRect);
|
||||
newVideoSize = videoSize.rotate();
|
||||
} else {
|
||||
newContentRect = contentRect;
|
||||
newVideoSize = videoSize;
|
||||
}
|
||||
return new ScreenInfo(newContentRect, newVideoSize, newRotation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ public final class Server {
|
||||
final Device device = new Device(options);
|
||||
boolean tunnelForward = options.isTunnelForward();
|
||||
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
|
||||
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps());
|
||||
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(),
|
||||
options.getLockedVideoOrientation());
|
||||
|
||||
if (options.getControl()) {
|
||||
Controller controller = new Controller(device, connection);
|
||||
@@ -79,8 +80,8 @@ public final class Server {
|
||||
"The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")");
|
||||
}
|
||||
|
||||
if (args.length != 8) {
|
||||
throw new IllegalArgumentException("Expecting 8 parameters");
|
||||
if (args.length != 9) {
|
||||
throw new IllegalArgumentException("Expecting 9 parameters");
|
||||
}
|
||||
|
||||
Options options = new Options();
|
||||
@@ -94,17 +95,20 @@ public final class Server {
|
||||
int maxFps = Integer.parseInt(args[3]);
|
||||
options.setMaxFps(maxFps);
|
||||
|
||||
int lockedVideoOrientation = Integer.parseInt(args[4]);
|
||||
options.setLockedVideoOrientation(lockedVideoOrientation);
|
||||
|
||||
// use "adb forward" instead of "adb tunnel"? (so the server must listen)
|
||||
boolean tunnelForward = Boolean.parseBoolean(args[4]);
|
||||
boolean tunnelForward = Boolean.parseBoolean(args[5]);
|
||||
options.setTunnelForward(tunnelForward);
|
||||
|
||||
Rect crop = parseCrop(args[5]);
|
||||
Rect crop = parseCrop(args[6]);
|
||||
options.setCrop(crop);
|
||||
|
||||
boolean sendFrameMeta = Boolean.parseBoolean(args[6]);
|
||||
boolean sendFrameMeta = Boolean.parseBoolean(args[7]);
|
||||
options.setSendFrameMeta(sendFrameMeta);
|
||||
|
||||
boolean control = Boolean.parseBoolean(args[7]);
|
||||
boolean control = Boolean.parseBoolean(args[8]);
|
||||
options.setControl(control);
|
||||
|
||||
return options;
|
||||
|
||||
Reference in New Issue
Block a user