Compare commits

..

22 Commits

Author SHA1 Message Date
Romain Vimont
94a53ea5d5 wip resizer 2021-02-26 23:11:11 +01:00
Romain Vimont
d76db3ad62 scale_filter 2021-02-26 23:11:11 +01:00
Romain Vimont
48606b2942 Move producer/consumer frames out of video buffer
The video buffer held 3 frames:
 - the producer frame (for decoding)
 - the pending frame (to exchange between the producer and consumer)
 - the consumer frame (for rendering)

It worked well, but it prevented video buffers to be chained, because
the consumer frame of the first video buffer must be the same as the
producer frame of the second video buffer.

To solve this problem, make the decoder and the screen handle their own
frames, and keep only the pending frame in the video_buffer.

This paves the way to support asynchronous swscale.
2021-02-25 22:24:32 +01:00
Romain Vimont
11e1fcd10f Release frame data as soon as possible
During a frame swap, one of the two frames involved can be released.
2021-02-25 22:19:05 +01:00
Romain Vimont
fc29468d3f Factorize frame swap 2021-02-25 22:19:05 +01:00
Romain Vimont
d819cf7973 Simplify screen initialization
Use a single function to initialize the screen instance.
2021-02-25 22:19:05 +01:00
Romain Vimont
64fc26b290 Group screen parameters into a struct
The function screen_init_rendering had too many parameters.
2021-02-25 22:19:05 +01:00
Romain Vimont
8e6efa2360 Remove screen static initializer
Most of the fields are initialized dynamically.
2021-02-25 22:19:05 +01:00
Romain Vimont
9ba323732b Use a callback to notify frame skip
A skipped frame is detected when the producer offers a frame while the
current pending frame has not been consumed.

However, the producer (in practice the decoder) is not interested in the
fact that a frame has been skipped, only the consumer (the renderer) is.

Therefore, notify frame skip via a consumer callback. This allows to
manage the skipped and rendered frames count at the same place, and
remove fps_counter from decoder.
2021-02-25 22:19:05 +01:00
Romain Vimont
e6e61bb241 Use a callback to notify a new frame
Make the decoder independant of the SDL even mechanism, by making the
consumer register a callback on the video_buffer.
2021-02-25 22:19:05 +01:00
Romain Vimont
660198ce3b Initialize screen before starting the stream
As soon as the stream is started, the video buffer could notify a new
frame available.

In order to pass this event to the screen without race condition, the
screen must be initialized before the screen is started.
2021-02-25 22:19:05 +01:00
Romain Vimont
24320d407f Make video buffer more generic
Video buffer is a tool between a frame producer and a frame consumer.

For now, it is used between a decoder and a renderer, but in the future
another instance might be used to swscale decoded frames.
2021-02-25 22:19:05 +01:00
Romain Vimont
8a20357133 Move fps counter out of video buffer
In order to make video buffer more generic, move out its specific
responsibility to count the fps between the decoder and the renderer.
2021-02-25 22:19:05 +01:00
Romain Vimont
b16b65a715 Simplify default values
It makes sense to extract default values for bitrate and port range
(which are arbitrary and might be changed in the future).

However, the default values for "max size" and "lock video orientation"
are naturally unlimited/unlocked, and will never be changed. Extracting
these options just added complexity for no benefit, so hardcode them.
2021-02-25 22:19:05 +01:00
Romain Vimont
a3aa5ac95e Insert numerical values statically in usage string 2021-02-25 22:19:05 +01:00
Romain Vimont
0207e3df33 Remove unused no_window field 2021-02-25 22:19:05 +01:00
Romain Vimont
9cd1a7380d Enable NDEBUG via Meson built-in option 2021-02-25 22:19:05 +01:00
Romain Vimont
24b637b972 Handle im-related events from input_manager.c 2021-02-25 22:19:05 +01:00
Romain Vimont
76a3d9805b Inline window events handling
Now that all screen-related events are handled from screen.c, there is
no need for a separate method for window events.
2021-02-25 22:19:05 +01:00
Romain Vimont
50b4a730e3 Handle screen-related events from screen.c 2021-02-25 22:19:05 +01:00
Romain Vimont
ea2369f568 Reference video buffer from screen
This paves the way to handle EVENT_NEW_FRAME from screen.c, by allowing
to call screen_update_frame() without an explicit video_buffer instance.
2021-02-25 22:19:05 +01:00
Romain Vimont
0538e9645b Improve error handling in screen initialization
After the struct screen is initialized, the window, the renderer and the
texture are necessarily valid, so there is no need to check in
screen_destroy().
2021-02-25 22:18:51 +01:00
14 changed files with 178 additions and 370 deletions

View File

@@ -47,7 +47,6 @@ if not get_option('crossbuild_windows')
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavutil'),
dependency('libswscale'),
dependency('sdl2'),
]
@@ -116,16 +115,6 @@ conf.set('PORTABLE', get_option('portable'))
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
# the default max video size for both dimensions, in pixels
# overridden by option --max-size
conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited
# the default video orientation
# natural device orientation is 0 and each increment adds 90 degrees
# counterclockwise
# overridden by option --lock-video-orientation
conf.set('DEFAULT_LOCK_VIDEO_ORIENTATION', '-1') # -1: unlocked
# the default video bitrate, in bits/second
# overridden by option --bit-rate
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps

View File

@@ -10,6 +10,9 @@
#include "util/log.h"
#include "util/str_util.h"
#define STR_IMPL_(x) #x
#define STR(x) STR_IMPL_(x)
void
scrcpy_print_usage(const char *arg0) {
fprintf(stderr,
@@ -23,7 +26,7 @@ scrcpy_print_usage(const char *arg0) {
" -b, --bit-rate value\n"
" Encode the video at the given bit-rate, expressed in bits/s.\n"
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
" Default is %d.\n"
" Default is " STR(DEFAULT_BIT_RATE) ".\n"
"\n"
" --codec-options key[:type]=value[,...]\n"
" Set a list of comma-separated key:type=value options for the\n"
@@ -81,7 +84,7 @@ scrcpy_print_usage(const char *arg0) {
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n"
" Natural device orientation is 0, and each increment adds a\n"
" 90 degrees rotation counterclockwise.\n"
" Default is %d%s.\n"
" Default is -1 (unlocked).\n"
"\n"
" --max-fps value\n"
" Limit the frame rate of screen capture (officially supported\n"
@@ -91,7 +94,7 @@ scrcpy_print_usage(const char *arg0) {
" Limit both the width and height of the video to value. The\n"
" other dimension is computed so that the device aspect-ratio\n"
" is preserved.\n"
" Default is %d%s.\n"
" Default is 0 (unlimited).\n"
"\n"
" -n, --no-control\n"
" Disable device control (mirror the device in read-only).\n"
@@ -105,7 +108,8 @@ scrcpy_print_usage(const char *arg0) {
"\n"
" -p, --port port[:port]\n"
" Set the TCP port (range) used by the client to listen.\n"
" Default is %d:%d.\n"
" Default is " STR(DEFAULT_LOCAL_PORT_RANGE_FIRST) ":"
STR(DEFAULT_LOCAL_PORT_RANGE_LAST) ".\n"
"\n"
" --prefer-text\n"
" Inject alpha characters and space as text events instead of\n"
@@ -297,12 +301,7 @@ scrcpy_print_usage(const char *arg0) {
"\n"
" Drag & drop APK file\n"
" Install APK from computer\n"
"\n",
arg0,
DEFAULT_BIT_RATE,
DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)",
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST);
"\n", arg0);
}
static bool
@@ -643,7 +642,6 @@ guess_record_format(const char *filename) {
static bool
parse_scale_filter(const char *optarg, enum sc_scale_filter *filter) {
// TODO store in a map and loop over the entries instead
if (!strcmp(optarg, "none")) {
*filter = SC_SCALE_FILTER_NONE;
return true;
@@ -652,49 +650,8 @@ parse_scale_filter(const char *optarg, enum sc_scale_filter *filter) {
*filter = SC_SCALE_FILTER_TRILINEAR;
return true;
}
if (!strcmp(optarg, "bilinear")) {
*filter = SC_SCALE_FILTER_BILINEAR;
return true;
}
if (!strcmp(optarg, "bicubic")) {
*filter = SC_SCALE_FILTER_BICUBIC;
return true;
}
if (!strcmp(optarg, "x")) {
*filter = SC_SCALE_FILTER_X;
return true;
}
if (!strcmp(optarg, "point")) {
*filter = SC_SCALE_FILTER_POINT;
return true;
}
if (!strcmp(optarg, "area")) {
*filter = SC_SCALE_FILTER_AREA;
return true;
}
if (!strcmp(optarg, "bicublin")) {
*filter = SC_SCALE_FILTER_BICUBLIN;
return true;
}
if (!strcmp(optarg, "gauss")) {
*filter = SC_SCALE_FILTER_GAUSS;
return true;
}
if (!strcmp(optarg, "sinc")) {
*filter = SC_SCALE_FILTER_SINC;
return true;
}
if (!strcmp(optarg, "lanczos")) {
*filter = SC_SCALE_FILTER_LANCZOS;
return true;
}
if (!strcmp(optarg, "spline")) {
*filter = SC_SCALE_FILTER_SPLINE;
return true;
}
LOGE("Unsupported scale filter: %s "
"(expected one of [TODO])", optarg);
"(expected \"none\" or \"trilinear\")", optarg);
return false;
}

View File

@@ -11,12 +11,6 @@
#include "util/buffer_util.h"
#include "util/log.h"
// set the decoded frame as ready for rendering, and notify
static void
push_frame(struct decoder *decoder) {
video_buffer_producer_offer_frame(decoder->video_buffer, &decoder->frame);
}
void
decoder_init(struct decoder *decoder, struct video_buffer *vb) {
decoder->video_buffer = vb;
@@ -38,6 +32,7 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) {
decoder->frame = av_frame_alloc();
if (!decoder->frame) {
LOGE("Could not create decoder frame");
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
return false;
@@ -66,7 +61,8 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
if (!ret) {
// a frame was received
push_frame(decoder);
video_buffer_producer_offer_frame(decoder->video_buffer,
&decoder->frame);
} else if (ret != AVERROR(EAGAIN)) {
LOGE("Could not receive video frame: %d", ret);
return false;

View File

@@ -10,6 +10,7 @@ struct video_buffer;
struct decoder {
struct video_buffer *video_buffer;
AVCodecContext *codec_ctx;
AVFrame *frame;
};

View File

@@ -168,7 +168,7 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
}
void
fps_counter_add_skipped_frames(struct fps_counter *counter, unsigned n) {
fps_counter_add_skipped_frame(struct fps_counter *counter) {
if (!is_started(counter)) {
return;
}
@@ -176,6 +176,6 @@ fps_counter_add_skipped_frames(struct fps_counter *counter, unsigned n) {
sc_mutex_lock(&counter->mutex);
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
counter->nr_skipped += n;
++counter->nr_skipped;
sc_mutex_unlock(&counter->mutex);
}

View File

@@ -54,6 +54,6 @@ void
fps_counter_add_rendered_frame(struct fps_counter *counter);
void
fps_counter_add_skipped_frames(struct fps_counter *counter, unsigned n);
fps_counter_add_skipped_frame(struct fps_counter *counter);
#endif

View File

@@ -1,14 +1,12 @@
#include "resizer.h"
#include <assert.h>
#include <libswscale/swscale.h>
#include "util/log.h"
bool
sc_resizer_init(struct sc_resizer *resizer, struct video_buffer *vb_in,
struct video_buffer *vb_out, enum sc_scale_filter scale_filter,
struct size size) {
struct video_buffer *vb_out, struct size size) {
bool ok = sc_mutex_init(&resizer->mutex);
if (!ok) {
return false;
@@ -29,7 +27,6 @@ sc_resizer_init(struct sc_resizer *resizer, struct video_buffer *vb_in,
resizer->vb_in = vb_in;
resizer->vb_out = vb_out;
resizer->scale_filter = scale_filter;
resizer->size = size;
resizer->input_frame = NULL;
@@ -47,57 +44,11 @@ sc_resizer_destroy(struct sc_resizer *resizer) {
sc_mutex_destroy(&resizer->mutex);
}
static int
to_sws_filter(enum sc_scale_filter scale_filter) {
switch (scale_filter) {
case SC_SCALE_FILTER_BILINEAR: return SWS_BILINEAR;
case SC_SCALE_FILTER_BICUBIC: return SWS_BICUBIC;
case SC_SCALE_FILTER_X: return SWS_X;
case SC_SCALE_FILTER_POINT: return SWS_POINT;
case SC_SCALE_FILTER_AREA: return SWS_AREA;
case SC_SCALE_FILTER_BICUBLIN: return SWS_BICUBLIN;
case SC_SCALE_FILTER_GAUSS: return SWS_GAUSS;
case SC_SCALE_FILTER_SINC: return SWS_SINC;
case SC_SCALE_FILTER_LANCZOS: return SWS_LANCZOS;
case SC_SCALE_FILTER_SPLINE: return SWS_SPLINE;
default: assert(!"unsupported filter");
}
}
static bool
sc_resizer_swscale(struct sc_resizer *resizer) {
assert(!resizer->resized_frame->buf[0]); // The frame must be "empty"
assert(resizer->size.width);
assert(resizer->size.height);
const AVFrame *in = resizer->input_frame;
struct size size = resizer->size;
AVFrame *out = resizer->resized_frame;
out->format = AV_PIX_FMT_RGB24;
out->width = size.width;
out->height = size.height;
int ret = av_frame_get_buffer(out, 32);
if (ret < 0) {
return false;
}
int flags = to_sws_filter(resizer->scale_filter);
struct SwsContext *ctx =
sws_getContext(in->width, in->height, AV_PIX_FMT_YUV420P,
size.width, size.height, AV_PIX_FMT_RGB24, flags, NULL,
NULL, NULL);
if (!ctx) {
av_frame_unref(out);
return false;
}
sws_scale(ctx, (const uint8_t *const *) in->data, in->linesize, 0,
in->height, out->data, out->linesize);
sws_freeContext(ctx);
return true;
return false;
}
static int
@@ -111,11 +62,9 @@ run_resizer(void *data) {
}
if (resizer->has_new_frame) {
unsigned skipped;
resizer->input_frame =
video_buffer_consumer_take_frame(resizer->vb_in, &skipped);
video_buffer_consumer_take_frame(resizer->vb_in);
(void) skipped; // FIXME forward skipped frames count
resizer->has_new_frame = false;
}

View File

@@ -7,14 +7,12 @@
#include <libavformat/avformat.h>
#include "coords.h"
#include "scrcpy.h"
#include "util/thread.h"
#include "video_buffer.h"
struct sc_resizer {
struct video_buffer *vb_in;
struct video_buffer *vb_out;
enum sc_scale_filter scale_filter;
struct size size;
// valid until the next call to video_buffer_consumer_take_frame(vb_in)
@@ -32,8 +30,7 @@ struct sc_resizer {
bool
sc_resizer_init(struct sc_resizer *resizer, struct video_buffer *vb_in,
struct video_buffer *vb_out, enum sc_scale_filter scale_filter,
struct size size);
struct video_buffer *vb_out, struct size initial_size);
void
sc_resizer_destroy(struct sc_resizer *resizer);

View File

@@ -267,14 +267,6 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
free(local_fmt);
}
static void
video_buffer_on_frame_available(struct video_buffer *vb, void *userdata) {
(void) vb;
(void) userdata;
screen_on_frame_available(&screen);
}
bool
scrcpy(const struct scrcpy_options *options) {
if (!server_init(&server)) {
@@ -341,12 +333,7 @@ scrcpy(const struct scrcpy_options *options) {
}
fps_counter_initialized = true;
static const struct video_buffer_callbacks video_buffer_cbs = {
.on_frame_available = video_buffer_on_frame_available,
};
if (!video_buffer_init(&video_buffer, options->render_expired_frames,
&video_buffer_cbs, NULL)) {
if (!video_buffer_init(&video_buffer, options->render_expired_frames)) {
goto end;
}
video_buffer_initialized = true;
@@ -395,14 +382,21 @@ scrcpy(const struct scrcpy_options *options) {
const char *window_title =
options->window_title ? options->window_title : device_name;
screen_init(&screen, &video_buffer, &fps_counter);
struct screen_params screen_params = {
.window_title = window_title,
.frame_size = frame_size,
.always_on_top = options->always_on_top,
.window_x = options->window_x,
.window_y = options->window_y,
.window_width = options->window_width,
.window_height = options->window_height,
.window_borderless = options->window_borderless,
.rotation = options->rotation,
.scale_filter = options->scale_filter,
};
if (!screen_init_rendering(&screen, window_title, frame_size,
options->always_on_top, options->window_x,
options->window_y, options->window_width,
options->window_height,
options->window_borderless,
options->rotation, options->scale_filter)) {
if (!screen_init(&screen, &video_buffer, &fps_counter,
&screen_params)) {
goto end;
}

View File

@@ -44,17 +44,6 @@ struct sc_port_range {
enum sc_scale_filter {
SC_SCALE_FILTER_NONE,
SC_SCALE_FILTER_TRILINEAR, // mipmaps
SC_SCALE_FILTER_BILINEAR,
SC_SCALE_FILTER_BICUBIC,
SC_SCALE_FILTER_X,
SC_SCALE_FILTER_POINT,
SC_SCALE_FILTER_AREA,
SC_SCALE_FILTER_BICUBLIN,
SC_SCALE_FILTER_GAUSS,
SC_SCALE_FILTER_SINC,
SC_SCALE_FILTER_LANCZOS,
SC_SCALE_FILTER_SPLINE,
SC_SCALE_FILTER__COUNT,
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
@@ -120,10 +109,10 @@ struct scrcpy_options {
.count = 2, \
}, \
.scale_filter = SC_SCALE_FILTER_TRILINEAR, \
.max_size = DEFAULT_MAX_SIZE, \
.max_size = 0, \
.bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.lock_video_orientation = -1, \
.rotation = 0, \
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
.window_y = SC_WINDOW_POSITION_UNDEFINED, \

View File

@@ -190,33 +190,39 @@ screen_update_content_rect(struct screen *screen) {
}
}
void
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter) {
screen->vb = vb;
screen->fps_counter = fps_counter;
static void
on_frame_available(struct video_buffer *vb, void *userdata) {
(void) vb;
(void) userdata;
screen->resize_pending = false;
screen->has_frame = false;
screen->fullscreen = false;
screen->maximized = false;
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
}
static void
on_frame_skipped(struct video_buffer *vb, void *userdata) {
(void) vb;
struct screen *screen = userdata;
fps_counter_add_skipped_frame(screen->fps_counter);
}
static inline SDL_Texture *
create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer;
struct size size = screen->frame_size;
SDL_PixelFormatEnum fmt = screen->use_swscale ? SDL_PIXELFORMAT_RGB24
: SDL_PIXELFORMAT_YV12;
SDL_Texture *texture = SDL_CreateTexture(renderer, fmt,
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
if (!texture) {
return NULL;
}
if (screen->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
assert(!screen->use_swscale);
if (screen->mipmaps) {
struct sc_opengl *gl = &screen->gl;
SDL_GL_BindTexture(texture, NULL, NULL);
@@ -232,46 +238,41 @@ create_texture(struct screen *screen) {
return texture;
}
static inline bool
is_swscale(enum sc_scale_filter scale_filter) {
return scale_filter != SC_SCALE_FILTER_NONE
&& scale_filter != SC_SCALE_FILTER_TRILINEAR;
}
static void
on_resizer_frame_available(struct video_buffer *vb, void *userdata) {
(void) vb;
(void) userdata;
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
}
bool
screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless,
uint8_t rotation, enum sc_scale_filter scale_filter) {
screen->frame_size = frame_size;
screen->rotation = rotation;
if (rotation) {
LOGI("Initial display rotation set to %u", rotation);
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter,
const struct screen_params *params) {
screen->vb = vb;
screen->fps_counter = fps_counter;
screen->resize_pending = false;
screen->has_frame = false;
screen->fullscreen = false;
screen->maximized = false;
static const struct video_buffer_callbacks cbs = {
.on_frame_available = on_frame_available,
.on_frame_skipped = on_frame_skipped,
};
video_buffer_set_consumer_callbacks(vb, &cbs, screen);
screen->frame_size = params->frame_size;
screen->rotation = params->rotation;
if (screen->rotation) {
LOGI("Initial display rotation set to %u", screen->rotation);
}
struct size content_size = get_rotated_size(frame_size, screen->rotation);
struct size content_size =
get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size;
struct size window_size =
get_initial_optimal_size(content_size, window_width, window_height);
struct size window_size = get_initial_optimal_size(content_size,
params->window_width,
params->window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
#ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
#endif
if (always_on_top) {
if (params->always_on_top) {
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
@@ -279,15 +280,15 @@ screen_init_rendering(struct screen *screen, const char *window_title,
"(compile with SDL >= 2.0.5 to enable it)");
#endif
}
if (window_borderless) {
if (params->window_borderless) {
window_flags |= SDL_WINDOW_BORDERLESS;
}
int x = window_x != SC_WINDOW_POSITION_UNDEFINED
? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = window_y != SC_WINDOW_POSITION_UNDEFINED
? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(window_title, x, y,
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(params->window_title, x, y,
window_size.width, window_size.height,
window_flags);
if (!screen->window) {
@@ -308,9 +309,11 @@ screen_init_rendering(struct screen *screen, const char *window_title,
const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
screen->mipmaps = false;
// starts with "opengl"
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
bool mipmaps = scale_filter == SC_SCALE_FILTER_TRILINEAR;
bool mipmaps = params->scale_filter == SC_SCALE_FILTER_TRILINEAR;
if (use_opengl) {
struct sc_opengl *gl = &screen->gl;
sc_opengl_init(gl);
@@ -323,10 +326,10 @@ screen_init_rendering(struct screen *screen, const char *window_title,
2, 0 /* OpenGL ES 2.0+ */);
if (supports_mipmaps) {
LOGI("Trilinear filtering enabled");
screen->mipmaps = true;
} else {
LOGW("Trilinear filtering disabled "
"(OpenGL 3.0+ or ES 2.0+ required)");
scale_filter = SC_SCALE_FILTER_NONE;
}
} else {
LOGI("Trilinear filtering disabled");
@@ -335,42 +338,6 @@ screen_init_rendering(struct screen *screen, const char *window_title,
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
screen->use_swscale = is_swscale(scale_filter);
if (screen->use_swscale) {
static const struct video_buffer_callbacks video_buffer_cbs = {
.on_frame_available = on_resizer_frame_available,
};
bool ok = video_buffer_init(&screen->resizer_vb, false,
&video_buffer_cbs, NULL);
if (!ok) {
LOGE("Could not create resizer video buffer");
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
return false;
}
ok = sc_resizer_init(&screen->resizer, screen->vb, &screen->resizer_vb,
scale_filter, window_size);
if (!ok) {
LOGE("Could not create resizer");
video_buffer_destroy(&screen->resizer_vb);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
return false;
}
ok = sc_resizer_start(&screen->resizer);
if (!ok) {
LOGE("Could not start resizer");
sc_resizer_destroy(&screen->resizer);
video_buffer_destroy(&screen->resizer_vb);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
}
}
screen->scale_filter = scale_filter;
SDL_Surface *icon = read_xpm(icon_xpm);
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
@@ -379,8 +346,8 @@ screen_init_rendering(struct screen *screen, const char *window_title,
LOGW("Could not load icon");
}
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
frame_size.height);
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, params->frame_size.width,
params->frame_size.height);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
@@ -389,6 +356,15 @@ screen_init_rendering(struct screen *screen, const char *window_title,
return false;
}
screen->frame = av_frame_alloc();
if (!screen->frame) {
LOGC("Could not create screen frame");
SDL_DestroyTexture(screen->texture);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
return false;
}
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
// HiDPI issues with some SDL renderers when several displays having
// different HiDPI scaling are connected
@@ -406,14 +382,8 @@ screen_show_window(struct screen *screen) {
void
screen_destroy(struct screen *screen) {
if (screen->use_swscale) {
sc_resizer_stop(&screen->resizer);
sc_resizer_join(&screen->resizer);
sc_resizer_destroy(&screen->resizer);
}
if (screen->texture) {
SDL_DestroyTexture(screen->texture);
}
av_frame_free(&screen->frame);
SDL_DestroyTexture(screen->texture);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
}
@@ -506,31 +476,23 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
// write the frame into the texture
static void
update_texture(struct screen *screen, const AVFrame *frame) {
if (screen->use_swscale) {
SDL_UpdateTexture(screen->texture, NULL, frame->data[0],
frame->linesize[0]);
} else {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (screen->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture);
}
if (screen->mipmaps) {
SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture);
}
}
static bool
screen_update_frame(struct screen *screen) {
unsigned skipped;
struct video_buffer *vb = screen->use_swscale ? &screen->resizer_vb
: screen->vb;
const AVFrame *frame = video_buffer_consumer_take_frame(vb, &skipped);
video_buffer_consumer_take_frame(screen->vb, &screen->frame);
AVFrame *frame = screen->frame;
fps_counter_add_skipped_frames(screen->fps_counter, skipped);
fps_counter_add_rendered_frame(screen->fps_counter);
struct size new_frame_size = {frame->width, frame->height};
@@ -727,17 +689,3 @@ screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
*x = (int64_t) *x * dw / ww;
*y = (int64_t) *y * dh / wh;
}
void
screen_on_frame_available(struct screen *screen) {
if (screen->use_swscale) {
sc_resizer_process_new_frame(&screen->resizer);
} else {
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
}
}

View File

@@ -9,9 +9,7 @@
#include "coords.h"
#include "opengl.h"
#include "resizer.h"
#include "scrcpy.h"
#include "video_buffer.h"
struct video_buffer;
@@ -40,27 +38,30 @@ struct screen {
bool maximized;
bool mipmaps;
enum sc_scale_filter scale_filter;
bool use_swscale;
// For swscale resizing
struct video_buffer resizer_vb;
struct sc_resizer resizer;
AVFrame *frame;
};
// initialize default values
void
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter);
struct screen_params {
const char *window_title;
struct size frame_size;
bool always_on_top;
int16_t window_x;
int16_t window_y;
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
bool window_borderless;
uint8_t rotation;
enum sc_scale_filter scale_filter;
};
// initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
bool
screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless,
uint8_t rotation, enum sc_scale_filter scale_filter);
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter,
const struct screen_params *params);
// show the window
void
@@ -116,9 +117,4 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y);
// Notify the screen that a new frame is available in the video_buffer.
// Called from a separate thread.
void
screen_on_frame_available(struct screen *screen);
#endif

View File

@@ -7,22 +7,15 @@
#include "util/log.h"
bool
video_buffer_init(struct video_buffer *vb, bool wait_consumer,
const struct video_buffer_callbacks *cbs,
void *cbs_userdata) {
video_buffer_init(struct video_buffer *vb, bool wait_consumer) {
vb->pending_frame = av_frame_alloc();
if (!vb->pending_frame) {
goto error_0;
}
vb->consumer_frame = av_frame_alloc();
if (!vb->consumer_frame) {
goto error_1;
}
bool ok = sc_mutex_init(&vb->mutex);
if (!ok) {
goto error_2;
goto error_1;
}
vb->wait_consumer = wait_consumer;
@@ -30,7 +23,7 @@ video_buffer_init(struct video_buffer *vb, bool wait_consumer,
ok = sc_cond_init(&vb->pending_frame_consumed_cond);
if (!ok) {
sc_mutex_destroy(&vb->mutex);
goto error_2;
goto error_1;
}
// interrupted is not used if wait_consumer is disabled since offering
// a frame will never block
@@ -40,17 +33,12 @@ video_buffer_init(struct video_buffer *vb, bool wait_consumer,
// there is initially no frame, so consider it has already been consumed
vb->pending_frame_consumed = true;
vb->skipped = 0;
assert(cbs);
assert(cbs->on_frame_available);
vb->cbs = cbs;
vb->cbs_userdata = cbs_userdata;
// The callbacks must be set by the consumer via
// video_buffer_set_consumer_callbacks()
vb->cbs = NULL;
return true;
error_2:
av_frame_free(&vb->consumer_frame);
error_1:
av_frame_free(&vb->pending_frame);
error_0:
@@ -63,7 +51,6 @@ video_buffer_destroy(struct video_buffer *vb) {
sc_cond_destroy(&vb->pending_frame_consumed_cond);
}
sc_mutex_destroy(&vb->mutex);
av_frame_free(&vb->consumer_frame);
av_frame_free(&vb->pending_frame);
}
@@ -74,8 +61,21 @@ swap_frames(AVFrame **lhs, AVFrame **rhs) {
*rhs = tmp;
}
void
video_buffer_set_consumer_callbacks(struct video_buffer *vb,
const struct video_buffer_callbacks *cbs,
void *cbs_userdata) {
assert(!vb->cbs); // must be set only once
assert(cbs);
assert(cbs->on_frame_available);
vb->cbs = cbs;
vb->cbs_userdata = cbs_userdata;
}
void
video_buffer_producer_offer_frame(struct video_buffer *vb, AVFrame **pframe) {
assert(vb->cbs);
sc_mutex_lock(&vb->mutex);
if (vb->wait_consumer) {
// wait for the current (expired) frame to be consumed
@@ -88,44 +88,32 @@ video_buffer_producer_offer_frame(struct video_buffer *vb, AVFrame **pframe) {
swap_frames(pframe, &vb->pending_frame);
bool skipped = !vb->pending_frame_consumed;
if (skipped) {
++vb->skipped;
}
vb->pending_frame_consumed = false;
sc_mutex_unlock(&vb->mutex);
if (!skipped) {
// If skipped, then the previous call will consume this frame, the
// callback must not be called
if (skipped) {
if (vb->cbs->on_frame_skipped)
vb->cbs->on_frame_skipped(vb, vb->cbs_userdata);
} else {
vb->cbs->on_frame_available(vb, vb->cbs_userdata);
}
}
const AVFrame *
video_buffer_consumer_take_frame(struct video_buffer *vb, unsigned *skipped) {
void
video_buffer_consumer_take_frame(struct video_buffer *vb, AVFrame **pframe) {
sc_mutex_lock(&vb->mutex);
assert(!vb->pending_frame_consumed);
vb->pending_frame_consumed = true;
swap_frames(&vb->consumer_frame, &vb->pending_frame);
swap_frames(pframe, &vb->pending_frame);
av_frame_unref(vb->pending_frame);
if (vb->wait_consumer) {
// unblock video_buffer_offer_decoded_frame()
sc_cond_signal(&vb->pending_frame_consumed_cond);
}
if (skipped) {
*skipped = vb->skipped;
}
vb->skipped = 0; // reset
sc_mutex_unlock(&vb->mutex);
// consumer_frame is only written from this thread, no need to lock
return vb->consumer_frame;
}
void

View File

@@ -30,7 +30,6 @@ typedef struct AVFrame AVFrame;
struct video_buffer {
AVFrame *pending_frame;
AVFrame *consumer_frame;
sc_mutex mutex;
bool wait_consumer; // never overwrite a pending frame if it is not consumed
@@ -39,8 +38,6 @@ struct video_buffer {
sc_cond pending_frame_consumed_cond;
bool pending_frame_consumed;
unsigned skipped;
const struct video_buffer_callbacks *cbs;
void *cbs_userdata;
};
@@ -48,27 +45,34 @@ struct video_buffer {
struct video_buffer_callbacks {
// Called when a new frame can be consumed by
// video_buffer_consumer_take_frame(vb)
// This callback is mandatory (it must not be NULL).
void (*on_frame_available)(struct video_buffer *vb, void *userdata);
// Called when a pending frame has been overwritten by the producer
// This callback is optional (it may be NULL).
void (*on_frame_skipped)(struct video_buffer *vb, void *userdata);
};
bool
video_buffer_init(struct video_buffer *vb, bool wait_consumer,
const struct video_buffer_callbacks *cbs, void *cbs_userdata);
video_buffer_init(struct video_buffer *vb, bool wait_consumer);
void
video_buffer_destroy(struct video_buffer *vb);
void
video_buffer_set_consumer_callbacks(struct video_buffer *vb,
const struct video_buffer_callbacks *cbs,
void *cbs_userdata);
// set the producer frame as ready for consuming
// the produced frame is exchanged with an unused allocated frame
void
video_buffer_producer_offer_frame(struct video_buffer *vb, AVFrame **pframe);
// mark the consumer frame as consumed and return it
// the frame is valid until the next call to this function
// the output parameter "skipped" indicates how many produced frames have been
// skipped
const AVFrame *
video_buffer_consumer_take_frame(struct video_buffer *vb, unsigned *skipped);
// mark the consumer frame as consumed and exchange it with an unused allocated
// frame
void
video_buffer_consumer_take_frame(struct video_buffer *vb, AVFrame **pframe);
// wake up and avoid any blocking call
void