Compare commits
15 Commits
linux_maco
...
swscale
Author | SHA1 | Date | |
---|---|---|---|
ba09254b38 | |||
03cc0cf543 | |||
f6570a7a41 | |||
87e3ad7756 | |||
c6de5b0a15 | |||
0b4b4609dc | |||
edf710af3c | |||
46bf4d55a7 | |||
017036ddab | |||
a9582b1d43 | |||
fa3e84b700 | |||
1521de9051 | |||
eabaabdb78 | |||
8b48003074 | |||
74bd25a0ed |
@ -2,6 +2,7 @@ src = [
|
|||||||
'src/main.c',
|
'src/main.c',
|
||||||
'src/adb.c',
|
'src/adb.c',
|
||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
|
'src/compat.c',
|
||||||
'src/control_msg.c',
|
'src/control_msg.c',
|
||||||
'src/controller.c',
|
'src/controller.c',
|
||||||
'src/decoder.c',
|
'src/decoder.c',
|
||||||
@ -10,6 +11,7 @@ src = [
|
|||||||
'src/event_converter.c',
|
'src/event_converter.c',
|
||||||
'src/file_handler.c',
|
'src/file_handler.c',
|
||||||
'src/fps_counter.c',
|
'src/fps_counter.c',
|
||||||
|
'src/frame_texture.c',
|
||||||
'src/input_manager.c',
|
'src/input_manager.c',
|
||||||
'src/opengl.c',
|
'src/opengl.c',
|
||||||
'src/receiver.c',
|
'src/receiver.c',
|
||||||
@ -22,7 +24,8 @@ src = [
|
|||||||
'src/video_buffer.c',
|
'src/video_buffer.c',
|
||||||
'src/util/net.c',
|
'src/util/net.c',
|
||||||
'src/util/process.c',
|
'src/util/process.c',
|
||||||
'src/util/str_util.c'
|
'src/util/str_util.c',
|
||||||
|
'src/util/thread.c',
|
||||||
]
|
]
|
||||||
|
|
||||||
if host_machine.system() == 'windows'
|
if host_machine.system() == 'windows'
|
||||||
@ -31,6 +34,10 @@ else
|
|||||||
src += [ 'src/sys/unix/process.c' ]
|
src += [ 'src/sys/unix/process.c' ]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
check_functions = [
|
||||||
|
'strdup'
|
||||||
|
]
|
||||||
|
|
||||||
cc = meson.get_compiler('c')
|
cc = meson.get_compiler('c')
|
||||||
|
|
||||||
if not get_option('crossbuild_windows')
|
if not get_option('crossbuild_windows')
|
||||||
@ -40,6 +47,7 @@ if not get_option('crossbuild_windows')
|
|||||||
dependency('libavformat'),
|
dependency('libavformat'),
|
||||||
dependency('libavcodec'),
|
dependency('libavcodec'),
|
||||||
dependency('libavutil'),
|
dependency('libavutil'),
|
||||||
|
dependency('libswscale'),
|
||||||
dependency('sdl2'),
|
dependency('sdl2'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -86,6 +94,13 @@ endif
|
|||||||
|
|
||||||
conf = configuration_data()
|
conf = configuration_data()
|
||||||
|
|
||||||
|
foreach f : check_functions
|
||||||
|
if cc.has_function(f)
|
||||||
|
define = 'HAVE_' + f.underscorify().to_upper()
|
||||||
|
conf.set(define, true)
|
||||||
|
endif
|
||||||
|
endforeach
|
||||||
|
|
||||||
# expose the build type
|
# expose the build type
|
||||||
conf.set('NDEBUG', get_option('buildtype') != 'debug')
|
conf.set('NDEBUG', get_option('buildtype') != 'debug')
|
||||||
|
|
||||||
|
@ -167,6 +167,13 @@ Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each incre
|
|||||||
.BI "\-s, \-\-serial " number
|
.BI "\-s, \-\-serial " number
|
||||||
The device serial number. Mandatory only if several devices are connected to adb.
|
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
|
.TP
|
||||||
.BI "\-\-shortcut\-mod " key[+...]][,...]
|
.BI "\-\-shortcut\-mod " key[+...]][,...]
|
||||||
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
|
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
|
||||||
|
@ -173,7 +173,7 @@ adb_push(const char *serial, const char *local, const char *remote) {
|
|||||||
}
|
}
|
||||||
remote = strquote(remote);
|
remote = strquote(remote);
|
||||||
if (!remote) {
|
if (!remote) {
|
||||||
SDL_free((void *) local);
|
free((void *) local);
|
||||||
return PROCESS_NONE;
|
return PROCESS_NONE;
|
||||||
}
|
}
|
||||||
#endif
|
#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));
|
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
SDL_free((void *) remote);
|
free((void *) remote);
|
||||||
SDL_free((void *) local);
|
free((void *) local);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return proc;
|
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));
|
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
SDL_free((void *) local);
|
free((void *) local);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return proc;
|
return proc;
|
||||||
|
@ -103,11 +103,6 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" --no-key-repeat\n"
|
" --no-key-repeat\n"
|
||||||
" Do not forward repeated key events when a key is held down.\n"
|
" Do not forward repeated key events when a key is held down.\n"
|
||||||
"\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"
|
" -p, --port port[:port]\n"
|
||||||
" Set the TCP port (range) used by the client to listen.\n"
|
" Set the TCP port (range) used by the client to listen.\n"
|
||||||
" Default is %d:%d.\n"
|
" Default is %d:%d.\n"
|
||||||
@ -154,6 +149,11 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" The device serial number. Mandatory only if several devices\n"
|
" The device serial number. Mandatory only if several devices\n"
|
||||||
" are connected to adb.\n"
|
" are connected to adb.\n"
|
||||||
"\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"
|
" --shortcut-mod key[+...]][,...]\n"
|
||||||
" Specify the modifiers to use for scrcpy shortcuts.\n"
|
" Specify the modifiers to use for scrcpy shortcuts.\n"
|
||||||
" Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\",\n"
|
" Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\",\n"
|
||||||
@ -641,6 +641,62 @@ guess_record_format(const char *filename) {
|
|||||||
return 0;
|
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_RENDER_EXPIRED_FRAMES 1000
|
||||||
#define OPT_WINDOW_TITLE 1001
|
#define OPT_WINDOW_TITLE 1001
|
||||||
#define OPT_PUSH_TARGET 1002
|
#define OPT_PUSH_TARGET 1002
|
||||||
@ -667,6 +723,7 @@ guess_record_format(const char *filename) {
|
|||||||
#define OPT_FORWARD_ALL_CLICKS 1023
|
#define OPT_FORWARD_ALL_CLICKS 1023
|
||||||
#define OPT_LEGACY_PASTE 1024
|
#define OPT_LEGACY_PASTE 1024
|
||||||
#define OPT_ENCODER_NAME 1025
|
#define OPT_ENCODER_NAME 1025
|
||||||
|
#define OPT_SCALE_FILTER 1026
|
||||||
|
|
||||||
bool
|
bool
|
||||||
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
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,
|
{"render-expired-frames", no_argument, NULL,
|
||||||
OPT_RENDER_EXPIRED_FRAMES},
|
OPT_RENDER_EXPIRED_FRAMES},
|
||||||
{"rotation", required_argument, NULL, OPT_ROTATION},
|
{"rotation", required_argument, NULL, OPT_ROTATION},
|
||||||
|
{"scale-filter", required_argument, NULL, OPT_SCALE_FILTER},
|
||||||
{"serial", required_argument, NULL, 's'},
|
{"serial", required_argument, NULL, 's'},
|
||||||
{"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD},
|
{"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD},
|
||||||
{"show-touches", no_argument, NULL, 't'},
|
{"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;
|
opts->render_driver = optarg;
|
||||||
break;
|
break;
|
||||||
case OPT_NO_MIPMAPS:
|
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;
|
break;
|
||||||
case OPT_NO_KEY_REPEAT:
|
case OPT_NO_KEY_REPEAT:
|
||||||
opts->forward_key_repeat = false;
|
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:
|
case OPT_LEGACY_PASTE:
|
||||||
opts->legacy_paste = true;
|
opts->legacy_paste = true;
|
||||||
break;
|
break;
|
||||||
|
case OPT_SCALE_FILTER:
|
||||||
|
if (!parse_scale_filter(optarg, &opts->scale_filter)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
14
app/src/compat.c
Normal file
14
app/src/compat.c
Normal 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
|
@ -56,4 +56,8 @@
|
|||||||
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_STRDUP
|
||||||
|
char *strdup(const char *s);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "util/buffer_util.h"
|
#include "util/buffer_util.h"
|
||||||
@ -93,10 +94,10 @@ void
|
|||||||
control_msg_destroy(struct control_msg *msg) {
|
control_msg_destroy(struct control_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case CONTROL_MSG_TYPE_INJECT_TEXT:
|
case CONTROL_MSG_TYPE_INJECT_TEXT:
|
||||||
SDL_free(msg->inject_text.text);
|
free(msg->inject_text.text);
|
||||||
break;
|
break;
|
||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||||
SDL_free(msg->set_clipboard.text);
|
free(msg->set_clipboard.text);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@ -50,7 +50,7 @@ struct control_msg {
|
|||||||
enum android_metastate metastate;
|
enum android_metastate metastate;
|
||||||
} inject_keycode;
|
} inject_keycode;
|
||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by SDL_free()
|
char *text; // owned, to be freed by free()
|
||||||
} inject_text;
|
} inject_text;
|
||||||
struct {
|
struct {
|
||||||
enum android_motionevent_action action;
|
enum android_motionevent_action action;
|
||||||
@ -65,7 +65,7 @@ struct control_msg {
|
|||||||
int32_t vscroll;
|
int32_t vscroll;
|
||||||
} inject_scroll_event;
|
} inject_scroll_event;
|
||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by SDL_free()
|
char *text; // owned, to be freed by free()
|
||||||
bool paste;
|
bool paste;
|
||||||
} set_clipboard;
|
} set_clipboard;
|
||||||
struct {
|
struct {
|
||||||
|
@ -2,25 +2,27 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
controller_init(struct controller *controller, socket_t control_socket) {
|
controller_init(struct controller *controller, socket_t control_socket) {
|
||||||
cbuf_init(&controller->queue);
|
cbuf_init(&controller->queue);
|
||||||
|
|
||||||
if (!receiver_init(&controller->receiver, control_socket)) {
|
bool ok = receiver_init(&controller->receiver, control_socket);
|
||||||
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(controller->mutex = SDL_CreateMutex())) {
|
ok = sc_mutex_init(&controller->mutex);
|
||||||
|
if (!ok) {
|
||||||
receiver_destroy(&controller->receiver);
|
receiver_destroy(&controller->receiver);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(controller->msg_cond = SDL_CreateCond())) {
|
ok = sc_cond_init(&controller->msg_cond);
|
||||||
|
if (!ok) {
|
||||||
receiver_destroy(&controller->receiver);
|
receiver_destroy(&controller->receiver);
|
||||||
SDL_DestroyMutex(controller->mutex);
|
sc_mutex_destroy(&controller->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,8 +34,8 @@ controller_init(struct controller *controller, socket_t control_socket) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
controller_destroy(struct controller *controller) {
|
controller_destroy(struct controller *controller) {
|
||||||
SDL_DestroyCond(controller->msg_cond);
|
sc_cond_destroy(&controller->msg_cond);
|
||||||
SDL_DestroyMutex(controller->mutex);
|
sc_mutex_destroy(&controller->mutex);
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
while (cbuf_take(&controller->queue, &msg)) {
|
while (cbuf_take(&controller->queue, &msg)) {
|
||||||
@ -46,13 +48,13 @@ controller_destroy(struct controller *controller) {
|
|||||||
bool
|
bool
|
||||||
controller_push_msg(struct controller *controller,
|
controller_push_msg(struct controller *controller,
|
||||||
const struct control_msg *msg) {
|
const struct control_msg *msg) {
|
||||||
mutex_lock(controller->mutex);
|
sc_mutex_lock(&controller->mutex);
|
||||||
bool was_empty = cbuf_is_empty(&controller->queue);
|
bool was_empty = cbuf_is_empty(&controller->queue);
|
||||||
bool res = cbuf_push(&controller->queue, *msg);
|
bool res = cbuf_push(&controller->queue, *msg);
|
||||||
if (was_empty) {
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,20 +75,20 @@ run_controller(void *data) {
|
|||||||
struct controller *controller = data;
|
struct controller *controller = data;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
mutex_lock(controller->mutex);
|
sc_mutex_lock(&controller->mutex);
|
||||||
while (!controller->stopped && cbuf_is_empty(&controller->queue)) {
|
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) {
|
if (controller->stopped) {
|
||||||
// stop immediately, do not process further msgs
|
// stop immediately, do not process further msgs
|
||||||
mutex_unlock(controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
bool non_empty = cbuf_take(&controller->queue, &msg);
|
bool non_empty = cbuf_take(&controller->queue, &msg);
|
||||||
assert(non_empty);
|
assert(non_empty);
|
||||||
(void) non_empty;
|
(void) non_empty;
|
||||||
mutex_unlock(controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
|
|
||||||
bool ok = process_msg(controller, &msg);
|
bool ok = process_msg(controller, &msg);
|
||||||
control_msg_destroy(&msg);
|
control_msg_destroy(&msg);
|
||||||
@ -102,16 +104,16 @@ bool
|
|||||||
controller_start(struct controller *controller) {
|
controller_start(struct controller *controller) {
|
||||||
LOGD("Starting controller thread");
|
LOGD("Starting controller thread");
|
||||||
|
|
||||||
controller->thread = SDL_CreateThread(run_controller, "controller",
|
bool ok = sc_thread_create(&controller->thread, run_controller,
|
||||||
controller);
|
"controller", controller);
|
||||||
if (!controller->thread) {
|
if (!ok) {
|
||||||
LOGC("Could not start controller thread");
|
LOGC("Could not start controller thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!receiver_start(&controller->receiver)) {
|
if (!receiver_start(&controller->receiver)) {
|
||||||
controller_stop(controller);
|
controller_stop(controller);
|
||||||
SDL_WaitThread(controller->thread, NULL);
|
sc_thread_join(&controller->thread, NULL);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,14 +122,14 @@ controller_start(struct controller *controller) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
controller_stop(struct controller *controller) {
|
controller_stop(struct controller *controller) {
|
||||||
mutex_lock(controller->mutex);
|
sc_mutex_lock(&controller->mutex);
|
||||||
controller->stopped = true;
|
controller->stopped = true;
|
||||||
cond_signal(controller->msg_cond);
|
sc_cond_signal(&controller->msg_cond);
|
||||||
mutex_unlock(controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
controller_join(struct controller *controller) {
|
controller_join(struct controller *controller) {
|
||||||
SDL_WaitThread(controller->thread, NULL);
|
sc_thread_join(&controller->thread, NULL);
|
||||||
receiver_join(&controller->receiver);
|
receiver_join(&controller->receiver);
|
||||||
}
|
}
|
||||||
|
@ -4,21 +4,20 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
|
|
||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
#include "receiver.h"
|
#include "receiver.h"
|
||||||
#include "util/cbuf.h"
|
#include "util/cbuf.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct control_msg_queue CBUF(struct control_msg, 64);
|
struct control_msg_queue CBUF(struct control_msg, 64);
|
||||||
|
|
||||||
struct controller {
|
struct controller {
|
||||||
socket_t control_socket;
|
socket_t control_socket;
|
||||||
SDL_Thread *thread;
|
sc_thread thread;
|
||||||
SDL_mutex *mutex;
|
sc_mutex mutex;
|
||||||
SDL_cond *msg_cond;
|
sc_cond msg_cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
struct control_msg_queue queue;
|
struct control_msg_queue queue;
|
||||||
struct receiver receiver;
|
struct receiver receiver;
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#include <libavutil/time.h>
|
#include <libavutil/time.h>
|
||||||
#include <SDL2/SDL_events.h>
|
#include <SDL2/SDL_events.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "device_msg.h"
|
#include "device_msg.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "util/buffer_util.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) {
|
if (clipboard_len > len - 5) {
|
||||||
return 0; // not available
|
return 0; // not available
|
||||||
}
|
}
|
||||||
char *text = SDL_malloc(clipboard_len + 1);
|
char *text = malloc(clipboard_len + 1);
|
||||||
if (!text) {
|
if (!text) {
|
||||||
LOGW("Could not allocate text for clipboard");
|
LOGW("Could not allocate text for clipboard");
|
||||||
return -1;
|
return -1;
|
||||||
@ -42,6 +43,6 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
|||||||
void
|
void
|
||||||
device_msg_destroy(struct device_msg *msg) {
|
device_msg_destroy(struct device_msg *msg) {
|
||||||
if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
|
if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
|
||||||
SDL_free(msg->clipboard.text);
|
free(msg->clipboard.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ struct device_msg {
|
|||||||
enum device_msg_type type;
|
enum device_msg_type type;
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by SDL_free()
|
char *text; // owned, to be freed by free()
|
||||||
} clipboard;
|
} clipboard;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -4,14 +4,13 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "adb.h"
|
#include "adb.h"
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define DEFAULT_PUSH_TARGET "/sdcard/"
|
#define DEFAULT_PUSH_TARGET "/sdcard/"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
file_handler_request_destroy(struct file_handler_request *req) {
|
file_handler_request_destroy(struct file_handler_request *req) {
|
||||||
SDL_free(req->file);
|
free(req->file);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -20,21 +19,23 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
|
|||||||
|
|
||||||
cbuf_init(&file_handler->queue);
|
cbuf_init(&file_handler->queue);
|
||||||
|
|
||||||
if (!(file_handler->mutex = SDL_CreateMutex())) {
|
bool ok = sc_mutex_init(&file_handler->mutex);
|
||||||
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(file_handler->event_cond = SDL_CreateCond())) {
|
ok = sc_cond_init(&file_handler->event_cond);
|
||||||
SDL_DestroyMutex(file_handler->mutex);
|
if (!ok) {
|
||||||
|
sc_mutex_destroy(&file_handler->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serial) {
|
if (serial) {
|
||||||
file_handler->serial = SDL_strdup(serial);
|
file_handler->serial = strdup(serial);
|
||||||
if (!file_handler->serial) {
|
if (!file_handler->serial) {
|
||||||
LOGW("Could not strdup serial");
|
LOGW("Could not strdup serial");
|
||||||
SDL_DestroyCond(file_handler->event_cond);
|
sc_cond_destroy(&file_handler->event_cond);
|
||||||
SDL_DestroyMutex(file_handler->mutex);
|
sc_mutex_destroy(&file_handler->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -54,9 +55,9 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
|
|||||||
|
|
||||||
void
|
void
|
||||||
file_handler_destroy(struct file_handler *file_handler) {
|
file_handler_destroy(struct file_handler *file_handler) {
|
||||||
SDL_DestroyCond(file_handler->event_cond);
|
sc_cond_destroy(&file_handler->event_cond);
|
||||||
SDL_DestroyMutex(file_handler->mutex);
|
sc_mutex_destroy(&file_handler->mutex);
|
||||||
SDL_free(file_handler->serial);
|
free(file_handler->serial);
|
||||||
|
|
||||||
struct file_handler_request req;
|
struct file_handler_request req;
|
||||||
while (cbuf_take(&file_handler->queue, &req)) {
|
while (cbuf_take(&file_handler->queue, &req)) {
|
||||||
@ -92,13 +93,13 @@ file_handler_request(struct file_handler *file_handler,
|
|||||||
.file = file,
|
.file = file,
|
||||||
};
|
};
|
||||||
|
|
||||||
mutex_lock(file_handler->mutex);
|
sc_mutex_lock(&file_handler->mutex);
|
||||||
bool was_empty = cbuf_is_empty(&file_handler->queue);
|
bool was_empty = cbuf_is_empty(&file_handler->queue);
|
||||||
bool res = cbuf_push(&file_handler->queue, req);
|
bool res = cbuf_push(&file_handler->queue, req);
|
||||||
if (was_empty) {
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,14 +108,14 @@ run_file_handler(void *data) {
|
|||||||
struct file_handler *file_handler = data;
|
struct file_handler *file_handler = data;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
mutex_lock(file_handler->mutex);
|
sc_mutex_lock(&file_handler->mutex);
|
||||||
file_handler->current_process = PROCESS_NONE;
|
file_handler->current_process = PROCESS_NONE;
|
||||||
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
|
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) {
|
if (file_handler->stopped) {
|
||||||
// stop immediately, do not process further events
|
// stop immediately, do not process further events
|
||||||
mutex_unlock(file_handler->mutex);
|
sc_mutex_unlock(&file_handler->mutex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
struct file_handler_request req;
|
struct file_handler_request req;
|
||||||
@ -132,7 +133,7 @@ run_file_handler(void *data) {
|
|||||||
file_handler->push_target);
|
file_handler->push_target);
|
||||||
}
|
}
|
||||||
file_handler->current_process = process;
|
file_handler->current_process = process;
|
||||||
mutex_unlock(file_handler->mutex);
|
sc_mutex_unlock(&file_handler->mutex);
|
||||||
|
|
||||||
if (req.action == ACTION_INSTALL_APK) {
|
if (req.action == ACTION_INSTALL_APK) {
|
||||||
if (process_check_success(process, "adb install", false)) {
|
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)
|
// Close the process (it is necessary already terminated)
|
||||||
// Execute this call with mutex locked to avoid race conditions with
|
// Execute this call with mutex locked to avoid race conditions with
|
||||||
// file_handler_stop()
|
// file_handler_stop()
|
||||||
process_close(file_handler->current_process);
|
process_close(file_handler->current_process);
|
||||||
file_handler->current_process = PROCESS_NONE;
|
file_handler->current_process = PROCESS_NONE;
|
||||||
mutex_unlock(file_handler->mutex);
|
sc_mutex_unlock(&file_handler->mutex);
|
||||||
|
|
||||||
file_handler_request_destroy(&req);
|
file_handler_request_destroy(&req);
|
||||||
}
|
}
|
||||||
@ -167,9 +168,9 @@ bool
|
|||||||
file_handler_start(struct file_handler *file_handler) {
|
file_handler_start(struct file_handler *file_handler) {
|
||||||
LOGD("Starting file_handler thread");
|
LOGD("Starting file_handler thread");
|
||||||
|
|
||||||
file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler",
|
bool ok = sc_thread_create(&file_handler->thread, run_file_handler,
|
||||||
file_handler);
|
"file_handler", file_handler);
|
||||||
if (!file_handler->thread) {
|
if (!ok) {
|
||||||
LOGC("Could not start file_handler thread");
|
LOGC("Could not start file_handler thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -179,18 +180,18 @@ file_handler_start(struct file_handler *file_handler) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
file_handler_stop(struct file_handler *file_handler) {
|
file_handler_stop(struct file_handler *file_handler) {
|
||||||
mutex_lock(file_handler->mutex);
|
sc_mutex_lock(&file_handler->mutex);
|
||||||
file_handler->stopped = true;
|
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 (file_handler->current_process != PROCESS_NONE) {
|
||||||
if (!process_terminate(file_handler->current_process)) {
|
if (!process_terminate(file_handler->current_process)) {
|
||||||
LOGW("Could not terminate push/install process");
|
LOGW("Could not terminate push/install process");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mutex_unlock(file_handler->mutex);
|
sc_mutex_unlock(&file_handler->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
file_handler_join(struct file_handler *file_handler) {
|
file_handler_join(struct file_handler *file_handler) {
|
||||||
SDL_WaitThread(file_handler->thread, NULL);
|
sc_thread_join(&file_handler->thread, NULL);
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,10 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
|
|
||||||
#include "adb.h"
|
#include "adb.h"
|
||||||
#include "util/cbuf.h"
|
#include "util/cbuf.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ACTION_INSTALL_APK,
|
ACTION_INSTALL_APK,
|
||||||
@ -25,9 +24,9 @@ struct file_handler_request_queue CBUF(struct file_handler_request, 16);
|
|||||||
struct file_handler {
|
struct file_handler {
|
||||||
char *serial;
|
char *serial;
|
||||||
const char *push_target;
|
const char *push_target;
|
||||||
SDL_Thread *thread;
|
sc_thread thread;
|
||||||
SDL_mutex *mutex;
|
sc_mutex mutex;
|
||||||
SDL_cond *event_cond;
|
sc_cond event_cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
bool initialized;
|
bool initialized;
|
||||||
process_t current_process;
|
process_t current_process;
|
||||||
@ -50,7 +49,7 @@ file_handler_stop(struct file_handler *file_handler);
|
|||||||
void
|
void
|
||||||
file_handler_join(struct file_handler *file_handler);
|
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
|
bool
|
||||||
file_handler_request(struct file_handler *file_handler,
|
file_handler_request(struct file_handler *file_handler,
|
||||||
file_handler_action_t action,
|
file_handler_action_t action,
|
||||||
|
@ -3,25 +3,24 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <SDL2/SDL_timer.h>
|
#include <SDL2/SDL_timer.h>
|
||||||
|
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define FPS_COUNTER_INTERVAL_MS 1000
|
#define FPS_COUNTER_INTERVAL_MS 1000
|
||||||
|
|
||||||
bool
|
bool
|
||||||
fps_counter_init(struct fps_counter *counter) {
|
fps_counter_init(struct fps_counter *counter) {
|
||||||
counter->mutex = SDL_CreateMutex();
|
bool ok = sc_mutex_init(&counter->mutex);
|
||||||
if (!counter->mutex) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
counter->state_cond = SDL_CreateCond();
|
ok = sc_cond_init(&counter->state_cond);
|
||||||
if (!counter->state_cond) {
|
if (!ok) {
|
||||||
SDL_DestroyMutex(counter->mutex);
|
sc_mutex_destroy(&counter->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
counter->thread = NULL;
|
counter->thread_started = false;
|
||||||
atomic_init(&counter->started, 0);
|
atomic_init(&counter->started, 0);
|
||||||
// no need to initialize the other fields, they are unused until started
|
// no need to initialize the other fields, they are unused until started
|
||||||
|
|
||||||
@ -30,8 +29,8 @@ fps_counter_init(struct fps_counter *counter) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
fps_counter_destroy(struct fps_counter *counter) {
|
fps_counter_destroy(struct fps_counter *counter) {
|
||||||
SDL_DestroyCond(counter->state_cond);
|
sc_cond_destroy(&counter->state_cond);
|
||||||
SDL_DestroyMutex(counter->mutex);
|
sc_mutex_destroy(&counter->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
@ -77,10 +76,10 @@ static int
|
|||||||
run_fps_counter(void *data) {
|
run_fps_counter(void *data) {
|
||||||
struct fps_counter *counter = data;
|
struct fps_counter *counter = data;
|
||||||
|
|
||||||
mutex_lock(counter->mutex);
|
sc_mutex_lock(&counter->mutex);
|
||||||
while (!counter->interrupted) {
|
while (!counter->interrupted) {
|
||||||
while (!counter->interrupted && !is_started(counter)) {
|
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)) {
|
while (!counter->interrupted && is_started(counter)) {
|
||||||
uint32_t now = SDL_GetTicks();
|
uint32_t now = SDL_GetTicks();
|
||||||
@ -90,32 +89,35 @@ run_fps_counter(void *data) {
|
|||||||
uint32_t remaining = counter->next_timestamp - now;
|
uint32_t remaining = counter->next_timestamp - now;
|
||||||
|
|
||||||
// ignore the reason (timeout or signaled), we just loop anyway
|
// 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
fps_counter_start(struct fps_counter *counter) {
|
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->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
|
||||||
counter->nr_rendered = 0;
|
counter->nr_rendered = 0;
|
||||||
counter->nr_skipped = 0;
|
counter->nr_skipped = 0;
|
||||||
mutex_unlock(counter->mutex);
|
sc_mutex_unlock(&counter->mutex);
|
||||||
|
|
||||||
set_started(counter, true);
|
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
|
// counter->thread_started and counter->thread are always accessed from the
|
||||||
if (!counter->thread) {
|
// same thread, no need to lock
|
||||||
counter->thread =
|
if (!counter->thread_started) {
|
||||||
SDL_CreateThread(run_fps_counter, "fps counter", counter);
|
bool ok = sc_thread_create(&counter->thread, run_fps_counter,
|
||||||
if (!counter->thread) {
|
"fps counter", counter);
|
||||||
|
if (!ok) {
|
||||||
LOGE("Could not start FPS counter thread");
|
LOGE("Could not start FPS counter thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
counter->thread_started = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -124,7 +126,7 @@ fps_counter_start(struct fps_counter *counter) {
|
|||||||
void
|
void
|
||||||
fps_counter_stop(struct fps_counter *counter) {
|
fps_counter_stop(struct fps_counter *counter) {
|
||||||
set_started(counter, false);
|
set_started(counter, false);
|
||||||
cond_signal(counter->state_cond);
|
sc_cond_signal(&counter->state_cond);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -134,21 +136,21 @@ fps_counter_is_started(struct fps_counter *counter) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
fps_counter_interrupt(struct fps_counter *counter) {
|
fps_counter_interrupt(struct fps_counter *counter) {
|
||||||
if (!counter->thread) {
|
if (!counter->thread_started) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(counter->mutex);
|
sc_mutex_lock(&counter->mutex);
|
||||||
counter->interrupted = true;
|
counter->interrupted = true;
|
||||||
mutex_unlock(counter->mutex);
|
sc_mutex_unlock(&counter->mutex);
|
||||||
// wake up blocking wait
|
// wake up blocking wait
|
||||||
cond_signal(counter->state_cond);
|
sc_cond_signal(&counter->state_cond);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
fps_counter_join(struct fps_counter *counter) {
|
fps_counter_join(struct fps_counter *counter) {
|
||||||
if (counter->thread) {
|
if (counter->thread_started) {
|
||||||
SDL_WaitThread(counter->thread, NULL);
|
sc_thread_join(&counter->thread, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,11 +160,11 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(counter->mutex);
|
sc_mutex_lock(&counter->mutex);
|
||||||
uint32_t now = SDL_GetTicks();
|
uint32_t now = SDL_GetTicks();
|
||||||
check_interval_expired(counter, now);
|
check_interval_expired(counter, now);
|
||||||
++counter->nr_rendered;
|
++counter->nr_rendered;
|
||||||
mutex_unlock(counter->mutex);
|
sc_mutex_unlock(&counter->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -171,9 +173,9 @@ fps_counter_add_skipped_frame(struct fps_counter *counter) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(counter->mutex);
|
sc_mutex_lock(&counter->mutex);
|
||||||
uint32_t now = SDL_GetTicks();
|
uint32_t now = SDL_GetTicks();
|
||||||
check_interval_expired(counter, now);
|
check_interval_expired(counter, now);
|
||||||
++counter->nr_skipped;
|
++counter->nr_skipped;
|
||||||
mutex_unlock(counter->mutex);
|
sc_mutex_unlock(&counter->mutex);
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,15 @@
|
|||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct fps_counter {
|
struct fps_counter {
|
||||||
SDL_Thread *thread;
|
sc_thread thread;
|
||||||
SDL_mutex *mutex;
|
sc_mutex mutex;
|
||||||
SDL_cond *state_cond;
|
sc_cond state_cond;
|
||||||
|
|
||||||
|
bool thread_started;
|
||||||
|
|
||||||
// atomic so that we can check without locking the mutex
|
// atomic so that we can check without locking the mutex
|
||||||
// if the FPS counter is disabled, we don't want to lock unnecessarily
|
// if the FPS counter is disabled, we don't want to lock unnecessarily
|
||||||
|
247
app/src/frame_texture.c
Normal file
247
app/src/frame_texture.c
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
#include "frame_texture.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <libavutil/imgutils.h>
|
||||||
|
#include <libswscale/swscale.h>
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
is_swscale_enabled(enum sc_scale_filter scale_filter) {
|
||||||
|
return scale_filter != SC_SCALE_FILTER_NONE
|
||||||
|
&& scale_filter != SC_SCALE_FILTER_TRILINEAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_Texture *
|
||||||
|
create_texture(struct sc_frame_texture *ftex, struct size size) {
|
||||||
|
SDL_Renderer *renderer = ftex->renderer;
|
||||||
|
SDL_PixelFormatEnum fmt = is_swscale_enabled(ftex->scale_filter)
|
||||||
|
? 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 (ftex->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
|
||||||
|
struct sc_opengl *gl = &ftex->gl;
|
||||||
|
|
||||||
|
SDL_GL_BindTexture(texture, NULL, NULL);
|
||||||
|
|
||||||
|
// Enable trilinear filtering for downscaling
|
||||||
|
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
||||||
|
GL_LINEAR_MIPMAP_LINEAR);
|
||||||
|
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
|
||||||
|
|
||||||
|
SDL_GL_UnbindTexture(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_frame_texture_init(struct sc_frame_texture *ftex, SDL_Renderer *renderer,
|
||||||
|
enum sc_scale_filter scale_filter,
|
||||||
|
struct size initial_size) {
|
||||||
|
SDL_RendererInfo renderer_info;
|
||||||
|
int r = SDL_GetRendererInfo(renderer, &renderer_info);
|
||||||
|
const char *renderer_name = r ? NULL : renderer_info.name;
|
||||||
|
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||||
|
|
||||||
|
ftex->scale_filter = scale_filter;
|
||||||
|
|
||||||
|
// starts with "opengl"
|
||||||
|
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||||
|
if (use_opengl) {
|
||||||
|
struct sc_opengl *gl = &ftex->gl;
|
||||||
|
sc_opengl_init(gl);
|
||||||
|
|
||||||
|
LOGI("OpenGL version: %s", gl->version);
|
||||||
|
|
||||||
|
if (scale_filter == SC_SCALE_FILTER_TRILINEAR) {
|
||||||
|
bool supports_mipmaps =
|
||||||
|
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
||||||
|
2, 0 /* OpenGL ES 2.0+ */);
|
||||||
|
if (!supports_mipmaps) {
|
||||||
|
LOGW("Trilinear filtering disabled "
|
||||||
|
"(OpenGL 3.0+ or ES 2.0+ required)");
|
||||||
|
ftex->scale_filter = SC_SCALE_FILTER_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (scale_filter == SC_SCALE_FILTER_TRILINEAR) {
|
||||||
|
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("Scale filter: %s\n", sc_scale_filter_name(ftex->scale_filter));
|
||||||
|
|
||||||
|
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, initial_size.width,
|
||||||
|
initial_size.height);
|
||||||
|
ftex->renderer = renderer;
|
||||||
|
ftex->texture = create_texture(ftex, initial_size);
|
||||||
|
if (!ftex->texture) {
|
||||||
|
LOGC("Could not create texture: %s", SDL_GetError());
|
||||||
|
SDL_DestroyRenderer(ftex->renderer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ftex->texture_size = initial_size;
|
||||||
|
ftex->decoded_frame = NULL;
|
||||||
|
memset(ftex->data, 0, sizeof(ftex->data));
|
||||||
|
memset(ftex->linesize, 0, sizeof(ftex->linesize));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_frame_texture_destroy(struct sc_frame_texture *ftex) {
|
||||||
|
if (ftex->texture) {
|
||||||
|
SDL_DestroyTexture(ftex->texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
screen_generate_resized_frame(struct sc_frame_texture *ftex,
|
||||||
|
struct size target_size) {
|
||||||
|
assert(is_swscale_enabled(ftex->scale_filter));
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
if (ftex->data[0]) {
|
||||||
|
av_freep(&ftex->data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = av_image_alloc(ftex->data, ftex->linesize, target_size.width,
|
||||||
|
target_size.height, AV_PIX_FMT_RGB24, 16);
|
||||||
|
if (ret < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVFrame *input = ftex->decoded_frame;
|
||||||
|
assert(input);
|
||||||
|
|
||||||
|
int flags = to_sws_filter(ftex->scale_filter);
|
||||||
|
struct SwsContext *ctx =
|
||||||
|
sws_getContext(input->width, input->height, AV_PIX_FMT_YUV420P,
|
||||||
|
target_size.width, target_size.height, AV_PIX_FMT_RGB24,
|
||||||
|
flags, NULL, NULL, NULL);
|
||||||
|
if (!ctx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sws_scale(ctx, (const uint8_t *const *) input->data, input->linesize, 0,
|
||||||
|
input->height, ftex->data, ftex->linesize);
|
||||||
|
sws_freeContext(ctx);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_frame_texture_update_direct(struct sc_frame_texture *ftex,
|
||||||
|
const AVFrame *frame) {
|
||||||
|
struct size frame_size = {frame->width, frame->height};
|
||||||
|
if (ftex->texture_size.width != frame_size.width
|
||||||
|
|| ftex->texture_size.height != frame_size.height) {
|
||||||
|
// Frame dimensions changed, destroy texture
|
||||||
|
SDL_DestroyTexture(ftex->texture);
|
||||||
|
|
||||||
|
LOGI("New texture: %" PRIu16 "x%" PRIu16, frame_size.width,
|
||||||
|
frame_size.height);
|
||||||
|
ftex->texture = create_texture(ftex, frame_size);
|
||||||
|
if (!ftex->texture) {
|
||||||
|
LOGC("Could not create texture: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ftex->texture_size = frame_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_UpdateYUVTexture(ftex->texture, NULL,
|
||||||
|
frame->data[0], frame->linesize[0],
|
||||||
|
frame->data[1], frame->linesize[1],
|
||||||
|
frame->data[2], frame->linesize[2]);
|
||||||
|
|
||||||
|
if (ftex->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
|
||||||
|
SDL_GL_BindTexture(ftex->texture, NULL, NULL);
|
||||||
|
ftex->gl.GenerateMipmap(GL_TEXTURE_2D);
|
||||||
|
SDL_GL_UnbindTexture(ftex->texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_frame_texture_update_swscale(struct sc_frame_texture *ftex,
|
||||||
|
const AVFrame *frame, struct size target_size) {
|
||||||
|
if (ftex->texture_size.width != target_size.width
|
||||||
|
|| ftex->texture_size.height != target_size.height) {
|
||||||
|
// Frame dimensions changed, destroy texture
|
||||||
|
SDL_DestroyTexture(ftex->texture);
|
||||||
|
|
||||||
|
ftex->texture = create_texture(ftex, target_size);
|
||||||
|
if (!ftex->texture) {
|
||||||
|
LOGC("Could not create texture: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ftex->texture_size = target_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The frame is valid until the next call to
|
||||||
|
// video_buffer_take_rendering_frame()
|
||||||
|
ftex->decoded_frame = frame;
|
||||||
|
|
||||||
|
bool ok = screen_generate_resized_frame(ftex, target_size);
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Failed to resize frame\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_UpdateTexture(ftex->texture, NULL, ftex->data[0], ftex->linesize[0]);
|
||||||
|
|
||||||
|
if (ftex->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
|
||||||
|
SDL_GL_BindTexture(ftex->texture, NULL, NULL);
|
||||||
|
ftex->gl.GenerateMipmap(GL_TEXTURE_2D);
|
||||||
|
SDL_GL_UnbindTexture(ftex->texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_frame_texture_update(struct sc_frame_texture *ftex, const AVFrame *frame,
|
||||||
|
struct size target_size) {
|
||||||
|
if (is_swscale_enabled(ftex->scale_filter)) {
|
||||||
|
return sc_frame_texture_update_swscale(ftex, frame, target_size);
|
||||||
|
}
|
||||||
|
return sc_frame_texture_update_direct(ftex, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_frame_texture_resize(struct sc_frame_texture *ftex,
|
||||||
|
struct size target_size) {
|
||||||
|
if (is_swscale_enabled(ftex->scale_filter)) {
|
||||||
|
return sc_frame_texture_update_swscale(ftex, ftex->decoded_frame,
|
||||||
|
target_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing to do
|
||||||
|
return true;
|
||||||
|
}
|
44
app/src/frame_texture.h
Normal file
44
app/src/frame_texture.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#ifndef SC_FRAME_TEXTURE_H
|
||||||
|
#define SC_FRAME_TEXTURE_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
|
#include "coords.h"
|
||||||
|
#include "opengl.h"
|
||||||
|
#include "scrcpy.h"
|
||||||
|
|
||||||
|
struct sc_frame_texture {
|
||||||
|
SDL_Renderer *renderer; // owned by struct screen
|
||||||
|
|
||||||
|
enum sc_scale_filter scale_filter;
|
||||||
|
struct sc_opengl gl;
|
||||||
|
|
||||||
|
SDL_Texture *texture;
|
||||||
|
struct size texture_size;
|
||||||
|
|
||||||
|
// For swscaling
|
||||||
|
const AVFrame *decoded_frame; // owned by the video_buffer
|
||||||
|
uint8_t *data[4];
|
||||||
|
int linesize[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_frame_texture_init(struct sc_frame_texture *ftex, SDL_Renderer *renderer,
|
||||||
|
enum sc_scale_filter scale_filter,
|
||||||
|
struct size initial_size);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_frame_texture_destroy(struct sc_frame_texture *ftex);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_frame_texture_update(struct sc_frame_texture *ftex, const AVFrame *frame,
|
||||||
|
struct size target_size);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_frame_texture_resize(struct sc_frame_texture *ftex, struct size target_size);
|
||||||
|
|
||||||
|
#endif
|
@ -4,7 +4,6 @@
|
|||||||
#include <SDL2/SDL_keycode.h>
|
#include <SDL2/SDL_keycode.h>
|
||||||
|
|
||||||
#include "event_converter.h"
|
#include "event_converter.h"
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
static const int ACTION_DOWN = 1;
|
static const int ACTION_DOWN = 1;
|
||||||
@ -190,13 +189,20 @@ set_device_clipboard(struct controller *controller, bool paste) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *text_dup = strdup(text);
|
||||||
|
SDL_free(text);
|
||||||
|
if (!text_dup) {
|
||||||
|
LOGW("Could not strdup input text");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
|
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
|
||||||
msg.set_clipboard.text = text;
|
msg.set_clipboard.text = text_dup;
|
||||||
msg.set_clipboard.paste = paste;
|
msg.set_clipboard.paste = paste;
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
SDL_free(text);
|
free(text_dup);
|
||||||
LOGW("Could not request 'set device clipboard'");
|
LOGW("Could not request 'set device clipboard'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -242,11 +248,18 @@ clipboard_paste(struct controller *controller) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *text_dup = strdup(text);
|
||||||
|
SDL_free(text);
|
||||||
|
if (!text_dup) {
|
||||||
|
LOGW("Could not strdup input text");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
msg.inject_text.text = text;
|
msg.inject_text.text = text_dup;
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
SDL_free(text);
|
free(text_dup);
|
||||||
LOGW("Could not request 'paste clipboard'");
|
LOGW("Could not request 'paste clipboard'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,13 +304,13 @@ input_manager_process_text_input(struct input_manager *im,
|
|||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
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) {
|
if (!msg.inject_text.text) {
|
||||||
LOGW("Could not strdup input text");
|
LOGW("Could not strdup input text");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!controller_push_msg(im->controller, &msg)) {
|
if (!controller_push_msg(im->controller, &msg)) {
|
||||||
SDL_free(msg.inject_text.text);
|
free(msg.inject_text.text);
|
||||||
LOGW("Could not request 'inject text'");
|
LOGW("Could not request 'inject text'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
#include <SDL2/SDL_clipboard.h>
|
#include <SDL2/SDL_clipboard.h>
|
||||||
|
|
||||||
#include "device_msg.h"
|
#include "device_msg.h"
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
receiver_init(struct receiver *receiver, socket_t control_socket) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
receiver->control_socket = control_socket;
|
receiver->control_socket = control_socket;
|
||||||
@ -18,7 +18,7 @@ receiver_init(struct receiver *receiver, socket_t control_socket) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
receiver_destroy(struct receiver *receiver) {
|
receiver_destroy(struct receiver *receiver) {
|
||||||
SDL_DestroyMutex(receiver->mutex);
|
sc_mutex_destroy(&receiver->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -101,8 +101,9 @@ bool
|
|||||||
receiver_start(struct receiver *receiver) {
|
receiver_start(struct receiver *receiver) {
|
||||||
LOGD("Starting receiver thread");
|
LOGD("Starting receiver thread");
|
||||||
|
|
||||||
receiver->thread = SDL_CreateThread(run_receiver, "receiver", receiver);
|
bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver",
|
||||||
if (!receiver->thread) {
|
receiver);
|
||||||
|
if (!ok) {
|
||||||
LOGC("Could not start receiver thread");
|
LOGC("Could not start receiver thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -112,5 +113,5 @@ receiver_start(struct receiver *receiver) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
receiver_join(struct receiver *receiver) {
|
receiver_join(struct receiver *receiver) {
|
||||||
SDL_WaitThread(receiver->thread, NULL);
|
sc_thread_join(&receiver->thread, NULL);
|
||||||
}
|
}
|
||||||
|
@ -4,17 +4,16 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
|
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
// receive events from the device
|
// receive events from the device
|
||||||
// managed by the controller
|
// managed by the controller
|
||||||
struct receiver {
|
struct receiver {
|
||||||
socket_t control_socket;
|
socket_t control_socket;
|
||||||
SDL_Thread *thread;
|
sc_thread thread;
|
||||||
SDL_mutex *mutex;
|
sc_mutex mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <libavutil/time.h>
|
#include <libavutil/time.h>
|
||||||
|
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
||||||
@ -27,7 +26,7 @@ find_muxer(const char *name) {
|
|||||||
|
|
||||||
static struct record_packet *
|
static struct record_packet *
|
||||||
record_packet_new(const AVPacket *packet) {
|
record_packet_new(const AVPacket *packet) {
|
||||||
struct record_packet *rec = SDL_malloc(sizeof(*rec));
|
struct record_packet *rec = malloc(sizeof(*rec));
|
||||||
if (!rec) {
|
if (!rec) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -37,7 +36,7 @@ record_packet_new(const AVPacket *packet) {
|
|||||||
av_init_packet(&rec->packet);
|
av_init_packet(&rec->packet);
|
||||||
|
|
||||||
if (av_packet_ref(&rec->packet, packet)) {
|
if (av_packet_ref(&rec->packet, packet)) {
|
||||||
SDL_free(rec);
|
free(rec);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return rec;
|
return rec;
|
||||||
@ -46,7 +45,7 @@ record_packet_new(const AVPacket *packet) {
|
|||||||
static void
|
static void
|
||||||
record_packet_delete(struct record_packet *rec) {
|
record_packet_delete(struct record_packet *rec) {
|
||||||
av_packet_unref(&rec->packet);
|
av_packet_unref(&rec->packet);
|
||||||
SDL_free(rec);
|
free(rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -63,24 +62,24 @@ recorder_init(struct recorder *recorder,
|
|||||||
const char *filename,
|
const char *filename,
|
||||||
enum sc_record_format format,
|
enum sc_record_format format,
|
||||||
struct size declared_frame_size) {
|
struct size declared_frame_size) {
|
||||||
recorder->filename = SDL_strdup(filename);
|
recorder->filename = strdup(filename);
|
||||||
if (!recorder->filename) {
|
if (!recorder->filename) {
|
||||||
LOGE("Could not strdup filename");
|
LOGE("Could not strdup filename");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
recorder->mutex = SDL_CreateMutex();
|
bool ok = sc_mutex_init(&recorder->mutex);
|
||||||
if (!recorder->mutex) {
|
if (!ok) {
|
||||||
LOGC("Could not create mutex");
|
LOGC("Could not create mutex");
|
||||||
SDL_free(recorder->filename);
|
free(recorder->filename);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
recorder->queue_cond = SDL_CreateCond();
|
ok = sc_cond_init(&recorder->queue_cond);
|
||||||
if (!recorder->queue_cond) {
|
if (!ok) {
|
||||||
LOGC("Could not create cond");
|
LOGC("Could not create cond");
|
||||||
SDL_DestroyMutex(recorder->mutex);
|
sc_mutex_destroy(&recorder->mutex);
|
||||||
SDL_free(recorder->filename);
|
free(recorder->filename);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,9 +96,9 @@ recorder_init(struct recorder *recorder,
|
|||||||
|
|
||||||
void
|
void
|
||||||
recorder_destroy(struct recorder *recorder) {
|
recorder_destroy(struct recorder *recorder) {
|
||||||
SDL_DestroyCond(recorder->queue_cond);
|
sc_cond_destroy(&recorder->queue_cond);
|
||||||
SDL_DestroyMutex(recorder->mutex);
|
sc_mutex_destroy(&recorder->mutex);
|
||||||
SDL_free(recorder->filename);
|
free(recorder->filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
@ -258,17 +257,17 @@ run_recorder(void *data) {
|
|||||||
struct recorder *recorder = data;
|
struct recorder *recorder = data;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
mutex_lock(recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
|
||||||
while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
|
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
|
// if stopped is set, continue to process the remaining events (to
|
||||||
// finish the recording) before actually stopping
|
// finish the recording) before actually stopping
|
||||||
|
|
||||||
if (recorder->stopped && queue_is_empty(&recorder->queue)) {
|
if (recorder->stopped && queue_is_empty(&recorder->queue)) {
|
||||||
mutex_unlock(recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
struct record_packet *last = recorder->previous;
|
struct record_packet *last = recorder->previous;
|
||||||
if (last) {
|
if (last) {
|
||||||
// assign an arbitrary duration to the last packet
|
// assign an arbitrary duration to the last packet
|
||||||
@ -288,7 +287,7 @@ run_recorder(void *data) {
|
|||||||
struct record_packet *rec;
|
struct record_packet *rec;
|
||||||
queue_take(&recorder->queue, next, &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
|
// recorder->previous is only written from this thread, no need to lock
|
||||||
struct record_packet *previous = recorder->previous;
|
struct record_packet *previous = recorder->previous;
|
||||||
@ -311,11 +310,11 @@ run_recorder(void *data) {
|
|||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not record packet");
|
LOGE("Could not record packet");
|
||||||
|
|
||||||
mutex_lock(recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
recorder->failed = true;
|
recorder->failed = true;
|
||||||
// discard pending packets
|
// discard pending packets
|
||||||
recorder_queue_clear(&recorder->queue);
|
recorder_queue_clear(&recorder->queue);
|
||||||
mutex_unlock(recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,8 +329,9 @@ bool
|
|||||||
recorder_start(struct recorder *recorder) {
|
recorder_start(struct recorder *recorder) {
|
||||||
LOGD("Starting recorder thread");
|
LOGD("Starting recorder thread");
|
||||||
|
|
||||||
recorder->thread = SDL_CreateThread(run_recorder, "recorder", recorder);
|
bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
|
||||||
if (!recorder->thread) {
|
recorder);
|
||||||
|
if (!ok) {
|
||||||
LOGC("Could not start recorder thread");
|
LOGC("Could not start recorder thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -341,38 +341,38 @@ recorder_start(struct recorder *recorder) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
recorder_stop(struct recorder *recorder) {
|
recorder_stop(struct recorder *recorder) {
|
||||||
mutex_lock(recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
recorder->stopped = true;
|
recorder->stopped = true;
|
||||||
cond_signal(recorder->queue_cond);
|
sc_cond_signal(&recorder->queue_cond);
|
||||||
mutex_unlock(recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
recorder_join(struct recorder *recorder) {
|
recorder_join(struct recorder *recorder) {
|
||||||
SDL_WaitThread(recorder->thread, NULL);
|
sc_thread_join(&recorder->thread, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
recorder_push(struct recorder *recorder, const AVPacket *packet) {
|
recorder_push(struct recorder *recorder, const AVPacket *packet) {
|
||||||
mutex_lock(recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
assert(!recorder->stopped);
|
assert(!recorder->stopped);
|
||||||
|
|
||||||
if (recorder->failed) {
|
if (recorder->failed) {
|
||||||
// reject any new packet (this will stop the stream)
|
// reject any new packet (this will stop the stream)
|
||||||
mutex_unlock(recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct record_packet *rec = record_packet_new(packet);
|
struct record_packet *rec = record_packet_new(packet);
|
||||||
if (!rec) {
|
if (!rec) {
|
||||||
LOGC("Could not allocate record packet");
|
LOGC("Could not allocate record packet");
|
||||||
mutex_unlock(recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
queue_push(&recorder->queue, next, rec);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,11 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
|
|
||||||
#include "coords.h"
|
#include "coords.h"
|
||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
#include "util/queue.h"
|
#include "util/queue.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct record_packet {
|
struct record_packet {
|
||||||
AVPacket packet;
|
AVPacket packet;
|
||||||
@ -26,9 +25,9 @@ struct recorder {
|
|||||||
struct size declared_frame_size;
|
struct size declared_frame_size;
|
||||||
bool header_written;
|
bool header_written;
|
||||||
|
|
||||||
SDL_Thread *thread;
|
sc_thread thread;
|
||||||
SDL_mutex *mutex;
|
sc_mutex mutex;
|
||||||
SDL_cond *queue_cond;
|
sc_cond queue_cond;
|
||||||
bool stopped; // set on recorder_stop() by the stream reader
|
bool stopped; // set on recorder_stop() by the stream reader
|
||||||
bool failed; // set on packet write failure
|
bool failed; // set on packet write failure
|
||||||
struct recorder_queue queue;
|
struct recorder_queue queue;
|
||||||
|
@ -26,10 +26,29 @@
|
|||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
#include "tiny_xpm.h"
|
#include "tiny_xpm.h"
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
|
||||||
|
static const char *sc_scale_filter_names[SC_SCALE_FILTER__COUNT] = {
|
||||||
|
[SC_SCALE_FILTER_NONE] = "none",
|
||||||
|
[SC_SCALE_FILTER_TRILINEAR] = "trilinear",
|
||||||
|
[SC_SCALE_FILTER_BILINEAR] = "bilinear",
|
||||||
|
[SC_SCALE_FILTER_BICUBIC] = "bicubic",
|
||||||
|
[SC_SCALE_FILTER_X] = "x",
|
||||||
|
[SC_SCALE_FILTER_POINT] = "point",
|
||||||
|
[SC_SCALE_FILTER_AREA] = "area",
|
||||||
|
[SC_SCALE_FILTER_BICUBLIN] = "bicublin",
|
||||||
|
[SC_SCALE_FILTER_GAUSS] = "gauss",
|
||||||
|
[SC_SCALE_FILTER_SINC] = "sinc",
|
||||||
|
[SC_SCALE_FILTER_LANCZOS] = "lanczos",
|
||||||
|
[SC_SCALE_FILTER_SPLINE] = "spline",
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *
|
||||||
|
sc_scale_filter_name(enum sc_scale_filter scale_filter) {
|
||||||
|
return sc_scale_filter_names[scale_filter];
|
||||||
|
}
|
||||||
|
|
||||||
static struct server server;
|
static struct server server;
|
||||||
static struct screen screen = SCREEN_INITIALIZER;
|
static struct screen screen = SCREEN_INITIALIZER;
|
||||||
static struct fps_counter fps_counter;
|
static struct fps_counter fps_counter;
|
||||||
@ -185,7 +204,9 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT:
|
case SDL_WINDOWEVENT:
|
||||||
screen_handle_window_event(&screen, &event->window);
|
if (screen.has_frame) {
|
||||||
|
screen_handle_window_event(&screen, &event->window);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_TEXTINPUT:
|
case SDL_TEXTINPUT:
|
||||||
if (!options->control) {
|
if (!options->control) {
|
||||||
@ -226,13 +247,20 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) {
|
|||||||
if (!options->control) {
|
if (!options->control) {
|
||||||
break;
|
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;
|
file_handler_action_t action;
|
||||||
if (is_apk(event->drop.file)) {
|
if (is_apk(file)) {
|
||||||
action = ACTION_INSTALL_APK;
|
action = ACTION_INSTALL_APK;
|
||||||
} else {
|
} else {
|
||||||
action = ACTION_PUSH_FILE;
|
action = ACTION_PUSH_FILE;
|
||||||
}
|
}
|
||||||
file_handler_request(&file_handler, action, event->drop.file);
|
file_handler_request(&file_handler, action, file);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,7 +314,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
|||||||
if (priority == 0) {
|
if (priority == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
char *local_fmt = SDL_malloc(strlen(fmt) + 10);
|
char *local_fmt = malloc(strlen(fmt) + 10);
|
||||||
if (!local_fmt) {
|
if (!local_fmt) {
|
||||||
LOGC("Could not allocate string");
|
LOGC("Could not allocate string");
|
||||||
return;
|
return;
|
||||||
@ -295,7 +323,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
|||||||
strcpy(local_fmt, "[FFmpeg] ");
|
strcpy(local_fmt, "[FFmpeg] ");
|
||||||
strcpy(local_fmt + 9, fmt);
|
strcpy(local_fmt + 9, fmt);
|
||||||
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
|
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
|
||||||
SDL_free(local_fmt);
|
free(local_fmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -426,7 +454,7 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
options->window_y, options->window_width,
|
options->window_y, options->window_width,
|
||||||
options->window_height,
|
options->window_height,
|
||||||
options->window_borderless,
|
options->window_borderless,
|
||||||
options->rotation, options->mipmaps)) {
|
options->rotation, options->scale_filter)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,25 @@ struct sc_port_range {
|
|||||||
uint16_t last;
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *
|
||||||
|
sc_scale_filter_name(enum sc_scale_filter scale_filter);
|
||||||
|
|
||||||
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
|
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
|
||||||
|
|
||||||
struct scrcpy_options {
|
struct scrcpy_options {
|
||||||
@ -56,6 +75,7 @@ struct scrcpy_options {
|
|||||||
enum sc_record_format record_format;
|
enum sc_record_format record_format;
|
||||||
struct sc_port_range port_range;
|
struct sc_port_range port_range;
|
||||||
struct sc_shortcut_mods shortcut_mods;
|
struct sc_shortcut_mods shortcut_mods;
|
||||||
|
enum sc_scale_filter scale_filter;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
@ -75,7 +95,6 @@ struct scrcpy_options {
|
|||||||
bool render_expired_frames;
|
bool render_expired_frames;
|
||||||
bool prefer_text;
|
bool prefer_text;
|
||||||
bool window_borderless;
|
bool window_borderless;
|
||||||
bool mipmaps;
|
|
||||||
bool stay_awake;
|
bool stay_awake;
|
||||||
bool force_adb_forward;
|
bool force_adb_forward;
|
||||||
bool disable_screensaver;
|
bool disable_screensaver;
|
||||||
@ -103,6 +122,7 @@ struct scrcpy_options {
|
|||||||
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
|
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
|
||||||
.count = 2, \
|
.count = 2, \
|
||||||
}, \
|
}, \
|
||||||
|
.scale_filter = SC_SCALE_FILTER_TRILINEAR, \
|
||||||
.max_size = DEFAULT_MAX_SIZE, \
|
.max_size = DEFAULT_MAX_SIZE, \
|
||||||
.bit_rate = DEFAULT_BIT_RATE, \
|
.bit_rate = DEFAULT_BIT_RATE, \
|
||||||
.max_fps = 0, \
|
.max_fps = 0, \
|
||||||
@ -122,7 +142,6 @@ struct scrcpy_options {
|
|||||||
.render_expired_frames = false, \
|
.render_expired_frames = false, \
|
||||||
.prefer_text = false, \
|
.prefer_text = false, \
|
||||||
.window_borderless = false, \
|
.window_borderless = false, \
|
||||||
.mipmaps = true, \
|
|
||||||
.stay_awake = false, \
|
.stay_awake = false, \
|
||||||
.force_adb_forward = false, \
|
.force_adb_forward = false, \
|
||||||
.disable_screensaver = false, \
|
.disable_screensaver = false, \
|
||||||
|
148
app/src/screen.c
148
app/src/screen.c
@ -5,10 +5,8 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "icon.xpm"
|
#include "icon.xpm"
|
||||||
#include "scrcpy.h"
|
|
||||||
#include "tiny_xpm.h"
|
#include "tiny_xpm.h"
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define DISPLAY_MARGINS 96
|
#define DISPLAY_MARGINS 96
|
||||||
@ -196,39 +194,12 @@ screen_init(struct screen *screen) {
|
|||||||
*screen = (struct screen) SCREEN_INITIALIZER;
|
*screen = (struct screen) SCREEN_INITIALIZER;
|
||||||
}
|
}
|
||||||
|
|
||||||
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_TEXTUREACCESS_STREAMING,
|
|
||||||
size.width, size.height);
|
|
||||||
if (!texture) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (screen->mipmaps) {
|
|
||||||
struct sc_opengl *gl = &screen->gl;
|
|
||||||
|
|
||||||
SDL_GL_BindTexture(texture, NULL, NULL);
|
|
||||||
|
|
||||||
// Enable trilinear filtering for downscaling
|
|
||||||
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
|
||||||
GL_LINEAR_MIPMAP_LINEAR);
|
|
||||||
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.f);
|
|
||||||
|
|
||||||
SDL_GL_UnbindTexture(texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
screen_init_rendering(struct screen *screen, const char *window_title,
|
screen_init_rendering(struct screen *screen, const char *window_title,
|
||||||
struct size frame_size, bool always_on_top,
|
struct size frame_size, bool always_on_top,
|
||||||
int16_t window_x, int16_t window_y, uint16_t window_width,
|
int16_t window_x, int16_t window_y, uint16_t window_width,
|
||||||
uint16_t window_height, bool window_borderless,
|
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->frame_size = frame_size;
|
||||||
screen->rotation = rotation;
|
screen->rotation = rotation;
|
||||||
if (rotation) {
|
if (rotation) {
|
||||||
@ -271,39 +242,17 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
SDL_RENDERER_ACCELERATED);
|
SDL_RENDERER_ACCELERATED);
|
||||||
if (!screen->renderer) {
|
if (!screen->renderer) {
|
||||||
LOGC("Could not create renderer: %s", SDL_GetError());
|
LOGC("Could not create renderer: %s", SDL_GetError());
|
||||||
screen_destroy(screen);
|
SDL_DestroyWindow(screen->window);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_RendererInfo renderer_info;
|
bool ok = sc_frame_texture_init(&screen->ftex, screen->renderer,
|
||||||
int r = SDL_GetRendererInfo(screen->renderer, &renderer_info);
|
scale_filter, frame_size);
|
||||||
const char *renderer_name = r ? NULL : renderer_info.name;
|
if (!ok) {
|
||||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
LOGC("Could not init frame texture");
|
||||||
|
SDL_DestroyRenderer(screen->renderer);
|
||||||
// starts with "opengl"
|
SDL_DestroyWindow(screen->window);
|
||||||
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
return false;
|
||||||
if (screen->use_opengl) {
|
|
||||||
struct sc_opengl *gl = &screen->gl;
|
|
||||||
sc_opengl_init(gl);
|
|
||||||
|
|
||||||
LOGI("OpenGL version: %s", gl->version);
|
|
||||||
|
|
||||||
if (mipmaps) {
|
|
||||||
bool supports_mipmaps =
|
|
||||||
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
|
||||||
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)");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOGI("Trilinear filtering disabled");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Surface *icon = read_xpm(icon_xpm);
|
SDL_Surface *icon = read_xpm(icon_xpm);
|
||||||
@ -314,15 +263,6 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
LOGW("Could not load icon");
|
LOGW("Could not load icon");
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
|
|
||||||
frame_size.height);
|
|
||||||
screen->texture = create_texture(screen);
|
|
||||||
if (!screen->texture) {
|
|
||||||
LOGC("Could not create texture: %s", SDL_GetError());
|
|
||||||
screen_destroy(screen);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
|
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
|
||||||
// HiDPI issues with some SDL renderers when several displays having
|
// HiDPI issues with some SDL renderers when several displays having
|
||||||
// different HiDPI scaling are connected
|
// different HiDPI scaling are connected
|
||||||
@ -340,15 +280,9 @@ screen_show_window(struct screen *screen) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
screen_destroy(struct screen *screen) {
|
screen_destroy(struct screen *screen) {
|
||||||
if (screen->texture) {
|
sc_frame_texture_destroy(&screen->ftex);
|
||||||
SDL_DestroyTexture(screen->texture);
|
SDL_DestroyRenderer(screen->renderer);
|
||||||
}
|
SDL_DestroyWindow(screen->window);
|
||||||
if (screen->renderer) {
|
|
||||||
SDL_DestroyRenderer(screen->renderer);
|
|
||||||
}
|
|
||||||
if (screen->window) {
|
|
||||||
SDL_DestroyWindow(screen->window);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -409,13 +343,10 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// recreate the texture and resize the window if the frame size has changed
|
// recreate the texture and resize the window if the frame size has changed
|
||||||
static bool
|
static void
|
||||||
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
||||||
if (screen->frame_size.width != new_frame_size.width
|
if (screen->frame_size.width != new_frame_size.width
|
||||||
|| screen->frame_size.height != new_frame_size.height) {
|
|| screen->frame_size.height != new_frame_size.height) {
|
||||||
// frame dimension changed, destroy texture
|
|
||||||
SDL_DestroyTexture(screen->texture);
|
|
||||||
|
|
||||||
screen->frame_size = new_frame_size;
|
screen->frame_size = new_frame_size;
|
||||||
|
|
||||||
struct size new_content_size =
|
struct size new_content_size =
|
||||||
@ -423,46 +354,20 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
|||||||
set_content_size(screen, new_content_size);
|
set_content_size(screen, new_content_size);
|
||||||
|
|
||||||
screen_update_content_rect(screen);
|
screen_update_content_rect(screen);
|
||||||
|
|
||||||
LOGI("New texture: %" PRIu16 "x%" PRIu16,
|
|
||||||
screen->frame_size.width, screen->frame_size.height);
|
|
||||||
screen->texture = create_texture(screen);
|
|
||||||
if (!screen->texture) {
|
|
||||||
LOGC("Could not create texture: %s", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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->mipmaps) {
|
|
||||||
assert(screen->use_opengl);
|
|
||||||
SDL_GL_BindTexture(screen->texture, NULL, NULL);
|
|
||||||
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
|
|
||||||
SDL_GL_UnbindTexture(screen->texture);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
screen_update_frame(struct screen *screen, struct video_buffer *vb) {
|
screen_update_frame(struct screen *screen, struct video_buffer *vb) {
|
||||||
mutex_lock(vb->mutex);
|
const AVFrame *frame = video_buffer_take_rendering_frame(vb);
|
||||||
const AVFrame *frame = video_buffer_consume_rendered_frame(vb);
|
|
||||||
struct size new_frame_size = {frame->width, frame->height};
|
struct size new_frame_size = {frame->width, frame->height};
|
||||||
if (!prepare_for_frame(screen, new_frame_size)) {
|
prepare_for_frame(screen, new_frame_size);
|
||||||
mutex_unlock(vb->mutex);
|
|
||||||
|
struct size rect_size = {screen->rect.w, screen->rect.h};
|
||||||
|
bool ok = sc_frame_texture_update(&screen->ftex, frame, rect_size);
|
||||||
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
update_texture(screen, frame);
|
|
||||||
mutex_unlock(vb->mutex);
|
|
||||||
|
|
||||||
screen_render(screen, false);
|
screen_render(screen, false);
|
||||||
return true;
|
return true;
|
||||||
@ -472,11 +377,18 @@ void
|
|||||||
screen_render(struct screen *screen, bool update_content_rect) {
|
screen_render(struct screen *screen, bool update_content_rect) {
|
||||||
if (update_content_rect) {
|
if (update_content_rect) {
|
||||||
screen_update_content_rect(screen);
|
screen_update_content_rect(screen);
|
||||||
|
struct size rect_size = {screen->rect.w, screen->rect.h};
|
||||||
|
if (!sc_frame_texture_resize(&screen->ftex, rect_size)) {
|
||||||
|
// FIXME return error
|
||||||
|
LOGC("oops");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_RenderClear(screen->renderer);
|
struct sc_frame_texture *ftex = &screen->ftex;
|
||||||
|
SDL_RenderClear(ftex->renderer);
|
||||||
if (screen->rotation == 0) {
|
if (screen->rotation == 0) {
|
||||||
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
|
SDL_RenderCopy(screen->renderer, ftex->texture, NULL,
|
||||||
|
&screen->rect);
|
||||||
} else {
|
} else {
|
||||||
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
|
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
|
||||||
// counterclockwise (to be consistent with --lock-video-orientation)
|
// counterclockwise (to be consistent with --lock-video-orientation)
|
||||||
@ -496,10 +408,10 @@ screen_render(struct screen *screen, bool update_content_rect) {
|
|||||||
dstrect = &screen->rect;
|
dstrect = &screen->rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
|
SDL_RenderCopyEx(screen->renderer, ftex->texture, NULL, dstrect,
|
||||||
angle, NULL, 0);
|
angle, NULL, 0);
|
||||||
}
|
}
|
||||||
SDL_RenderPresent(screen->renderer);
|
SDL_RenderPresent(ftex->renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -8,16 +8,17 @@
|
|||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#include "coords.h"
|
#include "coords.h"
|
||||||
|
#include "frame_texture.h"
|
||||||
#include "opengl.h"
|
#include "opengl.h"
|
||||||
|
#include "scrcpy.h"
|
||||||
|
|
||||||
struct video_buffer;
|
struct video_buffer;
|
||||||
|
|
||||||
struct screen {
|
struct screen {
|
||||||
SDL_Window *window;
|
SDL_Window *window;
|
||||||
SDL_Renderer *renderer;
|
SDL_Renderer *renderer;
|
||||||
SDL_Texture *texture;
|
struct sc_frame_texture ftex;
|
||||||
bool use_opengl;
|
|
||||||
struct sc_opengl gl;
|
|
||||||
struct size frame_size;
|
struct size frame_size;
|
||||||
struct size content_size; // rotated frame_size
|
struct size content_size; // rotated frame_size
|
||||||
|
|
||||||
@ -34,15 +35,12 @@ struct screen {
|
|||||||
bool fullscreen;
|
bool fullscreen;
|
||||||
bool maximized;
|
bool maximized;
|
||||||
bool no_window;
|
bool no_window;
|
||||||
bool mipmaps;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SCREEN_INITIALIZER { \
|
#define SCREEN_INITIALIZER { \
|
||||||
.window = NULL, \
|
.window = NULL, \
|
||||||
.renderer = NULL, \
|
.renderer = NULL, \
|
||||||
.texture = NULL, \
|
.ftex = {0}, \
|
||||||
.use_opengl = false, \
|
|
||||||
.gl = {0}, \
|
|
||||||
.frame_size = { \
|
.frame_size = { \
|
||||||
.width = 0, \
|
.width = 0, \
|
||||||
.height = 0, \
|
.height = 0, \
|
||||||
@ -67,7 +65,6 @@ struct screen {
|
|||||||
.fullscreen = false, \
|
.fullscreen = false, \
|
||||||
.maximized = false, \
|
.maximized = false, \
|
||||||
.no_window = false, \
|
.no_window = false, \
|
||||||
.mipmaps = false, \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize default values
|
// initialize default values
|
||||||
@ -81,7 +78,7 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
struct size frame_size, bool always_on_top,
|
struct size frame_size, bool always_on_top,
|
||||||
int16_t window_x, int16_t window_y, uint16_t window_width,
|
int16_t window_x, int16_t window_y, uint16_t window_width,
|
||||||
uint16_t window_height, bool window_borderless,
|
uint16_t window_height, bool window_borderless,
|
||||||
uint8_t rotation, bool mipmaps);
|
uint8_t rotation, enum sc_scale_filter scale_filter);
|
||||||
|
|
||||||
// show the window
|
// show the window
|
||||||
void
|
void
|
||||||
|
@ -5,12 +5,10 @@
|
|||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
#include <SDL2/SDL_timer.h>
|
#include <SDL2/SDL_timer.h>
|
||||||
#include <SDL2/SDL_platform.h>
|
#include <SDL2/SDL_platform.h>
|
||||||
|
|
||||||
#include "adb.h"
|
#include "adb.h"
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
@ -33,7 +31,7 @@ get_server_path(void) {
|
|||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
char *server_path = utf8_from_wide_char(server_path_env);
|
char *server_path = utf8_from_wide_char(server_path_env);
|
||||||
#else
|
#else
|
||||||
char *server_path = SDL_strdup(server_path_env);
|
char *server_path = strdup(server_path_env);
|
||||||
#endif
|
#endif
|
||||||
if (!server_path) {
|
if (!server_path) {
|
||||||
LOGE("Could not allocate memory");
|
LOGE("Could not allocate memory");
|
||||||
@ -45,7 +43,7 @@ get_server_path(void) {
|
|||||||
|
|
||||||
#ifndef PORTABLE
|
#ifndef PORTABLE
|
||||||
LOGD("Using server: " DEFAULT_SERVER_PATH);
|
LOGD("Using server: " DEFAULT_SERVER_PATH);
|
||||||
char *server_path = SDL_strdup(DEFAULT_SERVER_PATH);
|
char *server_path = strdup(DEFAULT_SERVER_PATH);
|
||||||
if (!server_path) {
|
if (!server_path) {
|
||||||
LOGE("Could not allocate memory");
|
LOGE("Could not allocate memory");
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -67,11 +65,11 @@ get_server_path(void) {
|
|||||||
|
|
||||||
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
|
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
|
||||||
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
|
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
|
||||||
char *server_path = SDL_malloc(len);
|
char *server_path = malloc(len);
|
||||||
if (!server_path) {
|
if (!server_path) {
|
||||||
LOGE("Could not alloc server path string, "
|
LOGE("Could not alloc server path string, "
|
||||||
"using " SERVER_FILENAME " from current directory");
|
"using " SERVER_FILENAME " from current directory");
|
||||||
SDL_free(executable_path);
|
free(executable_path);
|
||||||
return SERVER_FILENAME;
|
return SERVER_FILENAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +78,7 @@ get_server_path(void) {
|
|||||||
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
|
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
|
||||||
// the final null byte has been copied with 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);
|
LOGD("Using server (portable): %s", server_path);
|
||||||
return server_path;
|
return server_path;
|
||||||
@ -95,11 +93,11 @@ push_server(const char *serial) {
|
|||||||
}
|
}
|
||||||
if (!is_regular_file(server_path)) {
|
if (!is_regular_file(server_path)) {
|
||||||
LOGE("'%s' does not exist or is not a regular file\n", 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;
|
return false;
|
||||||
}
|
}
|
||||||
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
|
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);
|
return process_check_success(process, "adb push", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,18 +355,17 @@ bool
|
|||||||
server_init(struct server *server) {
|
server_init(struct server *server) {
|
||||||
server->serial = NULL;
|
server->serial = NULL;
|
||||||
server->process = PROCESS_NONE;
|
server->process = PROCESS_NONE;
|
||||||
server->wait_server_thread = NULL;
|
|
||||||
atomic_flag_clear_explicit(&server->server_socket_closed,
|
atomic_flag_clear_explicit(&server->server_socket_closed,
|
||||||
memory_order_relaxed);
|
memory_order_relaxed);
|
||||||
|
|
||||||
server->mutex = SDL_CreateMutex();
|
bool ok = sc_mutex_init(&server->mutex);
|
||||||
if (!server->mutex) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->process_terminated_cond = SDL_CreateCond();
|
ok = sc_cond_init(&server->process_terminated_cond);
|
||||||
if (!server->process_terminated_cond) {
|
if (!ok) {
|
||||||
SDL_DestroyMutex(server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,10 +390,10 @@ run_wait_server(void *data) {
|
|||||||
struct server *server = data;
|
struct server *server = data;
|
||||||
process_wait(server->process, false); // ignore exit code
|
process_wait(server->process, false); // ignore exit code
|
||||||
|
|
||||||
mutex_lock(server->mutex);
|
sc_mutex_lock(&server->mutex);
|
||||||
server->process_terminated = true;
|
server->process_terminated = true;
|
||||||
cond_signal(server->process_terminated_cond);
|
sc_cond_signal(&server->process_terminated_cond);
|
||||||
mutex_unlock(server->mutex);
|
sc_mutex_unlock(&server->mutex);
|
||||||
|
|
||||||
// no need for synchronization, server_socket is initialized before this
|
// no need for synchronization, server_socket is initialized before this
|
||||||
// thread was created
|
// thread was created
|
||||||
@ -416,7 +413,7 @@ server_start(struct server *server, const char *serial,
|
|||||||
server->port_range = params->port_range;
|
server->port_range = params->port_range;
|
||||||
|
|
||||||
if (serial) {
|
if (serial) {
|
||||||
server->serial = SDL_strdup(serial);
|
server->serial = strdup(serial);
|
||||||
if (!server->serial) {
|
if (!server->serial) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -443,9 +440,9 @@ server_start(struct server *server, const char *serial,
|
|||||||
// things simple and multiplatform, just spawn a new thread waiting for the
|
// things simple and multiplatform, just spawn a new thread waiting for the
|
||||||
// server process and calling shutdown()/close() on the server socket if
|
// server process and calling shutdown()/close() on the server socket if
|
||||||
// necessary to wake up any accept() blocking call.
|
// necessary to wake up any accept() blocking call.
|
||||||
server->wait_server_thread =
|
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
|
||||||
SDL_CreateThread(run_wait_server, "wait-server", server);
|
"wait-server", server);
|
||||||
if (!server->wait_server_thread) {
|
if (!ok) {
|
||||||
process_terminate(server->process);
|
process_terminate(server->process);
|
||||||
process_wait(server->process, true); // ignore exit code
|
process_wait(server->process, true); // ignore exit code
|
||||||
goto error2;
|
goto error2;
|
||||||
@ -466,7 +463,7 @@ error2:
|
|||||||
}
|
}
|
||||||
disable_tunnel(server);
|
disable_tunnel(server);
|
||||||
error1:
|
error1:
|
||||||
SDL_free(server->serial);
|
free(server->serial);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,33 +532,33 @@ server_stop(struct server *server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Give some delay for the server to terminate properly
|
// Give some delay for the server to terminate properly
|
||||||
mutex_lock(server->mutex);
|
sc_mutex_lock(&server->mutex);
|
||||||
int r = 0;
|
bool signaled = false;
|
||||||
if (!server->process_terminated) {
|
if (!server->process_terminated) {
|
||||||
#define WATCHDOG_DELAY_MS 1000
|
#define WATCHDOG_DELAY_MS 1000
|
||||||
r = cond_wait_timeout(server->process_terminated_cond,
|
signaled = sc_cond_timedwait(&server->process_terminated_cond,
|
||||||
server->mutex,
|
&server->mutex,
|
||||||
WATCHDOG_DELAY_MS);
|
WATCHDOG_DELAY_MS);
|
||||||
}
|
}
|
||||||
mutex_unlock(server->mutex);
|
sc_mutex_unlock(&server->mutex);
|
||||||
|
|
||||||
// After this delay, kill the server if it's not dead already.
|
// 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
|
// On some devices, closing the sockets is not sufficient to wake up the
|
||||||
// blocking calls while the device is asleep.
|
// 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
|
// The process is terminated, but not reaped (closed) yet, so its PID
|
||||||
// is still valid.
|
// is still valid.
|
||||||
LOGW("Killing the server...");
|
LOGW("Killing the server...");
|
||||||
process_terminate(server->process);
|
process_terminate(server->process);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_WaitThread(server->wait_server_thread, NULL);
|
sc_thread_join(&server->wait_server_thread, NULL);
|
||||||
process_close(server->process);
|
process_close(server->process);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
server_destroy(struct server *server) {
|
server_destroy(struct server *server) {
|
||||||
SDL_free(server->serial);
|
free(server->serial);
|
||||||
SDL_DestroyCond(server->process_terminated_cond);
|
sc_cond_destroy(&server->process_terminated_cond);
|
||||||
SDL_DestroyMutex(server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
}
|
}
|
||||||
|
@ -6,21 +6,21 @@
|
|||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
|
|
||||||
#include "adb.h"
|
#include "adb.h"
|
||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct server {
|
struct server {
|
||||||
char *serial;
|
char *serial;
|
||||||
process_t process;
|
process_t process;
|
||||||
SDL_Thread *wait_server_thread;
|
sc_thread wait_server_thread;
|
||||||
atomic_flag server_socket_closed;
|
atomic_flag server_socket_closed;
|
||||||
|
|
||||||
SDL_mutex *mutex;
|
sc_mutex mutex;
|
||||||
SDL_cond *process_terminated_cond;
|
sc_cond process_terminated_cond;
|
||||||
bool process_terminated;
|
bool process_terminated;
|
||||||
|
|
||||||
socket_t server_socket; // only used if !tunnel_forward
|
socket_t server_socket; // only used if !tunnel_forward
|
||||||
|
@ -4,8 +4,6 @@
|
|||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#include <libavutil/time.h>
|
#include <libavutil/time.h>
|
||||||
#include <SDL2/SDL_events.h>
|
#include <SDL2/SDL_events.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
@ -279,8 +277,8 @@ bool
|
|||||||
stream_start(struct stream *stream) {
|
stream_start(struct stream *stream) {
|
||||||
LOGD("Starting stream thread");
|
LOGD("Starting stream thread");
|
||||||
|
|
||||||
stream->thread = SDL_CreateThread(run_stream, "stream", stream);
|
bool ok = sc_thread_create(&stream->thread, run_stream, "stream", stream);
|
||||||
if (!stream->thread) {
|
if (!ok) {
|
||||||
LOGC("Could not start stream thread");
|
LOGC("Could not start stream thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -296,5 +294,5 @@ stream_stop(struct stream *stream) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
stream_join(struct stream *stream) {
|
stream_join(struct stream *stream) {
|
||||||
SDL_WaitThread(stream->thread, NULL);
|
sc_thread_join(&stream->thread, NULL);
|
||||||
}
|
}
|
||||||
|
@ -7,15 +7,15 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#include <SDL2/SDL_atomic.h>
|
#include <SDL2/SDL_atomic.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
|
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct video_buffer;
|
struct video_buffer;
|
||||||
|
|
||||||
struct stream {
|
struct stream {
|
||||||
socket_t socket;
|
socket_t socket;
|
||||||
SDL_Thread *thread;
|
sc_thread thread;
|
||||||
struct decoder *decoder;
|
struct decoder *decoder;
|
||||||
struct recorder *recorder;
|
struct recorder *recorder;
|
||||||
AVCodecContext *codec_ctx;
|
AVCodecContext *codec_ctx;
|
||||||
|
@ -156,7 +156,7 @@ get_executable_path(void) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
buf[len] = '\0';
|
buf[len] = '\0';
|
||||||
return SDL_strdup(buf);
|
return strdup(buf);
|
||||||
#else
|
#else
|
||||||
// in practice, we only need this feature for portable builds, only used on
|
// in practice, we only need this feature for portable builds, only used on
|
||||||
// Windows, so we don't care implementing it for every platform
|
// Windows, so we don't care implementing it for every platform
|
||||||
|
@ -41,7 +41,7 @@ process_execute(const char *const argv[], HANDLE *handle) {
|
|||||||
|
|
||||||
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si,
|
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si,
|
||||||
&pi)) {
|
&pi)) {
|
||||||
SDL_free(wide);
|
free(wide);
|
||||||
*handle = NULL;
|
*handle = NULL;
|
||||||
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
|
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
|
||||||
return PROCESS_ERROR_MISSING_BINARY;
|
return PROCESS_ERROR_MISSING_BINARY;
|
||||||
@ -49,7 +49,7 @@ process_execute(const char *const argv[], HANDLE *handle) {
|
|||||||
return PROCESS_ERROR_GENERIC;
|
return PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_free(wide);
|
free(wide);
|
||||||
*handle = pi.hProcess;
|
*handle = pi.hProcess;
|
||||||
return PROCESS_SUCCESS;
|
return PROCESS_SUCCESS;
|
||||||
}
|
}
|
||||||
@ -105,7 +105,7 @@ is_regular_file(const char *path) {
|
|||||||
|
|
||||||
struct _stat path_stat;
|
struct _stat path_stat;
|
||||||
int r = _wstat(wide_path, &path_stat);
|
int r = _wstat(wide_path, &path_stat);
|
||||||
SDL_free(wide_path);
|
free(wide_path);
|
||||||
|
|
||||||
if (r) {
|
if (r) {
|
||||||
perror("stat");
|
perror("stat");
|
||||||
|
@ -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
|
|
@ -70,7 +70,7 @@ search_executable(const char *file);
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// return the absolute path of the executable (the scrcpy binary)
|
// 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 *
|
char *
|
||||||
get_executable_path(void);
|
get_executable_path(void);
|
||||||
|
|
||||||
|
@ -10,8 +10,6 @@
|
|||||||
# include <tchar.h>
|
# include <tchar.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <SDL2/SDL_stdinc.h>
|
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
xstrncpy(char *dest, const char *src, size_t n) {
|
xstrncpy(char *dest, const char *src, size_t n) {
|
||||||
size_t i;
|
size_t i;
|
||||||
@ -49,7 +47,7 @@ truncated:
|
|||||||
char *
|
char *
|
||||||
strquote(const char *src) {
|
strquote(const char *src) {
|
||||||
size_t len = strlen(src);
|
size_t len = strlen(src);
|
||||||
char *quoted = SDL_malloc(len + 3);
|
char *quoted = malloc(len + 3);
|
||||||
if (!quoted) {
|
if (!quoted) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -167,7 +165,7 @@ utf8_to_wide_char(const char *utf8) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
wchar_t *wide = SDL_malloc(len * sizeof(wchar_t));
|
wchar_t *wide = malloc(len * sizeof(wchar_t));
|
||||||
if (!wide) {
|
if (!wide) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -183,7 +181,7 @@ utf8_from_wide_char(const wchar_t *ws) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *utf8 = SDL_malloc(len);
|
char *utf8 = malloc(len);
|
||||||
if (!utf8) {
|
if (!utf8) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
160
app/src/util/thread.c
Normal file
160
app/src/util/thread.c
Normal 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
81
app/src/util/thread.h
Normal 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
|
@ -1,11 +1,9 @@
|
|||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <libavutil/avutil.h>
|
#include <libavutil/avutil.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -13,22 +11,31 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
|
|||||||
bool render_expired_frames) {
|
bool render_expired_frames) {
|
||||||
vb->fps_counter = fps_counter;
|
vb->fps_counter = fps_counter;
|
||||||
|
|
||||||
if (!(vb->decoding_frame = av_frame_alloc())) {
|
vb->decoding_frame = av_frame_alloc();
|
||||||
|
if (!vb->decoding_frame) {
|
||||||
goto error_0;
|
goto error_0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(vb->rendering_frame = av_frame_alloc())) {
|
vb->pending_frame = av_frame_alloc();
|
||||||
|
if (!vb->pending_frame) {
|
||||||
goto error_1;
|
goto error_1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(vb->mutex = SDL_CreateMutex())) {
|
vb->rendering_frame = av_frame_alloc();
|
||||||
|
if (!vb->rendering_frame) {
|
||||||
goto error_2;
|
goto error_2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ok = sc_mutex_init(&vb->mutex);
|
||||||
|
if (!ok) {
|
||||||
|
goto error_3;
|
||||||
|
}
|
||||||
|
|
||||||
vb->render_expired_frames = render_expired_frames;
|
vb->render_expired_frames = render_expired_frames;
|
||||||
if (render_expired_frames) {
|
if (render_expired_frames) {
|
||||||
if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
|
ok = sc_cond_init(&vb->pending_frame_consumed_cond);
|
||||||
SDL_DestroyMutex(vb->mutex);
|
if (!ok) {
|
||||||
|
sc_mutex_destroy(&vb->mutex);
|
||||||
goto error_2;
|
goto error_2;
|
||||||
}
|
}
|
||||||
// interrupted is not used if expired frames are not rendered
|
// interrupted is not used if expired frames are not rendered
|
||||||
@ -38,12 +45,14 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
|
|||||||
|
|
||||||
// there is initially no rendering frame, so consider it has already been
|
// there is initially no rendering frame, so consider it has already been
|
||||||
// consumed
|
// consumed
|
||||||
vb->rendering_frame_consumed = true;
|
vb->pending_frame_consumed = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error_2:
|
error_3:
|
||||||
av_frame_free(&vb->rendering_frame);
|
av_frame_free(&vb->rendering_frame);
|
||||||
|
error_2:
|
||||||
|
av_frame_free(&vb->pending_frame);
|
||||||
error_1:
|
error_1:
|
||||||
av_frame_free(&vb->decoding_frame);
|
av_frame_free(&vb->decoding_frame);
|
||||||
error_0:
|
error_0:
|
||||||
@ -53,60 +62,78 @@ error_0:
|
|||||||
void
|
void
|
||||||
video_buffer_destroy(struct video_buffer *vb) {
|
video_buffer_destroy(struct video_buffer *vb) {
|
||||||
if (vb->render_expired_frames) {
|
if (vb->render_expired_frames) {
|
||||||
SDL_DestroyCond(vb->rendering_frame_consumed_cond);
|
sc_cond_destroy(&vb->pending_frame_consumed_cond);
|
||||||
}
|
}
|
||||||
SDL_DestroyMutex(vb->mutex);
|
sc_mutex_destroy(&vb->mutex);
|
||||||
av_frame_free(&vb->rendering_frame);
|
av_frame_free(&vb->rendering_frame);
|
||||||
|
av_frame_free(&vb->pending_frame);
|
||||||
av_frame_free(&vb->decoding_frame);
|
av_frame_free(&vb->decoding_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
video_buffer_swap_frames(struct video_buffer *vb) {
|
video_buffer_swap_decoding_frame(struct video_buffer *vb) {
|
||||||
|
sc_mutex_assert(&vb->mutex);
|
||||||
AVFrame *tmp = vb->decoding_frame;
|
AVFrame *tmp = vb->decoding_frame;
|
||||||
vb->decoding_frame = vb->rendering_frame;
|
vb->decoding_frame = vb->pending_frame;
|
||||||
vb->rendering_frame = tmp;
|
vb->pending_frame = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
video_buffer_swap_rendering_frame(struct video_buffer *vb) {
|
||||||
|
sc_mutex_assert(&vb->mutex);
|
||||||
|
AVFrame *tmp = vb->rendering_frame;
|
||||||
|
vb->rendering_frame = vb->pending_frame;
|
||||||
|
vb->pending_frame = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
video_buffer_offer_decoded_frame(struct video_buffer *vb,
|
video_buffer_offer_decoded_frame(struct video_buffer *vb,
|
||||||
bool *previous_frame_skipped) {
|
bool *previous_frame_skipped) {
|
||||||
mutex_lock(vb->mutex);
|
sc_mutex_lock(&vb->mutex);
|
||||||
if (vb->render_expired_frames) {
|
if (vb->render_expired_frames) {
|
||||||
// wait for the current (expired) frame to be consumed
|
// wait for the current (expired) frame to be consumed
|
||||||
while (!vb->rendering_frame_consumed && !vb->interrupted) {
|
while (!vb->pending_frame_consumed && !vb->interrupted) {
|
||||||
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
|
sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex);
|
||||||
}
|
}
|
||||||
} else if (!vb->rendering_frame_consumed) {
|
} else if (!vb->pending_frame_consumed) {
|
||||||
fps_counter_add_skipped_frame(vb->fps_counter);
|
fps_counter_add_skipped_frame(vb->fps_counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
video_buffer_swap_frames(vb);
|
video_buffer_swap_decoding_frame(vb);
|
||||||
|
|
||||||
*previous_frame_skipped = !vb->rendering_frame_consumed;
|
*previous_frame_skipped = !vb->pending_frame_consumed;
|
||||||
vb->rendering_frame_consumed = false;
|
vb->pending_frame_consumed = false;
|
||||||
|
|
||||||
mutex_unlock(vb->mutex);
|
sc_mutex_unlock(&vb->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
const AVFrame *
|
const AVFrame *
|
||||||
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
|
video_buffer_take_rendering_frame(struct video_buffer *vb) {
|
||||||
assert(!vb->rendering_frame_consumed);
|
sc_mutex_lock(&vb->mutex);
|
||||||
vb->rendering_frame_consumed = true;
|
assert(!vb->pending_frame_consumed);
|
||||||
|
vb->pending_frame_consumed = true;
|
||||||
|
|
||||||
fps_counter_add_rendered_frame(vb->fps_counter);
|
fps_counter_add_rendered_frame(vb->fps_counter);
|
||||||
|
|
||||||
|
video_buffer_swap_rendering_frame(vb);
|
||||||
|
|
||||||
if (vb->render_expired_frames) {
|
if (vb->render_expired_frames) {
|
||||||
// unblock video_buffer_offer_decoded_frame()
|
// unblock video_buffer_offer_decoded_frame()
|
||||||
cond_signal(vb->rendering_frame_consumed_cond);
|
sc_cond_signal(&vb->pending_frame_consumed_cond);
|
||||||
}
|
}
|
||||||
|
sc_mutex_unlock(&vb->mutex);
|
||||||
|
|
||||||
|
// rendering_frame is only written from this thread, no need to lock
|
||||||
return vb->rendering_frame;
|
return vb->rendering_frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
video_buffer_interrupt(struct video_buffer *vb) {
|
video_buffer_interrupt(struct video_buffer *vb) {
|
||||||
if (vb->render_expired_frames) {
|
if (vb->render_expired_frames) {
|
||||||
mutex_lock(vb->mutex);
|
sc_mutex_lock(&vb->mutex);
|
||||||
vb->interrupted = true;
|
vb->interrupted = true;
|
||||||
mutex_unlock(vb->mutex);
|
sc_mutex_unlock(&vb->mutex);
|
||||||
// wake up blocking wait
|
// wake up blocking wait
|
||||||
cond_signal(vb->rendering_frame_consumed_cond);
|
sc_cond_signal(&vb->pending_frame_consumed_cond);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,21 +4,42 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
|
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
// forward declarations
|
// forward declarations
|
||||||
typedef struct AVFrame AVFrame;
|
typedef struct AVFrame AVFrame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There are 3 frames in memory:
|
||||||
|
* - one frame is held by the decoder (decoding_frame)
|
||||||
|
* - one frame is held by the renderer (rendering_frame)
|
||||||
|
* - one frame is shared between the decoder and the renderer (pending_frame)
|
||||||
|
*
|
||||||
|
* The decoder decodes a packet into the decoding_frame (it may takes time).
|
||||||
|
*
|
||||||
|
* Once the frame is decoded, it calls video_buffer_offer_decoded_frame(),
|
||||||
|
* which swaps the decoding and pending frames.
|
||||||
|
*
|
||||||
|
* When the renderer is notified that a new frame is available, it calls
|
||||||
|
* video_buffer_take_rendering_frame() to retrieve it, which swaps the pending
|
||||||
|
* and rendering frames. The frame is valid until the next call, without
|
||||||
|
* blocking the decoder.
|
||||||
|
*/
|
||||||
|
|
||||||
struct video_buffer {
|
struct video_buffer {
|
||||||
AVFrame *decoding_frame;
|
AVFrame *decoding_frame;
|
||||||
|
AVFrame *pending_frame;
|
||||||
AVFrame *rendering_frame;
|
AVFrame *rendering_frame;
|
||||||
SDL_mutex *mutex;
|
|
||||||
|
sc_mutex mutex;
|
||||||
bool render_expired_frames;
|
bool render_expired_frames;
|
||||||
bool interrupted;
|
bool interrupted;
|
||||||
SDL_cond *rendering_frame_consumed_cond;
|
|
||||||
bool rendering_frame_consumed;
|
sc_cond pending_frame_consumed_cond;
|
||||||
|
bool pending_frame_consumed;
|
||||||
|
|
||||||
struct fps_counter *fps_counter;
|
struct fps_counter *fps_counter;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,18 +51,15 @@ void
|
|||||||
video_buffer_destroy(struct video_buffer *vb);
|
video_buffer_destroy(struct video_buffer *vb);
|
||||||
|
|
||||||
// set the decoded frame as ready for rendering
|
// 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
|
// the output flag is set to report whether the previous frame has been skipped
|
||||||
void
|
void
|
||||||
video_buffer_offer_decoded_frame(struct video_buffer *vb,
|
video_buffer_offer_decoded_frame(struct video_buffer *vb,
|
||||||
bool *previous_frame_skipped);
|
bool *previous_frame_skipped);
|
||||||
|
|
||||||
// mark the rendering frame as consumed and return it
|
// mark the rendering frame as consumed and return it
|
||||||
// MUST be called with frames->mutex locked!!!
|
// the frame is valid until the next call to this function
|
||||||
// the caller is expected to render the returned frame to some texture before
|
|
||||||
// unlocking frames->mutex
|
|
||||||
const AVFrame *
|
const AVFrame *
|
||||||
video_buffer_consume_rendered_frame(struct video_buffer *vb);
|
video_buffer_take_rendering_frame(struct video_buffer *vb);
|
||||||
|
|
||||||
// wake up and avoid any blocking call
|
// wake up and avoid any blocking call
|
||||||
void
|
void
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
|
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
@ -138,7 +137,7 @@ static void test_strquote(void) {
|
|||||||
// add '"' at the beginning and the end
|
// add '"' at the beginning and the end
|
||||||
assert(!strcmp("\"abcde\"", out));
|
assert(!strcmp("\"abcde\"", out));
|
||||||
|
|
||||||
SDL_free(out);
|
free(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_utf8_truncate(void) {
|
static void test_utf8_truncate(void) {
|
||||||
|
Reference in New Issue
Block a user