mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-04-21 01:33:36 +00:00
Add resizable virtual display feature
Introduce `--flex-display` to continuously resize the virtual display to match the window.
This commit is contained in:
parent
00c941ab03
commit
3ca88616d2
18 changed files with 219 additions and 19 deletions
|
|
@ -66,6 +66,7 @@ public class Options {
|
|||
private NewDisplay newDisplay;
|
||||
private boolean vdDestroyContent = true;
|
||||
private boolean vdSystemDecorations = true;
|
||||
private boolean flexDisplay;
|
||||
|
||||
private Orientation.Lock captureOrientationLock = Orientation.Lock.Unlocked;
|
||||
private Orientation captureOrientation = Orientation.Orient0;
|
||||
|
|
@ -258,6 +259,10 @@ public class Options {
|
|||
return vdSystemDecorations;
|
||||
}
|
||||
|
||||
public boolean getFlexDisplay() {
|
||||
return flexDisplay;
|
||||
}
|
||||
|
||||
public boolean getList() {
|
||||
return listEncoders || listDisplays || listCameras || listCameraSizes || listApps;
|
||||
}
|
||||
|
|
@ -506,6 +511,9 @@ public class Options {
|
|||
case "vd_system_decorations":
|
||||
options.vdSystemDecorations = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "flex_display":
|
||||
options.flexDisplay = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "capture_orientation":
|
||||
Pair<Orientation.Lock, Orientation> pair = parseCaptureOrientation(value);
|
||||
options.captureOrientationLock = pair.first;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.genymobile.scrcpy.util.Ln;
|
|||
import com.genymobile.scrcpy.util.LogUtils;
|
||||
import com.genymobile.scrcpy.video.CameraCapture;
|
||||
import com.genymobile.scrcpy.video.CaptureControl;
|
||||
import com.genymobile.scrcpy.video.NewDisplayCapture;
|
||||
import com.genymobile.scrcpy.video.SurfaceCapture;
|
||||
import com.genymobile.scrcpy.video.VideoSource;
|
||||
import com.genymobile.scrcpy.video.VirtualDisplayListener;
|
||||
|
|
@ -370,6 +371,9 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||
case ControlMessage.TYPE_START_APP:
|
||||
startAppAsync(msg.getText());
|
||||
return true;
|
||||
case ControlMessage.TYPE_RESIZE_DISPLAY:
|
||||
resizeDisplay(msg.getWidth(), msg.getHeight());
|
||||
return true;
|
||||
default:
|
||||
// fall through
|
||||
}
|
||||
|
|
@ -819,4 +823,9 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||
surfaceCapture.getCaptureControl().reset(CaptureControl.RESET_REASON_CLIENT_RESET);
|
||||
}
|
||||
}
|
||||
|
||||
private void resizeDisplay(int width, int height) {
|
||||
NewDisplayCapture newDisplayCapture = (NewDisplayCapture) surfaceCapture;
|
||||
newDisplayCapture.resizeDisplay(width, height);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,4 +46,9 @@ public enum Orientation {
|
|||
public int getRotation() {
|
||||
return ordinal() & 3;
|
||||
}
|
||||
|
||||
public boolean isSwap() {
|
||||
// width and height are swapped on 90-degree rotations
|
||||
return (ordinal() & 1) != 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,10 @@ public final class Size {
|
|||
return new Size(w, h);
|
||||
}
|
||||
|
||||
public boolean isAligned(int alignment) {
|
||||
return width / alignment * alignment == width && height / alignment * alignment == height;
|
||||
}
|
||||
|
||||
private static int round(int value, int alignment) {
|
||||
return (value + (alignment / 2)) / alignment * alignment;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ public class CaptureControl {
|
|||
public static final int RESET_REASON_TERMINATE = 1;
|
||||
public static final int RESET_REASON_SIZE_CHANGED = 1 << 1;
|
||||
public static final int RESET_REASON_CLIENT_RESET = 1 << 2;
|
||||
public static final int RESET_REASON_CLIENT_RESIZED = 1 << 2;
|
||||
|
||||
private int reset = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -58,12 +58,16 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||
private final float angle;
|
||||
private final boolean vdDestroyContent;
|
||||
private final boolean vdSystemDecorations;
|
||||
private final boolean flexDisplay;
|
||||
|
||||
private VirtualDisplay virtualDisplay;
|
||||
private Size videoSize;
|
||||
private Size displaySize; // the logical size of the display (including rotation)
|
||||
private Size physicalSize; // the physical size of the display (without rotation)
|
||||
|
||||
private Size pendingClientResized;
|
||||
private Size latestSize;
|
||||
|
||||
private int dpi;
|
||||
|
||||
public NewDisplayCapture(VirtualDisplayListener vdListener, Options options) {
|
||||
|
|
@ -79,13 +83,30 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||
this.angle = options.getAngle();
|
||||
this.vdDestroyContent = options.getVDDestroyContent();
|
||||
this.vdSystemDecorations = options.getVDSystemDecorations();
|
||||
this.flexDisplay = options.getFlexDisplay();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
if (flexDisplay && crop != null) {
|
||||
throw new IllegalArgumentException("Flex display does not support cropping");
|
||||
}
|
||||
if (getVideoConstraints().getMaxSize() != 0) {
|
||||
// A maxSize request constrains the resulting size while preserving the aspect ratio, which is meaningless for a flex display
|
||||
throw new IllegalArgumentException("Flex display does not support explicit maxSize constraint");
|
||||
}
|
||||
|
||||
displaySize = newDisplay.getSize();
|
||||
dpi = newDisplay.getDpi();
|
||||
if (displaySize == null || dpi == 0) {
|
||||
if (flexDisplay) {
|
||||
// Hardcode default values if not defined
|
||||
if (displaySize == null) {
|
||||
displaySize = new Size(1280, 960);
|
||||
}
|
||||
if (dpi == 0) {
|
||||
dpi = 160;
|
||||
}
|
||||
} else if (displaySize == null || dpi == 0) {
|
||||
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(0);
|
||||
if (displayInfo != null) {
|
||||
mainDisplaySize = displayInfo.getSize();
|
||||
|
|
@ -103,16 +124,20 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||
|
||||
@Override
|
||||
public void prepare() {
|
||||
VideoConstraints constraints = getVideoConstraints();
|
||||
|
||||
int displayRotation;
|
||||
if (virtualDisplay == null) {
|
||||
if (!newDisplay.hasExplicitSize()) {
|
||||
if (displaySize == null) {
|
||||
assert !flexDisplay;
|
||||
displaySize = mainDisplaySize;
|
||||
}
|
||||
|
||||
// Align the physical display size to avoid unnecessary mismatches with the output size
|
||||
displaySize = displaySize.align(getVideoConstraints().getAlignment());
|
||||
|
||||
if (!newDisplay.hasExplicitDpi()) {
|
||||
if (dpi == 0) {
|
||||
assert !flexDisplay;
|
||||
dpi = scaleDpi(mainDisplaySize, mainDisplayDpi, displaySize);
|
||||
}
|
||||
|
||||
|
|
@ -124,8 +149,27 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||
dpi = displayInfo.getDpi();
|
||||
displayRotation = displayInfo.getRotation();
|
||||
|
||||
// Align the physical display size to avoid unnecessary mismatches with the output size
|
||||
displaySize = displayInfo.getSize().align(getVideoConstraints().getAlignment());
|
||||
Size pendingClientResized = consumeClientResized();
|
||||
if (pendingClientResized != null) {
|
||||
assert pendingClientResized.isAligned(getVideoConstraints().getAlignment()) : "pendingClientResized ust be aligned";
|
||||
displaySize = pendingClientResized;
|
||||
if (captureOrientation.isSwap()) {
|
||||
displaySize = displaySize.rotate();
|
||||
}
|
||||
Size vdSize = displaySize;
|
||||
if ((displayRotation % 2) != 0) {
|
||||
vdSize = vdSize.rotate();
|
||||
}
|
||||
|
||||
displayMonitor.expectChange(new DisplayProperties(vdSize, displayRotation));
|
||||
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||
Ln.v(getClass().getSimpleName() + ": virtualDisplay.resize(" + vdSize.getWidth() + ", " + vdSize.getHeight() + ")");
|
||||
}
|
||||
virtualDisplay.resize(vdSize.getWidth(), vdSize.getHeight(), dpi);
|
||||
} else {
|
||||
// Align the physical display size to avoid unnecessary mismatches with the output size
|
||||
displaySize = displayInfo.getSize().align(getVideoConstraints().getAlignment());
|
||||
}
|
||||
}
|
||||
|
||||
VideoFilter filter = new VideoFilter(displaySize);
|
||||
|
|
@ -139,7 +183,7 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||
filter.addAngle(angle);
|
||||
|
||||
Size outputSize = filter.getOutputSize();
|
||||
Size filteredSize = outputSize.constrain(getVideoConstraints());
|
||||
Size filteredSize = outputSize.constrain(constraints);
|
||||
if (!filteredSize.equals(outputSize)) {
|
||||
filter.addResize(filteredSize);
|
||||
}
|
||||
|
|
@ -148,6 +192,7 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||
|
||||
// DisplayInfo gives the oriented size (so videoSize includes the display rotation)
|
||||
videoSize = filter.getOutputSize();
|
||||
setLatestSize(videoSize);
|
||||
|
||||
// However, the virtual display video always remains in its original orientation, so it must be rotated manually.
|
||||
// This additional display rotation must not be included in the input events transform (the expected coordinates are already in the
|
||||
|
|
@ -257,4 +302,51 @@ public class NewDisplayCapture extends SurfaceCapture {
|
|||
int num = size.getMax();
|
||||
return initialDpi * num / den;
|
||||
}
|
||||
|
||||
public synchronized void resizeDisplay(int width, int height) {
|
||||
if (!flexDisplay) {
|
||||
throw new IllegalStateException("Cannot resize a non-flex display");
|
||||
}
|
||||
|
||||
Size newSize = new Size(width, height).constrain(getVideoConstraints());
|
||||
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||
Ln.v(getClass().getSimpleName() + ": resizeDisplay(" + width + ", " + height + ")");
|
||||
Ln.v(getClass().getSimpleName() + ": constrained size=" + newSize);
|
||||
}
|
||||
if (newSize.equals(pendingClientResized)) {
|
||||
// Already requested
|
||||
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||
Ln.v(getClass().getSimpleName() + ": new size already requested (" + newSize + ")");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Size latestSize = getLatestSize(); // with synchro
|
||||
if (newSize.equals(latestSize)) {
|
||||
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||
Ln.v(getClass().getSimpleName() + ": requested new size (" + newSize + ") is already the latest one");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
pendingClientResized = newSize;
|
||||
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||
Ln.v(getClass().getSimpleName() + ": reset (" + newSize + ")");
|
||||
}
|
||||
getCaptureControl().reset(CaptureControl.RESET_REASON_CLIENT_RESIZED);
|
||||
}
|
||||
|
||||
private synchronized Size consumeClientResized() {
|
||||
Size size = pendingClientResized;
|
||||
pendingClientResized = null;
|
||||
return size;
|
||||
}
|
||||
|
||||
private synchronized Size getLatestSize() {
|
||||
return latestSize;
|
||||
}
|
||||
|
||||
private synchronized void setLatestSize(Size latestSize) {
|
||||
this.latestSize = latestSize;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,7 +128,11 @@ public class SurfaceEncoder implements AsyncProcessor {
|
|||
} else {
|
||||
if (!captureControl.isResetRequested()) {
|
||||
// If a reset is requested during encode(), it will interrupt the encoding by an EOS
|
||||
streamer.writeSessionMeta(size.getWidth(), size.getHeight(), false);
|
||||
|
||||
// The reset is due to a resize initiated by the client
|
||||
boolean clientResize = (resetReasons & CaptureControl.RESET_REASON_CLIENT_RESIZED) != 0
|
||||
&& (resetReasons & CaptureControl.RESET_REASON_SIZE_CHANGED) == 0;
|
||||
streamer.writeSessionMeta(size.getWidth(), size.getHeight(), clientResize);
|
||||
encode(mediaCodec, streamer);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue