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.
This commit is contained in:
Romain Vimont 2019-06-05 19:02:50 +02:00
parent a143b8b07a
commit ebccb9f6cc
9 changed files with 67 additions and 61 deletions

View file

@ -271,6 +271,19 @@ Or by pressing `Ctrl`+`o` at any time.
To turn it back on, press `POWER` (or `Ctrl`+`p`). 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 ### Forward audio
Audio is not forwarded by _scrcpy_. Audio is not forwarded by _scrcpy_.

View file

@ -122,11 +122,6 @@ conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited
# overridden by option --bit-rate # overridden by option --bit-rate
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps 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 # enable High DPI support
conf.set('HIDPI_SUPPORT', get_option('hidpi_support')) conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))

View file

@ -16,9 +16,7 @@ fps_counter_start(struct fps_counter *counter) {
counter->started = true; counter->started = true;
counter->slice_start = SDL_GetTicks(); counter->slice_start = SDL_GetTicks();
counter->nr_rendered = 0; counter->nr_rendered = 0;
#ifdef SKIP_FRAMES
counter->nr_skipped = 0; counter->nr_skipped = 0;
#endif
} }
void void
@ -28,16 +26,12 @@ fps_counter_stop(struct fps_counter *counter) {
static void static void
display_fps(struct fps_counter *counter) { display_fps(struct fps_counter *counter) {
#ifdef SKIP_FRAMES
if (counter->nr_skipped) { if (counter->nr_skipped) {
LOGI("%d fps (+%d frames skipped)", counter->nr_rendered, LOGI("%d fps (+%d frames skipped)", counter->nr_rendered,
counter->nr_skipped); counter->nr_skipped);
} else { } else {
#endif LOGI("%d fps", counter->nr_rendered);
LOGI("%d fps", counter->nr_rendered);
#ifdef SKIP_FRAMES
} }
#endif
} }
static void static void
@ -49,9 +43,7 @@ check_expired(struct fps_counter *counter) {
uint32_t elapsed_slices = (now - counter->slice_start) / 1000; uint32_t elapsed_slices = (now - counter->slice_start) / 1000;
counter->slice_start += 1000 * elapsed_slices; counter->slice_start += 1000 * elapsed_slices;
counter->nr_rendered = 0; counter->nr_rendered = 0;
#ifdef SKIP_FRAMES
counter->nr_skipped = 0; counter->nr_skipped = 0;
#endif
} }
} }
@ -61,10 +53,8 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
++counter->nr_rendered; ++counter->nr_rendered;
} }
#ifdef SKIP_FRAMES
void void
fps_counter_add_skipped_frame(struct fps_counter *counter) { fps_counter_add_skipped_frame(struct fps_counter *counter) {
check_expired(counter); check_expired(counter);
++counter->nr_skipped; ++counter->nr_skipped;
} }
#endif

View file

@ -4,15 +4,11 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "config.h"
struct fps_counter { struct fps_counter {
bool started; bool started;
uint32_t slice_start; // initialized by SDL_GetTicks() uint32_t slice_start; // initialized by SDL_GetTicks()
int nr_rendered; int nr_rendered;
#ifdef SKIP_FRAMES
int nr_skipped; int nr_skipped;
#endif
}; };
void void
@ -27,9 +23,7 @@ fps_counter_stop(struct fps_counter *counter);
void void
fps_counter_add_rendered_frame(struct fps_counter *counter); fps_counter_add_rendered_frame(struct fps_counter *counter);
#ifdef SKIP_FRAMES
void void
fps_counter_add_skipped_frame(struct fps_counter *counter); fps_counter_add_skipped_frame(struct fps_counter *counter);
#endif
#endif #endif

View file

@ -29,6 +29,7 @@ struct args {
uint32_t bit_rate; uint32_t bit_rate;
bool always_on_top; bool always_on_top;
bool turn_screen_off; bool turn_screen_off;
bool render_expired_frames;
}; };
static void usage(const char *arg0) { 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" " The format is determined by the -F/--record-format option if\n"
" set, or by the file extension (.mp4 or .mkv).\n" " set, or by the file extension (.mp4 or .mkv).\n"
"\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" " -s, --serial\n"
" The device serial number. Mandatory only if several devices\n" " The device serial number. Mandatory only if several devices\n"
" are connected to adb.\n" " are connected to adb.\n"
@ -287,6 +294,8 @@ guess_record_format(const char *filename) {
return 0; return 0;
} }
#define OPT_RENDER_EXPIRED_FRAMES 1000
static bool static bool
parse_args(struct args *args, int argc, char *argv[]) { parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = { static const struct option long_options[] = {
@ -301,6 +310,8 @@ parse_args(struct args *args, int argc, char *argv[]) {
{"port", required_argument, NULL, 'p'}, {"port", required_argument, NULL, 'p'},
{"record", required_argument, NULL, 'r'}, {"record", required_argument, NULL, 'r'},
{"record-format", required_argument, NULL, 'f'}, {"record-format", required_argument, NULL, 'f'},
{"render-expired-frames", no_argument, NULL,
OPT_RENDER_EXPIRED_FRAMES},
{"serial", required_argument, NULL, 's'}, {"serial", required_argument, NULL, 's'},
{"show-touches", no_argument, NULL, 't'}, {"show-touches", no_argument, NULL, 't'},
{"turn-screen-off", no_argument, NULL, 'S'}, {"turn-screen-off", no_argument, NULL, 'S'},
@ -364,6 +375,9 @@ parse_args(struct args *args, int argc, char *argv[]) {
case 'v': case 'v':
args->version = true; args->version = true;
break; break;
case OPT_RENDER_EXPIRED_FRAMES:
args->render_expired_frames = true;
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;
@ -426,6 +440,7 @@ main(int argc, char *argv[]) {
.no_control = false, .no_control = false,
.no_display = false, .no_display = false,
.turn_screen_off = false, .turn_screen_off = false,
.render_expired_frames = false,
}; };
if (!parse_args(&args, argc, argv)) { if (!parse_args(&args, argc, argv)) {
return 1; return 1;
@ -467,6 +482,7 @@ main(int argc, char *argv[]) {
.control = !args.no_control, .control = !args.no_control,
.display = !args.no_display, .display = !args.no_display,
.turn_screen_off = args.turn_screen_off, .turn_screen_off = args.turn_screen_off,
.render_expired_frames = args.render_expired_frames,
}; };
int res = scrcpy(&options) ? 0 : 1; int res = scrcpy(&options) ? 0 : 1;

View file

@ -320,7 +320,7 @@ scrcpy(const struct scrcpy_options *options) {
struct decoder *dec = NULL; struct decoder *dec = NULL;
if (options->display) { if (options->display) {
if (!video_buffer_init(&video_buffer)) { if (!video_buffer_init(&video_buffer, options->render_expired_frames)) {
goto end; goto end;
} }
video_buffer_initialized = true; video_buffer_initialized = true;

View file

@ -19,6 +19,7 @@ struct scrcpy_options {
bool control; bool control;
bool display; bool display;
bool turn_screen_off; bool turn_screen_off;
bool render_expired_frames;
}; };
bool bool

View file

@ -10,7 +10,7 @@
#include "log.h" #include "log.h"
bool 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())) { if (!(vb->decoding_frame = av_frame_alloc())) {
goto error_0; goto error_0;
} }
@ -23,13 +23,16 @@ video_buffer_init(struct video_buffer *vb) {
goto error_2; goto error_2;
} }
#ifndef SKIP_FRAMES vb->render_expired_frames = render_expired_frames;
if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) { if (render_expired_frames) {
SDL_DestroyMutex(vb->mutex); if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
goto error_2; 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 // there is initially no rendering frame, so consider it has already been
// consumed // consumed
@ -48,9 +51,9 @@ error_0:
void void
video_buffer_destroy(struct video_buffer *vb) { video_buffer_destroy(struct video_buffer *vb) {
#ifndef SKIP_FRAMES if (vb->render_expired_frames) {
SDL_DestroyCond(vb->rendering_frame_consumed_cond); SDL_DestroyCond(vb->rendering_frame_consumed_cond);
#endif }
SDL_DestroyMutex(vb->mutex); SDL_DestroyMutex(vb->mutex);
av_frame_free(&vb->rendering_frame); av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->decoding_frame); av_frame_free(&vb->decoding_frame);
@ -67,17 +70,16 @@ void
video_buffer_offer_decoded_frame(struct video_buffer *vb, video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped) { bool *previous_frame_skipped) {
mutex_lock(vb->mutex); mutex_lock(vb->mutex);
#ifndef SKIP_FRAMES if (vb->render_expired_frames) {
// if SKIP_FRAMES is disabled, then the decoder must wait for the current // wait for the current (expired) frame to be consumed
// frame to be consumed while (!vb->rendering_frame_consumed && !vb->interrupted) {
while (!vb->rendering_frame_consumed && !vb->interrupted) { cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
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); video_buffer_swap_frames(vb);
@ -94,23 +96,20 @@ video_buffer_consume_rendered_frame(struct video_buffer *vb) {
if (vb->fps_counter.started) { if (vb->fps_counter.started) {
fps_counter_add_rendered_frame(&vb->fps_counter); fps_counter_add_rendered_frame(&vb->fps_counter);
} }
#ifndef SKIP_FRAMES if (vb->render_expired_frames) {
// if SKIP_FRAMES is disabled, then notify the decoder the current frame is // unblock video_buffer_offer_decoded_frame()
// consumed, so that it may push a new one cond_signal(vb->rendering_frame_consumed_cond);
cond_signal(vb->rendering_frame_consumed_cond); }
#endif
return vb->rendering_frame; return vb->rendering_frame;
} }
void void
video_buffer_interrupt(struct video_buffer *vb) { video_buffer_interrupt(struct video_buffer *vb) {
#ifdef SKIP_FRAMES if (vb->render_expired_frames) {
(void) vb; // unused mutex_lock(vb->mutex);
#else vb->interrupted = true;
mutex_lock(vb->mutex); mutex_unlock(vb->mutex);
vb->interrupted = true; // wake up blocking wait
mutex_unlock(vb->mutex); cond_signal(vb->rendering_frame_consumed_cond);
// wake up blocking wait }
cond_signal(vb->rendering_frame_consumed_cond);
#endif
} }

View file

@ -4,7 +4,6 @@
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include "config.h"
#include "fps_counter.h" #include "fps_counter.h"
// forward declarations // forward declarations
@ -14,16 +13,15 @@ struct video_buffer {
AVFrame *decoding_frame; AVFrame *decoding_frame;
AVFrame *rendering_frame; AVFrame *rendering_frame;
SDL_mutex *mutex; SDL_mutex *mutex;
#ifndef SKIP_FRAMES bool render_expired_frames;
bool interrupted; bool interrupted;
SDL_cond *rendering_frame_consumed_cond; SDL_cond *rendering_frame_consumed_cond;
#endif
bool rendering_frame_consumed; bool rendering_frame_consumed;
struct fps_counter fps_counter; struct fps_counter fps_counter;
}; };
bool bool
video_buffer_init(struct video_buffer *vb); video_buffer_init(struct video_buffer *vb, bool render_expired_frames);
void void
video_buffer_destroy(struct video_buffer *vb); video_buffer_destroy(struct video_buffer *vb);