Compare commits

..

23 Commits

Author SHA1 Message Date
Romain Vimont
40febf4a91 Use device id 0 for touch/mouse events
Virtual device is only for keyboard sources, not mouse or touchscreen
sources. Here is the value of InputDevice.getDevice(-1).toString():

    Input Device -1: Virtual
      Descriptor: ...
      Generation: 2
      Location: built-in
      Keyboard Type: alphabetic
      Has Vibrator: false
      Has mic: false
      Sources: 0x301 ( keyboard dpad )

InputDevice.getDeviceId() documentation says:

> An id of zero indicates that the event didn't come from a physical
> device and maps to the default keymap.

<https://developer.android.com/reference/android/view/InputEvent#getDeviceId()>

However, injecting events with a device id of 0 causes event.getDevice()
to be null on the client-side.

Commit 26529d377f used -1 as a workaround
to avoid a NPE on a specific Android TV device. But this is a bug in the
device system, which wrongly assumes that input device may not be null.

A similar issue was present in Flutter, but it is now fixed:
 - <https://github.com/flutter/flutter/issues/30665>
 - <https://github.com/flutter/engine/pull/7986>

On the other hand, using an id of -1 for touchscreen events (which is
invalid) causes issues for some apps:
<https://github.com/Genymobile/scrcpy/issues/2125#issuecomment-790535792>

Therefore, use a device id of 0.

An alternative could be to find an existing device matching the source,
like "adb shell input" does. See getInputDeviceId():
<https://android.googlesource.com/platform/frameworks/base.git/+/master/cmds/input/src/com/android/commands/input/Input.java>

But it seems better to indicate that the event didn't come from a
physical device, and it would not solve #962 anyway, because an Android
TV has no touchscreen.

Refs #962 <https://github.com/Genymobile/scrcpy/issues/962>
Fixes #2125 <https://github.com/Genymobile/scrcpy/issues/2125>
2021-03-14 18:30:56 +01:00
Romain Vimont
429fdef04f Fix encoder parameter suggestion
The option is --encoder, not --encoder-name.
2021-03-11 10:55:12 +01:00
Biswapriyo Nath
d1789f082a meson: Do not use full path with mingw tools name
This helps to use mingw toolchains which are not in /usr/bin path.

PR #2185 <https://github.com/Genymobile/scrcpy/pull/2185>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-03-09 09:59:57 +01:00
Romain Vimont
eb7e1070cf Release frame data as soon as possible
During a frame swap, one of the two frames involved can be released.
2021-03-06 22:58:03 +01:00
Romain Vimont
386f017ba9 Factorize frame swap 2021-03-06 22:58:03 +01:00
Romain Vimont
cc48b24324 Simplify screen initialization
Use a single function to initialize the screen instance.
2021-03-06 22:58:03 +01:00
Romain Vimont
597c54f049 Group screen parameters into a struct
The function screen_init_rendering had too many parameters.
2021-03-06 22:58:03 +01:00
Romain Vimont
955da3b578 Remove screen static initializer
Most of the fields are initialized dynamically.
2021-03-06 22:58:03 +01:00
Romain Vimont
cb9c42bdcb 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-03-06 22:58:03 +01:00
Romain Vimont
fb9f9848bd 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-03-06 22:58:03 +01:00
Romain Vimont
c50b958ee4 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-03-06 22:58:03 +01:00
Romain Vimont
441d3fb119 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-03-06 22:58:03 +01:00
Romain Vimont
cb197ee3a2 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-03-06 22:58:03 +01:00
Romain Vimont
218636dc10 Inject touch events with smallest detectable size
A value of 1 means the "largest detectable size", a value of 0 means the
"smallest detectable size".

https://developer.android.com/reference/android/view/MotionEvent.PointerCoords#size
https://developer.android.com/reference/android/view/MotionEvent#AXIS_SIZE

Fixes #2125 <https://github.com/Genymobile/scrcpy/issues/2125>
2021-03-03 18:22:54 +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
17 changed files with 229 additions and 222 deletions

View File

@@ -114,16 +114,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"
@@ -110,7 +113,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

View File

@@ -11,22 +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) {
bool previous_frame_skipped;
video_buffer_offer_decoded_frame(decoder->video_buffer,
&previous_frame_skipped);
if (previous_frame_skipped) {
// the previous EVENT_NEW_FRAME will consume this frame
return;
}
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
SDL_PushEvent(&new_frame_event);
}
void
decoder_init(struct decoder *decoder, struct video_buffer *vb) {
decoder->video_buffer = vb;
@@ -66,10 +50,10 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
return false;
}
ret = avcodec_receive_frame(decoder->codec_ctx,
decoder->video_buffer->decoding_frame);
decoder->video_buffer->producer_frame);
if (!ret) {
// a frame was received
push_frame(decoder);
video_buffer_producer_offer_frame(decoder->video_buffer);
} 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;
};

View File

@@ -480,9 +480,7 @@ input_manager_process_key(struct input_manager *im,
return;
case SDLK_i:
if (!shift && !repeat && down) {
struct fps_counter *fps_counter =
im->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter);
switch_fps_counter_state(im->fps_counter);
}
return;
case SDLK_n:

View File

@@ -16,6 +16,7 @@
struct input_manager {
struct controller *controller;
struct video_buffer *video_buffer;
struct fps_counter *fps_counter;
struct screen *screen;
// SDL reports repeated events as a boolean, but Android expects the actual

View File

@@ -42,6 +42,7 @@ static struct file_handler file_handler;
static struct input_manager input_manager = {
.controller = &controller,
.video_buffer = &video_buffer,
.fps_counter = &fps_counter,
.screen = &screen,
.repeat = 0,
@@ -332,8 +333,7 @@ scrcpy(const struct scrcpy_options *options) {
}
fps_counter_initialized = true;
if (!video_buffer_init(&video_buffer, &fps_counter,
options->render_expired_frames)) {
if (!video_buffer_init(&video_buffer, options->render_expired_frames)) {
goto end;
}
video_buffer_initialized = true;
@@ -366,13 +366,6 @@ scrcpy(const struct scrcpy_options *options) {
stream_init(&stream, server.video_socket, dec, rec);
// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&stream)) {
goto end;
}
stream_started = true;
if (options->display) {
if (options->control) {
if (!controller_init(&controller, server.control_socket)) {
@@ -389,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);
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,
.mipmaps = options->mipmaps,
};
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->mipmaps)) {
if (!screen_init(&screen, &video_buffer, &fps_counter,
&screen_params)) {
goto end;
}
@@ -415,6 +415,13 @@ scrcpy(const struct scrcpy_options *options) {
}
}
// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&stream)) {
goto end;
}
stream_started = true;
input_manager_init(&input_manager, options);
ret = event_loop(options);

View File

@@ -103,10 +103,10 @@ struct scrcpy_options {
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
.count = 2, \
}, \
.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

@@ -191,10 +191,25 @@ screen_update_content_rect(struct screen *screen) {
}
}
void
screen_init(struct screen *screen, struct video_buffer *vb) {
*screen = (struct screen) SCREEN_INITIALIZER;
screen->vb = vb;
static void
on_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);
}
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 *
@@ -225,26 +240,40 @@ create_texture(struct screen *screen) {
}
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, bool mipmaps) {
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
@@ -252,15 +281,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) {
@@ -281,6 +310,8 @@ 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);
if (use_opengl) {
@@ -289,7 +320,7 @@ screen_init_rendering(struct screen *screen, const char *window_title,
LOGI("OpenGL version: %s", gl->version);
if (mipmaps) {
if (params->mipmaps) {
bool supports_mipmaps =
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
2, 0 /* OpenGL ES 2.0+ */);
@@ -303,7 +334,7 @@ screen_init_rendering(struct screen *screen, const char *window_title,
} else {
LOGI("Trilinear filtering disabled");
}
} else if (mipmaps) {
} else if (params->mipmaps) {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
@@ -315,14 +346,13 @@ 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());
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
screen_destroy(screen);
return false;
}
@@ -343,9 +373,7 @@ screen_show_window(struct screen *screen) {
void
screen_destroy(struct screen *screen) {
if (screen->texture) {
SDL_DestroyTexture(screen->texture);
}
SDL_DestroyTexture(screen->texture);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
}
@@ -452,7 +480,10 @@ update_texture(struct screen *screen, const AVFrame *frame) {
static bool
screen_update_frame(struct screen *screen) {
const AVFrame *frame = video_buffer_take_rendering_frame(screen->vb);
const AVFrame *frame = video_buffer_consumer_take_frame(screen->vb);
fps_counter_add_rendered_frame(screen->fps_counter);
struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
return false;

View File

@@ -14,6 +14,8 @@ struct video_buffer;
struct screen {
struct video_buffer *vb;
struct fps_counter *fps_counter;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
@@ -33,55 +35,30 @@ struct screen {
bool has_frame;
bool fullscreen;
bool maximized;
bool no_window;
bool mipmaps;
};
#define SCREEN_INITIALIZER { \
.vb = NULL, \
.window = NULL, \
.renderer = NULL, \
.texture = NULL, \
.gl = {0}, \
.frame_size = { \
.width = 0, \
.height = 0, \
}, \
.content_size = { \
.width = 0, \
.height = 0, \
}, \
.resize_pending = false, \
.windowed_content_size = { \
.width = 0, \
.height = 0, \
}, \
.rotation = 0, \
.rect = { \
.x = 0, \
.y = 0, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \
.fullscreen = false, \
.maximized = false, \
.no_window = false, \
.mipmaps = false, \
}
struct screen_params {
const char *window_title;
struct size frame_size;
bool always_on_top;
// initialize default values
void
screen_init(struct screen *screen, struct video_buffer *vb);
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;
bool mipmaps;
};
// 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, bool mipmaps);
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter,
const struct screen_params *params);
// show the window
void

View File

@@ -7,12 +7,9 @@
#include "util/log.h"
bool
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
bool render_expired_frames) {
vb->fps_counter = fps_counter;
vb->decoding_frame = av_frame_alloc();
if (!vb->decoding_frame) {
video_buffer_init(struct video_buffer *vb, bool wait_consumer) {
vb->producer_frame = av_frame_alloc();
if (!vb->producer_frame) {
goto error_0;
}
@@ -21,8 +18,8 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
goto error_1;
}
vb->rendering_frame = av_frame_alloc();
if (!vb->rendering_frame) {
vb->consumer_frame = av_frame_alloc();
if (!vb->consumer_frame) {
goto error_2;
}
@@ -31,105 +28,116 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
goto error_3;
}
vb->render_expired_frames = render_expired_frames;
if (render_expired_frames) {
vb->wait_consumer = wait_consumer;
if (wait_consumer) {
ok = sc_cond_init(&vb->pending_frame_consumed_cond);
if (!ok) {
sc_mutex_destroy(&vb->mutex);
goto error_2;
}
// interrupted is not used if expired frames are not rendered
// since offering a frame will never block
// interrupted is not used if wait_consumer is disabled since offering
// a frame will never block
vb->interrupted = false;
}
// there is initially no rendering frame, so consider it has already been
// consumed
// there is initially no frame, so consider it has already been consumed
vb->pending_frame_consumed = true;
// The callbacks must be set by the consumer via
// video_buffer_set_consumer_callbacks()
vb->cbs = NULL;
return true;
error_3:
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->consumer_frame);
error_2:
av_frame_free(&vb->pending_frame);
error_1:
av_frame_free(&vb->decoding_frame);
av_frame_free(&vb->producer_frame);
error_0:
return false;
}
void
video_buffer_destroy(struct video_buffer *vb) {
if (vb->render_expired_frames) {
if (vb->wait_consumer) {
sc_cond_destroy(&vb->pending_frame_consumed_cond);
}
sc_mutex_destroy(&vb->mutex);
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->consumer_frame);
av_frame_free(&vb->pending_frame);
av_frame_free(&vb->decoding_frame);
av_frame_free(&vb->producer_frame);
}
static void
video_buffer_swap_decoding_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->decoding_frame;
vb->decoding_frame = vb->pending_frame;
vb->pending_frame = tmp;
}
static void
video_buffer_swap_rendering_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->rendering_frame;
vb->rendering_frame = vb->pending_frame;
vb->pending_frame = tmp;
static inline void
swap_frames(AVFrame **lhs, AVFrame **rhs) {
AVFrame *tmp = *lhs;
*lhs = *rhs;
*rhs = tmp;
}
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped) {
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) {
assert(vb->cbs);
sc_mutex_lock(&vb->mutex);
if (vb->render_expired_frames) {
if (vb->wait_consumer) {
// wait for the current (expired) frame to be consumed
while (!vb->pending_frame_consumed && !vb->interrupted) {
sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex);
}
} else if (!vb->pending_frame_consumed) {
fps_counter_add_skipped_frame(vb->fps_counter);
}
video_buffer_swap_decoding_frame(vb);
av_frame_unref(vb->pending_frame);
swap_frames(&vb->producer_frame, &vb->pending_frame);
*previous_frame_skipped = !vb->pending_frame_consumed;
bool skipped = !vb->pending_frame_consumed;
vb->pending_frame_consumed = false;
sc_mutex_unlock(&vb->mutex);
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_take_rendering_frame(struct video_buffer *vb) {
video_buffer_consumer_take_frame(struct video_buffer *vb) {
sc_mutex_lock(&vb->mutex);
assert(!vb->pending_frame_consumed);
vb->pending_frame_consumed = true;
fps_counter_add_rendered_frame(vb->fps_counter);
swap_frames(&vb->consumer_frame, &vb->pending_frame);
av_frame_unref(vb->pending_frame);
video_buffer_swap_rendering_frame(vb);
if (vb->render_expired_frames) {
if (vb->wait_consumer) {
// unblock video_buffer_offer_decoded_frame()
sc_cond_signal(&vb->pending_frame_consumed_cond);
}
sc_mutex_unlock(&vb->mutex);
// rendering_frame is only written from this thread, no need to lock
return vb->rendering_frame;
// consumer_frame is only written from this thread, no need to lock
return vb->consumer_frame;
}
void
video_buffer_interrupt(struct video_buffer *vb) {
if (vb->render_expired_frames) {
if (vb->wait_consumer) {
sc_mutex_lock(&vb->mutex);
vb->interrupted = true;
sc_mutex_unlock(&vb->mutex);

View File

@@ -13,53 +13,67 @@ typedef struct AVFrame AVFrame;
/**
* There are 3 frames in memory:
* - one frame is held by the decoder (decoding_frame)
* - one frame is held by the renderer (rendering_frame)
* - one frame is shared between the decoder and the renderer (pending_frame)
* - one frame is held by the producer (producer_frame)
* - one frame is held by the consumer (consumer_frame)
* - one frame is shared between the producer and the consumer (pending_frame)
*
* The decoder decodes a packet into the decoding_frame (it may takes time).
* The producer generates a frame into the producer_frame (it may takes time).
*
* Once the frame is decoded, it calls video_buffer_offer_decoded_frame(),
* which swaps the decoding and pending frames.
* Once the frame is produced, it calls video_buffer_producer_offer_frame(),
* which swaps the producer and pending frames.
*
* When the renderer is notified that a new frame is available, it calls
* video_buffer_take_rendering_frame() to retrieve it, which swaps the pending
* and rendering frames. The frame is valid until the next call, without
* blocking the decoder.
* When the consumer is notified that a new frame is available, it calls
* video_buffer_consumer_take_frame() to retrieve it, which swaps the pending
* and consumer frames. The frame is valid until the next call, without
* blocking the producer.
*/
struct video_buffer {
AVFrame *decoding_frame;
AVFrame *producer_frame;
AVFrame *pending_frame;
AVFrame *rendering_frame;
AVFrame *consumer_frame;
sc_mutex mutex;
bool render_expired_frames;
bool wait_consumer; // never overwrite a pending frame if it is not consumed
bool interrupted;
sc_cond pending_frame_consumed_cond;
bool pending_frame_consumed;
struct fps_counter *fps_counter;
const struct video_buffer_callbacks *cbs;
void *cbs_userdata;
};
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, struct fps_counter *fps_counter,
bool render_expired_frames);
video_buffer_init(struct video_buffer *vb, bool wait_consumer);
void
video_buffer_destroy(struct video_buffer *vb);
// set the decoded frame as ready for rendering
// the output flag is set to report whether the previous frame has been skipped
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped);
video_buffer_set_consumer_callbacks(struct video_buffer *vb,
const struct video_buffer_callbacks *cbs,
void *cbs_userdata);
// mark the rendering frame as consumed and return it
// set the producer frame as ready for consuming
void
video_buffer_producer_offer_frame(struct video_buffer *vb);
// mark the consumer frame as consumed and return it
// the frame is valid until the next call to this function
const AVFrame *
video_buffer_take_rendering_frame(struct video_buffer *vb);
video_buffer_consumer_take_frame(struct video_buffer *vb);
// wake up and avoid any blocking call
void

View File

@@ -2,11 +2,11 @@
[binaries]
name = 'mingw'
c = '/usr/bin/i686-w64-mingw32-gcc'
cpp = '/usr/bin/i686-w64-mingw32-g++'
ar = '/usr/bin/i686-w64-mingw32-ar'
strip = '/usr/bin/i686-w64-mingw32-strip'
pkgconfig = '/usr/bin/i686-w64-mingw32-pkg-config'
c = 'i686-w64-mingw32-gcc'
cpp = 'i686-w64-mingw32-g++'
ar = 'i686-w64-mingw32-ar'
strip = 'i686-w64-mingw32-strip'
pkgconfig = 'i686-w64-mingw32-pkg-config'
[host_machine]
system = 'windows'

View File

@@ -2,11 +2,11 @@
[binaries]
name = 'mingw'
c = '/usr/bin/x86_64-w64-mingw32-gcc'
cpp = '/usr/bin/x86_64-w64-mingw32-g++'
ar = '/usr/bin/x86_64-w64-mingw32-ar'
strip = '/usr/bin/x86_64-w64-mingw32-strip'
pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config'
c = 'x86_64-w64-mingw32-gcc'
cpp = 'x86_64-w64-mingw32-g++'
ar = 'x86_64-w64-mingw32-ar'
strip = 'x86_64-w64-mingw32-strip'
pkgconfig = 'x86_64-w64-mingw32-pkg-config'
[host_machine]
system = 'windows'

View File

@@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit;
public class Controller {
private static final int DEVICE_ID_VIRTUAL = -1;
private static final int DEFAULT_DEVICE_ID = 0;
private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
@@ -45,7 +45,7 @@ public class Controller {
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
coords.orientation = 0;
coords.size = 1;
coords.size = 0;
pointerProperties[i] = props;
pointerCoords[i] = coords;
@@ -210,7 +210,7 @@ public class Controller {
int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN;
MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0, source,
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source,
0);
return device.injectEvent(event);
}
@@ -233,7 +233,7 @@ public class Controller {
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0);
return device.injectEvent(event);
}

View File

@@ -7,7 +7,6 @@ import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.os.Build;
import android.os.IBinder;
import android.view.Surface;
@@ -226,9 +225,7 @@ public class ScreenEncoder implements Device.RotationListener {
}
private static IBinder createDisplay() {
// Since Android 12, secure displays could not be created with shell permissions anymore
boolean secure = Build.VERSION.SDK_INT <= Build.VERSION_CODES.R;
return SurfaceControl.createDisplay("scrcpy", secure);
return SurfaceControl.createDisplay("scrcpy", true);
}
private static void configure(MediaCodec codec, MediaFormat format) {

View File

@@ -230,7 +230,7 @@ public final class Server {
if (encoders != null && encoders.length > 0) {
Ln.e("Try to use one of the available encoders:");
for (MediaCodecInfo encoder : encoders) {
Ln.e(" scrcpy --encoder-name '" + encoder.getName() + "'");
Ln.e(" scrcpy --encoder '" + encoder.getName() + "'");
}
}
}