From 8f9205e76cf3fca8879570bcc9c5b6c178393256 Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Thu, 12 Mar 2020 15:59:10 +0200 Subject: [PATCH 1/9] Adding codec_profile to scrcpy client default options. If the option is not requested with -c then the codec will be automatically choosen by the MediaCodec. (cherry picked from commit 43aea79f6acc69f68feaee0494f45a05bce0f782) (cherry picked from commit 504d6a42d543c83a50e1d24fe247ff1c64563232) --- app/src/cli.c | 1 + app/src/scrcpy.c | 1 + app/src/scrcpy.h | 2 ++ app/src/server.h | 1 + 4 files changed, 5 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index 4b093c49da..df1e54afad 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -431,6 +431,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, {"window-borderless", no_argument, NULL, OPT_WINDOW_BORDERLESS}, + {"codec-profile", required_argument, NULL, 'P'}, {NULL, 0, NULL, 0 }, }; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 4d9ad88b0b..0057c75b83 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -286,6 +286,7 @@ scrcpy(const struct scrcpy_options *options) { .max_fps = options->max_fps, .lock_video_orientation = options->lock_video_orientation, .control = options->control, + .codec_profile = options->codec_profile, }; if (!server_start(&server, options->serial, ¶ms)) { return false; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index e29298f2c1..4d5ad9fc34 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -34,6 +34,7 @@ struct scrcpy_options { bool render_expired_frames; bool prefer_text; bool window_borderless; + uint32_t codec_profile; }; #define SCRCPY_OPTIONS_DEFAULT { \ @@ -64,6 +65,7 @@ struct scrcpy_options { .render_expired_frames = false, \ .prefer_text = false, \ .window_borderless = false, \ + .codec_profile = 0, \ } bool diff --git a/app/src/server.h b/app/src/server.h index d84a5cc8c2..f16ad6b43c 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -44,6 +44,7 @@ struct server_params { uint16_t max_fps; int8_t lock_video_orientation; bool control; + uint32_t codec_profile; }; // init default values From 88a00ab28c3783511b1bf7066f55da6f85e53664 Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Thu, 12 Mar 2020 16:52:46 +0200 Subject: [PATCH 2/9] Adding codecProfile to server options (cherry picked from commit d4fb6a0c19af23d0d3c57721f6ccc91e4fad67f3) (cherry picked from commit e7f205ca19033c9b227171915bf9f748b5eefe85) (cherry picked from commit ef5d72cbb785d48d412b437fee95bc8f61829680) (cherry picked from commit 709a0c327a114857423c3f28eb46d3a1700af6a8) --- .../main/java/com/genymobile/scrcpy/Options.java | 9 +++++++++ .../main/java/com/genymobile/scrcpy/Server.java | 15 +++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index d9a29452b0..2b87b952bb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -11,6 +11,7 @@ public class Options { private Rect crop; private boolean sendFrameMeta; // send PTS so that the client may record properly private boolean control; + private int codecProfile; public int getMaxSize() { return maxSize; @@ -75,4 +76,12 @@ public boolean getControl() { public void setControl(boolean control) { this.control = control; } + + public int getCodecProfile() { + return codecProfile; + } + + public void setCodecProfile(int codecProfile) { + this.codecProfile = codecProfile; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 2b0d32a239..c2e501612c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -80,8 +80,8 @@ private static Options createOptions(String... args) { "The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")"); } - if (args.length != 9) { - throw new IllegalArgumentException("Expecting 9 parameters"); + if (args.length != 10) { + throw new IllegalArgumentException("Expecting 10 parameters"); } Options options = new Options(); @@ -98,17 +98,20 @@ private static Options createOptions(String... args) { int lockedVideoOrientation = Integer.parseInt(args[4]); options.setLockedVideoOrientation(lockedVideoOrientation); + int codecProfile = Integer.parseInt(args[5]); + options.setCodecProfile(codecProfile); + // use "adb forward" instead of "adb tunnel"? (so the server must listen) - boolean tunnelForward = Boolean.parseBoolean(args[5]); + boolean tunnelForward = Boolean.parseBoolean(args[6]); options.setTunnelForward(tunnelForward); - Rect crop = parseCrop(args[6]); + Rect crop = parseCrop(args[7]); options.setCrop(crop); - boolean sendFrameMeta = Boolean.parseBoolean(args[7]); + boolean sendFrameMeta = Boolean.parseBoolean(args[8]); options.setSendFrameMeta(sendFrameMeta); - boolean control = Boolean.parseBoolean(args[8]); + boolean control = Boolean.parseBoolean(args[9]); options.setControl(control); return options; From 15c0d5ee9819d9a7c56a214e60520ffcaf8d8466 Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Thu, 12 Mar 2020 16:56:27 +0200 Subject: [PATCH 3/9] Adding codecProfile and setCodecProfile to the ScreenEncoder (cherry picked from commit 25052420394fda937db5973290f564e5c6b13ba5) (cherry picked from commit a9685d3b564a82c43e83b74923e39ccaa2f32fb4) --- .../com/genymobile/scrcpy/ScreenEncoder.java | 25 ++++++++++++++++--- .../java/com/genymobile/scrcpy/Server.java | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 3e9772ee05..b886affb9b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -30,19 +30,21 @@ public class ScreenEncoder implements Device.RotationListener { private int maxFps; private int lockedVideoOrientation; private int iFrameInterval; + private int codecProfile; private boolean sendFrameMeta; private long ptsOrigin; - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int iFrameInterval) { + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int codecProfile, int iFrameInterval) { this.sendFrameMeta = sendFrameMeta; this.bitRate = bitRate; this.maxFps = maxFps; this.lockedVideoOrientation = lockedVideoOrientation; + this.codecProfile = codecProfile; this.iFrameInterval = iFrameInterval; } - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation) { - this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, DEFAULT_I_FRAME_INTERVAL); + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int codecProfile) { + this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, codecProfile, DEFAULT_I_FRAME_INTERVAL); } @Override @@ -64,6 +66,7 @@ public void streamScreen(Device device, FileDescriptor fd) throws IOException { try { do { MediaCodec codec = createCodec(); + setCodecProfile(codec, format); IBinder display = createDisplay(); ScreenInfo screenInfo = device.getScreenInfo(); Rect contentRect = screenInfo.getContentRect(); @@ -139,6 +142,22 @@ private void writeFrameMeta(FileDescriptor fd, MediaCodec.BufferInfo bufferInfo, IO.writeFully(fd, headerBuffer); } + private void setCodecProfile(MediaCodec codec, MediaFormat format) throws IOException { + if(codecProfile == 0) return; + int level = 0; + for (MediaCodecInfo.CodecProfileLevel profileLevel : codec.getCodecInfo().getCapabilitiesForType("video/avc").profileLevels) { + if(profileLevel.profile == codecProfile) { + level = Math.max(level, profileLevel.level); + } + } + if(level == 0) throw new IOException("Device doesn't support the requested codec profile."); + // Profile (SDK Level 21) and Level (SDK Level 23). + format.setInteger(MediaFormat.KEY_PROFILE, codecProfile); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + format.setInteger(MediaFormat.KEY_LEVEL, level); + } + } + private static MediaCodec createCodec() throws IOException { return MediaCodec.createEncoderByType("video/avc"); } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index c2e501612c..5a67acdb8b 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 { boolean tunnelForward = options.isTunnelForward(); try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), - options.getLockedVideoOrientation()); + options.getLockedVideoOrientation(), options.getCodecProfile()); if (options.getControl()) { Controller controller = new Controller(device, connection); From fd5489dce0385a546e5e1a247ba7385f97454333 Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Mon, 16 Mar 2020 11:23:11 +0200 Subject: [PATCH 4/9] Fix the printed versions (were opposite) (cherry picked from commit 7e36540c21eef4964b73824d8845b2468899981e) (cherry picked from commit ef95046c3e464beb94cb122224ac754f856685b4) (cherry picked from commit fa3644cca2ba2c4a562a203ff773c021f9082175) (cherry picked from commit a5d5ea1b73374cf6053a69e0a57e23d4de5bc540) --- server/src/main/java/com/genymobile/scrcpy/Server.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 5a67acdb8b..71d02abf69 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -77,7 +77,7 @@ private static Options createOptions(String... args) { String clientVersion = args[0]; if (!clientVersion.equals(BuildConfig.VERSION_NAME)) { throw new IllegalArgumentException( - "The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")"); + "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } if (args.length != 10) { From bc70f566bed8e4c367f70e8e115f88c811b1a2df Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Mon, 16 Mar 2020 15:03:43 +0200 Subject: [PATCH 5/9] Added help and parsing of the codec-profile option (cherry picked from commit e15824304434d1721cf928fde35c870c1fc01698) (cherry picked from commit a4ef318ae3e5764215282a9010d463a1ee271b1a) (cherry picked from commit a5b83c19081d04346ef580dfff09ec4076822302) (cherry picked from commit 1e3321cf58aecdbb97efabfd251cf5f46ede32d3) --- app/scrcpy.1 | 8 ++++++++ app/src/cli.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index b3c57064c5..443bb19d57 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -151,6 +151,14 @@ Set the initial window height. Default is 0 (automatic).\n +.TP +.BI "\-\-\codec\-options " 'options' +'options' is a list of key=value pairs seperated by comma\n +for the device encoder.\n +For a list of possible codec options:\n +https://developer.android.com/reference/android/media/MediaCodecInfo\n +Currently supported keys: [profile, level].\n + .SH SHORTCUTS .TP diff --git a/app/src/cli.c b/app/src/cli.c index df1e54afad..44fee83899 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -130,6 +130,12 @@ scrcpy_print_usage(const char *arg0) { " Set the initial window width.\n" " Default is 0 (automatic).\n" "\n" + " -P, --codec-profile value\n" + " Request specific encoding codec profile, see AVC profiles at:\n" + " https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel\n" + " for valid codec profile values.\n" + " Default is 0 (automatic).\n" + "\n" "Shortcuts:\n" "\n" " " CTRL_OR_CMD "+f\n" @@ -368,6 +374,35 @@ parse_record_format(const char *optarg, enum recorder_format *format) { return false; } +static bool +parse_codec_profile(const char *optarg, uint32_t *codec_profile) { + long value; + // long may be 32 bits (it is the case on mingw), so do not use more than + // 31 bits (long is signed) + bool ok = parse_integer_arg(optarg, &value, true, 0, 0x7FFFFFFF, "codec-profile"); + if (!ok) { + return false; + } + + switch (value) { + case 0: // Automatic + case 0x01: // AVCProfileBaseline + case 0x02: // AVCProfileMain + case 0x04: // AVCProfileExtended + case 0x08: // AVCProfileHigh + case 0x10: // AVCProfileHigh10 + case 0x20: // AVCProfileHigh422 + case 0x40: // AVCProfileHigh444 + case 0x10000: // AVCProfileConstrainedBaseline + case 0x80000: // AVCProfileConstrainedHigh + *codec_profile = (uint32_t) value; + return true; + default: + LOGE("Invalid codec-profile, Use -h for help"); + return false; + } +} + static enum recorder_format guess_record_format(const char *filename) { size_t len = strlen(filename); @@ -440,7 +475,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { optind = 0; // reset to start from the first argument in tests int c; +<<<<<<< HEAD + while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv:P:", long_options, +======= while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options, +>>>>>>> 1b12204... fixup! Added help and parsing of the codec-profile option NULL)) != -1) { switch (c) { case 'b': @@ -550,6 +589,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_PREFER_TEXT: opts->prefer_text = true; break; + case 'P': + if (!parse_codec_profile(optarg, &opts->codec_profile)) { + return false; + } + break; default: // getopt prints the error message on stderr return false; From eaef1c1f47f747748cf9158c89c236da02a78b31 Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Mon, 16 Mar 2020 15:04:13 +0200 Subject: [PATCH 6/9] Adding codec-profile option to the server exec params (cherry picked from commit fa56950f43a14a501d03a79c626103ae3fc9f046) (cherry picked from commit c12306a6c2c15c9368feab25f298e97015ee07eb) --- app/src/server.c | 4 ++++ app/src/server.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/app/src/server.c b/app/src/server.c index 4b2c1866cc..d4b36887f9 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -234,10 +234,12 @@ execute_server(struct server *server, const struct server_params *params) { char bit_rate_string[11]; char max_fps_string[6]; char lock_video_orientation_string[3]; + char codec_profile_string[11]; sprintf(max_size_string, "%"PRIu16, params->max_size); sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); sprintf(max_fps_string, "%"PRIu16, params->max_fps); sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation); + sprintf(codec_profile_string, "%"PRIu32, params->codec_profile); const char *const cmd[] = { "shell", "CLASSPATH=" DEVICE_SERVER_PATH, @@ -254,6 +256,7 @@ execute_server(struct server *server, const struct server_params *params) { bit_rate_string, max_fps_string, lock_video_orientation_string, + codec_profile_string, server->tunnel_forward ? "true" : "false", params->crop ? params->crop : "-", "true", // always send frame meta (packet boundaries + timestamp) @@ -327,6 +330,7 @@ bool server_start(struct server *server, const char *serial, const struct server_params *params) { server->port_range = params->port_range; + server->codec_profile = params->codec_profile; if (serial) { server->serial = SDL_strdup(serial); diff --git a/app/src/server.h b/app/src/server.h index f16ad6b43c..5d940f203a 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -19,6 +19,7 @@ struct server { uint16_t local_port; // selected from port_range bool tunnel_enabled; bool tunnel_forward; // use "adb forward" instead of "adb reverse" + uint32_t codec_profile; }; #define SERVER_INITIALIZER { \ @@ -34,6 +35,7 @@ struct server { .local_port = 0, \ .tunnel_enabled = false, \ .tunnel_forward = false, \ + .codec_profile = 0, \ } struct server_params { From c265d6306e85935546df24025d5018e9ad1d839f Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Tue, 14 Apr 2020 18:31:54 +0300 Subject: [PATCH 7/9] Changing codec-profile -> codec-options --- .../com/genymobile/scrcpy/CodecOptions.java | 25 +++++++++++ .../com/genymobile/scrcpy/NumberUtils.java | 16 ++++++++ .../java/com/genymobile/scrcpy/Options.java | 10 ++--- .../com/genymobile/scrcpy/ScreenEncoder.java | 41 +++++++++++-------- .../java/com/genymobile/scrcpy/Server.java | 19 +++++++-- 5 files changed, 86 insertions(+), 25 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/CodecOptions.java create mode 100644 server/src/main/java/com/genymobile/scrcpy/NumberUtils.java diff --git a/server/src/main/java/com/genymobile/scrcpy/CodecOptions.java b/server/src/main/java/com/genymobile/scrcpy/CodecOptions.java new file mode 100644 index 0000000000..0b6ae152d5 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/CodecOptions.java @@ -0,0 +1,25 @@ +package com.genymobile.scrcpy; + +import java.util.HashMap; + +public class CodecOptions { + static final String PROFILE_OPTION = "profile"; + static final String LEVEL_OPTION = "level"; + + private HashMap options; + + CodecOptions(HashMap options) { + this.options = options; + } + + Object parseValue(String profileOption) { + String value = options.get(profileOption); + switch (profileOption) { + case PROFILE_OPTION: + case LEVEL_OPTION: + return NumberUtils.tryParseInt(value); + default: + return null; + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/NumberUtils.java b/server/src/main/java/com/genymobile/scrcpy/NumberUtils.java new file mode 100644 index 0000000000..dafaaea486 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/NumberUtils.java @@ -0,0 +1,16 @@ +package com.genymobile.scrcpy; + +public class NumberUtils { + + public static int tryParseInt(final String str) { + return tryParseInt(str, 0); + } + + public static int tryParseInt(final String str, int defaultValue) { + try { + return Integer.parseInt(str); + } catch (NumberFormatException e) { + return defaultValue; + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 2b87b952bb..3ccba37f5f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -11,7 +11,7 @@ public class Options { private Rect crop; private boolean sendFrameMeta; // send PTS so that the client may record properly private boolean control; - private int codecProfile; + private CodecOptions codecOptions; public int getMaxSize() { return maxSize; @@ -77,11 +77,11 @@ public void setControl(boolean control) { this.control = control; } - public int getCodecProfile() { - return codecProfile; + public CodecOptions getCodecOptions() { + return codecOptions; } - public void setCodecProfile(int codecProfile) { - this.codecProfile = codecProfile; + public void setCodecOptions(CodecOptions 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 b886affb9b..7f5aaf7f73 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -15,6 +15,8 @@ import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; +import static android.media.MediaFormat.MIMETYPE_VIDEO_AVC; + public class ScreenEncoder implements Device.RotationListener { private static final int DEFAULT_I_FRAME_INTERVAL = 10; // seconds @@ -30,21 +32,21 @@ public class ScreenEncoder implements Device.RotationListener { private int maxFps; private int lockedVideoOrientation; private int iFrameInterval; - private int codecProfile; private boolean sendFrameMeta; private long ptsOrigin; + private CodecOptions codecOptions; - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int codecProfile, int iFrameInterval) { + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, CodecOptions codecOptions, int iFrameInterval) { this.sendFrameMeta = sendFrameMeta; this.bitRate = bitRate; this.maxFps = maxFps; this.lockedVideoOrientation = lockedVideoOrientation; - this.codecProfile = codecProfile; + this.codecOptions = codecOptions; this.iFrameInterval = iFrameInterval; } - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int codecProfile) { - this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, codecProfile, DEFAULT_I_FRAME_INTERVAL); + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, CodecOptions codecOptions) { + this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, codecOptions, DEFAULT_I_FRAME_INTERVAL); } @Override @@ -142,30 +144,35 @@ private void writeFrameMeta(FileDescriptor fd, MediaCodec.BufferInfo bufferInfo, IO.writeFully(fd, headerBuffer); } - private void setCodecProfile(MediaCodec codec, MediaFormat format) throws IOException { - if(codecProfile == 0) return; - int level = 0; - for (MediaCodecInfo.CodecProfileLevel profileLevel : codec.getCodecInfo().getCapabilitiesForType("video/avc").profileLevels) { - if(profileLevel.profile == codecProfile) { + private void setCodecProfile(MediaCodec codec, MediaFormat format) { + int profile = (int)codecOptions.parseValue(CodecOptions.PROFILE_OPTION); + int level = (int)codecOptions.parseValue(CodecOptions.LEVEL_OPTION); + if(profile == 0) return; + for (MediaCodecInfo.CodecProfileLevel profileLevel : codec.getCodecInfo().getCapabilitiesForType(MIMETYPE_VIDEO_AVC).profileLevels) { + if(profileLevel.profile == profile) { level = Math.max(level, profileLevel.level); } } - if(level == 0) throw new IOException("Device doesn't support the requested codec profile."); - // Profile (SDK Level 21) and Level (SDK Level 23). - format.setInteger(MediaFormat.KEY_PROFILE, codecProfile); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - format.setInteger(MediaFormat.KEY_LEVEL, level); + if(level == 0) { + Ln.w("Device doesn't support the requested codec profile.\n" + + "Profile and level will be chosen automatically."); + } else { + // Profile (SDK Level 21) and Level (SDK Level 23). + format.setInteger(MediaFormat.KEY_PROFILE, profile); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + format.setInteger(MediaFormat.KEY_LEVEL, level); + } } } private static MediaCodec createCodec() throws IOException { - return MediaCodec.createEncoderByType("video/avc"); + return MediaCodec.createEncoderByType(MIMETYPE_VIDEO_AVC); } @SuppressWarnings("checkstyle:MagicNumber") private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInterval) { MediaFormat format = new MediaFormat(); - format.setString(MediaFormat.KEY_MIME, "video/avc"); + format.setString(MediaFormat.KEY_MIME, MIMETYPE_VIDEO_AVC); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); // must be present to configure the encoder, but does not impact the actual frame rate, which is variable format.setInteger(MediaFormat.KEY_FRAME_RATE, 60); diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 71d02abf69..7dd53ff405 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -6,6 +6,7 @@ import java.io.File; import java.io.IOException; +import java.util.HashMap; public final class Server { @@ -20,7 +21,7 @@ private static void scrcpy(Options options) throws IOException { boolean tunnelForward = options.isTunnelForward(); try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), - options.getLockedVideoOrientation(), options.getCodecProfile()); + options.getLockedVideoOrientation(), options.getCodecOptions()); if (options.getControl()) { Controller controller = new Controller(device, connection); @@ -98,8 +99,8 @@ private static Options createOptions(String... args) { int lockedVideoOrientation = Integer.parseInt(args[4]); options.setLockedVideoOrientation(lockedVideoOrientation); - int codecProfile = Integer.parseInt(args[5]); - options.setCodecProfile(codecProfile); + CodecOptions codecOptions = parseCodecOptions(args[5]); + options.setCodecOptions(codecOptions); // use "adb forward" instead of "adb tunnel"? (so the server must listen) boolean tunnelForward = Boolean.parseBoolean(args[6]); @@ -134,6 +135,18 @@ private static Rect parseCrop(String crop) { return new Rect(x, y, x + width, y + height); } + private static CodecOptions parseCodecOptions(String codecOptions) { + HashMap codecOptionsMap = new HashMap<>(); + if (!"-".equals(codecOptions)) { + String[] pairs = codecOptions.split(","); + for (String pair : pairs) { + String[] option = pair.split("="); + codecOptionsMap.put(option[0], option.length > 1 ? option[1] : null); + } + } + return new CodecOptions(codecOptionsMap); + } + private static void unlinkSelf() { try { new File(SERVER_PATH).delete(); From aa42833cbbde15f5c1e64ab70a9c6714562a612c Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Tue, 14 Apr 2020 18:32:26 +0300 Subject: [PATCH 8/9] Changing codec-profile -> codec-options --- app/scrcpy.1 | 2 +- app/src/cli.c | 64 +++++++++++------------------------------------- app/src/scrcpy.c | 2 +- app/src/scrcpy.h | 4 +-- app/src/server.c | 5 +--- app/src/server.h | 4 +-- 6 files changed, 20 insertions(+), 61 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 443bb19d57..755bd981c0 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -153,7 +153,7 @@ Default is 0 (automatic).\n .TP .BI "\-\-\codec\-options " 'options' -'options' is a list of key=value pairs seperated by comma\n +options is a list of key=value pairs seperated by comma\n for the device encoder.\n For a list of possible codec options:\n https://developer.android.com/reference/android/media/MediaCodecInfo\n diff --git a/app/src/cli.c b/app/src/cli.c index 44fee83899..f62b7f9fe6 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -130,11 +130,12 @@ scrcpy_print_usage(const char *arg0) { " Set the initial window width.\n" " Default is 0 (automatic).\n" "\n" - " -P, --codec-profile value\n" - " Request specific encoding codec profile, see AVC profiles at:\n" - " https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel\n" - " for valid codec profile values.\n" - " Default is 0 (automatic).\n" + " --codec-options 'options'\n" + " 'options' is a list of key=value pairs seperated by comma\n" + " for the device encoder.\n" + " For a list of possible codec options:\n" + " https://developer.android.com/reference/android/media/MediaCodecInfo\n" + " Currently supported keys: [profile, level].\n" "\n" "Shortcuts:\n" "\n" @@ -374,35 +375,6 @@ parse_record_format(const char *optarg, enum recorder_format *format) { return false; } -static bool -parse_codec_profile(const char *optarg, uint32_t *codec_profile) { - long value; - // long may be 32 bits (it is the case on mingw), so do not use more than - // 31 bits (long is signed) - bool ok = parse_integer_arg(optarg, &value, true, 0, 0x7FFFFFFF, "codec-profile"); - if (!ok) { - return false; - } - - switch (value) { - case 0: // Automatic - case 0x01: // AVCProfileBaseline - case 0x02: // AVCProfileMain - case 0x04: // AVCProfileExtended - case 0x08: // AVCProfileHigh - case 0x10: // AVCProfileHigh10 - case 0x20: // AVCProfileHigh422 - case 0x40: // AVCProfileHigh444 - case 0x10000: // AVCProfileConstrainedBaseline - case 0x80000: // AVCProfileConstrainedHigh - *codec_profile = (uint32_t) value; - return true; - default: - LOGE("Invalid codec-profile, Use -h for help"); - return false; - } -} - static enum recorder_format guess_record_format(const char *filename) { size_t len = strlen(filename); @@ -433,6 +405,7 @@ guess_record_format(const char *filename) { #define OPT_WINDOW_BORDERLESS 1011 #define OPT_MAX_FPS 1012 #define OPT_LOCK_VIDEO_ORIENTATION 1013 +#define OPT_CODEC_OPTIONS 1014 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -442,8 +415,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"crop", required_argument, NULL, OPT_CROP}, {"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'}, @@ -452,8 +424,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, {"record", required_argument, NULL, 'r'}, {"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, - {"render-expired-frames", no_argument, NULL, - OPT_RENDER_EXPIRED_FRAMES}, + {"render-expired-frames", no_argument, NULL, OPT_RENDER_EXPIRED_FRAMES}, {"serial", required_argument, NULL, 's'}, {"show-touches", no_argument, NULL, 't'}, {"turn-screen-off", no_argument, NULL, 'S'}, @@ -464,9 +435,8 @@ 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}, - {"codec-profile", required_argument, NULL, 'P'}, + {"window-borderless", no_argument, NULL, OPT_WINDOW_BORDERLESS}, + {"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS}, {NULL, 0, NULL, 0 }, }; @@ -475,11 +445,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { optind = 0; // reset to start from the first argument in tests int c; -<<<<<<< HEAD - while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv:P:", long_options, -======= - while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv", long_options, ->>>>>>> 1b12204... fixup! Added help and parsing of the codec-profile option + while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTv:", long_options, NULL)) != -1) { switch (c) { case 'b': @@ -589,10 +555,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_PREFER_TEXT: opts->prefer_text = true; break; - case 'P': - if (!parse_codec_profile(optarg, &opts->codec_profile)) { - return false; - } + case OPT_CODEC_OPTIONS: + opts->codec_options = optarg; break; default: // getopt prints the error message on stderr diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 0057c75b83..5cc0f616dc 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -286,7 +286,7 @@ scrcpy(const struct scrcpy_options *options) { .max_fps = options->max_fps, .lock_video_orientation = options->lock_video_orientation, .control = options->control, - .codec_profile = options->codec_profile, + .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 4d5ad9fc34..17f2a4795d 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -15,6 +15,7 @@ struct scrcpy_options { const char *record_filename; const char *window_title; const char *push_target; + const char *codec_options; enum recorder_format record_format; struct port_range port_range; uint16_t max_size; @@ -34,7 +35,6 @@ struct scrcpy_options { bool render_expired_frames; bool prefer_text; bool window_borderless; - uint32_t codec_profile; }; #define SCRCPY_OPTIONS_DEFAULT { \ @@ -43,6 +43,7 @@ struct scrcpy_options { .record_filename = NULL, \ .window_title = NULL, \ .push_target = NULL, \ + .codec_options = NULL, \ .record_format = RECORDER_FORMAT_AUTO, \ .port_range = { \ .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ @@ -65,7 +66,6 @@ struct scrcpy_options { .render_expired_frames = false, \ .prefer_text = false, \ .window_borderless = false, \ - .codec_profile = 0, \ } bool diff --git a/app/src/server.c b/app/src/server.c index d4b36887f9..55a8151b16 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -234,12 +234,10 @@ execute_server(struct server *server, const struct server_params *params) { char bit_rate_string[11]; char max_fps_string[6]; char lock_video_orientation_string[3]; - char codec_profile_string[11]; sprintf(max_size_string, "%"PRIu16, params->max_size); sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); sprintf(max_fps_string, "%"PRIu16, params->max_fps); sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation); - sprintf(codec_profile_string, "%"PRIu32, params->codec_profile); const char *const cmd[] = { "shell", "CLASSPATH=" DEVICE_SERVER_PATH, @@ -256,7 +254,7 @@ execute_server(struct server *server, const struct server_params *params) { bit_rate_string, max_fps_string, lock_video_orientation_string, - codec_profile_string, + params->codec_options ? params->codec_options : "-", server->tunnel_forward ? "true" : "false", params->crop ? params->crop : "-", "true", // always send frame meta (packet boundaries + timestamp) @@ -330,7 +328,6 @@ bool server_start(struct server *server, const char *serial, const struct server_params *params) { server->port_range = params->port_range; - server->codec_profile = params->codec_profile; if (serial) { server->serial = SDL_strdup(serial); diff --git a/app/src/server.h b/app/src/server.h index 5d940f203a..9bc25671df 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -19,7 +19,6 @@ struct server { uint16_t local_port; // selected from port_range bool tunnel_enabled; bool tunnel_forward; // use "adb forward" instead of "adb reverse" - uint32_t codec_profile; }; #define SERVER_INITIALIZER { \ @@ -35,7 +34,6 @@ struct server { .local_port = 0, \ .tunnel_enabled = false, \ .tunnel_forward = false, \ - .codec_profile = 0, \ } struct server_params { @@ -46,7 +44,7 @@ struct server_params { uint16_t max_fps; int8_t lock_video_orientation; bool control; - uint32_t codec_profile; + char *codec_options; }; // init default values From fb7b2aee07f6b2404e172b377925c5a9e83f908b Mon Sep 17 00:00:00 2001 From: Tzah Mazuz Date: Wed, 15 Apr 2020 00:06:09 +0300 Subject: [PATCH 9/9] Added logic to choose profile level matching the streaming properties. --- .../com/genymobile/scrcpy/CodecOptions.java | 66 ++++++++++++++++++- .../com/genymobile/scrcpy/ScreenEncoder.java | 39 +++++++---- 2 files changed, 91 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/CodecOptions.java b/server/src/main/java/com/genymobile/scrcpy/CodecOptions.java index 0b6ae152d5..84b273efc2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CodecOptions.java +++ b/server/src/main/java/com/genymobile/scrcpy/CodecOptions.java @@ -1,10 +1,46 @@ package com.genymobile.scrcpy; +import android.media.MediaCodecInfo; +import android.os.Build; + import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; public class CodecOptions { - static final String PROFILE_OPTION = "profile"; - static final String LEVEL_OPTION = "level"; + + public static final String PROFILE_OPTION = "profile"; + public static final String LEVEL_OPTION = "level"; + + private static final LinkedHashMap levelsTable = new LinkedHashMap() { + { + // Adding all possible level and their properties + // 3rd property, bitrate was added but not sure if needed for now. + // Source: https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels + put(MediaCodecInfo.CodecProfileLevel.AVCLevel1, "485,99,64"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel1b, "485,99,128"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel11, "3000,396,192"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel12, "6000,396,384"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel13, "11880,396,768"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel2, "11880,396,2000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel21, "19800,792,4000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel22, "20250,1620,4000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel3, "40500,1620,10000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel31, "108000,3600,14000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel32, "216000,5120,20000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel4, "245760,8192,20000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel41, "245760,8192,50000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel42, "522240,8704,50000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel5, "589824,22080,135000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel51, "983040,36864,240000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel52, "2073600,36864,240000"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + put(MediaCodecInfo.CodecProfileLevel.AVCLevel6, "4177920,139264,240000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel61, "8355840,139264,480000"); + put(MediaCodecInfo.CodecProfileLevel.AVCLevel62, "16711680,139264,800000"); + } + } + }; private HashMap options; @@ -12,7 +48,31 @@ public class CodecOptions { this.options = options; } - Object parseValue(String profileOption) { + /** + * The purpose of this function is to return the lowest possible codec profile level + * that supports the given width/height/bitrate of the stream + * @param width of the device + * @param height of the device + * @param bitRate at which we stream + * @return the lowest possible level that should support the given properties. + */ + public static int calculateLevel(int width, int height, int bitRate) { + // Calculations source: https://stackoverflow.com/questions/32100635/vlc-2-2-and-levels + int macroblocks = (int)( Math.ceil(width/16.0) * Math.ceil(height/16.0) ); + int macroblocks_s = macroblocks * 60; + for (Map.Entry entry : levelsTable.entrySet()) { + String[] levelProperties = entry.getValue().split(","); + int levelMacroblocks_s = Integer.parseInt(levelProperties[0]); + int levelMacroblocks = Integer.parseInt(levelProperties[1]); + if(macroblocks_s > levelMacroblocks_s) continue; + if(macroblocks > levelMacroblocks) continue; + Ln.i("Level selected based on screen size calculation: " + entry.getKey()); + return entry.getKey(); + } + return 0; + } + + public Object parseValue(String profileOption) { String value = options.get(profileOption); switch (profileOption) { case PROFILE_OPTION: diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 7f5aaf7f73..db8f2abda3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -68,13 +68,13 @@ public void streamScreen(Device device, FileDescriptor fd) throws IOException { try { do { MediaCodec codec = createCodec(); - setCodecProfile(codec, format); IBinder display = createDisplay(); ScreenInfo screenInfo = device.getScreenInfo(); Rect contentRect = screenInfo.getContentRect(); Rect videoRect = screenInfo.getVideoSize().toRect(); int videoRotation = device.getVideoRotation(screenInfo.getRotation()); setSize(format, videoRotation, videoRect.width(), videoRect.height()); + setCodecProfile(codec, format, videoRect); configure(codec, format); Surface surface = codec.createInputSurface(); setDisplaySurface(display, surface, videoRotation, contentRect, videoRect); @@ -144,25 +144,42 @@ private void writeFrameMeta(FileDescriptor fd, MediaCodec.BufferInfo bufferInfo, IO.writeFully(fd, headerBuffer); } - private void setCodecProfile(MediaCodec codec, MediaFormat format) { + private void setCodecProfile(MediaCodec codec, MediaFormat format, Rect videoRect) { int profile = (int)codecOptions.parseValue(CodecOptions.PROFILE_OPTION); int level = (int)codecOptions.parseValue(CodecOptions.LEVEL_OPTION); + int suggestedLevel = CodecOptions.calculateLevel(videoRect.width(), videoRect.height(), bitRate); + boolean profileSupported = false; + if(profile == 0) return; - for (MediaCodecInfo.CodecProfileLevel profileLevel : codec.getCodecInfo().getCapabilitiesForType(MIMETYPE_VIDEO_AVC).profileLevels) { + for (MediaCodecInfo.CodecProfileLevel profileLevel : + codec.getCodecInfo().getCapabilitiesForType(MIMETYPE_VIDEO_AVC).profileLevels) { if(profileLevel.profile == profile) { - level = Math.max(level, profileLevel.level); + profileSupported = true; + break; } } - if(level == 0) { - Ln.w("Device doesn't support the requested codec profile.\n" + - "Profile and level will be chosen automatically."); - } else { - // Profile (SDK Level 21) and Level (SDK Level 23). + if(profileSupported) { + // Profile (SDK Level 21). format.setInteger(MediaFormat.KEY_PROFILE, profile); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - format.setInteger(MediaFormat.KEY_LEVEL, level); + if(level != 0) { + if(suggestedLevel != 0 && level != suggestedLevel) + Ln.w("Requested codec profile level is different from the suggested level"); + } else { + level = suggestedLevel; // If no level was given, use the pre calculated level. + } + // Level (SDK Level 23). + // We ask again because suggested level can be 0 and we cant set the level to 0, + // in that case we let the encoder choose the level. + if (level != 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + format.setInteger(MediaFormat.KEY_LEVEL, level); + } } + } else { + Ln.w("Device doesn't support the requested codec profile.\n" + + "Profile and level will be chosen automatically."); } + } private static MediaCodec createCodec() throws IOException {