Compare commits

...

12 commits

Author SHA1 Message Date
Romain Vimont e51e9c748f Handle frame available from screen.c 2021-02-20 11:54:54 +01:00
Romain Vimont c79d3a28ca Initialize screen before starting the stream
As soon as the stream is started, the video buffer could notify a new
frame available.

In order to pass this event to the screen without race condition, the
screen must be initialized before the screen is started.
2021-02-20 11:54:12 +01:00
Romain Vimont 108fe5818e Expose skipped frames to the consumer
A skipped frame is detected when the producer offers a frame while the
current pending frame has not been consumed.

However, the producer (in practice the decoder) is not interested in the
fact that a frame has been skipped, only the consumer (the renderer) is.
Therefore, expose the skipped count in consumer_take_frame() instead of
a flag in the producer_offer_frame().

This allows to manage the skipped and rendered frames count at the same
place, and remove fps_counter from decoder.
2021-02-20 11:53:37 +01:00
Romain Vimont 1ff357bd29 Use a callback to notify a new frame
Make the decoder independant of the SDL even mechanism, by registering a
callback on the video_buffer.
2021-02-19 22:02:36 +01:00
Romain Vimont 5f2f0ce595 Make video buffer more generic
Video buffer is a tool between a frame producer and a frame consumer.

For now, it is used between a decoder and a renderer, but in the future
another instance might be used to swscale decoded frames.
2021-02-19 21:16:57 +01:00
Romain Vimont 816c158577 Move fps counter out of video buffer
In order to make video buffer more generic, move out its specific
responsibility to count the fps between the decoder and the renderer.
2021-02-19 20:56:09 +01:00
Romain Vimont dce0867737 Enable NDEBUG via Meson built-in option 2021-02-17 09:54:03 +01:00
Romain Vimont 5c4c28c973 Handle im-related events from input_manager.c 2021-02-17 09:54:03 +01:00
Romain Vimont 98628f25b5 Inline window events handling
Now that all screen-related events are handled from screen.c, there is
no need for a separate method for window events.
2021-02-17 09:54:03 +01:00
Romain Vimont e1dbe4f71c Handle screen-related events from screen.c 2021-02-17 09:54:03 +01:00
Romain Vimont b1734ab737 Reference video buffer from screen
This paves the way to handle EVENT_NEW_FRAME from screen.c, by allowing
to call screen_update_frame() without an explicit video_buffer instance.
2021-02-17 09:54:03 +01:00
Romain Vimont 44aa7f2c88 Improve error handling in screen initialization
After the struct screen is initialized, the window and the renderer are
necessarily valid, so there is no need o check in screen_destroy().
2021-02-17 09:54:03 +01:00
12 changed files with 268 additions and 210 deletions

View file

@ -99,9 +99,6 @@ 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

@ -14,17 +14,7 @@
// set the decoded frame as ready for rendering, and notify
static void
push_frame(struct decoder *decoder) {
bool previous_frame_skipped;
video_buffer_offer_decoded_frame(decoder->video_buffer,
&previous_frame_skipped);
if (previous_frame_skipped) {
// the previous EVENT_NEW_FRAME will consume this frame
return;
}
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
SDL_PushEvent(&new_frame_event);
video_buffer_producer_offer_frame(decoder->video_buffer);
}
void
@ -66,7 +56,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
return false;
}
ret = avcodec_receive_frame(decoder->codec_ctx,
decoder->video_buffer->decoding_frame);
decoder->video_buffer->producer_frame);
if (!ret) {
// a frame was received
push_frame(decoder);

View file

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

View file

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

View file

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

View file

@ -30,7 +30,7 @@
#include "util/net.h"
static struct server server;
static struct screen screen = SCREEN_INITIALIZER;
static struct screen screen;
static struct fps_counter fps_counter;
static struct video_buffer video_buffer;
static struct stream stream;
@ -42,6 +42,7 @@ static struct file_handler file_handler;
static struct input_manager input_manager = {
.controller = &controller,
.video_buffer = &video_buffer,
.fps_counter = &fps_counter,
.screen = &screen,
.repeat = 0,
@ -173,56 +174,6 @@ 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;
@ -244,6 +195,16 @@ 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;
}
@ -306,6 +267,14 @@ 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)) {
@ -372,8 +341,12 @@ scrcpy(const struct scrcpy_options *options) {
}
fps_counter_initialized = true;
if (!video_buffer_init(&video_buffer, &fps_counter,
options->render_expired_frames)) {
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)) {
goto end;
}
video_buffer_initialized = true;
@ -406,13 +379,6 @@ scrcpy(const struct scrcpy_options *options) {
stream_init(&stream, server.video_socket, dec, rec);
// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&stream)) {
goto end;
}
stream_started = true;
if (options->display) {
if (options->control) {
if (!controller_init(&controller, server.control_socket)) {
@ -429,6 +395,8 @@ 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,
@ -453,6 +421,13 @@ scrcpy(const struct scrcpy_options *options) {
}
}
// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&stream)) {
goto end;
}
stream_started = true;
input_manager_init(&input_manager, options);
ret = event_loop(options);

View file

@ -4,6 +4,7 @@
#include <string.h>
#include <SDL2/SDL.h>
#include "events.h"
#include "icon.xpm"
#include "scrcpy.h"
#include "tiny_xpm.h"
@ -191,8 +192,11 @@ screen_update_content_rect(struct screen *screen) {
}
void
screen_init(struct screen *screen) {
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter) {
*screen = (struct screen) SCREEN_INITIALIZER;
screen->vb = vb;
screen->fps_counter = fps_counter;
}
static inline SDL_Texture *
@ -270,7 +274,7 @@ screen_init_rendering(struct screen *screen, const char *window_title,
SDL_RENDERER_ACCELERATED);
if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError());
screen_destroy(screen);
SDL_DestroyWindow(screen->window);
return false;
}
@ -318,6 +322,8 @@ screen_init_rendering(struct screen *screen, const char *window_title,
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
screen_destroy(screen);
return false;
}
@ -342,12 +348,8 @@ screen_destroy(struct screen *screen) {
if (screen->texture) {
SDL_DestroyTexture(screen->texture);
}
if (screen->renderer) {
SDL_DestroyRenderer(screen->renderer);
}
if (screen->window) {
SDL_DestroyWindow(screen->window);
}
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
}
static void
@ -450,9 +452,15 @@ update_texture(struct screen *screen, const AVFrame *frame) {
}
}
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb) {
const AVFrame *frame = video_buffer_take_rendering_frame(vb);
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);
struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
return false;
@ -544,31 +552,52 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
content_size.height);
}
void
screen_handle_window_event(struct screen *screen,
const SDL_WindowEvent *event) {
switch (event->event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling fullscreen
// mode unexpectedly triggers the "restored" then "maximized"
// events, leaving the window in a weird state (maximized
// according to the events, but not maximized visually).
break;
bool
screen_handle_event(struct screen *screen, SDL_Event *event) {
switch (event->type) {
case EVENT_NEW_FRAME:
if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
screen_show_window(screen);
}
screen->maximized = false;
apply_pending_resize(screen);
break;
bool ok = screen_update_frame(screen);
if (!ok) {
LOGW("Frame update failed\n");
}
return true;
case SDL_WINDOWEVENT:
if (!screen->has_frame) {
// Do nothing
return true;
}
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling
// fullscreen mode unexpectedly triggers the "restored"
// then "maximized" events, leaving the window in a
// weird state (maximized according to the events, but
// not maximized visually).
break;
}
screen->maximized = false;
apply_pending_resize(screen);
break;
}
return true;
}
return false;
}
struct point
@ -626,3 +655,15 @@ 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,6 +13,9 @@
struct video_buffer;
struct screen {
struct video_buffer *vb;
struct fps_counter *fps_counter;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
@ -37,6 +40,8 @@ struct screen {
};
#define SCREEN_INITIALIZER { \
.vb = NULL, \
.fps_counter = NULL, \
.window = NULL, \
.renderer = NULL, \
.texture = NULL, \
@ -70,7 +75,8 @@ struct screen {
// initialize default values
void
screen_init(struct screen *screen);
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter);
// initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
@ -89,10 +95,6 @@ screen_show_window(struct screen *screen);
void
screen_destroy(struct screen *screen);
// resize if necessary and write the rendered frame into the texture
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb);
// render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
@ -116,9 +118,9 @@ screen_resize_to_pixel_perfect(struct screen *screen);
void
screen_set_rotation(struct screen *screen, unsigned rotation);
// react to window events
void
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
// react to SDL events
bool
screen_handle_event(struct screen *screen, SDL_Event *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
@ -139,4 +141,9 @@ 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,12 +7,11 @@
#include "util/log.h"
bool
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
bool render_expired_frames) {
vb->fps_counter = fps_counter;
vb->decoding_frame = av_frame_alloc();
if (!vb->decoding_frame) {
video_buffer_init(struct video_buffer *vb, bool wait_consumer,
const struct video_buffer_callbacks *cbs,
void *cbs_userdata) {
vb->producer_frame = av_frame_alloc();
if (!vb->producer_frame) {
goto error_0;
}
@ -21,8 +20,8 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
goto error_1;
}
vb->rendering_frame = av_frame_alloc();
if (!vb->rendering_frame) {
vb->consumer_frame = av_frame_alloc();
if (!vb->consumer_frame) {
goto error_2;
}
@ -31,8 +30,8 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
goto error_3;
}
vb->render_expired_frames = render_expired_frames;
if (render_expired_frames) {
vb->wait_consumer = wait_consumer;
if (wait_consumer) {
ok = sc_cond_init(&vb->pending_frame_consumed_cond);
if (!ok) {
sc_mutex_destroy(&vb->mutex);
@ -47,89 +46,107 @@ video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
// 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);
av_frame_free(&vb->consumer_frame);
error_2:
av_frame_free(&vb->pending_frame);
error_1:
av_frame_free(&vb->decoding_frame);
av_frame_free(&vb->producer_frame);
error_0:
return false;
}
void
video_buffer_destroy(struct video_buffer *vb) {
if (vb->render_expired_frames) {
if (vb->wait_consumer) {
sc_cond_destroy(&vb->pending_frame_consumed_cond);
}
sc_mutex_destroy(&vb->mutex);
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->consumer_frame);
av_frame_free(&vb->pending_frame);
av_frame_free(&vb->decoding_frame);
av_frame_free(&vb->producer_frame);
}
static void
video_buffer_swap_decoding_frame(struct video_buffer *vb) {
video_buffer_swap_producer_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->decoding_frame;
vb->decoding_frame = vb->pending_frame;
AVFrame *tmp = vb->producer_frame;
vb->producer_frame = vb->pending_frame;
vb->pending_frame = tmp;
}
static void
video_buffer_swap_rendering_frame(struct video_buffer *vb) {
video_buffer_swap_consumer_frame(struct video_buffer *vb) {
sc_mutex_assert(&vb->mutex);
AVFrame *tmp = vb->rendering_frame;
vb->rendering_frame = vb->pending_frame;
AVFrame *tmp = vb->consumer_frame;
vb->consumer_frame = vb->pending_frame;
vb->pending_frame = tmp;
}
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped) {
video_buffer_producer_offer_frame(struct video_buffer *vb) {
sc_mutex_lock(&vb->mutex);
if (vb->render_expired_frames) {
if (vb->wait_consumer) {
// wait for the current (expired) frame to be consumed
while (!vb->pending_frame_consumed && !vb->interrupted) {
sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex);
}
} else if (!vb->pending_frame_consumed) {
fps_counter_add_skipped_frame(vb->fps_counter);
}
video_buffer_swap_decoding_frame(vb);
video_buffer_swap_producer_frame(vb);
bool skipped = !vb->pending_frame_consumed;
if (skipped) {
++vb->skipped;
}
*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_take_rendering_frame(struct video_buffer *vb) {
video_buffer_consumer_take_frame(struct video_buffer *vb, unsigned *skipped) {
sc_mutex_lock(&vb->mutex);
assert(!vb->pending_frame_consumed);
vb->pending_frame_consumed = true;
fps_counter_add_rendered_frame(vb->fps_counter);
video_buffer_swap_consumer_frame(vb);
video_buffer_swap_rendering_frame(vb);
if (vb->render_expired_frames) {
if (vb->wait_consumer) {
// unblock video_buffer_offer_decoded_frame()
sc_cond_signal(&vb->pending_frame_consumed_cond);
}
if (skipped) {
*skipped = vb->skipped;
}
vb->skipped = 0; // reset
sc_mutex_unlock(&vb->mutex);
// rendering_frame is only written from this thread, no need to lock
return vb->rendering_frame;
// consumer_frame is only written from this thread, no need to lock
return vb->consumer_frame;
}
void
video_buffer_interrupt(struct video_buffer *vb) {
if (vb->render_expired_frames) {
if (vb->wait_consumer) {
sc_mutex_lock(&vb->mutex);
vb->interrupted = true;
sc_mutex_unlock(&vb->mutex);

View file

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

View file

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