Compare commits

..

31 Commits

Author SHA1 Message Date
Romain Vimont
6093235a96 Assert screen closed on destroy
The destruction order is important, but tricky, because the screen is
open/close by the decoder, but destroyed by scrcpy.c on the main thread.

Add assertions to guarantee that the screen is not destroyed before
being closed.
2021-04-12 15:20:54 +02:00
Romain Vimont
9307cb3c96 Remove video_buffer callbacks
Now that screen is both the owner and the listener of the video buffer,
execute the code directly without callbacks.
2021-04-12 15:20:54 +02:00
Romain Vimont
350d9bfbfb Move video_buffer to screen
The video buffer is now an internal detail of the screen component.

Since the screen is plugged to the decoder via the frame sink trait, the
decoder does not access to the video buffer anymore.
2021-04-12 15:20:54 +02:00
Romain Vimont
082083b83e Make decoder push frames to sinks
Now that screen implements the packet sink trait, make decoder push
packets to the sinks without depending on the concrete sink types.
2021-04-12 15:20:54 +02:00
Romain Vimont
58c2619a6d Expose screen as frame sink
Make screen implement the frame sink trait.

This will allow the decoder to push frames without depending on the
concrete sink type.
2021-04-12 15:20:54 +02:00
Romain Vimont
6ba6ec40e3 Add frame sink trait
This trait will allow to abstract the concrete sink types from the frame
producer (decoder.c).
2021-04-12 15:20:54 +02:00
Romain Vimont
070525de06 Make stream push packets to sinks
Now that decoder and recorder implement the packet sink trait, make
stream push packets to the sinks without depending on the concrete sink
types.
2021-04-12 15:20:54 +02:00
Romain Vimont
3fb0594fe8 Expose decoder as packet sink
Make decoder implement the packet sink trait.

This will allow the stream to push packets without depending on the
concrete sink type.
2021-04-12 15:20:54 +02:00
Romain Vimont
d470943505 Reorder decoder functions
This will make further commits more readable.
2021-04-12 15:20:54 +02:00
Romain Vimont
1be76c5113 Expose recorder as packet sink
Make recorder implement the packet sink trait.

This will allow the stream to push packets without depending on the
concrete sink type.
2021-04-12 15:20:54 +02:00
Romain Vimont
6ff4e5c926 Privatize recorder threading
The fact that the recorder uses a separate thread is an internal detail,
so the functions _start(), _stop() and _join() should not be exposed.

Instead, start the thread on _open() and _stop()+_join() on close().

This paves the way to expose the recorder as a packet sink trait.
2021-04-12 15:20:54 +02:00
Romain Vimont
b2e9270e53 Reorder recorder functions
This will make further commits more readable.
2021-04-12 15:20:54 +02:00
Romain Vimont
f3879fc92d Add packet sink trait
This trait will allow to abstract the concrete sink types from the
packet producer (stream.c).
2021-04-12 15:20:54 +02:00
Romain Vimont
042ace4b4e Add container_of() macro
This will allow to get the parent of an embedded struct.
2021-04-11 15:01:05 +02:00
Romain Vimont
f43d66d5d6 Make video_buffer more generic
The video buffer took ownership of the producer frame (so that it could
swap frames quickly).

In order to support multiple sinks plugged to the decoder, the decoded
frame must not be consumed by the display video buffer.

Therefore, move the producer and consumer frames out of the video
buffer, and use FFmpeg AVFrame refcounting to share ownership while
avoiding copies.
2021-04-11 15:01:05 +02:00
Romain Vimont
e1b2824730 Remove compat with old FFmpeg codec params API
The new API has been introduced in 2016 in libavformat 57.xx, it's very
old.

This will avoid to maintain two code paths for codec parameters.
2021-04-11 15:01:05 +02:00
Romain Vimont
19b518bf24 Remove compat with old FFmpeg decoding API
The new API has been introduced in 2016 in libavcodec 57.xx, it's very
old.

This will avoid to maintain two code paths for decoding.
2021-04-11 15:01:05 +02:00
Romain Vimont
3ffea72fa6 Remove option --render-expired-frames
This flag forced the decoder to wait for the previous frame to be
consumed by the display.

It was initially implemented as a compilation flag for testing, not
intended to be exposed at runtime. But to remove ifdefs and to allow
users to test this flag easily, it had finally been exposed by commit
ebccb9f6cc.

In practice, it turned out to be useless: it had no practical impact,
and it did not solve or mitigate any performance issues causing frame
skipping.

But that added some complexity to the codebase: it required an
additional condition variable, and made video buffer calls possibly
blocking, which in turn required code to interrupt it on exit.

To prepare support for multiple sinks plugged to the decoder (display
and v4l2 for example), the blocking call used for pacing the decoder
output becomes unacceptable, so just remove this useless "feature".
2021-04-11 15:01:05 +02:00
Romain Vimont
a34bd5c43b Write trailer from recorder thread
The recorder thread wrote the whole content except the trailer, which
was odd.
2021-04-11 15:01:05 +02:00
Romain Vimont
28f6cbaea6 Destroy screen once stream is finished
The screen receives callbacks from the decoder, fed by the stream.

The decoder is run from the stream thread, so waiting for the end of
stream is sufficient to avoid possible use-after-destroy.
2021-04-11 14:42:09 +02:00
Romain Vimont
08fc6694e1 Do not destroy uninitialized screen
When --no-display was passed, screen_destroy() was called while
screen_init() was never called.

In practice, it did not crash because it just freed NULL pointers, but
it was still incorrect.
2021-04-11 13:07:44 +02:00
Romain Vimont
d0983db592 Make internal recorder function static 2021-04-10 18:48:52 +02:00
Romain Vimont
fb7870500a Remove unused field from input_manager 2021-04-10 18:48:52 +02:00
Romain Vimont
33006561c7 Remove useless forward declaration from stream.h 2021-04-10 18:48:52 +02:00
Romain Vimont
a09733d175 Remove useless includes from decoder.c 2021-04-10 18:48:44 +02:00
Romain Vimont
6231f683af Fix compilation error for old decoding API
Commits cb9c42bdcb and
441d3fb119 updated the code only for the
new decoding API.
2021-04-05 22:35:14 +02:00
Romain Vimont
9826c5c4a4 Remove HiDPI compilation flag
Always enable HiDPI support, there is no reason to expose a compilation
flag.
2021-04-04 15:00:13 +02:00
Yu-Chen Lin
1d615a0d51 Support power off on close
PR #824 <https://github.com/Genymobile/scrcpy/pull/824>

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-03-16 21:12:35 +01:00
Yu-Chen Lin
fb0bcaebc2 Export static method to power off screen in Device
PR #824 <https://github.com/Genymobile/scrcpy/pull/824>

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-03-16 21:12:29 +01:00
slingmint
dd453ad041 Pass scrcpy-noconsole arguments through to scrcpy
PR #2052 <https://github.com/Genymobile/scrcpy/pull/2052>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-03-16 21:04:04 +01:00
Romain Vimont
0308ef43f2 Do not set buttons on touch events
BUTTON_PRIMARY must not be set for touch events:

> This button constant is not set in response to simple touches with a
> finger or stylus tip. The user must actually push a button.

<https://developer.android.com/reference/android/view/MotionEvent#BUTTON_PRIMARY>

Fixes #2169 <https://github.com/Genymobile/scrcpy/issues/2169>
2021-03-15 18:46:56 +01:00
31 changed files with 650 additions and 544 deletions

View File

@@ -491,18 +491,6 @@ scrcpy -Sw
``` ```
#### Render expired frames
By default, to minimize latency, _scrcpy_ always renders the last decoded frame
available, and drops any previous one.
To force the rendering of all frames (at a cost of a possible increased
latency), use:
```bash
scrcpy --render-expired-frames
```
#### 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

View File

@@ -118,9 +118,6 @@ conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
# overridden by option --bit-rate # overridden by option --bit-rate
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
# enable High DPI support
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
# run a server debugger and wait for a client to be attached # run a server debugger and wait for a client to be attached
conf.set('SERVER_DEBUGGER', get_option('server_debugger')) conf.set('SERVER_DEBUGGER', get_option('server_debugger'))

View File

@@ -155,10 +155,6 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER .UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
.UE .UE
.TP
.B \-\-render\-expired\-frames
By default, to minimize latency, scrcpy always renders the last available decoded frame, and drops any previous ones. This flag forces to render all frames, at a cost of a possible increased latency.
.TP .TP
.BI "\-\-rotation " value .BI "\-\-rotation " value
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise. Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.

View File

@@ -143,12 +143,6 @@ scrcpy_print_usage(const char *arg0) {
" \"opengles2\", \"opengles\", \"metal\" and \"software\".\n" " \"opengles2\", \"opengles\", \"metal\" and \"software\".\n"
" <https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>\n" " <https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>\n"
"\n" "\n"
" --render-expired-frames\n"
" By default, to minimize latency, scrcpy always renders the\n"
" last available decoded frame, and drops any previous ones.\n"
" This flag forces to render all frames, at a cost of a\n"
" possible increased latency.\n"
"\n"
" --rotation value\n" " --rotation value\n"
" Set the initial display rotation.\n" " Set the initial display rotation.\n"
" Possibles values are 0, 1, 2 and 3. Each increment adds a 90\n" " Possibles values are 0, 1, 2 and 3. Each increment adds a 90\n"
@@ -666,6 +660,7 @@ guess_record_format(const char *filename) {
#define OPT_FORWARD_ALL_CLICKS 1023 #define OPT_FORWARD_ALL_CLICKS 1023
#define OPT_LEGACY_PASTE 1024 #define OPT_LEGACY_PASTE 1024
#define OPT_ENCODER_NAME 1025 #define OPT_ENCODER_NAME 1025
#define OPT_POWER_OFF_ON_CLOSE 1026
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[]) {
@@ -716,6 +711,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
{"window-borderless", no_argument, NULL, {"window-borderless", no_argument, NULL,
OPT_WINDOW_BORDERLESS}, OPT_WINDOW_BORDERLESS},
{"power-off-on-close", no_argument, NULL,
OPT_POWER_OFF_ON_CLOSE},
{NULL, 0, NULL, 0 }, {NULL, 0, NULL, 0 },
}; };
@@ -813,7 +810,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
opts->stay_awake = true; opts->stay_awake = true;
break; break;
case OPT_RENDER_EXPIRED_FRAMES: case OPT_RENDER_EXPIRED_FRAMES:
opts->render_expired_frames = true; LOGW("Option --render-expired-frames has been removed. This "
"flag has been ignored.");
break; break;
case OPT_WINDOW_TITLE: case OPT_WINDOW_TITLE:
opts->window_title = optarg; opts->window_title = optarg;
@@ -884,6 +882,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_LEGACY_PASTE: case OPT_LEGACY_PASTE:
opts->legacy_paste = true; opts->legacy_paste = true;
break; break;
case OPT_POWER_OFF_ON_CLOSE:
opts->power_off_on_close = true;
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;

View File

@@ -8,4 +8,7 @@
#define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y)
#define container_of(ptr, type, member) \
((type *) (((char *) (ptr)) - offsetof(type, member)))
#endif #endif

View File

@@ -8,20 +8,9 @@
# define _DARWIN_C_SOURCE # define _DARWIN_C_SOURCE
#endif #endif
#include <libavcodec/version.h>
#include <libavformat/version.h> #include <libavformat/version.h>
#include <SDL2/SDL_version.h> #include <SDL2/SDL_version.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 SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
#endif
// In ffmpeg/doc/APIchanges: // In ffmpeg/doc/APIchanges:
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h // 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
// Deprecate use of av_register_input_format(), av_register_output_format(), // Deprecate use of av_register_input_format(), av_register_output_format(),
@@ -33,15 +22,6 @@
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL # define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
#endif #endif
// In ffmpeg/doc/APIchanges:
// 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h
// Add a new audio/video encoding and decoding API with decoupled input
// and output -- avcodec_send_packet(), avcodec_receive_frame(),
// avcodec_send_frame() and avcodec_receive_packet().
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
# define SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
#endif
#if SDL_VERSION_ATLEAST(2, 0, 5) #if SDL_VERSION_ATLEAST(2, 0, 5)
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH> // <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH>
# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH # define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH

View File

@@ -1,22 +1,43 @@
#include "decoder.h" #include "decoder.h"
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_events.h>
#include <unistd.h>
#include "events.h" #include "events.h"
#include "recorder.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/buffer_util.h" #include "trait/frame_sink.h"
#include "util/log.h" #include "util/log.h"
void /** Downcast packet_sink to decoder */
decoder_init(struct decoder *decoder, struct video_buffer *vb) { #define DOWNCAST(SINK) container_of(SINK, struct decoder, packet_sink)
decoder->video_buffer = vb;
static void
decoder_close_first_sinks(struct decoder *decoder, unsigned count) {
while (count) {
struct sc_frame_sink *sink = decoder->sinks[--count];
sink->ops->close(sink);
}
} }
bool static inline void
decoder_close_sinks(struct decoder *decoder) {
decoder_close_first_sinks(decoder, decoder->sink_count);
}
static bool
decoder_open_sinks(struct decoder *decoder) {
for (unsigned i = 0; i < decoder->sink_count; ++i) {
struct sc_frame_sink *sink = decoder->sinks[i];
if (!sink->ops->open(sink)) {
LOGE("Could not open frame sink %d", i);
decoder_close_first_sinks(decoder, i);
return false;
}
}
return true;
}
static bool
decoder_open(struct decoder *decoder, const AVCodec *codec) { decoder_open(struct decoder *decoder, const AVCodec *codec) {
decoder->codec_ctx = avcodec_alloc_context3(codec); decoder->codec_ctx = avcodec_alloc_context3(codec);
if (!decoder->codec_ctx) { if (!decoder->codec_ctx) {
@@ -30,52 +51,105 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) {
return false; return false;
} }
return true; decoder->frame = av_frame_alloc();
if (!decoder->frame) {
LOGE("Could not create decoder frame");
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
return false;
} }
void if (!decoder_open_sinks(decoder)) {
decoder_close(struct decoder *decoder) { LOGE("Could not open decoder sinks");
av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx); avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx);
} }
bool return true;
}
static void
decoder_close(struct decoder *decoder) {
decoder_close_sinks(decoder);
av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
}
static bool
push_frame_to_sinks(struct decoder *decoder, const AVFrame *frame) {
for (unsigned i = 0; i < decoder->sink_count; ++i) {
struct sc_frame_sink *sink = decoder->sinks[i];
if (!sink->ops->push(sink, frame)) {
LOGE("Could not send frame to sink %d", i);
return false;
}
}
return true;
}
static bool
decoder_push(struct decoder *decoder, const AVPacket *packet) { decoder_push(struct decoder *decoder, const AVPacket *packet) {
// the new decoding/encoding API has been introduced by: bool is_config = packet->pts == AV_NOPTS_VALUE;
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726> if (is_config) {
#ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API // nothing to do
return true;
}
int ret; int ret;
if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) { if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) {
LOGE("Could not send video packet: %d", ret); LOGE("Could not send video packet: %d", ret);
return false; return false;
} }
ret = avcodec_receive_frame(decoder->codec_ctx, ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
decoder->video_buffer->producer_frame);
if (!ret) { if (!ret) {
// a frame was received // a frame was received
video_buffer_producer_offer_frame(decoder->video_buffer); bool ok = push_frame_to_sinks(decoder, decoder->frame);
// A frame lost should not make the whole pipeline fail. The error, if
// any, is already logged.
(void) ok;
} else if (ret != AVERROR(EAGAIN)) { } else if (ret != AVERROR(EAGAIN)) {
LOGE("Could not receive video frame: %d", ret); LOGE("Could not receive video frame: %d", ret);
return false; return false;
} }
#else
int got_picture;
int len = avcodec_decode_video2(decoder->codec_ctx,
decoder->video_buffer->decoding_frame,
&got_picture,
packet);
if (len < 0) {
LOGE("Could not decode video packet: %d", len);
return false;
}
if (got_picture) {
push_frame(decoder);
}
#endif
return true; return true;
} }
void static bool
decoder_interrupt(struct decoder *decoder) { decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
video_buffer_interrupt(decoder->video_buffer); struct decoder *decoder = DOWNCAST(sink);
return decoder_open(decoder, codec);
}
static void
decoder_packet_sink_close(struct sc_packet_sink *sink) {
struct decoder *decoder = DOWNCAST(sink);
decoder_close(decoder);
}
static bool
decoder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
struct decoder *decoder = DOWNCAST(sink);
return decoder_push(decoder, packet);
}
void
decoder_init(struct decoder *decoder) {
static const struct sc_packet_sink_ops ops = {
.open = decoder_packet_sink_open,
.close = decoder_packet_sink_close,
.push = decoder_packet_sink_push,
};
decoder->packet_sink.ops = &ops;
}
void
decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink) {
assert(decoder->sink_count < DECODER_MAX_SINKS);
assert(sink);
assert(sink->ops);
decoder->sinks[decoder->sink_count++] = sink;
} }

View File

@@ -3,30 +3,27 @@
#include "common.h" #include "common.h"
#include "trait/packet_sink.h"
#include <stdbool.h> #include <stdbool.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
struct video_buffer; #define DECODER_MAX_SINKS 1
struct decoder { struct decoder {
struct video_buffer *video_buffer; struct sc_packet_sink packet_sink; // packet sink trait
struct sc_frame_sink *sinks[DECODER_MAX_SINKS];
unsigned sink_count;
AVCodecContext *codec_ctx; AVCodecContext *codec_ctx;
AVFrame *frame;
}; };
void void
decoder_init(struct decoder *decoder, struct video_buffer *vb); decoder_init(struct decoder *decoder);
bool
decoder_open(struct decoder *decoder, const AVCodec *codec);
void void
decoder_close(struct decoder *decoder); decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink);
bool
decoder_push(struct decoder *decoder, const AVPacket *packet);
void
decoder_interrupt(struct decoder *decoder);
#endif #endif

View File

@@ -11,11 +11,9 @@
#include "fps_counter.h" #include "fps_counter.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "screen.h" #include "screen.h"
#include "video_buffer.h"
struct input_manager { struct input_manager {
struct controller *controller; struct controller *controller;
struct video_buffer *video_buffer;
struct fps_counter *fps_counter; struct fps_counter *fps_counter;
struct screen *screen; struct screen *screen;

View File

@@ -5,6 +5,9 @@
#include "util/log.h" #include "util/log.h"
/** Downcast packet_sink to recorder */
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
static const AVOutputFormat * static const AVOutputFormat *
@@ -57,50 +60,6 @@ recorder_queue_clear(struct recorder_queue *queue) {
} }
} }
bool
recorder_init(struct recorder *recorder,
const char *filename,
enum sc_record_format format,
struct size declared_frame_size) {
recorder->filename = strdup(filename);
if (!recorder->filename) {
LOGE("Could not strdup filename");
return false;
}
bool ok = sc_mutex_init(&recorder->mutex);
if (!ok) {
LOGC("Could not create mutex");
free(recorder->filename);
return false;
}
ok = sc_cond_init(&recorder->queue_cond);
if (!ok) {
LOGC("Could not create cond");
sc_mutex_destroy(&recorder->mutex);
free(recorder->filename);
return false;
}
queue_init(&recorder->queue);
recorder->stopped = false;
recorder->failed = false;
recorder->format = format;
recorder->declared_frame_size = declared_frame_size;
recorder->header_written = false;
recorder->previous = NULL;
return true;
}
void
recorder_destroy(struct recorder *recorder) {
sc_cond_destroy(&recorder->queue_cond);
sc_mutex_destroy(&recorder->mutex);
free(recorder->filename);
}
static const char * static const char *
recorder_get_format_name(enum sc_record_format format) { recorder_get_format_name(enum sc_record_format format) {
switch (format) { switch (format) {
@@ -110,88 +69,6 @@ recorder_get_format_name(enum sc_record_format format) {
} }
} }
bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
const char *format_name = recorder_get_format_name(recorder->format);
assert(format_name);
const AVOutputFormat *format = find_muxer(format_name);
if (!format) {
LOGE("Could not find muxer");
return false;
}
recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) {
LOGE("Could not allocate output context");
return false;
}
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
// still expects a pointer-to-non-const (it has not be updated accordingly)
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
recorder->ctx->oformat = (AVOutputFormat *) format;
av_dict_set(&recorder->ctx->metadata, "comment",
"Recorded by scrcpy " SCRCPY_VERSION, 0);
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
if (!ostream) {
avformat_free_context(recorder->ctx);
return false;
}
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codecpar->codec_id = input_codec->id;
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
ostream->codecpar->width = recorder->declared_frame_size.width;
ostream->codecpar->height = recorder->declared_frame_size.height;
#else
ostream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codec->codec_id = input_codec->id;
ostream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
ostream->codec->width = recorder->declared_frame_size.width;
ostream->codec->height = recorder->declared_frame_size.height;
#endif
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
AVIO_FLAG_WRITE);
if (ret < 0) {
LOGE("Failed to open output file: %s", recorder->filename);
// ostream will be cleaned up during context cleaning
avformat_free_context(recorder->ctx);
return false;
}
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
return true;
}
void
recorder_close(struct recorder *recorder) {
if (recorder->header_written) {
int ret = av_write_trailer(recorder->ctx);
if (ret < 0) {
LOGE("Failed to write trailer to %s", recorder->filename);
recorder->failed = true;
}
} else {
// the recorded file is empty
recorder->failed = true;
}
avio_close(recorder->ctx->pb);
avformat_free_context(recorder->ctx);
if (recorder->failed) {
LOGE("Recording failed to %s", recorder->filename);
} else {
const char *format_name = recorder_get_format_name(recorder->format);
LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
}
}
static bool static bool
recorder_write_header(struct recorder *recorder, const AVPacket *packet) { recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0]; AVStream *ostream = recorder->ctx->streams[0];
@@ -205,13 +82,8 @@ recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
// copy the first packet to the extra data // copy the first packet to the extra data
memcpy(extradata, packet->data, packet->size); memcpy(extradata, packet->data, packet->size);
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
ostream->codecpar->extradata = extradata; ostream->codecpar->extradata = extradata;
ostream->codecpar->extradata_size = packet->size; 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); int ret = avformat_write_header(recorder->ctx, NULL);
if (ret < 0) { if (ret < 0) {
@@ -228,7 +100,7 @@ recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base); av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
} }
bool static bool
recorder_write(struct recorder *recorder, AVPacket *packet) { recorder_write(struct recorder *recorder, AVPacket *packet) {
if (!recorder->header_written) { if (!recorder->header_written) {
if (packet->pts != AV_NOPTS_VALUE) { if (packet->pts != AV_NOPTS_VALUE) {
@@ -317,7 +189,26 @@ run_recorder(void *data) {
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
break; break;
} }
}
if (!recorder->failed) {
if (recorder->header_written) {
int ret = av_write_trailer(recorder->ctx);
if (ret < 0) {
LOGE("Failed to write trailer to %s", recorder->filename);
recorder->failed = true;
}
} else {
// the recorded file is empty
recorder->failed = true;
}
}
if (recorder->failed) {
LOGE("Recording failed to %s", recorder->filename);
} else {
const char *format_name = recorder_get_format_name(recorder->format);
LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
} }
LOGD("Recorder thread ended"); LOGD("Recorder thread ended");
@@ -325,34 +216,80 @@ run_recorder(void *data) {
return 0; return 0;
} }
bool static bool
recorder_start(struct recorder *recorder) { recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
LOGD("Starting recorder thread"); const char *format_name = recorder_get_format_name(recorder->format);
assert(format_name);
const AVOutputFormat *format = find_muxer(format_name);
if (!format) {
LOGE("Could not find muxer");
return false;
}
recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) {
LOGE("Could not allocate output context");
return false;
}
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
// still expects a pointer-to-non-const (it has not be updated accordingly)
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
recorder->ctx->oformat = (AVOutputFormat *) format;
av_dict_set(&recorder->ctx->metadata, "comment",
"Recorded by scrcpy " SCRCPY_VERSION, 0);
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
if (!ostream) {
avformat_free_context(recorder->ctx);
return false;
}
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codecpar->codec_id = input_codec->id;
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
ostream->codecpar->width = recorder->declared_frame_size.width;
ostream->codecpar->height = recorder->declared_frame_size.height;
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
AVIO_FLAG_WRITE);
if (ret < 0) {
LOGE("Failed to open output file: %s", recorder->filename);
// ostream will be cleaned up during context cleaning
avformat_free_context(recorder->ctx);
return false;
}
LOGD("Starting recorder thread");
bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder", bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
recorder); recorder);
if (!ok) { if (!ok) {
LOGC("Could not start recorder thread"); LOGC("Could not start recorder thread");
avformat_free_context(recorder->ctx);
return false; return false;
} }
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
return true; return true;
} }
void static void
recorder_stop(struct recorder *recorder) { recorder_close(struct recorder *recorder) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
recorder->stopped = true; recorder->stopped = true;
sc_cond_signal(&recorder->queue_cond); sc_cond_signal(&recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
}
void
recorder_join(struct recorder *recorder) {
sc_thread_join(&recorder->thread, NULL); sc_thread_join(&recorder->thread, NULL);
avio_close(recorder->ctx->pb);
avformat_free_context(recorder->ctx);
} }
bool static bool
recorder_push(struct recorder *recorder, const AVPacket *packet) { recorder_push(struct recorder *recorder, const AVPacket *packet) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
assert(!recorder->stopped); assert(!recorder->stopped);
@@ -376,3 +313,73 @@ recorder_push(struct recorder *recorder, const AVPacket *packet) {
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
return true; return true;
} }
static bool
recorder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
struct recorder *recorder = DOWNCAST(sink);
return recorder_open(recorder, codec);
}
static void
recorder_packet_sink_close(struct sc_packet_sink *sink) {
struct recorder *recorder = DOWNCAST(sink);
recorder_close(recorder);
}
static bool
recorder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
struct recorder *recorder = DOWNCAST(sink);
return recorder_push(recorder, packet);
}
bool
recorder_init(struct recorder *recorder,
const char *filename,
enum sc_record_format format,
struct size declared_frame_size) {
recorder->filename = strdup(filename);
if (!recorder->filename) {
LOGE("Could not strdup filename");
return false;
}
bool ok = sc_mutex_init(&recorder->mutex);
if (!ok) {
LOGC("Could not create mutex");
free(recorder->filename);
return false;
}
ok = sc_cond_init(&recorder->queue_cond);
if (!ok) {
LOGC("Could not create cond");
sc_mutex_destroy(&recorder->mutex);
free(recorder->filename);
return false;
}
queue_init(&recorder->queue);
recorder->stopped = false;
recorder->failed = false;
recorder->format = format;
recorder->declared_frame_size = declared_frame_size;
recorder->header_written = false;
recorder->previous = NULL;
static const struct sc_packet_sink_ops ops = {
.open = recorder_packet_sink_open,
.close = recorder_packet_sink_close,
.push = recorder_packet_sink_push,
};
recorder->packet_sink.ops = &ops;
return true;
}
void
recorder_destroy(struct recorder *recorder) {
sc_cond_destroy(&recorder->queue_cond);
sc_mutex_destroy(&recorder->mutex);
free(recorder->filename);
}

View File

@@ -8,6 +8,7 @@
#include "coords.h" #include "coords.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "trait/packet_sink.h"
#include "util/queue.h" #include "util/queue.h"
#include "util/thread.h" #include "util/thread.h"
@@ -19,6 +20,8 @@ struct record_packet {
struct recorder_queue QUEUE(struct record_packet); struct recorder_queue QUEUE(struct record_packet);
struct recorder { struct recorder {
struct sc_packet_sink packet_sink; // packet sink trait
char *filename; char *filename;
enum sc_record_format format; enum sc_record_format format;
AVFormatContext *ctx; AVFormatContext *ctx;
@@ -28,7 +31,7 @@ struct recorder {
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
sc_cond queue_cond; sc_cond queue_cond;
bool stopped; // set on recorder_stop() by the stream reader bool stopped; // set on recorder_close()
bool failed; // set on packet write failure bool failed; // set on packet write failure
struct recorder_queue queue; struct recorder_queue queue;
@@ -46,22 +49,4 @@ recorder_init(struct recorder *recorder, const char *filename,
void void
recorder_destroy(struct recorder *recorder); recorder_destroy(struct recorder *recorder);
bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec);
void
recorder_close(struct recorder *recorder);
bool
recorder_start(struct recorder *recorder);
void
recorder_stop(struct recorder *recorder);
void
recorder_join(struct recorder *recorder);
bool
recorder_push(struct recorder *recorder, const AVPacket *packet);
#endif #endif

View File

@@ -25,14 +25,12 @@
#include "server.h" #include "server.h"
#include "stream.h" #include "stream.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
static struct server server; static struct server server;
static struct screen screen; static struct screen screen;
static struct fps_counter fps_counter; static struct fps_counter fps_counter;
static struct video_buffer video_buffer;
static struct stream stream; static struct stream stream;
static struct decoder decoder; static struct decoder decoder;
static struct recorder recorder; static struct recorder recorder;
@@ -41,7 +39,6 @@ static struct file_handler file_handler;
static struct input_manager input_manager = { static struct input_manager input_manager = {
.controller = &controller, .controller = &controller,
.video_buffer = &video_buffer,
.fps_counter = &fps_counter, .fps_counter = &fps_counter,
.screen = &screen, .screen = &screen,
.repeat = 0, .repeat = 0,
@@ -277,12 +274,12 @@ scrcpy(const struct scrcpy_options *options) {
bool server_started = false; bool server_started = false;
bool fps_counter_initialized = false; bool fps_counter_initialized = false;
bool video_buffer_initialized = false;
bool file_handler_initialized = false; bool file_handler_initialized = false;
bool recorder_initialized = false; bool recorder_initialized = false;
bool stream_started = false; bool stream_started = false;
bool controller_initialized = false; bool controller_initialized = false;
bool controller_started = false; bool controller_started = false;
bool screen_initialized = false;
bool record = !!options->record_filename; bool record = !!options->record_filename;
struct server_params params = { struct server_params params = {
@@ -300,6 +297,7 @@ scrcpy(const struct scrcpy_options *options) {
.codec_options = options->codec_options, .codec_options = options->codec_options,
.encoder_name = options->encoder_name, .encoder_name = options->encoder_name,
.force_adb_forward = options->force_adb_forward, .force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close,
}; };
if (!server_start(&server, options->serial, &params)) { if (!server_start(&server, options->serial, &params)) {
goto end; goto end;
@@ -333,11 +331,6 @@ scrcpy(const struct scrcpy_options *options) {
} }
fps_counter_initialized = true; fps_counter_initialized = true;
if (!video_buffer_init(&video_buffer, options->render_expired_frames)) {
goto end;
}
video_buffer_initialized = true;
if (options->control) { if (options->control) {
if (!file_handler_init(&file_handler, server.serial, if (!file_handler_init(&file_handler, server.serial,
options->push_target)) { options->push_target)) {
@@ -346,7 +339,7 @@ scrcpy(const struct scrcpy_options *options) {
file_handler_initialized = true; file_handler_initialized = true;
} }
decoder_init(&decoder, &video_buffer); decoder_init(&decoder);
dec = &decoder; dec = &decoder;
} }
@@ -364,7 +357,15 @@ scrcpy(const struct scrcpy_options *options) {
av_log_set_callback(av_log_callback); av_log_set_callback(av_log_callback);
stream_init(&stream, server.video_socket, dec, rec); stream_init(&stream, server.video_socket);
if (dec) {
stream_add_sink(&stream, &dec->packet_sink);
}
if (rec) {
stream_add_sink(&stream, &rec->packet_sink);
}
if (options->display) { if (options->display) {
if (options->control) { if (options->control) {
@@ -395,10 +396,12 @@ scrcpy(const struct scrcpy_options *options) {
.mipmaps = options->mipmaps, .mipmaps = options->mipmaps,
}; };
if (!screen_init(&screen, &video_buffer, &fps_counter, if (!screen_init(&screen, &fps_counter, &screen_params)) {
&screen_params)) {
goto end; goto end;
} }
screen_initialized = true;
decoder_add_sink(&decoder, &screen.frame_sink);
if (options->turn_screen_off) { if (options->turn_screen_off) {
struct control_msg msg; struct control_msg msg;
@@ -427,14 +430,9 @@ scrcpy(const struct scrcpy_options *options) {
ret = event_loop(options); ret = event_loop(options);
LOGD("quit..."); LOGD("quit...");
screen_destroy(&screen);
end: end:
// stop stream and controller so that they don't continue once their socket // The stream is not stopped explicitly, because it will stop by itself on
// is shutdown // end-of-stream
if (stream_started) {
stream_stop(&stream);
}
if (controller_started) { if (controller_started) {
controller_stop(&controller); controller_stop(&controller);
} }
@@ -455,6 +453,13 @@ end:
if (stream_started) { if (stream_started) {
stream_join(&stream); stream_join(&stream);
} }
// Destroy the screen only after the stream is guaranteed to be finished,
// because otherwise the screen could receive new frames after destruction
if (screen_initialized) {
screen_destroy(&screen);
}
if (controller_started) { if (controller_started) {
controller_join(&controller); controller_join(&controller);
} }
@@ -471,10 +476,6 @@ end:
file_handler_destroy(&file_handler); file_handler_destroy(&file_handler);
} }
if (video_buffer_initialized) {
video_buffer_destroy(&video_buffer);
}
if (fps_counter_initialized) { if (fps_counter_initialized) {
fps_counter_join(&fps_counter); fps_counter_join(&fps_counter);
fps_counter_destroy(&fps_counter); fps_counter_destroy(&fps_counter);

View File

@@ -72,7 +72,6 @@ struct scrcpy_options {
bool control; bool control;
bool display; bool display;
bool turn_screen_off; bool turn_screen_off;
bool render_expired_frames;
bool prefer_text; bool prefer_text;
bool window_borderless; bool window_borderless;
bool mipmaps; bool mipmaps;
@@ -82,6 +81,7 @@ struct scrcpy_options {
bool forward_key_repeat; bool forward_key_repeat;
bool forward_all_clicks; bool forward_all_clicks;
bool legacy_paste; bool legacy_paste;
bool power_off_on_close;
}; };
#define SCRCPY_OPTIONS_DEFAULT { \ #define SCRCPY_OPTIONS_DEFAULT { \
@@ -119,7 +119,6 @@ struct scrcpy_options {
.control = true, \ .control = true, \
.display = true, \ .display = true, \
.turn_screen_off = false, \ .turn_screen_off = false, \
.render_expired_frames = false, \
.prefer_text = false, \ .prefer_text = false, \
.window_borderless = false, \ .window_borderless = false, \
.mipmaps = true, \ .mipmaps = true, \
@@ -129,6 +128,7 @@ struct scrcpy_options {
.forward_key_repeat = true, \ .forward_key_repeat = true, \
.forward_all_clicks = false, \ .forward_all_clicks = false, \
.legacy_paste = false, \ .legacy_paste = false, \
.power_off_on_close = false, \
} }
bool bool

View File

@@ -13,6 +13,8 @@
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
static inline struct size static inline struct size
get_rotated_size(struct size size, int rotation) { get_rotated_size(struct size size, int rotation) {
struct size rotated_size; struct size rotated_size;
@@ -191,27 +193,6 @@ screen_update_content_rect(struct screen *screen) {
} }
} }
static void
on_frame_available(struct video_buffer *vb, void *userdata) {
(void) vb;
(void) userdata;
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
}
static void
on_frame_skipped(struct video_buffer *vb, void *userdata) {
(void) vb;
struct screen *screen = userdata;
fps_counter_add_skipped_frame(screen->fps_counter);
}
static inline SDL_Texture * static inline SDL_Texture *
create_texture(struct screen *screen) { create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer; SDL_Renderer *renderer = screen->renderer;
@@ -239,11 +220,58 @@ create_texture(struct screen *screen) {
return texture; return texture;
} }
static bool
screen_frame_sink_open(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = true;
#endif
// nothing to do, the screen is already open on the main thread
return true;
}
static void
screen_frame_sink_close(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = false;
#endif
// nothing to do, the screen lifecycle is not managed by the frame producer
}
static bool
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct screen *screen = DOWNCAST(sink);
bool previous_frame_skipped;
bool ok = video_buffer_push(&screen->vb, frame, &previous_frame_skipped);
if (!ok) {
return false;
}
if (previous_frame_skipped) {
fps_counter_add_skipped_frame(screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead
} else {
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
}
return true;
}
bool bool
screen_init(struct screen *screen, struct video_buffer *vb, screen_init(struct screen *screen, struct fps_counter *fps_counter,
struct fps_counter *fps_counter,
const struct screen_params *params) { const struct screen_params *params) {
screen->vb = vb;
screen->fps_counter = fps_counter; screen->fps_counter = fps_counter;
screen->resize_pending = false; screen->resize_pending = false;
@@ -251,11 +279,11 @@ screen_init(struct screen *screen, struct video_buffer *vb,
screen->fullscreen = false; screen->fullscreen = false;
screen->maximized = false; screen->maximized = false;
static const struct video_buffer_callbacks cbs = { bool ok = video_buffer_init(&screen->vb);
.on_frame_available = on_frame_available, if (!ok) {
.on_frame_skipped = on_frame_skipped, LOGE("Could not initialize video buffer");
}; return false;
video_buffer_set_consumer_callbacks(vb, &cbs, screen); }
screen->frame_size = params->frame_size; screen->frame_size = params->frame_size;
screen->rotation = params->rotation; screen->rotation = params->rotation;
@@ -269,10 +297,9 @@ screen_init(struct screen *screen, struct video_buffer *vb,
struct size window_size = get_initial_optimal_size(content_size, struct size window_size = get_initial_optimal_size(content_size,
params->window_width, params->window_width,
params->window_height); params->window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; uint32_t window_flags = SDL_WINDOW_HIDDEN
#ifdef HIDPI_SUPPORT | SDL_WINDOW_RESIZABLE
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; | SDL_WINDOW_ALLOW_HIGHDPI;
#endif
if (params->always_on_top) { if (params->always_on_top) {
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP #ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
@@ -302,6 +329,7 @@ screen_init(struct screen *screen, struct video_buffer *vb,
if (!screen->renderer) { if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError()); LOGC("Could not create renderer: %s", SDL_GetError());
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
video_buffer_destroy(&screen->vb);
return false; return false;
} }
@@ -353,6 +381,17 @@ screen_init(struct screen *screen, struct video_buffer *vb,
LOGC("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
SDL_DestroyRenderer(screen->renderer); SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
video_buffer_destroy(&screen->vb);
return false;
}
screen->frame = av_frame_alloc();
if (!screen->frame) {
LOGC("Could not create screen frame");
SDL_DestroyTexture(screen->texture);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
video_buffer_destroy(&screen->vb);
return false; return false;
} }
@@ -363,6 +402,18 @@ screen_init(struct screen *screen, struct video_buffer *vb,
screen_update_content_rect(screen); screen_update_content_rect(screen);
static const struct sc_frame_sink_ops ops = {
.open = screen_frame_sink_open,
.close = screen_frame_sink_close,
.push = screen_frame_sink_push,
};
screen->frame_sink.ops = &ops;
#ifndef NDEBUG
screen->open = false;
#endif
return true; return true;
} }
@@ -373,9 +424,14 @@ screen_show_window(struct screen *screen) {
void void
screen_destroy(struct screen *screen) { screen_destroy(struct screen *screen) {
#ifndef NDEBUG
assert(!screen->open);
#endif
av_frame_free(&screen->frame);
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
SDL_DestroyRenderer(screen->renderer); SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
video_buffer_destroy(&screen->vb);
} }
static void static void
@@ -480,7 +536,9 @@ update_texture(struct screen *screen, const AVFrame *frame) {
static bool static bool
screen_update_frame(struct screen *screen) { screen_update_frame(struct screen *screen) {
const AVFrame *frame = video_buffer_consumer_take_frame(screen->vb); av_frame_unref(screen->frame);
video_buffer_consume(&screen->vb, screen->frame);
AVFrame *frame = screen->frame;
fps_counter_add_rendered_frame(screen->fps_counter); fps_counter_add_rendered_frame(screen->fps_counter);

View File

@@ -9,11 +9,19 @@
#include "coords.h" #include "coords.h"
#include "opengl.h" #include "opengl.h"
#include "trait/frame_sink.h"
#include "video_buffer.h"
struct video_buffer; struct video_buffer;
struct screen { struct screen {
struct video_buffer *vb; struct sc_frame_sink frame_sink; // frame sink trait
#ifndef NDEBUG
bool open; // track the open/close state to assert correct behavior
#endif
struct video_buffer vb;
struct fps_counter *fps_counter; struct fps_counter *fps_counter;
SDL_Window *window; SDL_Window *window;
@@ -36,6 +44,8 @@ struct screen {
bool fullscreen; bool fullscreen;
bool maximized; bool maximized;
bool mipmaps; bool mipmaps;
AVFrame *frame;
}; };
struct screen_params { struct screen_params {
@@ -56,8 +66,7 @@ struct screen_params {
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
bool bool
screen_init(struct screen *screen, struct video_buffer *vb, screen_init(struct screen *screen, struct fps_counter *fps_counter,
struct fps_counter *fps_counter,
const struct screen_params *params); const struct screen_params *params);
// show the window // show the window

View File

@@ -293,6 +293,7 @@ execute_server(struct server *server, const struct server_params *params) {
params->stay_awake ? "true" : "false", params->stay_awake ? "true" : "false",
params->codec_options ? params->codec_options : "-", params->codec_options ? params->codec_options : "-",
params->encoder_name ? params->encoder_name : "-", params->encoder_name ? params->encoder_name : "-",
params->power_off_on_close ? "true" : "false",
}; };
#ifdef SERVER_DEBUGGER #ifdef SERVER_DEBUGGER
LOGI("Server debugger waiting for a client on device port " LOGI("Server debugger waiting for a client on device port "

View File

@@ -46,6 +46,7 @@ struct server_params {
bool show_touches; bool show_touches;
bool stay_awake; bool stay_awake;
bool force_adb_forward; bool force_adb_forward;
bool power_off_on_close;
}; };
// init default values // init default values

View File

@@ -66,25 +66,11 @@ notify_stopped(void) {
} }
static bool static bool
process_config_packet(struct stream *stream, AVPacket *packet) { push_packet_to_sinks(struct stream *stream, const AVPacket *packet) {
if (stream->recorder && !recorder_push(stream->recorder, packet)) { for (unsigned i = 0; i < stream->sink_count; ++i) {
LOGE("Could not send config packet to recorder"); struct sc_packet_sink *sink = stream->sinks[i];
return false; if (!sink->ops->push(sink, packet)) {
} LOGE("Could not send config packet to sink %d", i);
return true;
}
static bool
process_frame(struct stream *stream, AVPacket *packet) {
if (stream->decoder && !decoder_push(stream->decoder, packet)) {
return false;
}
if (stream->recorder) {
packet->dts = packet->pts;
if (!recorder_push(stream->recorder, packet)) {
LOGE("Could not send packet to recorder");
return false; return false;
} }
} }
@@ -111,9 +97,11 @@ stream_parse(struct stream *stream, AVPacket *packet) {
packet->flags |= AV_PKT_FLAG_KEY; packet->flags |= AV_PKT_FLAG_KEY;
} }
bool ok = process_frame(stream, packet); packet->dts = packet->pts;
bool ok = push_packet_to_sinks(stream, packet);
if (!ok) { if (!ok) {
LOGE("Could not process frame"); LOGE("Could not process packet");
return false; return false;
} }
@@ -156,7 +144,7 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
if (is_config) { if (is_config) {
// config packet // config packet
bool ok = process_config_packet(stream, packet); bool ok = push_packet_to_sinks(stream, packet);
if (!ok) { if (!ok) {
return false; return false;
} }
@@ -177,6 +165,33 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
return true; return true;
} }
static void
stream_close_first_sinks(struct stream *stream, unsigned count) {
while (count) {
struct sc_packet_sink *sink = stream->sinks[--count];
sink->ops->close(sink);
}
}
static inline void
stream_close_sinks(struct stream *stream) {
stream_close_first_sinks(stream, stream->sink_count);
}
static bool
stream_open_sinks(struct stream *stream, const AVCodec *codec) {
for (unsigned i = 0; i < stream->sink_count; ++i) {
struct sc_packet_sink *sink = stream->sinks[i];
if (!sink->ops->open(sink, codec)) {
LOGE("Could not open packet sink %d", i);
stream_close_first_sinks(stream, i);
return false;
}
}
return true;
}
static int static int
run_stream(void *data) { run_stream(void *data) {
struct stream *stream = data; struct stream *stream = data;
@@ -193,27 +208,15 @@ run_stream(void *data) {
goto end; goto end;
} }
if (stream->decoder && !decoder_open(stream->decoder, codec)) { if (!stream_open_sinks(stream, codec)) {
LOGE("Could not open decoder"); LOGE("Could not open stream sinks");
goto finally_free_codec_ctx; goto finally_free_codec_ctx;
} }
if (stream->recorder) {
if (!recorder_open(stream->recorder, codec)) {
LOGE("Could not open recorder");
goto finally_close_decoder;
}
if (!recorder_start(stream->recorder)) {
LOGE("Could not start recorder");
goto finally_close_recorder;
}
}
stream->parser = av_parser_init(AV_CODEC_ID_H264); stream->parser = av_parser_init(AV_CODEC_ID_H264);
if (!stream->parser) { if (!stream->parser) {
LOGE("Could not initialize parser"); LOGE("Could not initialize parser");
goto finally_stop_and_join_recorder; goto finally_close_sinks;
} }
// We must only pass complete frames to av_parser_parse2()! // We must only pass complete frames to av_parser_parse2()!
@@ -243,20 +246,8 @@ run_stream(void *data) {
} }
av_parser_close(stream->parser); av_parser_close(stream->parser);
finally_stop_and_join_recorder: finally_close_sinks:
if (stream->recorder) { stream_close_sinks(stream);
recorder_stop(stream->recorder);
LOGI("Finishing recording...");
recorder_join(stream->recorder);
}
finally_close_recorder:
if (stream->recorder) {
recorder_close(stream->recorder);
}
finally_close_decoder:
if (stream->decoder) {
decoder_close(stream->decoder);
}
finally_free_codec_ctx: finally_free_codec_ctx:
avcodec_free_context(&stream->codec_ctx); avcodec_free_context(&stream->codec_ctx);
end: end:
@@ -265,12 +256,18 @@ end:
} }
void void
stream_init(struct stream *stream, socket_t socket, stream_init(struct stream *stream, socket_t socket) {
struct decoder *decoder, struct recorder *recorder) {
stream->socket = socket; stream->socket = socket;
stream->decoder = decoder,
stream->recorder = recorder;
stream->has_pending = false; stream->has_pending = false;
stream->sink_count = 0;
}
void
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink) {
assert(stream->sink_count < STREAM_MAX_SINKS);
assert(sink);
assert(sink->ops);
stream->sinks[stream->sink_count++] = sink;
} }
bool bool
@@ -285,13 +282,6 @@ stream_start(struct stream *stream) {
return true; return true;
} }
void
stream_stop(struct stream *stream) {
if (stream->decoder) {
decoder_interrupt(stream->decoder);
}
}
void void
stream_join(struct stream *stream) { stream_join(struct stream *stream) {
sc_thread_join(&stream->thread, NULL); sc_thread_join(&stream->thread, NULL);

View File

@@ -8,16 +8,19 @@
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL_atomic.h> #include <SDL2/SDL_atomic.h>
#include "trait/packet_sink.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h" #include "util/thread.h"
struct video_buffer; #define STREAM_MAX_SINKS 2
struct stream { struct stream {
socket_t socket; socket_t socket;
sc_thread thread; sc_thread thread;
struct decoder *decoder;
struct recorder *recorder; struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
unsigned sink_count;
AVCodecContext *codec_ctx; AVCodecContext *codec_ctx;
AVCodecParserContext *parser; AVCodecParserContext *parser;
// successive packets may need to be concatenated, until a non-config // successive packets may need to be concatenated, until a non-config
@@ -27,15 +30,14 @@ struct stream {
}; };
void void
stream_init(struct stream *stream, socket_t socket, stream_init(struct stream *stream, socket_t socket);
struct decoder *decoder, struct recorder *recorder);
void
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink);
bool bool
stream_start(struct stream *stream); stream_start(struct stream *stream);
void
stream_stop(struct stream *stream);
void void
stream_join(struct stream *stream); stream_join(struct stream *stream);

View File

@@ -0,0 +1,24 @@
#ifndef SC_FRAME_SINK
#define SC_FRAME_SINK
#include "common.h"
#include <assert.h>
#include <stdbool.h>
/**
* Frame sink trait.
*
* Component able to receive AVFrames should implement this trait.
*/
struct sc_frame_sink {
const struct sc_frame_sink_ops *ops;
};
struct sc_frame_sink_ops {
bool (*open)(struct sc_frame_sink *sink);
void (*close)(struct sc_frame_sink *sink);
bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame);
};
#endif

View File

@@ -0,0 +1,25 @@
#ifndef SC_PACKET_SINK
#define SC_PACKET_SINK
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <libavcodec/avcodec.h>
/**
* Packet sink trait.
*
* Component able to receive AVPackets should implement this trait.
*/
struct sc_packet_sink {
const struct sc_packet_sink_ops *ops;
};
struct sc_packet_sink_ops {
bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec);
void (*close)(struct sc_packet_sink *sink);
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet);
};
#endif

View File

@@ -7,67 +7,36 @@
#include "util/log.h" #include "util/log.h"
bool bool
video_buffer_init(struct video_buffer *vb, bool wait_consumer) { video_buffer_init(struct video_buffer *vb) {
vb->producer_frame = av_frame_alloc();
if (!vb->producer_frame) {
goto error_0;
}
vb->pending_frame = av_frame_alloc(); vb->pending_frame = av_frame_alloc();
if (!vb->pending_frame) { if (!vb->pending_frame) {
goto error_1; return false;
} }
vb->consumer_frame = av_frame_alloc(); vb->tmp_frame = av_frame_alloc();
if (!vb->consumer_frame) { if (!vb->tmp_frame) {
goto error_2; av_frame_free(&vb->pending_frame);
return false;
} }
bool ok = sc_mutex_init(&vb->mutex); bool ok = sc_mutex_init(&vb->mutex);
if (!ok) { if (!ok) {
goto error_3; av_frame_free(&vb->pending_frame);
} av_frame_free(&vb->tmp_frame);
return false;
vb->wait_consumer = wait_consumer;
if (wait_consumer) {
ok = sc_cond_init(&vb->pending_frame_consumed_cond);
if (!ok) {
sc_mutex_destroy(&vb->mutex);
goto error_2;
}
// interrupted is not used if wait_consumer is disabled since offering
// a frame will never block
vb->interrupted = false;
} }
// there is initially no frame, so consider it has already been consumed // there is initially no frame, so consider it has already been consumed
vb->pending_frame_consumed = true; vb->pending_frame_consumed = true;
// The callbacks must be set by the consumer via
// video_buffer_set_consumer_callbacks()
vb->cbs = NULL;
return true; return true;
error_3:
av_frame_free(&vb->consumer_frame);
error_2:
av_frame_free(&vb->pending_frame);
error_1:
av_frame_free(&vb->producer_frame);
error_0:
return false;
} }
void void
video_buffer_destroy(struct video_buffer *vb) { video_buffer_destroy(struct video_buffer *vb) {
if (vb->wait_consumer) {
sc_cond_destroy(&vb->pending_frame_consumed_cond);
}
sc_mutex_destroy(&vb->mutex); sc_mutex_destroy(&vb->mutex);
av_frame_free(&vb->consumer_frame);
av_frame_free(&vb->pending_frame); av_frame_free(&vb->pending_frame);
av_frame_free(&vb->producer_frame); av_frame_free(&vb->tmp_frame);
} }
static inline void static inline void
@@ -77,71 +46,43 @@ swap_frames(AVFrame **lhs, AVFrame **rhs) {
*rhs = tmp; *rhs = tmp;
} }
void bool
video_buffer_set_consumer_callbacks(struct video_buffer *vb, video_buffer_push(struct video_buffer *vb, const AVFrame *frame,
const struct video_buffer_callbacks *cbs, bool *previous_frame_skipped) {
void *cbs_userdata) {
assert(!vb->cbs); // must be set only once
assert(cbs);
assert(cbs->on_frame_available);
vb->cbs = cbs;
vb->cbs_userdata = cbs_userdata;
}
void
video_buffer_producer_offer_frame(struct video_buffer *vb) {
assert(vb->cbs);
sc_mutex_lock(&vb->mutex); sc_mutex_lock(&vb->mutex);
if (vb->wait_consumer) {
// wait for the current (expired) frame to be consumed // Use a temporary frame to preserve pending_frame in case of error.
while (!vb->pending_frame_consumed && !vb->interrupted) { // tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex); int r = av_frame_ref(vb->tmp_frame, frame);
} if (r) {
LOGE("Could not ref frame: %d", r);
return false;
} }
av_frame_unref(vb->pending_frame); // Now that av_frame_ref() succeeded, we can replace the previous
swap_frames(&vb->producer_frame, &vb->pending_frame); // pending_frame
swap_frames(&vb->pending_frame, &vb->tmp_frame);
av_frame_unref(vb->tmp_frame);
bool skipped = !vb->pending_frame_consumed; if (previous_frame_skipped) {
*previous_frame_skipped = !vb->pending_frame_consumed;
}
vb->pending_frame_consumed = false; vb->pending_frame_consumed = false;
sc_mutex_unlock(&vb->mutex); sc_mutex_unlock(&vb->mutex);
if (skipped) { return true;
if (vb->cbs->on_frame_skipped)
vb->cbs->on_frame_skipped(vb, vb->cbs_userdata);
} else {
vb->cbs->on_frame_available(vb, vb->cbs_userdata);
}
} }
const AVFrame * void
video_buffer_consumer_take_frame(struct video_buffer *vb) { video_buffer_consume(struct video_buffer *vb, AVFrame *dst) {
sc_mutex_lock(&vb->mutex); sc_mutex_lock(&vb->mutex);
assert(!vb->pending_frame_consumed); assert(!vb->pending_frame_consumed);
vb->pending_frame_consumed = true; vb->pending_frame_consumed = true;
swap_frames(&vb->consumer_frame, &vb->pending_frame); av_frame_move_ref(dst, vb->pending_frame);
av_frame_unref(vb->pending_frame); // av_frame_move_ref() resets its source frame, so no need to call
// av_frame_unref()
if (vb->wait_consumer) {
// unblock video_buffer_offer_decoded_frame()
sc_cond_signal(&vb->pending_frame_consumed_cond);
}
sc_mutex_unlock(&vb->mutex); sc_mutex_unlock(&vb->mutex);
// consumer_frame is only written from this thread, no need to lock
return vb->consumer_frame;
}
void
video_buffer_interrupt(struct video_buffer *vb) {
if (vb->wait_consumer) {
sc_mutex_lock(&vb->mutex);
vb->interrupted = true;
sc_mutex_unlock(&vb->mutex);
// wake up blocking wait
sc_cond_signal(&vb->pending_frame_consumed_cond);
}
} }

View File

@@ -12,71 +12,39 @@
typedef struct AVFrame AVFrame; typedef struct AVFrame AVFrame;
/** /**
* There are 3 frames in memory: * A video buffer holds 1 pending frame, which is the last frame received from
* - one frame is held by the producer (producer_frame) * the producer (typically, the decoder).
* - one frame is held by the consumer (consumer_frame)
* - one frame is shared between the producer and the consumer (pending_frame)
* *
* The producer generates a frame into the producer_frame (it may takes time). * 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.
* *
* Once the frame is produced, it calls video_buffer_producer_offer_frame(), * The producer and the consumer typically do not live in the same thread.
* which swaps the producer and pending frames. * 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
* When the consumer is notified that a new frame is available, it calls * retrieve the pending frame from there, and that frame may have changed since
* video_buffer_consumer_take_frame() to retrieve it, which swaps the pending * the callback if producer pushed a new one in between.
* and consumer frames. The frame is valid until the next call, without
* blocking the producer.
*/ */
struct video_buffer { struct video_buffer {
AVFrame *producer_frame;
AVFrame *pending_frame; AVFrame *pending_frame;
AVFrame *consumer_frame; AVFrame *tmp_frame; // To preserve the pending frame on error
sc_mutex mutex; sc_mutex mutex;
bool wait_consumer; // never overwrite a pending frame if it is not consumed
bool interrupted;
sc_cond pending_frame_consumed_cond;
bool pending_frame_consumed; bool pending_frame_consumed;
const struct video_buffer_callbacks *cbs;
void *cbs_userdata;
};
struct video_buffer_callbacks {
// Called when a new frame can be consumed by
// video_buffer_consumer_take_frame(vb)
// This callback is mandatory (it must not be NULL).
void (*on_frame_available)(struct video_buffer *vb, void *userdata);
// Called when a pending frame has been overwritten by the producer
// This callback is optional (it may be NULL).
void (*on_frame_skipped)(struct video_buffer *vb, void *userdata);
}; };
bool bool
video_buffer_init(struct video_buffer *vb, bool wait_consumer); video_buffer_init(struct video_buffer *vb);
void void
video_buffer_destroy(struct video_buffer *vb); video_buffer_destroy(struct video_buffer *vb);
void bool
video_buffer_set_consumer_callbacks(struct video_buffer *vb, video_buffer_push(struct video_buffer *vb, const AVFrame *frame, bool *skipped);
const struct video_buffer_callbacks *cbs,
void *cbs_userdata);
// set the producer frame as ready for consuming
void void
video_buffer_producer_offer_frame(struct video_buffer *vb); video_buffer_consume(struct video_buffer *vb, AVFrame *dst);
// mark the consumer frame as consumed and return it
// the frame is valid until the next call to this function
const AVFrame *
video_buffer_consumer_take_frame(struct video_buffer *vb);
// wake up and avoid any blocking call
void
video_buffer_interrupt(struct video_buffer *vb);
#endif #endif

View File

@@ -58,7 +58,6 @@ static void test_options(void) {
"--push-target", "/sdcard/Movies", "--push-target", "/sdcard/Movies",
"--record", "file", "--record", "file",
"--record-format", "mkv", "--record-format", "mkv",
"--render-expired-frames",
"--serial", "0123456789abcdef", "--serial", "0123456789abcdef",
"--show-touches", "--show-touches",
"--turn-screen-off", "--turn-screen-off",
@@ -87,7 +86,6 @@ static void test_options(void) {
assert(!strcmp(opts->push_target, "/sdcard/Movies")); assert(!strcmp(opts->push_target, "/sdcard/Movies"));
assert(!strcmp(opts->record_filename, "file")); assert(!strcmp(opts->record_filename, "file"));
assert(opts->record_format == SC_RECORD_FORMAT_MKV); assert(opts->record_format == SC_RECORD_FORMAT_MKV);
assert(opts->render_expired_frames);
assert(!strcmp(opts->serial, "0123456789abcdef")); assert(!strcmp(opts->serial, "0123456789abcdef"));
assert(opts->show_touches); assert(opts->show_touches);
assert(opts->turn_screen_off); assert(opts->turn_screen_off);

View File

@@ -1 +1,7 @@
CreateObject("Wscript.Shell").Run "cmd /c scrcpy.exe", 0, false strCommand = "cmd /c scrcpy.exe"
For Each Arg In WScript.Arguments
strCommand = strCommand & " """ & replace(Arg, """", """""""""") & """"
Next
CreateObject("Wscript.Shell").Run strCommand, 0, false

View File

@@ -3,6 +3,5 @@ option('compile_server', type: 'boolean', value: true, description: 'Build the s
option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux') option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux')
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server') option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable') option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached') option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")') option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")')

View File

@@ -19,19 +19,21 @@ public final class CleanUp {
// not instantiable // not instantiable
} }
public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException { public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen, int displayId)
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode; throws IOException {
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen;
if (needProcess) { if (needProcess) {
startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode); startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode, powerOffScreen, displayId);
} else { } else {
// There is no additional clean up to do when scrcpy dies // There is no additional clean up to do when scrcpy dies
unlinkSelf(); unlinkSelf();
} }
} }
private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException { private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen,
int displayId) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf( String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(
restoreStayOn), String.valueOf(restoreNormalPowerMode)}; restoreStayOn), String.valueOf(restoreNormalPowerMode), String.valueOf(powerOffScreen), String.valueOf(displayId)};
ProcessBuilder builder = new ProcessBuilder(cmd); ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", SERVER_PATH); builder.environment().put("CLASSPATH", SERVER_PATH);
@@ -61,6 +63,8 @@ public final class CleanUp {
boolean disableShowTouches = Boolean.parseBoolean(args[0]); boolean disableShowTouches = Boolean.parseBoolean(args[0]);
int restoreStayOn = Integer.parseInt(args[1]); int restoreStayOn = Integer.parseInt(args[1]);
boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]); boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]);
boolean powerOffScreen = Boolean.parseBoolean(args[3]);
int displayId = Integer.parseInt(args[4]);
if (disableShowTouches || restoreStayOn != -1) { if (disableShowTouches || restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager(); ServiceManager serviceManager = new ServiceManager();
@@ -76,9 +80,12 @@ public final class CleanUp {
} }
} }
if (restoreNormalPowerMode) {
Ln.i("Restoring normal power mode");
if (Device.isScreenOn()) { if (Device.isScreenOn()) {
if (powerOffScreen) {
Ln.i("Power off screen");
Device.powerOffScreen(displayId);
} else if (restoreNormalPowerMode) {
Ln.i("Restoring normal power mode");
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL); Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
} }
} }

View File

@@ -208,6 +208,10 @@ public class Controller {
// Right-click and middle-click only work if the source is a mouse // Right-click and middle-click only work if the source is a mouse
boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0; boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0;
int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN; int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN;
if (source != InputDevice.SOURCE_MOUSE) {
// Buttons must not be set for touch events
buttons = 0;
}
MotionEvent event = MotionEvent MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source,

View File

@@ -153,13 +153,17 @@ public final class Device {
return Build.MODEL; return Build.MODEL;
} }
public static boolean supportsInputEvents(int displayId) {
return displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
}
public boolean supportsInputEvents() { public boolean supportsInputEvents() {
return supportsInputEvents; return supportsInputEvents;
} }
public boolean injectEvent(InputEvent inputEvent, int mode) { public static boolean injectEvent(InputEvent inputEvent, int mode, int displayId) {
if (!supportsInputEvents()) { if (!supportsInputEvents(displayId)) {
throw new AssertionError("Could not inject input event if !supportsInputEvents()"); return false;
} }
if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) { if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) {
@@ -169,10 +173,29 @@ public final class Device {
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode); return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode);
} }
public boolean injectEvent(InputEvent inputEvent, int mode) {
if (!supportsInputEvents()) {
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
}
return injectEvent(inputEvent, mode, displayId);
}
public static boolean injectEventOnDisplay(InputEvent event, int displayId) {
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, displayId);
}
public boolean injectEvent(InputEvent event) { public boolean injectEvent(InputEvent event) {
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
} }
public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId) {
long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
return injectEventOnDisplay(event, displayId);
}
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
long now = SystemClock.uptimeMillis(); long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
@@ -180,6 +203,10 @@ public final class Device {
return injectEvent(event); return injectEvent(event);
} }
public static boolean injectKeycode(int keyCode, int displayId) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId);
}
public boolean injectKeycode(int keyCode) { public boolean injectKeycode(int keyCode) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0); return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
} }
@@ -249,6 +276,13 @@ public final class Device {
return SurfaceControl.setDisplayPowerMode(d, mode); return SurfaceControl.setDisplayPowerMode(d, mode);
} }
public static boolean powerOffScreen(int displayId) {
if (!isScreenOn()) {
return true;
}
return injectKeycode(KeyEvent.KEYCODE_POWER, displayId);
}
/** /**
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled). * Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
*/ */

View File

@@ -17,6 +17,7 @@ public class Options {
private boolean stayAwake; private boolean stayAwake;
private String codecOptions; private String codecOptions;
private String encoderName; private String encoderName;
private boolean powerOffScreenOnClose;
public Ln.Level getLogLevel() { public Ln.Level getLogLevel() {
return logLevel; return logLevel;
@@ -129,4 +130,12 @@ public class Options {
public void setEncoderName(String encoderName) { public void setEncoderName(String encoderName) {
this.encoderName = encoderName; this.encoderName = encoderName;
} }
public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) {
this.powerOffScreenOnClose = powerOffScreenOnClose;
}
public boolean getPowerOffScreenOnClose() {
return this.powerOffScreenOnClose;
}
} }

View File

@@ -50,7 +50,7 @@ public final class Server {
} }
} }
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true); CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true, options.getPowerOffScreenOnClose(), options.getDisplayId());
boolean tunnelForward = options.isTunnelForward(); boolean tunnelForward = options.isTunnelForward();
@@ -135,7 +135,7 @@ public final class Server {
"The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
} }
final int expectedParameters = 15; final int expectedParameters = 16;
if (args.length != expectedParameters) { if (args.length != expectedParameters) {
throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters"); throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters");
} }
@@ -185,6 +185,9 @@ public final class Server {
String encoderName = "-".equals(args[14]) ? null : args[14]; String encoderName = "-".equals(args[14]) ? null : args[14];
options.setEncoderName(encoderName); options.setEncoderName(encoderName);
boolean powerOffScreenOnClose = Boolean.parseBoolean(args[15]);
options.setPowerOffScreenOnClose(powerOffScreenOnClose);
return options; return options;
} }