Compare commits

..

4 Commits

Author SHA1 Message Date
Romain Vimont
86c4cbcf9e Extract current video_buffer to frame_buffer
The current video buffer only stores one pending frame.

In order to add a new buffering feature, move this part to a separate
"frame buffer". Keep the video_buffer, which currently delegates all its
calls to the frame_buffer.
2021-06-27 20:29:07 +02:00
Romain Vimont
7917dc313e Rename video_buffer to sc_video_buffer
Add a scrcpy-specific prefix.
2021-06-26 16:05:56 +02:00
Romain Vimont
d743e03f44 Move include fps_counter
The fps_counter is not used from video_buffer.
2021-06-26 16:04:52 +02:00
Romain Vimont
9217cfa079 Remove obsolete comment
Commit 2a94a2b119 removed video_buffer
callbacks, the comment is now meaningless.
2021-06-26 16:04:52 +02:00
27 changed files with 99 additions and 796 deletions

View File

@@ -321,24 +321,6 @@ For example, you could capture the video within [OBS].
[OBS]: https://obsproject.com/fr [OBS]: https://obsproject.com/fr
#### Buffering
It is possible to add buffering. This increases latency but reduces jitter (see
#2464).
The option is available for display buffering:
```bash
scrcpy --display-buffer=50 # add 50 ms buffering for display
```
and V4L2 sink:
```bash
scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink
```
### Connection ### Connection
#### Wireless #### Wireless

View File

@@ -2,7 +2,6 @@ src = [
'src/main.c', 'src/main.c',
'src/adb.c', 'src/adb.c',
'src/cli.c', 'src/cli.c',
'src/clock.c',
'src/compat.c', 'src/compat.c',
'src/control_msg.c', 'src/control_msg.c',
'src/controller.c', 'src/controller.c',
@@ -27,7 +26,6 @@ src = [
'src/util/process.c', 'src/util/process.c',
'src/util/str_util.c', 'src/util/str_util.c',
'src/util/thread.c', 'src/util/thread.c',
'src/util/tick.c',
] ]
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
@@ -168,10 +166,6 @@ if get_option('buildtype') == 'debug'
'src/cli.c', 'src/cli.c',
'src/util/str_util.c', 'src/util/str_util.c',
]], ]],
['test_clock', [
'tests/test_clock.c',
'src/clock.c',
]],
['test_control_msg_serialize', [ ['test_control_msg_serialize', [
'tests/test_control_msg_serialize.c', 'tests/test_control_msg_serialize.c',
'src/control_msg.c', 'src/control_msg.c',

View File

@@ -56,12 +56,6 @@ The list of possible display ids can be listed by "adb shell dumpsys display"
Default is 0. Default is 0.
.TP
.BI "\-\-display\-buffer ms
Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter.
Default is 0 (no buffering).
.TP .TP
.BI "\-\-encoder " name .BI "\-\-encoder " name
Use a specific MediaCodec encoder (must be a H.264 encoder). Use a specific MediaCodec encoder (must be a H.264 encoder).
@@ -89,7 +83,7 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically. This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
.TP .TP
.BI "\-\-lock\-video\-orientation[=value] .BI "\-\-lock\-video\-orientation " [value]
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise. Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise.
Default is "unlocked". Default is "unlocked".
@@ -195,13 +189,7 @@ It only shows physical touches (not clicks from scrcpy).
.BI "\-\-v4l2-sink " /dev/videoN .BI "\-\-v4l2-sink " /dev/videoN
Output to v4l2loopback device. Output to v4l2loopback device.
It requires to lock the video orientation (see \fB\-\-lock\-video\-orientation\fR). It requires to lock the video orientation (see --lock-video-orientation).
.TP
.BI "\-\-v4l2-buffer " ms
Add a buffering delay (in milliseconds) before pushing frames. This increases latency to compensate for jitter.
This option is similar to \fB\-\-display\-buffer\fR, but specific to V4L2 sink.
.TP .TP
.BI "\-V, \-\-verbosity " value .BI "\-V, \-\-verbosity " value
@@ -252,7 +240,7 @@ Default is 0 (automatic).
.SH SHORTCUTS .SH SHORTCUTS
In the following list, MOD is the shortcut modifier. By default, it's (left) In the following list, MOD is the shortcut modifier. By default, it's (left)
Alt or (left) Super, but it can be configured by \fB\-\-shortcut\-mod\fR (see above). Alt or (left) Super, but it can be configured by \-\-shortcut-mod (see above).
.TP .TP
.B MOD+f .B MOD+f

View File

@@ -55,12 +55,6 @@ scrcpy_print_usage(const char *arg0) {
"\n" "\n"
" Default is 0.\n" " Default is 0.\n"
"\n" "\n"
" --display-buffer ms\n"
" Add a buffering delay (in milliseconds) before displaying.\n"
" This increases latency to compensate for jitter.\n"
"\n"
" Default is 0 (no buffering).\n"
"\n"
" --encoder name\n" " --encoder name\n"
" Use a specific MediaCodec encoder (must be a H.264 encoder).\n" " Use a specific MediaCodec encoder (must be a H.264 encoder).\n"
"\n" "\n"
@@ -85,7 +79,7 @@ scrcpy_print_usage(const char *arg0) {
" This is a workaround for some devices not behaving as\n" " This is a workaround for some devices not behaving as\n"
" expected when setting the device clipboard programmatically.\n" " expected when setting the device clipboard programmatically.\n"
"\n" "\n"
" --lock-video-orientation[=value]\n" " --lock-video-orientation [value]\n"
" Lock video orientation to value.\n" " Lock video orientation to value.\n"
" Possible values are \"unlocked\", \"initial\" (locked to the\n" " Possible values are \"unlocked\", \"initial\" (locked to the\n"
" initial orientation), 0, 1, 2 and 3.\n" " initial orientation), 0, 1, 2 and 3.\n"
@@ -188,15 +182,6 @@ scrcpy_print_usage(const char *arg0) {
" It requires to lock the video orientation (see\n" " It requires to lock the video orientation (see\n"
" --lock-video-orientation).\n" " --lock-video-orientation).\n"
"\n" "\n"
" --v4l2-buffer ms\n"
" Add a buffering delay (in milliseconds) before pushing\n"
" frames. This increases latency to compensate for jitter.\n"
"\n"
" This option is similar to --display-buffer, but specific to\n"
" V4L2 sink.\n"
"\n"
" Default is 0 (no buffering).\n"
"\n"
#endif #endif
" -V, --verbosity value\n" " -V, --verbosity value\n"
" Set the log level (verbose, debug, info, warn or error).\n" " Set the log level (verbose, debug, info, warn or error).\n"
@@ -407,19 +392,6 @@ parse_max_fps(const char *s, uint16_t *max_fps) {
return true; return true;
} }
static bool
parse_buffering_time(const char *s, sc_tick *tick) {
long value;
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFFFFFF,
"buffering time");
if (!ok) {
return false;
}
*tick = SC_TICK_FROM_MS(value);
return true;
}
static bool static bool
parse_lock_video_orientation(const char *s, parse_lock_video_orientation(const char *s,
enum sc_lock_video_orientation *lock_mode) { enum sc_lock_video_orientation *lock_mode) {
@@ -717,8 +689,6 @@ guess_record_format(const char *filename) {
#define OPT_ENCODER_NAME 1025 #define OPT_ENCODER_NAME 1025
#define OPT_POWER_OFF_ON_CLOSE 1026 #define OPT_POWER_OFF_ON_CLOSE 1026
#define OPT_V4L2_SINK 1027 #define OPT_V4L2_SINK 1027
#define OPT_DISPLAY_BUFFER 1028
#define OPT_V4L2_BUFFER 1029
bool bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
@@ -730,7 +700,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"disable-screensaver", no_argument, NULL, {"disable-screensaver", no_argument, NULL,
OPT_DISABLE_SCREENSAVER}, OPT_DISABLE_SCREENSAVER},
{"display", required_argument, NULL, OPT_DISPLAY_ID}, {"display", required_argument, NULL, OPT_DISPLAY_ID},
{"display-buffer", required_argument, NULL, OPT_DISPLAY_BUFFER},
{"encoder", required_argument, NULL, OPT_ENCODER_NAME}, {"encoder", required_argument, NULL, OPT_ENCODER_NAME},
{"force-adb-forward", no_argument, NULL, {"force-adb-forward", no_argument, NULL,
OPT_FORCE_ADB_FORWARD}, OPT_FORCE_ADB_FORWARD},
@@ -763,7 +732,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"turn-screen-off", no_argument, NULL, 'S'}, {"turn-screen-off", no_argument, NULL, 'S'},
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
{"v4l2-sink", required_argument, NULL, OPT_V4L2_SINK}, {"v4l2-sink", required_argument, NULL, OPT_V4L2_SINK},
{"v4l2-buffer", required_argument, NULL, OPT_V4L2_BUFFER},
#endif #endif
{"verbosity", required_argument, NULL, 'V'}, {"verbosity", required_argument, NULL, 'V'},
{"version", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'v'},
@@ -949,20 +917,10 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_POWER_OFF_ON_CLOSE: case OPT_POWER_OFF_ON_CLOSE:
opts->power_off_on_close = true; opts->power_off_on_close = true;
break; break;
case OPT_DISPLAY_BUFFER:
if (!parse_buffering_time(optarg, &opts->display_buffer)) {
return false;
}
break;
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
case OPT_V4L2_SINK: case OPT_V4L2_SINK:
opts->v4l2_device = optarg; opts->v4l2_device = optarg;
break; break;
case OPT_V4L2_BUFFER:
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
return false;
}
break;
#endif #endif
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr

View File

@@ -1,111 +0,0 @@
#include "clock.h"
#include "util/log.h"
#define SC_CLOCK_NDEBUG // comment to debug
void
sc_clock_init(struct sc_clock *clock) {
clock->count = 0;
clock->head = 0;
clock->left_sum.system = 0;
clock->left_sum.stream = 0;
clock->right_sum.system = 0;
clock->right_sum.stream = 0;
}
// Estimate the affine function f(stream) = slope * stream + offset
static void
sc_clock_estimate(struct sc_clock *clock,
double *out_slope, sc_tick *out_offset) {
assert(clock->count > 1); // two points are necessary
struct sc_clock_point left_avg = {
.system = clock->left_sum.system / (clock->count / 2),
.stream = clock->left_sum.stream / (clock->count / 2),
};
struct sc_clock_point right_avg = {
.system = clock->right_sum.system / ((clock->count + 1) / 2),
.stream = clock->right_sum.stream / ((clock->count + 1) / 2),
};
double slope = (double) (right_avg.system - left_avg.system)
/ (right_avg.stream - left_avg.stream);
if (clock->count < SC_CLOCK_RANGE) {
/* The first frames are typically received and decoded with more delay
* than the others, causing a wrong slope estimation on start. To
* compensate, assume an initial slope of 1, then progressively use the
* estimated slope. */
slope = (clock->count * slope + (SC_CLOCK_RANGE - clock->count))
/ SC_CLOCK_RANGE;
}
struct sc_clock_point global_avg = {
.system = (clock->left_sum.system + clock->right_sum.system)
/ clock->count,
.stream = (clock->left_sum.stream + clock->right_sum.stream)
/ clock->count,
};
sc_tick offset = global_avg.system - (sc_tick) (global_avg.stream * slope);
*out_slope = slope;
*out_offset = offset;
}
void
sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
struct sc_clock_point *point = &clock->points[clock->head];
if (clock->count == SC_CLOCK_RANGE || clock->count & 1) {
// One point passes from the right sum to the left sum
unsigned mid;
if (clock->count == SC_CLOCK_RANGE) {
mid = (clock->head + SC_CLOCK_RANGE / 2) % SC_CLOCK_RANGE;
} else {
// Only for the first frames
mid = clock->count / 2;
}
struct sc_clock_point *mid_point = &clock->points[mid];
clock->left_sum.system += mid_point->system;
clock->left_sum.stream += mid_point->stream;
clock->right_sum.system -= mid_point->system;
clock->right_sum.stream -= mid_point->stream;
}
if (clock->count == SC_CLOCK_RANGE) {
// The current point overwrites the previous value in the circular
// array, update the left sum accordingly
clock->left_sum.system -= point->system;
clock->left_sum.stream -= point->stream;
} else {
++clock->count;
}
point->system = system;
point->stream = stream;
clock->right_sum.system += system;
clock->right_sum.stream += stream;
clock->head = (clock->head + 1) % SC_CLOCK_RANGE;
if (clock->count > 1) {
// Update estimation
sc_clock_estimate(clock, &clock->slope, &clock->offset);
#ifndef SC_CLOCK_NDEBUG
LOGD("Clock estimation: %g * pts + %" PRItick,
clock->slope, clock->offset);
#endif
}
}
sc_tick
sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream) {
assert(clock->count > 1); // sc_clock_update() must have been called
return (sc_tick) (stream * clock->slope) + clock->offset;
}

View File

@@ -1,70 +0,0 @@
#ifndef SC_CLOCK_H
#define SC_CLOCK_H
#include "common.h"
#include <assert.h>
#include "util/tick.h"
#define SC_CLOCK_RANGE 32
static_assert(!(SC_CLOCK_RANGE & 1), "SC_CLOCK_RANGE must be even");
struct sc_clock_point {
sc_tick system;
sc_tick stream;
};
/**
* The clock aims to estimate the affine relation between the stream (device)
* time and the system time:
*
* f(stream) = slope * stream + offset
*
* To that end, it stores the SC_CLOCK_RANGE last clock points (the timestamps
* of a frame expressed both in stream time and system time) in a circular
* array.
*
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
* sets of SC_CLOCK_RANGE/2 points, and compute their centroid ("average
* point"). The slope of the estimated affine function is that of the line
* passing through these two points.
*
* To estimate the offset, it computes the centroid of all the SC_CLOCK_RANGE
* points. The resulting affine function passes by this centroid.
*
* With a circular array, the rolling sums (and average) are quick to compute.
* In practice, the estimation is stable and the evolution is smooth.
*/
struct sc_clock {
// Circular array
struct sc_clock_point points[SC_CLOCK_RANGE];
// Number of points in the array (count <= SC_CLOCK_RANGE)
unsigned count;
// Index of the next point to write
unsigned head;
// Sum of the first count/2 points
struct sc_clock_point left_sum;
// Sum of the last (count+1)/2 points
struct sc_clock_point right_sum;
// Estimated slope and offset
// (computed on sc_clock_update(), used by sc_clock_to_system_time())
double slope;
sc_tick offset;
};
void
sc_clock_init(struct sc_clock *clock);
void
sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream);
sc_tick
sc_clock_to_system_time(struct sc_clock *clock, sc_tick stream);
#endif

View File

@@ -1,10 +1,11 @@
#include "fps_counter.h" #include "fps_counter.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_timer.h>
#include "util/log.h" #include "util/log.h"
#define FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1) #define FPS_COUNTER_INTERVAL_MS 1000
bool bool
fps_counter_init(struct fps_counter *counter) { fps_counter_init(struct fps_counter *counter) {
@@ -46,7 +47,7 @@ set_started(struct fps_counter *counter, bool started) {
static void static void
display_fps(struct fps_counter *counter) { display_fps(struct fps_counter *counter) {
unsigned rendered_per_second = unsigned rendered_per_second =
counter->nr_rendered * SC_TICK_FREQ / FPS_COUNTER_INTERVAL; counter->nr_rendered * 1000 / FPS_COUNTER_INTERVAL_MS;
if (counter->nr_skipped) { if (counter->nr_skipped) {
LOGI("%u fps (+%u frames skipped)", rendered_per_second, LOGI("%u fps (+%u frames skipped)", rendered_per_second,
counter->nr_skipped); counter->nr_skipped);
@@ -67,8 +68,8 @@ check_interval_expired(struct fps_counter *counter, uint32_t now) {
counter->nr_skipped = 0; counter->nr_skipped = 0;
// add a multiple of the interval // add a multiple of the interval
uint32_t elapsed_slices = uint32_t elapsed_slices =
(now - counter->next_timestamp) / FPS_COUNTER_INTERVAL + 1; (now - counter->next_timestamp) / FPS_COUNTER_INTERVAL_MS + 1;
counter->next_timestamp += FPS_COUNTER_INTERVAL * elapsed_slices; counter->next_timestamp += FPS_COUNTER_INTERVAL_MS * elapsed_slices;
} }
static int static int
@@ -81,12 +82,14 @@ run_fps_counter(void *data) {
sc_cond_wait(&counter->state_cond, &counter->mutex); sc_cond_wait(&counter->state_cond, &counter->mutex);
} }
while (!counter->interrupted && is_started(counter)) { while (!counter->interrupted && is_started(counter)) {
sc_tick now = sc_tick_now(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
assert(counter->next_timestamp > now);
uint32_t remaining = counter->next_timestamp - now;
// ignore the reason (timeout or signaled), we just loop anyway // ignore the reason (timeout or signaled), we just loop anyway
sc_cond_timedwait(&counter->state_cond, &counter->mutex, sc_cond_timedwait(&counter->state_cond, &counter->mutex, remaining);
counter->next_timestamp);
} }
} }
sc_mutex_unlock(&counter->mutex); sc_mutex_unlock(&counter->mutex);
@@ -96,7 +99,7 @@ run_fps_counter(void *data) {
bool bool
fps_counter_start(struct fps_counter *counter) { fps_counter_start(struct fps_counter *counter) {
sc_mutex_lock(&counter->mutex); sc_mutex_lock(&counter->mutex);
counter->next_timestamp = sc_tick_now() + FPS_COUNTER_INTERVAL; counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
counter->nr_rendered = 0; counter->nr_rendered = 0;
counter->nr_skipped = 0; counter->nr_skipped = 0;
sc_mutex_unlock(&counter->mutex); sc_mutex_unlock(&counter->mutex);
@@ -162,7 +165,7 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
} }
sc_mutex_lock(&counter->mutex); sc_mutex_lock(&counter->mutex);
sc_tick now = sc_tick_now(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
++counter->nr_rendered; ++counter->nr_rendered;
sc_mutex_unlock(&counter->mutex); sc_mutex_unlock(&counter->mutex);
@@ -175,7 +178,7 @@ fps_counter_add_skipped_frame(struct fps_counter *counter) {
} }
sc_mutex_lock(&counter->mutex); sc_mutex_lock(&counter->mutex);
sc_tick now = sc_tick_now(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
++counter->nr_skipped; ++counter->nr_skipped;
sc_mutex_unlock(&counter->mutex); sc_mutex_unlock(&counter->mutex);

View File

@@ -24,7 +24,7 @@ struct fps_counter {
bool interrupted; bool interrupted;
unsigned nr_rendered; unsigned nr_rendered;
unsigned nr_skipped; unsigned nr_skipped;
sc_tick next_timestamp; uint32_t next_timestamp;
}; };
bool bool

View File

@@ -51,15 +51,16 @@ record_packet_new(const AVPacket *packet) {
static void static void
record_packet_delete(struct record_packet *rec) { record_packet_delete(struct record_packet *rec) {
av_packet_unref(rec->packet);
av_packet_free(&rec->packet); av_packet_free(&rec->packet);
free(rec); free(rec);
} }
static void static void
recorder_queue_clear(struct recorder_queue *queue) { recorder_queue_clear(struct recorder_queue *queue) {
while (!sc_queue_is_empty(queue)) { while (!queue_is_empty(queue)) {
struct record_packet *rec; struct record_packet *rec;
sc_queue_take(queue, next, &rec); queue_take(queue, next, &rec);
record_packet_delete(rec); record_packet_delete(rec);
} }
} }
@@ -135,14 +136,14 @@ run_recorder(void *data) {
for (;;) { for (;;) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped && sc_queue_is_empty(&recorder->queue)) { while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
sc_cond_wait(&recorder->queue_cond, &recorder->mutex); sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
} }
// if stopped is set, continue to process the remaining events (to // if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping // finish the recording) before actually stopping
if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) { if (recorder->stopped && queue_is_empty(&recorder->queue)) {
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
struct record_packet *last = recorder->previous; struct record_packet *last = recorder->previous;
if (last) { if (last) {
@@ -161,7 +162,7 @@ run_recorder(void *data) {
} }
struct record_packet *rec; struct record_packet *rec;
sc_queue_take(&recorder->queue, next, &rec); queue_take(&recorder->queue, next, &rec);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
@@ -235,7 +236,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
goto error_mutex_destroy; goto error_mutex_destroy;
} }
sc_queue_init(&recorder->queue); queue_init(&recorder->queue);
recorder->stopped = false; recorder->stopped = false;
recorder->failed = false; recorder->failed = false;
recorder->header_written = false; recorder->header_written = false;
@@ -340,7 +341,7 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) {
return false; return false;
} }
sc_queue_push(&recorder->queue, next, rec); queue_push(&recorder->queue, next, rec);
sc_cond_signal(&recorder->queue_cond); sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);

View File

@@ -17,7 +17,7 @@ struct record_packet {
struct record_packet *next; struct record_packet *next;
}; };
struct recorder_queue SC_QUEUE(struct record_packet); struct recorder_queue QUEUE(struct record_packet);
struct recorder { struct recorder {
struct sc_packet_sink packet_sink; // packet sink trait struct sc_packet_sink packet_sink; // packet sink trait

View File

@@ -381,7 +381,6 @@ scrcpy(const struct scrcpy_options *options) {
.rotation = options->rotation, .rotation = options->rotation,
.mipmaps = options->mipmaps, .mipmaps = options->mipmaps,
.fullscreen = options->fullscreen, .fullscreen = options->fullscreen,
.buffering_time = options->display_buffer,
}; };
if (!screen_init(&s->screen, &screen_params)) { if (!screen_init(&s->screen, &screen_params)) {
@@ -394,8 +393,7 @@ scrcpy(const struct scrcpy_options *options) {
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
if (options->v4l2_device) { if (options->v4l2_device) {
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size, if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size)) {
options->v4l2_buffer)) {
goto end; goto end;
} }

View File

@@ -7,8 +7,6 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "util/tick.h"
enum sc_log_level { enum sc_log_level {
SC_LOG_LEVEL_VERBOSE, SC_LOG_LEVEL_VERBOSE,
SC_LOG_LEVEL_DEBUG, SC_LOG_LEVEL_DEBUG,
@@ -80,8 +78,6 @@ struct scrcpy_options {
uint16_t window_width; uint16_t window_width;
uint16_t window_height; uint16_t window_height;
uint32_t display_id; uint32_t display_id;
sc_tick display_buffer;
sc_tick v4l2_buffer;
bool show_touches; bool show_touches;
bool fullscreen; bool fullscreen;
bool always_on_top; bool always_on_top;
@@ -130,8 +126,6 @@ struct scrcpy_options {
.window_width = 0, \ .window_width = 0, \
.window_height = 0, \ .window_height = 0, \
.display_id = 0, \ .display_id = 0, \
.display_buffer = 0, \
.v4l2_buffer = 0, \
.show_touches = false, \ .show_touches = false, \
.fullscreen = false, \ .fullscreen = false, \
.always_on_top = false, \ .always_on_top = false, \

View File

@@ -274,16 +274,14 @@ screen_frame_sink_close(struct sc_frame_sink *sink) {
static bool static bool
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct screen *screen = DOWNCAST(sink); struct screen *screen = DOWNCAST(sink);
return sc_video_buffer_push(&screen->vb, frame);
}
static void bool previous_frame_skipped;
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped, bool ok = sc_video_buffer_push(&screen->vb, frame, &previous_frame_skipped);
void *userdata) { if (!ok) {
(void) vb; return false;
struct screen *screen = userdata; }
if (previous_skipped) { if (previous_frame_skipped) {
fps_counter_add_skipped_frame(&screen->fps_counter); fps_counter_add_skipped_frame(&screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume // The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead // this new frame instead
@@ -295,6 +293,8 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
// Post the event on the UI thread // Post the event on the UI thread
SDL_PushEvent(&new_frame_event); SDL_PushEvent(&new_frame_event);
} }
return true;
} }
bool bool
@@ -304,26 +304,15 @@ screen_init(struct screen *screen, const struct screen_params *params) {
screen->fullscreen = false; screen->fullscreen = false;
screen->maximized = false; screen->maximized = false;
static const struct sc_video_buffer_callbacks cbs = { bool ok = sc_video_buffer_init(&screen->vb);
.on_new_frame = sc_video_buffer_on_new_frame,
};
bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs,
screen);
if (!ok) { if (!ok) {
LOGE("Could not initialize video buffer"); LOGE("Could not initialize video buffer");
return false; return false;
} }
ok = sc_video_buffer_start(&screen->vb);
if (!ok) {
LOGE("Could not start video_buffer");
goto error_destroy_video_buffer;
}
if (!fps_counter_init(&screen->fps_counter)) { if (!fps_counter_init(&screen->fps_counter)) {
LOGE("Could not initialize FPS counter"); LOGE("Could not initialize FPS counter");
goto error_stop_and_join_video_buffer; goto error_destroy_video_buffer;
} }
screen->frame_size = params->frame_size; screen->frame_size = params->frame_size;
@@ -464,9 +453,6 @@ error_destroy_window:
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
error_destroy_fps_counter: error_destroy_fps_counter:
fps_counter_destroy(&screen->fps_counter); fps_counter_destroy(&screen->fps_counter);
error_stop_and_join_video_buffer:
sc_video_buffer_stop(&screen->vb);
sc_video_buffer_join(&screen->vb);
error_destroy_video_buffer: error_destroy_video_buffer:
sc_video_buffer_destroy(&screen->vb); sc_video_buffer_destroy(&screen->vb);
@@ -485,13 +471,11 @@ screen_hide_window(struct screen *screen) {
void void
screen_interrupt(struct screen *screen) { screen_interrupt(struct screen *screen) {
sc_video_buffer_stop(&screen->vb);
fps_counter_interrupt(&screen->fps_counter); fps_counter_interrupt(&screen->fps_counter);
} }
void void
screen_join(struct screen *screen) { screen_join(struct screen *screen) {
sc_video_buffer_join(&screen->vb);
fps_counter_join(&screen->fps_counter); fps_counter_join(&screen->fps_counter);
} }

View File

@@ -63,8 +63,6 @@ struct screen_params {
bool mipmaps; bool mipmaps;
bool fullscreen; bool fullscreen;
sc_tick buffering_time;
}; };
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)

View File

@@ -554,10 +554,10 @@ server_stop(struct server *server) {
sc_mutex_lock(&server->mutex); sc_mutex_lock(&server->mutex);
bool signaled = false; bool signaled = false;
if (!server->process_terminated) { if (!server->process_terminated) {
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1) #define WATCHDOG_DELAY_MS 1000
signaled = sc_cond_timedwait(&server->process_terminated_cond, signaled = sc_cond_timedwait(&server->process_terminated_cond,
&server->mutex, &server->mutex,
sc_tick_now() + WATCHDOG_DELAY); WATCHDOG_DELAY_MS);
} }
sc_mutex_unlock(&server->mutex); sc_mutex_unlock(&server->mutex);

View File

@@ -151,6 +151,7 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
if (stream->pending) { if (stream->pending) {
// the pending packet must be discarded (consumed or error) // the pending packet must be discarded (consumed or error)
av_packet_unref(stream->pending);
av_packet_free(&stream->pending); av_packet_free(&stream->pending);
} }
@@ -243,6 +244,7 @@ run_stream(void *data) {
LOGD("End of frames"); LOGD("End of frames");
if (stream->pending) { if (stream->pending) {
av_packet_unref(stream->pending);
av_packet_free(&stream->pending); av_packet_free(&stream->pending);
} }

View File

@@ -1,6 +1,6 @@
// generic intrusive FIFO queue // generic intrusive FIFO queue
#ifndef SC_QUEUE_H #ifndef QUEUE_H
#define SC_QUEUE_H #define QUEUE_H
#include "common.h" #include "common.h"
@@ -10,15 +10,15 @@
// To define a queue type of "struct foo": // To define a queue type of "struct foo":
// struct queue_foo QUEUE(struct foo); // struct queue_foo QUEUE(struct foo);
#define SC_QUEUE(TYPE) { \ #define QUEUE(TYPE) { \
TYPE *first; \ TYPE *first; \
TYPE *last; \ TYPE *last; \
} }
#define sc_queue_init(PQ) \ #define queue_init(PQ) \
(void) ((PQ)->first = (PQ)->last = NULL) (void) ((PQ)->first = (PQ)->last = NULL)
#define sc_queue_is_empty(PQ) \ #define queue_is_empty(PQ) \
!(PQ)->first !(PQ)->first
// NEXTFIELD is the field in the ITEM type used for intrusive linked-list // NEXTFIELD is the field in the ITEM type used for intrusive linked-list
@@ -30,30 +30,30 @@
// }; // };
// //
// // define the type "struct my_queue" // // define the type "struct my_queue"
// struct my_queue SC_QUEUE(struct foo); // struct my_queue QUEUE(struct foo);
// //
// struct my_queue queue; // struct my_queue queue;
// sc_queue_init(&queue); // queue_init(&queue);
// //
// struct foo v1 = { .value = 42 }; // struct foo v1 = { .value = 42 };
// struct foo v2 = { .value = 27 }; // struct foo v2 = { .value = 27 };
// //
// sc_queue_push(&queue, next, v1); // queue_push(&queue, next, v1);
// sc_queue_push(&queue, next, v2); // queue_push(&queue, next, v2);
// //
// struct foo *foo; // struct foo *foo;
// sc_queue_take(&queue, next, &foo); // queue_take(&queue, next, &foo);
// assert(foo->value == 42); // assert(foo->value == 42);
// sc_queue_take(&queue, next, &foo); // queue_take(&queue, next, &foo);
// assert(foo->value == 27); // assert(foo->value == 27);
// assert(sc_queue_is_empty(&queue)); // assert(queue_is_empty(&queue));
// //
// push a new item into the queue // push a new item into the queue
#define sc_queue_push(PQ, NEXTFIELD, ITEM) \ #define queue_push(PQ, NEXTFIELD, ITEM) \
(void) ({ \ (void) ({ \
(ITEM)->NEXTFIELD = NULL; \ (ITEM)->NEXTFIELD = NULL; \
if (sc_queue_is_empty(PQ)) { \ if (queue_is_empty(PQ)) { \
(PQ)->first = (PQ)->last = (ITEM); \ (PQ)->first = (PQ)->last = (ITEM); \
} else { \ } else { \
(PQ)->last->NEXTFIELD = (ITEM); \ (PQ)->last->NEXTFIELD = (ITEM); \
@@ -65,9 +65,9 @@
// the result is stored in *(PITEM) // the result is stored in *(PITEM)
// (without typeof(), we could not store a local variable having the correct // (without typeof(), we could not store a local variable having the correct
// type so that we can "return" it) // type so that we can "return" it)
#define sc_queue_take(PQ, NEXTFIELD, PITEM) \ #define queue_take(PQ, NEXTFIELD, PITEM) \
(void) ({ \ (void) ({ \
assert(!sc_queue_is_empty(PQ)); \ assert(!queue_is_empty(PQ)); \
*(PITEM) = (PQ)->first; \ *(PITEM) = (PQ)->first; \
(PQ)->first = (PQ)->first->NEXTFIELD; \ (PQ)->first = (PQ)->first->NEXTFIELD; \
}) })

View File

@@ -123,13 +123,7 @@ sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
} }
bool bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) { sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms) {
sc_tick now = sc_tick_now();
if (deadline <= now) {
return false; // timeout
}
uint32_t ms = SC_TICK_TO_MS(deadline - now);
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms); int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
#ifndef NDEBUG #ifndef NDEBUG
if (r < 0) { if (r < 0) {

View File

@@ -5,8 +5,7 @@
#include <stdatomic.h> #include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#include "tick.h"
/* Forward declarations */ /* Forward declarations */
typedef struct SDL_Thread SDL_Thread; typedef struct SDL_Thread SDL_Thread;
@@ -73,7 +72,7 @@ sc_cond_wait(sc_cond *cond, sc_mutex *mutex);
// return true on signaled, false on timeout // return true on signaled, false on timeout
bool bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline); sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms);
void void
sc_cond_signal(sc_cond *cond); sc_cond_signal(sc_cond *cond);

View File

@@ -1,16 +0,0 @@
#include "tick.h"
#include <SDL2/SDL_timer.h>
sc_tick
sc_tick_now(void) {
// SDL_GetTicks() resolution is in milliseconds, but sc_tick are expressed
// in microseconds to avoid loosing precision on PTS.
//
// SDL_GetPerformanceCounter()/SDL_GetPerformanceFrequency() could be used,
// but:
// - the conversions (to avoid overflow) are not zero-cost, since the
// frequency is not known at compile time;
// - in practice, we don't need more precision for now.
return (sc_tick) SDL_GetTicks() * 1000;
}

View File

@@ -1,21 +0,0 @@
#ifndef SC_TICK_H
#define SC_TICK_H
#include <stdint.h>
typedef int64_t sc_tick;
#define PRItick PRIi64
#define SC_TICK_FREQ 1000000 // microsecond
// To be adapted if SC_TICK_FREQ changes
#define SC_TICK_TO_US(tick) (tick)
#define SC_TICK_TO_MS(tick) ((tick) / 1000)
#define SC_TICK_TO_SEC(tick) ((tick) / 1000000)
#define SC_TICK_FROM_US(us) (us)
#define SC_TICK_FROM_MS(ms) ((ms) * 1000)
#define SC_TICK_FROM_SEC(sec) ((sec) * 1000000)
sc_tick
sc_tick_now(void);
#endif

View File

@@ -121,10 +121,10 @@ run_v4l2_sink(void *data) {
break; break;
} }
vs->has_frame = false;
sc_mutex_unlock(&vs->mutex);
sc_video_buffer_consume(&vs->vb, vs->frame); sc_video_buffer_consume(&vs->vb, vs->frame);
vs->has_frame = false;
sc_mutex_unlock(&vs->mutex);
bool ok = encode_and_write_frame(vs, vs->frame); bool ok = encode_and_write_frame(vs, vs->frame);
av_frame_unref(vs->frame); av_frame_unref(vs->frame);
@@ -139,42 +139,17 @@ run_v4l2_sink(void *data) {
return 0; return 0;
} }
static void
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata) {
(void) vb;
struct sc_v4l2_sink *vs = userdata;
if (!previous_skipped) {
sc_mutex_lock(&vs->mutex);
vs->has_frame = true;
sc_cond_signal(&vs->cond);
sc_mutex_unlock(&vs->mutex);
}
}
static bool static bool
sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
static const struct sc_video_buffer_callbacks cbs = { bool ok = sc_video_buffer_init(&vs->vb);
.on_new_frame = sc_video_buffer_on_new_frame,
};
bool ok = sc_video_buffer_init(&vs->vb, vs->buffering_time, &cbs, vs);
if (!ok) { if (!ok) {
LOGE("Could not initialize video buffer");
return false; return false;
} }
ok = sc_video_buffer_start(&vs->vb);
if (!ok) {
LOGE("Could not start video buffer");
goto error_video_buffer_destroy;
}
ok = sc_mutex_init(&vs->mutex); ok = sc_mutex_init(&vs->mutex);
if (!ok) { if (!ok) {
LOGC("Could not create mutex"); LOGC("Could not create mutex");
goto error_video_buffer_stop_and_join; goto error_video_buffer_destroy;
} }
ok = sc_cond_init(&vs->cond); ok = sc_cond_init(&vs->cond);
@@ -299,9 +274,6 @@ error_cond_destroy:
sc_cond_destroy(&vs->cond); sc_cond_destroy(&vs->cond);
error_mutex_destroy: error_mutex_destroy:
sc_mutex_destroy(&vs->mutex); sc_mutex_destroy(&vs->mutex);
error_video_buffer_stop_and_join:
sc_video_buffer_stop(&vs->vb);
sc_video_buffer_join(&vs->vb);
error_video_buffer_destroy: error_video_buffer_destroy:
sc_video_buffer_destroy(&vs->vb); sc_video_buffer_destroy(&vs->vb);
@@ -315,10 +287,7 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
sc_cond_signal(&vs->cond); sc_cond_signal(&vs->cond);
sc_mutex_unlock(&vs->mutex); sc_mutex_unlock(&vs->mutex);
sc_video_buffer_stop(&vs->vb);
sc_thread_join(&vs->thread, NULL); sc_thread_join(&vs->thread, NULL);
sc_video_buffer_join(&vs->vb);
av_packet_free(&vs->packet); av_packet_free(&vs->packet);
av_frame_free(&vs->frame); av_frame_free(&vs->frame);
@@ -333,7 +302,19 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
static bool static bool
sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) { sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) {
return sc_video_buffer_push(&vs->vb, frame); sc_mutex_lock(&vs->mutex);
bool ok = sc_video_buffer_push(&vs->vb, frame, NULL);
if (!ok) {
return false;
}
vs->has_frame = true;
sc_cond_signal(&vs->cond);
sc_mutex_unlock(&vs->mutex);
return true;
} }
static bool static bool
@@ -356,7 +337,7 @@ sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
bool bool
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
struct size frame_size, sc_tick buffering_time) { struct size frame_size) {
vs->device_name = strdup(device_name); vs->device_name = strdup(device_name);
if (!vs->device_name) { if (!vs->device_name) {
LOGE("Could not strdup v4l2 device name"); LOGE("Could not strdup v4l2 device name");
@@ -364,7 +345,6 @@ sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
} }
vs->frame_size = frame_size; vs->frame_size = frame_size;
vs->buffering_time = buffering_time;
static const struct sc_frame_sink_ops ops = { static const struct sc_frame_sink_ops ops = {
.open = sc_v4l2_frame_sink_open, .open = sc_v4l2_frame_sink_open,

View File

@@ -6,7 +6,6 @@
#include "coords.h" #include "coords.h"
#include "trait/frame_sink.h" #include "trait/frame_sink.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/tick.h"
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
@@ -19,7 +18,6 @@ struct sc_v4l2_sink {
char *device_name; char *device_name;
struct size frame_size; struct size frame_size;
sc_tick buffering_time;
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
@@ -34,7 +32,7 @@ struct sc_v4l2_sink {
bool bool
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
struct size frame_size, sc_tick buffering_time); struct size frame_size);
void void
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs); sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);

View File

@@ -1,252 +1,25 @@
#include "video_buffer.h" #include "video_buffer.h"
#include <assert.h> #include <assert.h>
#include <stdlib.h>
#include <libavutil/avutil.h> #include <libavutil/avutil.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "util/log.h" #include "util/log.h"
#define SC_BUFFERING_NDEBUG // comment to debug
static struct sc_video_buffer_frame *
sc_video_buffer_frame_new(const AVFrame *frame) {
struct sc_video_buffer_frame *vb_frame = malloc(sizeof(*vb_frame));
if (!vb_frame) {
return NULL;
}
vb_frame->frame = av_frame_alloc();
if (!vb_frame->frame) {
free(vb_frame);
return NULL;
}
if (av_frame_ref(vb_frame->frame, frame)) {
av_frame_free(&vb_frame->frame);
free(vb_frame);
return NULL;
}
return vb_frame;
}
static void
sc_video_buffer_frame_delete(struct sc_video_buffer_frame *vb_frame) {
av_frame_unref(vb_frame->frame);
av_frame_free(&vb_frame->frame);
free(vb_frame);
}
static bool
sc_video_buffer_offer(struct sc_video_buffer *vb, const AVFrame *frame) {
bool previous_skipped;
bool ok = sc_frame_buffer_push(&vb->fb, frame, &previous_skipped);
if (!ok) {
return false;
}
vb->cbs->on_new_frame(vb, previous_skipped, vb->cbs_userdata);
return true;
}
static int
run_buffering(void *data) {
struct sc_video_buffer *vb = data;
assert(vb->buffering_time > 0);
for (;;) {
sc_mutex_lock(&vb->b.mutex);
while (!vb->b.stopped && sc_queue_is_empty(&vb->b.queue)) {
sc_cond_wait(&vb->b.queue_cond, &vb->b.mutex);
}
if (vb->b.stopped) {
sc_mutex_unlock(&vb->b.mutex);
goto stopped;
}
struct sc_video_buffer_frame *vb_frame;
sc_queue_take(&vb->b.queue, next, &vb_frame);
sc_tick max_deadline = sc_tick_now() + vb->buffering_time;
// PTS (written by the server) are expressed in microseconds
sc_tick pts = SC_TICK_TO_US(vb_frame->frame->pts);
bool timed_out = false;
while (!vb->b.stopped && !timed_out) {
sc_tick deadline = sc_clock_to_system_time(&vb->b.clock, pts)
+ vb->buffering_time;
if (deadline > max_deadline) {
deadline = max_deadline;
}
timed_out =
!sc_cond_timedwait(&vb->b.wait_cond, &vb->b.mutex, deadline);
}
if (vb->b.stopped) {
sc_video_buffer_frame_delete(vb_frame);
sc_mutex_unlock(&vb->b.mutex);
goto stopped;
}
sc_mutex_unlock(&vb->b.mutex);
#ifndef SC_BUFFERING_NDEBUG
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
pts, vb_frame->push_date, sc_tick_now());
#endif
sc_video_buffer_offer(vb, vb_frame->frame);
sc_video_buffer_frame_delete(vb_frame);
}
stopped:
// Flush queue
while (!sc_queue_is_empty(&vb->b.queue)) {
struct sc_video_buffer_frame *vb_frame;
sc_queue_take(&vb->b.queue, next, &vb_frame);
sc_video_buffer_frame_delete(vb_frame);
}
LOGD("Buffering thread ended");
return 0;
}
bool bool
sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time, sc_video_buffer_init(struct sc_video_buffer *vb) {
const struct sc_video_buffer_callbacks *cbs, return sc_frame_buffer_init(&vb->fb);
void *cbs_userdata) {
bool ok = sc_frame_buffer_init(&vb->fb);
if (!ok) {
return false;
}
assert(buffering_time >= 0);
if (buffering_time) {
ok = sc_mutex_init(&vb->b.mutex);
if (!ok) {
LOGC("Could not create mutex");
sc_frame_buffer_destroy(&vb->fb);
return false;
}
ok = sc_cond_init(&vb->b.queue_cond);
if (!ok) {
LOGC("Could not create cond");
sc_mutex_destroy(&vb->b.mutex);
sc_frame_buffer_destroy(&vb->fb);
return false;
}
ok = sc_cond_init(&vb->b.wait_cond);
if (!ok) {
LOGC("Could not create wait cond");
sc_cond_destroy(&vb->b.queue_cond);
sc_mutex_destroy(&vb->b.mutex);
sc_frame_buffer_destroy(&vb->fb);
return false;
}
sc_clock_init(&vb->b.clock);
sc_queue_init(&vb->b.queue);
}
assert(cbs);
assert(cbs->on_new_frame);
vb->buffering_time = buffering_time;
vb->cbs = cbs;
vb->cbs_userdata = cbs_userdata;
return true;
}
bool
sc_video_buffer_start(struct sc_video_buffer *vb) {
if (vb->buffering_time) {
bool ok =
sc_thread_create(&vb->b.thread, run_buffering, "buffering", vb);
if (!ok) {
LOGE("Could not start buffering thread");
return false;
}
}
return true;
}
void
sc_video_buffer_stop(struct sc_video_buffer *vb) {
if (vb->buffering_time) {
sc_mutex_lock(&vb->b.mutex);
vb->b.stopped = true;
sc_cond_signal(&vb->b.queue_cond);
sc_cond_signal(&vb->b.wait_cond);
sc_mutex_unlock(&vb->b.mutex);
}
}
void
sc_video_buffer_join(struct sc_video_buffer *vb) {
if (vb->buffering_time) {
sc_thread_join(&vb->b.thread, NULL);
}
} }
void void
sc_video_buffer_destroy(struct sc_video_buffer *vb) { sc_video_buffer_destroy(struct sc_video_buffer *vb) {
sc_frame_buffer_destroy(&vb->fb); sc_frame_buffer_destroy(&vb->fb);
if (vb->buffering_time) {
sc_cond_destroy(&vb->b.wait_cond);
sc_cond_destroy(&vb->b.queue_cond);
sc_mutex_destroy(&vb->b.mutex);
}
} }
bool bool
sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) { sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame,
if (!vb->buffering_time) { bool *previous_frame_skipped) {
// No buffering return sc_frame_buffer_push(&vb->fb, frame, previous_frame_skipped);
return sc_video_buffer_offer(vb, frame);
}
sc_mutex_lock(&vb->b.mutex);
sc_tick pts = SC_TICK_FROM_US(frame->pts);
sc_clock_update(&vb->b.clock, sc_tick_now(), pts);
sc_cond_signal(&vb->b.wait_cond);
if (vb->b.clock.count == 1) {
sc_mutex_unlock(&vb->b.mutex);
// First frame, offer it immediately, for two reasons:
// - not to delay the opening of the scrcpy window
// - the buffering estimation needs at least two clock points, so it
// could not handle the first frame
return sc_video_buffer_offer(vb, frame);
}
struct sc_video_buffer_frame *vb_frame = sc_video_buffer_frame_new(frame);
if (!vb_frame) {
sc_mutex_unlock(&vb->b.mutex);
LOGE("Could not allocate frame");
return false;
}
#ifndef SC_BUFFERING_NDEBUG
vb_frame->push_date = sc_tick_now();
#endif
sc_queue_push(&vb->b.queue, next, vb_frame);
sc_cond_signal(&vb->b.queue_cond);
sc_mutex_unlock(&vb->b.mutex);
return true;
} }
void void

View File

@@ -5,70 +5,24 @@
#include <stdbool.h> #include <stdbool.h>
#include "clock.h"
#include "frame_buffer.h" #include "frame_buffer.h"
#include "util/queue.h"
#include "util/thread.h"
#include "util/tick.h"
// forward declarations // forward declarations
typedef struct AVFrame AVFrame; typedef struct AVFrame AVFrame;
struct sc_video_buffer_frame {
AVFrame *frame;
struct sc_video_buffer_frame *next;
#ifndef NDEBUG
sc_tick push_date;
#endif
};
struct sc_video_buffer_frame_queue SC_QUEUE(struct sc_video_buffer_frame);
struct sc_video_buffer { struct sc_video_buffer {
struct sc_frame_buffer fb; struct sc_frame_buffer fb;
sc_tick buffering_time;
// only if buffering_ms > 0
struct {
sc_thread thread;
sc_mutex mutex;
sc_cond queue_cond;
sc_cond wait_cond;
struct sc_clock clock;
struct sc_video_buffer_frame_queue queue;
bool stopped;
} b; // buffering
const struct sc_video_buffer_callbacks *cbs;
void *cbs_userdata;
};
struct sc_video_buffer_callbacks {
void (*on_new_frame)(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata);
}; };
bool bool
sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time, sc_video_buffer_init(struct sc_video_buffer *vb);
const struct sc_video_buffer_callbacks *cbs,
void *cbs_userdata);
bool
sc_video_buffer_start(struct sc_video_buffer *vb);
void
sc_video_buffer_stop(struct sc_video_buffer *vb);
void
sc_video_buffer_join(struct sc_video_buffer *vb);
void void
sc_video_buffer_destroy(struct sc_video_buffer *vb); sc_video_buffer_destroy(struct sc_video_buffer *vb);
bool bool
sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame); sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame,
bool *skipped);
void void
sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst); sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst);

View File

@@ -1,79 +0,0 @@
#include "common.h"
#include <assert.h>
#include "clock.h"
void test_small_rolling_sum(void) {
struct sc_clock clock;
sc_clock_init(&clock);
assert(clock.count == 0);
assert(clock.left_sum.system == 0);
assert(clock.left_sum.stream == 0);
assert(clock.right_sum.system == 0);
assert(clock.right_sum.stream == 0);
sc_clock_update(&clock, 2, 3);
assert(clock.count == 1);
assert(clock.left_sum.system == 0);
assert(clock.left_sum.stream == 0);
assert(clock.right_sum.system == 2);
assert(clock.right_sum.stream == 3);
sc_clock_update(&clock, 10, 20);
assert(clock.count == 2);
assert(clock.left_sum.system == 2);
assert(clock.left_sum.stream == 3);
assert(clock.right_sum.system == 10);
assert(clock.right_sum.stream == 20);
sc_clock_update(&clock, 40, 80);
assert(clock.count == 3);
assert(clock.left_sum.system == 2);
assert(clock.left_sum.stream == 3);
assert(clock.right_sum.system == 50);
assert(clock.right_sum.stream == 100);
sc_clock_update(&clock, 400, 800);
assert(clock.count == 4);
assert(clock.left_sum.system == 12);
assert(clock.left_sum.stream == 23);
assert(clock.right_sum.system == 440);
assert(clock.right_sum.stream == 880);
}
void test_large_rolling_sum(void) {
const unsigned half_range = SC_CLOCK_RANGE / 2;
struct sc_clock clock1;
sc_clock_init(&clock1);
for (unsigned i = 0; i < 5 * half_range; ++i) {
sc_clock_update(&clock1, i, 2 * i + 1);
}
struct sc_clock clock2;
sc_clock_init(&clock2);
for (unsigned i = 3 * half_range; i < 5 * half_range; ++i) {
sc_clock_update(&clock2, i, 2 * i + 1);
}
assert(clock1.count == SC_CLOCK_RANGE);
assert(clock2.count == SC_CLOCK_RANGE);
// The values before the last SC_CLOCK_RANGE points in clock1 should have
// no impact
assert(clock1.left_sum.system == clock2.left_sum.system);
assert(clock1.left_sum.stream == clock2.left_sum.stream);
assert(clock1.right_sum.system == clock2.right_sum.system);
assert(clock1.right_sum.stream == clock2.right_sum.stream);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_small_rolling_sum();
test_large_rolling_sum();
return 0;
};

View File

@@ -10,28 +10,28 @@ struct foo {
}; };
static void test_queue(void) { static void test_queue(void) {
struct my_queue SC_QUEUE(struct foo) queue; struct my_queue QUEUE(struct foo) queue;
sc_queue_init(&queue); queue_init(&queue);
assert(sc_queue_is_empty(&queue)); assert(queue_is_empty(&queue));
struct foo v1 = { .value = 42 }; struct foo v1 = { .value = 42 };
struct foo v2 = { .value = 27 }; struct foo v2 = { .value = 27 };
sc_queue_push(&queue, next, &v1); queue_push(&queue, next, &v1);
sc_queue_push(&queue, next, &v2); queue_push(&queue, next, &v2);
struct foo *foo; struct foo *foo;
assert(!sc_queue_is_empty(&queue)); assert(!queue_is_empty(&queue));
sc_queue_take(&queue, next, &foo); queue_take(&queue, next, &foo);
assert(foo->value == 42); assert(foo->value == 42);
assert(!sc_queue_is_empty(&queue)); assert(!queue_is_empty(&queue));
sc_queue_take(&queue, next, &foo); queue_take(&queue, next, &foo);
assert(foo->value == 27); assert(foo->value == 27);
assert(sc_queue_is_empty(&queue)); assert(queue_is_empty(&queue));
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {