From 33b1bc62099a42cb5f9de9a7e7832184e1c848b3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 10 Apr 2026 22:43:37 +0200 Subject: [PATCH] Respect encoder maximum size constraint Encoders cannot encode at any resolution. Use video capabilities to constrain a given size by the limits of the selected encoder. These video capabilities describe the range of supported widths and heights, as well as the supported heights for a given width (and vice-versa). Use this information to compute the maximum portrait size and the maximum landscape size. PR #6766 --- .../com/genymobile/scrcpy/device/Size.java | 34 +++++++++++++------ .../scrcpy/video/SurfaceEncoder.java | 10 +++++- .../scrcpy/video/VideoConstraints.java | 30 +++++++++++++++- .../genymobile/scrcpy/device/SizeTest.java | 22 +++++++++++- 4 files changed, 83 insertions(+), 13 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/device/Size.java b/server/src/main/java/com/genymobile/scrcpy/device/Size.java index 1a3de832..a62af0af 100644 --- a/server/src/main/java/com/genymobile/scrcpy/device/Size.java +++ b/server/src/main/java/com/genymobile/scrcpy/device/Size.java @@ -39,24 +39,38 @@ public final class Size { assert alignment > 0 : "Alignment must be positive"; assert (alignment & (alignment - 1)) == 0 : "Alignment must be a power-of-two"; - int alignedMaxSize = maxSize / alignment * alignment; // round to a multiple of alignment - int w, h; + boolean portrait = width < height; + Size maxCodecSize = portrait ? constraints.getMaxCodecPortraitSize() : constraints.getMaxCodecLandscapeSize(); - if (maxSize > 0 && (width > alignedMaxSize || height > alignedMaxSize)) { - if (width > height) { - w = alignedMaxSize; - h = round(height * alignedMaxSize / width, alignment); + int maxWidth = maxCodecSize.width; + int maxHeight = maxCodecSize.height; + if (maxSize > 0) { + if (maxSize < maxWidth) { + maxWidth = maxSize; + } + if (maxSize < maxHeight) { + maxHeight = maxSize; + } + } + maxWidth = maxWidth / alignment * alignment; + maxHeight = maxHeight / alignment * alignment; + + int w, h; + if (width > maxWidth || height > maxHeight) { + if (width * maxHeight > height * maxWidth) { + w = maxWidth; + h = round(height * maxWidth / width, alignment); } else { - w = round(width * alignedMaxSize / height, alignment); - h = alignedMaxSize; + w = round(width * maxHeight / height, alignment); + h = maxHeight; } } else { w = round(width, alignment); h = round(height, alignment); } - assert maxSize == 0 || w <= maxSize : "The width cannot exceed maxSize if maxSize is aligned"; - assert maxSize == 0 || h <= maxSize : "The height cannot exceed maxSize if maxSize is aligned"; + assert w <= maxWidth : "The width cannot exceed maxWidth"; + assert h <= maxHeight : "The height cannot exceed maxHeight"; return new Size(w, h); } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java b/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java index a9304187..62e379bd 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java @@ -65,7 +65,15 @@ public class SurfaceEncoder implements AsyncProcessor { Ln.d("Actual video size alignment: " + alignment + "px"); } - return new VideoConstraints(maxSize, alignment); + int maxLandscapeWidth = caps.getSupportedWidths().getUpper(); + int maxLandscapeHeight = caps.getSupportedHeightsFor(maxLandscapeWidth).getUpper(); + Size maxLandscapeSize = new Size(maxLandscapeWidth, maxLandscapeHeight); + + int maxPortraitHeight = caps.getSupportedHeights().getUpper(); + int maxPortraitWidth = caps.getSupportedWidthsFor(maxPortraitHeight).getUpper(); + Size maxPortraitSize = new Size(maxPortraitWidth, maxPortraitHeight); + + return new VideoConstraints(maxSize, alignment, maxLandscapeSize, maxPortraitSize); } private void streamCapture() throws IOException, ConfigurationException { diff --git a/server/src/main/java/com/genymobile/scrcpy/video/VideoConstraints.java b/server/src/main/java/com/genymobile/scrcpy/video/VideoConstraints.java index 7e2837ac..748f27be 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/VideoConstraints.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/VideoConstraints.java @@ -1,16 +1,26 @@ package com.genymobile.scrcpy.video; +import com.genymobile.scrcpy.device.Size; + public class VideoConstraints { private final int maxSize; private final int alignment; + private final Size maxCodecLandscapeSize; + private final Size maxCodecPortraitSize; - public VideoConstraints(int maxSize, int alignment) { + public VideoConstraints(int maxSize, int alignment, Size maxCodecLandscapeSize, Size maxCodecPortraitSize) { assert maxSize >= 0 : "Max size must not be negative"; this.maxSize = maxSize; assert alignment > 0 : "Alignment must be positive"; assert (alignment & (alignment - 1)) == 0 : "Alignment must be a power-of-two"; this.alignment = alignment; + + assert maxCodecLandscapeSize != null; + this.maxCodecLandscapeSize = maxCodecLandscapeSize; + + assert maxCodecPortraitSize != null; + this.maxCodecPortraitSize = maxCodecPortraitSize; } /** @@ -32,4 +42,22 @@ public class VideoConstraints { public int getAlignment() { return alignment; } + + /** + * Return the max landscape size supported by the codec. + * + * @return the max landscape size + */ + public Size getMaxCodecLandscapeSize() { + return maxCodecLandscapeSize; + } + + /** + * Return the max portrait size supported by the codec. + * + * @return the max portrait size + */ + public Size getMaxCodecPortraitSize() { + return maxCodecPortraitSize; + } } diff --git a/server/src/test/java/com/genymobile/scrcpy/device/SizeTest.java b/server/src/test/java/com/genymobile/scrcpy/device/SizeTest.java index 3668ea0e..14834b8b 100644 --- a/server/src/test/java/com/genymobile/scrcpy/device/SizeTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/device/SizeTest.java @@ -8,7 +8,12 @@ import org.junit.Test; public class SizeTest { private VideoConstraints createVideoConstraints(int maxSize, int alignment) { - return new VideoConstraints(maxSize, alignment); + Size maxCodecSize = new Size(4096, 4096); + return createVideoConstraints(maxSize, alignment, maxCodecSize); + } + + private VideoConstraints createVideoConstraints(int maxSize, int alignment, Size maxCodecSize) { + return new VideoConstraints(maxSize, alignment, maxCodecSize, maxCodecSize); } @Test @@ -39,4 +44,19 @@ public class SizeTest { Assert.assertEquals(size, size.constrain(createVideoConstraints(0, 16))); Assert.assertEquals(size, size.constrain(createVideoConstraints(4096, 16))); } + + @Test + public void testConstrainMaxCodecSize() { + Size maxCodecSize = new Size(400, 600); + + Size size = new Size(314, 617); + Assert.assertEquals(new Size(314 * 600 / 617, 600), size.constrain(createVideoConstraints(0, 1, maxCodecSize))); + Assert.assertEquals(new Size(314 * 550 / 617, 550), size.constrain(createVideoConstraints(550, 1, maxCodecSize))); + + size = new Size(512, 536); + Assert.assertEquals(new Size(400, 536 * 400 / 512), size.constrain(createVideoConstraints(0, 1, maxCodecSize))); + Assert.assertEquals(new Size(400, 536 * 400 / 512), size.constrain(createVideoConstraints(550, 1, maxCodecSize))); + Assert.assertEquals(new Size(400, 536 * 400 / 512), size.constrain(createVideoConstraints(500, 1, maxCodecSize))); + Assert.assertEquals(new Size(512 * 410 / 536, 410), size.constrain(createVideoConstraints(410, 1, maxCodecSize))); + } }