From e40b6ca301adc96f6aabbc4d636bbc935d6b819e Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 18 May 2026 23:22:42 +0200 Subject: [PATCH 1/2] Add debug logs for video encoder constraints --- .../main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java | 3 +++ 1 file changed, 3 insertions(+) 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 1ccb775abc..870a6fe7eb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java @@ -78,14 +78,17 @@ private static VideoConstraints createVideoConstraints(int maxSize, int minSizeA int maxLandscapeWidth = caps.getSupportedWidths().getUpper(); int maxLandscapeHeight = caps.getSupportedHeightsFor(maxLandscapeWidth).getUpper(); Size maxLandscapeSize = new Size(maxLandscapeWidth, maxLandscapeHeight); + Ln.d("Maximum landscape size: " + maxLandscapeSize); int maxPortraitHeight = caps.getSupportedHeights().getUpper(); int maxPortraitWidth = caps.getSupportedWidthsFor(maxPortraitHeight).getUpper(); Size maxPortraitSize = new Size(maxPortraitWidth, maxPortraitHeight); + Ln.d("Maximum portrait size: " + maxPortraitSize); int minWidth = caps.getSupportedWidths().getLower(); int minHeight = caps.getSupportedHeights().getLower(); int minSize = Math.max(minWidth, minHeight); + Ln.d("Minimum size: " + minSize); return new VideoConstraints(maxSize, alignment, maxLandscapeSize, maxPortraitSize, minSize); } From e781bdb5b5cf700860b14c8ca2b73fa25c19522b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 18 May 2026 23:24:32 +0200 Subject: [PATCH 2/2] Add --ignore-video-encoder-constraints Some encoders report incorrect capabilities. Add an option to ignore these constraints. Fixes #6829 Fixes #6848 --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 8 +++ app/src/cli.c | 11 ++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 2 + app/src/server.c | 3 + app/src/server.h | 1 + .../java/com/genymobile/scrcpy/Options.java | 8 +++ .../scrcpy/video/SurfaceEncoder.java | 61 ++++++++++++------- 11 files changed, 77 insertions(+), 21 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 80551d7765..abd9ceba55 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -34,6 +34,7 @@ _scrcpy() { -G --gamepad= -h --help + --ignore-video-encoder-constraints -K --keep-active --keyboard= diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index e5986af726..8a597ac688 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -41,6 +41,7 @@ arguments=( '-G[Use UHID/AOA gamepad \(same as --gamepad=uhid or --gamepad=aoa, depending on OTG mode\)]' '--gamepad=[Set the gamepad input mode]:mode:(disabled uhid aoa)' {-h,--help}'[Print the help]' + '--ignore-video-encoder-constraints[Do not consider video encoder capabilities]' '-K[Use UHID/AOA keyboard \(same as --keyboard=uhid or --keyboard=aoa, depending on OTG mode\)]' '--keep-active[Keep the screen on by simulating user activity]' '--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 644206cb82..2dd62479b2 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -235,6 +235,14 @@ Also see \fB\-\-keyboard\fR and R\fB\-\-mouse\fR. .B \-h, \-\-help Print this help. +.TP +.B \-\-ignore\-video\-encoder\-constraints +Do not consider video encoder capabilities. + +This is useful if the reported capabilities are incorrect. + +It may help to force a value for \fB\-\-min\-size\-alignment\fR. + .TP .B \-K Same as \fB\-\-keyboard=uhid\fR, or \fB\-\-keyboard=aoa\fR if \fB\-\-otg\fR is set. diff --git a/app/src/cli.c b/app/src/cli.c index 785e8b9f09..0e6e13d7c6 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -109,6 +109,7 @@ enum { OPT_KEEP_ACTIVE, OPT_BACKGROUND_COLOR, OPT_RENDER_FIT, + OPT_IGNORE_VIDEO_ENCODER_CONSTRAINTS, }; struct sc_option { @@ -428,6 +429,13 @@ static const struct sc_option options[] = { .longopt = "help", .text = "Print this help.", }, + { + .longopt_id = OPT_IGNORE_VIDEO_ENCODER_CONSTRAINTS, + .longopt = "ignore-video-encoder-constraints", + .text = "Do not consider video encoder capabilities.\n" + "This is useful if the reported capabilities are incorrect.\n" + "It may help to force a value for --min-size-alignment.", + }, { .shortopt = 'K', .text = "Same as --keyboard=uhid, or --keyboard=aoa if --otg is set.", @@ -2917,6 +2925,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case 'x': opts->flex_display = true; break; + case OPT_IGNORE_VIDEO_ENCODER_CONSTRAINTS: + opts->ignore_video_encoder_constraints = true; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/options.c b/app/src/options.c index 55a7089817..0788ac7e64 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -121,6 +121,7 @@ const struct scrcpy_options scrcpy_options_default = { .camera_torch = false, .keep_active = false, .flex_display = false, + .ignore_video_encoder_constraints = false, }; enum sc_orientation diff --git a/app/src/options.h b/app/src/options.h index cf4b2fbe11..69b25422d5 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -342,6 +342,7 @@ struct scrcpy_options { bool camera_torch; bool keep_active; bool flex_display; + bool ignore_video_encoder_constraints; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 7738800abc..e6e5d4b64f 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -404,6 +404,8 @@ scrcpy(struct scrcpy_options *options) { .vd_system_decorations = options->vd_system_decorations, .keep_active = options->keep_active, .flex_display = options->flex_display, + .ignore_video_encoder_constraints = + options->ignore_video_encoder_constraints, .list = options->list, }; diff --git a/app/src/server.c b/app/src/server.c index a6e33c09e8..0267ff32ef 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -420,6 +420,9 @@ execute_server(struct sc_server *server, if (params->flex_display) { ADD_PARAM("flex_display=true"); } + if (params->ignore_video_encoder_constraints) { + ADD_PARAM("ignore_video_encoder_constraints=true"); + } if (params->display_ime_policy != SC_DISPLAY_IME_POLICY_UNDEFINED) { ADD_PARAM("display_ime_policy=%s", sc_server_get_display_ime_policy_name(params->display_ime_policy)); diff --git a/app/src/server.h b/app/src/server.h index c581a3dd62..956a8844c2 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -75,6 +75,7 @@ struct sc_server_params { bool vd_system_decorations; bool keep_active; bool flex_display; + bool ignore_video_encoder_constraints; uint8_t list; }; diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 3bca7b5965..26a6ba9aa5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -70,6 +70,7 @@ public class Options { private boolean flexDisplay; private boolean keepActive; + private boolean ignoreVideoEncoderConstraints; private Orientation.Lock captureOrientationLock = Orientation.Lock.Unlocked; private Orientation captureOrientation = Orientation.Orient0; @@ -274,6 +275,10 @@ public boolean getFlexDisplay() { return flexDisplay; } + public boolean getIgnoreVideoEncoderConstraints() { + return ignoreVideoEncoderConstraints; + } + public boolean getList() { return listEncoders || listDisplays || listCameras || listCameraSizes || listApps; } @@ -539,6 +544,9 @@ public static Options parse(String... args) { case "keep_active": options.keepActive = Boolean.parseBoolean(value); break; + case "ignore_video_encoder_constraints": + options.ignoreVideoEncoderConstraints = Boolean.parseBoolean(value); + break; case "send_device_meta": options.sendDeviceMeta = Boolean.parseBoolean(value); break; 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 870a6fe7eb..c291a792bf 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java @@ -45,6 +45,7 @@ public class SurfaceEncoder implements AsyncProcessor { private final float maxFps; private final boolean downsizeOnError; private final int minSizeAlignment; + private final boolean ignoreVideoEncoderConstraints; private boolean firstFrameSent; private int consecutiveErrors; @@ -64,32 +65,44 @@ public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, Options options this.encoderName = options.getVideoEncoder(); this.downsizeOnError = options.getDownsizeOnError(); this.minSizeAlignment = options.getMinSizeAlignment(); + this.ignoreVideoEncoderConstraints = options.getIgnoreVideoEncoderConstraints(); } private static VideoConstraints createVideoConstraints(int maxSize, int minSizeAlignment, MediaCodecInfo.VideoCapabilities caps) { - assert caps != null; - int alignment = Math.max(caps.getWidthAlignment(), caps.getHeightAlignment()); - Ln.d("Video codec size alignment requirement: " + alignment + "px"); - if (alignment < minSizeAlignment) { + int alignment; + Size maxLandscapeSize; + Size maxPortraitSize; + int minSize; + if (caps != null) { + alignment = Math.max(caps.getWidthAlignment(), caps.getHeightAlignment()); + Ln.d("Video codec size alignment requirement: " + alignment + "px"); + if (alignment < minSizeAlignment) { + alignment = minSizeAlignment; + Ln.d("Actual video size alignment: " + alignment + "px"); + } + + int maxLandscapeWidth = caps.getSupportedWidths().getUpper(); + int maxLandscapeHeight = caps.getSupportedHeightsFor(maxLandscapeWidth).getUpper(); + maxLandscapeSize = new Size(maxLandscapeWidth, maxLandscapeHeight); + Ln.d("Maximum landscape size: " + maxLandscapeSize); + + int maxPortraitHeight = caps.getSupportedHeights().getUpper(); + int maxPortraitWidth = caps.getSupportedWidthsFor(maxPortraitHeight).getUpper(); + maxPortraitSize = new Size(maxPortraitWidth, maxPortraitHeight); + Ln.d("Maximum portrait size: " + maxPortraitSize); + + int minWidth = caps.getSupportedWidths().getLower(); + int minHeight = caps.getSupportedHeights().getLower(); + minSize = Math.max(minWidth, minHeight); + Ln.d("Minimum size: " + minSize); + } else { alignment = minSizeAlignment; Ln.d("Actual video size alignment: " + alignment + "px"); + maxLandscapeSize = new Size(8192, 8192); + maxPortraitSize = maxLandscapeSize; + minSize = 0; } - int maxLandscapeWidth = caps.getSupportedWidths().getUpper(); - int maxLandscapeHeight = caps.getSupportedHeightsFor(maxLandscapeWidth).getUpper(); - Size maxLandscapeSize = new Size(maxLandscapeWidth, maxLandscapeHeight); - Ln.d("Maximum landscape size: " + maxLandscapeSize); - - int maxPortraitHeight = caps.getSupportedHeights().getUpper(); - int maxPortraitWidth = caps.getSupportedWidthsFor(maxPortraitHeight).getUpper(); - Size maxPortraitSize = new Size(maxPortraitWidth, maxPortraitHeight); - Ln.d("Maximum portrait size: " + maxPortraitSize); - - int minWidth = caps.getSupportedWidths().getLower(); - int minHeight = caps.getSupportedHeights().getLower(); - int minSize = Math.max(minWidth, minHeight); - Ln.d("Minimum size: " + minSize); - return new VideoConstraints(maxSize, alignment, maxLandscapeSize, maxPortraitSize, minSize); } @@ -98,8 +111,14 @@ private void streamCapture() throws IOException, ConfigurationException { MediaCodec mediaCodec = createMediaCodec(codec, encoderName); MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions); - MediaCodecInfo.VideoCapabilities caps = mediaCodec.getCodecInfo().getCapabilitiesForType(codec.getMimeType()).getVideoCapabilities(); - assert caps != null; // caps cannot be null for a video codec + MediaCodecInfo.VideoCapabilities caps; + if (ignoreVideoEncoderConstraints) { + caps = null; + } else { + caps = mediaCodec.getCodecInfo().getCapabilitiesForType(codec.getMimeType()).getVideoCapabilities(); + assert caps != null; // caps cannot be null for a video codec + } + VideoConstraints constraints = createVideoConstraints(maxSize, minSizeAlignment, caps); capture.init(captureControl, constraints);