Compare commits

..

8 Commits

Author SHA1 Message Date
Romain Vimont
5f2cf12acf Add intermediate frame in video buffer
There were only two frames simultaneously:
 - one used by the decoder;
 - one used by the renderer.

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

Adding a third frame provides several benefits:
 - the decoder do not have to wait for the renderer to release the
   mutex;
 - it simplifies the video_buffer API;
 - it makes the rendering frame valid until the next call to
   video_buffer_take_rendering_frame(), which will be useful for
   swscaling on window resize.
2021-02-04 08:12:22 +01:00
Romain Vimont
2a3c6b64dd Assert non-recursive usage of mutexes 2021-02-04 08:12:22 +01:00
Romain Vimont
a9582b1d43 Add mutex assertions 2021-02-04 08:12:22 +01:00
Romain Vimont
fa3e84b700 Expose mutex assertions
Add a function to assert that the mutex is held (or not).
2021-02-04 08:12:22 +01:00
Romain Vimont
1521de9051 Expose thread id 2021-02-04 08:12:22 +01:00
Romain Vimont
eabaabdb78 Wrap SDL thread functions into scrcpy-specific API
The goal is to expose a consistent API for system tools, and paves the
way to make the "core" independant of SDL in the future.
2021-02-04 08:12:22 +01:00
Romain Vimont
8b48003074 Replace SDL_strdup() by strdup()
The functions SDL_malloc(), SDL_free() and SDL_strdup() were used only
because strdup() was not available everywhere.

Now that it is available, use the native version of these functions.
2021-02-04 08:12:22 +01:00
Romain Vimont
74bd25a0ed Provide strdup() compat
Make strdup() available on all platforms.
2021-02-04 08:12:22 +01:00
23 changed files with 304 additions and 726 deletions

View File

@@ -15,7 +15,6 @@ src = [
'src/opengl.c',
'src/receiver.c',
'src/recorder.c',
'src/resizer.c',
'src/scrcpy.c',
'src/screen.c',
'src/server.c',
@@ -47,7 +46,6 @@ if not get_option('crossbuild_windows')
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavutil'),
dependency('libswscale'),
dependency('sdl2'),
]
@@ -101,6 +99,9 @@ foreach f : check_functions
endif
endforeach
# expose the build type
conf.set('NDEBUG', get_option('buildtype') != 'debug')
# the version, updated on release
conf.set_quoted('SCRCPY_VERSION', meson.project_version())

View File

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

View File

@@ -103,6 +103,11 @@ scrcpy_print_usage(const char *arg0) {
" --no-key-repeat\n"
" Do not forward repeated key events when a key is held down.\n"
"\n"
" --no-mipmaps\n"
" If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then\n"
" mipmaps are automatically generated to improve downscaling\n"
" quality. This option disables the generation of mipmaps.\n"
"\n"
" -p, --port port[:port]\n"
" Set the TCP port (range) used by the client to listen.\n"
" Default is %d:%d.\n"
@@ -149,11 +154,6 @@ scrcpy_print_usage(const char *arg0) {
" The device serial number. Mandatory only if several devices\n"
" are connected to adb.\n"
"\n"
" --scale-filter filter\n"
" Supported filters are \"none\" and \"trilinear\".\n"
" Trilinear filtering is only available if the renderer is\n"
" OpenGL 3.0+ or OpenGL ES 2.0+.\n"
"\n"
" --shortcut-mod key[+...]][,...]\n"
" Specify the modifiers to use for scrcpy shortcuts.\n"
" Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\",\n"
@@ -641,63 +641,6 @@ guess_record_format(const char *filename) {
return 0;
}
static bool
parse_scale_filter(const char *optarg, enum sc_scale_filter *filter) {
// TODO store in a map and loop over the entries instead
if (!strcmp(optarg, "none")) {
*filter = SC_SCALE_FILTER_NONE;
return true;
}
if (!strcmp(optarg, "trilinear")) {
*filter = SC_SCALE_FILTER_TRILINEAR;
return true;
}
if (!strcmp(optarg, "bilinear")) {
*filter = SC_SCALE_FILTER_BILINEAR;
return true;
}
if (!strcmp(optarg, "bicubic")) {
*filter = SC_SCALE_FILTER_BICUBIC;
return true;
}
if (!strcmp(optarg, "x")) {
*filter = SC_SCALE_FILTER_X;
return true;
}
if (!strcmp(optarg, "point")) {
*filter = SC_SCALE_FILTER_POINT;
return true;
}
if (!strcmp(optarg, "area")) {
*filter = SC_SCALE_FILTER_AREA;
return true;
}
if (!strcmp(optarg, "bicublin")) {
*filter = SC_SCALE_FILTER_BICUBLIN;
return true;
}
if (!strcmp(optarg, "gauss")) {
*filter = SC_SCALE_FILTER_GAUSS;
return true;
}
if (!strcmp(optarg, "sinc")) {
*filter = SC_SCALE_FILTER_SINC;
return true;
}
if (!strcmp(optarg, "lanczos")) {
*filter = SC_SCALE_FILTER_LANCZOS;
return true;
}
if (!strcmp(optarg, "spline")) {
*filter = SC_SCALE_FILTER_SPLINE;
return true;
}
LOGE("Unsupported scale filter: %s "
"(expected one of [TODO])", optarg);
return false;
}
#define OPT_RENDER_EXPIRED_FRAMES 1000
#define OPT_WINDOW_TITLE 1001
#define OPT_PUSH_TARGET 1002
@@ -724,7 +667,6 @@ parse_scale_filter(const char *optarg, enum sc_scale_filter *filter) {
#define OPT_FORWARD_ALL_CLICKS 1023
#define OPT_LEGACY_PASTE 1024
#define OPT_ENCODER_NAME 1025
#define OPT_SCALE_FILTER 1026
bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
@@ -761,7 +703,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"render-expired-frames", no_argument, NULL,
OPT_RENDER_EXPIRED_FRAMES},
{"rotation", required_argument, NULL, OPT_ROTATION},
{"scale-filter", required_argument, NULL, OPT_SCALE_FILTER},
{"serial", required_argument, NULL, 's'},
{"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD},
{"show-touches", no_argument, NULL, 't'},
@@ -916,9 +857,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
opts->render_driver = optarg;
break;
case OPT_NO_MIPMAPS:
LOGW("Deprecated option --no-mipmaps. "
"Use --scale-filter=none instead.");
opts->scale_filter = SC_SCALE_FILTER_NONE;
opts->mipmaps = false;
break;
case OPT_NO_KEY_REPEAT:
opts->forward_key_repeat = false;
@@ -946,11 +885,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_LEGACY_PASTE:
opts->legacy_paste = true;
break;
case OPT_SCALE_FILTER:
if (!parse_scale_filter(optarg, &opts->scale_filter)) {
return false;
}
break;
default:
// getopt prints the error message on stderr
return false;

View File

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

View File

@@ -14,7 +14,17 @@
// set the decoded frame as ready for rendering, and notify
static void
push_frame(struct decoder *decoder) {
video_buffer_producer_offer_frame(decoder->video_buffer, &decoder->frame);
bool previous_frame_skipped;
video_buffer_offer_decoded_frame(decoder->video_buffer,
&previous_frame_skipped);
if (previous_frame_skipped) {
// the previous EVENT_NEW_FRAME will consume this frame
return;
}
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
SDL_PushEvent(&new_frame_event);
}
void
@@ -36,19 +46,11 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) {
return false;
}
decoder->frame = av_frame_alloc();
if (!decoder->frame) {
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
return false;
}
return true;
}
void
decoder_close(struct decoder *decoder) {
av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
}
@@ -63,7 +65,8 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
LOGE("Could not send video packet: %d", ret);
return false;
}
ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
ret = avcodec_receive_frame(decoder->codec_ctx,
decoder->video_buffer->decoding_frame);
if (!ret) {
// a frame was received
push_frame(decoder);
@@ -74,7 +77,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
#else
int got_picture;
int len = avcodec_decode_video2(decoder->codec_ctx,
decoder->frame,
decoder->video_buffer->decoding_frame,
&got_picture,
packet);
if (len < 0) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -286,7 +286,7 @@ rotate_client_right(struct screen *screen) {
screen_set_rotation(screen, new_rotation);
}
static void
void
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) {
if (is_shortcut_mod(im, SDL_GetModState())) {
@@ -366,7 +366,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
return true;
}
static void
void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event) {
// control: indicates the state of the command-line option --no-control
@@ -480,7 +480,9 @@ input_manager_process_key(struct input_manager *im,
return;
case SDLK_i:
if (!shift && !repeat && down) {
switch_fps_counter_state(im->fps_counter);
struct fps_counter *fps_counter =
im->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter);
}
return;
case SDLK_n:
@@ -549,7 +551,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
return true;
}
static void
void
input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event) {
if (!event->state) {
@@ -603,7 +605,7 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
return true;
}
static void
void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event) {
struct control_msg msg;
@@ -635,7 +637,7 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
return true;
}
static void
void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event) {
bool control = im->control;
@@ -734,7 +736,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
return true;
}
static void
void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event) {
struct control_msg msg;
@@ -744,46 +746,3 @@ input_manager_process_mouse_wheel(struct input_manager *im,
}
}
}
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event) {
switch (event->type) {
case SDL_TEXTINPUT:
if (!im->control) {
return true;
}
input_manager_process_text_input(im, &event->text);
return true;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(im, &event->key);
return true;
case SDL_MOUSEMOTION:
if (!im->control) {
break;
}
input_manager_process_mouse_motion(im, &event->motion);
return true;
case SDL_MOUSEWHEEL:
if (!im->control) {
break;
}
input_manager_process_mouse_wheel(im, &event->wheel);
return true;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(im, &event->button);
return true;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(im, &event->tfinger);
return true;
}
return false;
}

View File

@@ -16,7 +16,6 @@
struct input_manager {
struct controller *controller;
struct video_buffer *video_buffer;
struct fps_counter *fps_counter;
struct screen *screen;
// SDL reports repeated events as a boolean, but Android expects the actual
@@ -41,7 +40,28 @@ void
input_manager_init(struct input_manager *im,
const struct scrcpy_options *options);
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event);
void
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event);
void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event);
void
input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event);
void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event);
void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event);
void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event);
#endif

View File

@@ -1,183 +0,0 @@
#include "resizer.h"
#include <assert.h>
#include <libswscale/swscale.h>
#include "util/log.h"
bool
sc_resizer_init(struct sc_resizer *resizer, struct video_buffer *vb_in,
struct video_buffer *vb_out, enum sc_scale_filter scale_filter,
struct size size) {
bool ok = sc_mutex_init(&resizer->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&resizer->req_cond);
if (!ok) {
sc_mutex_destroy(&resizer->mutex);
return false;
}
resizer->resized_frame = av_frame_alloc();
if (!resizer->resized_frame) {
sc_cond_destroy(&resizer->req_cond);
sc_mutex_destroy(&resizer->mutex);
return false;
}
resizer->vb_in = vb_in;
resizer->vb_out = vb_out;
resizer->scale_filter = scale_filter;
resizer->size = size;
resizer->input_frame = NULL;
resizer->has_request = false;
resizer->has_new_frame = false;
resizer->interrupted = false;
return true;
}
void
sc_resizer_destroy(struct sc_resizer *resizer) {
av_frame_free(&resizer->resized_frame);
sc_cond_destroy(&resizer->req_cond);
sc_mutex_destroy(&resizer->mutex);
}
static int
to_sws_filter(enum sc_scale_filter scale_filter) {
switch (scale_filter) {
case SC_SCALE_FILTER_BILINEAR: return SWS_BILINEAR;
case SC_SCALE_FILTER_BICUBIC: return SWS_BICUBIC;
case SC_SCALE_FILTER_X: return SWS_X;
case SC_SCALE_FILTER_POINT: return SWS_POINT;
case SC_SCALE_FILTER_AREA: return SWS_AREA;
case SC_SCALE_FILTER_BICUBLIN: return SWS_BICUBLIN;
case SC_SCALE_FILTER_GAUSS: return SWS_GAUSS;
case SC_SCALE_FILTER_SINC: return SWS_SINC;
case SC_SCALE_FILTER_LANCZOS: return SWS_LANCZOS;
case SC_SCALE_FILTER_SPLINE: return SWS_SPLINE;
default: assert(!"unsupported filter");
}
}
static bool
sc_resizer_swscale(struct sc_resizer *resizer) {
assert(!resizer->resized_frame->buf[0]); // The frame must be "empty"
assert(resizer->size.width);
assert(resizer->size.height);
const AVFrame *in = resizer->input_frame;
struct size size = resizer->size;
AVFrame *out = resizer->resized_frame;
out->format = AV_PIX_FMT_RGB24;
out->width = size.width;
out->height = size.height;
int ret = av_frame_get_buffer(out, 32);
if (ret < 0) {
return false;
}
int flags = to_sws_filter(resizer->scale_filter);
struct SwsContext *ctx =
sws_getContext(in->width, in->height, AV_PIX_FMT_YUV420P,
size.width, size.height, AV_PIX_FMT_RGB24, flags, NULL,
NULL, NULL);
if (!ctx) {
av_frame_unref(out);
return false;
}
sws_scale(ctx, (const uint8_t *const *) in->data, in->linesize, 0,
in->height, out->data, out->linesize);
sws_freeContext(ctx);
return true;
}
static int
run_resizer(void *data) {
struct sc_resizer *resizer = data;
sc_mutex_lock(&resizer->mutex);
while (!resizer->interrupted) {
while (!resizer->interrupted && !resizer->has_request) {
sc_cond_wait(&resizer->req_cond, &resizer->mutex);
}
if (resizer->has_new_frame) {
unsigned skipped;
resizer->input_frame =
video_buffer_consumer_take_frame(resizer->vb_in, &skipped);
(void) skipped; // FIXME forward skipped frames count
resizer->has_new_frame = false;
}
resizer->has_request = false;
sc_mutex_unlock(&resizer->mutex);
// Do the actual work without mutex
sc_resizer_swscale(resizer);
video_buffer_producer_offer_frame(resizer->vb_out,
&resizer->resized_frame);
sc_mutex_lock(&resizer->mutex);
}
sc_mutex_unlock(&resizer->mutex);
return 0;
}
bool
sc_resizer_start(struct sc_resizer *resizer) {
LOGD("Starting resizer thread");
bool ok = sc_thread_create(&resizer->thread, run_resizer, "resizer",
resizer);
if (!ok) {
LOGE("Could not start resizer thread");
return false;
}
return true;
}
void
sc_resizer_stop(struct sc_resizer *resizer) {
sc_mutex_lock(&resizer->mutex);
resizer->interrupted = true;
sc_cond_signal(&resizer->req_cond);
sc_mutex_unlock(&resizer->mutex);
video_buffer_interrupt(resizer->vb_out);
}
void
sc_resizer_join(struct sc_resizer *resizer) {
sc_thread_join(&resizer->thread, NULL);
}
void
sc_resizer_process_new_frame(struct sc_resizer *resizer) {
sc_mutex_lock(&resizer->mutex);
resizer->has_request = true;
resizer->has_new_frame = true;
sc_cond_signal(&resizer->req_cond);
sc_mutex_unlock(&resizer->mutex);
}
void
sc_resizer_process_new_size(struct sc_resizer *resizer, struct size size) {
sc_mutex_lock(&resizer->mutex);
resizer->size = size;
resizer->has_request = true;
sc_cond_signal(&resizer->req_cond);
sc_mutex_unlock(&resizer->mutex);
}

View File

@@ -1,56 +0,0 @@
#ifndef SC_RESIZER
#define SC_RESIZER
#include "common.h"
#include <stdbool.h>
#include <libavformat/avformat.h>
#include "coords.h"
#include "scrcpy.h"
#include "util/thread.h"
#include "video_buffer.h"
struct sc_resizer {
struct video_buffer *vb_in;
struct video_buffer *vb_out;
enum sc_scale_filter scale_filter;
struct size size;
// valid until the next call to video_buffer_consumer_take_frame(vb_in)
const AVFrame *input_frame;
AVFrame *resized_frame;
sc_thread thread;
sc_mutex mutex;
sc_cond req_cond;
bool has_request;
bool has_new_frame;
bool interrupted;
};
bool
sc_resizer_init(struct sc_resizer *resizer, struct video_buffer *vb_in,
struct video_buffer *vb_out, enum sc_scale_filter scale_filter,
struct size size);
void
sc_resizer_destroy(struct sc_resizer *resizer);
bool
sc_resizer_start(struct sc_resizer *resizer);
void
sc_resizer_stop(struct sc_resizer *resizer);
void
sc_resizer_join(struct sc_resizer *resizer);
void
sc_resizer_process_new_frame(struct sc_resizer *resizer);
void
sc_resizer_process_new_size(struct sc_resizer *resizer, struct size size);
#endif

View File

@@ -30,7 +30,7 @@
#include "util/net.h"
static struct server server;
static struct screen screen;
static struct screen screen = SCREEN_INITIALIZER;
static struct fps_counter fps_counter;
static struct video_buffer video_buffer;
static struct stream stream;
@@ -42,7 +42,6 @@ static struct file_handler file_handler;
static struct input_manager input_manager = {
.controller = &controller,
.video_buffer = &video_buffer,
.fps_counter = &fps_counter,
.screen = &screen,
.repeat = 0,
@@ -174,6 +173,54 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) {
case SDL_QUIT:
LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = true;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &video_buffer)) {
return EVENT_RESULT_CONTINUE;
}
break;
case SDL_WINDOWEVENT:
screen_handle_window_event(&screen, &event->window);
break;
case SDL_TEXTINPUT:
if (!options->control) {
break;
}
input_manager_process_text_input(&input_manager, &event->text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(&input_manager, &event->key);
break;
case SDL_MOUSEMOTION:
if (!options->control) {
break;
}
input_manager_process_mouse_motion(&input_manager, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!options->control) {
break;
}
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(&input_manager, &event->button);
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(&input_manager, &event->tfinger);
break;
case SDL_DROPFILE: {
if (!options->control) {
break;
@@ -195,16 +242,6 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) {
break;
}
}
bool consumed = screen_handle_event(&screen, event);
if (consumed) {
goto end;
}
consumed = input_manager_handle_event(&input_manager, event);
(void) consumed;
end:
return EVENT_RESULT_CONTINUE;
}
@@ -267,14 +304,6 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
free(local_fmt);
}
static void
video_buffer_on_frame_available(struct video_buffer *vb, void *userdata) {
(void) vb;
(void) userdata;
screen_on_frame_available(&screen);
}
bool
scrcpy(const struct scrcpy_options *options) {
if (!server_init(&server)) {
@@ -341,12 +370,8 @@ scrcpy(const struct scrcpy_options *options) {
}
fps_counter_initialized = true;
static const struct video_buffer_callbacks video_buffer_cbs = {
.on_frame_available = video_buffer_on_frame_available,
};
if (!video_buffer_init(&video_buffer, options->render_expired_frames,
&video_buffer_cbs, NULL)) {
if (!video_buffer_init(&video_buffer, &fps_counter,
options->render_expired_frames)) {
goto end;
}
video_buffer_initialized = true;
@@ -379,6 +404,13 @@ scrcpy(const struct scrcpy_options *options) {
stream_init(&stream, server.video_socket, dec, rec);
// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&stream)) {
goto end;
}
stream_started = true;
if (options->display) {
if (options->control) {
if (!controller_init(&controller, server.control_socket)) {
@@ -395,14 +427,12 @@ scrcpy(const struct scrcpy_options *options) {
const char *window_title =
options->window_title ? options->window_title : device_name;
screen_init(&screen, &video_buffer, &fps_counter);
if (!screen_init_rendering(&screen, window_title, frame_size,
options->always_on_top, options->window_x,
options->window_y, options->window_width,
options->window_height,
options->window_borderless,
options->rotation, options->scale_filter)) {
options->rotation, options->mipmaps)) {
goto end;
}
@@ -421,13 +451,6 @@ scrcpy(const struct scrcpy_options *options) {
}
}
// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&stream)) {
goto end;
}
stream_started = true;
input_manager_init(&input_manager, options);
ret = event_loop(options);

View File

@@ -41,22 +41,6 @@ struct sc_port_range {
uint16_t last;
};
enum sc_scale_filter {
SC_SCALE_FILTER_NONE,
SC_SCALE_FILTER_TRILINEAR, // mipmaps
SC_SCALE_FILTER_BILINEAR,
SC_SCALE_FILTER_BICUBIC,
SC_SCALE_FILTER_X,
SC_SCALE_FILTER_POINT,
SC_SCALE_FILTER_AREA,
SC_SCALE_FILTER_BICUBLIN,
SC_SCALE_FILTER_GAUSS,
SC_SCALE_FILTER_SINC,
SC_SCALE_FILTER_LANCZOS,
SC_SCALE_FILTER_SPLINE,
SC_SCALE_FILTER__COUNT,
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options {
@@ -72,7 +56,6 @@ struct scrcpy_options {
enum sc_record_format record_format;
struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
enum sc_scale_filter scale_filter;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
@@ -92,6 +75,7 @@ struct scrcpy_options {
bool render_expired_frames;
bool prefer_text;
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
@@ -119,7 +103,6 @@ struct scrcpy_options {
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
.count = 2, \
}, \
.scale_filter = SC_SCALE_FILTER_TRILINEAR, \
.max_size = DEFAULT_MAX_SIZE, \
.bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \
@@ -139,6 +122,7 @@ struct scrcpy_options {
.render_expired_frames = false, \
.prefer_text = false, \
.window_borderless = false, \
.mipmaps = true, \
.stay_awake = false, \
.force_adb_forward = false, \
.disable_screensaver = false, \

View File

@@ -4,8 +4,8 @@
#include <string.h>
#include <SDL2/SDL.h>
#include "events.h"
#include "icon.xpm"
#include "scrcpy.h"
#include "tiny_xpm.h"
#include "video_buffer.h"
#include "util/log.h"
@@ -191,32 +191,22 @@ screen_update_content_rect(struct screen *screen) {
}
void
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter) {
screen->vb = vb;
screen->fps_counter = fps_counter;
screen->resize_pending = false;
screen->has_frame = false;
screen->fullscreen = false;
screen->maximized = false;
screen_init(struct screen *screen) {
*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_PixelFormatEnum fmt = screen->use_swscale ? SDL_PIXELFORMAT_RGB24
: SDL_PIXELFORMAT_YV12;
SDL_Texture *texture = SDL_CreateTexture(renderer, fmt,
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
if (!texture) {
return NULL;
}
if (screen->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
assert(!screen->use_swscale);
if (screen->mipmaps) {
struct sc_opengl *gl = &screen->gl;
SDL_GL_BindTexture(texture, NULL, NULL);
@@ -232,31 +222,12 @@ create_texture(struct screen *screen) {
return texture;
}
static inline bool
is_swscale(enum sc_scale_filter scale_filter) {
return scale_filter != SC_SCALE_FILTER_NONE
&& scale_filter != SC_SCALE_FILTER_TRILINEAR;
}
static void
on_resizer_frame_available(struct video_buffer *vb, void *userdata) {
(void) vb;
(void) userdata;
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
}
bool
screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless,
uint8_t rotation, enum sc_scale_filter scale_filter) {
uint8_t rotation, bool mipmaps) {
screen->frame_size = frame_size;
screen->rotation = rotation;
if (rotation) {
@@ -299,7 +270,7 @@ screen_init_rendering(struct screen *screen, const char *window_title,
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError());
SDL_DestroyWindow(screen->window);
screen_destroy(screen);
return false;
}
@@ -309,9 +280,8 @@ screen_init_rendering(struct screen *screen, const char *window_title,
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
// starts with "opengl"
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
bool mipmaps = scale_filter == SC_SCALE_FILTER_TRILINEAR;
if (use_opengl) {
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (screen->use_opengl) {
struct sc_opengl *gl = &screen->gl;
sc_opengl_init(gl);
@@ -323,54 +293,18 @@ screen_init_rendering(struct screen *screen, const char *window_title,
2, 0 /* OpenGL ES 2.0+ */);
if (supports_mipmaps) {
LOGI("Trilinear filtering enabled");
screen->mipmaps = true;
} else {
LOGW("Trilinear filtering disabled "
"(OpenGL 3.0+ or ES 2.0+ required)");
scale_filter = SC_SCALE_FILTER_NONE;
}
} else {
LOGI("Trilinear filtering disabled");
}
} else if (mipmaps) {
} else {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
screen->use_swscale = is_swscale(scale_filter);
if (screen->use_swscale) {
static const struct video_buffer_callbacks video_buffer_cbs = {
.on_frame_available = on_resizer_frame_available,
};
bool ok = video_buffer_init(&screen->resizer_vb, false,
&video_buffer_cbs, NULL);
if (!ok) {
LOGE("Could not create resizer video buffer");
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
return false;
}
ok = sc_resizer_init(&screen->resizer, screen->vb, &screen->resizer_vb,
scale_filter, window_size);
if (!ok) {
LOGE("Could not create resizer");
video_buffer_destroy(&screen->resizer_vb);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
return false;
}
ok = sc_resizer_start(&screen->resizer);
if (!ok) {
LOGE("Could not start resizer");
sc_resizer_destroy(&screen->resizer);
video_buffer_destroy(&screen->resizer_vb);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
}
}
screen->scale_filter = scale_filter;
SDL_Surface *icon = read_xpm(icon_xpm);
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
@@ -384,8 +318,7 @@ screen_init_rendering(struct screen *screen, const char *window_title,
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
screen_destroy(screen);
return false;
}
@@ -406,16 +339,15 @@ screen_show_window(struct screen *screen) {
void
screen_destroy(struct screen *screen) {
if (screen->use_swscale) {
sc_resizer_stop(&screen->resizer);
sc_resizer_join(&screen->resizer);
sc_resizer_destroy(&screen->resizer);
}
if (screen->texture) {
SDL_DestroyTexture(screen->texture);
}
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
if (screen->renderer) {
SDL_DestroyRenderer(screen->renderer);
}
if (screen->window) {
SDL_DestroyWindow(screen->window);
}
}
static void
@@ -506,35 +438,25 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
// write the frame into the texture
static void
update_texture(struct screen *screen, const AVFrame *frame) {
if (screen->use_swscale) {
SDL_UpdateTexture(screen->texture, NULL, frame->data[0],
frame->linesize[0]);
} else {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (screen->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture);
}
if (screen->mipmaps) {
assert(screen->use_opengl);
SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture);
}
}
static bool
screen_update_frame(struct screen *screen) {
unsigned skipped;
struct video_buffer *vb = screen->use_swscale ? &screen->resizer_vb
: screen->vb;
const AVFrame *frame = video_buffer_consumer_take_frame(vb, &skipped);
fps_counter_add_skipped_frames(screen->fps_counter, skipped);
fps_counter_add_rendered_frame(screen->fps_counter);
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb) {
const AVFrame *frame = video_buffer_take_rendering_frame(vb);
struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
sc_mutex_unlock(&vb->mutex);
return false;
}
update_texture(screen, frame);
@@ -624,52 +546,31 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
content_size.height);
}
bool
screen_handle_event(struct screen *screen, SDL_Event *event) {
switch (event->type) {
case EVENT_NEW_FRAME:
if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
screen_show_window(screen);
void
screen_handle_window_event(struct screen *screen,
const SDL_WindowEvent *event) {
switch (event->event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling fullscreen
// mode unexpectedly triggers the "restored" then "maximized"
// events, leaving the window in a weird state (maximized
// according to the events, but not maximized visually).
break;
}
bool ok = screen_update_frame(screen);
if (!ok) {
LOGW("Frame update failed\n");
}
return true;
case SDL_WINDOWEVENT:
if (!screen->has_frame) {
// Do nothing
return true;
}
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling
// fullscreen mode unexpectedly triggers the "restored"
// then "maximized" events, leaving the window in a
// weird state (maximized according to the events, but
// not maximized visually).
break;
}
screen->maximized = false;
apply_pending_resize(screen);
break;
}
return true;
screen->maximized = false;
apply_pending_resize(screen);
break;
}
return false;
}
struct point
@@ -727,17 +628,3 @@ screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
*x = (int64_t) *x * dw / ww;
*y = (int64_t) *y * dh / wh;
}
void
screen_on_frame_available(struct screen *screen) {
if (screen->use_swscale) {
sc_resizer_process_new_frame(&screen->resizer);
} else {
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
}
}

View File

@@ -9,19 +9,14 @@
#include "coords.h"
#include "opengl.h"
#include "resizer.h"
#include "scrcpy.h"
#include "video_buffer.h"
struct video_buffer;
struct screen {
struct video_buffer *vb;
struct fps_counter *fps_counter;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
bool use_opengl;
struct sc_opengl gl;
struct size frame_size;
struct size content_size; // rotated frame_size
@@ -38,20 +33,46 @@ struct screen {
bool has_frame;
bool fullscreen;
bool maximized;
bool no_window;
bool mipmaps;
enum sc_scale_filter scale_filter;
bool use_swscale;
// For swscale resizing
struct video_buffer resizer_vb;
struct sc_resizer resizer;
};
#define SCREEN_INITIALIZER { \
.window = NULL, \
.renderer = NULL, \
.texture = NULL, \
.use_opengl = false, \
.gl = {0}, \
.frame_size = { \
.width = 0, \
.height = 0, \
}, \
.content_size = { \
.width = 0, \
.height = 0, \
}, \
.resize_pending = false, \
.windowed_content_size = { \
.width = 0, \
.height = 0, \
}, \
.rotation = 0, \
.rect = { \
.x = 0, \
.y = 0, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \
.fullscreen = false, \
.maximized = false, \
.no_window = false, \
.mipmaps = false, \
}
// initialize default values
void
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter);
screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
@@ -60,7 +81,7 @@ screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless,
uint8_t rotation, enum sc_scale_filter scale_filter);
uint8_t rotation, bool mipmaps);
// show the window
void
@@ -70,6 +91,10 @@ screen_show_window(struct screen *screen);
void
screen_destroy(struct screen *screen);
// resize if necessary and write the rendered frame into the texture
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb);
// render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
@@ -93,9 +118,9 @@ screen_resize_to_pixel_perfect(struct screen *screen);
void
screen_set_rotation(struct screen *screen, unsigned rotation);
// react to SDL events
bool
screen_handle_event(struct screen *screen, SDL_Event *event);
// react to window events
void
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
@@ -116,9 +141,4 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y);
// Notify the screen that a new frame is available in the video_buffer.
// Called from a separate thread.
void
screen_on_frame_available(struct screen *screen);
#endif

View File

@@ -375,6 +375,8 @@ server_init(struct server *server) {
server->video_socket = INVALID_SOCKET;
server->control_socket = INVALID_SOCKET;
server->port_range.first = 0;
server->port_range.last = 0;
server->local_port = 0;
server->tunnel_enabled = false;
@@ -408,6 +410,8 @@ run_wait_server(void *data) {
bool
server_start(struct server *server, const char *serial,
const struct server_params *params) {
server->port_range = params->port_range;
if (serial) {
server->serial = strdup(serial);
if (!server->serial) {

View File

@@ -26,6 +26,7 @@ struct server {
socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket;
socket_t control_socket;
struct sc_port_range port_range;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"

View File

@@ -61,7 +61,6 @@ sc_mutex_lock(sc_mutex *mutex) {
void
sc_mutex_unlock(sc_mutex *mutex) {
#ifndef NDEBUG
assert(sc_mutex_held(mutex));
mutex->locker = 0;
#endif
int r = SDL_UnlockMutex(mutex->mutex);

View File

@@ -7,130 +7,129 @@
#include "util/log.h"
bool
video_buffer_init(struct video_buffer *vb, bool wait_consumer,
const struct video_buffer_callbacks *cbs,
void *cbs_userdata) {
vb->pending_frame = av_frame_alloc();
if (!vb->pending_frame) {
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
bool render_expired_frames) {
vb->fps_counter = fps_counter;
vb->decoding_frame = av_frame_alloc();
if (!vb->decoding_frame) {
goto error_0;
}
vb->consumer_frame = av_frame_alloc();
if (!vb->consumer_frame) {
vb->pending_frame = av_frame_alloc();
if (!vb->pending_frame) {
goto error_1;
}
vb->rendering_frame = av_frame_alloc();
if (!vb->rendering_frame) {
goto error_2;
}
bool ok = sc_mutex_init(&vb->mutex);
if (!ok) {
goto error_2;
goto error_3;
}
vb->wait_consumer = wait_consumer;
if (wait_consumer) {
vb->render_expired_frames = render_expired_frames;
if (render_expired_frames) {
ok = sc_cond_init(&vb->pending_frame_consumed_cond);
if (!ok) {
sc_mutex_destroy(&vb->mutex);
goto error_2;
}
// interrupted is not used if wait_consumer is disabled since offering
// a frame will never block
// interrupted is not used if expired frames are not rendered
// since offering a frame will never block
vb->interrupted = false;
}
// there is initially no frame, so consider it has already been consumed
// there is initially no rendering frame, so consider it has already been
// consumed
vb->pending_frame_consumed = true;
vb->skipped = 0;
assert(cbs);
assert(cbs->on_frame_available);
vb->cbs = cbs;
vb->cbs_userdata = cbs_userdata;
return true;
error_3:
av_frame_free(&vb->rendering_frame);
error_2:
av_frame_free(&vb->consumer_frame);
error_1:
av_frame_free(&vb->pending_frame);
error_1:
av_frame_free(&vb->decoding_frame);
error_0:
return false;
}
void
video_buffer_destroy(struct video_buffer *vb) {
if (vb->wait_consumer) {
if (vb->render_expired_frames) {
sc_cond_destroy(&vb->pending_frame_consumed_cond);
}
sc_mutex_destroy(&vb->mutex);
av_frame_free(&vb->consumer_frame);
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->pending_frame);
av_frame_free(&vb->decoding_frame);
}
static inline void
swap_frames(AVFrame **lhs, AVFrame **rhs) {
AVFrame *tmp = *lhs;
*lhs = *rhs;
*rhs = tmp;
static void
video_buffer_swap_decoding_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->decoding_frame;
vb->decoding_frame = vb->pending_frame;
vb->pending_frame = tmp;
}
static void
video_buffer_swap_rendering_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->rendering_frame;
vb->rendering_frame = vb->pending_frame;
vb->pending_frame = tmp;
}
void
video_buffer_producer_offer_frame(struct video_buffer *vb, AVFrame **pframe) {
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped) {
sc_mutex_lock(&vb->mutex);
if (vb->wait_consumer) {
if (vb->render_expired_frames) {
// wait for the current (expired) frame to be consumed
while (!vb->pending_frame_consumed && !vb->interrupted) {
sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex);
}
} else if (!vb->pending_frame_consumed) {
fps_counter_add_skipped_frame(vb->fps_counter);
}
av_frame_unref(vb->pending_frame);
swap_frames(pframe, &vb->pending_frame);
bool skipped = !vb->pending_frame_consumed;
if (skipped) {
++vb->skipped;
}
video_buffer_swap_decoding_frame(vb);
*previous_frame_skipped = !vb->pending_frame_consumed;
vb->pending_frame_consumed = false;
sc_mutex_unlock(&vb->mutex);
if (!skipped) {
// If skipped, then the previous call will consume this frame, the
// callback must not be called
vb->cbs->on_frame_available(vb, vb->cbs_userdata);
}
}
const AVFrame *
video_buffer_consumer_take_frame(struct video_buffer *vb, unsigned *skipped) {
video_buffer_take_rendering_frame(struct video_buffer *vb) {
sc_mutex_lock(&vb->mutex);
assert(!vb->pending_frame_consumed);
vb->pending_frame_consumed = true;
swap_frames(&vb->consumer_frame, &vb->pending_frame);
av_frame_unref(vb->pending_frame);
fps_counter_add_rendered_frame(vb->fps_counter);
if (vb->wait_consumer) {
video_buffer_swap_rendering_frame(vb);
if (vb->render_expired_frames) {
// unblock video_buffer_offer_decoded_frame()
sc_cond_signal(&vb->pending_frame_consumed_cond);
}
if (skipped) {
*skipped = vb->skipped;
}
vb->skipped = 0; // reset
sc_mutex_unlock(&vb->mutex);
// consumer_frame is only written from this thread, no need to lock
return vb->consumer_frame;
// rendering_frame is only written from this thread, no need to lock
return vb->rendering_frame;
}
void
video_buffer_interrupt(struct video_buffer *vb) {
if (vb->wait_consumer) {
if (vb->render_expired_frames) {
sc_mutex_lock(&vb->mutex);
vb->interrupted = true;
sc_mutex_unlock(&vb->mutex);

View File

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

View File

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