Compare commits

...

9 Commits

Author SHA1 Message Date
57687bdfcd Write header file with correct extradata
When recording, the header must be written with extradata set to the
content of the very first packet.

Suggested-by: Steve Lhomme <robux4@ycbcr.xyz>
2019-02-09 12:54:12 +01:00
ee3cba57a8 Forward FFmpeg logs
FFmpeg logs can be useful to understand the cause of issues.
2019-02-09 12:27:13 +01:00
c11905b860 Add log verbose macro
This was the only log priority missing.
2019-02-09 12:27:13 +01:00
1a5ba59504 Fix memory leak on close
The buffer associated to the AVIOContext must be freed.
2019-02-09 12:27:13 +01:00
aa07dfd2ca Merge pull request from npes87184/dev
app: add always_on_top
2019-01-27 14:09:19 +01:00
eca82e09c3 app: add always_on_top
It is very convenient when I play mobile game and watch video at the
same time.

Tested on Linux mint Cinnamon as well as Windows 10.

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
2019-01-27 20:54:24 +08:00
b35733edb6 Fix expected mouse event sizes
Commit fefb9816a9 modified mouse events
serialization. The server-side parsing was updated to correctly read the
position, but the expected size of these events was not updated.

As a result, the server might try to parse incomplete events, leading
to BufferUnderflowException.

Fixes
<https://github.com/Genymobile/scrcpy/issues/350#issuecomment-456298816>.
2019-01-22 09:47:26 +01:00
7764a836f1 Fix incorrect comment
Comment had not been updated along with the code.
2019-01-22 09:38:51 +01:00
0bfaf7b7ff Update links to v1.6 in README and BUILD 2019-01-20 22:05:32 +01:00
13 changed files with 149 additions and 44 deletions

@ -234,10 +234,10 @@ You can then [run](README.md#run) _scrcpy_.
## Prebuilt server ## Prebuilt server
- [`scrcpy-server-v1.5.jar`][direct-scrcpy-server] - [`scrcpy-server-v1.6.jar`][direct-scrcpy-server]
_(SHA-256: d97aab6f60294e33e7ff79c2856ad3e01f912892395131f4f337e9ece03c24de)_ _(SHA-256: 08df924bf6d10943df9eaacfff548a99871ebfca4641f8c7ddddb73f27cb905b)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.5-fixversion/scrcpy-server-v1.5.jar [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.6/scrcpy-server-v1.6.jar
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:

@ -1,4 +1,4 @@
# scrcpy (v1.5) # scrcpy (v1.6)
This application provides display and control of Android devices connected on This application provides display and control of Android devices connected on
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access. USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
@ -47,13 +47,13 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
For Windows, for simplicity, prebuilt archives with all the dependencies For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available: (including `adb`) are available:
- [`scrcpy-win32-v1.5.zip`][direct-win32] - [`scrcpy-win32-v1.6.zip`][direct-win32]
_(SHA-256: 46ae0d4c1c6bd049ec4a30080d2ad91a32b31d3f758afdca2c3a915ecabf02c1)_ _(SHA-256: 4ca0c5924ab2ebf19b70f6598b2e546f65ba469a72ded2d1b213df3380fb46b1)_
- [`scrcpy-win64-v1.5.zip`][direct-win64] - [`scrcpy-win64-v1.6.zip`][direct-win64]
_(SHA-256: 89daa07325129617cf943a84bc4e304ee5e57118416fe265b9b5d4a1bf87c501)_ _(SHA-256: f66b7eace8dd6537a9a27176fd824704a284d8e82077ccc903344396043f90c9)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.5-fixversion/scrcpy-win32-v1.5.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.6/scrcpy-win32-v1.6.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.5-fixversion/scrcpy-win64-v1.5.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.6/scrcpy-win64-v1.6.zip
You can also [build the app manually][BUILD]. You can also [build the app manually][BUILD].
@ -196,6 +196,16 @@ scrcpy -f # short version
Fullscreen can then be toggled dynamically with `Ctrl`+`f`. Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
### Always on top
The window of app can always be above others by:
```bash
scrcpy --always-on-top
scrcpy -T # short version
```
### Show touches ### Show touches
For presentations, it may be useful to show physical touches (on the physical For presentations, it may be useful to show physical touches (on the physical

@ -23,7 +23,7 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu
buffer_write32be(&buf[6], event->keycode_event.metastate); buffer_write32be(&buf[6], event->keycode_event.metastate);
return 10; return 10;
case CONTROL_EVENT_TYPE_TEXT: { case CONTROL_EVENT_TYPE_TEXT: {
// write length (1 byte) + date (non nul-terminated) // write length (2 bytes) + string (non nul-terminated)
size_t len = strlen(event->text_event.text); size_t len = strlen(event->text_event.text);
if (len > TEXT_MAX_LENGTH) { if (len > TEXT_MAX_LENGTH) {
// injecting a text takes time, so limit the text length // injecting a text takes time, so limit the text length

@ -284,7 +284,8 @@ run_quit:
run_finally_close_input: run_finally_close_input:
avformat_close_input(&format_ctx); avformat_close_input(&format_ctx);
run_finally_free_avio_ctx: run_finally_free_avio_ctx:
av_freep(&avio_ctx); av_free(avio_ctx->buffer);
av_free(avio_ctx);
run_finally_free_format_ctx: run_finally_free_format_ctx:
avformat_free_context(format_ctx); avformat_free_context(format_ctx);
run_finally_close_codec: run_finally_close_codec:

@ -3,6 +3,7 @@
#include <SDL2/SDL_log.h> #include <SDL2/SDL_log.h>
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGD(...) SDL_LogDebug(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__) #define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGW(...) SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGW(...) SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)

@ -19,6 +19,7 @@ struct args {
Uint16 port; Uint16 port;
Uint16 max_size; Uint16 max_size;
Uint32 bit_rate; Uint32 bit_rate;
SDL_bool always_on_top;
}; };
static void usage(const char *arg0) { static void usage(const char *arg0) {
@ -65,6 +66,9 @@ static void usage(const char *arg0) {
" Enable \"show touches\" on start, disable on quit.\n" " Enable \"show touches\" on start, disable on quit.\n"
" It only shows physical touches (not clicks from scrcpy).\n" " It only shows physical touches (not clicks from scrcpy).\n"
"\n" "\n"
" -T, --always-on-top\n"
" Make scrcpy window always on top (above other windows).\n"
"\n"
" -v, --version\n" " -v, --version\n"
" Print the version of scrcpy.\n" " Print the version of scrcpy.\n"
"\n" "\n"
@ -206,20 +210,21 @@ static SDL_bool parse_port(char *optarg, Uint16 *port) {
static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = { static const struct option long_options[] = {
{"bit-rate", required_argument, NULL, 'b'}, {"always-on-top", no_argument, NULL, 'T'},
{"crop", required_argument, NULL, 'c'}, {"bit-rate", required_argument, NULL, 'b'},
{"fullscreen", no_argument, NULL, 'f'}, {"crop", required_argument, NULL, 'c'},
{"help", no_argument, NULL, 'h'}, {"fullscreen", no_argument, NULL, 'f'},
{"max-size", required_argument, NULL, 'm'}, {"help", no_argument, NULL, 'h'},
{"port", required_argument, NULL, 'p'}, {"max-size", required_argument, NULL, 'm'},
{"record", required_argument, NULL, 'r'}, {"port", required_argument, NULL, 'p'},
{"serial", required_argument, NULL, 's'}, {"record", required_argument, NULL, 'r'},
{"show-touches", no_argument, NULL, 't'}, {"serial", required_argument, NULL, 's'},
{"version", no_argument, NULL, 'v'}, {"show-touches", no_argument, NULL, 't'},
{NULL, 0, NULL, 0 }, {"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0 },
}; };
int c; int c;
while ((c = getopt_long(argc, argv, "b:c:fhm:p:r:s:tv", long_options, NULL)) != -1) { while ((c = getopt_long(argc, argv, "b:c:fhm:p:r:s:tTv", long_options, NULL)) != -1) {
switch (c) { switch (c) {
case 'b': case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) { if (!parse_bit_rate(optarg, &args->bit_rate)) {
@ -254,6 +259,9 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
case 't': case 't':
args->show_touches = SDL_TRUE; args->show_touches = SDL_TRUE;
break; break;
case 'T':
args->always_on_top = SDL_TRUE;
break;
case 'v': case 'v':
args->version = SDL_TRUE; args->version = SDL_TRUE;
break; break;
@ -288,6 +296,7 @@ int main(int argc, char *argv[]) {
.port = DEFAULT_LOCAL_PORT, .port = DEFAULT_LOCAL_PORT,
.max_size = DEFAULT_MAX_SIZE, .max_size = DEFAULT_MAX_SIZE,
.bit_rate = DEFAULT_BIT_RATE, .bit_rate = DEFAULT_BIT_RATE,
.always_on_top = SDL_FALSE,
}; };
if (!parse_args(&args, argc, argv)) { if (!parse_args(&args, argc, argv)) {
return 1; return 1;
@ -324,6 +333,7 @@ int main(int argc, char *argv[]) {
.bit_rate = args.bit_rate, .bit_rate = args.bit_rate,
.show_touches = args.show_touches, .show_touches = args.show_touches,
.fullscreen = args.fullscreen, .fullscreen = args.fullscreen,
.always_on_top = args.always_on_top,
}; };
int res = scrcpy(&options) ? 0 : 1; int res = scrcpy(&options) ? 0 : 1;

@ -5,6 +5,16 @@
#include "config.h" #include "config.h"
#include "log.h" #include "log.h"
// In ffmpeg/doc/APIchanges:
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
// Add AVStream.codecpar, deprecate AVStream.codec.
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
# define LAVF_NEW_CODEC_API
#endif
static const AVOutputFormat *find_mp4_muxer(void) { static const AVOutputFormat *find_mp4_muxer(void) {
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100) #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
void *opaque = NULL; void *opaque = NULL;
@ -30,6 +40,7 @@ SDL_bool recorder_init(struct recorder *recorder, const char *filename,
} }
recorder->declared_frame_size = declared_frame_size; recorder->declared_frame_size = declared_frame_size;
recorder->header_written = SDL_FALSE;
return SDL_TRUE; return SDL_TRUE;
} }
@ -63,13 +74,7 @@ SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) {
return SDL_FALSE; return SDL_FALSE;
} }
// In ffmpeg/doc/APIchanges: #ifdef LAVF_NEW_CODEC_API
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
// Add AVStream.codecpar, deprecate AVStream.codec.
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codecpar->codec_id = input_codec->id; ostream->codecpar->codec_id = input_codec->id;
ostream->codecpar->format = AV_PIX_FMT_YUV420P; ostream->codecpar->format = AV_PIX_FMT_YUV420P;
@ -93,14 +98,6 @@ SDL_bool recorder_open(struct recorder *recorder, AVCodec *input_codec) {
return SDL_FALSE; return SDL_FALSE;
} }
ret = avformat_write_header(recorder->ctx, NULL);
if (ret < 0) {
LOGE("Failed to write header to %s", recorder->filename);
avio_closep(&recorder->ctx->pb);
avformat_free_context(recorder->ctx);
return SDL_FALSE;
}
return SDL_TRUE; return SDL_TRUE;
} }
@ -113,6 +110,46 @@ void recorder_close(struct recorder *recorder) {
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);
} }
SDL_bool recorder_write_header(struct recorder *recorder, AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0];
uint8_t *extradata = SDL_malloc(packet->size * sizeof(uint8_t));
if (!extradata) {
LOGC("Cannot allocate extradata");
return SDL_FALSE;
}
// copy the first packet to the extra data
memcpy(extradata, packet->data, packet->size);
#ifdef LAVF_NEW_CODEC_API
ostream->codecpar->extradata = extradata;
ostream->codecpar->extradata_size = packet->size;
#else
ostream->codec->extradata = extradata;
ostream->codec->extradata_size = packet->size;
#endif
int ret = avformat_write_header(recorder->ctx, NULL);
if (ret < 0) {
LOGE("Failed to write header to %s", recorder->filename);
SDL_free(extradata);
avio_closep(&recorder->ctx->pb);
avformat_free_context(recorder->ctx);
return SDL_FALSE;
}
return SDL_TRUE;
}
SDL_bool recorder_write(struct recorder *recorder, AVPacket *packet) { SDL_bool recorder_write(struct recorder *recorder, AVPacket *packet) {
if (!recorder->header_written) {
SDL_bool ok = recorder_write_header(recorder, packet);
if (!ok) {
return SDL_FALSE;
}
recorder->header_written = SDL_TRUE;
}
return av_write_frame(recorder->ctx, packet) >= 0; return av_write_frame(recorder->ctx, packet) >= 0;
} }

@ -10,6 +10,7 @@ struct recorder {
char *filename; char *filename;
AVFormatContext *ctx; AVFormatContext *ctx;
struct size declared_frame_size; struct size declared_frame_size;
SDL_bool header_written;
}; };
SDL_bool recorder_init(struct recorder *recoder, const char *filename, SDL_bool recorder_init(struct recorder *recoder, const char *filename,

@ -139,6 +139,40 @@ static void wait_show_touches(process_t process) {
process_check_success(process, "show_touches"); process_check_success(process, "show_touches");
} }
static SDL_LogPriority sdl_priority_from_av_level(int level) {
switch (level) {
case AV_LOG_PANIC:
case AV_LOG_FATAL:
return SDL_LOG_PRIORITY_CRITICAL;
case AV_LOG_ERROR:
return SDL_LOG_PRIORITY_ERROR;
case AV_LOG_WARNING:
return SDL_LOG_PRIORITY_WARN;
case AV_LOG_INFO:
return SDL_LOG_PRIORITY_INFO;
}
// do not forward others, which are too verbose
return 0;
}
static void
av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
SDL_LogPriority priority = sdl_priority_from_av_level(level);
if (priority == 0) {
return;
}
char *local_fmt = SDL_malloc(strlen(fmt) + 10);
if (!local_fmt) {
LOGC("Cannot allocate string");
return;
}
// strcpy is safe here, the destination is large enough
strcpy(local_fmt, "[FFmpeg] ");
strcpy(local_fmt + 9, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
SDL_free(local_fmt);
}
SDL_bool scrcpy(const struct scrcpy_options *options) { SDL_bool scrcpy(const struct scrcpy_options *options) {
SDL_bool send_frame_meta = !!options->record_filename; SDL_bool send_frame_meta = !!options->record_filename;
if (!server_start(&server, options->serial, options->port, if (!server_start(&server, options->serial, options->port,
@ -203,6 +237,8 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
rec = &recorder; rec = &recorder;
} }
av_log_set_callback(av_log_callback);
decoder_init(&decoder, &frames, device_socket, rec); decoder_init(&decoder, &frames, device_socket, rec);
// now we consumed the header values, the socket receives the video stream // now we consumed the header values, the socket receives the video stream
@ -223,7 +259,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
goto finally_destroy_controller; goto finally_destroy_controller;
} }
if (!screen_init_rendering(&screen, device_name, frame_size)) { if (!screen_init_rendering(&screen, device_name, frame_size, options->always_on_top)) {
ret = SDL_FALSE; ret = SDL_FALSE;
goto finally_stop_and_join_controller; goto finally_stop_and_join_controller;
} }

@ -12,6 +12,7 @@ struct scrcpy_options {
Uint32 bit_rate; Uint32 bit_rate;
SDL_bool show_touches; SDL_bool show_touches;
SDL_bool fullscreen; SDL_bool fullscreen;
SDL_bool always_on_top;
}; };
SDL_bool scrcpy(const struct scrcpy_options *options); SDL_bool scrcpy(const struct scrcpy_options *options);

@ -144,7 +144,10 @@ static inline SDL_Texture *create_texture(SDL_Renderer *renderer, struct size fr
frame_size.width, frame_size.height); frame_size.width, frame_size.height);
} }
SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, struct size frame_size) { SDL_bool screen_init_rendering(struct screen *screen,
const char *device_name,
struct size frame_size,
SDL_bool always_on_top) {
screen->frame_size = frame_size; screen->frame_size = frame_size;
struct size window_size = get_initial_optimal_size(frame_size); struct size window_size = get_initial_optimal_size(frame_size);
@ -152,6 +155,10 @@ SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, s
#ifdef HIDPI_SUPPORT #ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
#endif #endif
if (always_on_top) {
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
}
screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
window_size.width, window_size.height, window_flags); window_size.width, window_size.height, window_flags);
if (!screen->window) { if (!screen->window) {

@ -43,7 +43,8 @@ void screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
SDL_bool screen_init_rendering(struct screen *screen, SDL_bool screen_init_rendering(struct screen *screen,
const char *device_name, const char *device_name,
struct size frame_size); struct size frame_size,
SDL_bool always_on_top);
// show the window // show the window
void screen_show_window(struct screen *screen); void screen_show_window(struct screen *screen);

@ -9,8 +9,8 @@ import java.nio.charset.StandardCharsets;
public class ControlEventReader { public class ControlEventReader {
private static final int KEYCODE_PAYLOAD_LENGTH = 9; private static final int KEYCODE_PAYLOAD_LENGTH = 9;
private static final int MOUSE_PAYLOAD_LENGTH = 13; private static final int MOUSE_PAYLOAD_LENGTH = 17;
private static final int SCROLL_PAYLOAD_LENGTH = 16; private static final int SCROLL_PAYLOAD_LENGTH = 20;
private static final int COMMAND_PAYLOAD_LENGTH = 1; private static final int COMMAND_PAYLOAD_LENGTH = 1;
public static final int TEXT_MAX_LENGTH = 300; public static final int TEXT_MAX_LENGTH = 300;