diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 673cd11d55..1b071e26bf 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -176,6 +176,14 @@ Set the initial window height. Default is 0 (automatic).\n +.TP +.BI "\-\-\codec\-options " 'options' +options is a list of key=value:type pairs seperated by comma\n +for the device encoder.\n +type is optional, Integer by default.\n +For a list of possible codec options:\n +developer.android.com/reference/android/media/MediaFormat\n + .SH SHORTCUTS .TP diff --git a/app/src/cli.c b/app/src/cli.c index 13351deeb3..27ece729f8 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -158,6 +158,13 @@ scrcpy_print_usage(const char *arg0) { " Set the initial window width.\n" " Default is 0 (automatic).\n" "\n" + " --codec-options 'options'\n" + " 'options' is a list of key=value:type pairs seperated by comma\n" + " for the device encoder.\n" + " 'type' is optional, Integer by default.\n" + " For a list of possible codec options:\n" + " developer.android.com/reference/android/media/MediaFormat\n" + "\n" "Shortcuts:\n" "\n" " " CTRL_OR_CMD "+f\n" @@ -468,18 +475,19 @@ guess_record_format(const char *filename) { #define OPT_ROTATION 1015 #define OPT_RENDER_DRIVER 1016 #define OPT_NO_MIPMAPS 1017 +#define OPT_CODEC_OPTIONS 1018 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { static const struct option long_options[] = { {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, {"bit-rate", required_argument, NULL, 'b'}, + {"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS}, {"crop", required_argument, NULL, OPT_CROP}, {"display", required_argument, NULL, OPT_DISPLAY_ID}, {"fullscreen", no_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, - {"lock-video-orientation", required_argument, NULL, - OPT_LOCK_VIDEO_ORIENTATION}, + {"lock-video-orientation", required_argument, NULL, OPT_LOCK_VIDEO_ORIENTATION}, {"max-fps", required_argument, NULL, OPT_MAX_FPS}, {"max-size", required_argument, NULL, 'm'}, {"no-control", no_argument, NULL, 'n'}, @@ -490,8 +498,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"record", required_argument, NULL, 'r'}, {"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, {"render-driver", required_argument, NULL, OPT_RENDER_DRIVER}, - {"render-expired-frames", no_argument, NULL, - OPT_RENDER_EXPIRED_FRAMES}, + {"render-expired-frames", no_argument, NULL, OPT_RENDER_EXPIRED_FRAMES}, {"rotation", required_argument, NULL, OPT_ROTATION}, {"serial", required_argument, NULL, 's'}, {"show-touches", no_argument, NULL, 't'}, @@ -503,8 +510,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"window-y", required_argument, NULL, OPT_WINDOW_Y}, {"window-width", required_argument, NULL, OPT_WINDOW_WIDTH}, {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, - {"window-borderless", no_argument, NULL, - OPT_WINDOW_BORDERLESS}, + {"window-borderless", no_argument, NULL, OPT_WINDOW_BORDERLESS}, {NULL, 0, NULL, 0 }, }; @@ -639,6 +645,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_NO_MIPMAPS: opts->mipmaps = false; break; + case OPT_CODEC_OPTIONS: + opts->codec_options = optarg; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d557b20887..d2f3d6b5c0 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -292,6 +292,7 @@ scrcpy(const struct scrcpy_options *options) { .lock_video_orientation = options->lock_video_orientation, .control = options->control, .display_id = options->display_id, + .codec_options = options->codec_options, }; if (!server_start(&server, options->serial, ¶ms)) { return false; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index d6b0a0f6d6..bbda1437bd 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -16,6 +16,7 @@ struct scrcpy_options { const char *window_title; const char *push_target; const char *render_driver; + const char *codec_options; enum recorder_format record_format; struct port_range port_range; uint16_t max_size; @@ -47,6 +48,7 @@ struct scrcpy_options { .window_title = NULL, \ .push_target = NULL, \ .render_driver = NULL, \ + .codec_options = NULL, \ .record_format = RECORDER_FORMAT_AUTO, \ .port_range = { \ .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ diff --git a/app/src/server.c b/app/src/server.c index b102f0c27d..0081d64cba 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -268,6 +268,7 @@ execute_server(struct server *server, const struct server_params *params) { "true", // always send frame meta (packet boundaries + timestamp) params->control ? "true" : "false", display_id_string, + params->codec_options ? params->codec_options : "-", }; #ifdef SERVER_DEBUGGER LOGI("Server debugger waiting for a client on device port " diff --git a/app/src/server.h b/app/src/server.h index a2ecdefcda..778aa4ed9e 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -44,6 +44,7 @@ struct server { struct server_params { const char *crop; + const char *codec_options; struct port_range port_range; uint16_t max_size; uint32_t bit_rate; diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 2a4bba3374..9736880cf1 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -12,6 +12,7 @@ public class Options { private boolean sendFrameMeta; // send PTS so that the client may record properly private boolean control; private int displayId; + private String codecOptions; public int getMaxSize() { return maxSize; @@ -84,4 +85,12 @@ public int getDisplayId() { public void setDisplayId(int displayId) { this.displayId = displayId; } + + public String getCodecOptions() { + return codecOptions; + } + + public void setCodecOptions(String codecOptions) { + this.codecOptions = codecOptions; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index fc1a25b125..a994cda0fb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -25,15 +25,17 @@ public class ScreenEncoder implements Device.RotationListener { private final AtomicBoolean rotationChanged = new AtomicBoolean(); private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); + private String codecOptions; private int bitRate; private int maxFps; private boolean sendFrameMeta; private long ptsOrigin; - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) { + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, String codecOptions) { this.sendFrameMeta = sendFrameMeta; this.bitRate = bitRate; this.maxFps = maxFps; + this.codecOptions = codecOptions; } @Override @@ -49,7 +51,7 @@ public void streamScreen(Device device, FileDescriptor fd) throws IOException { Workarounds.prepareMainLooper(); Workarounds.fillAppInfo(); - MediaFormat format = createFormat(bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL); + MediaFormat format = createFormat(bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL, codecOptions); device.setRotationListener(this); boolean alive; try { @@ -139,7 +141,7 @@ private static MediaCodec createCodec() throws IOException { return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); } - private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInterval) { + private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInterval, String codecOptions) { MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); @@ -155,6 +157,33 @@ private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInter // format.setFloat(KEY_MAX_FPS_TO_ENCODER, maxFps); } + // Adding additional options specified by the user + if(!"-".equals(codecOptions)) { + for (String pair : codecOptions.split(",")) { + String[] option = pair.split("="); + String key = option[0]; + if(format.containsKey(key)) { + if (option.length < 2) { + Ln.w("No value specified for codec option - " + key); + continue; + } + String[] valueAndType = option[1].split(":"); + String value = valueAndType[0]; + String type = valueAndType.length < 2 ? "" : valueAndType[1].toLowerCase(); + if (type.contains("str")) { + format.setString(key, value); + } else if (type.contains("long")) { + format.setLong(key, Long.parseLong(value)); + } else if (type.contains("float")) { + format.setFloat(key, Float.parseFloat(value)); + } else { + format.setInteger(key, Integer.parseInt(value)); + } + } else { + Ln.w("Codec format doesn't contain the requested codec option - " + key); + } + } + } return format; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index a6f7a78c3a..3a2f718fb7 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -20,7 +20,7 @@ private static void scrcpy(Options options) throws IOException { final Device device = new Device(options); boolean tunnelForward = options.isTunnelForward(); try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { - ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps()); + ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), options.getCodecOptions()); if (options.getControl()) { Controller controller = new Controller(device, connection); @@ -79,8 +79,8 @@ private static Options createOptions(String... args) { "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } - if (args.length != 10) { - throw new IllegalArgumentException("Expecting 10 parameters"); + if (args.length != 11) { + throw new IllegalArgumentException("Expecting 11 parameters"); } Options options = new Options(); @@ -113,6 +113,9 @@ private static Options createOptions(String... args) { int displayId = Integer.parseInt(args[9]); options.setDisplayId(displayId); + String codecOptions = args[10]; + options.setCodecOptions(codecOptions); + return options; }