diff --git a/README.md b/README.md index 0dfa068c..7b1d2e78 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,24 @@ For example, you could capture the video within [OBS]. [OBS]: https://obsproject.com/fr +#### Buffering + +It is possible to add buffering. This increases latency but reduces jitter (see +#2464). + +The option is available for display buffering: + +```bash +scrcpy --display-buffer=50 # add 50 ms buffering for display +``` + +and V4L2 sink: + +```bash +scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink +``` + + ### Connection #### Wireless diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 253dd04f..00589a43 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -56,6 +56,12 @@ The list of possible display ids can be listed by "adb shell dumpsys display" Default is 0. +.TP +.BI "\-\-display\-buffer ms +Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter. + +Default is 0 (no buffering). + .TP .BI "\-\-encoder " name Use a specific MediaCodec encoder (must be a H.264 encoder). @@ -191,6 +197,14 @@ Output to v4l2loopback device. It requires to lock the video orientation (see \fB\-\-lock\-video\-orientation\fR). +.TP +.BI "\-\-v4l2-buffer " ms +Add a buffering delay (in milliseconds) before pushing frames. This increases latency to compensate for jitter. + +This option is similar to \fB\-\-display\-buffer\fR, but specific to V4L2 sink. + +Default is 0 (no buffering). + .TP .BI "\-V, \-\-verbosity " value Set the log level ("verbose", "debug", "info", "warn" or "error"). diff --git a/app/src/cli.c b/app/src/cli.c index ab35745d..d22096ca 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -55,6 +55,12 @@ scrcpy_print_usage(const char *arg0) { "\n" " Default is 0.\n" "\n" + " --display-buffer ms\n" + " Add a buffering delay (in milliseconds) before displaying.\n" + " This increases latency to compensate for jitter.\n" + "\n" + " Default is 0 (no buffering).\n" + "\n" " --encoder name\n" " Use a specific MediaCodec encoder (must be a H.264 encoder).\n" "\n" @@ -182,6 +188,15 @@ scrcpy_print_usage(const char *arg0) { " It requires to lock the video orientation (see\n" " --lock-video-orientation).\n" "\n" + " --v4l2-buffer ms\n" + " Add a buffering delay (in milliseconds) before pushing\n" + " frames. This increases latency to compensate for jitter.\n" + "\n" + " This option is similar to --display-buffer, but specific to\n" + " V4L2 sink.\n" + "\n" + " Default is 0 (no buffering).\n" + "\n" #endif " -V, --verbosity value\n" " Set the log level (verbose, debug, info, warn or error).\n" @@ -392,6 +407,19 @@ parse_max_fps(const char *s, uint16_t *max_fps) { return true; } +static bool +parse_buffering_time(const char *s, sc_tick *tick) { + long value; + bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF, + "buffering time"); + if (!ok) { + return false; + } + + *tick = SC_TICK_FROM_MS(value); + return true; +} + static bool parse_lock_video_orientation(const char *s, enum sc_lock_video_orientation *lock_mode) { @@ -689,6 +717,8 @@ guess_record_format(const char *filename) { #define OPT_ENCODER_NAME 1025 #define OPT_POWER_OFF_ON_CLOSE 1026 #define OPT_V4L2_SINK 1027 +#define OPT_DISPLAY_BUFFER 1028 +#define OPT_V4L2_BUFFER 1029 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { @@ -700,6 +730,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"disable-screensaver", no_argument, NULL, OPT_DISABLE_SCREENSAVER}, {"display", required_argument, NULL, OPT_DISPLAY_ID}, + {"display-buffer", required_argument, NULL, OPT_DISPLAY_BUFFER}, {"encoder", required_argument, NULL, OPT_ENCODER_NAME}, {"force-adb-forward", no_argument, NULL, OPT_FORCE_ADB_FORWARD}, @@ -732,6 +763,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { {"turn-screen-off", no_argument, NULL, 'S'}, #ifdef HAVE_V4L2 {"v4l2-sink", required_argument, NULL, OPT_V4L2_SINK}, + {"v4l2-buffer", required_argument, NULL, OPT_V4L2_BUFFER}, #endif {"verbosity", required_argument, NULL, 'V'}, {"version", no_argument, NULL, 'v'}, @@ -917,10 +949,20 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_POWER_OFF_ON_CLOSE: opts->power_off_on_close = true; break; + case OPT_DISPLAY_BUFFER: + if (!parse_buffering_time(optarg, &opts->display_buffer)) { + return false; + } + break; #ifdef HAVE_V4L2 case OPT_V4L2_SINK: opts->v4l2_device = optarg; break; + case OPT_V4L2_BUFFER: + if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) { + return false; + } + break; #endif default: // getopt prints the error message on stderr @@ -941,6 +983,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { "See --lock-video-orientation."); opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL; } + + if (opts->v4l2_buffer && !opts->v4l2_device) { + LOGE("V4L2 buffer value without V4L2 sink\n"); + return false; + } #else if (!opts->display && !opts->record_filename) { LOGE("-N/--no-display requires screen recording (-r/--record)"); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index d0a22e77..25822526 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -381,6 +381,7 @@ scrcpy(const struct scrcpy_options *options) { .rotation = options->rotation, .mipmaps = options->mipmaps, .fullscreen = options->fullscreen, + .buffering_time = options->display_buffer, }; if (!screen_init(&s->screen, &screen_params)) { @@ -393,7 +394,8 @@ scrcpy(const struct scrcpy_options *options) { #ifdef HAVE_V4L2 if (options->v4l2_device) { - if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size)) { + if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size, + options->v4l2_buffer)) { goto end; } diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 0a2deb71..8b76fb25 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -7,6 +7,8 @@ #include #include +#include "util/tick.h" + enum sc_log_level { SC_LOG_LEVEL_VERBOSE, SC_LOG_LEVEL_DEBUG, @@ -78,6 +80,8 @@ struct scrcpy_options { uint16_t window_width; uint16_t window_height; uint32_t display_id; + sc_tick display_buffer; + sc_tick v4l2_buffer; bool show_touches; bool fullscreen; bool always_on_top; @@ -126,6 +130,8 @@ struct scrcpy_options { .window_width = 0, \ .window_height = 0, \ .display_id = 0, \ + .display_buffer = 0, \ + .v4l2_buffer = 0, \ .show_touches = false, \ .fullscreen = false, \ .always_on_top = false, \ diff --git a/app/src/screen.c b/app/src/screen.c index 126caf9b..3cd4329f 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -308,7 +308,8 @@ screen_init(struct screen *screen, const struct screen_params *params) { .on_new_frame = sc_video_buffer_on_new_frame, }; - bool ok = sc_video_buffer_init(&screen->vb, 0, &cbs, screen); + bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs, + screen); if (!ok) { LOGE("Could not initialize video buffer"); return false; diff --git a/app/src/screen.h b/app/src/screen.h index e38d65bc..86aa1183 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -63,6 +63,8 @@ struct screen_params { bool mipmaps; bool fullscreen; + + sc_tick buffering_time; }; // initialize screen, create window, renderer and texture (window is hidden) diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 8f8b98ee..cae3eee9 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -159,7 +159,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { .on_new_frame = sc_video_buffer_on_new_frame, }; - bool ok = sc_video_buffer_init(&vs->vb, 0, &cbs, vs); + bool ok = sc_video_buffer_init(&vs->vb, vs->buffering_time, &cbs, vs); if (!ok) { LOGE("Could not initialize video buffer"); return false; @@ -356,7 +356,7 @@ sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { bool sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, - struct size frame_size) { + struct size frame_size, sc_tick buffering_time) { vs->device_name = strdup(device_name); if (!vs->device_name) { LOGE("Could not strdup v4l2 device name"); @@ -364,6 +364,7 @@ sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, } vs->frame_size = frame_size; + vs->buffering_time = buffering_time; static const struct sc_frame_sink_ops ops = { .open = sc_v4l2_frame_sink_open, diff --git a/app/src/v4l2_sink.h b/app/src/v4l2_sink.h index cf3fdddc..6773cd26 100644 --- a/app/src/v4l2_sink.h +++ b/app/src/v4l2_sink.h @@ -6,6 +6,7 @@ #include "coords.h" #include "trait/frame_sink.h" #include "video_buffer.h" +#include "util/tick.h" #include @@ -18,6 +19,7 @@ struct sc_v4l2_sink { char *device_name; struct size frame_size; + sc_tick buffering_time; sc_thread thread; sc_mutex mutex; @@ -32,7 +34,7 @@ struct sc_v4l2_sink { bool sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, - struct size frame_size); + struct size frame_size, sc_tick buffering_time); void sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);