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`).
|
||||
|
||||
|
||||
### 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_.
|
||||
|
|
|
@ -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'))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -19,6 +19,7 @@ struct scrcpy_options {
|
|||
bool control;
|
||||
bool display;
|
||||
bool turn_screen_off;
|
||||
bool render_expired_frames;
|
||||
};
|
||||
|
||||
bool
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue