Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
13b8fc0ad9 Use SOURCE_MOUSE for scroll events
This might explain why scroll events are sometimes not handled properly.

Refs <https://stackoverflow.com/questions/68954790/why-is-lazycolumn-not-scrollable-using-mouse-but-recycler-view-is>
2021-08-28 13:59:21 +02:00
53 changed files with 479 additions and 1449 deletions

View File

@@ -268,10 +268,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v1.19`][direct-scrcpy-server] - [`scrcpy-server-v1.18`][direct-scrcpy-server]
_(SHA-256: 876f9322182e6aac6a58db1334f4225855ef3a17eaebc80aab6601d9d1ecb867)_ _(SHA-256: 641c5c6beda9399dfae72d116f5ff43b5ed1059d871c9ebc3f47610fd33c51a3)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-server-v1.19 [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.18/scrcpy-server-v1.18
Download the prebuilt server somewhere, and specify its path during the Meson Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:

View File

@@ -1,6 +1,4 @@
# scrcpy (v1.19) # scrcpy (v1.18)
![icon](data/icon.png)
[Read in another language](#translations) [Read in another language](#translations)
@@ -90,10 +88,10 @@ process][BUILD_simple]).
For Windows, for simplicity, a prebuilt archive with all the dependencies For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available: (including `adb`) is available:
- [`scrcpy-win64-v1.19.zip`][direct-win64] - [`scrcpy-win64-v1.18.zip`][direct-win64]
_(SHA-256: 383d6483f25ac0092d4bb9fef6c967351ecd50fc248e0c82932db97d6d32f11b)_ _(SHA-256: 37212f5087fe6f3e258f1d44fa5c02207496b30e1d7ec442cbcf8358910a5c63)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-win64-v1.19.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.18/scrcpy-win64-v1.18.zip
It is also available in [Chocolatey]: It is also available in [Chocolatey]:
@@ -323,24 +321,6 @@ For example, you could capture the video within [OBS].
[OBS]: https://obsproject.com/ [OBS]: https://obsproject.com/
#### Buffering
It is possible to add buffering. This increases latency but reduces jitter (see
#2464).
The option is available for display buffering:
```bash
scrcpy --display-buffer=50 # add 50 ms buffering for display
```
and V4L2 sink:
```bash
scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink
```
### Connection ### Connection
#### Wireless #### Wireless

View File

@@ -2,17 +2,14 @@ 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',
'src/decoder.c', 'src/decoder.c',
'src/device_msg.c', 'src/device_msg.c',
'src/event_converter.c', 'src/event_converter.c',
'src/icon.c',
'src/file_handler.c', 'src/file_handler.c',
'src/fps_counter.c', 'src/fps_counter.c',
'src/frame_buffer.c',
'src/input_manager.c', 'src/input_manager.c',
'src/opengl.c', 'src/opengl.c',
'src/receiver.c', 'src/receiver.c',
@@ -21,13 +18,13 @@ src = [
'src/screen.c', 'src/screen.c',
'src/server.c', 'src/server.c',
'src/stream.c', 'src/stream.c',
'src/tiny_xpm.c',
'src/video_buffer.c', 'src/video_buffer.c',
'src/util/log.c', 'src/util/log.c',
'src/util/net.c', 'src/util/net.c',
'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'
@@ -150,9 +147,6 @@ executable('scrcpy', src,
c_args: []) c_args: [])
install_man('scrcpy.1') install_man('scrcpy.1')
install_data('../data/icon.png',
rename: 'scrcpy.png',
install_dir: 'share/icons/hicolor/256x256/apps')
### TESTS ### TESTS
@@ -171,10 +165,6 @@ if get_option('buildtype') == 'debug'
'src/cli.c', 'src/cli.c',
'src/util/str_util.c', 'src/util/str_util.c',
]], ]],
['test_clock', [
'tests/test_clock.c',
'src/clock.c',
]],
['test_control_msg_serialize', [ ['test_control_msg_serialize', [
'tests/test_control_msg_serialize.c', 'tests/test_control_msg_serialize.c',
'src/control_msg.c', 'src/control_msg.c',

View File

@@ -56,12 +56,6 @@ The list of possible display ids can be listed by "adb shell dumpsys display"
Default is 0. Default is 0.
.TP
.BI "\-\-display\-buffer ms
Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter.
Default is 0 (no buffering).
.TP .TP
.BI "\-\-encoder " name .BI "\-\-encoder " name
Use a specific MediaCodec encoder (must be a H.264 encoder). Use a specific MediaCodec encoder (must be a H.264 encoder).
@@ -89,8 +83,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 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".
@@ -195,15 +189,7 @@ It only shows physical touches (not clicks from scrcpy).
.BI "\-\-v4l2-sink " /dev/videoN .BI "\-\-v4l2-sink " /dev/videoN
Output to v4l2loopback device. Output to v4l2loopback device.
It requires to lock the video orientation (see \fB\-\-lock\-video\-orientation\fR). It requires to lock the video orientation (see --lock-video-orientation).
.TP
.BI "\-\-v4l2-buffer " ms
Add a buffering delay (in milliseconds) before pushing frames. This increases latency to compensate for jitter.
This option is similar to \fB\-\-display\-buffer\fR, but specific to V4L2 sink.
Default is 0 (no buffering).
.TP .TP
.BI "\-V, \-\-verbosity " value .BI "\-V, \-\-verbosity " value
@@ -254,7 +240,7 @@ Default is 0 (automatic).
.SH SHORTCUTS .SH SHORTCUTS
In the following list, MOD is the shortcut modifier. By default, it's (left) In the following list, MOD is the shortcut modifier. By default, it's (left)
Alt or (left) Super, but it can be configured by \fB\-\-shortcut\-mod\fR (see above). Alt or (left) Super, but it can be configured by \-\-shortcut-mod (see above).
.TP .TP
.B MOD+f .B MOD+f

View File

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

View File

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

View File

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

View File

@@ -63,14 +63,14 @@ controller_push_msg(struct controller *controller,
} }
static bool static bool
process_msg(struct controller *controller, const struct control_msg *msg) { process_msg(struct controller *controller,
const struct control_msg *msg) {
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
size_t length = control_msg_serialize(msg, serialized_msg); size_t length = control_msg_serialize(msg, serialized_msg);
if (!length) { if (!length) {
return false; return false;
} }
ssize_t w = int w = net_send_all(controller->control_socket, serialized_msg, length);
net_send_all(controller->control_socket, serialized_msg, length);
return (size_t) w == length; return (size_t) w == length;
} }

View File

@@ -1,6 +1,5 @@
#include "decoder.h" #include "decoder.h"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "events.h" #include "events.h"

View File

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

View File

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

View File

@@ -1,88 +0,0 @@
#include "frame_buffer.h"
#include <assert.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "util/log.h"
bool
sc_frame_buffer_init(struct sc_frame_buffer *fb) {
fb->pending_frame = av_frame_alloc();
if (!fb->pending_frame) {
return false;
}
fb->tmp_frame = av_frame_alloc();
if (!fb->tmp_frame) {
av_frame_free(&fb->pending_frame);
return false;
}
bool ok = sc_mutex_init(&fb->mutex);
if (!ok) {
av_frame_free(&fb->pending_frame);
av_frame_free(&fb->tmp_frame);
return false;
}
// there is initially no frame, so consider it has already been consumed
fb->pending_frame_consumed = true;
return true;
}
void
sc_frame_buffer_destroy(struct sc_frame_buffer *fb) {
sc_mutex_destroy(&fb->mutex);
av_frame_free(&fb->pending_frame);
av_frame_free(&fb->tmp_frame);
}
static inline void
swap_frames(AVFrame **lhs, AVFrame **rhs) {
AVFrame *tmp = *lhs;
*lhs = *rhs;
*rhs = tmp;
}
bool
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
bool *previous_frame_skipped) {
sc_mutex_lock(&fb->mutex);
// Use a temporary frame to preserve pending_frame in case of error.
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
int r = av_frame_ref(fb->tmp_frame, frame);
if (r) {
LOGE("Could not ref frame: %d", r);
return false;
}
// Now that av_frame_ref() succeeded, we can replace the previous
// pending_frame
swap_frames(&fb->pending_frame, &fb->tmp_frame);
av_frame_unref(fb->tmp_frame);
if (previous_frame_skipped) {
*previous_frame_skipped = !fb->pending_frame_consumed;
}
fb->pending_frame_consumed = false;
sc_mutex_unlock(&fb->mutex);
return true;
}
void
sc_frame_buffer_consume(struct sc_frame_buffer *fb, AVFrame *dst) {
sc_mutex_lock(&fb->mutex);
assert(!fb->pending_frame_consumed);
fb->pending_frame_consumed = true;
av_frame_move_ref(dst, fb->pending_frame);
// av_frame_move_ref() resets its source frame, so no need to call
// av_frame_unref()
sc_mutex_unlock(&fb->mutex);
}

View File

@@ -1,44 +0,0 @@
#ifndef SC_FRAME_BUFFER_H
#define SC_FRAME_BUFFER_H
#include "common.h"
#include <stdbool.h>
#include "util/thread.h"
// forward declarations
typedef struct AVFrame AVFrame;
/**
* A frame buffer holds 1 pending frame, which is the last frame received from
* the producer (typically, the decoder).
*
* If a pending frame has not been consumed when the producer pushes a new
* frame, then it is lost. The intent is to always provide access to the very
* last frame to minimize latency.
*/
struct sc_frame_buffer {
AVFrame *pending_frame;
AVFrame *tmp_frame; // To preserve the pending frame on error
sc_mutex mutex;
bool pending_frame_consumed;
};
bool
sc_frame_buffer_init(struct sc_frame_buffer *fb);
void
sc_frame_buffer_destroy(struct sc_frame_buffer *fb);
bool
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
bool *skipped);
void
sc_frame_buffer_consume(struct sc_frame_buffer *fb, AVFrame *dst);
#endif

View File

@@ -1,284 +0,0 @@
#include "icon.h"
#include <assert.h>
#include <stdbool.h>
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h>
#include "config.h"
#include "compat.h"
#include "util/log.h"
#include "util/process.h"
#include "util/str_util.h"
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
#define SCRCPY_DEFAULT_ICON_PATH \
PREFIX "/share/icons/hicolor/256x256/apps/scrcpy.png"
static char *
get_envvar_icon_path(void) {
#ifdef __WINDOWS__
const wchar_t *icon_path_env = _wgetenv(L"SCRCPY_ICON_PATH");
#else
const char *icon_path_env = getenv("SCRCPY_ICON_PATH");
#endif
if (!icon_path_env) {
return NULL;
}
#ifdef __WINDOWS__
char *icon_path = utf8_from_wide_char(icon_path_env);
#else
char *icon_path = strdup(icon_path_env);
#endif
if (!icon_path) {
LOGE("Could not allocate memory");
return NULL;
}
return icon_path;
}
static AVFrame *
decode_image(const char *path) {
AVFrame *result = NULL;
AVFormatContext *ctx = avformat_alloc_context();
if (!ctx) {
LOGE("Could not allocate image decoder context");
return NULL;
}
if (avformat_open_input(&ctx, path, NULL, NULL) < 0) {
LOGE("Could not open image codec: %s", path);
goto free_ctx;
}
if (avformat_find_stream_info(ctx, NULL) < 0) {
LOGE("Could not find image stream info");
goto close_input;
}
int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (stream < 0 ) {
LOGE("Could not find best image stream");
goto close_input;
}
AVCodecParameters *params = ctx->streams[stream]->codecpar;
AVCodec *codec = avcodec_find_decoder(params->codec_id);
if (!codec) {
LOGE("Could not find image decoder");
goto close_input;
}
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
LOGE("Could not allocate codec context");
goto close_input;
}
if (avcodec_parameters_to_context(codec_ctx, params) < 0) {
LOGE("Could not fill codec context");
goto free_codec_ctx;
}
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
LOGE("Could not open image codec");
goto free_codec_ctx;
}
AVFrame *frame = av_frame_alloc();
if (!frame) {
LOGE("Could not allocate frame");
goto close_codec;
}
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOGE("Could not allocate packet");
av_frame_free(&frame);
goto close_codec;
}
if (av_read_frame(ctx, packet) < 0) {
LOGE("Could not read frame");
av_packet_free(&packet);
av_frame_free(&frame);
goto close_codec;
}
int ret;
if ((ret = avcodec_send_packet(codec_ctx, packet)) < 0) {
LOGE("Could not send icon packet: %d", ret);
av_packet_free(&packet);
av_frame_free(&frame);
goto close_codec;
}
if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) {
LOGE("Could not receive icon frame: %d", ret);
av_packet_free(&packet);
av_frame_free(&frame);
goto close_codec;
}
av_packet_free(&packet);
result = frame;
close_codec:
avcodec_close(codec_ctx);
free_codec_ctx:
avcodec_free_context(&codec_ctx);
close_input:
avformat_close_input(&ctx);
free_ctx:
avformat_free_context(ctx);
return result;
}
static SDL_PixelFormatEnum
to_sdl_pixel_format(enum AVPixelFormat fmt) {
switch (fmt) {
case AV_PIX_FMT_RGB24: return SDL_PIXELFORMAT_RGB24;
case AV_PIX_FMT_BGR24: return SDL_PIXELFORMAT_BGR24;
case AV_PIX_FMT_ARGB: return SDL_PIXELFORMAT_ARGB32;
case AV_PIX_FMT_RGBA: return SDL_PIXELFORMAT_RGBA32;
case AV_PIX_FMT_ABGR: return SDL_PIXELFORMAT_ABGR32;
case AV_PIX_FMT_BGRA: return SDL_PIXELFORMAT_BGRA32;
case AV_PIX_FMT_RGB565BE: return SDL_PIXELFORMAT_RGB565;
case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_RGB555;
case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565;
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555;
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444;
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444;
case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8;
default: return SDL_PIXELFORMAT_UNKNOWN;
}
}
static SDL_Surface *
load_from_path(const char *path) {
AVFrame *frame = decode_image(path);
if (!frame) {
return NULL;
}
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
if (!desc) {
LOGE("Could not get icon format descriptor");
goto error;
}
bool is_packed = !(desc->flags & AV_PIX_FMT_FLAG_PLANAR);
if (!is_packed) {
LOGE("Could not load non-packed icon");
goto error;
}
SDL_PixelFormatEnum format = to_sdl_pixel_format(frame->format);
if (format == SDL_PIXELFORMAT_UNKNOWN) {
LOGE("Unsupported icon pixel format: %s (%d)", desc->name,
frame->format);
goto error;
}
int bits_per_pixel = av_get_bits_per_pixel(desc);
SDL_Surface *surface =
SDL_CreateRGBSurfaceWithFormatFrom(frame->data[0],
frame->width, frame->height,
bits_per_pixel,
frame->linesize[0],
format);
if (!surface) {
LOGE("Could not create icon surface");
goto error;
}
if (frame->format == AV_PIX_FMT_PAL8) {
// Initialize the SDL palette
uint8_t *data = frame->data[1];
SDL_Color colors[256];
for (int i = 0; i < 256; ++i) {
SDL_Color *color = &colors[i];
// The palette is transported in AVFrame.data[1], is 1024 bytes
// long (256 4-byte entries) and is formatted the same as in
// AV_PIX_FMT_RGB32 described above (i.e., it is also
// endian-specific).
// <https://ffmpeg.org/doxygen/4.1/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5>
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
color->a = data[i * 4];
color->r = data[i * 4 + 1];
color->g = data[i * 4 + 2];
color->b = data[i * 4 + 3];
#else
color->a = data[i * 4 + 3];
color->r = data[i * 4 + 2];
color->g = data[i * 4 + 1];
color->b = data[i * 4];
#endif
}
SDL_Palette *palette = surface->format->palette;
assert(palette);
int ret = SDL_SetPaletteColors(palette, colors, 0, 256);
if (ret) {
LOGE("Could not set palette colors");
SDL_FreeSurface(surface);
goto error;
}
}
surface->userdata = frame; // frame owns the data
return surface;
error:
av_frame_free(&frame);
return NULL;
}
SDL_Surface *
scrcpy_icon_load() {
SDL_Surface *icon = NULL;
char *icon_path = get_envvar_icon_path();
if (icon_path) {
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
// An icon path is forced by environment variable
icon = load_from_path(icon_path);
free(icon_path);
// Do not fallback on default path even if icon is NULL
return icon;
}
#ifndef PORTABLE
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
icon = load_from_path(SCRCPY_DEFAULT_ICON_PATH);
#else
icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME);
if (!icon_path) {
LOGE("Could not get icon path");
}
LOGD("Using icon (portable): %s", icon_path);
icon = load_from_path(icon_path);
free(icon_path);
#endif
return icon;
}
void
scrcpy_icon_destroy(SDL_Surface *icon) {
AVFrame *frame = icon->userdata;
assert(frame);
av_frame_free(&frame);
SDL_FreeSurface(icon);
}

View File

@@ -1,16 +0,0 @@
#ifndef ICON_H
#define ICON_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL.h>
#include <libavformat/avformat.h>
SDL_Surface *
scrcpy_icon_load(void);
void
scrcpy_icon_destroy(SDL_Surface *icon);
#endif

53
app/src/icon.xpm Normal file
View File

@@ -0,0 +1,53 @@
/* XPM */
static char * icon_xpm[] = {
"48 48 2 1",
" c None",
". c #96C13E",
" .. .. ",
" ... ... ",
" ... ...... ... ",
" ................ ",
" .............. ",
" ................ ",
" .................. ",
" .................... ",
" ..... ........ ..... ",
" ..... ........ ..... ",
" ...................... ",
" ........................ ",
" ........................ ",
" ........................ ",
" ",
" ",
" .... ........................ .... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" .... ........................ .... ",
" ........................ ",
" ...................... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" .... .... "};

View File

@@ -1,8 +1,6 @@
#include "recorder.h" #include "recorder.h"
#include <assert.h> #include <assert.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h> #include <libavutil/time.h>
#include "util/log.h" #include "util/log.h"
@@ -53,15 +51,16 @@ record_packet_new(const AVPacket *packet) {
static void static void
record_packet_delete(struct record_packet *rec) { record_packet_delete(struct record_packet *rec) {
av_packet_unref(rec->packet);
av_packet_free(&rec->packet); av_packet_free(&rec->packet);
free(rec); free(rec);
} }
static void static void
recorder_queue_clear(struct recorder_queue *queue) { recorder_queue_clear(struct recorder_queue *queue) {
while (!sc_queue_is_empty(queue)) { while (!queue_is_empty(queue)) {
struct record_packet *rec; struct record_packet *rec;
sc_queue_take(queue, next, &rec); queue_take(queue, next, &rec);
record_packet_delete(rec); record_packet_delete(rec);
} }
} }
@@ -137,14 +136,14 @@ run_recorder(void *data) {
for (;;) { for (;;) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped && sc_queue_is_empty(&recorder->queue)) { while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
sc_cond_wait(&recorder->queue_cond, &recorder->mutex); sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
} }
// if stopped is set, continue to process the remaining events (to // if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping // finish the recording) before actually stopping
if (recorder->stopped && sc_queue_is_empty(&recorder->queue)) { if (recorder->stopped && queue_is_empty(&recorder->queue)) {
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
struct record_packet *last = recorder->previous; struct record_packet *last = recorder->previous;
if (last) { if (last) {
@@ -163,7 +162,7 @@ run_recorder(void *data) {
} }
struct record_packet *rec; struct record_packet *rec;
sc_queue_take(&recorder->queue, next, &rec); queue_take(&recorder->queue, next, &rec);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
@@ -215,8 +214,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");
@@ -238,7 +236,7 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
goto error_mutex_destroy; goto error_mutex_destroy;
} }
sc_queue_init(&recorder->queue); queue_init(&recorder->queue);
recorder->stopped = false; recorder->stopped = false;
recorder->failed = false; recorder->failed = false;
recorder->header_written = false; recorder->header_written = false;
@@ -343,7 +341,7 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) {
return false; return false;
} }
sc_queue_push(&recorder->queue, next, rec); queue_push(&recorder->queue, next, rec);
sc_cond_signal(&recorder->queue_cond); sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);

View File

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

View File

@@ -22,6 +22,7 @@
#include "screen.h" #include "screen.h"
#include "server.h" #include "server.h"
#include "stream.h" #include "stream.h"
#include "tiny_xpm.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
@@ -329,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);
@@ -342,6 +343,7 @@ scrcpy(const struct scrcpy_options *options) {
stream_add_sink(&s->stream, &rec->packet_sink); stream_add_sink(&s->stream, &rec->packet_sink);
} }
if (options->display) {
if (options->control) { if (options->control) {
if (!controller_init(&s->controller, s->server.control_socket)) { if (!controller_init(&s->controller, s->server.control_socket)) {
goto end; goto end;
@@ -352,19 +354,8 @@ scrcpy(const struct scrcpy_options *options) {
goto end; goto end;
} }
controller_started = true; controller_started = true;
if (options->turn_screen_off) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
if (!controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
} }
if (options->display) {
const char *window_title = const char *window_title =
options->window_title ? options->window_title : device_name; options->window_title ? options->window_title : device_name;
@@ -380,7 +371,6 @@ scrcpy(const struct scrcpy_options *options) {
.rotation = options->rotation, .rotation = options->rotation,
.mipmaps = options->mipmaps, .mipmaps = options->mipmaps,
.fullscreen = options->fullscreen, .fullscreen = options->fullscreen,
.buffering_time = options->display_buffer,
}; };
if (!screen_init(&s->screen, &screen_params)) { if (!screen_init(&s->screen, &screen_params)) {
@@ -389,12 +379,21 @@ scrcpy(const struct scrcpy_options *options) {
screen_initialized = true; screen_initialized = true;
decoder_add_sink(&s->decoder, &s->screen.frame_sink); decoder_add_sink(&s->decoder, &s->screen.frame_sink);
if (options->turn_screen_off) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
if (!controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
} }
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
if (options->v4l2_device) { if (options->v4l2_device) {
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size, if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size)) {
options->v4l2_buffer)) {
goto end; goto end;
} }

View File

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

View File

@@ -5,8 +5,9 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "events.h" #include "events.h"
#include "icon.h" #include "icon.xpm"
#include "scrcpy.h" #include "scrcpy.h"
#include "tiny_xpm.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/log.h" #include "util/log.h"
@@ -273,16 +274,14 @@ screen_frame_sink_close(struct sc_frame_sink *sink) {
static bool static bool
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct screen *screen = DOWNCAST(sink); struct screen *screen = DOWNCAST(sink);
return sc_video_buffer_push(&screen->vb, frame);
bool previous_frame_skipped;
bool ok = video_buffer_push(&screen->vb, frame, &previous_frame_skipped);
if (!ok) {
return false;
} }
static void if (previous_frame_skipped) {
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata) {
(void) vb;
struct screen *screen = userdata;
if (previous_skipped) {
fps_counter_add_skipped_frame(&screen->fps_counter); fps_counter_add_skipped_frame(&screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume // The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead // this new frame instead
@@ -294,6 +293,8 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
// Post the event on the UI thread // Post the event on the UI thread
SDL_PushEvent(&new_frame_event); SDL_PushEvent(&new_frame_event);
} }
return true;
} }
bool bool
@@ -303,26 +304,15 @@ screen_init(struct screen *screen, const struct screen_params *params) {
screen->fullscreen = false; screen->fullscreen = false;
screen->maximized = false; screen->maximized = false;
static const struct sc_video_buffer_callbacks cbs = { bool ok = video_buffer_init(&screen->vb);
.on_new_frame = sc_video_buffer_on_new_frame,
};
bool ok = sc_video_buffer_init(&screen->vb, params->buffering_time, &cbs,
screen);
if (!ok) { if (!ok) {
LOGE("Could not initialize video buffer"); LOGE("Could not initialize video buffer");
return false; return false;
} }
ok = sc_video_buffer_start(&screen->vb);
if (!ok) {
LOGE("Could not start video_buffer");
goto error_destroy_video_buffer;
}
if (!fps_counter_init(&screen->fps_counter)) { if (!fps_counter_init(&screen->fps_counter)) {
LOGE("Could not initialize FPS counter"); LOGE("Could not initialize FPS counter");
goto error_stop_and_join_video_buffer; goto error_destroy_video_buffer;
} }
screen->frame_size = params->frame_size; screen->frame_size = params->frame_size;
@@ -404,10 +394,10 @@ screen_init(struct screen *screen, const struct screen_params *params) {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)"); LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
} }
SDL_Surface *icon = scrcpy_icon_load(); SDL_Surface *icon = read_xpm(icon_xpm);
if (icon) { if (icon) {
SDL_SetWindowIcon(screen->window, icon); SDL_SetWindowIcon(screen->window, icon);
scrcpy_icon_destroy(icon); SDL_FreeSurface(icon);
} else { } else {
LOGW("Could not load icon"); LOGW("Could not load icon");
} }
@@ -463,11 +453,8 @@ error_destroy_window:
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
error_destroy_fps_counter: error_destroy_fps_counter:
fps_counter_destroy(&screen->fps_counter); fps_counter_destroy(&screen->fps_counter);
error_stop_and_join_video_buffer:
sc_video_buffer_stop(&screen->vb);
sc_video_buffer_join(&screen->vb);
error_destroy_video_buffer: error_destroy_video_buffer:
sc_video_buffer_destroy(&screen->vb); video_buffer_destroy(&screen->vb);
return false; return false;
} }
@@ -484,13 +471,11 @@ screen_hide_window(struct screen *screen) {
void void
screen_interrupt(struct screen *screen) { screen_interrupt(struct screen *screen) {
sc_video_buffer_stop(&screen->vb);
fps_counter_interrupt(&screen->fps_counter); fps_counter_interrupt(&screen->fps_counter);
} }
void void
screen_join(struct screen *screen) { screen_join(struct screen *screen) {
sc_video_buffer_join(&screen->vb);
fps_counter_join(&screen->fps_counter); fps_counter_join(&screen->fps_counter);
} }
@@ -504,7 +489,7 @@ screen_destroy(struct screen *screen) {
SDL_DestroyRenderer(screen->renderer); SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
fps_counter_destroy(&screen->fps_counter); fps_counter_destroy(&screen->fps_counter);
sc_video_buffer_destroy(&screen->vb); video_buffer_destroy(&screen->vb);
} }
static void static void
@@ -610,7 +595,7 @@ update_texture(struct screen *screen, const AVFrame *frame) {
static bool static bool
screen_update_frame(struct screen *screen) { screen_update_frame(struct screen *screen) {
av_frame_unref(screen->frame); av_frame_unref(screen->frame);
sc_video_buffer_consume(&screen->vb, screen->frame); video_buffer_consume(&screen->vb, screen->frame);
AVFrame *frame = screen->frame; AVFrame *frame = screen->frame;
fps_counter_add_rendered_frame(&screen->fps_counter); fps_counter_add_rendered_frame(&screen->fps_counter);

View File

@@ -8,7 +8,6 @@
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "coords.h" #include "coords.h"
#include "fps_counter.h"
#include "opengl.h" #include "opengl.h"
#include "trait/frame_sink.h" #include "trait/frame_sink.h"
#include "video_buffer.h" #include "video_buffer.h"
@@ -20,7 +19,7 @@ struct screen {
bool open; // track the open/close state to assert correct behavior bool open; // track the open/close state to assert correct behavior
#endif #endif
struct sc_video_buffer vb; struct video_buffer vb;
struct fps_counter fps_counter; struct fps_counter fps_counter;
SDL_Window *window; SDL_Window *window;
@@ -63,8 +62,6 @@ struct screen_params {
bool mipmaps; bool mipmaps;
bool fullscreen; bool fullscreen;
sc_tick buffering_time;
}; };
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)

View File

@@ -3,6 +3,7 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <libgen.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
@@ -50,12 +51,34 @@ get_server_path(void) {
// the absolute path is hardcoded // the absolute path is hardcoded
return server_path; return server_path;
#else #else
char *server_path = get_local_file_path(SERVER_FILENAME);
if (!server_path) { // use scrcpy-server in the same directory as the executable
LOGE("Could not get local file path, " char *executable_path = get_executable_path();
if (!executable_path) {
LOGE("Could not get executable path, "
"using " SERVER_FILENAME " from current directory"); "using " SERVER_FILENAME " from current directory");
// not found, use current directory
return strdup(SERVER_FILENAME); return strdup(SERVER_FILENAME);
} }
char *dir = dirname(executable_path);
size_t dirlen = strlen(dir);
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
char *server_path = malloc(len);
if (!server_path) {
LOGE("Could not alloc server path string, "
"using " SERVER_FILENAME " from current directory");
free(executable_path);
return strdup(SERVER_FILENAME);
}
memcpy(server_path, dir, dirlen);
server_path[dirlen] = PATH_SEPARATOR;
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
// the final null byte has been copied with SERVER_FILENAME
free(executable_path);
LOGD("Using server (portable): %s", server_path); LOGD("Using server (portable): %s", server_path);
return server_path; return server_path;
@@ -238,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",
@@ -249,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="
@@ -447,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;
@@ -533,10 +554,10 @@ server_stop(struct server *server) {
sc_mutex_lock(&server->mutex); sc_mutex_lock(&server->mutex);
bool signaled = false; bool signaled = false;
if (!server->process_terminated) { if (!server->process_terminated) {
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1) #define WATCHDOG_DELAY_MS 1000
signaled = sc_cond_timedwait(&server->process_terminated_cond, signaled = sc_cond_timedwait(&server->process_terminated_cond,
&server->mutex, &server->mutex,
sc_tick_now() + WATCHDOG_DELAY); WATCHDOG_DELAY_MS);
} }
sc_mutex_unlock(&server->mutex); sc_mutex_unlock(&server->mutex);

View File

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

119
app/src/tiny_xpm.c Normal file
View File

@@ -0,0 +1,119 @@
#include "tiny_xpm.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "util/log.h"
struct index {
char c;
uint32_t color;
};
static bool
find_color(struct index *index, int len, char c, uint32_t *color) {
// there are typically very few color, so it's ok to iterate over the array
for (int i = 0; i < len; ++i) {
if (index[i].c == c) {
*color = index[i].color;
return true;
}
}
*color = 0;
return false;
}
// We encounter some problems with SDL2_image on MSYS2 (Windows),
// so here is our own XPM parsing not to depend on SDL_image.
//
// We do not hardcode the binary image to keep some flexibility to replace the
// icon easily (just by replacing icon.xpm).
//
// Parameter is not "const char *" because XPM formats are generally stored in a
// (non-const) "char *"
SDL_Surface *
read_xpm(char *xpm[]) {
#ifndef NDEBUG
// patch the XPM to change the icon color in debug mode
xpm[2] = ". c #CC00CC";
#endif
char *endptr;
// *** No error handling, assume the XPM source is valid ***
// (it's in our source repo)
// Assertions are only checked in debug
int width = strtol(xpm[0], &endptr, 10);
int height = strtol(endptr + 1, &endptr, 10);
int colors = strtol(endptr + 1, &endptr, 10);
int chars = strtol(endptr + 1, &endptr, 10);
// sanity checks
assert(0 <= width && width < 256);
assert(0 <= height && height < 256);
assert(0 <= colors && colors < 256);
assert(chars == 1); // this implementation does not support more
(void) chars;
// init index
struct index index[colors];
for (int i = 0; i < colors; ++i) {
const char *line = xpm[1+i];
index[i].c = line[0];
assert(line[1] == '\t');
assert(line[2] == 'c');
assert(line[3] == ' ');
if (line[4] == '#') {
index[i].color = 0xff000000 | strtol(&line[5], &endptr, 0x10);
assert(*endptr == '\0');
} else {
assert(!strcmp("None", &line[4]));
index[i].color = 0;
}
}
// parse image
uint32_t *pixels = SDL_malloc(4 * width * height);
if (!pixels) {
LOGE("Could not allocate icon memory");
return NULL;
}
for (int y = 0; y < height; ++y) {
const char *line = xpm[1 + colors + y];
for (int x = 0; x < width; ++x) {
char c = line[x];
uint32_t color;
bool color_found = find_color(index, colors, c, &color);
assert(color_found);
(void) color_found;
pixels[y * width + x] = color;
}
}
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
uint32_t amask = 0x000000ff;
uint32_t rmask = 0x0000ff00;
uint32_t gmask = 0x00ff0000;
uint32_t bmask = 0xff000000;
#else // little endian, like x86
uint32_t amask = 0xff000000;
uint32_t rmask = 0x00ff0000;
uint32_t gmask = 0x0000ff00;
uint32_t bmask = 0x000000ff;
#endif
SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels,
width, height,
32, 4 * width,
rmask, gmask, bmask, amask);
if (!surface) {
LOGE("Could not create icon surface");
return NULL;
}
// make the surface own the raw pixels
surface->flags &= ~SDL_PREALLOC;
return surface;
}

11
app/src/tiny_xpm.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef TINYXPM_H
#define TINYXPM_H
#include "common.h"
#include <SDL2/SDL.h>
SDL_Surface *
read_xpm(char *xpm[]);
#endif

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,17 +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;
while (len > 0) { while (len > 0) {
ssize_t 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

View File

@@ -1,6 +1,5 @@
#include "process.h" #include "process.h"
#include <libgen.h>
#include "log.h" #include "log.h"
bool bool
@@ -20,44 +19,3 @@ process_check_success(process_t proc, const char *name, bool close) {
} }
return true; return true;
} }
char *
get_local_file_path(const char *name) {
char *executable_path = get_executable_path();
if (!executable_path) {
return NULL;
}
// 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 NULL;
}
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
size_t dirlen = strlen(dir);
size_t namelen = strlen(name);
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
char *file_path = malloc(len);
if (!file_path) {
LOGE("Could not alloc path");
free(executable_path);
return NULL;
}
memcpy(file_path, dir, dirlen);
file_path[dirlen] = PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&file_path[dirlen + 1], name, namelen + 1);
free(executable_path);
return file_path;
}

View File

@@ -74,11 +74,6 @@ search_executable(const char *file);
char * char *
get_executable_path(void); get_executable_path(void);
// Return the absolute path of a file in the same directory as he executable.
// May be NULL on error. To be freed by free().
char *
get_local_file_path(const char *name);
// returns true if the file exists and is not a directory // returns true if the file exists and is not a directory
bool bool
is_regular_file(const char *path); is_regular_file(const char *path);

View File

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

View File

@@ -31,7 +31,7 @@ sc_mutex_init(sc_mutex *mutex) {
mutex->mutex = sdl_mutex; mutex->mutex = sdl_mutex;
#ifndef NDEBUG #ifndef NDEBUG
atomic_init(&mutex->locker, 0); mutex->locker = 0;
#endif #endif
return true; return true;
} }
@@ -52,8 +52,7 @@ sc_mutex_lock(sc_mutex *mutex) {
abort(); abort();
} }
atomic_store_explicit(&mutex->locker, sc_thread_get_id(), mutex->locker = sc_thread_get_id();
memory_order_relaxed);
#else #else
(void) r; (void) r;
#endif #endif
@@ -63,7 +62,7 @@ void
sc_mutex_unlock(sc_mutex *mutex) { sc_mutex_unlock(sc_mutex *mutex) {
#ifndef NDEBUG #ifndef NDEBUG
assert(sc_mutex_held(mutex)); assert(sc_mutex_held(mutex));
atomic_store_explicit(&mutex->locker, 0, memory_order_relaxed); mutex->locker = 0;
#endif #endif
int r = SDL_UnlockMutex(mutex->mutex); int r = SDL_UnlockMutex(mutex->mutex);
#ifndef NDEBUG #ifndef NDEBUG
@@ -84,9 +83,7 @@ sc_thread_get_id(void) {
#ifndef NDEBUG #ifndef NDEBUG
bool bool
sc_mutex_held(struct sc_mutex *mutex) { sc_mutex_held(struct sc_mutex *mutex) {
sc_thread_id locker_id = return mutex->locker == sc_thread_get_id();
atomic_load_explicit(&mutex->locker, memory_order_relaxed);
return locker_id == sc_thread_get_id();
} }
#endif #endif
@@ -115,21 +112,14 @@ sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
abort(); abort();
} }
atomic_store_explicit(&mutex->locker, sc_thread_get_id(), mutex->locker = sc_thread_get_id();
memory_order_relaxed);
#else #else
(void) r; (void) r;
#endif #endif
} }
bool bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) { sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms) {
sc_tick now = sc_tick_now();
if (deadline <= now) {
return false; // timeout
}
uint32_t ms = SC_TICK_TO_MS(deadline - now);
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms); int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
#ifndef NDEBUG #ifndef NDEBUG
if (r < 0) { if (r < 0) {
@@ -137,8 +127,7 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) {
abort(); abort();
} }
atomic_store_explicit(&mutex->locker, sc_thread_get_id(), mutex->locker = sc_thread_get_id();
memory_order_relaxed);
#endif #endif
assert(r == 0 || r == SDL_MUTEX_TIMEDOUT); assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
return r == 0; return r == 0;

View File

@@ -3,10 +3,8 @@
#include "common.h" #include "common.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;
@@ -14,8 +12,7 @@ typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond; typedef struct SDL_cond SDL_cond;
typedef int sc_thread_fn(void *); typedef int sc_thread_fn(void *);
typedef unsigned sc_thread_id; typedef unsigned int sc_thread_id;
typedef atomic_uint sc_atomic_thread_id;
typedef struct sc_thread { typedef struct sc_thread {
SDL_Thread *thread; SDL_Thread *thread;
@@ -24,7 +21,7 @@ typedef struct sc_thread {
typedef struct sc_mutex { typedef struct sc_mutex {
SDL_mutex *mutex; SDL_mutex *mutex;
#ifndef NDEBUG #ifndef NDEBUG
sc_atomic_thread_id locker; sc_thread_id locker;
#endif #endif
} sc_mutex; } sc_mutex;
@@ -73,7 +70,7 @@ sc_cond_wait(sc_cond *cond, sc_mutex *mutex);
// return true on signaled, false on timeout // return true on signaled, false on timeout
bool bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline); sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms);
void void
sc_cond_signal(sc_cond *cond); sc_cond_signal(sc_cond *cond);

View File

@@ -1,16 +0,0 @@
#include "tick.h"
#include <SDL2/SDL_timer.h>
sc_tick
sc_tick_now(void) {
// SDL_GetTicks() resolution is in milliseconds, but sc_tick are expressed
// in microseconds to 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;
}

View File

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

View File

@@ -112,7 +112,7 @@ run_v4l2_sink(void *data) {
for (;;) { for (;;) {
sc_mutex_lock(&vs->mutex); sc_mutex_lock(&vs->mutex);
while (!vs->stopped && !vs->has_frame) { while (!vs->stopped && vs->vb.pending_frame_consumed) {
sc_cond_wait(&vs->cond, &vs->mutex); sc_cond_wait(&vs->cond, &vs->mutex);
} }
@@ -121,11 +121,9 @@ run_v4l2_sink(void *data) {
break; break;
} }
vs->has_frame = false;
sc_mutex_unlock(&vs->mutex); sc_mutex_unlock(&vs->mutex);
sc_video_buffer_consume(&vs->vb, vs->frame); video_buffer_consume(&vs->vb, vs->frame);
bool ok = encode_and_write_frame(vs, vs->frame); bool ok = encode_and_write_frame(vs, vs->frame);
av_frame_unref(vs->frame); av_frame_unref(vs->frame);
if (!ok) { if (!ok) {
@@ -139,42 +137,17 @@ run_v4l2_sink(void *data) {
return 0; return 0;
} }
static void
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata) {
(void) vb;
struct sc_v4l2_sink *vs = userdata;
if (!previous_skipped) {
sc_mutex_lock(&vs->mutex);
vs->has_frame = true;
sc_cond_signal(&vs->cond);
sc_mutex_unlock(&vs->mutex);
}
}
static bool static bool
sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
static const struct sc_video_buffer_callbacks cbs = { bool ok = video_buffer_init(&vs->vb);
.on_new_frame = sc_video_buffer_on_new_frame,
};
bool ok = sc_video_buffer_init(&vs->vb, vs->buffering_time, &cbs, vs);
if (!ok) { if (!ok) {
LOGE("Could not initialize video buffer");
return false; return false;
} }
ok = sc_video_buffer_start(&vs->vb);
if (!ok) {
LOGE("Could not start video buffer");
goto error_video_buffer_destroy;
}
ok = sc_mutex_init(&vs->mutex); ok = sc_mutex_init(&vs->mutex);
if (!ok) { if (!ok) {
LOGC("Could not create mutex"); LOGC("Could not create mutex");
goto error_video_buffer_stop_and_join; goto error_video_buffer_destroy;
} }
ok = sc_cond_init(&vs->cond); ok = sc_cond_init(&vs->cond);
@@ -183,11 +156,8 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
goto error_mutex_destroy; goto error_mutex_destroy;
} }
const AVOutputFormat *format = find_muxer("v4l2"); // FIXME
if (!format) { const AVOutputFormat *format = find_muxer("video4linux2,v4l2");
// Alternative name
format = find_muxer("video4linux2");
}
if (!format) { if (!format) {
LOGE("Could not find v4l2 muxer"); LOGE("Could not find v4l2 muxer");
goto error_cond_destroy; goto error_cond_destroy;
@@ -271,10 +241,6 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
goto error_av_frame_free; goto error_av_frame_free;
} }
vs->has_frame = false;
vs->header_written = false;
vs->stopped = false;
LOGD("Starting v4l2 thread"); LOGD("Starting v4l2 thread");
ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs); ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs);
if (!ok) { if (!ok) {
@@ -282,6 +248,9 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
goto error_av_packet_free; goto error_av_packet_free;
} }
vs->header_written = false;
vs->stopped = false;
LOGI("v4l2 sink started to device: %s", vs->device_name); LOGI("v4l2 sink started to device: %s", vs->device_name);
return true; return true;
@@ -302,11 +271,8 @@ error_cond_destroy:
sc_cond_destroy(&vs->cond); sc_cond_destroy(&vs->cond);
error_mutex_destroy: error_mutex_destroy:
sc_mutex_destroy(&vs->mutex); sc_mutex_destroy(&vs->mutex);
error_video_buffer_stop_and_join:
sc_video_buffer_stop(&vs->vb);
sc_video_buffer_join(&vs->vb);
error_video_buffer_destroy: error_video_buffer_destroy:
sc_video_buffer_destroy(&vs->vb); video_buffer_destroy(&vs->vb);
return false; return false;
} }
@@ -318,10 +284,7 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
sc_cond_signal(&vs->cond); sc_cond_signal(&vs->cond);
sc_mutex_unlock(&vs->mutex); sc_mutex_unlock(&vs->mutex);
sc_video_buffer_stop(&vs->vb);
sc_thread_join(&vs->thread, NULL); sc_thread_join(&vs->thread, NULL);
sc_video_buffer_join(&vs->vb);
av_packet_free(&vs->packet); av_packet_free(&vs->packet);
av_frame_free(&vs->frame); av_frame_free(&vs->frame);
@@ -331,12 +294,20 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
avformat_free_context(vs->format_ctx); avformat_free_context(vs->format_ctx);
sc_cond_destroy(&vs->cond); sc_cond_destroy(&vs->cond);
sc_mutex_destroy(&vs->mutex); sc_mutex_destroy(&vs->mutex);
sc_video_buffer_destroy(&vs->vb); video_buffer_destroy(&vs->vb);
} }
static bool static bool
sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) { sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) {
return sc_video_buffer_push(&vs->vb, frame); bool ok = video_buffer_push(&vs->vb, frame, NULL);
if (!ok) {
return false;
}
// signal possible change of vs->vb.pending_frame_consumed
sc_cond_signal(&vs->cond);
return true;
} }
static bool static bool
@@ -359,7 +330,7 @@ sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
bool bool
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
struct size frame_size, sc_tick buffering_time) { struct size frame_size) {
vs->device_name = strdup(device_name); vs->device_name = strdup(device_name);
if (!vs->device_name) { if (!vs->device_name) {
LOGE("Could not strdup v4l2 device name"); LOGE("Could not strdup v4l2 device name");
@@ -367,7 +338,6 @@ sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
} }
vs->frame_size = frame_size; vs->frame_size = frame_size;
vs->buffering_time = buffering_time;
static const struct sc_frame_sink_ops ops = { static const struct sc_frame_sink_ops ops = {
.open = sc_v4l2_frame_sink_open, .open = sc_v4l2_frame_sink_open,

View File

@@ -6,25 +6,22 @@
#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>
struct sc_v4l2_sink { struct sc_v4l2_sink {
struct sc_frame_sink frame_sink; // frame sink trait struct sc_frame_sink frame_sink; // frame sink trait
struct sc_video_buffer vb; struct video_buffer vb;
AVFormatContext *format_ctx; AVFormatContext *format_ctx;
AVCodecContext *encoder_ctx; AVCodecContext *encoder_ctx;
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;
sc_cond cond; sc_cond cond;
bool has_frame;
bool stopped; bool stopped;
bool header_written; bool header_written;
@@ -34,7 +31,7 @@ struct sc_v4l2_sink {
bool bool
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
struct size frame_size, sc_tick buffering_time); struct size frame_size);
void void
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs); sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);

View File

@@ -1,255 +1,88 @@
#include "video_buffer.h" #include "video_buffer.h"
#include <assert.h> #include <assert.h>
#include <stdlib.h>
#include <libavutil/avutil.h> #include <libavutil/avutil.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "util/log.h" #include "util/log.h"
#define SC_BUFFERING_NDEBUG // comment to debug bool
video_buffer_init(struct video_buffer *vb) {
static struct sc_video_buffer_frame * vb->pending_frame = av_frame_alloc();
sc_video_buffer_frame_new(const AVFrame *frame) { if (!vb->pending_frame) {
struct sc_video_buffer_frame *vb_frame = malloc(sizeof(*vb_frame));
if (!vb_frame) {
return NULL;
}
vb_frame->frame = av_frame_alloc();
if (!vb_frame->frame) {
free(vb_frame);
return NULL;
}
if (av_frame_ref(vb_frame->frame, frame)) {
av_frame_free(&vb_frame->frame);
free(vb_frame);
return NULL;
}
return vb_frame;
}
static void
sc_video_buffer_frame_delete(struct sc_video_buffer_frame *vb_frame) {
av_frame_unref(vb_frame->frame);
av_frame_free(&vb_frame->frame);
free(vb_frame);
}
static bool
sc_video_buffer_offer(struct sc_video_buffer *vb, const AVFrame *frame) {
bool previous_skipped;
bool ok = sc_frame_buffer_push(&vb->fb, frame, &previous_skipped);
if (!ok) {
return false; return false;
} }
vb->cbs->on_new_frame(vb, previous_skipped, vb->cbs_userdata); vb->tmp_frame = av_frame_alloc();
if (!vb->tmp_frame) {
av_frame_free(&vb->pending_frame);
return false;
}
bool ok = sc_mutex_init(&vb->mutex);
if (!ok) {
av_frame_free(&vb->pending_frame);
av_frame_free(&vb->tmp_frame);
return false;
}
// there is initially no frame, so consider it has already been consumed
vb->pending_frame_consumed = true;
return true; return true;
} }
static int void
run_buffering(void *data) { video_buffer_destroy(struct video_buffer *vb) {
struct sc_video_buffer *vb = data; sc_mutex_destroy(&vb->mutex);
av_frame_free(&vb->pending_frame);
assert(vb->buffering_time > 0); av_frame_free(&vb->tmp_frame);
for (;;) {
sc_mutex_lock(&vb->b.mutex);
while (!vb->b.stopped && sc_queue_is_empty(&vb->b.queue)) {
sc_cond_wait(&vb->b.queue_cond, &vb->b.mutex);
} }
if (vb->b.stopped) { static inline void
sc_mutex_unlock(&vb->b.mutex); swap_frames(AVFrame **lhs, AVFrame **rhs) {
goto stopped; AVFrame *tmp = *lhs;
} *lhs = *rhs;
*rhs = tmp;
struct sc_video_buffer_frame *vb_frame;
sc_queue_take(&vb->b.queue, next, &vb_frame);
sc_tick max_deadline = sc_tick_now() + vb->buffering_time;
// PTS (written by the server) are expressed in microseconds
sc_tick pts = SC_TICK_TO_US(vb_frame->frame->pts);
bool timed_out = false;
while (!vb->b.stopped && !timed_out) {
sc_tick deadline = sc_clock_to_system_time(&vb->b.clock, pts)
+ vb->buffering_time;
if (deadline > max_deadline) {
deadline = max_deadline;
}
timed_out =
!sc_cond_timedwait(&vb->b.wait_cond, &vb->b.mutex, deadline);
}
if (vb->b.stopped) {
sc_video_buffer_frame_delete(vb_frame);
sc_mutex_unlock(&vb->b.mutex);
goto stopped;
}
sc_mutex_unlock(&vb->b.mutex);
#ifndef SC_BUFFERING_NDEBUG
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
pts, vb_frame->push_date, sc_tick_now());
#endif
sc_video_buffer_offer(vb, vb_frame->frame);
sc_video_buffer_frame_delete(vb_frame);
}
stopped:
// Flush queue
while (!sc_queue_is_empty(&vb->b.queue)) {
struct sc_video_buffer_frame *vb_frame;
sc_queue_take(&vb->b.queue, next, &vb_frame);
sc_video_buffer_frame_delete(vb_frame);
}
LOGD("Buffering thread ended");
return 0;
} }
bool bool
sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time, video_buffer_push(struct video_buffer *vb, const AVFrame *frame,
const struct sc_video_buffer_callbacks *cbs, bool *previous_frame_skipped) {
void *cbs_userdata) { sc_mutex_lock(&vb->mutex);
bool ok = sc_frame_buffer_init(&vb->fb);
if (!ok) { // Use a temporary frame to preserve pending_frame in case of error.
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
int r = av_frame_ref(vb->tmp_frame, frame);
if (r) {
LOGE("Could not ref frame: %d", r);
return false; return false;
} }
assert(buffering_time >= 0); // Now that av_frame_ref() succeeded, we can replace the previous
if (buffering_time) { // pending_frame
ok = sc_mutex_init(&vb->b.mutex); swap_frames(&vb->pending_frame, &vb->tmp_frame);
if (!ok) { av_frame_unref(vb->tmp_frame);
LOGC("Could not create mutex");
sc_frame_buffer_destroy(&vb->fb);
return false;
}
ok = sc_cond_init(&vb->b.queue_cond); if (previous_frame_skipped) {
if (!ok) { *previous_frame_skipped = !vb->pending_frame_consumed;
LOGC("Could not create cond");
sc_mutex_destroy(&vb->b.mutex);
sc_frame_buffer_destroy(&vb->fb);
return false;
} }
vb->pending_frame_consumed = false;
ok = sc_cond_init(&vb->b.wait_cond); sc_mutex_unlock(&vb->mutex);
if (!ok) {
LOGC("Could not create wait cond");
sc_cond_destroy(&vb->b.queue_cond);
sc_mutex_destroy(&vb->b.mutex);
sc_frame_buffer_destroy(&vb->fb);
return false;
}
sc_clock_init(&vb->b.clock);
sc_queue_init(&vb->b.queue);
}
assert(cbs);
assert(cbs->on_new_frame);
vb->buffering_time = buffering_time;
vb->cbs = cbs;
vb->cbs_userdata = cbs_userdata;
return true;
}
bool
sc_video_buffer_start(struct sc_video_buffer *vb) {
if (vb->buffering_time) {
bool ok =
sc_thread_create(&vb->b.thread, run_buffering, "buffering", vb);
if (!ok) {
LOGE("Could not start buffering thread");
return false;
}
}
return true; return true;
} }
void void
sc_video_buffer_stop(struct sc_video_buffer *vb) { video_buffer_consume(struct video_buffer *vb, AVFrame *dst) {
if (vb->buffering_time) { sc_mutex_lock(&vb->mutex);
sc_mutex_lock(&vb->b.mutex); assert(!vb->pending_frame_consumed);
vb->b.stopped = true; vb->pending_frame_consumed = true;
sc_cond_signal(&vb->b.queue_cond);
sc_cond_signal(&vb->b.wait_cond);
sc_mutex_unlock(&vb->b.mutex);
}
}
void av_frame_move_ref(dst, vb->pending_frame);
sc_video_buffer_join(struct sc_video_buffer *vb) { // av_frame_move_ref() resets its source frame, so no need to call
if (vb->buffering_time) { // av_frame_unref()
sc_thread_join(&vb->b.thread, NULL);
} sc_mutex_unlock(&vb->mutex);
}
void
sc_video_buffer_destroy(struct sc_video_buffer *vb) {
sc_frame_buffer_destroy(&vb->fb);
if (vb->buffering_time) {
sc_cond_destroy(&vb->b.wait_cond);
sc_cond_destroy(&vb->b.queue_cond);
sc_mutex_destroy(&vb->b.mutex);
}
}
bool
sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) {
if (!vb->buffering_time) {
// 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);
}
struct sc_video_buffer_frame *vb_frame = sc_video_buffer_frame_new(frame);
if (!vb_frame) {
sc_mutex_unlock(&vb->b.mutex);
LOGE("Could not allocate frame");
return false;
}
#ifndef SC_BUFFERING_NDEBUG
vb_frame->push_date = sc_tick_now();
#endif
sc_queue_push(&vb->b.queue, next, vb_frame);
sc_cond_signal(&vb->b.queue_cond);
sc_mutex_unlock(&vb->b.mutex);
return true;
}
void
sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst) {
sc_frame_buffer_consume(&vb->fb, dst);
} }

View File

@@ -1,76 +1,50 @@
#ifndef SC_VIDEO_BUFFER_H #ifndef VIDEO_BUFFER_H
#define SC_VIDEO_BUFFER_H #define VIDEO_BUFFER_H
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include "clock.h" #include "fps_counter.h"
#include "frame_buffer.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 { /**
AVFrame *frame; * A video buffer holds 1 pending frame, which is the last frame received from
struct sc_video_buffer_frame *next; * the producer (typically, the decoder).
#ifndef NDEBUG *
sc_tick push_date; * If a pending frame has not been consumed when the producer pushes a new
#endif * frame, then it is lost. The intent is to always provide access to the very
}; * last frame to minimize latency.
*
* The producer and the consumer typically do not live in the same thread.
* That's the reason why the callback on_frame_available() does not provide the
* frame as parameter: the consumer might post an event to its own thread to
* retrieve the pending frame from there, and that frame may have changed since
* the callback if producer pushed a new one in between.
*/
struct sc_video_buffer_frame_queue SC_QUEUE(struct sc_video_buffer_frame); struct video_buffer {
AVFrame *pending_frame;
AVFrame *tmp_frame; // To preserve the pending frame on error
struct sc_video_buffer {
struct sc_frame_buffer fb;
sc_tick buffering_time;
// only if buffering_time > 0
struct {
sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
sc_cond queue_cond;
sc_cond wait_cond;
struct sc_clock clock; bool pending_frame_consumed;
struct sc_video_buffer_frame_queue queue;
bool stopped;
} b; // buffering
const struct sc_video_buffer_callbacks *cbs;
void *cbs_userdata;
};
struct sc_video_buffer_callbacks {
void (*on_new_frame)(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata);
}; };
bool bool
sc_video_buffer_init(struct sc_video_buffer *vb, sc_tick buffering_time, video_buffer_init(struct video_buffer *vb);
const struct sc_video_buffer_callbacks *cbs,
void *cbs_userdata); void
video_buffer_destroy(struct video_buffer *vb);
bool bool
sc_video_buffer_start(struct sc_video_buffer *vb); video_buffer_push(struct video_buffer *vb, const AVFrame *frame, bool *skipped);
void void
sc_video_buffer_stop(struct sc_video_buffer *vb); video_buffer_consume(struct video_buffer *vb, AVFrame *dst);
void
sc_video_buffer_join(struct sc_video_buffer *vb);
void
sc_video_buffer_destroy(struct sc_video_buffer *vb);
bool
sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame);
void
sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst);
#endif #endif

View File

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

View File

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

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'

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'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -1,16 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" version="1.1">
<path style="opacity:0.2" d="m 16.846877,12 c -1.116351,0 -2.227419,0.912229 -2.015075,2 l 3.122973,16 -5.596557,11.109375 C 11.959876,41.871734 11.885244,42.336988 12.177176,43 c 0.278672,0.632897 0.998812,1 1.747448,1 H 24 34.075375 c 0.748637,0 1.468777,-0.367103 1.747448,-1 0.291932,-0.663012 0.217302,-1.128266 -0.181041,-1.890625 L 30.045225,30 33.168198,14 c 0.212344,-1.087771 -0.898724,-2 -2.015075,-2 H 24 Z"/>
<path style="fill:#cccccc" d="m 16.846877,11 c -1.116351,0 -2.227419,0.912229 -2.015075,2 l 3.122973,16 -5.596557,11.109375 C 11.959876,40.871734 11.885244,41.336988 12.177176,42 c 0.278672,0.632897 0.998812,1 1.747448,1 H 24 34.075375 c 0.748637,0 1.468777,-0.367103 1.747448,-1 0.291932,-0.663012 0.217302,-1.128266 -0.181041,-1.890625 L 30.045225,29 33.168198,13 c 0.212344,-1.087771 -0.898724,-2 -2.015075,-2 H 24 Z"/>
<rect style="opacity:0.2" width="40" height="32" x="4" y="6" rx="2" ry="2"/>
<path style="fill:#e4e4e4" d="m 4,33 v 2 c 0,1.108 0.892,2 2,2 h 36 c 1.108,0 2,-0.892 2,-2 v -2 z"/>
<path style="opacity:0.1" d="m 11.494141,15 a 1.5,1.5 0 0 0 -0.832032,0.255859 1.5,1.5 0 0 0 -0.40625,2.082032 l 3.13086,4.654297 C 10.404945,24.606192 8.4012371,28.299159 8.0019531,32.460938 7.9284599,34.000879 9.5546875,34 9.5546875,34 H 38.40625 c 0,0 1.672856,-3.38e-4 1.591797,-1.617188 -0.416529,-4.131451 -2.415618,-7.796833 -5.380859,-10.394531 l 3.126953,-4.65039 a 1.5,1.5 0 0 0 -0.40625,-2.082032 1.5,1.5 0 0 0 -1.125,-0.228515 1.5,1.5 0 0 0 -0.957032,0.634765 l -3.072265,4.566407 C 29.78649,18.814887 26.990024,18 24.001953,18 c -2.989385,0 -5.786177,0.815488 -8.183594,2.230469 l -3.074218,-4.56836 A 1.5,1.5 0 0 0 11.787109,15.027344 1.5,1.5 0 0 0 11.494141,15 Z"/>
<path style="fill:#077063" d="M 6,5 C 4.892,5 4,5.892 4,7 V 33 H 44 V 7 C 44,5.892 43.108,5 42,5 Z"/>
<path style="opacity:0.1;fill:#ffffff" d="M 6,5 C 4.892,5 4,5.892 4,7 V 8 C 4,6.892 4.892,6 6,6 h 36 c 1.108,0 2,0.892 2,2 V 7 C 44,5.892 43.108,5 42,5 Z"/>
<path style="fill:none;stroke:#30dd81;stroke-width:3;stroke-linecap:round" d="M 15.1998,21.000026 11.5,15.5"/>
<path style="fill:none;stroke:#30dd81;stroke-width:3;stroke-linecap:round" d="M 32.799764,21.000026 36.5,15.5"/>
<path style="fill:#30dd81" d="m 24.002386,17.000034 c -8.355868,0 -15.2214979,6.346843 -15.9999669,14.460906 C 7.9289259,33.000882 9.5544999,33 9.5544999,33 H 38.406003 c 0,0 1.672201,-3.35e-4 1.591142,-1.617185 C 39.182807,23.305596 32.331836,17.000034 24.002386,17.000034 Z"/>
<path style="opacity:0.2" d="m 16,25 a 1.9999959,1.9999959 0 0 0 -2,2 1.9999959,1.9999959 0 0 0 2,2 1.9999959,1.9999959 0 0 0 2,-2 1.9999959,1.9999959 0 0 0 -2,-2 z m 16,0 a 1.9999959,1.9999959 0 0 0 -2,2 1.9999959,1.9999959 0 0 0 2,2 1.9999959,1.9999959 0 0 0 2,-2 1.9999959,1.9999959 0 0 0 -2,-2 z"/>
<path style="fill:#ffffff" d="M 15.999996,24.000008 A 1.9999959,1.9999959 0 0 1 17.999992,26.000004 1.9999959,1.9999959 0 0 1 15.999996,28 1.9999959,1.9999959 0 0 1 14,26.000004 1.9999959,1.9999959 0 0 1 15.999996,24.000008 Z"/>
<path style="fill:#ffffff" d="M 31.999996,24.000008 A 1.9999959,1.9999959 0 0 1 33.999991,26.000004 1.9999959,1.9999959 0 0 1 31.999996,28 1.9999959,1.9999959 0 0 1 30,26.000004 1.9999959,1.9999959 0 0 1 31.999996,24.000008 Z"/>
<path style="fill:#ffffff;opacity:0.2" d="M 11.494141 14 A 1.5 1.5 0 0 0 10.662109 14.255859 A 1.5 1.5 0 0 0 10.115234 16.001953 A 1.5 1.5 0 0 1 10.662109 15.255859 A 1.5 1.5 0 0 1 11.494141 15 A 1.5 1.5 0 0 1 11.787109 15.027344 A 1.5 1.5 0 0 1 12.744141 15.662109 L 15.818359 20.230469 C 18.215776 18.815488 21.012568 18 24.001953 18 C 26.990024 18 29.78649 18.814887 32.183594 20.228516 L 35.255859 15.662109 A 1.5 1.5 0 0 1 36.212891 15.027344 A 1.5 1.5 0 0 1 37.337891 15.255859 A 1.5 1.5 0 0 1 37.910156 16.001953 A 1.5 1.5 0 0 0 37.337891 14.255859 A 1.5 1.5 0 0 0 36.212891 14.027344 A 1.5 1.5 0 0 0 35.255859 14.662109 L 32.183594 19.228516 C 29.78649 17.814887 26.990024 17 24.001953 17 C 21.012568 17 18.215776 17.815488 15.818359 19.230469 L 12.744141 14.662109 A 1.5 1.5 0 0 0 11.787109 14.027344 A 1.5 1.5 0 0 0 11.494141 14 z M 35.033203 21.369141 L 34.617188 21.988281 C 37.477056 24.493668 39.433905 27.993356 39.943359 31.945312 C 39.986866 31.783283 40.008864 31.598575 39.998047 31.382812 C 39.601372 27.448291 37.768055 23.938648 35.033203 21.369141 z M 12.970703 21.373047 C 10.220358 23.959215 8.3822757 27.496796 8.0019531 31.460938 C 7.9920657 31.668114 8.0150508 31.844846 8.0585938 32 C 8.5570234 28.027243 10.515755 24.509049 13.386719 21.992188 L 12.970703 21.373047 z"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -2,8 +2,8 @@
set -e set -e
BUILDDIR=build-auto BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-server-v1.19 PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.18/scrcpy-server-v1.18
PREBUILT_SERVER_SHA256=876f9322182e6aac6a58db1334f4225855ef3a17eaebc80aab6601d9d1ecb867 PREBUILT_SERVER_SHA256=641c5c6beda9399dfae72d116f5ff43b5ed1059d871c9ebc3f47610fd33c51a3
echo "[scrcpy] Downloading prebuilt server..." echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@@ -1,5 +1,5 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '1.19', version: '1.18',
meson_version: '>= 0.48', meson_version: '>= 0.48',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',

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

View File

@@ -94,7 +94,6 @@ dist-win32: build-server build-win32
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)" cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
@@ -103,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)"
@@ -111,7 +110,6 @@ dist-win64: build-server build-win64
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)" cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
@@ -120,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)"; \

4
run
View File

@@ -20,6 +20,4 @@ then
exit 1 exit 1
fi fi
SCRCPY_ICON_PATH="data/icon.png" \ SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" "$BUILDDIR/app/scrcpy" "$@"
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \
"$BUILDDIR/app/scrcpy" "$@"

View File

@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 30
versionCode 11900 versionCode 11800
versionName "1.19" versionName "1.18"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View File

@@ -12,7 +12,7 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.19 SCRCPY_VERSION_NAME=1.18
PLATFORM=${ANDROID_PLATFORM:-30} PLATFORM=${ANDROID_PLATFORM:-30}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0}

View File

@@ -56,13 +56,17 @@ public class ScreenEncoder implements Device.RotationListener {
public void streamScreen(Device device, FileDescriptor fd) throws IOException { public void streamScreen(Device device, FileDescriptor fd) throws IOException {
Workarounds.prepareMainLooper(); Workarounds.prepareMainLooper();
if (Build.BRAND.equalsIgnoreCase("meizu")) {
// <https://github.com/Genymobile/scrcpy/issues/240>
// <https://github.com/Genymobile/scrcpy/issues/2656>
Workarounds.fillAppInfo();
}
try {
internalStreamScreen(device, fd); internalStreamScreen(device, fd);
} catch (NullPointerException e) {
// Retry with workarounds enabled:
// <https://github.com/Genymobile/scrcpy/issues/365>
// <https://github.com/Genymobile/scrcpy/issues/940>
Ln.d("Applying workarounds to avoid NullPointerException");
Workarounds.fillAppInfo();
internalStreamScreen(device, fd);
}
} }
private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException { private void internalStreamScreen(Device device, FileDescriptor fd) throws IOException {

View File

@@ -11,7 +11,6 @@ public class StatusBarManager {
private final IInterface manager; private final IInterface manager;
private Method expandNotificationsPanelMethod; private Method expandNotificationsPanelMethod;
private boolean expandNotificationPanelMethodCustomVersion;
private Method expandSettingsPanelMethod; private Method expandSettingsPanelMethod;
private boolean expandSettingsPanelMethodNewVersion = true; private boolean expandSettingsPanelMethodNewVersion = true;
private Method collapsePanelsMethod; private Method collapsePanelsMethod;
@@ -22,13 +21,7 @@ public class StatusBarManager {
private Method getExpandNotificationsPanelMethod() throws NoSuchMethodException { private Method getExpandNotificationsPanelMethod() throws NoSuchMethodException {
if (expandNotificationsPanelMethod == null) { if (expandNotificationsPanelMethod == null) {
try {
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel"); expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
} catch (NoSuchMethodException e) {
// Custom version for custom vendor ROM: <https://github.com/Genymobile/scrcpy/issues/2551>
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel", int.class);
expandNotificationPanelMethodCustomVersion = true;
}
} }
return expandNotificationsPanelMethod; return expandNotificationsPanelMethod;
} }
@@ -57,11 +50,7 @@ public class StatusBarManager {
public void expandNotificationsPanel() { public void expandNotificationsPanel() {
try { try {
Method method = getExpandNotificationsPanelMethod(); Method method = getExpandNotificationsPanelMethod();
if (expandNotificationPanelMethodCustomVersion) {
method.invoke(manager, 0);
} else {
method.invoke(manager); method.invoke(manager);
}
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);
} }