Compare commits

...

38 Commits

Author SHA1 Message Date
205d9808fa wip_everything_broken 2021-02-23 22:20:52 +01:00
f13febe8b7 resizer_callbacks 2021-02-23 22:04:20 +01:00
86d152ea86 enable async resizing 2021-02-23 20:19:59 +01:00
c6ccb6a9a9 swscale-wip 2021-02-23 20:19:59 +01:00
a2d1709bfc wip resizer 2021-02-23 20:19:53 +01:00
eca374c423 scale_filter 2021-02-23 20:12:19 +01:00
1e33d9b306 Move decoder frame to decoder
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 handle its decoding frame, and
keep only the pending and consumer frames in the video_buffer.

    decoder -> pending -> consumer -> pending -> consumer
             |---------------------||---------------------|
                  video_buffer 1         video_buffer 2

This paves the way to support asynchronous swscale.
2021-02-23 20:06:31 +01:00
2f5546d33a Release frame data as soon as possible
During a frame swap, one of the two frames involved can be released.
2021-02-23 20:03:12 +01:00
d8ddf74865 Factorize frame swap 2021-02-23 20:03:12 +01:00
2f4d312142 Remove screen static initializer
Most of the fields are initialized dynamically.
2021-02-23 20:03:12 +01:00
62c5e73a3d 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-23 20:03:11 +01:00
f82e92d65e 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-23 15:22:25 +01:00
4c0554d225 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-23 15:19:50 +01:00
51b1b0f603 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-23 14:56:56 +01:00
9f18c82863 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-23 14:56:53 +01:00
2630512620 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-23 09:37:16 +01:00
b0b0240ce0 Insert numerical values statically in usage string 2021-02-22 22:03:50 +01:00
b521ee180d Fix double-free on error
Commit 44aa7f2c88 added calls to destroy
the renderer and the window, but forgot to remove the call to
screen_destroy() they were supposed to replace.
2021-02-21 22:52:24 +01:00
025388d38b Remove unused no_window field 2021-02-21 22:52:20 +01:00
dce0867737 Enable NDEBUG via Meson built-in option 2021-02-17 09:54:03 +01:00
5c4c28c973 Handle im-related events from input_manager.c 2021-02-17 09:54:03 +01:00
98628f25b5 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-17 09:54:03 +01:00
e1dbe4f71c Handle screen-related events from screen.c 2021-02-17 09:54:03 +01:00
b1734ab737 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-17 09:54:03 +01:00
44aa7f2c88 Improve error handling in screen initialization
After the struct screen is initialized, the window and the renderer are
necessarily valid, so there is no need o check in screen_destroy().
2021-02-17 09:54:03 +01:00
626094ad13 Handle window events only once visible
This will avoid corner cases where we need to resize while no frame has
been received yet.
2021-02-17 09:54:03 +01:00
a566635c43 Log mipmaps error only if mipmaps are enabled 2021-02-17 09:54:03 +01:00
862948b132 Make use_opengl local
The flag is used only locally, there is no need to store it in the
screen structure.
2021-02-17 09:54:03 +01:00
c0c4ba7009 Add intermediate frame in video buffer
There were only two frames simultaneously:
 - one used by the decoder;
 - one used by the renderer.

When the decoder finished decoding a frame, it swapped it with the
rendering frame.

Adding a third frame provides several benefits:
 - the decoder do not have to wait for the renderer to release the
   mutex;
 - it simplifies the video_buffer API;
 - it makes the rendering frame valid until the next call to
   video_buffer_take_rendering_frame(), which will be useful for
   swscaling on window resize.
2021-02-17 09:54:03 +01:00
c53bd4d8b6 Assert non-recursive usage of mutexes 2021-02-17 09:54:03 +01:00
54f5c42d7b Add mutex assertions 2021-02-17 09:54:03 +01:00
21d206f360 Expose mutex assertions
Add a function to assert that the mutex is held (or not).
2021-02-17 09:54:03 +01:00
d2689fc168 Expose thread id 2021-02-17 09:54:03 +01:00
f6320c7e31 Wrap SDL thread functions into scrcpy-specific API
The goal is to expose a consistent API for system tools, and paves the
way to make the "core" independant of SDL in the future.
2021-02-17 09:54:03 +01:00
30e619d37f Replace SDL_strdup() by strdup()
The functions SDL_malloc(), SDL_free() and SDL_strdup() were used only
because strdup() was not available everywhere.

Now that it is available, use the native version of these functions.
2021-02-17 09:54:03 +01:00
c0dde0fade Provide strdup() compat
Make strdup() available on all platforms.
2021-02-17 09:53:25 +01:00
ace438e52a Remove unused port_range field
The port_range is used from "struct server_params", the copy in
"struct server" was unused.
2021-02-14 14:47:49 +01:00
8e83f3e8af Remove unused custom event 2021-02-14 14:44:05 +01:00
46 changed files with 1346 additions and 634 deletions

View File

@ -2,6 +2,7 @@ src = [
'src/main.c',
'src/adb.c',
'src/cli.c',
'src/compat.c',
'src/control_msg.c',
'src/controller.c',
'src/decoder.c',
@ -14,6 +15,7 @@ src = [
'src/opengl.c',
'src/receiver.c',
'src/recorder.c',
'src/resizer.c',
'src/scrcpy.c',
'src/screen.c',
'src/server.c',
@ -22,7 +24,8 @@ src = [
'src/video_buffer.c',
'src/util/net.c',
'src/util/process.c',
'src/util/str_util.c'
'src/util/str_util.c',
'src/util/thread.c',
]
if host_machine.system() == 'windows'
@ -31,6 +34,10 @@ else
src += [ 'src/sys/unix/process.c' ]
endif
check_functions = [
'strdup'
]
cc = meson.get_compiler('c')
if not get_option('crossbuild_windows')
@ -40,6 +47,7 @@ if not get_option('crossbuild_windows')
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavutil'),
dependency('libswscale'),
dependency('sdl2'),
]
@ -86,8 +94,12 @@ endif
conf = configuration_data()
# expose the build type
conf.set('NDEBUG', get_option('buildtype') != 'debug')
foreach f : check_functions
if cc.has_function(f)
define = 'HAVE_' + f.underscorify().to_upper()
conf.set(define, true)
endif
endforeach
# the version, updated on release
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
@ -104,16 +116,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

@ -167,6 +167,13 @@ Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each incre
.BI "\-s, \-\-serial " number
The device serial number. Mandatory only if several devices are connected to adb.
.TP
.BI "\-\-scale\-filter filter
Supported filters are "none" and "trilinear".
Trilinear filtering is only available if the renderer is OpenGL 3.0+ or OpenGL
ES 2.0+.
.TP
.BI "\-\-shortcut\-mod " key[+...]][,...]
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".

View File

@ -173,7 +173,7 @@ adb_push(const char *serial, const char *local, const char *remote) {
}
remote = strquote(remote);
if (!remote) {
SDL_free((void *) local);
free((void *) local);
return PROCESS_NONE;
}
#endif
@ -182,8 +182,8 @@ adb_push(const char *serial, const char *local, const char *remote) {
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
SDL_free((void *) remote);
SDL_free((void *) local);
free((void *) remote);
free((void *) local);
#endif
return proc;
@ -204,7 +204,7 @@ adb_install(const char *serial, const char *local) {
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
SDL_free((void *) local);
free((void *) local);
#endif
return proc;

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"
@ -103,14 +106,10 @@ scrcpy_print_usage(const char *arg0) {
" --no-key-repeat\n"
" Do not forward repeated key events when a key is held down.\n"
"\n"
" --no-mipmaps\n"
" If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then\n"
" mipmaps are automatically generated to improve downscaling\n"
" quality. This option disables the generation of mipmaps.\n"
"\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"
@ -154,6 +153,11 @@ scrcpy_print_usage(const char *arg0) {
" The device serial number. Mandatory only if several devices\n"
" are connected to adb.\n"
"\n"
" --scale-filter filter\n"
" Supported filters are \"none\" and \"trilinear\".\n"
" Trilinear filtering is only available if the renderer is\n"
" OpenGL 3.0+ or OpenGL ES 2.0+.\n"
"\n"
" --shortcut-mod key[+...]][,...]\n"
" Specify the modifiers to use for scrcpy shortcuts.\n"
" Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\",\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
@ -641,6 +640,63 @@ guess_record_format(const char *filename) {
return 0;
}
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;
}
if (!strcmp(optarg, "trilinear")) {
*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);
return false;
}
#define OPT_RENDER_EXPIRED_FRAMES 1000
#define OPT_WINDOW_TITLE 1001
#define OPT_PUSH_TARGET 1002
@ -667,6 +723,7 @@ guess_record_format(const char *filename) {
#define OPT_FORWARD_ALL_CLICKS 1023
#define OPT_LEGACY_PASTE 1024
#define OPT_ENCODER_NAME 1025
#define OPT_SCALE_FILTER 1026
bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
@ -703,6 +760,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"render-expired-frames", no_argument, NULL,
OPT_RENDER_EXPIRED_FRAMES},
{"rotation", required_argument, NULL, OPT_ROTATION},
{"scale-filter", required_argument, NULL, OPT_SCALE_FILTER},
{"serial", required_argument, NULL, 's'},
{"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD},
{"show-touches", no_argument, NULL, 't'},
@ -857,7 +915,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
opts->render_driver = optarg;
break;
case OPT_NO_MIPMAPS:
opts->mipmaps = false;
LOGW("Deprecated option --no-mipmaps. "
"Use --scale-filter=none instead.");
opts->scale_filter = SC_SCALE_FILTER_NONE;
break;
case OPT_NO_KEY_REPEAT:
opts->forward_key_repeat = false;
@ -885,6 +945,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_LEGACY_PASTE:
opts->legacy_paste = true;
break;
case OPT_SCALE_FILTER:
if (!parse_scale_filter(optarg, &opts->scale_filter)) {
return false;
}
break;
default:
// getopt prints the error message on stderr
return false;

14
app/src/compat.c Normal file
View File

@ -0,0 +1,14 @@
#include "compat.h"
#include "config.h"
#ifndef HAVE_STRDUP
char *strdup(const char *s) {
size_t size = strlen(s) + 1;
char *dup = malloc(size);
if (dup) {
memcpy(dup, s, size);
}
return dup;
}
#endif

View File

@ -56,4 +56,8 @@
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
#endif
#ifndef HAVE_STRDUP
char *strdup(const char *s);
#endif
#endif

View File

@ -1,6 +1,7 @@
#include "control_msg.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "util/buffer_util.h"
@ -93,10 +94,10 @@ void
control_msg_destroy(struct control_msg *msg) {
switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_TEXT:
SDL_free(msg->inject_text.text);
free(msg->inject_text.text);
break;
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
SDL_free(msg->set_clipboard.text);
free(msg->set_clipboard.text);
break;
default:
// do nothing

View File

@ -50,7 +50,7 @@ struct control_msg {
enum android_metastate metastate;
} inject_keycode;
struct {
char *text; // owned, to be freed by SDL_free()
char *text; // owned, to be freed by free()
} inject_text;
struct {
enum android_motionevent_action action;
@ -65,7 +65,7 @@ struct control_msg {
int32_t vscroll;
} inject_scroll_event;
struct {
char *text; // owned, to be freed by SDL_free()
char *text; // owned, to be freed by free()
bool paste;
} set_clipboard;
struct {

View File

@ -2,25 +2,27 @@
#include <assert.h>
#include "util/lock.h"
#include "util/log.h"
bool
controller_init(struct controller *controller, socket_t control_socket) {
cbuf_init(&controller->queue);
if (!receiver_init(&controller->receiver, control_socket)) {
bool ok = receiver_init(&controller->receiver, control_socket);
if (!ok) {
return false;
}
if (!(controller->mutex = SDL_CreateMutex())) {
ok = sc_mutex_init(&controller->mutex);
if (!ok) {
receiver_destroy(&controller->receiver);
return false;
}
if (!(controller->msg_cond = SDL_CreateCond())) {
ok = sc_cond_init(&controller->msg_cond);
if (!ok) {
receiver_destroy(&controller->receiver);
SDL_DestroyMutex(controller->mutex);
sc_mutex_destroy(&controller->mutex);
return false;
}
@ -32,8 +34,8 @@ controller_init(struct controller *controller, socket_t control_socket) {
void
controller_destroy(struct controller *controller) {
SDL_DestroyCond(controller->msg_cond);
SDL_DestroyMutex(controller->mutex);
sc_cond_destroy(&controller->msg_cond);
sc_mutex_destroy(&controller->mutex);
struct control_msg msg;
while (cbuf_take(&controller->queue, &msg)) {
@ -46,13 +48,13 @@ controller_destroy(struct controller *controller) {
bool
controller_push_msg(struct controller *controller,
const struct control_msg *msg) {
mutex_lock(controller->mutex);
sc_mutex_lock(&controller->mutex);
bool was_empty = cbuf_is_empty(&controller->queue);
bool res = cbuf_push(&controller->queue, *msg);
if (was_empty) {
cond_signal(controller->msg_cond);
sc_cond_signal(&controller->msg_cond);
}
mutex_unlock(controller->mutex);
sc_mutex_unlock(&controller->mutex);
return res;
}
@ -73,20 +75,20 @@ run_controller(void *data) {
struct controller *controller = data;
for (;;) {
mutex_lock(controller->mutex);
sc_mutex_lock(&controller->mutex);
while (!controller->stopped && cbuf_is_empty(&controller->queue)) {
cond_wait(controller->msg_cond, controller->mutex);
sc_cond_wait(&controller->msg_cond, &controller->mutex);
}
if (controller->stopped) {
// stop immediately, do not process further msgs
mutex_unlock(controller->mutex);
sc_mutex_unlock(&controller->mutex);
break;
}
struct control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty);
(void) non_empty;
mutex_unlock(controller->mutex);
sc_mutex_unlock(&controller->mutex);
bool ok = process_msg(controller, &msg);
control_msg_destroy(&msg);
@ -102,16 +104,16 @@ bool
controller_start(struct controller *controller) {
LOGD("Starting controller thread");
controller->thread = SDL_CreateThread(run_controller, "controller",
controller);
if (!controller->thread) {
bool ok = sc_thread_create(&controller->thread, run_controller,
"controller", controller);
if (!ok) {
LOGC("Could not start controller thread");
return false;
}
if (!receiver_start(&controller->receiver)) {
controller_stop(controller);
SDL_WaitThread(controller->thread, NULL);
sc_thread_join(&controller->thread, NULL);
return false;
}
@ -120,14 +122,14 @@ controller_start(struct controller *controller) {
void
controller_stop(struct controller *controller) {
mutex_lock(controller->mutex);
sc_mutex_lock(&controller->mutex);
controller->stopped = true;
cond_signal(controller->msg_cond);
mutex_unlock(controller->mutex);
sc_cond_signal(&controller->msg_cond);
sc_mutex_unlock(&controller->mutex);
}
void
controller_join(struct controller *controller) {
SDL_WaitThread(controller->thread, NULL);
sc_thread_join(&controller->thread, NULL);
receiver_join(&controller->receiver);
}

View File

@ -4,21 +4,20 @@
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "control_msg.h"
#include "receiver.h"
#include "util/cbuf.h"
#include "util/net.h"
#include "util/thread.h"
struct control_msg_queue CBUF(struct control_msg, 64);
struct controller {
socket_t control_socket;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *msg_cond;
sc_thread thread;
sc_mutex mutex;
sc_cond msg_cond;
bool stopped;
struct control_msg_queue queue;
struct receiver receiver;

View File

@ -3,8 +3,6 @@
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "events.h"
@ -13,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;
@ -48,11 +30,19 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) {
return false;
}
decoder->frame = av_frame_alloc();
if (!decoder->frame) {
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
return false;
}
return true;
}
void
decoder_close(struct decoder *decoder) {
av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
}
@ -67,11 +57,11 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
LOGE("Could not send video packet: %d", ret);
return false;
}
ret = avcodec_receive_frame(decoder->codec_ctx,
decoder->video_buffer->decoding_frame);
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;
@ -79,7 +69,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
#else
int got_picture;
int len = avcodec_decode_video2(decoder->codec_ctx,
decoder->video_buffer->decoding_frame,
decoder->frame,
&got_picture,
packet);
if (len < 0) {

View File

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

View File

@ -1,5 +1,6 @@
#include "device_msg.h"
#include <stdlib.h>
#include <string.h>
#include "util/buffer_util.h"
@ -20,7 +21,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
if (clipboard_len > len - 5) {
return 0; // not available
}
char *text = SDL_malloc(clipboard_len + 1);
char *text = malloc(clipboard_len + 1);
if (!text) {
LOGW("Could not allocate text for clipboard");
return -1;
@ -42,6 +43,6 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
void
device_msg_destroy(struct device_msg *msg) {
if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
SDL_free(msg->clipboard.text);
free(msg->clipboard.text);
}
}

View File

@ -19,7 +19,7 @@ struct device_msg {
enum device_msg_type type;
union {
struct {
char *text; // owned, to be freed by SDL_free()
char *text; // owned, to be freed by free()
} clipboard;
};
};

View File

@ -1,3 +1,2 @@
#define EVENT_NEW_SESSION SDL_USEREVENT
#define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2)
#define EVENT_NEW_FRAME SDL_USEREVENT
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)

View File

@ -4,14 +4,13 @@
#include <string.h>
#include "adb.h"
#include "util/lock.h"
#include "util/log.h"
#define DEFAULT_PUSH_TARGET "/sdcard/"
static void
file_handler_request_destroy(struct file_handler_request *req) {
SDL_free(req->file);
free(req->file);
}
bool
@ -20,21 +19,23 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
cbuf_init(&file_handler->queue);
if (!(file_handler->mutex = SDL_CreateMutex())) {
bool ok = sc_mutex_init(&file_handler->mutex);
if (!ok) {
return false;
}
if (!(file_handler->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(file_handler->mutex);
ok = sc_cond_init(&file_handler->event_cond);
if (!ok) {
sc_mutex_destroy(&file_handler->mutex);
return false;
}
if (serial) {
file_handler->serial = SDL_strdup(serial);
file_handler->serial = strdup(serial);
if (!file_handler->serial) {
LOGW("Could not strdup serial");
SDL_DestroyCond(file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex);
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
}
} else {
@ -54,9 +55,9 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
void
file_handler_destroy(struct file_handler *file_handler) {
SDL_DestroyCond(file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex);
SDL_free(file_handler->serial);
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
free(file_handler->serial);
struct file_handler_request req;
while (cbuf_take(&file_handler->queue, &req)) {
@ -92,13 +93,13 @@ file_handler_request(struct file_handler *file_handler,
.file = file,
};
mutex_lock(file_handler->mutex);
sc_mutex_lock(&file_handler->mutex);
bool was_empty = cbuf_is_empty(&file_handler->queue);
bool res = cbuf_push(&file_handler->queue, req);
if (was_empty) {
cond_signal(file_handler->event_cond);
sc_cond_signal(&file_handler->event_cond);
}
mutex_unlock(file_handler->mutex);
sc_mutex_unlock(&file_handler->mutex);
return res;
}
@ -107,14 +108,14 @@ run_file_handler(void *data) {
struct file_handler *file_handler = data;
for (;;) {
mutex_lock(file_handler->mutex);
sc_mutex_lock(&file_handler->mutex);
file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
cond_wait(file_handler->event_cond, file_handler->mutex);
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
}
if (file_handler->stopped) {
// stop immediately, do not process further events
mutex_unlock(file_handler->mutex);
sc_mutex_unlock(&file_handler->mutex);
break;
}
struct file_handler_request req;
@ -132,7 +133,7 @@ run_file_handler(void *data) {
file_handler->push_target);
}
file_handler->current_process = process;
mutex_unlock(file_handler->mutex);
sc_mutex_unlock(&file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) {
if (process_check_success(process, "adb install", false)) {
@ -150,13 +151,13 @@ run_file_handler(void *data) {
}
}
mutex_lock(file_handler->mutex);
sc_mutex_lock(&file_handler->mutex);
// Close the process (it is necessary already terminated)
// Execute this call with mutex locked to avoid race conditions with
// file_handler_stop()
process_close(file_handler->current_process);
file_handler->current_process = PROCESS_NONE;
mutex_unlock(file_handler->mutex);
sc_mutex_unlock(&file_handler->mutex);
file_handler_request_destroy(&req);
}
@ -167,9 +168,9 @@ bool
file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread");
file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler",
file_handler);
if (!file_handler->thread) {
bool ok = sc_thread_create(&file_handler->thread, run_file_handler,
"file_handler", file_handler);
if (!ok) {
LOGC("Could not start file_handler thread");
return false;
}
@ -179,18 +180,18 @@ file_handler_start(struct file_handler *file_handler) {
void
file_handler_stop(struct file_handler *file_handler) {
mutex_lock(file_handler->mutex);
sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true;
cond_signal(file_handler->event_cond);
sc_cond_signal(&file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) {
if (!process_terminate(file_handler->current_process)) {
LOGW("Could not terminate push/install process");
}
}
mutex_unlock(file_handler->mutex);
sc_mutex_unlock(&file_handler->mutex);
}
void
file_handler_join(struct file_handler *file_handler) {
SDL_WaitThread(file_handler->thread, NULL);
sc_thread_join(&file_handler->thread, NULL);
}

View File

@ -4,11 +4,10 @@
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "adb.h"
#include "util/cbuf.h"
#include "util/thread.h"
typedef enum {
ACTION_INSTALL_APK,
@ -25,9 +24,9 @@ struct file_handler_request_queue CBUF(struct file_handler_request, 16);
struct file_handler {
char *serial;
const char *push_target;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *event_cond;
sc_thread thread;
sc_mutex mutex;
sc_cond event_cond;
bool stopped;
bool initialized;
process_t current_process;
@ -50,7 +49,7 @@ file_handler_stop(struct file_handler *file_handler);
void
file_handler_join(struct file_handler *file_handler);
// take ownership of file, and will SDL_free() it
// take ownership of file, and will free() it
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,

View File

@ -3,25 +3,24 @@
#include <assert.h>
#include <SDL2/SDL_timer.h>
#include "util/lock.h"
#include "util/log.h"
#define FPS_COUNTER_INTERVAL_MS 1000
bool
fps_counter_init(struct fps_counter *counter) {
counter->mutex = SDL_CreateMutex();
if (!counter->mutex) {
bool ok = sc_mutex_init(&counter->mutex);
if (!ok) {
return false;
}
counter->state_cond = SDL_CreateCond();
if (!counter->state_cond) {
SDL_DestroyMutex(counter->mutex);
ok = sc_cond_init(&counter->state_cond);
if (!ok) {
sc_mutex_destroy(&counter->mutex);
return false;
}
counter->thread = NULL;
counter->thread_started = false;
atomic_init(&counter->started, 0);
// no need to initialize the other fields, they are unused until started
@ -30,8 +29,8 @@ fps_counter_init(struct fps_counter *counter) {
void
fps_counter_destroy(struct fps_counter *counter) {
SDL_DestroyCond(counter->state_cond);
SDL_DestroyMutex(counter->mutex);
sc_cond_destroy(&counter->state_cond);
sc_mutex_destroy(&counter->mutex);
}
static inline bool
@ -77,10 +76,10 @@ static int
run_fps_counter(void *data) {
struct fps_counter *counter = data;
mutex_lock(counter->mutex);
sc_mutex_lock(&counter->mutex);
while (!counter->interrupted) {
while (!counter->interrupted && !is_started(counter)) {
cond_wait(counter->state_cond, counter->mutex);
sc_cond_wait(&counter->state_cond, &counter->mutex);
}
while (!counter->interrupted && is_started(counter)) {
uint32_t now = SDL_GetTicks();
@ -90,32 +89,35 @@ run_fps_counter(void *data) {
uint32_t remaining = counter->next_timestamp - now;
// ignore the reason (timeout or signaled), we just loop anyway
cond_wait_timeout(counter->state_cond, counter->mutex, remaining);
sc_cond_timedwait(&counter->state_cond, &counter->mutex, remaining);
}
}
mutex_unlock(counter->mutex);
sc_mutex_unlock(&counter->mutex);
return 0;
}
bool
fps_counter_start(struct fps_counter *counter) {
mutex_lock(counter->mutex);
sc_mutex_lock(&counter->mutex);
counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
counter->nr_rendered = 0;
counter->nr_skipped = 0;
mutex_unlock(counter->mutex);
sc_mutex_unlock(&counter->mutex);
set_started(counter, true);
cond_signal(counter->state_cond);
sc_cond_signal(&counter->state_cond);
// counter->thread is always accessed from the same thread, no need to lock
if (!counter->thread) {
counter->thread =
SDL_CreateThread(run_fps_counter, "fps counter", counter);
if (!counter->thread) {
// counter->thread_started and counter->thread are always accessed from the
// same thread, no need to lock
if (!counter->thread_started) {
bool ok = sc_thread_create(&counter->thread, run_fps_counter,
"fps counter", counter);
if (!ok) {
LOGE("Could not start FPS counter thread");
return false;
}
counter->thread_started = true;
}
return true;
@ -124,7 +126,7 @@ fps_counter_start(struct fps_counter *counter) {
void
fps_counter_stop(struct fps_counter *counter) {
set_started(counter, false);
cond_signal(counter->state_cond);
sc_cond_signal(&counter->state_cond);
}
bool
@ -134,21 +136,21 @@ fps_counter_is_started(struct fps_counter *counter) {
void
fps_counter_interrupt(struct fps_counter *counter) {
if (!counter->thread) {
if (!counter->thread_started) {
return;
}
mutex_lock(counter->mutex);
sc_mutex_lock(&counter->mutex);
counter->interrupted = true;
mutex_unlock(counter->mutex);
sc_mutex_unlock(&counter->mutex);
// wake up blocking wait
cond_signal(counter->state_cond);
sc_cond_signal(&counter->state_cond);
}
void
fps_counter_join(struct fps_counter *counter) {
if (counter->thread) {
SDL_WaitThread(counter->thread, NULL);
if (counter->thread_started) {
sc_thread_join(&counter->thread, NULL);
}
}
@ -158,11 +160,11 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
return;
}
mutex_lock(counter->mutex);
sc_mutex_lock(&counter->mutex);
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
++counter->nr_rendered;
mutex_unlock(counter->mutex);
sc_mutex_unlock(&counter->mutex);
}
void
@ -171,9 +173,9 @@ fps_counter_add_skipped_frame(struct fps_counter *counter) {
return;
}
mutex_lock(counter->mutex);
sc_mutex_lock(&counter->mutex);
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
++counter->nr_skipped;
mutex_unlock(counter->mutex);
sc_mutex_unlock(&counter->mutex);
}

View File

@ -6,13 +6,15 @@
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "util/thread.h"
struct fps_counter {
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *state_cond;
sc_thread thread;
sc_mutex mutex;
sc_cond state_cond;
bool thread_started;
// atomic so that we can check without locking the mutex
// if the FPS counter is disabled, we don't want to lock unnecessarily

View File

@ -4,7 +4,6 @@
#include <SDL2/SDL_keycode.h>
#include "event_converter.h"
#include "util/lock.h"
#include "util/log.h"
static const int ACTION_DOWN = 1;
@ -190,13 +189,20 @@ set_device_clipboard(struct controller *controller, bool paste) {
return;
}
char *text_dup = strdup(text);
SDL_free(text);
if (!text_dup) {
LOGW("Could not strdup input text");
return;
}
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
msg.set_clipboard.text = text;
msg.set_clipboard.text = text_dup;
msg.set_clipboard.paste = paste;
if (!controller_push_msg(controller, &msg)) {
SDL_free(text);
free(text_dup);
LOGW("Could not request 'set device clipboard'");
}
}
@ -242,11 +248,18 @@ clipboard_paste(struct controller *controller) {
return;
}
char *text_dup = strdup(text);
SDL_free(text);
if (!text_dup) {
LOGW("Could not strdup input text");
return;
}
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text;
msg.inject_text.text = text_dup;
if (!controller_push_msg(controller, &msg)) {
SDL_free(text);
free(text_dup);
LOGW("Could not request 'paste clipboard'");
}
}
@ -273,7 +286,7 @@ rotate_client_right(struct screen *screen) {
screen_set_rotation(screen, new_rotation);
}
void
static void
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) {
if (is_shortcut_mod(im, SDL_GetModState())) {
@ -291,13 +304,13 @@ input_manager_process_text_input(struct input_manager *im,
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = SDL_strdup(event->text);
msg.inject_text.text = strdup(event->text);
if (!msg.inject_text.text) {
LOGW("Could not strdup input text");
return;
}
if (!controller_push_msg(im->controller, &msg)) {
SDL_free(msg.inject_text.text);
free(msg.inject_text.text);
LOGW("Could not request 'inject text'");
}
}
@ -353,7 +366,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
return true;
}
void
static void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event) {
// control: indicates the state of the command-line option --no-control
@ -467,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:
@ -538,7 +549,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
return true;
}
void
static void
input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event) {
if (!event->state) {
@ -592,7 +603,7 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
return true;
}
void
static void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event) {
struct control_msg msg;
@ -624,7 +635,7 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
return true;
}
void
static void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event) {
bool control = im->control;
@ -723,7 +734,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
return true;
}
void
static void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event) {
struct control_msg msg;
@ -733,3 +744,46 @@ input_manager_process_mouse_wheel(struct input_manager *im,
}
}
}
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event) {
switch (event->type) {
case SDL_TEXTINPUT:
if (!im->control) {
return true;
}
input_manager_process_text_input(im, &event->text);
return true;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(im, &event->key);
return true;
case SDL_MOUSEMOTION:
if (!im->control) {
break;
}
input_manager_process_mouse_motion(im, &event->motion);
return true;
case SDL_MOUSEWHEEL:
if (!im->control) {
break;
}
input_manager_process_mouse_wheel(im, &event->wheel);
return true;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(im, &event->button);
return true;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(im, &event->tfinger);
return true;
}
return false;
}

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
@ -40,28 +41,7 @@ void
input_manager_init(struct input_manager *im,
const struct scrcpy_options *options);
void
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event);
void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event);
void
input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event);
void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event);
void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event);
void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event);
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event);
#endif

View File

@ -4,12 +4,12 @@
#include <SDL2/SDL_clipboard.h>
#include "device_msg.h"
#include "util/lock.h"
#include "util/log.h"
bool
receiver_init(struct receiver *receiver, socket_t control_socket) {
if (!(receiver->mutex = SDL_CreateMutex())) {
bool ok = sc_mutex_init(&receiver->mutex);
if (!ok) {
return false;
}
receiver->control_socket = control_socket;
@ -18,7 +18,7 @@ receiver_init(struct receiver *receiver, socket_t control_socket) {
void
receiver_destroy(struct receiver *receiver) {
SDL_DestroyMutex(receiver->mutex);
sc_mutex_destroy(&receiver->mutex);
}
static void
@ -101,8 +101,9 @@ bool
receiver_start(struct receiver *receiver) {
LOGD("Starting receiver thread");
receiver->thread = SDL_CreateThread(run_receiver, "receiver", receiver);
if (!receiver->thread) {
bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver",
receiver);
if (!ok) {
LOGC("Could not start receiver thread");
return false;
}
@ -112,5 +113,5 @@ receiver_start(struct receiver *receiver) {
void
receiver_join(struct receiver *receiver) {
SDL_WaitThread(receiver->thread, NULL);
sc_thread_join(&receiver->thread, NULL);
}

View File

@ -4,17 +4,16 @@
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "util/net.h"
#include "util/thread.h"
// receive events from the device
// managed by the controller
struct receiver {
socket_t control_socket;
SDL_Thread *thread;
SDL_mutex *mutex;
sc_thread thread;
sc_mutex mutex;
};
bool

View File

@ -3,7 +3,6 @@
#include <assert.h>
#include <libavutil/time.h>
#include "util/lock.h"
#include "util/log.h"
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
@ -27,7 +26,7 @@ find_muxer(const char *name) {
static struct record_packet *
record_packet_new(const AVPacket *packet) {
struct record_packet *rec = SDL_malloc(sizeof(*rec));
struct record_packet *rec = malloc(sizeof(*rec));
if (!rec) {
return NULL;
}
@ -37,7 +36,7 @@ record_packet_new(const AVPacket *packet) {
av_init_packet(&rec->packet);
if (av_packet_ref(&rec->packet, packet)) {
SDL_free(rec);
free(rec);
return NULL;
}
return rec;
@ -46,7 +45,7 @@ record_packet_new(const AVPacket *packet) {
static void
record_packet_delete(struct record_packet *rec) {
av_packet_unref(&rec->packet);
SDL_free(rec);
free(rec);
}
static void
@ -63,24 +62,24 @@ recorder_init(struct recorder *recorder,
const char *filename,
enum sc_record_format format,
struct size declared_frame_size) {
recorder->filename = SDL_strdup(filename);
recorder->filename = strdup(filename);
if (!recorder->filename) {
LOGE("Could not strdup filename");
return false;
}
recorder->mutex = SDL_CreateMutex();
if (!recorder->mutex) {
bool ok = sc_mutex_init(&recorder->mutex);
if (!ok) {
LOGC("Could not create mutex");
SDL_free(recorder->filename);
free(recorder->filename);
return false;
}
recorder->queue_cond = SDL_CreateCond();
if (!recorder->queue_cond) {
ok = sc_cond_init(&recorder->queue_cond);
if (!ok) {
LOGC("Could not create cond");
SDL_DestroyMutex(recorder->mutex);
SDL_free(recorder->filename);
sc_mutex_destroy(&recorder->mutex);
free(recorder->filename);
return false;
}
@ -97,9 +96,9 @@ recorder_init(struct recorder *recorder,
void
recorder_destroy(struct recorder *recorder) {
SDL_DestroyCond(recorder->queue_cond);
SDL_DestroyMutex(recorder->mutex);
SDL_free(recorder->filename);
sc_cond_destroy(&recorder->queue_cond);
sc_mutex_destroy(&recorder->mutex);
free(recorder->filename);
}
static const char *
@ -258,17 +257,17 @@ run_recorder(void *data) {
struct recorder *recorder = data;
for (;;) {
mutex_lock(recorder->mutex);
sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
cond_wait(recorder->queue_cond, recorder->mutex);
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
}
// if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping
if (recorder->stopped && queue_is_empty(&recorder->queue)) {
mutex_unlock(recorder->mutex);
sc_mutex_unlock(&recorder->mutex);
struct record_packet *last = recorder->previous;
if (last) {
// assign an arbitrary duration to the last packet
@ -288,7 +287,7 @@ run_recorder(void *data) {
struct record_packet *rec;
queue_take(&recorder->queue, next, &rec);
mutex_unlock(recorder->mutex);
sc_mutex_unlock(&recorder->mutex);
// recorder->previous is only written from this thread, no need to lock
struct record_packet *previous = recorder->previous;
@ -311,11 +310,11 @@ run_recorder(void *data) {
if (!ok) {
LOGE("Could not record packet");
mutex_lock(recorder->mutex);
sc_mutex_lock(&recorder->mutex);
recorder->failed = true;
// discard pending packets
recorder_queue_clear(&recorder->queue);
mutex_unlock(recorder->mutex);
sc_mutex_unlock(&recorder->mutex);
break;
}
@ -330,8 +329,9 @@ bool
recorder_start(struct recorder *recorder) {
LOGD("Starting recorder thread");
recorder->thread = SDL_CreateThread(run_recorder, "recorder", recorder);
if (!recorder->thread) {
bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
recorder);
if (!ok) {
LOGC("Could not start recorder thread");
return false;
}
@ -341,38 +341,38 @@ recorder_start(struct recorder *recorder) {
void
recorder_stop(struct recorder *recorder) {
mutex_lock(recorder->mutex);
sc_mutex_lock(&recorder->mutex);
recorder->stopped = true;
cond_signal(recorder->queue_cond);
mutex_unlock(recorder->mutex);
sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex);
}
void
recorder_join(struct recorder *recorder) {
SDL_WaitThread(recorder->thread, NULL);
sc_thread_join(&recorder->thread, NULL);
}
bool
recorder_push(struct recorder *recorder, const AVPacket *packet) {
mutex_lock(recorder->mutex);
sc_mutex_lock(&recorder->mutex);
assert(!recorder->stopped);
if (recorder->failed) {
// reject any new packet (this will stop the stream)
mutex_unlock(recorder->mutex);
sc_mutex_unlock(&recorder->mutex);
return false;
}
struct record_packet *rec = record_packet_new(packet);
if (!rec) {
LOGC("Could not allocate record packet");
mutex_unlock(recorder->mutex);
sc_mutex_unlock(&recorder->mutex);
return false;
}
queue_push(&recorder->queue, next, rec);
cond_signal(recorder->queue_cond);
sc_cond_signal(&recorder->queue_cond);
mutex_unlock(recorder->mutex);
sc_mutex_unlock(&recorder->mutex);
return true;
}

View File

@ -5,12 +5,11 @@
#include <stdbool.h>
#include <libavformat/avformat.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "coords.h"
#include "scrcpy.h"
#include "util/queue.h"
#include "util/thread.h"
struct record_packet {
AVPacket packet;
@ -26,9 +25,9 @@ struct recorder {
struct size declared_frame_size;
bool header_written;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *queue_cond;
sc_thread thread;
sc_mutex mutex;
sc_cond queue_cond;
bool stopped; // set on recorder_stop() by the stream reader
bool failed; // set on packet write failure
struct recorder_queue queue;

207
app/src/resizer.c Normal file
View File

@ -0,0 +1,207 @@
#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,
enum sc_scale_filter scale_filter, struct size size) {
bool ok = sc_mutex_init(&resizer->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&resizer->req_cond);
if (!ok) {
sc_mutex_destroy(&resizer->mutex);
return false;
}
ok = video_buffer_init(&resizer->vb_out, false); // FIXME wait_consumer
if (!ok) {
sc_cond_destroy(&resizer->req_cond);
sc_mutex_destroy(&resizer->mutex);
return false;
}
resizer->resized_frame = av_frame_alloc();
if (!resizer->resized_frame) {
video_buffer_destroy(&resizer->vb_out);
sc_cond_destroy(&resizer->req_cond);
sc_mutex_destroy(&resizer->mutex);
return false;
}
resizer->vb_in = vb_in;
resizer->scale_filter = scale_filter;
resizer->size = size;
resizer->input_frame = NULL;
resizer->has_request = false;
resizer->has_new_frame = false;
resizer->interrupted = false;
return true;
}
void
sc_resizer_destroy(struct sc_resizer *resizer) {
av_frame_free(&resizer->resized_frame);
sc_cond_destroy(&resizer->req_cond);
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;
}
static int
run_resizer(void *data) {
struct sc_resizer *resizer = data;
sc_mutex_lock(&resizer->mutex);
while (!resizer->interrupted) {
while (!resizer->interrupted && !resizer->has_request) {
sc_cond_wait(&resizer->req_cond, &resizer->mutex);
}
if (resizer->has_new_frame) {
resizer->input_frame =
video_buffer_consumer_take_frame(resizer->vb_in);
resizer->has_new_frame = false;
}
resizer->has_request = false;
sc_mutex_unlock(&resizer->mutex);
// Do the actual work without mutex
sc_resizer_swscale(resizer);
sc_mutex_lock(&resizer->mutex);
// Update the original size of the resized frame
resizer->original_size.width = resizer->input_frame->width;
resizer->original_size.height = resizer->input_frame->height;
assert(resizer->original_size.width);
assert(resizer->original_size.height);
video_buffer_producer_offer_frame(&resizer->vb_out,
&resizer->resized_frame);
}
sc_mutex_unlock(&resizer->mutex);
return 0;
}
bool
sc_resizer_start(struct sc_resizer *resizer) {
LOGD("Starting resizer thread");
bool ok = sc_thread_create(&resizer->thread, run_resizer, "resizer",
resizer);
if (!ok) {
LOGE("Could not start resizer thread");
return false;
}
return true;
}
void
sc_resizer_stop(struct sc_resizer *resizer) {
sc_mutex_lock(&resizer->mutex);
resizer->interrupted = true;
sc_cond_signal(&resizer->req_cond);
sc_mutex_unlock(&resizer->mutex);
video_buffer_interrupt(&resizer->vb_out);
}
void
sc_resizer_join(struct sc_resizer *resizer) {
sc_thread_join(&resizer->thread, NULL);
}
const AVFrame *
sc_resizer_consumer_take_frame(struct sc_resizer *resizer,
struct size *out_original_size) {
// Locking the mutex is necessary to ensure that the size corresponds to the correct frame
sc_mutex_lock(&resizer->mutex);
const AVFrame *frame = video_buffer_consumer_take_frame(&resizer->vb_out);
*out_original_size = resizer->original_size;
sc_mutex_unlock(&resizer->mutex);
return frame;
}
void
sc_resizer_process_new_frame(struct sc_resizer *resizer) {
sc_mutex_lock(&resizer->mutex);
resizer->has_request = true;
resizer->has_new_frame = true;
sc_cond_signal(&resizer->req_cond);
sc_mutex_unlock(&resizer->mutex);
}
void
sc_resizer_process_new_size(struct sc_resizer *resizer, struct size size) {
sc_mutex_lock(&resizer->mutex);
resizer->size = size;
resizer->has_request = true;
sc_cond_signal(&resizer->req_cond);
sc_mutex_unlock(&resizer->mutex);
}

62
app/src/resizer.h Normal file
View File

@ -0,0 +1,62 @@
#ifndef SC_RESIZER
#define SC_RESIZER
#include "common.h"
#include <stdbool.h>
#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)
const AVFrame *input_frame;
AVFrame *resized_frame;
// original size of the available (resized) frame in vb_out
struct size original_size;
sc_thread thread;
sc_mutex mutex;
sc_cond req_cond;
bool has_request;
bool has_new_frame;
bool interrupted;
};
bool
sc_resizer_init(struct sc_resizer *resizer, struct video_buffer *vb_in,
enum sc_scale_filter scale_filter, struct size size);
void
sc_resizer_destroy(struct sc_resizer *resizer);
bool
sc_resizer_start(struct sc_resizer *resizer);
void
sc_resizer_stop(struct sc_resizer *resizer);
void
sc_resizer_join(struct sc_resizer *resizer);
const AVFrame *
sc_resizer_consumer_take_frame(struct sc_resizer *resizer,
struct size *out_original_size);
void
sc_resizer_process_new_frame(struct sc_resizer *resizer);
void
sc_resizer_process_new_size(struct sc_resizer *resizer, struct size size);
#endif

View File

@ -26,12 +26,11 @@
#include "stream.h"
#include "tiny_xpm.h"
#include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h"
#include "util/net.h"
static struct server server;
static struct screen screen = SCREEN_INITIALIZER;
static struct screen screen;
static struct fps_counter fps_counter;
static struct video_buffer video_buffer;
static struct stream stream;
@ -43,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,
@ -174,68 +174,37 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) {
case SDL_QUIT:
LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = true;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &video_buffer)) {
return EVENT_RESULT_CONTINUE;
}
break;
case SDL_WINDOWEVENT:
screen_handle_window_event(&screen, &event->window);
break;
case SDL_TEXTINPUT:
if (!options->control) {
break;
}
input_manager_process_text_input(&input_manager, &event->text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(&input_manager, &event->key);
break;
case SDL_MOUSEMOTION:
if (!options->control) {
break;
}
input_manager_process_mouse_motion(&input_manager, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!options->control) {
break;
}
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(&input_manager, &event->button);
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(&input_manager, &event->tfinger);
break;
case SDL_DROPFILE: {
if (!options->control) {
break;
}
char *file = strdup(event->drop.file);
SDL_free(event->drop.file);
if (!file) {
LOGW("Could not strdup drop filename\n");
break;
}
file_handler_action_t action;
if (is_apk(event->drop.file)) {
if (is_apk(file)) {
action = ACTION_INSTALL_APK;
} else {
action = ACTION_PUSH_FILE;
}
file_handler_request(&file_handler, action, event->drop.file);
file_handler_request(&file_handler, action, file);
break;
}
}
bool consumed = screen_handle_event(&screen, event);
if (consumed) {
goto end;
}
consumed = input_manager_handle_event(&input_manager, event);
(void) consumed;
end:
return EVENT_RESULT_CONTINUE;
}
@ -286,7 +255,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
if (priority == 0) {
return;
}
char *local_fmt = SDL_malloc(strlen(fmt) + 10);
char *local_fmt = malloc(strlen(fmt) + 10);
if (!local_fmt) {
LOGC("Could not allocate string");
return;
@ -295,7 +264,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
strcpy(local_fmt, "[FFmpeg] ");
strcpy(local_fmt + 9, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
SDL_free(local_fmt);
free(local_fmt);
}
bool
@ -364,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;
@ -398,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)) {
@ -421,12 +382,14 @@ 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);
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)) {
options->rotation, options->scale_filter)) {
goto end;
}
@ -445,6 +408,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

@ -41,6 +41,22 @@ struct sc_port_range {
uint16_t last;
};
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)
struct scrcpy_options {
@ -56,6 +72,7 @@ struct scrcpy_options {
enum sc_record_format record_format;
struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
enum sc_scale_filter scale_filter;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
@ -75,7 +92,6 @@ struct scrcpy_options {
bool render_expired_frames;
bool prefer_text;
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
@ -103,10 +119,11 @@ struct scrcpy_options {
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
.count = 2, \
}, \
.max_size = DEFAULT_MAX_SIZE, \
.scale_filter = SC_SCALE_FILTER_TRILINEAR, \
.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, \
@ -122,7 +139,6 @@ struct scrcpy_options {
.render_expired_frames = false, \
.prefer_text = false, \
.window_borderless = false, \
.mipmaps = true, \
.stay_awake = false, \
.force_adb_forward = false, \
.disable_screensaver = false, \

View File

@ -4,11 +4,10 @@
#include <string.h>
#include <SDL2/SDL.h>
#include "events.h"
#include "icon.xpm"
#include "scrcpy.h"
#include "tiny_xpm.h"
#include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h"
#define DISPLAY_MARGINS 96
@ -191,23 +190,85 @@ screen_update_content_rect(struct screen *screen) {
}
}
static void
on_frame_available(struct video_buffer *vb, void *userdata) {
(void) vb;
struct screen *screen = userdata;
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);
}
}
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 void
resizer_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
resizer_on_frame_skipped(struct video_buffer *vb, void *userdata) {
// Count skipped frames from decoder or resizer the same way
on_frame_skipped(vb, userdata);
}
void
screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER;
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter) {
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);
}
static inline SDL_Texture *
create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer;
struct size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_PixelFormatEnum fmt = screen->use_swscale ? SDL_PIXELFORMAT_RGB24
: SDL_PIXELFORMAT_YV12;
SDL_Texture *texture = SDL_CreateTexture(renderer, fmt,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
if (!texture) {
return NULL;
}
if (screen->mipmaps) {
if (screen->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
assert(!screen->use_swscale);
struct sc_opengl *gl = &screen->gl;
SDL_GL_BindTexture(texture, NULL, NULL);
@ -223,12 +284,18 @@ 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;
}
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) {
uint8_t rotation, enum sc_scale_filter scale_filter) {
screen->frame_size = frame_size;
screen->rotation = rotation;
if (rotation) {
@ -271,7 +338,7 @@ screen_init_rendering(struct screen *screen, const char *window_title,
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError());
screen_destroy(screen);
SDL_DestroyWindow(screen->window);
return false;
}
@ -281,8 +348,9 @@ screen_init_rendering(struct screen *screen, const char *window_title,
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
// starts with "opengl"
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (screen->use_opengl) {
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
bool mipmaps = scale_filter == SC_SCALE_FILTER_TRILINEAR;
if (use_opengl) {
struct sc_opengl *gl = &screen->gl;
sc_opengl_init(gl);
@ -294,18 +362,58 @@ 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");
}
} else {
} else if (mipmaps) {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
screen->use_swscale = is_swscale(scale_filter);
if (screen->use_swscale) {
bool ok = video_buffer_init(&screen->resizer_vb, false);
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, 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;
}
static const struct video_buffer_callbacks cbs = {
.on_frame_available = resizer_on_frame_available,
.on_frame_skipped = resizer_on_frame_skipped,
};
video_buffer_set_consumer_callbacks(&screen->resizer.vb_out, &cbs,
screen);
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);
@ -319,7 +427,8 @@ screen_init_rendering(struct screen *screen, const char *window_title,
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
screen_destroy(screen);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
return false;
}
@ -340,15 +449,16 @@ 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);
}
if (screen->renderer) {
SDL_DestroyRenderer(screen->renderer);
}
if (screen->window) {
SDL_DestroyWindow(screen->window);
}
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
}
static void
@ -410,16 +520,17 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
// recreate the texture and resize the window if the frame size has changed
static bool
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
prepare_for_frame(struct screen *screen, struct size original_frame_size,
struct size frame_size) {
if (screen->frame_size.width != original_frame_size.width
|| screen->frame_size.height != original_frame_size.height) {
// frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture);
screen->frame_size = new_frame_size;
screen->frame_size = original_frame_size;
struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
get_rotated_size(original_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
screen_update_content_rect(screen);
@ -439,30 +550,44 @@ 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) {
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->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]);
if (screen->mipmaps) {
assert(screen->use_opengl);
SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture);
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);
}
}
}
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb) {
mutex_lock(vb->mutex);
const AVFrame *frame = video_buffer_consume_rendered_frame(vb);
struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
mutex_unlock(vb->mutex);
static bool
screen_update_frame(struct screen *screen) {
const AVFrame *frame;
struct size original_frame_size;
if (screen->use_swscale) {
frame = sc_resizer_consumer_take_frame(&screen->resizer,
&original_frame_size);
} else {
frame = video_buffer_consumer_take_frame(screen->vb);
original_frame_size.width = frame->width;
original_frame_size.height = frame->height;
}
struct size frame_size = {frame->width, frame->height};
fps_counter_add_rendered_frame(screen->fps_counter);
if (!prepare_for_frame(screen, original_frame_size, frame_size)) {
return false;
}
update_texture(screen, frame);
mutex_unlock(vb->mutex);
screen_render(screen, false);
return true;
@ -549,31 +674,52 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
content_size.height);
}
void
screen_handle_window_event(struct screen *screen,
const SDL_WindowEvent *event) {
switch (event->event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling fullscreen
// mode unexpectedly triggers the "restored" then "maximized"
// events, leaving the window in a weird state (maximized
// according to the events, but not maximized visually).
break;
bool
screen_handle_event(struct screen *screen, SDL_Event *event) {
switch (event->type) {
case EVENT_NEW_FRAME:
if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
screen_show_window(screen);
}
screen->maximized = false;
apply_pending_resize(screen);
break;
bool ok = screen_update_frame(screen);
if (!ok) {
LOGW("Frame update failed\n");
}
return true;
case SDL_WINDOWEVENT:
if (!screen->has_frame) {
// Do nothing
return true;
}
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling
// fullscreen mode unexpectedly triggers the "restored"
// then "maximized" events, leaving the window in a
// weird state (maximized according to the events, but
// not maximized visually).
break;
}
screen->maximized = false;
apply_pending_resize(screen);
break;
}
return true;
}
return false;
}
struct point

View File

@ -9,14 +9,19 @@
#include "coords.h"
#include "opengl.h"
#include "resizer.h"
#include "scrcpy.h"
#include "video_buffer.h"
struct video_buffer;
struct screen {
struct video_buffer *vb;
struct fps_counter *fps_counter;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
bool use_opengl;
struct sc_opengl gl;
struct size frame_size;
struct size content_size; // rotated frame_size
@ -33,46 +38,20 @@ struct screen {
bool has_frame;
bool fullscreen;
bool maximized;
bool no_window;
bool mipmaps;
};
#define SCREEN_INITIALIZER { \
.window = NULL, \
.renderer = NULL, \
.texture = NULL, \
.use_opengl = false, \
.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, \
}
enum sc_scale_filter scale_filter;
bool use_swscale;
// For swscale resizing
struct video_buffer resizer_vb;
struct sc_resizer resizer;
};
// initialize default values
void
screen_init(struct screen *screen);
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter);
// initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
@ -81,7 +60,7 @@ 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);
uint8_t rotation, enum sc_scale_filter scale_filter);
// show the window
void
@ -91,10 +70,6 @@ screen_show_window(struct screen *screen);
void
screen_destroy(struct screen *screen);
// resize if necessary and write the rendered frame into the texture
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb);
// render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
@ -118,9 +93,9 @@ screen_resize_to_pixel_perfect(struct screen *screen);
void
screen_set_rotation(struct screen *screen, unsigned rotation);
// react to window events
void
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
// react to SDL events
bool
screen_handle_event(struct screen *screen, SDL_Event *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels

View File

@ -5,12 +5,10 @@
#include <inttypes.h>
#include <libgen.h>
#include <stdio.h>
#include <SDL2/SDL_thread.h>
#include <SDL2/SDL_timer.h>
#include <SDL2/SDL_platform.h>
#include "adb.h"
#include "util/lock.h"
#include "util/log.h"
#include "util/net.h"
#include "util/str_util.h"
@ -33,7 +31,7 @@ get_server_path(void) {
#ifdef __WINDOWS__
char *server_path = utf8_from_wide_char(server_path_env);
#else
char *server_path = SDL_strdup(server_path_env);
char *server_path = strdup(server_path_env);
#endif
if (!server_path) {
LOGE("Could not allocate memory");
@ -45,7 +43,7 @@ get_server_path(void) {
#ifndef PORTABLE
LOGD("Using server: " DEFAULT_SERVER_PATH);
char *server_path = SDL_strdup(DEFAULT_SERVER_PATH);
char *server_path = strdup(DEFAULT_SERVER_PATH);
if (!server_path) {
LOGE("Could not allocate memory");
return NULL;
@ -67,11 +65,11 @@ get_server_path(void) {
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
char *server_path = SDL_malloc(len);
char *server_path = malloc(len);
if (!server_path) {
LOGE("Could not alloc server path string, "
"using " SERVER_FILENAME " from current directory");
SDL_free(executable_path);
free(executable_path);
return SERVER_FILENAME;
}
@ -80,7 +78,7 @@ get_server_path(void) {
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
// the final null byte has been copied with SERVER_FILENAME
SDL_free(executable_path);
free(executable_path);
LOGD("Using server (portable): %s", server_path);
return server_path;
@ -95,11 +93,11 @@ push_server(const char *serial) {
}
if (!is_regular_file(server_path)) {
LOGE("'%s' does not exist or is not a regular file\n", server_path);
SDL_free(server_path);
free(server_path);
return false;
}
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
SDL_free(server_path);
free(server_path);
return process_check_success(process, "adb push", true);
}
@ -357,18 +355,17 @@ bool
server_init(struct server *server) {
server->serial = NULL;
server->process = PROCESS_NONE;
server->wait_server_thread = NULL;
atomic_flag_clear_explicit(&server->server_socket_closed,
memory_order_relaxed);
server->mutex = SDL_CreateMutex();
if (!server->mutex) {
bool ok = sc_mutex_init(&server->mutex);
if (!ok) {
return false;
}
server->process_terminated_cond = SDL_CreateCond();
if (!server->process_terminated_cond) {
SDL_DestroyMutex(server->mutex);
ok = sc_cond_init(&server->process_terminated_cond);
if (!ok) {
sc_mutex_destroy(&server->mutex);
return false;
}
@ -378,8 +375,6 @@ server_init(struct server *server) {
server->video_socket = INVALID_SOCKET;
server->control_socket = INVALID_SOCKET;
server->port_range.first = 0;
server->port_range.last = 0;
server->local_port = 0;
server->tunnel_enabled = false;
@ -393,10 +388,10 @@ run_wait_server(void *data) {
struct server *server = data;
process_wait(server->process, false); // ignore exit code
mutex_lock(server->mutex);
sc_mutex_lock(&server->mutex);
server->process_terminated = true;
cond_signal(server->process_terminated_cond);
mutex_unlock(server->mutex);
sc_cond_signal(&server->process_terminated_cond);
sc_mutex_unlock(&server->mutex);
// no need for synchronization, server_socket is initialized before this
// thread was created
@ -413,10 +408,8 @@ run_wait_server(void *data) {
bool
server_start(struct server *server, const char *serial,
const struct server_params *params) {
server->port_range = params->port_range;
if (serial) {
server->serial = SDL_strdup(serial);
server->serial = strdup(serial);
if (!server->serial) {
return false;
}
@ -443,9 +436,9 @@ server_start(struct server *server, const char *serial,
// things simple and multiplatform, just spawn a new thread waiting for the
// server process and calling shutdown()/close() on the server socket if
// necessary to wake up any accept() blocking call.
server->wait_server_thread =
SDL_CreateThread(run_wait_server, "wait-server", server);
if (!server->wait_server_thread) {
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
"wait-server", server);
if (!ok) {
process_terminate(server->process);
process_wait(server->process, true); // ignore exit code
goto error2;
@ -466,7 +459,7 @@ error2:
}
disable_tunnel(server);
error1:
SDL_free(server->serial);
free(server->serial);
return false;
}
@ -535,33 +528,33 @@ server_stop(struct server *server) {
}
// Give some delay for the server to terminate properly
mutex_lock(server->mutex);
int r = 0;
sc_mutex_lock(&server->mutex);
bool signaled = false;
if (!server->process_terminated) {
#define WATCHDOG_DELAY_MS 1000
r = cond_wait_timeout(server->process_terminated_cond,
server->mutex,
WATCHDOG_DELAY_MS);
signaled = sc_cond_timedwait(&server->process_terminated_cond,
&server->mutex,
WATCHDOG_DELAY_MS);
}
mutex_unlock(server->mutex);
sc_mutex_unlock(&server->mutex);
// After this delay, kill the server if it's not dead already.
// On some devices, closing the sockets is not sufficient to wake up the
// blocking calls while the device is asleep.
if (r == SDL_MUTEX_TIMEDOUT) {
if (!signaled) {
// The process is terminated, but not reaped (closed) yet, so its PID
// is still valid.
LOGW("Killing the server...");
process_terminate(server->process);
}
SDL_WaitThread(server->wait_server_thread, NULL);
sc_thread_join(&server->wait_server_thread, NULL);
process_close(server->process);
}
void
server_destroy(struct server *server) {
SDL_free(server->serial);
SDL_DestroyCond(server->process_terminated_cond);
SDL_DestroyMutex(server->mutex);
free(server->serial);
sc_cond_destroy(&server->process_terminated_cond);
sc_mutex_destroy(&server->mutex);
}

View File

@ -6,27 +6,26 @@
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_thread.h>
#include "adb.h"
#include "scrcpy.h"
#include "util/log.h"
#include "util/net.h"
#include "util/thread.h"
struct server {
char *serial;
process_t process;
SDL_Thread *wait_server_thread;
sc_thread wait_server_thread;
atomic_flag server_socket_closed;
SDL_mutex *mutex;
SDL_cond *process_terminated_cond;
sc_mutex mutex;
sc_cond process_terminated_cond;
bool process_terminated;
socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket;
socket_t control_socket;
struct sc_port_range port_range;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"

View File

@ -4,8 +4,6 @@
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "decoder.h"
@ -279,8 +277,8 @@ bool
stream_start(struct stream *stream) {
LOGD("Starting stream thread");
stream->thread = SDL_CreateThread(run_stream, "stream", stream);
if (!stream->thread) {
bool ok = sc_thread_create(&stream->thread, run_stream, "stream", stream);
if (!ok) {
LOGC("Could not start stream thread");
return false;
}
@ -296,5 +294,5 @@ stream_stop(struct stream *stream) {
void
stream_join(struct stream *stream) {
SDL_WaitThread(stream->thread, NULL);
sc_thread_join(&stream->thread, NULL);
}

View File

@ -7,15 +7,15 @@
#include <stdint.h>
#include <libavformat/avformat.h>
#include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_thread.h>
#include "util/net.h"
#include "util/thread.h"
struct video_buffer;
struct stream {
socket_t socket;
SDL_Thread *thread;
sc_thread thread;
struct decoder *decoder;
struct recorder *recorder;
AVCodecContext *codec_ctx;

View File

@ -156,7 +156,7 @@ get_executable_path(void) {
return NULL;
}
buf[len] = '\0';
return SDL_strdup(buf);
return strdup(buf);
#else
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform

View File

@ -41,7 +41,7 @@ process_execute(const char *const argv[], HANDLE *handle) {
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si,
&pi)) {
SDL_free(wide);
free(wide);
*handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return PROCESS_ERROR_MISSING_BINARY;
@ -49,7 +49,7 @@ process_execute(const char *const argv[], HANDLE *handle) {
return PROCESS_ERROR_GENERIC;
}
SDL_free(wide);
free(wide);
*handle = pi.hProcess;
return PROCESS_SUCCESS;
}
@ -105,7 +105,7 @@ is_regular_file(const char *path) {
struct _stat path_stat;
int r = _wstat(wide_path, &path_stat);
SDL_free(wide_path);
free(wide_path);
if (r) {
perror("stat");

View File

@ -1,75 +0,0 @@
#ifndef LOCK_H
#define LOCK_H
#include "common.h"
#include <stdint.h>
#include <SDL2/SDL_mutex.h>
#include "log.h"
static inline void
mutex_lock(SDL_mutex *mutex) {
int r = SDL_LockMutex(mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline void
mutex_unlock(SDL_mutex *mutex) {
int r = SDL_UnlockMutex(mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not unlock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline void
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
int r = SDL_CondWait(cond, mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not wait on condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline int
cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) {
int r = SDL_CondWaitTimeout(cond, mutex, ms);
#ifndef NDEBUG
if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
abort();
}
#endif
return r;
}
static inline void
cond_signal(SDL_cond *cond) {
int r = SDL_CondSignal(cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not signal a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
#endif

View File

@ -70,7 +70,7 @@ search_executable(const char *file);
#endif
// return the absolute path of the executable (the scrcpy binary)
// may be NULL on error; to be freed by SDL_free
// may be NULL on error; to be freed by free()
char *
get_executable_path(void);

View File

@ -10,8 +10,6 @@
# include <tchar.h>
#endif
#include <SDL2/SDL_stdinc.h>
size_t
xstrncpy(char *dest, const char *src, size_t n) {
size_t i;
@ -49,7 +47,7 @@ truncated:
char *
strquote(const char *src) {
size_t len = strlen(src);
char *quoted = SDL_malloc(len + 3);
char *quoted = malloc(len + 3);
if (!quoted) {
return NULL;
}
@ -167,7 +165,7 @@ utf8_to_wide_char(const char *utf8) {
return NULL;
}
wchar_t *wide = SDL_malloc(len * sizeof(wchar_t));
wchar_t *wide = malloc(len * sizeof(wchar_t));
if (!wide) {
return NULL;
}
@ -183,7 +181,7 @@ utf8_from_wide_char(const wchar_t *ws) {
return NULL;
}
char *utf8 = SDL_malloc(len);
char *utf8 = malloc(len);
if (!utf8) {
return NULL;
}

160
app/src/util/thread.c Normal file
View File

@ -0,0 +1,160 @@
#include "thread.h"
#include <assert.h>
#include <SDL2/SDL_thread.h>
#include "log.h"
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata) {
SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata);
if (!sdl_thread) {
return false;
}
thread->thread = sdl_thread;
return true;
}
void
sc_thread_join(sc_thread *thread, int *status) {
SDL_WaitThread(thread->thread, status);
}
bool
sc_mutex_init(sc_mutex *mutex) {
SDL_mutex *sdl_mutex = SDL_CreateMutex();
if (!sdl_mutex) {
return false;
}
mutex->mutex = sdl_mutex;
#ifndef NDEBUG
mutex->locker = 0;
#endif
return true;
}
void
sc_mutex_destroy(sc_mutex *mutex) {
SDL_DestroyMutex(mutex->mutex);
}
void
sc_mutex_lock(sc_mutex *mutex) {
// SDL mutexes are recursive, but we don't want to use recursive mutexes
assert(!sc_mutex_held(mutex));
int r = SDL_LockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#else
(void) r;
#endif
}
void
sc_mutex_unlock(sc_mutex *mutex) {
#ifndef NDEBUG
assert(sc_mutex_held(mutex));
mutex->locker = 0;
#endif
int r = SDL_UnlockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
sc_thread_id
sc_thread_get_id(void) {
return SDL_ThreadID();
}
#ifndef NDEBUG
bool
sc_mutex_held(struct sc_mutex *mutex) {
return mutex->locker == sc_thread_get_id();
}
#endif
bool
sc_cond_init(sc_cond *cond) {
SDL_cond *sdl_cond = SDL_CreateCond();
if (!sdl_cond) {
return false;
}
cond->cond = sdl_cond;
return true;
}
void
sc_cond_destroy(sc_cond *cond) {
SDL_DestroyCond(cond->cond);
}
void
sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
int r = SDL_CondWait(cond->cond, mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not wait on condition: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#else
(void) r;
#endif
}
bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms) {
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
#ifndef NDEBUG
if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#endif
assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
return r == 0;
}
void
sc_cond_signal(sc_cond *cond) {
int r = SDL_CondSignal(cond->cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not signal a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
void
sc_cond_broadcast(sc_cond *cond) {
int r = SDL_CondBroadcast(cond->cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not broadcast a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}

81
app/src/util/thread.h Normal file
View File

@ -0,0 +1,81 @@
#ifndef SC_THREAD_H
#define SC_THREAD_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
/* Forward declarations */
typedef struct SDL_Thread SDL_Thread;
typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond;
typedef int sc_thread_fn(void *);
typedef unsigned int sc_thread_id;
typedef struct sc_thread {
SDL_Thread *thread;
} sc_thread;
typedef struct sc_mutex {
SDL_mutex *mutex;
#ifndef NDEBUG
sc_thread_id locker;
#endif
} sc_mutex;
typedef struct sc_cond {
SDL_cond *cond;
} sc_cond;
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata);
void
sc_thread_join(sc_thread *thread, int *status);
bool
sc_mutex_init(sc_mutex *mutex);
void
sc_mutex_destroy(sc_mutex *mutex);
void
sc_mutex_lock(sc_mutex *mutex);
void
sc_mutex_unlock(sc_mutex *mutex);
sc_thread_id
sc_thread_get_id(void);
#ifndef NDEBUG
bool
sc_mutex_held(struct sc_mutex *mutex);
# define sc_mutex_assert(mutex) assert(sc_mutex_held(mutex))
#else
# define sc_mutex_assert(mutex)
#endif
bool
sc_cond_init(sc_cond *cond);
void
sc_cond_destroy(sc_cond *cond);
void
sc_cond_wait(sc_cond *cond, sc_mutex *mutex);
// return true on signaled, false on timeout
bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms);
void
sc_cond_signal(sc_cond *cond);
void
sc_cond_broadcast(sc_cond *cond);
#endif

View File

@ -1,112 +1,139 @@
#include "video_buffer.h"
#include <assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "util/lock.h"
#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;
if (!(vb->decoding_frame = av_frame_alloc())) {
video_buffer_init(struct video_buffer *vb, bool wait_consumer) {
vb->pending_frame = av_frame_alloc();
if (!vb->pending_frame) {
goto error_0;
}
if (!(vb->rendering_frame = av_frame_alloc())) {
vb->consumer_frame = av_frame_alloc();
if (!vb->consumer_frame) {
goto error_1;
}
if (!(vb->mutex = SDL_CreateMutex())) {
bool ok = sc_mutex_init(&vb->mutex);
if (!ok) {
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);
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
vb->rendering_frame_consumed = true;
// 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_2:
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->consumer_frame);
error_1:
av_frame_free(&vb->decoding_frame);
av_frame_free(&vb->pending_frame);
error_0:
return false;
}
void
video_buffer_destroy(struct video_buffer *vb) {
if (vb->render_expired_frames) {
SDL_DestroyCond(vb->rendering_frame_consumed_cond);
if (vb->wait_consumer) {
sc_cond_destroy(&vb->pending_frame_consumed_cond);
}
SDL_DestroyMutex(vb->mutex);
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->decoding_frame);
sc_mutex_destroy(&vb->mutex);
av_frame_free(&vb->consumer_frame);
av_frame_free(&vb->pending_frame);
}
static void
video_buffer_swap_frames(struct video_buffer *vb) {
AVFrame *tmp = vb->decoding_frame;
vb->decoding_frame = vb->rendering_frame;
vb->rendering_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) {
mutex_lock(vb->mutex);
if (vb->render_expired_frames) {
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
while (!vb->rendering_frame_consumed && !vb->interrupted) {
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
while (!vb->pending_frame_consumed && !vb->interrupted) {
sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex);
}
} else if (!vb->rendering_frame_consumed) {
fps_counter_add_skipped_frame(vb->fps_counter);
}
video_buffer_swap_frames(vb);
av_frame_unref(vb->pending_frame);
swap_frames(pframe, &vb->pending_frame);
*previous_frame_skipped = !vb->rendering_frame_consumed;
vb->rendering_frame_consumed = false;
bool skipped = !vb->pending_frame_consumed;
vb->pending_frame_consumed = false;
mutex_unlock(vb->mutex);
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_consume_rendered_frame(struct video_buffer *vb) {
assert(!vb->rendering_frame_consumed);
vb->rendering_frame_consumed = true;
fps_counter_add_rendered_frame(vb->fps_counter);
if (vb->render_expired_frames) {
video_buffer_consumer_take_frame(struct video_buffer *vb) {
sc_mutex_lock(&vb->mutex);
assert(!vb->pending_frame_consumed);
vb->pending_frame_consumed = true;
swap_frames(&vb->consumer_frame, &vb->pending_frame);
av_frame_unref(vb->pending_frame);
if (vb->wait_consumer) {
// unblock video_buffer_offer_decoded_frame()
cond_signal(vb->rendering_frame_consumed_cond);
sc_cond_signal(&vb->pending_frame_consumed_cond);
}
return vb->rendering_frame;
sc_mutex_unlock(&vb->mutex);
// 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) {
mutex_lock(vb->mutex);
if (vb->wait_consumer) {
sc_mutex_lock(&vb->mutex);
vb->interrupted = true;
mutex_unlock(vb->mutex);
sc_mutex_unlock(&vb->mutex);
// wake up blocking wait
cond_signal(vb->rendering_frame_consumed_cond);
sc_cond_signal(&vb->pending_frame_consumed_cond);
}
}

View File

@ -4,44 +4,76 @@
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include "fps_counter.h"
#include "util/thread.h"
// forward declarations
typedef struct AVFrame AVFrame;
/**
* There are 3 frames in memory:
* - 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 producer generates a frame into the producer_frame (it may takes time).
*
* Once the frame is produced, it calls video_buffer_producer_offer_frame(),
* which swaps the producer and pending frames.
*
* 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 *rendering_frame;
SDL_mutex *mutex;
bool render_expired_frames;
AVFrame *pending_frame;
AVFrame *consumer_frame;
sc_mutex mutex;
bool wait_consumer; // never overwrite a pending frame if it is not consumed
bool interrupted;
SDL_cond *rendering_frame_consumed_cond;
bool rendering_frame_consumed;
struct fps_counter *fps_counter;
sc_cond pending_frame_consumed_cond;
bool pending_frame_consumed;
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
// this function locks frames->mutex during its execution
// 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
// MUST be called with frames->mutex locked!!!
// the caller is expected to render the returned frame to some texture before
// unlocking frames->mutex
// 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
const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb);
video_buffer_consumer_take_frame(struct video_buffer *vb);
// wake up and avoid any blocking call
void

View File

@ -4,7 +4,6 @@
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <SDL2/SDL.h>
#include "util/str_util.h"
@ -138,7 +137,7 @@ static void test_strquote(void) {
// add '"' at the beginning and the end
assert(!strcmp("\"abcde\"", out));
SDL_free(out);
free(out);
}
static void test_utf8_truncate(void) {

View File

@ -4,6 +4,7 @@ project('scrcpy', 'c',
default_options: [
'c_std=c11',
'warning_level=2',
'b_ndebug=if-release',
])
if get_option('compile_app')