Compare commits

..

17 Commits

Author SHA1 Message Date
Romain Vimont
6a9f3df434 enable async resizing 2021-02-21 22:52:24 +01:00
Romain Vimont
da44fa4476 morewip 2021-02-21 22:52:24 +01:00
Romain Vimont
3ed6e70814 swscale-wip 2021-02-21 22:52:24 +01:00
Romain Vimont
2dadeef21a scale_filter 2021-02-21 22:52:24 +01:00
Romain Vimont
cf25631f71 wip 2021-02-21 22:52:24 +01:00
Romain Vimont
738d17e843 Remove screen static initializer
Most of the fields are initialized dynamically.
2021-02-21 22:52:24 +01:00
Romain Vimont
5cfa159e16 Move decoder frame to decoder
The video buffer held 3 frames:
 - the producer frame (for decoding)
 - the pending frame (to exchange between the producer and consumer)
 - the consumer frame (for rendering)

It worked well, but it prevented video buffers to be chained, because
the consumer frame of the first video buffer must be the same as the
producer frame of the second video buffer.

To solve this problem, make the decoder handle its decoding frame, and
keep only the pending and consumer frames in the video_buffer.

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

This paves the way to support asynchronous swscale.
2021-02-21 22:52:24 +01:00
Romain Vimont
9e737a1bef Release frame data as soon as possible
During a frame swap, one of the two frames involved can be released.
2021-02-21 22:52:24 +01:00
Romain Vimont
86ae6955c6 Factorize frame swap 2021-02-21 22:52:24 +01:00
Romain Vimont
fb464614c7 Handle frame available from screen.c 2021-02-21 22:52:24 +01:00
Romain Vimont
6cb357943a 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-21 22:52:24 +01:00
Romain Vimont
6627459cc5 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-21 22:52:24 +01:00
Romain Vimont
b1c7c71160 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-21 22:52:24 +01:00
Romain Vimont
72df2c23f9 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-21 22:52:24 +01:00
Romain Vimont
c9d4755bf9 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-21 22:52:24 +01:00
Romain Vimont
b521ee180d Fix double-free on error
Commit 44aa7f2c88 added calls to destroy
the renderer and the window, but forgot to remove the call to
screen_destroy() they were supposed to replace.
2021-02-21 22:52:24 +01:00
Romain Vimont
025388d38b Remove unused no_window field 2021-02-21 22:52:20 +01:00
16 changed files with 541 additions and 192 deletions

View File

@@ -15,6 +15,7 @@ src = [
'src/opengl.c',
'src/receiver.c',
'src/recorder.c',
'src/resizer.c',
'src/scrcpy.c',
'src/screen.c',
'src/server.c',
@@ -46,6 +47,7 @@ if not get_option('crossbuild_windows')
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavutil'),
dependency('libswscale'),
dependency('sdl2'),
]

View File

@@ -643,6 +643,7 @@ guess_record_format(const char *filename) {
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;
@@ -651,8 +652,49 @@ parse_scale_filter(const char *optarg, enum sc_scale_filter *filter) {
*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 \"none\" or \"trilinear\")", optarg);
"(expected one of [TODO])", optarg);
return false;
}

View File

@@ -2,6 +2,7 @@
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_events.h>
#include <unistd.h>
#include "events.h"
@@ -13,26 +14,12 @@
// 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 callback will consume this frame
return;
}
decoder->cbs->on_new_frame(decoder, decoder->cbs_userdata);
video_buffer_producer_offer_frame(decoder->video_buffer, &decoder->frame);
}
void
decoder_init(struct decoder *decoder, struct video_buffer *vb,
const struct decoder_callbacks *cbs, void *cbs_userdata) {
decoder_init(struct decoder *decoder, struct video_buffer *vb) {
decoder->video_buffer = vb;
assert(cbs);
assert(cbs->on_new_frame);
decoder->cbs = cbs;
decoder->cbs_userdata = cbs_userdata;
}
bool
@@ -49,11 +36,19 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) {
return false;
}
decoder->frame = av_frame_alloc();
if (!decoder->frame) {
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
return false;
}
return true;
}
void
decoder_close(struct decoder *decoder) {
av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
}
@@ -68,8 +63,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
LOGE("Could not send video packet: %d", ret);
return false;
}
ret = avcodec_receive_frame(decoder->codec_ctx,
decoder->video_buffer->decoding_frame);
ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
if (!ret) {
// a frame was received
push_frame(decoder);
@@ -80,7 +74,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
#else
int got_picture;
int len = avcodec_decode_video2(decoder->codec_ctx,
decoder->video_buffer->decoding_frame,
decoder->frame,
&got_picture,
packet);
if (len < 0) {

View File

@@ -11,20 +11,11 @@ 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);
AVFrame *frame;
};
void
decoder_init(struct decoder *decoder, struct video_buffer *vb,
const struct decoder_callbacks *cbs, void *cbs_userdata);
decoder_init(struct decoder *decoder, struct video_buffer *vb);
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_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

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

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

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

@@ -0,0 +1,183 @@
#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);
}

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

@@ -0,0 +1,56 @@
#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

@@ -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,
@@ -267,11 +268,11 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
}
static void
decoder_on_new_frame(struct decoder *decoder, void *userdata) {
(void) decoder;
video_buffer_on_frame_available(struct video_buffer *vb, void *userdata) {
(void) vb;
(void) userdata;
screen_on_new_frame(&screen);
screen_on_frame_available(&screen);
}
bool
@@ -340,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;
@@ -354,11 +359,7 @@ scrcpy(const struct scrcpy_options *options) {
file_handler_initialized = true;
}
static const struct decoder_callbacks decoder_cbs = {
.on_new_frame = decoder_on_new_frame,
};
decoder_init(&decoder, &video_buffer, &decoder_cbs, NULL);
decoder_init(&decoder, &video_buffer);
dec = &decoder;
}
@@ -378,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)) {
@@ -401,7 +395,7 @@ scrcpy(const struct scrcpy_options *options) {
const char *window_title =
options->window_title ? options->window_title : device_name;
screen_init(&screen, &video_buffer);
screen_init(&screen, &video_buffer, &fps_counter);
if (!screen_init_rendering(&screen, window_title, frame_size,
options->always_on_top, options->window_x,
@@ -427,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

@@ -44,6 +44,17 @@ struct sc_port_range {
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)

View File

@@ -191,23 +191,32 @@ screen_update_content_rect(struct screen *screen) {
}
void
screen_init(struct screen *screen, struct video_buffer *vb) {
*screen = (struct screen) SCREEN_INITIALIZER;
screen_init(struct screen *screen, struct video_buffer *vb,
struct fps_counter *fps_counter) {
screen->vb = vb;
screen->fps_counter = fps_counter;
screen->resize_pending = false;
screen->has_frame = false;
screen->fullscreen = false;
screen->maximized = false;
}
static inline SDL_Texture *
create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer;
struct size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_PixelFormatEnum fmt = screen->use_swscale ? SDL_PIXELFORMAT_RGB24
: SDL_PIXELFORMAT_YV12;
SDL_Texture *texture = SDL_CreateTexture(renderer, fmt,
SDL_TEXTUREACCESS_STREAMING,
size.width, size.height);
if (!texture) {
return NULL;
}
if (screen->mipmaps) {
if (screen->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
assert(!screen->use_swscale);
struct sc_opengl *gl = &screen->gl;
SDL_GL_BindTexture(texture, NULL, NULL);
@@ -223,6 +232,25 @@ 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,
@@ -295,10 +323,10 @@ 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");
@@ -307,6 +335,42 @@ screen_init_rendering(struct screen *screen, const char *window_title,
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);
@@ -322,7 +386,6 @@ screen_init_rendering(struct screen *screen, const char *window_title,
LOGC("Could not create texture: %s", SDL_GetError());
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
screen_destroy(screen);
return false;
}
@@ -343,6 +406,11 @@ 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);
}
@@ -438,21 +506,33 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
// write the frame into the texture
static void
update_texture(struct screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (screen->use_swscale) {
SDL_UpdateTexture(screen->texture, NULL, frame->data[0],
frame->linesize[0]);
} else {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
if (screen->mipmaps) {
SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture);
if (screen->scale_filter == SC_SCALE_FILTER_TRILINEAR) {
SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture);
}
}
}
static bool
screen_update_frame(struct screen *screen) {
const AVFrame *frame = video_buffer_take_rendering_frame(screen->vb);
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);
struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
return false;
@@ -649,13 +729,15 @@ screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
}
void
screen_on_new_frame(struct screen *screen) {
(void) screen;
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,
};
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
}
}

View File

@@ -9,12 +9,16 @@
#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;
@@ -34,46 +38,20 @@ struct screen {
bool has_frame;
bool fullscreen;
bool maximized;
bool no_window;
bool mipmaps;
};
#define SCREEN_INITIALIZER { \
.vb = NULL, \
.window = NULL, \
.renderer = NULL, \
.texture = NULL, \
.gl = {0}, \
.frame_size = { \
.width = 0, \
.height = 0, \
}, \
.content_size = { \
.width = 0, \
.height = 0, \
}, \
.resize_pending = false, \
.windowed_content_size = { \
.width = 0, \
.height = 0, \
}, \
.rotation = 0, \
.rect = { \
.x = 0, \
.y = 0, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \
.fullscreen = false, \
.maximized = false, \
.no_window = false, \
.mipmaps = false, \
}
enum sc_scale_filter scale_filter;
bool use_swscale;
// For swscale resizing
struct video_buffer resizer_vb;
struct sc_resizer resizer;
};
// initialize default values
void
screen_init(struct screen *screen, struct video_buffer *vb);
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
@@ -141,6 +119,6 @@ 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_new_frame(struct screen *screen);
screen_on_frame_available(struct screen *screen);
#endif

View File

@@ -7,129 +7,130 @@
#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->pending_frame = av_frame_alloc();
if (!vb->pending_frame) {
goto error_0;
}
vb->pending_frame = av_frame_alloc();
if (!vb->pending_frame) {
vb->consumer_frame = av_frame_alloc();
if (!vb->consumer_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_3;
goto error_2;
}
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);
goto error_2;
}
// interrupted is not used if expired frames are not rendered
// since offering a frame will never block
// interrupted is not used if wait_consumer is disabled since offering
// a frame will never block
vb->interrupted = false;
}
// there is initially no rendering frame, so consider it has already been
// consumed
// there is initially no 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->pending_frame);
av_frame_free(&vb->consumer_frame);
error_1:
av_frame_free(&vb->decoding_frame);
av_frame_free(&vb->pending_frame);
error_0:
return false;
}
void
video_buffer_destroy(struct video_buffer *vb) {
if (vb->render_expired_frames) {
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);
}
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;
static inline void
swap_frames(AVFrame **lhs, AVFrame **rhs) {
AVFrame *tmp = *lhs;
*lhs = *rhs;
*rhs = tmp;
}
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped) {
video_buffer_producer_offer_frame(struct video_buffer *vb, AVFrame **pframe) {
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);
av_frame_unref(vb->pending_frame);
swap_frames(pframe, &vb->pending_frame);
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);
swap_frames(&vb->consumer_frame, &vb->pending_frame);
av_frame_unref(vb->pending_frame);
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 *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
// the produced frame is exchanged with an unused allocated frame
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped);
video_buffer_producer_offer_frame(struct video_buffer *vb, AVFrame **pframe);
// 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