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:
parent
a143b8b07a
commit
ebccb9f6cc
9 changed files with 67 additions and 61 deletions
13
README.md
13
README.md
|
@ -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_.
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue