diff --git a/app/src/cli.c b/app/src/cli.c index 4f4aa551..e2ae4804 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1447,6 +1447,26 @@ parse_integers_arg(const char *s, const char sep, size_t max_items, long *out, return count; } +static bool +parse_float_arg(const char *s, float *out, float min, float max, + const char *name) { + float value; + bool ok = sc_str_parse_float(s, &value); + if (!ok) { + LOGE("Could not parse %s: %s", name, s); + return false; + } + + if (value < min || value > max) { + LOGE("Could not parse %s: value (%f) out-of-range (%f; %f)", + name, value, min, max); + return false; + } + + *out = value; + return true; +} + static bool parse_bit_rate(const char *s, uint32_t *bit_rate) { long value; @@ -1474,14 +1494,14 @@ parse_max_size(const char *s, uint16_t *max_size) { } static bool -parse_max_fps(const char *s, uint16_t *max_fps) { - long value; - bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "max fps"); +parse_max_fps(const char *s, float *max_fps) { + float value; + bool ok = parse_float_arg(s, &value, 0, (float) (1 << 16), "max fps"); if (!ok) { return false; } - *max_fps = (uint16_t) value; + *max_fps = value; return true; } diff --git a/app/src/options.h b/app/src/options.h index 140d12b1..ee0be00a 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -240,7 +240,7 @@ struct scrcpy_options { uint16_t max_size; uint32_t video_bit_rate; uint32_t audio_bit_rate; - uint16_t max_fps; + float max_fps; enum sc_lock_video_orientation lock_video_orientation; enum sc_orientation display_orientation; enum sc_orientation record_orientation; diff --git a/app/src/server.c b/app/src/server.c index b67cb8b2..320062a4 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -321,7 +321,7 @@ execute_server(struct sc_server *server, ADD_PARAM("max_size=%" PRIu16, params->max_size); } if (params->max_fps) { - ADD_PARAM("max_fps=%" PRIu16, params->max_fps); + ADD_PARAM("max_fps=%f" , params->max_fps); } if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { ADD_PARAM("lock_video_orientation=%" PRIi8, diff --git a/app/src/server.h b/app/src/server.h index cffa510e..81e8e05b 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -44,7 +44,7 @@ struct sc_server_params { uint16_t max_size; uint32_t video_bit_rate; uint32_t audio_bit_rate; - uint16_t max_fps; + float max_fps; int8_t lock_video_orientation; bool control; uint32_t display_id; diff --git a/app/src/util/str.c b/app/src/util/str.c index 755369d8..7ca880d7 100644 --- a/app/src/util/str.c +++ b/app/src/util/str.c @@ -147,6 +147,25 @@ sc_str_parse_integer_with_suffix(const char *s, long *out) { return true; } +bool +sc_str_parse_float(const char *s, float *out) { + char *endptr; + if (*s == '\0') { + return false; + } + errno = 0; + float value = strtof(s, &endptr); + if (errno == ERANGE) { + return false; + } + if (*endptr != '\0') { + return false; + } + + *out = value; + return true; +} + bool sc_str_list_contains(const char *list, char sep, const char *s) { char *p; diff --git a/app/src/util/str.h b/app/src/util/str.h index 20da26f0..98cb1d74 100644 --- a/app/src/util/str.h +++ b/app/src/util/str.h @@ -66,6 +66,14 @@ sc_str_parse_integers(const char *s, const char sep, size_t max_items, bool sc_str_parse_integer_with_suffix(const char *s, long *out); +/** + * `Parse `s` as a float into `out` + * + * Return true if the conversion succeeded, false otherwise. + */ +bool +sc_str_parse_float(const char *s, float *out); + /** * Search `s` in the list separated by `sep` * diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index d07828eb..51daeced 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -29,7 +29,7 @@ public class Options { private boolean audioDup; private int videoBitRate = 8000000; private int audioBitRate = 128000; - private int maxFps; + private float maxFps; private int lockVideoOrientation = -1; private boolean tunnelForward; private Rect crop; @@ -113,7 +113,7 @@ public class Options { return audioBitRate; } - public int getMaxFps() { + public float getMaxFps() { return maxFps; } @@ -321,7 +321,7 @@ public class Options { options.audioBitRate = Integer.parseInt(value); break; case "max_fps": - options.maxFps = Integer.parseInt(value); + options.maxFps = parseFloat("max_fps", value); break; case "lock_video_orientation": options.lockVideoOrientation = Integer.parseInt(value); @@ -493,4 +493,12 @@ public class Options { float floatAr = Float.parseFloat(tokens[0]); return CameraAspectRatio.fromFloat(floatAr); } + + private static float parseFloat(String key, String value) { + try { + return Float.parseFloat(value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid float value for " + key + ": \"" + value + "\""); + } + } } 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 8fe0b227..a5f2d1e9 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/SurfaceEncoder.java @@ -39,7 +39,7 @@ public class SurfaceEncoder implements AsyncProcessor { private final String encoderName; private final List codecOptions; private final int videoBitRate; - private final int maxFps; + private final float maxFps; private final boolean downsizeOnError; private boolean firstFrameSent; @@ -48,8 +48,8 @@ public class SurfaceEncoder implements AsyncProcessor { private Thread thread; private final AtomicBoolean stopped = new AtomicBoolean(); - public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, int videoBitRate, int maxFps, List codecOptions, String encoderName, - boolean downsizeOnError) { + public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, int videoBitRate, float maxFps, List codecOptions, + String encoderName, boolean downsizeOnError) { this.capture = capture; this.streamer = streamer; this.videoBitRate = videoBitRate; @@ -225,7 +225,7 @@ public class SurfaceEncoder implements AsyncProcessor { } } - private static MediaFormat createFormat(String videoMimeType, int bitRate, int maxFps, List codecOptions) { + private static MediaFormat createFormat(String videoMimeType, int bitRate, float maxFps, List codecOptions) { MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, videoMimeType); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);