Compare commits
27 Commits
buffering.
...
net_perror
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d1c9f36f4 | ||
|
|
4d6dd9d281 | ||
|
|
b5e98db635 | ||
|
|
116acc8d25 | ||
|
|
3a39bacb76 | ||
|
|
3fdc89ad42 | ||
|
|
3761f56c28 | ||
|
|
f78608ab29 | ||
|
|
6f03022646 | ||
|
|
daf90d33d5 | ||
|
|
0ae10f2b39 | ||
|
|
4c4d02295c | ||
|
|
2f03141e9f | ||
|
|
3397720330 | ||
|
|
79278961b9 | ||
|
|
408a301201 | ||
|
|
4d8bcfc68a | ||
|
|
336248df08 | ||
|
|
28bce48d47 | ||
|
|
32e692d5d2 | ||
|
|
ec871dd3f5 | ||
|
|
5524f378c8 | ||
|
|
4ed3aa3604 | ||
|
|
40cea1f677 | ||
|
|
099cba07f0 | ||
|
|
af8a21ed7c | ||
|
|
5938e862a1 |
18
README.md
18
README.md
@@ -321,6 +321,24 @@ 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
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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',
|
||||||
@@ -26,6 +27,7 @@ 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'
|
||||||
@@ -166,6 +168,10 @@ 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',
|
||||||
|
|||||||
22
app/scrcpy.1
22
app/scrcpy.1
@@ -56,6 +56,12 @@ 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).
|
||||||
@@ -83,8 +89,8 @@ 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 rotation counterclockwise.
|
||||||
|
|
||||||
Default is "unlocked".
|
Default is "unlocked".
|
||||||
|
|
||||||
@@ -189,7 +195,15 @@ 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 --lock-video-orientation).
|
It requires to lock the video orientation (see \fB\-\-lock\-video\-orientation\fR).
|
||||||
|
|
||||||
|
.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.
|
||||||
|
|
||||||
|
Default is 0 (no buffering).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-V, \-\-verbosity " value
|
.BI "\-V, \-\-verbosity " value
|
||||||
@@ -240,7 +254,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 \-\-shortcut-mod (see above).
|
Alt or (left) Super, but it can be configured by \fB\-\-shortcut\-mod\fR (see above).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B MOD+f
|
.B MOD+f
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ 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"
|
||||||
@@ -79,7 +85,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"
|
||||||
@@ -182,6 +188,15 @@ 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"
|
||||||
@@ -392,6 +407,19 @@ 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, 0x7FFFFFFF,
|
||||||
|
"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) {
|
||||||
@@ -689,6 +717,8 @@ 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[]) {
|
||||||
@@ -700,6 +730,7 @@ 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},
|
||||||
@@ -732,6 +763,7 @@ 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'},
|
||||||
@@ -917,10 +949,20 @@ 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
|
||||||
@@ -941,6 +983,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
"See --lock-video-orientation.");
|
"See --lock-video-orientation.");
|
||||||
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts->v4l2_buffer && !opts->v4l2_device) {
|
||||||
|
LOGE("V4L2 buffer value without V4L2 sink\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
if (!opts->display && !opts->record_filename) {
|
if (!opts->display && !opts->record_filename) {
|
||||||
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
||||||
|
|||||||
111
app/src/clock.c
Normal file
111
app/src/clock.c
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
70
app/src/clock.h
Normal file
70
app/src/clock.h
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#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
|
||||||
@@ -70,7 +70,7 @@ process_msg(struct controller *controller,
|
|||||||
if (!length) {
|
if (!length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int w = net_send_all(controller->control_socket, serialized_msg, length);
|
ssize_t w = net_send_all(controller->control_socket, serialized_msg, length);
|
||||||
return (size_t) w == length;
|
return (size_t) w == length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define FPS_COUNTER_INTERVAL_MS 1000
|
#define FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1)
|
||||||
|
|
||||||
bool
|
bool
|
||||||
fps_counter_init(struct fps_counter *counter) {
|
fps_counter_init(struct fps_counter *counter) {
|
||||||
@@ -46,7 +46,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 * 1000 / FPS_COUNTER_INTERVAL_MS;
|
counter->nr_rendered * SC_TICK_FREQ / FPS_COUNTER_INTERVAL;
|
||||||
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 +67,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_MS + 1;
|
(now - counter->next_timestamp) / FPS_COUNTER_INTERVAL + 1;
|
||||||
counter->next_timestamp += FPS_COUNTER_INTERVAL_MS * elapsed_slices;
|
counter->next_timestamp += FPS_COUNTER_INTERVAL * elapsed_slices;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -96,7 +96,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_MS;
|
counter->next_timestamp = sc_tick_now() + FPS_COUNTER_INTERVAL;
|
||||||
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);
|
||||||
|
|||||||
@@ -213,7 +213,8 @@ run_recorder(void *data) {
|
|||||||
LOGE("Recording failed to %s", recorder->filename);
|
LOGE("Recording failed to %s", recorder->filename);
|
||||||
} else {
|
} else {
|
||||||
const char *format_name = recorder_get_format_name(recorder->format);
|
const char *format_name = recorder_get_format_name(recorder->format);
|
||||||
LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
|
LOGI("Recording complete to %s file: %s", format_name,
|
||||||
|
recorder->filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGD("Recorder thread ended");
|
LOGD("Recorder thread ended");
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
|
|
||||||
av_log_set_callback(av_log_callback);
|
av_log_set_callback(av_log_callback);
|
||||||
|
|
||||||
const struct stream_callbacks stream_cbs = {
|
static const struct stream_callbacks stream_cbs = {
|
||||||
.on_eos = stream_on_eos,
|
.on_eos = stream_on_eos,
|
||||||
};
|
};
|
||||||
stream_init(&s->stream, s->server.video_socket, &stream_cbs, NULL);
|
stream_init(&s->stream, s->server.video_socket, &stream_cbs, NULL);
|
||||||
@@ -381,6 +381,7 @@ 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)) {
|
||||||
@@ -393,7 +394,8 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
#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,
|
||||||
@@ -78,6 +80,8 @@ 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;
|
||||||
@@ -126,6 +130,8 @@ 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, \
|
||||||
|
|||||||
@@ -308,7 +308,8 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
|||||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
.on_new_frame = sc_video_buffer_on_new_frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
bool ok = sc_video_buffer_init(&screen->vb, 2000, &cbs, screen);
|
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;
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ 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)
|
||||||
|
|||||||
@@ -60,7 +60,20 @@ get_server_path(void) {
|
|||||||
// not found, use current directory
|
// not found, use current directory
|
||||||
return strdup(SERVER_FILENAME);
|
return strdup(SERVER_FILENAME);
|
||||||
}
|
}
|
||||||
char *dir = dirname(executable_path);
|
|
||||||
|
// dirname() does not work correctly everywhere, so get the parent
|
||||||
|
// directory manually.
|
||||||
|
// See <https://github.com/Genymobile/scrcpy/issues/2619>
|
||||||
|
char *p = strrchr(executable_path, PATH_SEPARATOR);
|
||||||
|
if (!p) {
|
||||||
|
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
|
||||||
|
executable_path, PATH_SEPARATOR);
|
||||||
|
free(executable_path);
|
||||||
|
return strdup(SERVER_FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = '\0'; // modify executable_path in place
|
||||||
|
char *dir = executable_path;
|
||||||
size_t dirlen = strlen(dir);
|
size_t dirlen = strlen(dir);
|
||||||
|
|
||||||
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
|
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
|
||||||
@@ -261,7 +274,8 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
sprintf(max_size_string, "%"PRIu16, params->max_size);
|
sprintf(max_size_string, "%"PRIu16, params->max_size);
|
||||||
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
|
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
|
||||||
sprintf(max_fps_string, "%"PRIu16, params->max_fps);
|
sprintf(max_fps_string, "%"PRIu16, params->max_fps);
|
||||||
sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation);
|
sprintf(lock_video_orientation_string, "%"PRIi8,
|
||||||
|
params->lock_video_orientation);
|
||||||
sprintf(display_id_string, "%"PRIu32, params->display_id);
|
sprintf(display_id_string, "%"PRIu32, params->display_id);
|
||||||
const char *const cmd[] = {
|
const char *const cmd[] = {
|
||||||
"shell",
|
"shell",
|
||||||
@@ -271,7 +285,8 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
# define SERVER_DEBUGGER_PORT "5005"
|
# define SERVER_DEBUGGER_PORT "5005"
|
||||||
# ifdef SERVER_DEBUGGER_METHOD_NEW
|
# ifdef SERVER_DEBUGGER_METHOD_NEW
|
||||||
/* Android 9 and above */
|
/* Android 9 and above */
|
||||||
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,server=y,address="
|
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,"
|
||||||
|
"server=y,address="
|
||||||
# else
|
# else
|
||||||
/* Android 8 and below */
|
/* Android 8 and below */
|
||||||
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
|
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
|
||||||
@@ -468,7 +483,7 @@ error:
|
|||||||
static bool
|
static bool
|
||||||
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
|
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
|
||||||
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
|
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
|
||||||
int r = net_recv_all(device_socket, buf, sizeof(buf));
|
ssize_t r = net_recv_all(device_socket, buf, sizeof(buf));
|
||||||
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
|
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
|
||||||
LOGE("Could not retrieve device information");
|
LOGE("Could not retrieve device information");
|
||||||
return false;
|
return false;
|
||||||
@@ -554,10 +569,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_MS 1000
|
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1)
|
||||||
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_MS);
|
sc_tick_now() + WATCHDOG_DELAY);
|
||||||
}
|
}
|
||||||
sc_mutex_unlock(&server->mutex);
|
sc_mutex_unlock(&server->mutex);
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,27 @@
|
|||||||
typedef struct in_addr IN_ADDR;
|
typedef struct in_addr IN_ADDR;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void
|
||||||
|
net_perror(const char *s) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
int error = WSAGetLastError();
|
||||||
|
char *wsa_message;
|
||||||
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
||||||
|
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
|
(char *) &wsa_message, 0, NULL);
|
||||||
|
// no explicit '\n', wsa_message already contains a trailing '\n'
|
||||||
|
fprintf(stderr, "%s: [%d] %s", s, error, wsa_message);
|
||||||
|
LocalFree(wsa_message);
|
||||||
|
#else
|
||||||
|
perror(s);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
socket_t
|
socket_t
|
||||||
net_connect(uint32_t addr, uint16_t port) {
|
net_connect(uint32_t addr, uint16_t port) {
|
||||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if (sock == INVALID_SOCKET) {
|
if (sock == INVALID_SOCKET) {
|
||||||
perror("socket");
|
net_perror("socket");
|
||||||
return INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +49,7 @@ net_connect(uint32_t addr, uint16_t port) {
|
|||||||
sin.sin_port = htons(port);
|
sin.sin_port = htons(port);
|
||||||
|
|
||||||
if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
|
if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
|
||||||
perror("connect");
|
net_perror("connect");
|
||||||
net_close(sock);
|
net_close(sock);
|
||||||
return INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
@@ -45,14 +61,14 @@ socket_t
|
|||||||
net_listen(uint32_t addr, uint16_t port, int backlog) {
|
net_listen(uint32_t addr, uint16_t port, int backlog) {
|
||||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if (sock == INVALID_SOCKET) {
|
if (sock == INVALID_SOCKET) {
|
||||||
perror("socket");
|
net_perror("socket");
|
||||||
return INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
int reuse = 1;
|
int reuse = 1;
|
||||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
|
||||||
sizeof(reuse)) == -1) {
|
sizeof(reuse)) == -1) {
|
||||||
perror("setsockopt(SO_REUSEADDR)");
|
net_perror("setsockopt(SO_REUSEADDR)");
|
||||||
}
|
}
|
||||||
|
|
||||||
SOCKADDR_IN sin;
|
SOCKADDR_IN sin;
|
||||||
@@ -61,13 +77,13 @@ net_listen(uint32_t addr, uint16_t port, int backlog) {
|
|||||||
sin.sin_port = htons(port);
|
sin.sin_port = htons(port);
|
||||||
|
|
||||||
if (bind(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
|
if (bind(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
|
||||||
perror("bind");
|
net_perror("bind");
|
||||||
net_close(sock);
|
net_close(sock);
|
||||||
return INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listen(sock, backlog) == SOCKET_ERROR) {
|
if (listen(sock, backlog) == SOCKET_ERROR) {
|
||||||
perror("listen");
|
net_perror("listen");
|
||||||
net_close(sock);
|
net_close(sock);
|
||||||
return INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
@@ -99,16 +115,18 @@ net_send(socket_t socket, const void *buf, size_t len) {
|
|||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
net_send_all(socket_t socket, const void *buf, size_t len) {
|
net_send_all(socket_t socket, const void *buf, size_t len) {
|
||||||
|
size_t copied = 0;
|
||||||
ssize_t w = 0;
|
ssize_t w = 0;
|
||||||
while (len > 0) {
|
while (len > 0) {
|
||||||
w = send(socket, buf, len, 0);
|
w = send(socket, buf, len, 0);
|
||||||
if (w == -1) {
|
if (w == -1) {
|
||||||
return -1;
|
return copied ? (ssize_t) copied : -1;
|
||||||
}
|
}
|
||||||
len -= w;
|
len -= w;
|
||||||
buf = (char *) buf + w;
|
buf = (char *) buf + w;
|
||||||
|
copied += w;
|
||||||
}
|
}
|
||||||
return w;
|
return copied;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
#include <SDL2/SDL_thread.h>
|
||||||
#include <SDL2/SDL_timer.h>
|
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
@@ -130,8 +129,8 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) {
|
|||||||
return false; // timeout
|
return false; // timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_tick delay = deadline - now;
|
uint32_t ms = SC_TICK_TO_MS(deadline - now);
|
||||||
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, delay);
|
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
|
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
|
||||||
@@ -170,11 +169,3 @@ sc_cond_broadcast(sc_cond *cond) {
|
|||||||
(void) r;
|
(void) r;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_tick
|
|
||||||
sc_tick_now(void) {
|
|
||||||
// SDL ticks is an unsigned 32 bits, but this is an implementation detail.
|
|
||||||
// It wraps if the program runs for more than ~49 days, but in practice we
|
|
||||||
// can assume it does not.
|
|
||||||
return (sc_tick) SDL_GetTicks();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
#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;
|
||||||
@@ -16,8 +17,6 @@ typedef int sc_thread_fn(void *);
|
|||||||
typedef unsigned sc_thread_id;
|
typedef unsigned sc_thread_id;
|
||||||
typedef atomic_uint sc_atomic_thread_id;
|
typedef atomic_uint sc_atomic_thread_id;
|
||||||
|
|
||||||
typedef int64_t sc_tick;
|
|
||||||
|
|
||||||
typedef struct sc_thread {
|
typedef struct sc_thread {
|
||||||
SDL_Thread *thread;
|
SDL_Thread *thread;
|
||||||
} sc_thread;
|
} sc_thread;
|
||||||
@@ -82,7 +81,4 @@ sc_cond_signal(sc_cond *cond);
|
|||||||
void
|
void
|
||||||
sc_cond_broadcast(sc_cond *cond);
|
sc_cond_broadcast(sc_cond *cond);
|
||||||
|
|
||||||
sc_tick
|
|
||||||
sc_tick_now(void);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
16
app/src/util/tick.c
Normal file
16
app/src/util/tick.c
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#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 store PTS without precision loss.
|
||||||
|
//
|
||||||
|
// As an alternative, SDL_GetPerformanceCounter() and
|
||||||
|
// SDL_GetPerformanceFrequency() could be used, but:
|
||||||
|
// - the conversions (avoiding overflow) are expansive, 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;
|
||||||
|
}
|
||||||
21
app/src/util/tick.h
Normal file
21
app/src/util/tick.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#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
|
||||||
@@ -159,7 +159,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
|
|||||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
.on_new_frame = sc_video_buffer_on_new_frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
bool ok = sc_video_buffer_init(&vs->vb, 1, &cbs, vs);
|
bool ok = sc_video_buffer_init(&vs->vb, vs->buffering_time, &cbs, vs);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not initialize video buffer");
|
LOGE("Could not initialize video buffer");
|
||||||
return false;
|
return false;
|
||||||
@@ -356,7 +356,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) {
|
struct size frame_size, sc_tick buffering_time) {
|
||||||
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,6 +364,7 @@ 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,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#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>
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ 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;
|
||||||
@@ -32,7 +34,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);
|
struct size frame_size, sc_tick buffering_time);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
||||||
|
|||||||
@@ -8,58 +8,7 @@
|
|||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define SC_CLOCK_AVERAGE_RANGE 32
|
#define SC_BUFFERING_NDEBUG // comment to debug
|
||||||
|
|
||||||
static void
|
|
||||||
sc_clock_init(struct sc_clock *clock) {
|
|
||||||
clock->coeff = 1;
|
|
||||||
clock->offset = 0;
|
|
||||||
clock->weight = 0;
|
|
||||||
|
|
||||||
clock->last.system = 0;
|
|
||||||
clock->last.stream = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static sc_tick
|
|
||||||
sc_clock_to_system_ts(struct sc_clock *clock, sc_tick stream_ts) {
|
|
||||||
assert(clock->weight); // sc_clock_update() must have been called
|
|
||||||
return (sc_tick) (stream_ts * clock->coeff) + clock->offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_clock_update(struct sc_clock *clock, sc_tick now, sc_tick stream_ts) {
|
|
||||||
double instant_coeff;
|
|
||||||
sc_tick mad;
|
|
||||||
if (clock->weight) {
|
|
||||||
sc_tick system_delta = now - clock->last.system;
|
|
||||||
sc_tick stream_delta = stream_ts - clock->last.stream;
|
|
||||||
instant_coeff = (double) system_delta / stream_delta;
|
|
||||||
sc_tick system_pts = sc_clock_to_system_ts(clock, stream_ts);
|
|
||||||
mad = llabs(now - system_pts);
|
|
||||||
} else {
|
|
||||||
// This is the first update, we cannot compute delta
|
|
||||||
instant_coeff = 1;
|
|
||||||
mad = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clock->weight < SC_CLOCK_AVERAGE_RANGE) {
|
|
||||||
++clock->weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// (1-t) * avg + t * new
|
|
||||||
clock->coeff = ((clock->weight - 1) * clock->coeff + instant_coeff)
|
|
||||||
/ clock->weight;
|
|
||||||
|
|
||||||
// FIXME it cannot change at every frame!
|
|
||||||
clock->offset = now - (sc_tick) (stream_ts * clock->coeff);
|
|
||||||
|
|
||||||
LOGD("%g x + %ld", clock->coeff, clock->offset);
|
|
||||||
|
|
||||||
clock->mad = ((clock->weight - 1) * clock->mad + mad) / clock->weight;
|
|
||||||
|
|
||||||
clock->last.system = now;
|
|
||||||
clock->last.stream = stream_ts;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct sc_video_buffer_frame *
|
static struct sc_video_buffer_frame *
|
||||||
sc_video_buffer_frame_new(const AVFrame *frame) {
|
sc_video_buffer_frame_new(const AVFrame *frame) {
|
||||||
@@ -106,7 +55,7 @@ static int
|
|||||||
run_buffering(void *data) {
|
run_buffering(void *data) {
|
||||||
struct sc_video_buffer *vb = data;
|
struct sc_video_buffer *vb = data;
|
||||||
|
|
||||||
assert(vb->buffering_ms);
|
assert(vb->buffering_time > 0);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
sc_mutex_lock(&vb->b.mutex);
|
sc_mutex_lock(&vb->b.mutex);
|
||||||
@@ -123,22 +72,18 @@ run_buffering(void *data) {
|
|||||||
struct sc_video_buffer_frame *vb_frame;
|
struct sc_video_buffer_frame *vb_frame;
|
||||||
sc_queue_take(&vb->b.queue, next, &vb_frame);
|
sc_queue_take(&vb->b.queue, next, &vb_frame);
|
||||||
|
|
||||||
sc_tick now = sc_tick_now();
|
sc_tick max_deadline = sc_tick_now() + vb->buffering_time;
|
||||||
// FIXME time_base units
|
// PTS (written by the server) are expressed in microseconds
|
||||||
int64_t pts = vb_frame->frame->pts; // micros to millis
|
sc_tick pts = SC_TICK_TO_US(vb_frame->frame->pts);
|
||||||
LOGD("==== pts = %ld", pts);
|
|
||||||
sc_tick system_pts = sc_clock_to_system_ts(&vb->b.clock, pts);
|
|
||||||
if (now + vb->buffering_ms < system_pts) {
|
|
||||||
system_pts = now + vb->buffering_ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_tick deadline = system_pts + vb->buffering_ms;
|
|
||||||
|
|
||||||
LOGD("==== %ld %ld %ld\n", now, system_pts, deadline);
|
|
||||||
|
|
||||||
LOGD("[WAITING FOR] %ld ", deadline-now);
|
|
||||||
bool timed_out = false;
|
bool timed_out = false;
|
||||||
while (!vb->b.stopped && !timed_out) {
|
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 =
|
timed_out =
|
||||||
!sc_cond_timedwait(&vb->b.wait_cond, &vb->b.mutex, deadline);
|
!sc_cond_timedwait(&vb->b.wait_cond, &vb->b.mutex, deadline);
|
||||||
}
|
}
|
||||||
@@ -151,6 +96,11 @@ run_buffering(void *data) {
|
|||||||
|
|
||||||
sc_mutex_unlock(&vb->b.mutex);
|
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_offer(vb, vb_frame->frame);
|
||||||
|
|
||||||
sc_video_buffer_frame_delete(vb_frame);
|
sc_video_buffer_frame_delete(vb_frame);
|
||||||
@@ -170,7 +120,7 @@ stopped:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_video_buffer_init(struct sc_video_buffer *vb, unsigned buffering_ms,
|
sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time,
|
||||||
const struct sc_video_buffer_callbacks *cbs,
|
const struct sc_video_buffer_callbacks *cbs,
|
||||||
void *cbs_userdata) {
|
void *cbs_userdata) {
|
||||||
bool ok = sc_frame_buffer_init(&vb->fb);
|
bool ok = sc_frame_buffer_init(&vb->fb);
|
||||||
@@ -178,7 +128,8 @@ sc_video_buffer_init(struct sc_video_buffer *vb, unsigned buffering_ms,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffering_ms) {
|
assert(buffering_time >= 0);
|
||||||
|
if (buffering_time) {
|
||||||
ok = sc_mutex_init(&vb->b.mutex);
|
ok = sc_mutex_init(&vb->b.mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGC("Could not create mutex");
|
LOGC("Could not create mutex");
|
||||||
@@ -210,7 +161,7 @@ sc_video_buffer_init(struct sc_video_buffer *vb, unsigned buffering_ms,
|
|||||||
assert(cbs);
|
assert(cbs);
|
||||||
assert(cbs->on_new_frame);
|
assert(cbs->on_new_frame);
|
||||||
|
|
||||||
vb->buffering_ms = buffering_ms;
|
vb->buffering_time = buffering_time;
|
||||||
vb->cbs = cbs;
|
vb->cbs = cbs;
|
||||||
vb->cbs_userdata = cbs_userdata;
|
vb->cbs_userdata = cbs_userdata;
|
||||||
return true;
|
return true;
|
||||||
@@ -218,7 +169,7 @@ sc_video_buffer_init(struct sc_video_buffer *vb, unsigned buffering_ms,
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
sc_video_buffer_start(struct sc_video_buffer *vb) {
|
sc_video_buffer_start(struct sc_video_buffer *vb) {
|
||||||
if (vb->buffering_ms) {
|
if (vb->buffering_time) {
|
||||||
bool ok =
|
bool ok =
|
||||||
sc_thread_create(&vb->b.thread, run_buffering, "buffering", vb);
|
sc_thread_create(&vb->b.thread, run_buffering, "buffering", vb);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -232,7 +183,7 @@ sc_video_buffer_start(struct sc_video_buffer *vb) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
sc_video_buffer_stop(struct sc_video_buffer *vb) {
|
sc_video_buffer_stop(struct sc_video_buffer *vb) {
|
||||||
if (vb->buffering_ms) {
|
if (vb->buffering_time) {
|
||||||
sc_mutex_lock(&vb->b.mutex);
|
sc_mutex_lock(&vb->b.mutex);
|
||||||
vb->b.stopped = true;
|
vb->b.stopped = true;
|
||||||
sc_cond_signal(&vb->b.queue_cond);
|
sc_cond_signal(&vb->b.queue_cond);
|
||||||
@@ -243,7 +194,7 @@ sc_video_buffer_stop(struct sc_video_buffer *vb) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
sc_video_buffer_join(struct sc_video_buffer *vb) {
|
sc_video_buffer_join(struct sc_video_buffer *vb) {
|
||||||
if (vb->buffering_ms) {
|
if (vb->buffering_time) {
|
||||||
sc_thread_join(&vb->b.thread, NULL);
|
sc_thread_join(&vb->b.thread, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,7 +202,7 @@ 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) {
|
||||||
sc_frame_buffer_destroy(&vb->fb);
|
sc_frame_buffer_destroy(&vb->fb);
|
||||||
if (vb->buffering_ms) {
|
if (vb->buffering_time) {
|
||||||
sc_cond_destroy(&vb->b.wait_cond);
|
sc_cond_destroy(&vb->b.wait_cond);
|
||||||
sc_cond_destroy(&vb->b.queue_cond);
|
sc_cond_destroy(&vb->b.queue_cond);
|
||||||
sc_mutex_destroy(&vb->b.mutex);
|
sc_mutex_destroy(&vb->b.mutex);
|
||||||
@@ -260,22 +211,39 @@ 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) {
|
||||||
if (!vb->buffering_ms) {
|
if (!vb->buffering_time) {
|
||||||
// no buffering
|
// No buffering
|
||||||
|
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);
|
return sc_video_buffer_offer(vb, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_video_buffer_frame *vb_frame = sc_video_buffer_frame_new(frame);
|
struct sc_video_buffer_frame *vb_frame = sc_video_buffer_frame_new(frame);
|
||||||
if (!vb_frame) {
|
if (!vb_frame) {
|
||||||
|
sc_mutex_unlock(&vb->b.mutex);
|
||||||
LOGE("Could not allocate frame");
|
LOGE("Could not allocate frame");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_clock_update(&vb->b.clock, sc_tick_now(), vb_frame->frame->pts);
|
#ifndef SC_BUFFERING_NDEBUG
|
||||||
|
vb_frame->push_date = sc_tick_now();
|
||||||
sc_mutex_lock(&vb->b.mutex);
|
#endif
|
||||||
sc_queue_push(&vb->b.queue, next, vb_frame);
|
sc_queue_push(&vb->b.queue, next, vb_frame);
|
||||||
sc_cond_signal(&vb->b.queue_cond);
|
sc_cond_signal(&vb->b.queue_cond);
|
||||||
|
|
||||||
sc_mutex_unlock(&vb->b.mutex);
|
sc_mutex_unlock(&vb->b.mutex);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -5,40 +5,31 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "clock.h"
|
||||||
#include "frame_buffer.h"
|
#include "frame_buffer.h"
|
||||||
#include "util/queue.h"
|
#include "util/queue.h"
|
||||||
#include "util/thread.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 {
|
struct sc_video_buffer_frame {
|
||||||
AVFrame *frame;
|
AVFrame *frame;
|
||||||
sc_tick system_pts;
|
|
||||||
struct sc_video_buffer_frame *next;
|
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_frame_queue SC_QUEUE(struct sc_video_buffer_frame);
|
||||||
|
|
||||||
struct sc_clock {
|
|
||||||
double coeff;
|
|
||||||
sc_tick offset;
|
|
||||||
unsigned weight;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
sc_tick system;
|
|
||||||
sc_tick stream;
|
|
||||||
} last;
|
|
||||||
|
|
||||||
sc_tick mad; // mean absolute difference
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sc_video_buffer {
|
struct sc_video_buffer {
|
||||||
struct sc_frame_buffer fb;
|
struct sc_frame_buffer fb;
|
||||||
|
|
||||||
unsigned buffering_ms;
|
sc_tick buffering_time;
|
||||||
|
|
||||||
// only if buffering_ms > 0
|
// only if buffering_time > 0
|
||||||
struct {
|
struct {
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
@@ -60,7 +51,7 @@ struct sc_video_buffer_callbacks {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_video_buffer_init(struct sc_video_buffer *vb, unsigned buffering_ms,
|
sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time,
|
||||||
const struct sc_video_buffer_callbacks *cbs,
|
const struct sc_video_buffer_callbacks *cbs,
|
||||||
void *cbs_userdata);
|
void *cbs_userdata);
|
||||||
|
|
||||||
|
|||||||
79
app/tests/test_clock.c
Normal file
79
app/tests/test_clock.c
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#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;
|
||||||
|
};
|
||||||
@@ -17,4 +17,4 @@ endian = 'little'
|
|||||||
[properties]
|
[properties]
|
||||||
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared'
|
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared'
|
||||||
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev'
|
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev'
|
||||||
prebuilt_sdl2 = 'SDL2-2.0.14/i686-w64-mingw32'
|
prebuilt_sdl2 = 'SDL2-2.0.16/i686-w64-mingw32'
|
||||||
|
|||||||
@@ -17,4 +17,4 @@ endian = 'little'
|
|||||||
[properties]
|
[properties]
|
||||||
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared'
|
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared'
|
||||||
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev'
|
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev'
|
||||||
prebuilt_sdl2 = 'SDL2-2.0.14/x86_64-w64-mingw32'
|
prebuilt_sdl2 = 'SDL2-2.0.16/x86_64-w64-mingw32'
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ prepare-ffmpeg-dev-win64:
|
|||||||
ffmpeg-4.3.1-win64-dev
|
ffmpeg-4.3.1-win64-dev
|
||||||
|
|
||||||
prepare-sdl2:
|
prepare-sdl2:
|
||||||
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.14-mingw.tar.gz \
|
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.16-mingw.tar.gz \
|
||||||
405eaff3eb18f2e08fe669ef9e63bc9a8710b7d343756f238619761e9b60407d \
|
2bfe48628aa9635c12eac7d421907e291525de1d0b04b3bca4a5bd6e6c881a6f \
|
||||||
SDL2-2.0.14
|
SDL2-2.0.16
|
||||||
|
|
||||||
prepare-adb:
|
prepare-adb:
|
||||||
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.2-windows.zip \
|
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.3-windows.zip \
|
||||||
d560cb8ded83ae04763b94632673481f14843a5969256569623cfeac82db4ba5 \
|
0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24 \
|
||||||
platform-tools
|
platform-tools
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ dist-win32: build-server build-win32
|
|||||||
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/SDL2-2.0.14/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/SDL2-2.0.16/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
|
|
||||||
dist-win64: build-server build-win64
|
dist-win64: build-server build-win64
|
||||||
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
|
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||||
@@ -118,7 +118,7 @@ dist-win64: build-server build-win64
|
|||||||
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/SDL2-2.0.14/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/SDL2-2.0.16/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
|
|
||||||
zip-win32: dist-win32
|
zip-win32: dist-win32
|
||||||
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \
|
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ public class Controller {
|
|||||||
|
|
||||||
MotionEvent event = MotionEvent
|
MotionEvent event = MotionEvent
|
||||||
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0,
|
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0,
|
||||||
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
InputDevice.SOURCE_MOUSE, 0);
|
||||||
return device.injectEvent(event);
|
return device.injectEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user