From ebccb9f6cc111e8acfbe10d656cac5c1f1b744a0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 5 Jun 2019 19:02:50 +0200 Subject: [PATCH] Add runtime option to render expired frames Replace the compilation flag SKIP_FRAMES by a runtime flag to force rendering of expired frames. By default, the expired frames are skipped. --- README.md | 13 ++++++++ app/meson.build | 5 ---- app/src/fps_counter.c | 12 +------- app/src/fps_counter.h | 6 ---- app/src/main.c | 16 ++++++++++ app/src/scrcpy.c | 2 +- app/src/scrcpy.h | 1 + app/src/video_buffer.c | 67 +++++++++++++++++++++--------------------- app/src/video_buffer.h | 6 ++-- 9 files changed, 67 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 2dcb3434a3..10f6138e54 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,19 @@ Or by pressing `Ctrl`+`o` at any time. To turn it back on, press `POWER` (or `Ctrl`+`p`). +### Render expired frames + +By default, to minimize latency, _scrcpy_ always renders the last decoded frame +available, and drops any previous one. + +To force the rendering of all frames (at a cost of a possible increased +latency), use: + +```bash +scrcpy --render-expired-frames +``` + + ### Forward audio Audio is not forwarded by _scrcpy_. diff --git a/app/meson.build b/app/meson.build index d47eda7958..732cf1eeb3 100644 --- a/app/meson.build +++ b/app/meson.build @@ -122,11 +122,6 @@ conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited # overridden by option --bit-rate conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps -# whether the app should always display the most recent available frame, even -# if the previous one has not been displayed -# SKIP_FRAMES improves latency at the cost of framerate -conf.set('SKIP_FRAMES', get_option('skip_frames')) - # enable High DPI support conf.set('HIDPI_SUPPORT', get_option('hidpi_support')) diff --git a/app/src/fps_counter.c b/app/src/fps_counter.c index 6c8ef7957c..133c7a250e 100644 --- a/app/src/fps_counter.c +++ b/app/src/fps_counter.c @@ -16,9 +16,7 @@ fps_counter_start(struct fps_counter *counter) { counter->started = true; counter->slice_start = SDL_GetTicks(); counter->nr_rendered = 0; -#ifdef SKIP_FRAMES counter->nr_skipped = 0; -#endif } void @@ -28,16 +26,12 @@ fps_counter_stop(struct fps_counter *counter) { static void display_fps(struct fps_counter *counter) { -#ifdef SKIP_FRAMES if (counter->nr_skipped) { LOGI("%d fps (+%d frames skipped)", counter->nr_rendered, counter->nr_skipped); } else { -#endif - LOGI("%d fps", counter->nr_rendered); -#ifdef SKIP_FRAMES + LOGI("%d fps", counter->nr_rendered); } -#endif } static void @@ -49,9 +43,7 @@ check_expired(struct fps_counter *counter) { uint32_t elapsed_slices = (now - counter->slice_start) / 1000; counter->slice_start += 1000 * elapsed_slices; counter->nr_rendered = 0; -#ifdef SKIP_FRAMES counter->nr_skipped = 0; -#endif } } @@ -61,10 +53,8 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) { ++counter->nr_rendered; } -#ifdef SKIP_FRAMES void fps_counter_add_skipped_frame(struct fps_counter *counter) { check_expired(counter); ++counter->nr_skipped; } -#endif diff --git a/app/src/fps_counter.h b/app/src/fps_counter.h index dcdf10bf30..fecef806fb 100644 --- a/app/src/fps_counter.h +++ b/app/src/fps_counter.h @@ -4,15 +4,11 @@ #include #include -#include "config.h" - struct fps_counter { bool started; uint32_t slice_start; // initialized by SDL_GetTicks() int nr_rendered; -#ifdef SKIP_FRAMES int nr_skipped; -#endif }; void @@ -27,9 +23,7 @@ fps_counter_stop(struct fps_counter *counter); void fps_counter_add_rendered_frame(struct fps_counter *counter); -#ifdef SKIP_FRAMES void fps_counter_add_skipped_frame(struct fps_counter *counter); -#endif #endif diff --git a/app/src/main.c b/app/src/main.c index 09ea107c60..bf3b7a50ae 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -29,6 +29,7 @@ struct args { uint32_t bit_rate; bool always_on_top; bool turn_screen_off; + bool render_expired_frames; }; static void usage(const char *arg0) { @@ -79,6 +80,12 @@ static void usage(const char *arg0) { " The format is determined by the -F/--record-format option if\n" " set, or by the file extension (.mp4 or .mkv).\n" "\n" + " --render-expired-frames\n" + " By default, to minimize latency, scrcpy always renders the\n" + " last available decoded frame, and drops any previous ones.\n" + " This flag forces to render all frames, at a cost of a\n" + " possible increased latency.\n" + "\n" " -s, --serial\n" " The device serial number. Mandatory only if several devices\n" " are connected to adb.\n" @@ -287,6 +294,8 @@ guess_record_format(const char *filename) { return 0; } +#define OPT_RENDER_EXPIRED_FRAMES 1000 + static bool parse_args(struct args *args, int argc, char *argv[]) { static const struct option long_options[] = { @@ -301,6 +310,8 @@ parse_args(struct args *args, int argc, char *argv[]) { {"port", required_argument, NULL, 'p'}, {"record", required_argument, NULL, 'r'}, {"record-format", required_argument, NULL, 'f'}, + {"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'}, @@ -364,6 +375,9 @@ parse_args(struct args *args, int argc, char *argv[]) { case 'v': args->version = true; break; + case OPT_RENDER_EXPIRED_FRAMES: + args->render_expired_frames = true; + break; default: // getopt prints the error message on stderr return false; @@ -426,6 +440,7 @@ main(int argc, char *argv[]) { .no_control = false, .no_display = false, .turn_screen_off = false, + .render_expired_frames = false, }; if (!parse_args(&args, argc, argv)) { return 1; @@ -467,6 +482,7 @@ main(int argc, char *argv[]) { .control = !args.no_control, .display = !args.no_display, .turn_screen_off = args.turn_screen_off, + .render_expired_frames = args.render_expired_frames, }; int res = scrcpy(&options) ? 0 : 1; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 0e24fc7c5a..f50419027d 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -320,7 +320,7 @@ scrcpy(const struct scrcpy_options *options) { struct decoder *dec = NULL; if (options->display) { - if (!video_buffer_init(&video_buffer)) { + if (!video_buffer_init(&video_buffer, options->render_expired_frames)) { goto end; } video_buffer_initialized = true; diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 4cbadcad8a..d705d2db11 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -19,6 +19,7 @@ struct scrcpy_options { bool control; bool display; bool turn_screen_off; + bool render_expired_frames; }; bool diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 8f1d1d9dcd..f26af0b6a8 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -10,7 +10,7 @@ #include "log.h" bool -video_buffer_init(struct video_buffer *vb) { +video_buffer_init(struct video_buffer *vb, bool render_expired_frames) { if (!(vb->decoding_frame = av_frame_alloc())) { goto error_0; } @@ -23,13 +23,16 @@ video_buffer_init(struct video_buffer *vb) { goto error_2; } -#ifndef SKIP_FRAMES - if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) { - SDL_DestroyMutex(vb->mutex); - goto error_2; + vb->render_expired_frames = render_expired_frames; + if (render_expired_frames) { + if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) { + SDL_DestroyMutex(vb->mutex); + goto error_2; + } + // interrupted is not used if expired frames are not rendered + // since offering a frame will never block + vb->interrupted = false; } - vb->interrupted = false; -#endif // there is initially no rendering frame, so consider it has already been // consumed @@ -48,9 +51,9 @@ video_buffer_init(struct video_buffer *vb) { void video_buffer_destroy(struct video_buffer *vb) { -#ifndef SKIP_FRAMES - SDL_DestroyCond(vb->rendering_frame_consumed_cond); -#endif + if (vb->render_expired_frames) { + SDL_DestroyCond(vb->rendering_frame_consumed_cond); + } SDL_DestroyMutex(vb->mutex); av_frame_free(&vb->rendering_frame); av_frame_free(&vb->decoding_frame); @@ -67,17 +70,16 @@ void video_buffer_offer_decoded_frame(struct video_buffer *vb, bool *previous_frame_skipped) { mutex_lock(vb->mutex); -#ifndef SKIP_FRAMES - // if SKIP_FRAMES is disabled, then the decoder must wait for the current - // frame to be consumed - while (!vb->rendering_frame_consumed && !vb->interrupted) { - cond_wait(vb->rendering_frame_consumed_cond, vb->mutex); + if (vb->render_expired_frames) { + // wait for the current (expired) frame to be consumed + while (!vb->rendering_frame_consumed && !vb->interrupted) { + cond_wait(vb->rendering_frame_consumed_cond, vb->mutex); + } + } else { + if (vb->fps_counter.started && !vb->rendering_frame_consumed) { + fps_counter_add_skipped_frame(&vb->fps_counter); + } } -#else - if (vb->fps_counter.started && !vb->rendering_frame_consumed) { - fps_counter_add_skipped_frame(&vb->fps_counter); - } -#endif video_buffer_swap_frames(vb); @@ -94,23 +96,20 @@ video_buffer_consume_rendered_frame(struct video_buffer *vb) { if (vb->fps_counter.started) { fps_counter_add_rendered_frame(&vb->fps_counter); } -#ifndef SKIP_FRAMES - // if SKIP_FRAMES is disabled, then notify the decoder the current frame is - // consumed, so that it may push a new one - cond_signal(vb->rendering_frame_consumed_cond); -#endif + if (vb->render_expired_frames) { + // unblock video_buffer_offer_decoded_frame() + cond_signal(vb->rendering_frame_consumed_cond); + } return vb->rendering_frame; } void video_buffer_interrupt(struct video_buffer *vb) { -#ifdef SKIP_FRAMES - (void) vb; // unused -#else - mutex_lock(vb->mutex); - vb->interrupted = true; - mutex_unlock(vb->mutex); - // wake up blocking wait - cond_signal(vb->rendering_frame_consumed_cond); -#endif + if (vb->render_expired_frames) { + mutex_lock(vb->mutex); + vb->interrupted = true; + mutex_unlock(vb->mutex); + // wake up blocking wait + cond_signal(vb->rendering_frame_consumed_cond); + } } diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index 93222236f7..9f3281725a 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -4,7 +4,6 @@ #include #include -#include "config.h" #include "fps_counter.h" // forward declarations @@ -14,16 +13,15 @@ struct video_buffer { AVFrame *decoding_frame; AVFrame *rendering_frame; SDL_mutex *mutex; -#ifndef SKIP_FRAMES + bool render_expired_frames; bool interrupted; SDL_cond *rendering_frame_consumed_cond; -#endif bool rendering_frame_consumed; struct fps_counter fps_counter; }; bool -video_buffer_init(struct video_buffer *vb); +video_buffer_init(struct video_buffer *vb, bool render_expired_frames); void video_buffer_destroy(struct video_buffer *vb);