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 <https://github.com/Genymobile/scrcpy/pull/6766>
This commit is contained in:
Romain Vimont 2026-04-10 22:43:37 +02:00
parent 809718ed25
commit 33b1bc6209
4 changed files with 83 additions and 13 deletions

View file

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

View file

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

View file

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

View file

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