diff --git a/app/src/main.c b/app/src/main.c index d05c607b..beb25faa 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -6,13 +6,15 @@ #include #define DEFAULT_LOCAL_PORT 27183 -#define DEFAULT_MAX_SIZE 0 +#define DEFAULT_MAX_SIZE 0 // unlimited +#define DEFAULT_BIT_RATE 4000000 // 4Mbps struct args { const char *serial; SDL_bool help; Uint16 port; Uint16 max_size; + Uint32 bit_rate; }; static void usage(const char *arg0) { @@ -25,6 +27,11 @@ static void usage(const char *arg0) { "\n" "Options:\n" "\n" + " -b, --bit-rate value\n" + " Encode the video at the given bit-rate, expressed in bits/s.\n" + " Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" + " Default is %d.\n" + "\n" " -h, --help\n" " Print this help.\n" "\n" @@ -45,6 +52,7 @@ static void usage(const char *arg0) { " Ctrl+x: resize window to optimal size (remove black borders)\n" "\n", arg0, + DEFAULT_BIT_RATE, DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)", DEFAULT_LOCAL_PORT); } @@ -54,10 +62,11 @@ static int parse_args(struct args *args, int argc, char *argv[]) { {"help", no_argument, NULL, 'h'}, {"port", required_argument, NULL, 'p'}, {"max-size", required_argument, NULL, 'm'}, + {"bit-rate", required_argument, NULL, 'b'}, {NULL, 0, NULL, 0 }, }; int c; - while ((c = getopt_long(argc, argv, "hp:m:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hp:m:b:", long_options, NULL)) != -1) { switch (c) { case 'h': { args->help = SDL_TRUE; @@ -99,6 +108,35 @@ static int parse_args(struct args *args, int argc, char *argv[]) { args->max_size = (Uint16) value; break; } + case 'b': { + char *endptr; + if (*optarg == '\0') { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Bit-rate parameter is empty"); + return -1; + } + long value = strtol(optarg, &endptr, 0); + int mul = 1; + if (*endptr != '\0') { + if (optarg == endptr) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid bit-rate: %s", optarg); + return -1; + } + if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') { + mul = 1000000; + } else if ((*endptr == 'K' || *endptr == 'k') && endptr[1] == '\0') { + mul = 1000; + } else { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid bit-rate unit: %s", optarg); + return -1; + } + } + if (value < 0 || ((Uint32) -1) / mul < value) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Bitrate must be positive and less than 2^32: %s", optarg); + return -1; + } + args->bit_rate = (Uint32) value * mul; + break; + } default: // getopt prints the error message on stderr return -1; @@ -120,10 +158,11 @@ int main(int argc, char *argv[]) { int res; struct args args = { - .help = SDL_FALSE, .serial = NULL, - .max_size = DEFAULT_MAX_SIZE, + .help = SDL_FALSE, .port = DEFAULT_LOCAL_PORT, + .max_size = DEFAULT_MAX_SIZE, + .bit_rate = DEFAULT_BIT_RATE, }; if (parse_args(&args, argc, argv)) { usage(argv[0]); @@ -143,7 +182,7 @@ int main(int argc, char *argv[]) { SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG); - res = scrcpy(args.serial, args.port, args.max_size) ? 0 : 1; + res = scrcpy(args.serial, args.port, args.max_size, args.bit_rate) ? 0 : 1; avformat_network_deinit(); // ignore failure diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 0bd3ce8b..7b50e8be 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -417,7 +417,7 @@ void event_loop(void) { } } -SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size) { +SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate) { SDL_bool ret = 0; process_t push_proc = push_server(serial); @@ -437,7 +437,7 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size) { } // server will connect to our socket - process_t server = start_server(serial, max_size); + process_t server = start_server(serial, max_size, bit_rate); if (server == PROCESS_NONE) { ret = SDL_FALSE; SDLNet_TCP_Close(server_socket); diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 1a89dc89..f8936c18 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -3,6 +3,6 @@ #include -SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size); +SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate); #endif diff --git a/app/src/server.c b/app/src/server.c index a7bb9b60..41f2a20a 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -2,6 +2,7 @@ #include #include +#include #define SOCKET_NAME "scrcpy" @@ -21,9 +22,11 @@ process_t disable_tunnel(const char *serial) { return adb_reverse_remove(serial, SOCKET_NAME); } -process_t start_server(const char *serial, Uint16 max_size) { +process_t start_server(const char *serial, Uint16 max_size, Uint32 bit_rate) { char max_size_string[6]; - sprintf(max_size_string, "%d", max_size); + char bit_rate_string[11]; + sprintf(max_size_string, "%"PRIu16, max_size); + sprintf(bit_rate_string, "%"PRIu32, bit_rate); const char *const cmd[] = { "shell", "CLASSPATH=/data/local/tmp/scrcpy.apk", @@ -31,6 +34,7 @@ process_t start_server(const char *serial, Uint16 max_size) { "/", // unused "com.genymobile.scrcpy.ScrCpyServer", max_size_string, + bit_rate_string, }; return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0])); } diff --git a/app/src/server.h b/app/src/server.h index 3b49ef9e..3030327f 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -4,5 +4,5 @@ process_t push_server(const char *serial); process_t enable_tunnel(const char *serial, Uint16 local_port); process_t disable_tunnel(const char *serial); -process_t start_server(const char *serial, Uint16 max_size); +process_t start_server(const char *serial, Uint16 max_size, Uint32 bit_rate); void stop_server(process_t server); diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 30083e91..6ab2f694 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -2,6 +2,7 @@ package com.genymobile.scrcpy; public class Options { private int maxSize; + private int bitRate; public int getMaxSize() { return maxSize; @@ -10,4 +11,12 @@ public class Options { public void setMaxSize(int maxSize) { this.maxSize = maxSize; } + + public int getBitRate() { + return bitRate; + } + + public void setBitRate(int bitRate) { + this.bitRate = bitRate; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScrCpyServer.java b/server/src/main/java/com/genymobile/scrcpy/ScrCpyServer.java index 23d29e3a..cfe508ba 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScrCpyServer.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScrCpyServer.java @@ -7,7 +7,7 @@ public class ScrCpyServer { private static void scrcpy(Options options) throws IOException { final Device device = new Device(options); try (DesktopConnection connection = DesktopConnection.open(device)) { - ScreenEncoder screenEncoder = new ScreenEncoder(); + ScreenEncoder screenEncoder = new ScreenEncoder(options.getBitRate()); // asynchronous startEventController(device, connection); @@ -36,10 +36,18 @@ public class ScrCpyServer { private static Options createOptions(String... args) { Options options = new Options(); - if (args.length > 0) { - int maxSize = Integer.parseInt(args[0]) & ~7; // multiple of 8 - options.setMaxSize(maxSize); + if (args.length < 1) { + return options; } + int maxSize = Integer.parseInt(args[0]) & ~7; // multiple of 8 + options.setMaxSize(maxSize); + + if (args.length < 2) { + return options; + } + int bitRate = Integer.parseInt(args[1]); + options.setBitRate(bitRate); + return options; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index 2f33e4e3..7da2ca2f 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -34,6 +34,10 @@ public class ScreenEncoder implements Device.RotationListener { this.iFrameInterval = iFrameInterval; } + public ScreenEncoder(int bitRate) { + this(bitRate, DEFAULT_FRAME_RATE, DEFAULT_I_FRAME_INTERVAL); + } + public ScreenEncoder() { this(DEFAULT_BIT_RATE, DEFAULT_FRAME_RATE, DEFAULT_I_FRAME_INTERVAL); }