mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-03-06 12:14:28 +01:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a55cea1004 | ||
|
|
fe43453f47 | ||
|
|
0bdb995f2e |
@@ -167,6 +167,13 @@ 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".
|
||||
|
||||
@@ -103,11 +103,6 @@ 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"
|
||||
@@ -154,6 +149,11 @@ 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,6 +641,21 @@ guess_record_format(const char *filename) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_scale_filter(const char *optarg, enum sc_scale_filter *filter) {
|
||||
if (!strcmp(optarg, "none")) {
|
||||
*filter = SC_SCALE_FILTER_NONE;
|
||||
return true;
|
||||
}
|
||||
if (!strcmp(optarg, "trilinear")) {
|
||||
*filter = SC_SCALE_FILTER_TRILINEAR;
|
||||
return true;
|
||||
}
|
||||
LOGE("Unsupported scale filter: %s "
|
||||
"(expected \"none\" or \"trilinear\")", optarg);
|
||||
return false;
|
||||
}
|
||||
|
||||
#define OPT_RENDER_EXPIRED_FRAMES 1000
|
||||
#define OPT_WINDOW_TITLE 1001
|
||||
#define OPT_PUSH_TARGET 1002
|
||||
@@ -667,6 +682,7 @@ guess_record_format(const char *filename) {
|
||||
#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[]) {
|
||||
@@ -703,6 +719,7 @@ 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'},
|
||||
@@ -857,7 +874,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
opts->render_driver = optarg;
|
||||
break;
|
||||
case OPT_NO_MIPMAPS:
|
||||
opts->mipmaps = false;
|
||||
LOGW("Deprecated option --no-mipmaps. "
|
||||
"Use --scale-filter=none instead.");
|
||||
opts->scale_filter = SC_SCALE_FILTER_NONE;
|
||||
break;
|
||||
case OPT_NO_KEY_REPEAT:
|
||||
opts->forward_key_repeat = false;
|
||||
@@ -885,6 +904,11 @@ 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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -268,11 +267,11 @@ 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);
|
||||
screen_on_new_frame(&screen);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -341,12 +340,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 +354,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 +378,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 +401,14 @@ 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);
|
||||
screen_init(&screen, &video_buffer);
|
||||
|
||||
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->mipmaps)) {
|
||||
options->rotation, options->scale_filter)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
@@ -421,13 +427,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);
|
||||
|
||||
@@ -41,6 +41,11 @@ struct sc_port_range {
|
||||
uint16_t last;
|
||||
};
|
||||
|
||||
enum sc_scale_filter {
|
||||
SC_SCALE_FILTER_NONE,
|
||||
SC_SCALE_FILTER_TRILINEAR, // mipmaps
|
||||
};
|
||||
|
||||
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
|
||||
|
||||
struct scrcpy_options {
|
||||
@@ -56,6 +61,7 @@ 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;
|
||||
@@ -75,7 +81,6 @@ 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;
|
||||
@@ -103,6 +108,7 @@ 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, \
|
||||
@@ -122,7 +128,6 @@ 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, \
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include "events.h"
|
||||
#include "icon.xpm"
|
||||
#include "scrcpy.h"
|
||||
#include "tiny_xpm.h"
|
||||
#include "video_buffer.h"
|
||||
#include "util/log.h"
|
||||
@@ -192,11 +191,9 @@ 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, struct video_buffer *vb) {
|
||||
*screen = (struct screen) SCREEN_INITIALIZER;
|
||||
screen->vb = vb;
|
||||
screen->fps_counter = fps_counter;
|
||||
}
|
||||
|
||||
static inline SDL_Texture *
|
||||
@@ -231,7 +228,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, bool mipmaps) {
|
||||
uint8_t rotation, enum sc_scale_filter scale_filter) {
|
||||
screen->frame_size = frame_size;
|
||||
screen->rotation = rotation;
|
||||
if (rotation) {
|
||||
@@ -285,6 +282,7 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
||||
|
||||
// starts with "opengl"
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
bool mipmaps = scale_filter == SC_SCALE_FILTER_TRILINEAR;
|
||||
if (use_opengl) {
|
||||
struct sc_opengl *gl = &screen->gl;
|
||||
sc_opengl_init(gl);
|
||||
@@ -454,13 +452,7 @@ 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);
|
||||
|
||||
const AVFrame *frame = video_buffer_take_rendering_frame(screen->vb);
|
||||
struct size new_frame_size = {frame->width, frame->height};
|
||||
if (!prepare_for_frame(screen, new_frame_size)) {
|
||||
return false;
|
||||
@@ -657,7 +649,7 @@ screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
|
||||
}
|
||||
|
||||
void
|
||||
screen_on_frame_available(struct screen *screen) {
|
||||
screen_on_new_frame(struct screen *screen) {
|
||||
(void) screen;
|
||||
|
||||
static SDL_Event new_frame_event = {
|
||||
|
||||
@@ -9,13 +9,12 @@
|
||||
|
||||
#include "coords.h"
|
||||
#include "opengl.h"
|
||||
#include "scrcpy.h"
|
||||
|
||||
struct video_buffer;
|
||||
|
||||
struct screen {
|
||||
struct video_buffer *vb;
|
||||
struct fps_counter *fps_counter;
|
||||
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
@@ -41,7 +40,6 @@ struct screen {
|
||||
|
||||
#define SCREEN_INITIALIZER { \
|
||||
.vb = NULL, \
|
||||
.fps_counter = NULL, \
|
||||
.window = NULL, \
|
||||
.renderer = NULL, \
|
||||
.texture = NULL, \
|
||||
@@ -75,8 +73,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, struct video_buffer *vb);
|
||||
|
||||
// initialize screen, create window, renderer and texture (window is hidden)
|
||||
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
|
||||
@@ -85,7 +82,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, bool mipmaps);
|
||||
uint8_t rotation, enum sc_scale_filter scale_filter);
|
||||
|
||||
// show the window
|
||||
void
|
||||
@@ -144,6 +141,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_frame_available(struct screen *screen);
|
||||
screen_on_new_frame(struct screen *screen);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user