Skip to content

Commit

Permalink
Add runtime option to render expired frames
Browse files Browse the repository at this point in the history
Replace the compilation flag SKIP_FRAMES by a runtime flag to force
rendering of expired frames. By default, the expired frames are skipped.
  • Loading branch information
rom1v committed Jun 5, 2019
1 parent a143b8b commit ebccb9f
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 61 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_.
Expand Down
5 changes: 0 additions & 5 deletions app/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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'))

Expand Down
12 changes: 1 addition & 11 deletions app/src/fps_counter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
}
}

Expand All @@ -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
6 changes: 0 additions & 6 deletions app/src/fps_counter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
16 changes: 16 additions & 0 deletions app/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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[] = {
Expand All @@ -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'},
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions app/src/scrcpy.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct scrcpy_options {
bool control;
bool display;
bool turn_screen_off;
bool render_expired_frames;
};

bool
Expand Down
67 changes: 33 additions & 34 deletions app/src/video_buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -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);

Expand All @@ -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);
}
}
6 changes: 2 additions & 4 deletions app/src/video_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>

#include "config.h"
#include "fps_counter.h"

// forward declarations
Expand All @@ -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);
Expand Down

0 comments on commit ebccb9f

Please sign in to comment.