Compare commits

..
11 Commits
Author SHA1 Message Date
Romain Vimont b454af6808 Compensate slope on start
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.
2021-07-14 00:39:35 +02:00
Romain Vimont 2b0399a615 Add buffering debugging tools
Output buffering and clock logs by disabling a compilation flag.
2021-07-14 00:39:35 +02:00
Romain Vimont fb6863068c Add clock tests
The clock rolling sum is not trivial. Test it.
2021-07-14 00:39:35 +02:00
Romain Vimont 5cbcd10356 Add buffering command line options
Add --display-buffer and --v4l2-buffer options to configure buffering
time.
2021-07-14 00:39:35 +02:00
Romain Vimont ecf417fc49 Implement buffering
To minimize latency (at the cost of jitter), scrcpy always displays a
frame as soon as it available, without waiting.

However, when recording (--record), it still writes the captured
timestamps to the output file, so that the recorded file can be played
correctly without jitter.

Some real-time use cases might benefit from adding a small latency to
compensate for jitter too. For example, few tens of seconds of latency
for live-streaming are not important, but jitter is noticeable.

Therefore, implement a buffering mechanism (disabled by default) to add
a configurable latency delay.

PR #2417 <https://github.com/Genymobile/scrcpy/issues/2417>
2021-07-14 00:39:35 +02:00
Romain Vimont 85bc66ad05 Notify new frames via callbacks
Currently, a frame is available to the consumer as soon as it is pushed
by the producer (which can detect if the previous frame is skipped).

Notify the new frames (and frame skipped) via callbacks instead.

This paves the way to add (optional) buffering, which will introduce a
delay between the time when the frame is produced and the time it is
available to be consumed.
2021-07-14 00:39:35 +02:00
Romain Vimont ed2ac27596 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-07-14 00:39:35 +02:00
Romain Vimont 70a1b46e13 Rename video_buffer to sc_video_buffer
Add a scrcpy-specific prefix.
2021-07-14 00:39:35 +02:00
Romain Vimont c1eeaa080a Relax v4l2_sink lock constraints
To fix a data race, commit 5caeab5f6d
called video_buffer_push() and video_buffer_consume() under the
v4l2_sink lock.

Instead, use the previous_skipped indication (initialized with video
buffer locked) to lock only for protecting the has_frame flag.

This enables the possibility for the video_buffer to notify new frames
via callbacks without lock inversion issues.
2021-07-14 00:39:35 +02:00
Romain Vimont 7ae18c3601 Replace delay by deadline in timedwait()
The function sc_cond_timedwait() accepted a parameter representing the
max duration to wait, because it internally uses SDL_CondWaitTimeout().

Instead, accept a deadline, to be consistent with
pthread_cond_timedwait().
2021-07-14 00:39:35 +02:00
Romain Vimont 1ffabc6fcd Wrap tick API
This avoids to use the SDL timer API directly, and will allow to handle
generic ticks (possibly negative).
2021-07-14 00:39:35 +02:00
14 changed files with 32 additions and 73 deletions
+1 -3
View File
@@ -90,7 +90,7 @@ This is a workaround for some devices not behaving as expected when setting the
.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 rotation 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".
@@ -203,8 +203,6 @@ Add a buffering delay (in milliseconds) before pushing frames. This increases la
This option is similar to \fB\-\-display\-buffer\fR, but specific to V4L2 sink. 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
Set the log level ("verbose", "debug", "info", "warn" or "error"). Set the log level ("verbose", "debug", "info", "warn" or "error").
+1 -6
View File
@@ -410,7 +410,7 @@ parse_max_fps(const char *s, uint16_t *max_fps) {
static bool static bool
parse_buffering_time(const char *s, sc_tick *tick) { parse_buffering_time(const char *s, sc_tick *tick) {
long value; long value;
bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF, bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFFFFFF,
"buffering time"); "buffering time");
if (!ok) { if (!ok) {
return false; return false;
@@ -983,11 +983,6 @@ 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)");
+1 -1
View File
@@ -70,7 +70,7 @@ process_msg(struct controller *controller,
if (!length) { if (!length) {
return false; return false;
} }
ssize_t w = net_send_all(controller->control_socket, serialized_msg, length); int w = net_send_all(controller->control_socket, serialized_msg, length);
return (size_t) w == length; return (size_t) w == length;
} }
+1 -2
View File
@@ -213,8 +213,7 @@ 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, LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
recorder->filename);
} }
LOGD("Recorder thread ended"); LOGD("Recorder thread ended");
+1 -1
View File
@@ -330,7 +330,7 @@ scrcpy(const struct scrcpy_options *options) {
av_log_set_callback(av_log_callback); av_log_set_callback(av_log_callback);
static const struct stream_callbacks stream_cbs = { 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);
+4 -19
View File
@@ -60,20 +60,7 @@ 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
@@ -274,8 +261,7 @@ 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, sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation);
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",
@@ -285,8 +271,7 @@ 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," "-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,server=y,address="
"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="
@@ -483,7 +468,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];
ssize_t r = net_recv_all(device_socket, buf, sizeof(buf)); int 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;
+8 -26
View File
@@ -19,27 +19,11 @@
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) {
net_perror("socket"); perror("socket");
return INVALID_SOCKET; return INVALID_SOCKET;
} }
@@ -49,7 +33,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) {
net_perror("connect"); perror("connect");
net_close(sock); net_close(sock);
return INVALID_SOCKET; return INVALID_SOCKET;
} }
@@ -61,14 +45,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) {
net_perror("socket"); 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) {
net_perror("setsockopt(SO_REUSEADDR)"); perror("setsockopt(SO_REUSEADDR)");
} }
SOCKADDR_IN sin; SOCKADDR_IN sin;
@@ -77,13 +61,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) {
net_perror("bind"); 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) {
net_perror("listen"); perror("listen");
net_close(sock); net_close(sock);
return INVALID_SOCKET; return INVALID_SOCKET;
} }
@@ -115,18 +99,16 @@ 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 copied ? (ssize_t) copied : -1; return -1;
} }
len -= w; len -= w;
buf = (char *) buf + w; buf = (char *) buf + w;
copied += w;
} }
return copied; return w;
} }
bool bool
+4 -4
View File
@@ -5,11 +5,11 @@
sc_tick sc_tick
sc_tick_now(void) { sc_tick_now(void) {
// SDL_GetTicks() resolution is in milliseconds, but sc_tick are expressed // SDL_GetTicks() resolution is in milliseconds, but sc_tick are expressed
// in microseconds to store PTS without precision loss. // in microseconds to avoid loosing precision on PTS.
// //
// As an alternative, SDL_GetPerformanceCounter() and // SDL_GetPerformanceCounter()/SDL_GetPerformanceFrequency() could be used,
// SDL_GetPerformanceFrequency() could be used, but: // but:
// - the conversions (avoiding overflow) are expansive, since the // - the conversions (to avoid overflow) are not zero-cost, since the
// frequency is not known at compile time; // frequency is not known at compile time;
// - in practice, we don't need more precision for now. // - in practice, we don't need more precision for now.
return (sc_tick) SDL_GetTicks() * 1000; return (sc_tick) SDL_GetTicks() * 1000;
+1 -1
View File
@@ -29,7 +29,7 @@ struct sc_video_buffer {
sc_tick buffering_time; sc_tick buffering_time;
// only if buffering_time > 0 // only if buffering_ms > 0
struct { struct {
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
+1 -1
View File
@@ -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.16/i686-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.0.14/i686-w64-mingw32'
+1 -1
View File
@@ -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.16/x86_64-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.0.14/x86_64-w64-mingw32'
+5 -5
View File
@@ -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.16-mingw.tar.gz \ @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.14-mingw.tar.gz \
2bfe48628aa9635c12eac7d421907e291525de1d0b04b3bca4a5bd6e6c881a6f \ 405eaff3eb18f2e08fe669ef9e63bc9a8710b7d343756f238619761e9b60407d \
SDL2-2.0.16 SDL2-2.0.14
prepare-adb: prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.3-windows.zip \ @./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.2-windows.zip \
0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24 \ d560cb8ded83ae04763b94632673481f14843a5969256569623cfeac82db4ba5 \
platform-tools platform-tools
+2 -2
View File
@@ -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.16/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/SDL2-2.0.14/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.16/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/SDL2-2.0.14/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_MOUSE, 0); InputDevice.SOURCE_TOUCHSCREEN, 0);
return device.injectEvent(event); return device.injectEvent(event);
} }