Use optimal size alignment

The video was always constrained to use a size that is a multiple of 8.
This was sometimes not necessary (recent codecs only require a video
size that is a multiple of 2 or even 1) and sometimes insufficient (some
codecs require a size that is multiple of 16).

Use the size alignment required by the codec.

Fixes #4949 <https://github.com/Genymobile/scrcpy/issues/4949>
Fixes #6236 <https://github.com/Genymobile/scrcpy/issues/6236>
PR #6746 <https://github.com/Genymobile/scrcpy/pull/6746>
This commit is contained in:
Romain Vimont 2026-03-31 20:25:49 +02:00
parent 5fedc79530
commit ae8ecb1d0e
7 changed files with 37 additions and 17 deletions

View file

@ -369,7 +369,7 @@ public class Options {
options.audioDup = Boolean.parseBoolean(value);
break;
case "max_size":
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
options.maxSize = Integer.parseInt(value);
break;
case "video_bit_rate":
options.videoBitRate = Integer.parseInt(value);

View file

@ -31,7 +31,6 @@ public final class Size {
public Size limit(int maxSize) {
assert maxSize >= 0 : "Max size may not be negative";
assert maxSize % 8 == 0 : "Max size must be a multiple of 8";
if (maxSize == 0) {
// No limit
@ -55,13 +54,14 @@ public final class Size {
}
/**
* Round both dimensions of this size to be a multiple of 8 (as required by many encoders).
* Round both dimensions of this size to be multiples of {@code alignment}.
*
* @return The current size rounded.
* @param alignment the required alignment
* @return the current size rounded
*/
public Size round8() {
if (isMultipleOf8()) {
// Already a multiple of 8
public Size round(int alignment) {
if (isMultipleOf(alignment)) {
// Already aligned
return this;
}
@ -69,8 +69,8 @@ public final class Size {
int major = portrait ? height : width;
int minor = portrait ? width : height;
major &= ~7; // round down to not exceed the initial size
minor = (minor + 4) & ~7; // round to the nearest to minimize aspect ratio distortion
major = major / alignment * alignment; // round down to not exceed the initial size
minor = (minor + (alignment / 2)) / alignment * alignment; // round to the nearest to minimize aspect ratio distortion
if (minor > major) {
minor = major;
}
@ -80,8 +80,8 @@ public final class Size {
return new Size(w, h);
}
public boolean isMultipleOf8() {
return (width & 7) == 0 && (height & 7) == 0;
public boolean isMultipleOf(int alignment) {
return width % alignment == 0 && height % alignment == 0;
}
public Rect toRect() {

View file

@ -149,7 +149,7 @@ public class CameraCapture extends SurfaceCapture {
filter.addAngle(angle);
transform = filter.getInverseTransform();
videoSize = filter.getOutputSize().limit(maxSize).round8();
videoSize = filter.getOutputSize().limit(maxSize).round(getAlignment());
}
private static String selectCamera(String explicitCameraId, CameraFacing cameraFacing) throws CameraAccessException, ConfigurationException {

View file

@ -133,12 +133,13 @@ public class NewDisplayCapture extends SurfaceCapture {
filter.addOrientation(displayRotation, captureOrientationLocked, captureOrientation);
filter.addAngle(angle);
int alignment = getAlignment();
Size filteredSize = filter.getOutputSize();
if (!filteredSize.isMultipleOf8() || (maxSize != 0 && filteredSize.getMax() > maxSize)) {
if (!filteredSize.isMultipleOf(alignment) || (maxSize != 0 && filteredSize.getMax() > maxSize)) {
if (maxSize != 0) {
filteredSize = filteredSize.limit(maxSize);
}
filteredSize = filteredSize.round8();
filteredSize = filteredSize.round(alignment);
filter.addResize(filteredSize);
}

View file

@ -97,7 +97,7 @@ public class ScreenCapture extends SurfaceCapture {
filter.addAngle(angle);
transform = filter.getInverseTransform();
videoSize = filter.getOutputSize().limit(maxSize).round8();
videoSize = filter.getOutputSize().limit(maxSize).round(getAlignment());
}
@Override

View file

@ -17,6 +17,7 @@ public abstract class SurfaceCapture {
}
private CaptureListener listener;
private int alignment;
/**
* Notify the listener that the capture has been invalidated (for example, because its size changed, or due to a manual user request).
@ -28,8 +29,9 @@ public abstract class SurfaceCapture {
/**
* Called once before the first capture starts.
*/
public final void init(CaptureListener listener) throws ConfigurationException, IOException {
public final void init(CaptureListener listener, int alignment) throws ConfigurationException, IOException {
this.listener = listener;
this.alignment = alignment;
init();
}
@ -86,4 +88,15 @@ public abstract class SurfaceCapture {
public boolean isClosed() {
return false;
}
/**
* Return the video alignment
* <p>
* This a power-of-2 value that the video width and height must be multiples of.
*
* @return the video alignment
*/
protected int getAlignment() {
return alignment;
}
}

View file

@ -65,9 +65,15 @@ public class SurfaceEncoder implements AsyncProcessor {
private void streamCapture() throws IOException, ConfigurationException {
Codec codec = streamer.getCodec();
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
MediaCodecInfo.VideoCapabilities caps = mediaCodec.getCodecInfo().getCapabilitiesForType(codec.getMimeType())
.getVideoCapabilities();
int alignment = caps != null ? Math.max(caps.getWidthAlignment(), caps.getHeightAlignment()) : 8;
Ln.d("Video codec size alignment requirement: " + alignment + "px");
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
capture.init(reset);
capture.init(reset, alignment);
try {
boolean alive;