Add buffering command line options

Add --display-buffer and --v4l2-buffer options to configure buffering
time.
This commit is contained in:
Romain Vimont 2021-07-06 19:04:05 +02:00
parent 79278961b9
commit 3397720330
9 changed files with 98 additions and 5 deletions

View file

@ -321,6 +321,24 @@ For example, you could capture the video within [OBS].
[OBS]: https://obsproject.com/fr [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 ### Connection
#### Wireless #### Wireless

View file

@ -56,6 +56,12 @@ The list of possible display ids can be listed by "adb shell dumpsys display"
Default is 0. 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 .TP
.BI "\-\-encoder " name .BI "\-\-encoder " name
Use a specific MediaCodec encoder (must be a H.264 encoder). 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). 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 .TP
.BI "\-V, \-\-verbosity " value .BI "\-V, \-\-verbosity " value
Set the log level ("verbose", "debug", "info", "warn" or "error"). Set the log level ("verbose", "debug", "info", "warn" or "error").

View file

@ -55,6 +55,12 @@ scrcpy_print_usage(const char *arg0) {
"\n" "\n"
" Default is 0.\n" " Default is 0.\n"
"\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" " --encoder name\n"
" Use a specific MediaCodec encoder (must be a H.264 encoder).\n" " Use a specific MediaCodec encoder (must be a H.264 encoder).\n"
"\n" "\n"
@ -182,6 +188,15 @@ scrcpy_print_usage(const char *arg0) {
" It requires to lock the video orientation (see\n" " It requires to lock the video orientation (see\n"
" --lock-video-orientation).\n" " --lock-video-orientation).\n"
"\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 #endif
" -V, --verbosity value\n" " -V, --verbosity value\n"
" Set the log level (verbose, debug, info, warn or error).\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; 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 static bool
parse_lock_video_orientation(const char *s, parse_lock_video_orientation(const char *s,
enum sc_lock_video_orientation *lock_mode) { enum sc_lock_video_orientation *lock_mode) {
@ -689,6 +717,8 @@ guess_record_format(const char *filename) {
#define OPT_ENCODER_NAME 1025 #define OPT_ENCODER_NAME 1025
#define OPT_POWER_OFF_ON_CLOSE 1026 #define OPT_POWER_OFF_ON_CLOSE 1026
#define OPT_V4L2_SINK 1027 #define OPT_V4L2_SINK 1027
#define OPT_DISPLAY_BUFFER 1028
#define OPT_V4L2_BUFFER 1029
bool bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { 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, {"disable-screensaver", no_argument, NULL,
OPT_DISABLE_SCREENSAVER}, OPT_DISABLE_SCREENSAVER},
{"display", required_argument, NULL, OPT_DISPLAY_ID}, {"display", required_argument, NULL, OPT_DISPLAY_ID},
{"display-buffer", required_argument, NULL, OPT_DISPLAY_BUFFER},
{"encoder", required_argument, NULL, OPT_ENCODER_NAME}, {"encoder", required_argument, NULL, OPT_ENCODER_NAME},
{"force-adb-forward", no_argument, NULL, {"force-adb-forward", no_argument, NULL,
OPT_FORCE_ADB_FORWARD}, 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'}, {"turn-screen-off", no_argument, NULL, 'S'},
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
{"v4l2-sink", required_argument, NULL, OPT_V4L2_SINK}, {"v4l2-sink", required_argument, NULL, OPT_V4L2_SINK},
{"v4l2-buffer", required_argument, NULL, OPT_V4L2_BUFFER},
#endif #endif
{"verbosity", required_argument, NULL, 'V'}, {"verbosity", required_argument, NULL, 'V'},
{"version", no_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: case OPT_POWER_OFF_ON_CLOSE:
opts->power_off_on_close = true; opts->power_off_on_close = true;
break; break;
case OPT_DISPLAY_BUFFER:
if (!parse_buffering_time(optarg, &opts->display_buffer)) {
return false;
}
break;
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
case OPT_V4L2_SINK: case OPT_V4L2_SINK:
opts->v4l2_device = optarg; opts->v4l2_device = optarg;
break; break;
case OPT_V4L2_BUFFER:
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
return false;
}
break;
#endif #endif
default: default:
// getopt prints the error message on stderr // 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."); "See --lock-video-orientation.");
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL; 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 #else
if (!opts->display && !opts->record_filename) { if (!opts->display && !opts->record_filename) {
LOGE("-N/--no-display requires screen recording (-r/--record)"); LOGE("-N/--no-display requires screen recording (-r/--record)");

View file

@ -381,6 +381,7 @@ scrcpy(const struct scrcpy_options *options) {
.rotation = options->rotation, .rotation = options->rotation,
.mipmaps = options->mipmaps, .mipmaps = options->mipmaps,
.fullscreen = options->fullscreen, .fullscreen = options->fullscreen,
.buffering_time = options->display_buffer,
}; };
if (!screen_init(&s->screen, &screen_params)) { if (!screen_init(&s->screen, &screen_params)) {
@ -393,7 +394,8 @@ scrcpy(const struct scrcpy_options *options) {
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
if (options->v4l2_device) { 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; goto end;
} }

View file

@ -7,6 +7,8 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "util/tick.h"
enum sc_log_level { enum sc_log_level {
SC_LOG_LEVEL_VERBOSE, SC_LOG_LEVEL_VERBOSE,
SC_LOG_LEVEL_DEBUG, SC_LOG_LEVEL_DEBUG,
@ -78,6 +80,8 @@ struct scrcpy_options {
uint16_t window_width; uint16_t window_width;
uint16_t window_height; uint16_t window_height;
uint32_t display_id; uint32_t display_id;
sc_tick display_buffer;
sc_tick v4l2_buffer;
bool show_touches; bool show_touches;
bool fullscreen; bool fullscreen;
bool always_on_top; bool always_on_top;
@ -126,6 +130,8 @@ struct scrcpy_options {
.window_width = 0, \ .window_width = 0, \
.window_height = 0, \ .window_height = 0, \
.display_id = 0, \ .display_id = 0, \
.display_buffer = 0, \
.v4l2_buffer = 0, \
.show_touches = false, \ .show_touches = false, \
.fullscreen = false, \ .fullscreen = false, \
.always_on_top = false, \ .always_on_top = false, \

View file

@ -308,7 +308,8 @@ screen_init(struct screen *screen, const struct screen_params *params) {
.on_new_frame = sc_video_buffer_on_new_frame, .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) { if (!ok) {
LOGE("Could not initialize video buffer"); LOGE("Could not initialize video buffer");
return false; return false;

View file

@ -63,6 +63,8 @@ struct screen_params {
bool mipmaps; bool mipmaps;
bool fullscreen; bool fullscreen;
sc_tick buffering_time;
}; };
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)

View file

@ -159,7 +159,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
.on_new_frame = sc_video_buffer_on_new_frame, .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) { if (!ok) {
LOGE("Could not initialize video buffer"); LOGE("Could not initialize video buffer");
return false; return false;
@ -356,7 +356,7 @@ sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
bool bool
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, 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); vs->device_name = strdup(device_name);
if (!vs->device_name) { if (!vs->device_name) {
LOGE("Could not strdup v4l2 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->frame_size = frame_size;
vs->buffering_time = buffering_time;
static const struct sc_frame_sink_ops ops = { static const struct sc_frame_sink_ops ops = {
.open = sc_v4l2_frame_sink_open, .open = sc_v4l2_frame_sink_open,

View file

@ -6,6 +6,7 @@
#include "coords.h" #include "coords.h"
#include "trait/frame_sink.h" #include "trait/frame_sink.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/tick.h"
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
@ -18,6 +19,7 @@ struct sc_v4l2_sink {
char *device_name; char *device_name;
struct size frame_size; struct size frame_size;
sc_tick buffering_time;
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
@ -32,7 +34,7 @@ struct sc_v4l2_sink {
bool bool
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, 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 void
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs); sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);