Compare commits

..

19 Commits

Author SHA1 Message Date
Romain Vimont
0de534d3bc Attempt to log message only in verbose mode
If the log level is not verbose, there is no need to attept to log
control messages at all.
2021-06-20 16:04:18 +02:00
Romain Vimont
488991116b Expose function to get the current log level
This will allow to avoid unnecessary processing for creating logs which
will be discarded anyway.
2021-06-20 16:04:18 +02:00
Romain Vimont
5c95d18beb Move log level conversion to log API 2021-06-20 16:04:18 +02:00
Romain Vimont
1039f9b531 Workaround PRIu64 on Windows
On Windows, PRIu64 is defined to "llu", which is not supported:

    error: unknown conversion type character 'l' in format
2021-06-20 16:04:18 +02:00
Marti Raudsepp
19ca02cd8f Log control messages in verbose mode
PR #2371 <https://github.com/Genymobile/scrcpy/pull/2371>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-06-20 16:03:52 +02:00
Marti Raudsepp
937fa704a6 Add --verbosity=verbose log level
PR #2371 <https://github.com/Genymobile/scrcpy/pull/2371>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-06-20 12:34:19 +02:00
Romain Vimont
7db0189f23 Forward mouse motion only on main clicks
Mouse motion events were forwarded as soon as any mouse button was
pressed.

Instead, only consider left-click (and also middle-click and right-click
if --forward-all-clicks is enabled).
2021-06-20 12:34:19 +02:00
Romain Vimont
8b90e1d3f4 Remove extra ';' in #define 2021-06-20 12:34:19 +02:00
Romain Vimont
df017160ed Replace strcpy() by memcpy()
It was safe to call strcpy() since the input length was checked, but
then it is more straightforward to call memcpy() directly.
2021-06-20 12:34:19 +02:00
Romain Vimont
b846d3a085 Adapt call() on ContentProvider for Android 12
Android 12 changed one of the call() overloads with a new parameter
AttributionSource. Adapt the wrapper.

Fixes #2402 <https://github.com/Genymobile/scrcpy/issues/2402>
2021-06-19 22:59:48 +02:00
Romain Vimont
a1f2094787 Push to /sdcard/Download/ by default
Change the default push target from /sdcard/ to /sdcard/Download/.

Pushing to the root of /sdcard/ is not very convenient, many apps do not
expose its content directly.

It can still be changed by --push-target.

PR #2384 <https://github.com/Genymobile/scrcpy/pull/2384>
2021-06-16 22:56:11 +02:00
Romain Vimont
9b89b7ab72 Center the window on resize-to-fit
When removing the black borders (by double-clicking on them, or by
pressing MOD+w), the window is resized to fit the device screen, but its
top-left position was left unchanged.

Instead, move the window so that the new window area is at the center of
the old window area.

Refs #2387 <https://github.com/Genymobile/scrcpy/issues/2387>
2021-06-14 21:24:51 +02:00
Romain Vimont
7343b233e4 Render screen on window restored
It should not be necessary, since screen_render() is called just after
on SDL_WINDOWEVENT_EXPOSED, but in practice the window content might not
be correctly displayed on restored if a rotation occurred while
minimized.

Note that calling screen_render() twice in a row on
SDL_WINDOWEVENT_EXPOSED also "fixes" the issue.
2021-06-14 09:36:08 +02:00
Romain Vimont
cd2894570d Allocate AVPacket for v4l2_sink
From FFmpeg/doc/APIchanges:

    2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h
      Deprecated av_init_packet(). Once removed, sizeof(AVPacket) will
      no longer be a part of the public ABI.

Refs #2302 <https://github.com/Genymobile/scrcpy/issues/2302>
2021-06-14 09:07:49 +02:00
Romain Vimont
4af317d40d Allocate AVPacket for recorder
From FFmpeg/doc/APIchanges:

    2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h
      Deprecated av_init_packet(). Once removed, sizeof(AVPacket) will
      no longer be a part of the public ABI.

Refs #2302 <https://github.com/Genymobile/scrcpy/issues/2302>
2021-06-14 09:07:49 +02:00
Romain Vimont
318b6a572e Allocate AVPacket for local stream packet
From FFmpeg/doc/APIchanges:

    2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h
      Deprecated av_init_packet(). Once removed, sizeof(AVPacket) will
      no longer be a part of the public ABI.

Refs #2302 <https://github.com/Genymobile/scrcpy/issues/2302>
2021-06-14 09:07:49 +02:00
Romain Vimont
e8b053ad2f Allocate AVPacket for stream->pending
From FFmpeg/doc/APIchanges:

    2021-03-17 - f7db77bd87 - lavc 58.133.100 - codec.h
      Deprecated av_init_packet(). Once removed, sizeof(AVPacket) will
      no longer be a part of the public ABI.

Remove the has_pending boolean, which can be replaced by:

    stream->pending != NULL

Refs #2302 <https://github.com/Genymobile/scrcpy/issues/2302>
2021-06-14 09:07:49 +02:00
Romain Vimont
a5d71eee45 Clarify --no-display usage with v4l2 2021-06-14 08:16:05 +02:00
Romain Vimont
af228706f1 Fix compatibility with old FFmpeg
V4L2 sink used a "url" field format AVFormatContext which has been
introduced in lavf 58.7.100.

Fixes #2382 <https://github.com/Genymobile/scrcpy/issues/2382>

Refs <ea3672b7d6>
Refs <fa8308d3d4>
2021-06-13 19:20:57 +02:00
24 changed files with 379 additions and 103 deletions

View File

@@ -303,7 +303,8 @@ To start scrcpy using a v4l2 sink:
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN -N # --no-display to disable mirroring window
scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window
scrcpy --v4l2-sink=/dev/videoN -N # short version
```
(replace `N` by the device ID, check with `ls /dev/video*`)
@@ -709,15 +710,15 @@ There is no visual feedback, a log is printed to the console.
#### Push file to device
To push a file to `/sdcard/` on the device, drag & drop a (non-APK) file to the
_scrcpy_ window.
To push a file to `/sdcard/Download/` on the device, drag & drop a (non-APK)
file to the _scrcpy_ window.
There is no visual feedback, a log is printed to the console.
The target directory can be changed on start:
```bash
scrcpy --push-target=/sdcard/Download/
scrcpy --push-target=/sdcard/Movies/
```

View File

@@ -20,6 +20,7 @@ src = [
'src/stream.c',
'src/tiny_xpm.c',
'src/video_buffer.c',
'src/util/log.c',
'src/util/net.c',
'src/util/process.c',
'src/util/str_util.c',

View File

@@ -133,7 +133,7 @@ but breaks the expected behavior of alpha keys in games (typically WASD).
.BI "\-\-push\-target " path
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
Default is "/sdcard/".
Default is "/sdcard/Download/".
.TP
.BI "\-r, \-\-record " file
@@ -193,7 +193,7 @@ It requires to lock the video orientation (see --lock-video-orientation).
.TP
.BI "\-V, \-\-verbosity " value
Set the log level ("debug", "info", "warn" or "error").
Set the log level ("verbose", "debug", "info", "warn" or "error").
Default is "info" for release builds, "debug" for debug builds.

View File

@@ -129,7 +129,7 @@ scrcpy_print_usage(const char *arg0) {
" --push-target path\n"
" Set the target directory for pushing files to the device by\n"
" drag & drop. It is passed as-is to \"adb push\".\n"
" Default is \"/sdcard/\".\n"
" Default is \"/sdcard/Download/\".\n"
"\n"
" -r, --record file.mp4\n"
" Record screen to file.\n"
@@ -184,7 +184,7 @@ scrcpy_print_usage(const char *arg0) {
"\n"
#endif
" -V, --verbosity value\n"
" Set the log level (debug, info, warn or error).\n"
" Set the log level (verbose, debug, info, warn or error).\n"
#ifndef NDEBUG
" Default is debug.\n"
#else
@@ -505,6 +505,11 @@ parse_display_id(const char *s, uint32_t *display_id) {
static bool
parse_log_level(const char *s, enum sc_log_level *log_level) {
if (!strcmp(s, "verbose")) {
*log_level = SC_LOG_LEVEL_VERBOSE;
return true;
}
if (!strcmp(s, "debug")) {
*log_level = SC_LOG_LEVEL_DEBUG;
return true;

View File

@@ -1,6 +1,7 @@
#include "control_msg.h"
#include <assert.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
@@ -8,6 +9,52 @@
#include "util/log.h"
#include "util/str_util.h"
/**
* Map an enum value to a string based on an array, without crashing on an
* out-of-bounds index.
*/
#define ENUM_TO_LABEL(labels, value) \
((size_t) (value) < ARRAY_LEN(labels) ? labels[value] : "???")
#define KEYEVENT_ACTION_LABEL(value) \
ENUM_TO_LABEL(android_keyevent_action_labels, value)
#define MOTIONEVENT_ACTION_LABEL(value) \
ENUM_TO_LABEL(android_motionevent_action_labels, value)
#define SCREEN_POWER_MODE_LABEL(value) \
ENUM_TO_LABEL(screen_power_mode_labels, value)
static const char *const android_keyevent_action_labels[] = {
"down",
"up",
"multi",
};
static const char *const android_motionevent_action_labels[] = {
"down",
"up",
"move",
"cancel",
"outside",
"ponter-down",
"pointer-up",
"hover-move",
"scroll",
"hover-enter"
"hover-exit",
"btn-press",
"btn-release",
};
static const char *const screen_power_mode_labels[] = {
"off",
"doze",
"normal",
"doze-suspend",
"suspend",
};
static void
write_position(uint8_t *buf, const struct position *position) {
buffer_write32be(&buf[0], position->point.x);
@@ -93,6 +140,94 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
}
}
void
control_msg_log(const struct control_msg *msg) {
#define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__)
switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
LOG_CMSG("key %-4s code=%d repeat=%" PRIu32 " meta=%06lx",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action),
(int) msg->inject_keycode.keycode,
msg->inject_keycode.repeat,
(long) msg->inject_keycode.metastate);
break;
case CONTROL_MSG_TYPE_INJECT_TEXT:
LOG_CMSG("text \"%s\"", msg->inject_text.text);
break;
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
int action = msg->inject_touch_event.action
& AMOTION_EVENT_ACTION_MASK;
uint64_t id = msg->inject_touch_event.pointer_id;
if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) {
// string pointer id
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
" pressure=%g buttons=%06lx",
id == POINTER_ID_MOUSE ? "mouse" : "vfinger",
MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x,
msg->inject_touch_event.position.point.y,
msg->inject_touch_event.pressure,
(long) msg->inject_touch_event.buttons);
} else {
// numeric pointer id
#ifndef __WIN32
# define PRIu64_ PRIu64
#else
# define PRIu64_ "I64u" // Windows...
#endif
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
PRIi32 " pressure=%g buttons=%06lx",
id,
MOTIONEVENT_ACTION_LABEL(action),
msg->inject_touch_event.position.point.x,
msg->inject_touch_event.position.point.y,
msg->inject_touch_event.pressure,
(long) msg->inject_touch_event.buttons);
}
break;
}
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
" vscroll=%" PRIi32,
msg->inject_scroll_event.position.point.x,
msg->inject_scroll_event.position.point.y,
msg->inject_scroll_event.hscroll,
msg->inject_scroll_event.vscroll);
break;
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
LOG_CMSG("back-or-screen-on %s",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
break;
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
LOG_CMSG("clipboard %s \"%s\"",
msg->set_clipboard.paste ? "paste" : "copy",
msg->set_clipboard.text);
break;
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
LOG_CMSG("power mode %s",
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode));
break;
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
LOG_CMSG("expand notification panel");
break;
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
LOG_CMSG("expand settings panel");
break;
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
LOG_CMSG("collapse panels");
break;
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
LOG_CMSG("get clipboard");
break;
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
LOG_CMSG("rotate device");
break;
default:
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
break;
}
}
void
control_msg_destroy(struct control_msg *msg) {
switch (msg->type) {

View File

@@ -17,8 +17,8 @@
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
#define POINTER_ID_MOUSE UINT64_C(-1);
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2);
#define POINTER_ID_MOUSE UINT64_C(-1)
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
enum control_msg_type {
CONTROL_MSG_TYPE_INJECT_KEYCODE,
@@ -84,6 +84,9 @@ struct control_msg {
size_t
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
void
control_msg_log(const struct control_msg *msg);
void
control_msg_destroy(struct control_msg *msg);

View File

@@ -48,6 +48,10 @@ controller_destroy(struct controller *controller) {
bool
controller_push_msg(struct controller *controller,
const struct control_msg *msg) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
control_msg_log(msg);
}
sc_mutex_lock(&controller->mutex);
bool was_empty = cbuf_is_empty(&controller->queue);
bool res = cbuf_push(&controller->queue, *msg);

View File

@@ -6,7 +6,7 @@
#include "adb.h"
#include "util/log.h"
#define DEFAULT_PUSH_TARGET "/sdcard/"
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
static void
file_handler_request_destroy(struct file_handler_request *req) {

View File

@@ -595,8 +595,12 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
static void
input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event) {
if (!event->state) {
// do not send motion events when no button is pressed
uint32_t mask = SDL_BUTTON_LMASK;
if (im->forward_all_clicks) {
mask |= SDL_BUTTON_MMASK | SDL_BUTTON_RMASK;
}
if (!(event->state & mask)) {
// do not send motion events when no click is pressed
return;
}
if (event->which == SDL_TOUCH_MOUSEID) {

View File

@@ -38,24 +38,6 @@ print_version(void) {
#endif
}
static SDL_LogPriority
convert_log_level_to_sdl(enum sc_log_level level) {
switch (level) {
case SC_LOG_LEVEL_DEBUG:
return SDL_LOG_PRIORITY_DEBUG;
case SC_LOG_LEVEL_INFO:
return SDL_LOG_PRIORITY_INFO;
case SC_LOG_LEVEL_WARN:
return SDL_LOG_PRIORITY_WARN;
case SC_LOG_LEVEL_ERROR:
return SDL_LOG_PRIORITY_ERROR;
default:
assert(!"unexpected log level");
return SDL_LOG_PRIORITY_INFO;
}
}
int
main(int argc, char *argv[]) {
#ifdef __WINDOWS__
@@ -79,8 +61,7 @@ main(int argc, char *argv[]) {
return 1;
}
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
sc_set_log_level(args.opts.log_level);
if (args.help) {
scrcpy_print_usage(argv[0]);

View File

@@ -35,11 +35,14 @@ record_packet_new(const AVPacket *packet) {
return NULL;
}
// av_packet_ref() does not initialize all fields in old FFmpeg versions
// See <https://github.com/Genymobile/scrcpy/issues/707>
av_init_packet(&rec->packet);
rec->packet = av_packet_alloc();
if (!rec->packet) {
free(rec);
return NULL;
}
if (av_packet_ref(&rec->packet, packet)) {
if (av_packet_ref(rec->packet, packet)) {
av_packet_free(&rec->packet);
free(rec);
return NULL;
}
@@ -48,7 +51,8 @@ record_packet_new(const AVPacket *packet) {
static void
record_packet_delete(struct record_packet *rec) {
av_packet_unref(&rec->packet);
av_packet_unref(rec->packet);
av_packet_free(&rec->packet);
free(rec);
}
@@ -144,8 +148,8 @@ run_recorder(void *data) {
struct record_packet *last = recorder->previous;
if (last) {
// assign an arbitrary duration to the last packet
last->packet.duration = 100000;
bool ok = recorder_write(recorder, &last->packet);
last->packet->duration = 100000;
bool ok = recorder_write(recorder, last->packet);
if (!ok) {
// failing to write the last frame is not very serious, no
// future frame may depend on it, so the resulting file
@@ -172,13 +176,14 @@ run_recorder(void *data) {
}
// config packets have no PTS, we must ignore them
if (rec->packet.pts != AV_NOPTS_VALUE
&& previous->packet.pts != AV_NOPTS_VALUE) {
if (rec->packet->pts != AV_NOPTS_VALUE
&& previous->packet->pts != AV_NOPTS_VALUE) {
// we now know the duration of the previous packet
previous->packet.duration = rec->packet.pts - previous->packet.pts;
previous->packet->duration =
rec->packet->pts - previous->packet->pts;
}
bool ok = recorder_write(recorder, &previous->packet);
bool ok = recorder_write(recorder, previous->packet);
record_packet_delete(previous);
if (!ok) {
LOGE("Could not record packet");

View File

@@ -13,7 +13,7 @@
#include "util/thread.h"
struct record_packet {
AVPacket packet;
AVPacket *packet;
struct record_packet *next;
};

View File

@@ -216,14 +216,15 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
if (priority == 0) {
return;
}
char *local_fmt = malloc(strlen(fmt) + 10);
size_t fmt_len = strlen(fmt);
char *local_fmt = malloc(fmt_len + 10);
if (!local_fmt) {
LOGC("Could not allocate string");
return;
}
// strcpy is safe here, the destination is large enough
strcpy(local_fmt, "[FFmpeg] ");
strcpy(local_fmt + 9, fmt);
memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
memcpy(local_fmt + 9, fmt, fmt_len + 1); // include '\0'
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
free(local_fmt);
}

View File

@@ -8,6 +8,7 @@
#include <stdint.h>
enum sc_log_level {
SC_LOG_LEVEL_VERBOSE,
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,

View File

@@ -41,6 +41,18 @@ get_window_size(const struct screen *screen) {
return size;
}
static struct point
get_window_position(const struct screen *screen) {
int x;
int y;
SDL_GetWindowPosition(screen->window, &x, &y);
struct point point;
point.x = x;
point.y = y;
return point;
}
// set the window size to be applied when fullscreen is disabled
static void
set_window_size(struct screen *screen, struct size new_size) {
@@ -122,13 +134,6 @@ get_optimal_size(struct size current_size, struct size content_size) {
return window_size;
}
// same as get_optimal_size(), but read the current size from the window
static inline struct size
get_optimal_window_size(const struct screen *screen, struct size content_size) {
struct size window_size = get_window_size(screen);
return get_optimal_size(window_size, content_size);
}
// initially, there is no current size, so use the frame size as current size
// req_width and req_height, if not 0, are the sizes requested by the user
static inline struct size
@@ -662,9 +667,20 @@ screen_resize_to_fit(struct screen *screen) {
return;
}
struct point point = get_window_position(screen);
struct size window_size = get_window_size(screen);
struct size optimal_size =
get_optimal_window_size(screen, screen->content_size);
get_optimal_size(window_size, screen->content_size);
// Center the window related to the device screen
assert(optimal_size.width <= window_size.width);
assert(optimal_size.height <= window_size.height);
uint32_t new_x = point.x + (window_size.width - optimal_size.width) / 2;
uint32_t new_y = point.y + (window_size.height - optimal_size.height) / 2;
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
SDL_SetWindowPosition(screen->window, new_x, new_y);
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
optimal_size.height);
}
@@ -726,6 +742,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
}
screen->maximized = false;
apply_pending_resize(screen);
screen_render(screen, true);
break;
}
return true;

View File

@@ -235,6 +235,8 @@ enable_tunnel_any_port(struct server *server, struct sc_port_range port_range,
static const char *
log_level_to_server_string(enum sc_log_level level) {
switch (level) {
case SC_LOG_LEVEL_VERBOSE:
return "verbose";
case SC_LOG_LEVEL_DEBUG:
return "debug";
case SC_LOG_LEVEL_INFO:

View File

@@ -104,33 +104,38 @@ static bool
stream_push_packet(struct stream *stream, AVPacket *packet) {
bool is_config = packet->pts == AV_NOPTS_VALUE;
// A config packet must not be decoded immetiately (it contains no
// A config packet must not be decoded immediately (it contains no
// frame); instead, it must be concatenated with the future data packet.
if (stream->has_pending || is_config) {
if (stream->pending || is_config) {
size_t offset;
if (stream->has_pending) {
offset = stream->pending.size;
if (av_grow_packet(&stream->pending, packet->size)) {
if (stream->pending) {
offset = stream->pending->size;
if (av_grow_packet(stream->pending, packet->size)) {
LOGE("Could not grow packet");
return false;
}
} else {
offset = 0;
if (av_new_packet(&stream->pending, packet->size)) {
LOGE("Could not create packet");
stream->pending = av_packet_alloc();
if (!stream->pending) {
LOGE("Could not allocate packet");
return false;
}
if (av_new_packet(stream->pending, packet->size)) {
LOGE("Could not create packet");
av_packet_free(&stream->pending);
return false;
}
stream->has_pending = true;
}
memcpy(stream->pending.data + offset, packet->data, packet->size);
memcpy(stream->pending->data + offset, packet->data, packet->size);
if (!is_config) {
// prepare the concat packet to send to the decoder
stream->pending.pts = packet->pts;
stream->pending.dts = packet->dts;
stream->pending.flags = packet->flags;
packet = &stream->pending;
stream->pending->pts = packet->pts;
stream->pending->dts = packet->dts;
stream->pending->flags = packet->flags;
packet = stream->pending;
}
}
@@ -144,10 +149,10 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
// data packet
bool ok = stream_parse(stream, packet);
if (stream->has_pending) {
if (stream->pending) {
// the pending packet must be discarded (consumed or error)
stream->has_pending = false;
av_packet_unref(&stream->pending);
av_packet_unref(stream->pending);
av_packet_free(&stream->pending);
}
if (!ok) {
@@ -215,16 +220,21 @@ run_stream(void *data) {
// It's more complicated, but this allows to reduce the latency by 1 frame!
stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOGE("Could not allocate packet");
goto finally_close_parser;
}
for (;;) {
AVPacket packet;
bool ok = stream_recv_packet(stream, &packet);
bool ok = stream_recv_packet(stream, packet);
if (!ok) {
// end of stream
break;
}
ok = stream_push_packet(stream, &packet);
av_packet_unref(&packet);
ok = stream_push_packet(stream, packet);
av_packet_unref(packet);
if (!ok) {
// cannot process packet (error already logged)
break;
@@ -233,10 +243,13 @@ run_stream(void *data) {
LOGD("End of frames");
if (stream->has_pending) {
av_packet_unref(&stream->pending);
if (stream->pending) {
av_packet_unref(stream->pending);
av_packet_free(&stream->pending);
}
av_packet_free(&packet);
finally_close_parser:
av_parser_close(stream->parser);
finally_close_sinks:
stream_close_sinks(stream);
@@ -252,7 +265,7 @@ void
stream_init(struct stream *stream, socket_t socket,
const struct stream_callbacks *cbs, void *cbs_userdata) {
stream->socket = socket;
stream->has_pending = false;
stream->pending = NULL;
stream->sink_count = 0;
assert(cbs && cbs->on_eos);

View File

@@ -24,8 +24,7 @@ struct stream {
AVCodecParserContext *parser;
// successive packets may need to be concatenated, until a non-config
// packet is available
bool has_pending;
AVPacket pending;
AVPacket *pending;
const struct stream_callbacks *cbs;
void *cbs_userdata;

53
app/src/util/log.c Normal file
View File

@@ -0,0 +1,53 @@
#include "log.h"
#include <assert.h>
static SDL_LogPriority
log_level_sc_to_sdl(enum sc_log_level level) {
switch (level) {
case SC_LOG_LEVEL_VERBOSE:
return SDL_LOG_PRIORITY_VERBOSE;
case SC_LOG_LEVEL_DEBUG:
return SDL_LOG_PRIORITY_DEBUG;
case SC_LOG_LEVEL_INFO:
return SDL_LOG_PRIORITY_INFO;
case SC_LOG_LEVEL_WARN:
return SDL_LOG_PRIORITY_WARN;
case SC_LOG_LEVEL_ERROR:
return SDL_LOG_PRIORITY_ERROR;
default:
assert(!"unexpected log level");
return SDL_LOG_PRIORITY_INFO;
}
}
static enum sc_log_level
log_level_sdl_to_sc(SDL_LogPriority priority) {
switch (priority) {
case SDL_LOG_PRIORITY_VERBOSE:
return SC_LOG_LEVEL_VERBOSE;
case SDL_LOG_PRIORITY_DEBUG:
return SC_LOG_LEVEL_DEBUG;
case SDL_LOG_PRIORITY_INFO:
return SC_LOG_LEVEL_INFO;
case SDL_LOG_PRIORITY_WARN:
return SC_LOG_LEVEL_WARN;
case SDL_LOG_PRIORITY_ERROR:
return SC_LOG_LEVEL_ERROR;
default:
assert(!"unexpected log level");
return SC_LOG_LEVEL_INFO;
}
}
void
sc_set_log_level(enum sc_log_level level) {
SDL_LogPriority sdl_log = log_level_sc_to_sdl(level);
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
}
enum sc_log_level
sc_get_log_level(void) {
SDL_LogPriority sdl_log = SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION);
return log_level_sdl_to_sc(sdl_log);
}

View File

@@ -1,8 +1,12 @@
#ifndef LOG_H
#define LOG_H
#ifndef SC_LOG_H
#define SC_LOG_H
#include "common.h"
#include <SDL2/SDL_log.h>
#include "scrcpy.h"
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
@@ -10,4 +14,10 @@
#define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGC(...) SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
void
sc_set_log_level(enum sc_log_level level);
enum sc_log_level
sc_get_log_level(void);
#endif

View File

@@ -86,7 +86,7 @@ encode_and_write_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) {
return false;
}
AVPacket *packet = &vs->packet;
AVPacket *packet = vs->packet;
ret = avcodec_receive_packet(vs->encoder_ctx, packet);
if (ret == 0) {
// A packet was received
@@ -181,16 +181,16 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
vs->format_ctx->oformat = (AVOutputFormat *) format;
#ifdef SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
# define CTX_URL_FIELD url
#else
# define CTX_URL_FIELD filename
#endif
vs->format_ctx->CTX_URL_FIELD = strdup(vs->device_name);
vs->format_ctx->url = strdup(vs->device_name);
if (!vs->format_ctx->url) {
LOGE("Could not strdup v4l2 device name");
goto error_avformat_free_context;
return false;
}
#else
strncpy(vs->format_ctx->filename, vs->device_name,
sizeof(vs->format_ctx->filename));
#endif
AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder);
if (!ostream) {
@@ -235,11 +235,17 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
goto error_avcodec_close;
}
vs->packet = av_packet_alloc();
if (!vs->packet) {
LOGE("Could not allocate packet");
goto error_av_frame_free;
}
LOGD("Starting v4l2 thread");
ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs);
if (!ok) {
LOGC("Could not start v4l2 thread");
goto error_av_frame_free;
goto error_av_packet_free;
}
vs->header_written = false;
@@ -249,6 +255,8 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
return true;
error_av_packet_free:
av_packet_free(&vs->packet);
error_av_frame_free:
av_frame_free(&vs->frame);
error_avcodec_close:
@@ -278,6 +286,7 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
sc_thread_join(&vs->thread, NULL);
av_packet_free(&vs->packet);
av_frame_free(&vs->frame);
avcodec_close(vs->encoder_ctx);
avcodec_free_context(&vs->encoder_ctx);

View File

@@ -26,7 +26,7 @@ struct sc_v4l2_sink {
bool header_written;
AVFrame *frame;
AVPacket packet;
AVPacket *packet;
};
bool

View File

@@ -12,7 +12,7 @@ public final class Ln {
private static final String PREFIX = "[server] ";
enum Level {
DEBUG, INFO, WARN, ERROR
VERBOSE, DEBUG, INFO, WARN, ERROR
}
private static Level threshold = Level.INFO;
@@ -36,6 +36,13 @@ public final class Ln {
return level.ordinal() >= threshold.ordinal();
}
public static void v(String message) {
if (isEnabled(Level.VERBOSE)) {
Log.v(TAG, message);
System.out.println(PREFIX + "VERBOSE: " + message);
}
}
public static void d(String message) {
if (isEnabled(Level.DEBUG)) {
Log.d(TAG, message);

View File

@@ -2,6 +2,7 @@ package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.IBinder;
@@ -37,6 +38,8 @@ public class ContentProvider implements Closeable {
private Method callMethod;
private int callMethodVersion;
private Object attributionSource;
ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) {
this.manager = manager;
this.provider = provider;
@@ -44,36 +47,58 @@ public class ContentProvider implements Closeable {
this.token = token;
}
@SuppressLint("PrivateApi")
private Method getCallMethod() throws NoSuchMethodException {
if (callMethod == null) {
try {
callMethod = provider.getClass()
.getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
Class<?> attributionSourceClass = Class.forName("android.content.AttributionSource");
callMethod = provider.getClass().getMethod("call", attributionSourceClass, String.class, String.class, String.class, Bundle.class);
callMethodVersion = 0;
} catch (NoSuchMethodException e) {
} catch (NoSuchMethodException | ClassNotFoundException e0) {
// old versions
try {
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
callMethod = provider.getClass()
.getMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
callMethodVersion = 1;
} catch (NoSuchMethodException e2) {
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
callMethodVersion = 2;
} catch (NoSuchMethodException e1) {
try {
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
callMethodVersion = 2;
} catch (NoSuchMethodException e2) {
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
callMethodVersion = 3;
}
}
}
}
return callMethod;
}
@SuppressLint("PrivateApi")
private Object getAttributionSource()
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (attributionSource == null) {
Class<?> cl = Class.forName("android.content.AttributionSource$Builder");
Object builder = cl.getConstructor(int.class).newInstance(ServiceManager.USER_ID);
cl.getDeclaredMethod("setPackageName", String.class).invoke(builder, ServiceManager.PACKAGE_NAME);
attributionSource = cl.getDeclaredMethod("build").invoke(builder);
}
return attributionSource;
}
private Bundle call(String callMethod, String arg, Bundle extras) {
try {
Method method = getCallMethod();
Object[] args;
switch (callMethodVersion) {
case 0:
args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras};
args = new Object[]{getAttributionSource(), "settings", callMethod, arg, extras};
break;
case 1:
args = new Object[]{ServiceManager.PACKAGE_NAME, null, "settings", callMethod, arg, extras};
break;
case 2:
args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
break;
default:
@@ -81,7 +106,7 @@ public class ContentProvider implements Closeable {
break;
}
return (Bundle) method.invoke(provider, args);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException | InstantiationException e) {
Ln.e("Could not invoke method", e);
return null;
}