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`).
### 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_.

View file

@ -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'))

View file

@ -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

View file

@ -4,15 +4,11 @@
#include <stdbool.h>
#include <stdint.h>
#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

View file

@ -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;

View file

@ -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;

View file

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

View file

@ -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 @@ error_0:
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);
}
}

View file

@ -4,7 +4,6 @@
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#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);