Compare commits

..

13 Commits

Author SHA1 Message Date
Romain Vimont
e306bffbc0 Use a callback to notify a new decoded frame
Make the decoder independant of the SDL event mechanism.
2021-02-14 15:31:31 +01:00
Romain Vimont
2512c53227 Improve error handling in screen initialization
After the struct screen is initialized, the window and the renderer are
necessarily valid, so there is no need o check in screen_destroy().
2021-02-14 14:47:49 +01:00
Romain Vimont
3cdbd00430 Handle window events only once visible
This will avoid corner cases where we need to resize while no frame has
been received yet.
2021-02-14 14:47:49 +01:00
Romain Vimont
93a8ef20a6 Log mipmaps error only if mipmaps are enabled 2021-02-14 14:47:49 +01:00
Romain Vimont
8c845efe9a Make use_opengl local
The flag is used only locally, there is no need to store it in the
screen structure.
2021-02-14 14:47:49 +01:00
Romain Vimont
bf38331c76 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-14 14:47:49 +01:00
Romain Vimont
7f6069213d Assert non-recursive usage of mutexes 2021-02-14 14:47:49 +01:00
Romain Vimont
5c4462ce70 Add mutex assertions 2021-02-14 14:47:49 +01:00
Romain Vimont
c894a0b937 Expose mutex assertions
Add a function to assert that the mutex is held (or not).
2021-02-14 14:47:49 +01:00
Romain Vimont
569e188254 Expose thread id 2021-02-14 14:47:49 +01:00
Romain Vimont
152fb0f915 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-14 14:47:49 +01:00
Romain Vimont
de3c1a7b4c 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-14 14:47:49 +01:00
Romain Vimont
e44714d2d1 Provide strdup() compat
Make strdup() available on all platforms.
2021-02-14 14:47:49 +01:00
14 changed files with 231 additions and 263 deletions

View File

@@ -99,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

@@ -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

@@ -2,7 +2,6 @@
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_events.h>
#include <unistd.h>
#include "events.h"
@@ -14,12 +13,26 @@
// 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);
bool previous_frame_skipped;
video_buffer_offer_decoded_frame(decoder->video_buffer,
&previous_frame_skipped);
if (previous_frame_skipped) {
// the previous callback will consume this frame
return;
}
decoder->cbs->on_new_frame(decoder, decoder->cbs_userdata);
}
void
decoder_init(struct decoder *decoder, struct video_buffer *vb) {
decoder_init(struct decoder *decoder, struct video_buffer *vb,
const struct decoder_callbacks *cbs, void *cbs_userdata) {
decoder->video_buffer = vb;
assert(cbs);
assert(cbs->on_new_frame);
decoder->cbs = cbs;
decoder->cbs_userdata = cbs_userdata;
}
bool
@@ -56,7 +69,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
return false;
}
ret = avcodec_receive_frame(decoder->codec_ctx,
decoder->video_buffer->producer_frame);
decoder->video_buffer->decoding_frame);
if (!ret) {
// a frame was received
push_frame(decoder);

View File

@@ -11,10 +11,20 @@ struct video_buffer;
struct decoder {
struct video_buffer *video_buffer;
AVCodecContext *codec_ctx;
const struct decoder_callbacks *cbs;
void *cbs_userdata;
};
struct decoder_callbacks {
// Called when a new frame can be consumed by
// video_buffer_take_rendering_frame(decoder->video_buffer).
void (*on_new_frame)(struct decoder *decoder, void *userdata);
};
void
decoder_init(struct decoder *decoder, struct video_buffer *vb);
decoder_init(struct decoder *decoder, struct video_buffer *vb,
const struct decoder_callbacks *cbs, void *cbs_userdata);
bool
decoder_open(struct decoder *decoder, const AVCodec *codec);

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

@@ -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,56 @@ 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:
if (screen.has_frame) {
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 +244,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;
}
@@ -268,11 +307,14 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
}
static void
video_buffer_on_frame_available(struct video_buffer *vb, void *userdata) {
(void) vb;
decoder_on_new_frame(struct decoder *decoder, void *userdata) {
(void) decoder;
(void) userdata;
screen_on_frame_available(&screen);
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
SDL_PushEvent(&new_frame_event);
}
bool
@@ -341,12 +383,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;
@@ -359,7 +397,11 @@ scrcpy(const struct scrcpy_options *options) {
file_handler_initialized = true;
}
decoder_init(&decoder, &video_buffer);
static const struct decoder_callbacks decoder_cbs = {
.on_new_frame = decoder_on_new_frame,
};
decoder_init(&decoder, &video_buffer, &decoder_cbs, NULL);
dec = &decoder;
}
@@ -379,6 +421,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,8 +444,6 @@ 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,
@@ -421,13 +468,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

@@ -4,7 +4,6 @@
#include <string.h>
#include <SDL2/SDL.h>
#include "events.h"
#include "icon.xpm"
#include "scrcpy.h"
#include "tiny_xpm.h"
@@ -192,11 +191,8 @@ screen_update_content_rect(struct screen *screen) {
}
void
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter) {
screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER;
screen->vb = vb;
screen->fps_counter = fps_counter;
}
static inline SDL_Texture *
@@ -452,15 +448,9 @@ update_texture(struct screen *screen, const AVFrame *frame) {
}
}
static bool
screen_update_frame(struct screen *screen) {
unsigned skipped;
const AVFrame *frame =
video_buffer_consumer_take_frame(screen->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)) {
return false;
@@ -552,52 +542,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
@@ -655,15 +624,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) {
(void) screen;
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
}

View File

@@ -13,9 +13,6 @@
struct video_buffer;
struct screen {
struct video_buffer *vb;
struct fps_counter *fps_counter;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
@@ -40,8 +37,6 @@ struct screen {
};
#define SCREEN_INITIALIZER { \
.vb = NULL, \
.fps_counter = NULL, \
.window = NULL, \
.renderer = NULL, \
.texture = NULL, \
@@ -75,8 +70,7 @@ struct screen {
// 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
@@ -95,6 +89,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
@@ -118,9 +116,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
@@ -141,9 +139,4 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y);
// Notify the screen that a new frame is available in the video_buffer.
// Called from a separate thread.
void
screen_on_frame_available(struct screen *screen);
#endif

View File

@@ -7,11 +7,12 @@
#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->producer_frame = av_frame_alloc();
if (!vb->producer_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;
}
@@ -20,8 +21,8 @@ video_buffer_init(struct video_buffer *vb, bool wait_consumer,
goto error_1;
}
vb->consumer_frame = av_frame_alloc();
if (!vb->consumer_frame) {
vb->rendering_frame = av_frame_alloc();
if (!vb->rendering_frame) {
goto error_2;
}
@@ -30,8 +31,8 @@ video_buffer_init(struct video_buffer *vb, bool wait_consumer,
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);
@@ -46,107 +47,89 @@ video_buffer_init(struct video_buffer *vb, bool wait_consumer,
// 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->consumer_frame);
av_frame_free(&vb->rendering_frame);
error_2:
av_frame_free(&vb->pending_frame);
error_1:
av_frame_free(&vb->producer_frame);
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->producer_frame);
av_frame_free(&vb->decoding_frame);
}
static void
video_buffer_swap_producer_frame(struct video_buffer *vb) {
video_buffer_swap_decoding_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->producer_frame;
vb->producer_frame = vb->pending_frame;
AVFrame *tmp = vb->decoding_frame;
vb->decoding_frame = vb->pending_frame;
vb->pending_frame = tmp;
}
static void
video_buffer_swap_consumer_frame(struct video_buffer *vb) {
video_buffer_swap_rendering_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->consumer_frame;
vb->consumer_frame = vb->pending_frame;
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) {
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);
}
video_buffer_swap_producer_frame(vb);
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;
video_buffer_swap_consumer_frame(vb);
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 *producer_frame;
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
// 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);
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')