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

View File

@@ -90,7 +90,7 @@ This is a workaround for some devices not behaving as expected when setting the
.TP
.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".
@@ -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.
Default is 0 (no buffering).
.TP
.BI "\-V, \-\-verbosity " value
Set the log level ("verbose", "debug", "info", "warn" or "error").

View File

@@ -410,7 +410,7 @@ parse_max_fps(const char *s, uint16_t *max_fps) {
static bool
parse_buffering_time(const char *s, sc_tick *tick) {
long value;
bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF,
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFFFFFF,
"buffering time");
if (!ok) {
return false;
@@ -983,11 +983,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
"See --lock-video-orientation.");
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
if (!opts->display && !opts->record_filename) {
LOGE("-N/--no-display requires screen recording (-r/--record)");

View File

@@ -70,7 +70,7 @@ process_msg(struct controller *controller,
if (!length) {
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;
}

View File

@@ -213,8 +213,7 @@ run_recorder(void *data) {
LOGE("Recording failed to %s", recorder->filename);
} else {
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");

View File

@@ -330,7 +330,7 @@ scrcpy(const struct scrcpy_options *options) {
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,
};
stream_init(&s->stream, s->server.video_socket, &stream_cbs, NULL);

View File

@@ -60,20 +60,7 @@ get_server_path(void) {
// not found, use current directory
return strdup(SERVER_FILENAME);
}
// 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;
char *dir = dirname(executable_path);
size_t dirlen = strlen(dir);
// 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(bit_rate_string, "%"PRIu32, params->bit_rate);
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);
const char *const cmd[] = {
"shell",
@@ -285,8 +271,7 @@ execute_server(struct server *server, const struct server_params *params) {
# define SERVER_DEBUGGER_PORT "5005"
# ifdef SERVER_DEBUGGER_METHOD_NEW
/* 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
/* Android 8 and below */
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
@@ -483,7 +468,7 @@ error:
static bool
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
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) {
LOGE("Could not retrieve device information");
return false;

View File

@@ -19,27 +19,11 @@
typedef struct in_addr IN_ADDR;
#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
net_connect(uint32_t addr, uint16_t port) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
net_perror("socket");
perror("socket");
return INVALID_SOCKET;
}
@@ -49,7 +33,7 @@ net_connect(uint32_t addr, uint16_t port) {
sin.sin_port = htons(port);
if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("connect");
perror("connect");
net_close(sock);
return INVALID_SOCKET;
}
@@ -61,14 +45,14 @@ socket_t
net_listen(uint32_t addr, uint16_t port, int backlog) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
net_perror("socket");
perror("socket");
return INVALID_SOCKET;
}
int reuse = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
sizeof(reuse)) == -1) {
net_perror("setsockopt(SO_REUSEADDR)");
perror("setsockopt(SO_REUSEADDR)");
}
SOCKADDR_IN sin;
@@ -77,13 +61,13 @@ net_listen(uint32_t addr, uint16_t port, int backlog) {
sin.sin_port = htons(port);
if (bind(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("bind");
perror("bind");
net_close(sock);
return INVALID_SOCKET;
}
if (listen(sock, backlog) == SOCKET_ERROR) {
net_perror("listen");
perror("listen");
net_close(sock);
return INVALID_SOCKET;
}
@@ -115,18 +99,16 @@ net_send(socket_t socket, const void *buf, size_t len) {
ssize_t
net_send_all(socket_t socket, const void *buf, size_t len) {
size_t copied = 0;
ssize_t w = 0;
while (len > 0) {
w = send(socket, buf, len, 0);
if (w == -1) {
return copied ? (ssize_t) copied : -1;
return -1;
}
len -= w;
buf = (char *) buf + w;
copied += w;
}
return copied;
return w;
}
bool

View File

@@ -5,11 +5,11 @@
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.
// in microseconds to avoid loosing precision on PTS.
//
// As an alternative, SDL_GetPerformanceCounter() and
// SDL_GetPerformanceFrequency() could be used, but:
// - the conversions (avoiding overflow) are expansive, since the
// 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

@@ -29,7 +29,7 @@ struct sc_video_buffer {
sc_tick buffering_time;
// only if buffering_time > 0
// only if buffering_ms > 0
struct {
sc_thread thread;
sc_mutex mutex;

View File

@@ -17,4 +17,4 @@ endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared'
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'

View File

@@ -17,4 +17,4 @@ endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared'
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'

View File

@@ -30,11 +30,11 @@ prepare-ffmpeg-dev-win64:
ffmpeg-4.3.1-win64-dev
prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.16-mingw.tar.gz \
2bfe48628aa9635c12eac7d421907e291525de1d0b04b3bca4a5bd6e6c881a6f \
SDL2-2.0.16
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.14-mingw.tar.gz \
405eaff3eb18f2e08fe669ef9e63bc9a8710b7d343756f238619761e9b60407d \
SDL2-2.0.14
prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.3-windows.zip \
0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24 \
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.2-windows.zip \
d560cb8ded83ae04763b94632673481f14843a5969256569623cfeac82db4ba5 \
platform-tools

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/AdbWinApi.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
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/AdbWinApi.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
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \

View File

@@ -241,7 +241,7 @@ public class Controller {
MotionEvent event = MotionEvent
.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);
}